summaryrefslogtreecommitdiffstats
path: root/kexi
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-20 01:29:50 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-20 01:29:50 +0000
commit8362bf63dea22bbf6736609b0f49c152f975eb63 (patch)
tree0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi
downloadkoffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz
koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kexi')
-rw-r--r--kexi/3rdparty/Makefile.am6
-rw-r--r--kexi/3rdparty/README.3rdparty18
-rw-r--r--kexi/3rdparty/configure.in.in60
-rw-r--r--kexi/3rdparty/kexisql/Makefile.am1
-rw-r--r--kexi/3rdparty/kexisql/Makefile.msvc167
-rw-r--r--kexi/3rdparty/kexisql/kexisql.def40
-rw-r--r--kexi/3rdparty/kexisql/src/Makefile.am20
-rw-r--r--kexi/3rdparty/kexisql/src/attach.c311
-rw-r--r--kexi/3rdparty/kexisql/src/auth.c219
-rw-r--r--kexi/3rdparty/kexisql/src/btree.c3584
-rw-r--r--kexi/3rdparty/kexisql/src/btree.h156
-rw-r--r--kexi/3rdparty/kexisql/src/btree_rb.c1488
-rw-r--r--kexi/3rdparty/kexisql/src/build.c2156
-rw-r--r--kexi/3rdparty/kexisql/src/copy.c110
-rw-r--r--kexi/3rdparty/kexisql/src/date.c875
-rw-r--r--kexi/3rdparty/kexisql/src/delete.c393
-rw-r--r--kexi/3rdparty/kexisql/src/encode.c254
-rw-r--r--kexi/3rdparty/kexisql/src/expr.c1662
-rw-r--r--kexi/3rdparty/kexisql/src/func.c658
-rw-r--r--kexi/3rdparty/kexisql/src/hash.c356
-rw-r--r--kexi/3rdparty/kexisql/src/hash.h109
-rw-r--r--kexi/3rdparty/kexisql/src/insert.c919
-rw-r--r--kexi/3rdparty/kexisql/src/main.c1143
-rw-r--r--kexi/3rdparty/kexisql/src/opcodes.c140
-rw-r--r--kexi/3rdparty/kexisql/src/opcodes.h138
-rw-r--r--kexi/3rdparty/kexisql/src/os.c1845
-rw-r--r--kexi/3rdparty/kexisql/src/os.h191
-rw-r--r--kexi/3rdparty/kexisql/src/pager.c2220
-rw-r--r--kexi/3rdparty/kexisql/src/pager.h107
-rw-r--r--kexi/3rdparty/kexisql/src/parse.c4035
-rw-r--r--kexi/3rdparty/kexisql/src/parse.h130
-rw-r--r--kexi/3rdparty/kexisql/src/parse.out10575
-rw-r--r--kexi/3rdparty/kexisql/src/parse.y866
-rw-r--r--kexi/3rdparty/kexisql/src/patches/shell.c.patch217
-rw-r--r--kexi/3rdparty/kexisql/src/pragma.c712
-rw-r--r--kexi/3rdparty/kexisql/src/printf.c858
-rw-r--r--kexi/3rdparty/kexisql/src/random.c97
-rw-r--r--kexi/3rdparty/kexisql/src/select.c2434
-rw-r--r--kexi/3rdparty/kexisql/src/shell.c1424
-rw-r--r--kexi/3rdparty/kexisql/src/sqlite.h868
-rw-r--r--kexi/3rdparty/kexisql/src/sqliteInt.h1270
-rw-r--r--kexi/3rdparty/kexisql/src/sqliteconfig.h7
-rw-r--r--kexi/3rdparty/kexisql/src/table.c203
-rw-r--r--kexi/3rdparty/kexisql/src/tclsqlite.c1293
-rw-r--r--kexi/3rdparty/kexisql/src/threadtest.c279
-rw-r--r--kexi/3rdparty/kexisql/src/tokenize.c679
-rw-r--r--kexi/3rdparty/kexisql/src/trigger.c764
-rw-r--r--kexi/3rdparty/kexisql/src/update.c459
-rw-r--r--kexi/3rdparty/kexisql/src/util.c1135
-rw-r--r--kexi/3rdparty/kexisql/src/vacuum.c327
-rw-r--r--kexi/3rdparty/kexisql/src/vdbe.c4917
-rw-r--r--kexi/3rdparty/kexisql/src/vdbe.h112
-rw-r--r--kexi/3rdparty/kexisql/src/vdbeInt.h303
-rw-r--r--kexi/3rdparty/kexisql/src/vdbeaux.c1061
-rw-r--r--kexi/3rdparty/kexisql/src/where.c1235
-rw-r--r--kexi/3rdparty/kexisql/tool/Makefile.am1
-rw-r--r--kexi/3rdparty/kexisql/tool/diffdb.c44
-rw-r--r--kexi/3rdparty/kexisql/tool/lemon.c4117
-rw-r--r--kexi/3rdparty/kexisql/tool/lempar.c657
-rwxr-xr-xkexi/3rdparty/kexisql/tool/opcodes.sh14
-rw-r--r--kexi/3rdparty/kexisql3/Makefile.am1
-rw-r--r--kexi/3rdparty/kexisql3/Makefile.msvc233
-rw-r--r--kexi/3rdparty/kexisql3/Makefile.msvc.release233
-rw-r--r--kexi/3rdparty/kexisql3/README37
-rwxr-xr-xkexi/3rdparty/kexisql3/mkdll.sh42
-rwxr-xr-xkexi/3rdparty/kexisql3/mkso.sh27
-rwxr-xr-xkexi/3rdparty/kexisql3/publish.sh113
-rw-r--r--kexi/3rdparty/kexisql3/spec.template62
-rwxr-xr-xkexi/3rdparty/kexisql3/sqlite3.1229
-rw-r--r--kexi/3rdparty/kexisql3/sqlite3.def99
-rw-r--r--kexi/3rdparty/kexisql3/src/Makefile.am61
-rw-r--r--kexi/3rdparty/kexisql3/src/alter.c557
-rw-r--r--kexi/3rdparty/kexisql3/src/analyze.c386
-rw-r--r--kexi/3rdparty/kexisql3/src/attach.c352
-rw-r--r--kexi/3rdparty/kexisql3/src/auth.c225
-rw-r--r--kexi/3rdparty/kexisql3/src/btree.c5841
-rw-r--r--kexi/3rdparty/kexisql3/src/btree.h146
-rw-r--r--kexi/3rdparty/kexisql3/src/build.c2938
-rw-r--r--kexi/3rdparty/kexisql3/src/callback.c305
-rw-r--r--kexi/3rdparty/kexisql3/src/complete.c263
-rw-r--r--kexi/3rdparty/kexisql3/src/date.c996
-rw-r--r--kexi/3rdparty/kexisql3/src/delete.c445
-rw-r--r--kexi/3rdparty/kexisql3/src/expr.c2253
-rw-r--r--kexi/3rdparty/kexisql3/src/func.c1118
-rw-r--r--kexi/3rdparty/kexisql3/src/hash.c387
-rw-r--r--kexi/3rdparty/kexisql3/src/hash.h109
-rw-r--r--kexi/3rdparty/kexisql3/src/insert.c1107
-rw-r--r--kexi/3rdparty/kexisql3/src/kexisql.h36
-rw-r--r--kexi/3rdparty/kexisql3/src/keywordhash.h97
-rwxr-xr-xkexi/3rdparty/kexisql3/src/ksqlite2to338
-rw-r--r--kexi/3rdparty/kexisql3/src/legacy.c138
-rw-r--r--kexi/3rdparty/kexisql3/src/main.c1079
-rw-r--r--kexi/3rdparty/kexisql3/src/md5.c387
-rw-r--r--kexi/3rdparty/kexisql3/src/opcodes.c140
-rw-r--r--kexi/3rdparty/kexisql3/src/opcodes.h149
-rw-r--r--kexi/3rdparty/kexisql3/src/os.h208
-rw-r--r--kexi/3rdparty/kexisql3/src/os_common.h123
-rw-r--r--kexi/3rdparty/kexisql3/src/os_mac.c740
-rw-r--r--kexi/3rdparty/kexisql3/src/os_mac.h46
-rw-r--r--kexi/3rdparty/kexisql3/src/os_unix.c1462
-rw-r--r--kexi/3rdparty/kexisql3/src/os_unix.h116
-rw-r--r--kexi/3rdparty/kexisql3/src/os_win.c1004
-rw-r--r--kexi/3rdparty/kexisql3/src/os_win.h40
-rw-r--r--kexi/3rdparty/kexisql3/src/pager.c3636
-rw-r--r--kexi/3rdparty/kexisql3/src/pager.h116
-rw-r--r--kexi/3rdparty/kexisql3/src/parse.c3449
-rw-r--r--kexi/3rdparty/kexisql3/src/parse.h145
-rw-r--r--kexi/3rdparty/kexisql3/src/parse.y915
-rw-r--r--kexi/3rdparty/kexisql3/src/patches/README18
-rw-r--r--kexi/3rdparty/kexisql3/src/patches/kexisql-3.2.8.patch664
-rwxr-xr-xkexi/3rdparty/kexisql3/src/patches/mk_patch.sh5
-rwxr-xr-xkexi/3rdparty/kexisql3/src/patches/remove_id.sh3
-rw-r--r--kexi/3rdparty/kexisql3/src/pragma.c935
-rw-r--r--kexi/3rdparty/kexisql3/src/prepare.c540
-rw-r--r--kexi/3rdparty/kexisql3/src/printf.c866
-rw-r--r--kexi/3rdparty/kexisql3/src/random.c100
-rw-r--r--kexi/3rdparty/kexisql3/src/select.c3127
-rw-r--r--kexi/3rdparty/kexisql3/src/shell.c1821
-rw-r--r--kexi/3rdparty/kexisql3/src/sqlite.h1
-rw-r--r--kexi/3rdparty/kexisql3/src/sqlite3.h1302
-rw-r--r--kexi/3rdparty/kexisql3/src/sqliteInt.h1666
-rw-r--r--kexi/3rdparty/kexisql3/src/sqliteconfig.h8
-rw-r--r--kexi/3rdparty/kexisql3/src/table.c195
-rw-r--r--kexi/3rdparty/kexisql3/src/tclsqlite.c2079
-rw-r--r--kexi/3rdparty/kexisql3/src/tokenize.c433
-rw-r--r--kexi/3rdparty/kexisql3/src/trigger.c802
-rw-r--r--kexi/3rdparty/kexisql3/src/update.c506
-rw-r--r--kexi/3rdparty/kexisql3/src/utf.c570
-rw-r--r--kexi/3rdparty/kexisql3/src/util.c1005
-rw-r--r--kexi/3rdparty/kexisql3/src/vacuum.c337
-rw-r--r--kexi/3rdparty/kexisql3/src/vdbe.c4432
-rw-r--r--kexi/3rdparty/kexisql3/src/vdbe.h131
-rw-r--r--kexi/3rdparty/kexisql3/src/vdbeInt.h377
-rw-r--r--kexi/3rdparty/kexisql3/src/vdbeapi.c737
-rw-r--r--kexi/3rdparty/kexisql3/src/vdbeaux.c1819
-rw-r--r--kexi/3rdparty/kexisql3/src/vdbefifo.c114
-rw-r--r--kexi/3rdparty/kexisql3/src/vdbemem.c840
-rw-r--r--kexi/3rdparty/kexisql3/src/where.c2052
-rw-r--r--kexi/3rdparty/kexisql3/version1
-rw-r--r--kexi/3rdparty/kolibs/KoPageLayoutColumns.cpp70
-rw-r--r--kexi/3rdparty/kolibs/KoPageLayoutColumns.h75
-rw-r--r--kexi/3rdparty/kolibs/KoPageLayoutColumnsBase.ui99
-rw-r--r--kexi/3rdparty/kolibs/KoPageLayoutHeader.cpp73
-rw-r--r--kexi/3rdparty/kolibs/KoPageLayoutHeader.h44
-rw-r--r--kexi/3rdparty/kolibs/KoPageLayoutHeaderBase.ui221
-rw-r--r--kexi/3rdparty/kolibs/KoPageLayoutSize.cpp343
-rw-r--r--kexi/3rdparty/kolibs/KoPageLayoutSize.h115
-rw-r--r--kexi/3rdparty/kolibs/Makefile.am29
-rw-r--r--kexi/3rdparty/kolibs/README13
-rw-r--r--kexi/3rdparty/kolibs/koGlobal.cc202
-rw-r--r--kexi/3rdparty/kolibs/koGlobal.h107
-rw-r--r--kexi/3rdparty/kolibs/koPageLayout.cpp244
-rw-r--r--kexi/3rdparty/kolibs/koPageLayout.h260
-rw-r--r--kexi/3rdparty/kolibs/koPageLayoutDia.cc402
-rw-r--r--kexi/3rdparty/kolibs/koPageLayoutDia.h200
-rw-r--r--kexi/3rdparty/kolibs/koUnit.cc219
-rw-r--r--kexi/3rdparty/kolibs/koUnit.h177
-rw-r--r--kexi/3rdparty/kolibs/koUnitWidgets.cc454
-rw-r--r--kexi/3rdparty/kolibs/koUnitWidgets.h246
-rw-r--r--kexi/3rdparty/kolibs/koffice_export.h2
-rw-r--r--kexi/3rdparty/uuid/ChangeLog435
-rw-r--r--kexi/3rdparty/uuid/Makefile.am14
-rw-r--r--kexi/3rdparty/uuid/clear.c20
-rw-r--r--kexi/3rdparty/uuid/compare.c32
-rw-r--r--kexi/3rdparty/uuid/configure.in10
-rw-r--r--kexi/3rdparty/uuid/copy.c22
-rw-r--r--kexi/3rdparty/uuid/gen_uuid.c280
-rw-r--r--kexi/3rdparty/uuid/gen_uuid_nt.c92
-rw-r--r--kexi/3rdparty/uuid/isnull.c25
-rw-r--r--kexi/3rdparty/uuid/libuuid.3.in70
-rw-r--r--kexi/3rdparty/uuid/pack.c46
-rw-r--r--kexi/3rdparty/uuid/parse.c56
-rw-r--r--kexi/3rdparty/uuid/tst_uuid.c147
-rw-r--r--kexi/3rdparty/uuid/unpack.c40
-rw-r--r--kexi/3rdparty/uuid/unparse.c28
-rw-r--r--kexi/3rdparty/uuid/uuid.h68
-rw-r--r--kexi/3rdparty/uuid/uuid.pro24
-rw-r--r--kexi/3rdparty/uuid/uuidP.h58
-rw-r--r--kexi/3rdparty/uuid/uuid_clear.3.in35
-rw-r--r--kexi/3rdparty/uuid/uuid_compare.3.in41
-rw-r--r--kexi/3rdparty/uuid/uuid_copy.3.in37
-rw-r--r--kexi/3rdparty/uuid/uuid_generate.3.in81
-rw-r--r--kexi/3rdparty/uuid/uuid_is_null.3.in37
-rw-r--r--kexi/3rdparty/uuid/uuid_parse.3.in50
-rw-r--r--kexi/3rdparty/uuid/uuid_time.3.in52
-rw-r--r--kexi/3rdparty/uuid/uuid_time.c138
-rw-r--r--kexi/3rdparty/uuid/uuid_types.h.in51
-rw-r--r--kexi/3rdparty/uuid/uuid_unparse.3.in40
-rw-r--r--kexi/CHANGES92
-rw-r--r--kexi/Makefile.am77
-rw-r--r--kexi/Makefile.global31
-rw-r--r--kexi/README43
-rw-r--r--kexi/chartable.txt444
-rw-r--r--kexi/configure.in.in5
-rw-r--r--kexi/core/Makefile.am41
-rw-r--r--kexi/core/kexi.cpp348
-rw-r--r--kexi/core/kexi.h147
-rw-r--r--kexi/core/kexi_global.cpp50
-rw-r--r--kexi/core/kexiaboutdata.cpp81
-rw-r--r--kexi/core/kexiaboutdata.h33
-rw-r--r--kexi/core/kexiactioncategories.cpp149
-rw-r--r--kexi/core/kexiactioncategories.h108
-rw-r--r--kexi/core/kexiactionproxy.cpp282
-rw-r--r--kexi/core/kexiactionproxy.h190
-rw-r--r--kexi/core/kexiactionproxy_p.h42
-rw-r--r--kexi/core/kexiblobbuffer.cpp373
-rw-r--r--kexi/core/kexiblobbuffer.h223
-rw-r--r--kexi/core/kexicmdlineargs.h181
-rw-r--r--kexi/core/kexicontexthelp.cpp49
-rw-r--r--kexi/core/kexicontexthelp.h41
-rw-r--r--kexi/core/kexicontexthelp_p.h35
-rw-r--r--kexi/core/kexidataiteminterface.cpp146
-rw-r--r--kexi/core/kexidataiteminterface.h249
-rw-r--r--kexi/core/kexidbconnectionset.cpp183
-rw-r--r--kexi/core/kexidbconnectionset.h77
-rw-r--r--kexi/core/kexidbshortcutfile.cpp314
-rw-r--r--kexi/core/kexidbshortcutfile.h124
-rw-r--r--kexi/core/kexidialogbase.cpp661
-rw-r--r--kexi/core/kexidialogbase.h352
-rw-r--r--kexi/core/kexidragobjects.cpp146
-rw-r--r--kexi/core/kexidragobjects.h82
-rw-r--r--kexi/core/kexievents.cpp92
-rw-r--r--kexi/core/kexievents.h100
-rw-r--r--kexi/core/kexiguimsghandler.cpp176
-rw-r--r--kexi/core/kexiguimsghandler.h62
-rw-r--r--kexi/core/kexiinternalpart.cpp209
-rw-r--r--kexi/core/kexiinternalpart.h159
-rw-r--r--kexi/core/keximainwindow.cpp36
-rw-r--r--kexi/core/keximainwindow.h189
-rw-r--r--kexi/core/kexipart.cpp452
-rw-r--r--kexi/core/kexipart.h333
-rw-r--r--kexi/core/kexipartdatasource.cpp49
-rw-r--r--kexi/core/kexipartdatasource.h72
-rw-r--r--kexi/core/kexipartguiclient.h56
-rw-r--r--kexi/core/kexipartinfo.cpp133
-rw-r--r--kexi/core/kexipartinfo.h160
-rw-r--r--kexi/core/kexipartinfo_p.h51
-rw-r--r--kexi/core/kexipartitem.cpp33
-rw-r--r--kexi/core/kexipartitem.h117
-rw-r--r--kexi/core/kexipartmanager.cpp280
-rw-r--r--kexi/core/kexipartmanager.h141
-rw-r--r--kexi/core/kexiproject.cpp1023
-rw-r--r--kexi/core/kexiproject.h334
-rw-r--r--kexi/core/kexiprojectconnectiondata.cpp152
-rw-r--r--kexi/core/kexiprojectconnectiondata.h69
-rw-r--r--kexi/core/kexiprojectdata.cpp176
-rw-r--r--kexi/core/kexiprojectdata.h108
-rw-r--r--kexi/core/kexiprojectset.cpp112
-rw-r--r--kexi/core/kexiprojectset.h67
-rw-r--r--kexi/core/kexisearchandreplaceiface.cpp41
-rw-r--r--kexi/core/kexisearchandreplaceiface.h106
-rw-r--r--kexi/core/kexisharedactionhost.cpp291
-rw-r--r--kexi/core/kexisharedactionhost.h165
-rw-r--r--kexi/core/kexisharedactionhost_p.h64
-rw-r--r--kexi/core/kexistartupdata.cpp84
-rw-r--r--kexi/core/kexistartupdata.h90
-rw-r--r--kexi/core/kexistaticpart.cpp63
-rw-r--r--kexi/core/kexistaticpart.h64
-rw-r--r--kexi/core/kexitabledesignerinterface.cpp28
-rw-r--r--kexi/core/kexitabledesignerinterface.h104
-rw-r--r--kexi/core/kexitemplateloader.cpp112
-rw-r--r--kexi/core/kexitemplateloader.h44
-rw-r--r--kexi/core/kexitextmsghandler.cpp57
-rw-r--r--kexi/core/kexitextmsghandler.h37
-rw-r--r--kexi/core/kexiuseraction.cpp108
-rw-r--r--kexi/core/kexiuseraction.h81
-rw-r--r--kexi/core/kexiuseractionmethod.cpp32
-rw-r--r--kexi/core/kexiuseractionmethod.h42
-rw-r--r--kexi/core/kexiviewbase.cpp328
-rw-r--r--kexi/core/kexiviewbase.h280
-rw-r--r--kexi/data/Makefile.am52
-rw-r--r--kexi/data/kde34compat/Makefile.am10
-rw-r--r--kexi/data/kde34compat/msaccess.magic5
-rw-r--r--kexi/data/kde34compat/sqlite.magic2
-rw-r--r--kexi/data/kde34compat/x-sqlite2.desktop53
-rw-r--r--kexi/data/kde34compat/x-sqlite3.desktop53
-rw-r--r--kexi/data/kexi.magic1
-rw-r--r--kexi/data/kexihandler.desktop77
-rw-r--r--kexi/data/kexirc5
-rw-r--r--kexi/data/kexiui.rc204
-rw-r--r--kexi/data/tips9
-rw-r--r--kexi/data/x-kexi-connectiondata.desktop45
-rw-r--r--kexi/data/x-kexiproject-shortcut.desktop54
-rw-r--r--kexi/data/x-kexiproject-sqlite.desktop50
-rw-r--r--kexi/data/x-kexiproject-sqlite2.desktop50
-rw-r--r--kexi/data/x-kexiproject-sqlite3.desktop50
-rw-r--r--kexi/debian/changelog73
-rw-r--r--kexi/debian/compat1
-rw-r--r--kexi/debian/control45
-rw-r--r--kexi/debian/copyright25
-rw-r--r--kexi/debian/debiandirs10
-rw-r--r--kexi/debian/kexi-mysql-driver.install6
-rw-r--r--kexi/debian/kexi-mysql-driver.postinst3
-rw-r--r--kexi/debian/kexi-mysql-driver.postrm4
-rw-r--r--kexi/debian/kexi-postgresql-driver.install8
-rw-r--r--kexi/debian/kexi-postgresql-driver.postinst3
-rw-r--r--kexi/debian/kexi-postgresql-driver.postrm4
-rw-r--r--kexi/debian/kexi.install124
-rw-r--r--kexi/debian/kexi.manpages5
-rw-r--r--kexi/debian/kexi.menu6
-rw-r--r--kexi/debian/kexi.postinst8
-rw-r--r--kexi/debian/kexi.postrm9
-rw-r--r--kexi/debian/kformdesigner.menu6
-rw-r--r--kexi/debian/libkexi-dev.install5
-rw-r--r--kexi/debian/libkexi-dev.postinst3
-rw-r--r--kexi/debian/libkexi-dev.postrm4
-rw-r--r--kexi/debian/man/kexi.1266
-rw-r--r--kexi/debian/man/kformdesigner.1151
-rw-r--r--kexi/debian/man/ksqlite.1230
-rw-r--r--kexi/debian/man/ksqlite2.1203
-rw-r--r--kexi/debian/man/ksqlite2to3.122
-rw-r--r--kexi/debian/overrides/kexi15
-rw-r--r--kexi/debian/overrides/kexi-postgresql-driver2
-rw-r--r--kexi/debian/rules32
-rw-r--r--kexi/doc/README7
-rw-r--r--kexi/doc/common/bottom1.pngbin0 -> 167 bytes
-rw-r--r--kexi/doc/common/bottom2.pngbin0 -> 6978 bytes
-rw-r--r--kexi/doc/common/docheadergears.pngbin0 -> 5959 bytes
-rw-r--r--kexi/doc/common/doctop1a-online.pngbin0 -> 443 bytes
-rw-r--r--kexi/doc/common/doxygen.css147
-rw-r--r--kexi/doc/common/footer.html14
-rw-r--r--kexi/doc/common/grad.pngbin0 -> 247 bytes
-rw-r--r--kexi/doc/common/header.html34
-rw-r--r--kexi/doc/common/headerbg.pngbin0 -> 492 bytes
-rw-r--r--kexi/doc/common/kde-common.css32
-rw-r--r--kexi/doc/common/kde-default.css187
-rw-r--r--kexi/doc/common/kde-web.css178
-rw-r--r--kexi/doc/common/kmenu.pngbin0 -> 844 bytes
-rw-r--r--kexi/doc/common/shadow.pngbin0 -> 212 bytes
-rw-r--r--kexi/doc/common/web-docbottom.pngbin0 -> 6761 bytes
-rwxr-xr-xkexi/doc/dev/CHANGELOG-Kexi-js5867
-rw-r--r--kexi/doc/dev/INTERESTING29
-rw-r--r--kexi/doc/dev/TODO-Kexi-js1098
-rw-r--r--kexi/doc/dev/advantages.txt17
-rw-r--r--kexi/doc/dev/alter_table_type_conversions.odsbin0 -> 13520 bytes
-rw-r--r--kexi/doc/dev/auto_update_service.txt29
-rw-r--r--kexi/doc/dev/compile_time_options.txt158
-rw-r--r--kexi/doc/dev/kexi_alter_table.txt110
-rw-r--r--kexi/doc/dev/kexi_final_mode.txt21
-rw-r--r--kexi/doc/dev/kexi_general_import_export.txt53
-rw-r--r--kexi/doc/dev/kexi_guidelines.txt35
-rw-r--r--kexi/doc/dev/kexi_i18n_guidelines.txt39
-rw-r--r--kexi/doc/dev/kexi_import.txt61
-rw-r--r--kexi/doc/dev/kexi_issues.txt30
-rw-r--r--kexi/doc/dev/kexidb_api_changes.txt125
-rw-r--r--kexi/doc/dev/kexidb_issues.txt211
-rw-r--r--kexi/doc/dev/kexidb_sql.txt36
-rw-r--r--kexi/doc/dev/kexisql_grammar_notes.txt62
-rw-r--r--kexi/doc/dev/lib_dependency.odgbin0 -> 10317 bytes
-rw-r--r--kexi/doc/dev/lib_dependency.pngbin0 -> 19560 bytes
-rw-r--r--kexi/doc/dev/mysql_bugs.txt6
-rw-r--r--kexi/doc/dev/naming_conventions.txt137
-rw-r--r--kexi/doc/dev/pgsql_issues.txt7
-rw-r--r--kexi/doc/dev/settings.txt168
-rw-r--r--kexi/doc/dev/sql_engine_specifics.txt22
-rw-r--r--kexi/doc/dev/sqlite_issues.txt200
-rw-r--r--kexi/doc/dev/tableview_issues.txt27
-rw-r--r--kexi/doc/handbook/docbook-status.txt163
-rw-r--r--kexi/doc/handbook/html.tmp/01_01_00_what_is_db.html34
-rw-r--r--kexi/doc/handbook/html.tmp/01_02_00_db_spreadsheet.html206
-rw-r--r--kexi/doc/handbook/html.tmp/01_03_00_design.html8
-rw-r--r--kexi/doc/handbook/html.tmp/01_04_00_who_needs.html24
-rw-r--r--kexi/doc/handbook/html.tmp/01_05_00_db_software.html54
-rw-r--r--kexi/doc/handbook/html.tmp/02_00_00_idx_intro_to_kexi.html7
-rw-r--r--kexi/doc/handbook/html.tmp/02_01_00_what_is_kexi.html25
-rw-r--r--kexi/doc/handbook/html.tmp/02_02_00_features_of_kexi.html15
-rw-r--r--kexi/doc/handbook/html.tmp/02_03_00_is_kexi_for_me.html12
-rw-r--r--kexi/doc/handbook/html.tmp/02_04_00_differences.html103
-rw-r--r--kexi/doc/handbook/html.tmp/04_00_00_idx_basics_kexi.html3
-rw-r--r--kexi/doc/handbook/html.tmp/04_01_00_project_files.html13
-rw-r--r--kexi/doc/handbook/html.tmp/04_02_00_running_kexi.html25
-rw-r--r--kexi/doc/handbook/html.tmp/04_03_00_creating_database.html18
-rw-r--r--kexi/doc/handbook/html.tmp/04_04_00_project_opening.html59
-rw-r--r--kexi/doc/handbook/html.tmp/04_05_00_help_on_help.html10
-rw-r--r--kexi/doc/handbook/html.tmp/04_06_00_main_application_elements.html21
-rw-r--r--kexi/doc/handbook/html.tmp/04_06_01_project_navigator.html33
-rw-r--r--kexi/doc/handbook/html.tmp/04_06_02_object_windows.html35
-rw-r--r--kexi/doc/handbook/html.tmp/04_06_03_property_editor.html44
-rw-r--r--kexi/doc/handbook/html.tmp/05_00_00_idx_building_simple_database.html18
-rw-r--r--kexi/doc/handbook/html.tmp/05_01_00_table_designing.html109
-rw-r--r--kexi/doc/handbook/html.tmp/05_02_00_entering_data_into_tables.html38
-rwxr-xr-xkexi/doc/handbook/html.tmp/05_03_00_query_designing.html37
-rw-r--r--kexi/doc/handbook/html.tmp/05_04_00_form_designing.html510
-rw-r--r--kexi/doc/handbook/html.tmp/05_05_00_data_entering_into_forms.html12
-rw-r--r--kexi/doc/handbook/html.tmp/08_00_00_kexi_tuning.html11
-rw-r--r--kexi/doc/handbook/html.tmp/08_01_00_mdi.html20
-rw-r--r--kexi/doc/handbook/html.tmp/08_02_00_dock_undock.html32
-rw-r--r--kexi/doc/handbook/html.tmp/08_03_00_conf_keys.html32
-rw-r--r--kexi/doc/handbook/translation-status.txt111
-rw-r--r--kexi/doc/kexidb/kexidb.doxygen220
-rw-r--r--kexi/doc/plan/kexi-announce.txt42
-rw-r--r--kexi/examples/Makefile.am5
-rw-r--r--kexi/examples/README29
-rw-r--r--kexi/examples/Simple_Database.kexi.sql684
-rwxr-xr-xkexi/examples/build_kexi_file.sh33
-rwxr-xr-xkexi/examples/build_kexi_files.sh9
-rwxr-xr-xkexi/examples/update_sql_files.sh21
-rw-r--r--kexi/formeditor/Makefile.am34
-rw-r--r--kexi/formeditor/TODO66
-rw-r--r--kexi/formeditor/commands.cpp1601
-rw-r--r--kexi/formeditor/commands.h380
-rw-r--r--kexi/formeditor/connectiondialog.cpp420
-rw-r--r--kexi/formeditor/connectiondialog.h114
-rw-r--r--kexi/formeditor/container.cpp1182
-rw-r--r--kexi/formeditor/container.h248
-rw-r--r--kexi/formeditor/editlistviewdialog.cpp460
-rw-r--r--kexi/formeditor/editlistviewdialog.h93
-rw-r--r--kexi/formeditor/events.cpp145
-rw-r--r--kexi/formeditor/events.h78
-rw-r--r--kexi/formeditor/factories/Makefile.am20
-rw-r--r--kexi/formeditor/factories/containerfactory.cpp936
-rw-r--r--kexi/formeditor/factories/containerfactory.h271
-rw-r--r--kexi/formeditor/factories/kformdesigner_containers.desktop53
-rw-r--r--kexi/formeditor/factories/kformdesigner_stdwidgets.desktop55
-rw-r--r--kexi/formeditor/factories/stdwidgetfactory.cpp984
-rw-r--r--kexi/formeditor/factories/stdwidgetfactory.h99
-rw-r--r--kexi/formeditor/form.cpp600
-rw-r--r--kexi/formeditor/form.h397
-rw-r--r--kexi/formeditor/formIO.cpp1626
-rw-r--r--kexi/formeditor/formIO.h222
-rw-r--r--kexi/formeditor/formmanager.cpp1716
-rw-r--r--kexi/formeditor/formmanager.h496
-rw-r--r--kexi/formeditor/kdevelop_plugin/Makefile.am21
-rw-r--r--kexi/formeditor/kdevelop_plugin/kfd_kdev_part.cpp694
-rw-r--r--kexi/formeditor/kdevelop_plugin/kfd_kdev_part.h139
-rw-r--r--kexi/formeditor/kdevelop_plugin/kformdesigner_kdev_part.desktop50
-rw-r--r--kexi/formeditor/kdevelop_plugin/kformdesigner_part.rc129
-rw-r--r--kexi/formeditor/kdevelop_plugin/kformdesigner_part_shell.rc142
-rw-r--r--kexi/formeditor/kfdpixmapedit.cpp59
-rw-r--r--kexi/formeditor/kfdpixmapedit.h44
-rw-r--r--kexi/formeditor/libactionwidget.cpp52
-rw-r--r--kexi/formeditor/libactionwidget.h60
-rw-r--r--kexi/formeditor/objecttree.cpp244
-rw-r--r--kexi/formeditor/objecttree.h184
-rw-r--r--kexi/formeditor/objecttreeview.cpp377
-rw-r--r--kexi/formeditor/objecttreeview.h129
-rw-r--r--kexi/formeditor/resizehandle.cpp339
-rw-r--r--kexi/formeditor/resizehandle.h102
-rw-r--r--kexi/formeditor/richtextdialog.cpp210
-rw-r--r--kexi/formeditor/richtextdialog.h63
-rw-r--r--kexi/formeditor/scripting/Makefile.am18
-rw-r--r--kexi/formeditor/scripting/formscript.cpp109
-rw-r--r--kexi/formeditor/scripting/formscript.h78
-rw-r--r--kexi/formeditor/scripting/scriptIO.cpp186
-rw-r--r--kexi/formeditor/scripting/scriptIO.h64
-rw-r--r--kexi/formeditor/scripting/scriptmanager.cpp70
-rw-r--r--kexi/formeditor/scripting/scriptmanager.h69
-rw-r--r--kexi/formeditor/spring.cpp158
-rw-r--r--kexi/formeditor/spring.h74
-rw-r--r--kexi/formeditor/tabstopdialog.cpp168
-rw-r--r--kexi/formeditor/tabstopdialog.h63
-rw-r--r--kexi/formeditor/test/Makefile.am46
-rw-r--r--kexi/formeditor/test/cr16-app-kformdesigner.pngbin0 -> 493 bytes
-rw-r--r--kexi/formeditor/test/cr22-app-kformdesigner.pngbin0 -> 733 bytes
-rw-r--r--kexi/formeditor/test/cr32-app-kformdesigner.pngbin0 -> 1126 bytes
-rw-r--r--kexi/formeditor/test/kfd_mainwindow.cpp88
-rw-r--r--kexi/formeditor/test/kfd_mainwindow.h46
-rw-r--r--kexi/formeditor/test/kfd_mainwindow.rc26
-rw-r--r--kexi/formeditor/test/kfd_part.cpp729
-rw-r--r--kexi/formeditor/test/kfd_part.h142
-rw-r--r--kexi/formeditor/test/kformdesigner.desktop61
-rw-r--r--kexi/formeditor/test/kformdesigner_part.desktop52
-rw-r--r--kexi/formeditor/test/kformdesigner_part.rc125
-rw-r--r--kexi/formeditor/test/kformdesigner_part_shell.rc138
-rw-r--r--kexi/formeditor/test/main.cpp81
-rw-r--r--kexi/formeditor/utils.cpp184
-rw-r--r--kexi/formeditor/utils.h113
-rw-r--r--kexi/formeditor/widgetfactory.cpp725
-rw-r--r--kexi/formeditor/widgetfactory.desktop53
-rw-r--r--kexi/formeditor/widgetfactory.h518
-rw-r--r--kexi/formeditor/widgetlibrary.cpp769
-rw-r--r--kexi/formeditor/widgetlibrary.h212
-rw-r--r--kexi/formeditor/widgetpropertyset.cpp1120
-rw-r--r--kexi/formeditor/widgetpropertyset.h206
-rw-r--r--kexi/formeditor/widgetwithsubpropertiesinterface.cpp98
-rw-r--r--kexi/formeditor/widgetwithsubpropertiesinterface.h72
-rw-r--r--kexi/kexi.desktop100
-rw-r--r--kexi/kexi_export.h192
-rw-r--r--kexi/kexi_global.h70
-rw-r--r--kexi/kexi_version.h96
-rw-r--r--kexi/kexidb/Makefile.am64
-rw-r--r--kexi/kexidb/admin.cpp42
-rw-r--r--kexi/kexidb/admin.h56
-rw-r--r--kexi/kexidb/alter.cpp1115
-rw-r--r--kexi/kexidb/alter.h468
-rw-r--r--kexi/kexidb/common.pro8
-rw-r--r--kexi/kexidb/connection.cpp3552
-rw-r--r--kexi/kexidb/connection.h1198
-rw-r--r--kexi/kexidb/connection_p.h40
-rw-r--r--kexi/kexidb/connectiondata.cpp114
-rw-r--r--kexi/kexidb/connectiondata.h239
-rw-r--r--kexi/kexidb/cursor.cpp571
-rw-r--r--kexi/kexidb/cursor.h365
-rw-r--r--kexi/kexidb/cursor_p.h40
-rw-r--r--kexi/kexidb/dbobjectnamevalidator.cpp51
-rw-r--r--kexi/kexidb/dbobjectnamevalidator.h49
-rw-r--r--kexi/kexidb/dbproperties.cpp148
-rw-r--r--kexi/kexidb/dbproperties.h67
-rw-r--r--kexi/kexidb/driver.cpp367
-rw-r--r--kexi/kexidb/driver.h375
-rw-r--r--kexi/kexidb/driver_p.cpp129
-rw-r--r--kexi/kexidb/driver_p.h262
-rw-r--r--kexi/kexidb/drivermanager.cpp435
-rw-r--r--kexi/kexidb/drivermanager.h104
-rw-r--r--kexi/kexidb/drivermanager_p.h94
-rw-r--r--kexi/kexidb/drivers/Makefile.am11
-rw-r--r--kexi/kexidb/drivers/common.pro11
-rw-r--r--kexi/kexidb/drivers/configure.in.bot99
-rw-r--r--kexi/kexidb/drivers/configure.in.in244
-rw-r--r--kexi/kexidb/drivers/drivers.pro7
-rw-r--r--kexi/kexidb/drivers/mySQL/Makefile.am33
-rw-r--r--kexi/kexidb/drivers/mySQL/kexidb_mysqldriver.desktop11
-rw-r--r--kexi/kexidb/drivers/mySQL/mySQL.pro28
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlconnection.cpp208
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlconnection.h87
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlconnection_p.cpp175
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlconnection_p.h101
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlcursor.cpp218
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlcursor.h68
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqldriver.cpp212
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqldriver.h59
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlkeywords.cpp338
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.cpp298
-rw-r--r--kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.h56
-rw-r--r--kexi/kexidb/drivers/odbc/Makefile.am21
-rw-r--r--kexi/kexidb/drivers/odbc/kexidb_odbcdriver.desktop54
-rw-r--r--kexi/kexidb/drivers/odbc/odbcconnection.cpp153
-rw-r--r--kexi/kexidb/drivers/odbc/odbcconnection.h92
-rw-r--r--kexi/kexidb/drivers/odbc/odbcdriver.cpp108
-rw-r--r--kexi/kexidb/drivers/odbc/odbcdriver.h73
-rw-r--r--kexi/kexidb/drivers/pqxx/Makefile.am22
-rw-r--r--kexi/kexidb/drivers/pqxx/README18
-rw-r--r--kexi/kexidb/drivers/pqxx/kexidb_pqxxsqldriver.desktop11
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxconnection.cpp448
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxconnection.h104
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxconnection_p.cpp51
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxconnection_p.h63
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxcursor.cpp339
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxcursor.h110
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxdriver.cpp181
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxdriver.h71
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxkeywords.cpp244
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.cpp56
-rw-r--r--kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.h49
-rw-r--r--kexi/kexidb/drivers/sqlite/Makefile.am27
-rw-r--r--kexi/kexidb/drivers/sqlite/driver/sqlite.h687
-rw-r--r--kexi/kexidb/drivers/sqlite/kexidb_sqlite3driver.desktop56
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlite.pro10
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlite_common.pro16
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteadmin.cpp64
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteadmin.h36
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitealter.cpp114
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteconnection.cpp414
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteconnection.h125
-rw-r--r--kexi/kexidb/drivers/sqlite/sqliteconnection_p.h73
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitecursor.cpp567
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitecursor.h92
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitedriver.cpp159
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitedriver.h82
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitekeywords.cpp39
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.cpp242
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.h50
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitevacuum.cpp150
-rw-r--r--kexi/kexidb/drivers/sqlite/sqlitevacuum.h70
-rw-r--r--kexi/kexidb/drivers/sqlite2/Makefile.am31
-rw-r--r--kexi/kexidb/drivers/sqlite2/kexidb_sqlite2driver.desktop57
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlite2.pro12
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteadmin.cpp1
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteadmin.h1
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitealter.cpp1
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteconnection.cpp1
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteconnection.h2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqliteconnection_p.h2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitecursor.cpp2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitecursor.h2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitedriver.cpp2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitedriver.h2
-rw-r--r--kexi/kexidb/drivers/sqlite2/sqlitepreparedstatement.cpp1
-rw-r--r--kexi/kexidb/error.h138
-rw-r--r--kexi/kexidb/expression.cpp914
-rw-r--r--kexi/kexidb/expression.h311
-rw-r--r--kexi/kexidb/field.cpp726
-rw-r--r--kexi/kexidb/field.h632
-rw-r--r--kexi/kexidb/fieldlist.cpp278
-rw-r--r--kexi/kexidb/fieldlist.h175
-rw-r--r--kexi/kexidb/fieldvalidator.cpp100
-rw-r--r--kexi/kexidb/fieldvalidator.h49
-rw-r--r--kexi/kexidb/global.cpp55
-rw-r--r--kexi/kexidb/global.h171
-rw-r--r--kexi/kexidb/indexschema.cpp199
-rw-r--r--kexi/kexidb/indexschema.h209
-rw-r--r--kexi/kexidb/kexidb.pro51
-rw-r--r--kexi/kexidb/kexidb_driver.desktop73
-rw-r--r--kexi/kexidb/kexidb_export.h61
-rw-r--r--kexi/kexidb/keywords.cpp92
-rw-r--r--kexi/kexidb/lookupfieldschema.cpp394
-rw-r--r--kexi/kexidb/lookupfieldschema.h236
-rw-r--r--kexi/kexidb/msghandler.cpp62
-rw-r--r--kexi/kexidb/msghandler.h98
-rw-r--r--kexi/kexidb/object.cpp191
-rw-r--r--kexi/kexidb/object.h186
-rw-r--r--kexi/kexidb/parser/Makefile.am38
-rw-r--r--kexi/kexidb/parser/TODO9
-rwxr-xr-xkexi/kexidb/parser/extract_tokens.sh7
-rw-r--r--kexi/kexidb/parser/parser.cpp155
-rw-r--r--kexi/kexidb/parser/parser.h240
-rw-r--r--kexi/kexidb/parser/parser_p.cpp641
-rw-r--r--kexi/kexidb/parser/parser_p.h86
-rw-r--r--kexi/kexidb/parser/sqlparser.cpp3472
-rw-r--r--kexi/kexidb/parser/sqlparser.h778
-rw-r--r--kexi/kexidb/parser/sqlparser.y1368
-rw-r--r--kexi/kexidb/parser/sqlscanner.cpp2051
-rw-r--r--kexi/kexidb/parser/sqlscanner.l318
-rw-r--r--kexi/kexidb/parser/sqltypes.h80
-rw-r--r--kexi/kexidb/parser/tokens.cpp25
-rw-r--r--kexi/kexidb/preparedstatement.cpp136
-rw-r--r--kexi/kexidb/preparedstatement.h117
-rw-r--r--kexi/kexidb/queryschema.cpp1859
-rw-r--r--kexi/kexidb/queryschema.h832
-rw-r--r--kexi/kexidb/queryschemaparameter.cpp103
-rw-r--r--kexi/kexidb/queryschemaparameter.h69
-rw-r--r--kexi/kexidb/record.h71
-rw-r--r--kexi/kexidb/relationship.cpp201
-rw-r--r--kexi/kexidb/relationship.h156
-rw-r--r--kexi/kexidb/roweditbuffer.cpp129
-rw-r--r--kexi/kexidb/roweditbuffer.h136
-rw-r--r--kexi/kexidb/schemadata.cpp55
-rw-r--r--kexi/kexidb/schemadata.h92
-rw-r--r--kexi/kexidb/simplecommandlineapp.cpp228
-rw-r--r--kexi/kexidb/simplecommandlineapp.h86
-rw-r--r--kexi/kexidb/tableschema.cpp453
-rw-r--r--kexi/kexidb/tableschema.h210
-rw-r--r--kexi/kexidb/transaction.cpp165
-rw-r--r--kexi/kexidb/transaction.h159
-rw-r--r--kexi/kexidb/utils.cpp1262
-rw-r--r--kexi/kexidb/utils.h476
-rw-r--r--kexi/kexidb/utils_p.h59
-rw-r--r--kexi/kexiutils/Makefile.am24
-rw-r--r--kexi/kexiutils/debuggui.cpp172
-rwxr-xr-xkexi/kexiutils/generate_transliteration_table.sh75
-rw-r--r--kexi/kexiutils/identifier.cpp131
-rw-r--r--kexi/kexiutils/identifier.h61
-rw-r--r--kexi/kexiutils/kexiutils_export.h33
-rw-r--r--kexi/kexiutils/longlongvalidator.cpp136
-rw-r--r--kexi/kexiutils/longlongvalidator.h73
-rw-r--r--kexi/kexiutils/styleproxy.cpp43
-rw-r--r--kexi/kexiutils/styleproxy.h175
-rw-r--r--kexi/kexiutils/transliteration_table.cpp.bz2bin0 -> 49597 bytes
-rw-r--r--kexi/kexiutils/transliteration_table.cpp.patch87
-rw-r--r--kexi/kexiutils/transliteration_table.h8
-rw-r--r--kexi/kexiutils/transliteration_table.readme54
-rw-r--r--kexi/kexiutils/tristate.h238
-rwxr-xr-xkexi/kexiutils/update_transliteration_table_patch.sh34
-rw-r--r--kexi/kexiutils/utils.cpp434
-rw-r--r--kexi/kexiutils/utils.h267
-rw-r--r--kexi/kexiutils/utils_p.h54
-rw-r--r--kexi/kexiutils/validator.cpp118
-rw-r--r--kexi/kexiutils/validator.h158
-rw-r--r--kexi/main.cpp37
-rw-r--r--kexi/main/Makefile.am35
-rw-r--r--kexi/main/configure.in.in102
-rw-r--r--kexi/main/kde2_closebutton.xpm22
-rw-r--r--kexi/main/kexifinddialog.cpp279
-rw-r--r--kexi/main/kexifinddialog.h130
-rw-r--r--kexi/main/kexifinddialogbase.ui357
-rw-r--r--kexi/main/keximainwindowimpl.cpp4641
-rw-r--r--kexi/main/keximainwindowimpl.h538
-rw-r--r--kexi/main/keximainwindowimpl_p.h599
-rw-r--r--kexi/main/kexinamedialog.cpp111
-rw-r--r--kexi/main/kexinamedialog.h61
-rw-r--r--kexi/main/kexinamewidget.cpp236
-rw-r--r--kexi/main/kexinamewidget.h142
-rw-r--r--kexi/main/kexinewstuff.cpp81
-rw-r--r--kexi/main/kexinewstuff.h41
-rw-r--r--kexi/main/kexistatusbar.cpp145
-rw-r--r--kexi/main/kexistatusbar.h77
-rw-r--r--kexi/main/ksplitter.h256
-rw-r--r--kexi/main/printing/Makefile.am32
-rw-r--r--kexi/main/printing/kexisimpleprintingengine.cpp558
-rw-r--r--kexi/main/printing/kexisimpleprintingengine.h130
-rw-r--r--kexi/main/printing/kexisimpleprintingpagesetup.cpp550
-rw-r--r--kexi/main/printing/kexisimpleprintingpagesetup.h117
-rw-r--r--kexi/main/printing/kexisimpleprintingpagesetupbase.ui447
-rw-r--r--kexi/main/printing/kexisimpleprintingpart.cpp63
-rw-r--r--kexi/main/printing/kexisimpleprintingpart.h51
-rw-r--r--kexi/main/printing/kexisimpleprintpreviewwindow.cpp381
-rw-r--r--kexi/main/printing/kexisimpleprintpreviewwindow.h83
-rw-r--r--kexi/main/printing/kexisimpleprintpreviewwindow_p.h55
-rw-r--r--kexi/main/startup/KexiConnSelector.cpp432
-rw-r--r--kexi/main/startup/KexiConnSelector.h142
-rw-r--r--kexi/main/startup/KexiConnSelectorBase.ui285
-rw-r--r--kexi/main/startup/KexiDBTitlePage.cpp35
-rw-r--r--kexi/main/startup/KexiDBTitlePage.h42
-rw-r--r--kexi/main/startup/KexiDBTitlePageBase.ui94
-rw-r--r--kexi/main/startup/KexiNewPrjTypeSelector.ui94
-rw-r--r--kexi/main/startup/KexiNewProjectWizard.cpp422
-rw-r--r--kexi/main/startup/KexiNewProjectWizard.h90
-rw-r--r--kexi/main/startup/KexiOpenExistingFile.ui127
-rw-r--r--kexi/main/startup/KexiProjectSelector.cpp297
-rw-r--r--kexi/main/startup/KexiProjectSelector.h134
-rw-r--r--kexi/main/startup/KexiProjectSelectorBase.ui128
-rw-r--r--kexi/main/startup/KexiServerDBNamePage.ui141
-rw-r--r--kexi/main/startup/KexiStartup.cpp965
-rw-r--r--kexi/main/startup/KexiStartup.h136
-rw-r--r--kexi/main/startup/KexiStartupDialog.cpp699
-rw-r--r--kexi/main/startup/KexiStartupDialog.h185
-rw-r--r--kexi/main/startup/KexiStartupDialogTemplatesPage.cpp157
-rw-r--r--kexi/main/startup/KexiStartupDialogTemplatesPage.h57
-rw-r--r--kexi/main/startup/KexiStartupFileDialog.cpp422
-rw-r--r--kexi/main/startup/KexiStartupFileDialog.h132
-rw-r--r--kexi/main/startup/KexiStartupFileDialogBase_win.h67
-rw-r--r--kexi/main/startup/KexiStartupFileDialog_win.cpp476
-rw-r--r--kexi/main/startup/KexiStartup_p.cpp127
-rw-r--r--kexi/main/startup/KexiStartup_p.h59
-rw-r--r--kexi/main/startup/Makefile.am47
-rw-r--r--kexi/migration/Makefile.am57
-rw-r--r--kexi/migration/configure.in.in7
-rw-r--r--kexi/migration/importoptionsdlg.cpp111
-rw-r--r--kexi/migration/importoptionsdlg.h51
-rw-r--r--kexi/migration/importwizard.cpp1031
-rw-r--r--kexi/migration/importwizard.h153
-rw-r--r--kexi/migration/keximigrate.cpp616
-rw-r--r--kexi/migration/keximigrate.h314
-rw-r--r--kexi/migration/keximigratedata.cpp34
-rw-r--r--kexi/migration/keximigratedata.h59
-rw-r--r--kexi/migration/keximigratetest.cpp49
-rw-r--r--kexi/migration/keximigration_driver.desktop59
-rw-r--r--kexi/migration/migratemanager.cpp384
-rw-r--r--kexi/migration/migratemanager.h82
-rw-r--r--kexi/migration/migratemanager_p.h85
-rw-r--r--kexi/migration/mysql/Makefile.am18
-rw-r--r--kexi/migration/mysql/keximigrate_mysql.desktop54
-rw-r--r--kexi/migration/mysql/mysqlmigrate.cpp522
-rw-r--r--kexi/migration/mysql/mysqlmigrate.h85
-rw-r--r--kexi/migration/pqxx/Makefile.am20
-rw-r--r--kexi/migration/pqxx/keximigrate_pqxx.desktop53
-rw-r--r--kexi/migration/pqxx/pg_type.h192
-rw-r--r--kexi/migration/pqxx/pqxxmigrate.cpp660
-rw-r--r--kexi/migration/pqxx/pqxxmigrate.h120
-rw-r--r--kexi/migration/txt/Makefile.am18
-rw-r--r--kexi/migration/txt/txtmigrate.cpp27
-rw-r--r--kexi/migration/txt/txtmigrate.h33
-rw-r--r--kexi/pics/Makefile.am34
-rwxr-xr-xkexi/pics/blendkdeicons.sh46
-rw-r--r--kexi/pics/cp-wiz.pngbin0 -> 15836 bytes
-rw-r--r--kexi/pics/cr128-action-form_action.pngbin0 -> 7670 bytes
-rw-r--r--kexi/pics/cr16-action-add_field.pngbin0 -> 455 bytes
-rw-r--r--kexi/pics/cr16-action-aofit.pngbin0 -> 583 bytes
-rw-r--r--kexi/pics/cr16-action-aogrid.pngbin0 -> 463 bytes
-rw-r--r--kexi/pics/cr16-action-aopos2grid.pngbin0 -> 497 bytes
-rw-r--r--kexi/pics/cr16-action-autofield.pngbin0 -> 357 bytes
-rw-r--r--kexi/pics/cr16-action-autonumber.pngbin0 -> 263 bytes
-rw-r--r--kexi/pics/cr16-action-business_user.pngbin0 -> 767 bytes
-rw-r--r--kexi/pics/cr16-action-button.pngbin0 -> 554 bytes
-rw-r--r--kexi/pics/cr16-action-button_no.pngbin0 -> 1165 bytes
-rw-r--r--kexi/pics/cr16-action-check.pngbin0 -> 337 bytes
-rw-r--r--kexi/pics/cr16-action-clear_table_contents.pngbin0 -> 264 bytes
-rw-r--r--kexi/pics/cr16-action-combo.pngbin0 -> 435 bytes
-rw-r--r--kexi/pics/cr16-action-database.pngbin0 -> 771 bytes
-rw-r--r--kexi/pics/cr16-action-database_import.pngbin0 -> 690 bytes
-rw-r--r--kexi/pics/cr16-action-delete_table_row.pngbin0 -> 362 bytes
-rw-r--r--kexi/pics/cr16-action-form.pngbin0 -> 493 bytes
-rw-r--r--kexi/pics/cr16-action-form_action.pngbin0 -> 777 bytes
-rw-r--r--kexi/pics/cr16-action-form_newobj.pngbin0 -> 628 bytes
-rw-r--r--kexi/pics/cr16-action-grid.pngbin0 -> 367 bytes
-rw-r--r--kexi/pics/cr16-action-insert_table_row.pngbin0 -> 522 bytes
-rw-r--r--kexi/pics/cr16-action-key.pngbin0 -> 567 bytes
-rw-r--r--kexi/pics/cr16-action-line.pngbin0 -> 191 bytes
-rw-r--r--kexi/pics/cr16-action-line_horizontal.pngbin0 -> 145 bytes
-rw-r--r--kexi/pics/cr16-action-line_vertical.pngbin0 -> 154 bytes
-rw-r--r--kexi/pics/cr16-action-lineedit.pngbin0 -> 295 bytes
-rw-r--r--kexi/pics/cr16-action-macro.pngbin0 -> 732 bytes
-rw-r--r--kexi/pics/cr16-action-macro_newobj.pngbin0 -> 780 bytes
-rw-r--r--kexi/pics/cr16-action-mouse_pointer.pngbin0 -> 482 bytes
-rw-r--r--kexi/pics/cr16-action-multiple_obj.pngbin0 -> 346 bytes
-rw-r--r--kexi/pics/cr16-action-navigator_first.pngbin0 -> 682 bytes
-rw-r--r--kexi/pics/cr16-action-navigator_last.pngbin0 -> 680 bytes
-rw-r--r--kexi/pics/cr16-action-navigator_new.pngbin0 -> 545 bytes
-rw-r--r--kexi/pics/cr16-action-navigator_next.pngbin0 -> 511 bytes
-rw-r--r--kexi/pics/cr16-action-navigator_prev.pngbin0 -> 512 bytes
-rw-r--r--kexi/pics/cr16-action-new_sign.pngbin0 -> 320 bytes
-rw-r--r--kexi/pics/cr16-action-pixmaplabel.pngbin0 -> 806 bytes
-rw-r--r--kexi/pics/cr16-action-query.pngbin0 -> 616 bytes
-rw-r--r--kexi/pics/cr16-action-query_newobj.pngbin0 -> 677 bytes
-rw-r--r--kexi/pics/cr16-action-radio.pngbin0 -> 521 bytes
-rw-r--r--kexi/pics/cr16-action-relation.pngbin0 -> 596 bytes
-rw-r--r--kexi/pics/cr16-action-report.pngbin0 -> 565 bytes
-rw-r--r--kexi/pics/cr16-action-report_newobj.pngbin0 -> 649 bytes
-rw-r--r--kexi/pics/cr16-action-script.pngbin0 -> 652 bytes
-rw-r--r--kexi/pics/cr16-action-script_newobj.pngbin0 -> 703 bytes
-rw-r--r--kexi/pics/cr16-action-select_item.pngbin0 -> 467 bytes
-rw-r--r--kexi/pics/cr16-action-sort_az.pngbin0 -> 433 bytes
-rw-r--r--kexi/pics/cr16-action-sort_za.pngbin0 -> 376 bytes
-rw-r--r--kexi/pics/cr16-action-spring.pngbin0 -> 810 bytes
-rw-r--r--kexi/pics/cr16-action-spring_vertical.pngbin0 -> 862 bytes
-rw-r--r--kexi/pics/cr16-action-state_data.pngbin0 -> 349 bytes
-rw-r--r--kexi/pics/cr16-action-state_edit.pngbin0 -> 702 bytes
-rw-r--r--kexi/pics/cr16-action-state_sql.pngbin0 -> 570 bytes
-rw-r--r--kexi/pics/cr16-action-state_text.pngbin0 -> 720 bytes
-rw-r--r--kexi/pics/cr16-action-subform.pngbin0 -> 447 bytes
-rw-r--r--kexi/pics/cr16-action-table.pngbin0 -> 524 bytes
-rw-r--r--kexi/pics/cr16-action-table_newobj.pngbin0 -> 611 bytes
-rw-r--r--kexi/pics/cr16-action-tabwidget.pngbin0 -> 456 bytes
-rw-r--r--kexi/pics/cr16-action-test_it.pngbin0 -> 538 bytes
-rw-r--r--kexi/pics/cr16-action-textedit.pngbin0 -> 414 bytes
-rw-r--r--kexi/pics/cr16-action-unknown_widget.pngbin0 -> 609 bytes
-rw-r--r--kexi/pics/cr16-action-widgets.pngbin0 -> 477 bytes
-rw-r--r--kexi/pics/cr16-mime-kexiproject_sqlite.pngbin0 -> 727 bytes
-rw-r--r--kexi/pics/cr16-mime-kexiproject_sqlite.xcfbin0 -> 2122 bytes
-rw-r--r--kexi/pics/cr16-mime-kexiproject_sqlite2.pngbin0 -> 743 bytes
-rw-r--r--kexi/pics/cr16-mime-kexiproject_sqlite2.xcfbin0 -> 2127 bytes
-rw-r--r--kexi/pics/cr22-action-alignobjs.pngbin0 -> 508 bytes
-rw-r--r--kexi/pics/cr22-action-aobottom.pngbin0 -> 429 bytes
-rw-r--r--kexi/pics/cr22-action-aofit.pngbin0 -> 687 bytes
-rw-r--r--kexi/pics/cr22-action-aogrid.pngbin0 -> 515 bytes
-rw-r--r--kexi/pics/cr22-action-aoleft.pngbin0 -> 454 bytes
-rw-r--r--kexi/pics/cr22-action-aonarrowest.pngbin0 -> 559 bytes
-rw-r--r--kexi/pics/cr22-action-aopos2grid.pngbin0 -> 503 bytes
-rw-r--r--kexi/pics/cr22-action-aoright.pngbin0 -> 453 bytes
-rw-r--r--kexi/pics/cr22-action-aoshortest.pngbin0 -> 613 bytes
-rw-r--r--kexi/pics/cr22-action-aotallest.pngbin0 -> 582 bytes
-rw-r--r--kexi/pics/cr22-action-aotop.pngbin0 -> 443 bytes
-rw-r--r--kexi/pics/cr22-action-aowidest.pngbin0 -> 541 bytes
-rw-r--r--kexi/pics/cr22-action-autofield.pngbin0 -> 483 bytes
-rw-r--r--kexi/pics/cr22-action-business_user.pngbin0 -> 1371 bytes
-rw-r--r--kexi/pics/cr22-action-button.pngbin0 -> 760 bytes
-rw-r--r--kexi/pics/cr22-action-check.pngbin0 -> 382 bytes
-rw-r--r--kexi/pics/cr22-action-clear_table_contents.pngbin0 -> 300 bytes
-rw-r--r--kexi/pics/cr22-action-combo.pngbin0 -> 526 bytes
-rw-r--r--kexi/pics/cr22-action-database.pngbin0 -> 1375 bytes
-rw-r--r--kexi/pics/cr22-action-database_import.pngbin0 -> 947 bytes
-rw-r--r--kexi/pics/cr22-action-dateedit.pngbin0 -> 1212 bytes
-rw-r--r--kexi/pics/cr22-action-datetimeedit.pngbin0 -> 1355 bytes
-rw-r--r--kexi/pics/cr22-action-delete_table_row.pngbin0 -> 411 bytes
-rw-r--r--kexi/pics/cr22-action-form.pngbin0 -> 733 bytes
-rw-r--r--kexi/pics/cr22-action-form_action.pngbin0 -> 1153 bytes
-rw-r--r--kexi/pics/cr22-action-form_edit.pngbin0 -> 920 bytes
-rw-r--r--kexi/pics/cr22-action-frame.pngbin0 -> 486 bytes
-rw-r--r--kexi/pics/cr22-action-grid.pngbin0 -> 398 bytes
-rw-r--r--kexi/pics/cr22-action-groupbox.pngbin0 -> 444 bytes
-rw-r--r--kexi/pics/cr22-action-insert_table_row.pngbin0 -> 582 bytes
-rw-r--r--kexi/pics/cr22-action-key.pngbin0 -> 685 bytes
-rw-r--r--kexi/pics/cr22-action-label.pngbin0 -> 329 bytes
-rw-r--r--kexi/pics/cr22-action-line.pngbin0 -> 236 bytes
-rw-r--r--kexi/pics/cr22-action-line_horizontal.pngbin0 -> 160 bytes
-rw-r--r--kexi/pics/cr22-action-line_vertical.pngbin0 -> 143 bytes
-rw-r--r--kexi/pics/cr22-action-lineedit.pngbin0 -> 348 bytes
-rw-r--r--kexi/pics/cr22-action-listbox.pngbin0 -> 386 bytes
-rw-r--r--kexi/pics/cr22-action-listview.pngbin0 -> 460 bytes
-rw-r--r--kexi/pics/cr22-action-lower.pngbin0 -> 429 bytes
-rw-r--r--kexi/pics/cr22-action-macro.pngbin0 -> 1128 bytes
-rw-r--r--kexi/pics/cr22-action-macro_newobj.pngbin0 -> 1237 bytes
-rw-r--r--kexi/pics/cr22-action-mouse_pointer.pngbin0 -> 521 bytes
-rw-r--r--kexi/pics/cr22-action-multiple_obj.pngbin0 -> 577 bytes
-rw-r--r--kexi/pics/cr22-action-new_sign.pngbin0 -> 522 bytes
-rw-r--r--kexi/pics/cr22-action-pixmaplabel.pngbin0 -> 1258 bytes
-rw-r--r--kexi/pics/cr22-action-progress.pngbin0 -> 514 bytes
-rw-r--r--kexi/pics/cr22-action-radio.pngbin0 -> 559 bytes
-rw-r--r--kexi/pics/cr22-action-raise.pngbin0 -> 513 bytes
-rw-r--r--kexi/pics/cr22-action-relation.pngbin0 -> 981 bytes
-rw-r--r--kexi/pics/cr22-action-signalslot.pngbin0 -> 484 bytes
-rw-r--r--kexi/pics/cr22-action-slider.pngbin0 -> 719 bytes
-rw-r--r--kexi/pics/cr22-action-sort_az.pngbin0 -> 514 bytes
-rw-r--r--kexi/pics/cr22-action-sort_za.pngbin0 -> 513 bytes
-rw-r--r--kexi/pics/cr22-action-spin.pngbin0 -> 572 bytes
-rw-r--r--kexi/pics/cr22-action-spring.pngbin0 -> 1279 bytes
-rw-r--r--kexi/pics/cr22-action-spring_vertical.pngbin0 -> 1334 bytes
-rw-r--r--kexi/pics/cr22-action-state_data.pngbin0 -> 504 bytes
-rw-r--r--kexi/pics/cr22-action-state_edit.pngbin0 -> 920 bytes
-rw-r--r--kexi/pics/cr22-action-state_sql.pngbin0 -> 864 bytes
-rw-r--r--kexi/pics/cr22-action-state_text.pngbin0 -> 1103 bytes
-rw-r--r--kexi/pics/cr22-action-subform.pngbin0 -> 560 bytes
-rw-r--r--kexi/pics/cr22-action-table.pngbin0 -> 694 bytes
-rw-r--r--kexi/pics/cr22-action-table_newobj.pngbin0 -> 943 bytes
-rw-r--r--kexi/pics/cr22-action-tabwidget.pngbin0 -> 517 bytes
-rw-r--r--kexi/pics/cr22-action-test_it.pngbin0 -> 687 bytes
-rw-r--r--kexi/pics/cr22-action-textedit.pngbin0 -> 459 bytes
-rw-r--r--kexi/pics/cr22-action-timeedit.pngbin0 -> 1100 bytes
-rw-r--r--kexi/pics/cr22-action-unknown_widget.pngbin0 -> 642 bytes
-rw-r--r--kexi/pics/cr22-action-urlrequest.pngbin0 -> 1326 bytes
-rw-r--r--kexi/pics/cr22-action-widgets.pngbin0 -> 446 bytes
-rw-r--r--kexi/pics/cr22-action-widgetstack.pngbin0 -> 433 bytes
-rw-r--r--kexi/pics/cr32-action-business_user.pngbin0 -> 1806 bytes
-rw-r--r--kexi/pics/cr32-action-clear_table_contents.pngbin0 -> 416 bytes
-rw-r--r--kexi/pics/cr32-action-database.pngbin0 -> 2205 bytes
-rw-r--r--kexi/pics/cr32-action-database_import.pngbin0 -> 1623 bytes
-rw-r--r--kexi/pics/cr32-action-delete_table_row.pngbin0 -> 571 bytes
-rw-r--r--kexi/pics/cr32-action-form.pngbin0 -> 1126 bytes
-rw-r--r--kexi/pics/cr32-action-form_action.pngbin0 -> 1699 bytes
-rw-r--r--kexi/pics/cr32-action-grid.pngbin0 -> 870 bytes
-rw-r--r--kexi/pics/cr32-action-insert_table_row.pngbin0 -> 794 bytes
-rw-r--r--kexi/pics/cr32-action-key.pngbin0 -> 1114 bytes
-rw-r--r--kexi/pics/cr32-action-macro.pngbin0 -> 2130 bytes
-rw-r--r--kexi/pics/cr32-action-macro_newobj.pngbin0 -> 2201 bytes
-rw-r--r--kexi/pics/cr32-action-new_sign.pngbin0 -> 315 bytes
-rw-r--r--kexi/pics/cr32-action-pixmaplabel.pngbin0 -> 2524 bytes
-rw-r--r--kexi/pics/cr32-action-query.pngbin0 -> 987 bytes
-rw-r--r--kexi/pics/cr32-action-spring.pngbin0 -> 2271 bytes
-rw-r--r--kexi/pics/cr32-action-state_data.pngbin0 -> 700 bytes
-rw-r--r--kexi/pics/cr32-action-state_sql.pngbin0 -> 1316 bytes
-rw-r--r--kexi/pics/cr32-action-state_text.pngbin0 -> 1883 bytes
-rw-r--r--kexi/pics/cr32-action-table.pngbin0 -> 965 bytes
-rw-r--r--kexi/pics/cr32-action-table_newobj.pngbin0 -> 1229 bytes
-rw-r--r--kexi/pics/cr32-mime-kexiproject_shortcut.pngbin0 -> 1670 bytes
-rw-r--r--kexi/pics/cr32-mime-kexiproject_shortcut.xcfbin0 -> 5422 bytes
-rw-r--r--kexi/pics/cr32-mime-kexiproject_sqlite.pngbin0 -> 1520 bytes
-rw-r--r--kexi/pics/cr32-mime-kexiproject_sqlite.xcfbin0 -> 4675 bytes
-rw-r--r--kexi/pics/cr32-mime-kexiproject_sqlite2.pngbin0 -> 1625 bytes
-rw-r--r--kexi/pics/cr32-mime-kexiproject_sqlite2.xcfbin0 -> 5266 bytes
-rw-r--r--kexi/pics/cr48-action-database_import.pngbin0 -> 3149 bytes
-rw-r--r--kexi/pics/cr48-action-form_action.pngbin0 -> 2483 bytes
-rw-r--r--kexi/pics/cr48-action-key.pngbin0 -> 1649 bytes
-rw-r--r--kexi/pics/cr64-action-business_user.pngbin0 -> 4467 bytes
-rw-r--r--kexi/pics/cr64-action-database.pngbin0 -> 5683 bytes
-rw-r--r--kexi/pics/cr64-action-form_action.pngbin0 -> 3419 bytes
-rw-r--r--kexi/pics/database-80.pngbin0 -> 7255 bytes
-rwxr-xr-xkexi/pics/generate_newobj_icons.sh6
-rw-r--r--kexi/pics/hi16-app-kexi.pngbin0 -> 960 bytes
-rw-r--r--kexi/pics/hi22-app-kexi.pngbin0 -> 1530 bytes
-rw-r--r--kexi/pics/hi32-app-kexi.pngbin0 -> 2250 bytes
-rw-r--r--kexi/pics/hi48-app-kexi.pngbin0 -> 3758 bytes
-rw-r--r--kexi/pics/hisc-app-kexi.svgzbin0 -> 7402 bytes
-rw-r--r--kexi/pics/imagebox.pngbin0 -> 5453 bytes
-rw-r--r--kexi/pics/kexi_yellow.svg603
-rw-r--r--kexi/pics/tableview_pen.pngbin0 -> 935 bytes
-rw-r--r--kexi/pics/tableview_plus.pngbin0 -> 185 bytes
-rw-r--r--kexi/pics/wiz-temlate.pngbin0 -> 7080 bytes
-rw-r--r--kexi/pics/wiz-template.pngbin0 -> 7080 bytes
-rw-r--r--kexi/plugins/Makefile.am15
-rw-r--r--kexi/plugins/Makefile.common2
-rw-r--r--kexi/plugins/configure.in.in20
-rw-r--r--kexi/plugins/configure.in.mid26
-rw-r--r--kexi/plugins/forms/Makefile.am56
-rw-r--r--kexi/plugins/forms/kexiactionselectiondialog.cpp724
-rw-r--r--kexi/plugins/forms/kexiactionselectiondialog.h71
-rw-r--r--kexi/plugins/forms/kexiactionselectiondialog_p.h51
-rw-r--r--kexi/plugins/forms/kexidataawarewidgetinfo.cpp43
-rw-r--r--kexi/plugins/forms/kexidataawarewidgetinfo.h44
-rw-r--r--kexi/plugins/forms/kexidataprovider.cpp315
-rw-r--r--kexi/plugins/forms/kexidataprovider.h95
-rw-r--r--kexi/plugins/forms/kexidatasourcepage.cpp471
-rw-r--r--kexi/plugins/forms/kexidatasourcepage.h112
-rw-r--r--kexi/plugins/forms/kexidbfactory.cpp713
-rw-r--r--kexi/plugins/forms/kexidbfactory.h74
-rw-r--r--kexi/plugins/forms/kexidbtextwidgetinterface.cpp71
-rw-r--r--kexi/plugins/forms/kexidbtextwidgetinterface.h53
-rw-r--r--kexi/plugins/forms/kexiformdataiteminterface.cpp68
-rw-r--r--kexi/plugins/forms/kexiformdataiteminterface.h145
-rw-r--r--kexi/plugins/forms/kexiformeventhandler.cpp188
-rw-r--r--kexi/plugins/forms/kexiformeventhandler.h101
-rw-r--r--kexi/plugins/forms/kexiformhandler.desktop115
-rw-r--r--kexi/plugins/forms/kexiformmanager.cpp235
-rw-r--r--kexi/plugins/forms/kexiformmanager.h87
-rw-r--r--kexi/plugins/forms/kexiformpart.cpp550
-rw-r--r--kexi/plugins/forms/kexiformpart.h108
-rw-r--r--kexi/plugins/forms/kexiformpartinstui.rc77
-rw-r--r--kexi/plugins/forms/kexiformpartui.rc10
-rw-r--r--kexi/plugins/forms/kexiforms.cpp25
-rw-r--r--kexi/plugins/forms/kexiformscrollview.cpp587
-rw-r--r--kexi/plugins/forms/kexiformscrollview.h297
-rw-r--r--kexi/plugins/forms/kexiformview.cpp1278
-rw-r--r--kexi/plugins/forms/kexiformview.h231
-rw-r--r--kexi/plugins/forms/kformdesigner_kexidbfactory.desktop55
-rw-r--r--kexi/plugins/forms/widgets/Makefile.am28
-rw-r--r--kexi/plugins/forms/widgets/kexidbautofield.cpp846
-rw-r--r--kexi/plugins/forms/widgets/kexidbautofield.h210
-rw-r--r--kexi/plugins/forms/widgets/kexidbcheckbox.cpp175
-rw-r--r--kexi/plugins/forms/widgets/kexidbcheckbox.h99
-rw-r--r--kexi/plugins/forms/widgets/kexidbcombobox.cpp550
-rw-r--r--kexi/plugins/forms/widgets/kexidbcombobox.h181
-rw-r--r--kexi/plugins/forms/widgets/kexidbdateedit.cpp230
-rw-r--r--kexi/plugins/forms/widgets/kexidbdateedit.h118
-rw-r--r--kexi/plugins/forms/widgets/kexidbdatetimeedit.cpp243
-rw-r--r--kexi/plugins/forms/widgets/kexidbdatetimeedit.h106
-rw-r--r--kexi/plugins/forms/widgets/kexidbdoublespinbox.cpp113
-rw-r--r--kexi/plugins/forms/widgets/kexidbdoublespinbox.h79
-rw-r--r--kexi/plugins/forms/widgets/kexidbform.cpp714
-rw-r--r--kexi/plugins/forms/widgets/kexidbform.h139
-rw-r--r--kexi/plugins/forms/widgets/kexidbimagebox.cpp870
-rw-r--r--kexi/plugins/forms/widgets/kexidbimagebox.h275
-rw-r--r--kexi/plugins/forms/widgets/kexidbintspinbox.cpp114
-rw-r--r--kexi/plugins/forms/widgets/kexidbintspinbox.h80
-rw-r--r--kexi/plugins/forms/widgets/kexidblabel.cpp650
-rw-r--r--kexi/plugins/forms/widgets/kexidblabel.h140
-rw-r--r--kexi/plugins/forms/widgets/kexidblineedit.cpp417
-rw-r--r--kexi/plugins/forms/widgets/kexidblineedit.h170
-rw-r--r--kexi/plugins/forms/widgets/kexidbsubform.cpp131
-rw-r--r--kexi/plugins/forms/widgets/kexidbsubform.h52
-rw-r--r--kexi/plugins/forms/widgets/kexidbtextedit.cpp209
-rw-r--r--kexi/plugins/forms/widgets/kexidbtextedit.h113
-rw-r--r--kexi/plugins/forms/widgets/kexidbtimeedit.cpp156
-rw-r--r--kexi/plugins/forms/widgets/kexidbtimeedit.h87
-rw-r--r--kexi/plugins/forms/widgets/kexidbutils.cpp99
-rw-r--r--kexi/plugins/forms/widgets/kexidbutils.h71
-rw-r--r--kexi/plugins/forms/widgets/kexiframe.cpp77
-rw-r--r--kexi/plugins/forms/widgets/kexiframe.h84
-rw-r--r--kexi/plugins/forms/widgets/kexiframeutils_p.cpp232
-rw-r--r--kexi/plugins/forms/widgets/kexipushbutton.cpp32
-rw-r--r--kexi/plugins/forms/widgets/kexipushbutton.h55
-rw-r--r--kexi/plugins/importexport/Makefile.am1
-rw-r--r--kexi/plugins/importexport/csv/Makefile.am21
-rw-r--r--kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop51
-rw-r--r--kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp87
-rw-r--r--kexi/plugins/importexport/csv/kexicsv_importexportpart.h44
-rw-r--r--kexi/plugins/importexport/csv/kexicsvexport.cpp271
-rw-r--r--kexi/plugins/importexport/csv/kexicsvexport.h58
-rw-r--r--kexi/plugins/importexport/csv/kexicsvexportwizard.cpp431
-rw-r--r--kexi/plugins/importexport/csv/kexicsvexportwizard.h113
-rw-r--r--kexi/plugins/importexport/csv/kexicsvimportdialog.cpp1662
-rw-r--r--kexi/plugins/importexport/csv/kexicsvimportdialog.h231
-rw-r--r--kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp140
-rw-r--r--kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h62
-rw-r--r--kexi/plugins/importexport/csv/kexicsvwidgets.cpp233
-rw-r--r--kexi/plugins/importexport/csv/kexicsvwidgets.h116
-rw-r--r--kexi/plugins/macros/Makefile.am9
-rw-r--r--kexi/plugins/macros/configure.in.in13
-rw-r--r--kexi/plugins/macros/kexiactions/Makefile.am27
-rw-r--r--kexi/plugins/macros/kexiactions/datatableaction.cpp185
-rw-r--r--kexi/plugins/macros/kexiactions/datatableaction.h76
-rw-r--r--kexi/plugins/macros/kexiactions/executeaction.cpp96
-rw-r--r--kexi/plugins/macros/kexiactions/executeaction.h78
-rw-r--r--kexi/plugins/macros/kexiactions/kexiaction.cpp48
-rw-r--r--kexi/plugins/macros/kexiactions/kexiaction.h75
-rw-r--r--kexi/plugins/macros/kexiactions/kexivariable.h76
-rw-r--r--kexi/plugins/macros/kexiactions/messageaction.cpp50
-rw-r--r--kexi/plugins/macros/kexiactions/messageaction.h66
-rw-r--r--kexi/plugins/macros/kexiactions/navigateaction.cpp158
-rw-r--r--kexi/plugins/macros/kexiactions/navigateaction.h78
-rw-r--r--kexi/plugins/macros/kexiactions/objectnamevariable.h76
-rw-r--r--kexi/plugins/macros/kexiactions/objectvariable.h87
-rw-r--r--kexi/plugins/macros/kexiactions/openaction.cpp154
-rw-r--r--kexi/plugins/macros/kexiactions/openaction.h79
-rw-r--r--kexi/plugins/macros/kexipart/Makefile.am32
-rw-r--r--kexi/plugins/macros/kexipart/keximacrodesignview.cpp497
-rw-r--r--kexi/plugins/macros/kexipart/keximacrodesignview.h129
-rw-r--r--kexi/plugins/macros/kexipart/keximacroerror.cpp130
-rw-r--r--kexi/plugins/macros/kexipart/keximacroerror.h89
-rw-r--r--kexi/plugins/macros/kexipart/keximacroerrorbase.ui213
-rw-r--r--kexi/plugins/macros/kexipart/keximacrohandler.desktop81
-rw-r--r--kexi/plugins/macros/kexipart/keximacropart.cpp172
-rw-r--r--kexi/plugins/macros/kexipart/keximacropart.h95
-rw-r--r--kexi/plugins/macros/kexipart/keximacroproperty.cpp626
-rw-r--r--kexi/plugins/macros/kexipart/keximacroproperty.h186
-rw-r--r--kexi/plugins/macros/kexipart/keximacrotextview.cpp90
-rw-r--r--kexi/plugins/macros/kexipart/keximacrotextview.h77
-rw-r--r--kexi/plugins/macros/kexipart/keximacroview.cpp175
-rw-r--r--kexi/plugins/macros/kexipart/keximacroview.h140
-rw-r--r--kexi/plugins/macros/lib/Makefile.am23
-rw-r--r--kexi/plugins/macros/lib/action.cpp170
-rw-r--r--kexi/plugins/macros/lib/action.h187
-rw-r--r--kexi/plugins/macros/lib/context.cpp261
-rw-r--r--kexi/plugins/macros/lib/context.h141
-rw-r--r--kexi/plugins/macros/lib/exception.cpp97
-rw-r--r--kexi/plugins/macros/lib/exception.h84
-rw-r--r--kexi/plugins/macros/lib/komacro_export.h39
-rw-r--r--kexi/plugins/macros/lib/macro.cpp126
-rw-r--r--kexi/plugins/macros/lib/macro.h130
-rw-r--r--kexi/plugins/macros/lib/macroitem.cpp217
-rw-r--r--kexi/plugins/macros/lib/macroitem.h142
-rw-r--r--kexi/plugins/macros/lib/manager.cpp170
-rw-r--r--kexi/plugins/macros/lib/manager.h219
-rw-r--r--kexi/plugins/macros/lib/metamethod.cpp344
-rw-r--r--kexi/plugins/macros/lib/metamethod.h150
-rw-r--r--kexi/plugins/macros/lib/metaobject.cpp151
-rw-r--r--kexi/plugins/macros/lib/metaobject.h118
-rw-r--r--kexi/plugins/macros/lib/metaparameter.cpp146
-rw-r--r--kexi/plugins/macros/lib/metaparameter.h136
-rw-r--r--kexi/plugins/macros/lib/variable.cpp246
-rw-r--r--kexi/plugins/macros/lib/variable.h222
-rw-r--r--kexi/plugins/macros/lib/xmlhandler.cpp226
-rw-r--r--kexi/plugins/macros/lib/xmlhandler.h77
-rw-r--r--kexi/plugins/macros/tests/Makefile.am28
-rw-r--r--kexi/plugins/macros/tests/actiontests.cpp211
-rw-r--r--kexi/plugins/macros/tests/actiontests.h89
-rw-r--r--kexi/plugins/macros/tests/commontests.cpp907
-rw-r--r--kexi/plugins/macros/tests/commontests.h118
-rw-r--r--kexi/plugins/macros/tests/komacrotest.cpp58
-rw-r--r--kexi/plugins/macros/tests/komacrotestbase.h90
-rw-r--r--kexi/plugins/macros/tests/komacrotestgui.cpp60
-rw-r--r--kexi/plugins/macros/tests/macroitemtests.cpp243
-rw-r--r--kexi/plugins/macros/tests/macroitemtests.h87
-rw-r--r--kexi/plugins/macros/tests/macrotests.cpp192
-rw-r--r--kexi/plugins/macros/tests/macrotests.h74
-rw-r--r--kexi/plugins/macros/tests/testaction.cpp61
-rw-r--r--kexi/plugins/macros/tests/testaction.h78
-rw-r--r--kexi/plugins/macros/tests/testobject.cpp117
-rw-r--r--kexi/plugins/macros/tests/testobject.h85
-rw-r--r--kexi/plugins/macros/tests/variabletests.cpp236
-rw-r--r--kexi/plugins/macros/tests/variabletests.h87
-rw-r--r--kexi/plugins/macros/tests/xmlhandlertests.cpp619
-rw-r--r--kexi/plugins/macros/tests/xmlhandlertests.h122
-rw-r--r--kexi/plugins/macros/tests/xmlhandlertests2.cpp1161
-rw-r--r--kexi/plugins/macros/tests/xmlhandlertests2.h132
-rw-r--r--kexi/plugins/migration/Makefile.am20
-rw-r--r--kexi/plugins/migration/keximigrationhandler.desktop102
-rw-r--r--kexi/plugins/migration/keximigrationpart.cpp46
-rw-r--r--kexi/plugins/migration/keximigrationpart.h38
-rw-r--r--kexi/plugins/queries/Makefile.am29
-rw-r--r--kexi/plugins/queries/kexiaddparamdialog.cpp47
-rw-r--r--kexi/plugins/queries/kexiaddparamdialog.h40
-rw-r--r--kexi/plugins/queries/kexiaddparamwidget.ui135
-rw-r--r--kexi/plugins/queries/kexidynamicqueryparameterdialog.cpp63
-rw-r--r--kexi/plugins/queries/kexidynamicqueryparameterdialog.h45
-rw-r--r--kexi/plugins/queries/kexiparameterlisteditor.ui88
-rw-r--r--kexi/plugins/queries/kexiquerydesignerguieditor.cpp1803
-rw-r--r--kexi/plugins/queries/kexiquerydesignerguieditor.h170
-rw-r--r--kexi/plugins/queries/kexiquerydesignersql.cpp542
-rw-r--r--kexi/plugins/queries/kexiquerydesignersql.h82
-rw-r--r--kexi/plugins/queries/kexiquerydesignersqlhistory.cpp373
-rw-r--r--kexi/plugins/queries/kexiquerydesignersqlhistory.h104
-rw-r--r--kexi/plugins/queries/kexiqueryhandler.desktop111
-rw-r--r--kexi/plugins/queries/kexiquerypart.cpp310
-rw-r--r--kexi/plugins/queries/kexiquerypart.h118
-rw-r--r--kexi/plugins/queries/kexiquerypartinstui.rc24
-rw-r--r--kexi/plugins/queries/kexiquerypartui.rc11
-rw-r--r--kexi/plugins/queries/kexiqueryview.cpp154
-rw-r--r--kexi/plugins/queries/kexiqueryview.h58
-rw-r--r--kexi/plugins/relations/Makefile.am28
-rw-r--r--kexi/plugins/relations/kexirelationhandler.desktop110
-rw-r--r--kexi/plugins/relations/kexirelationmaindlg.cpp81
-rw-r--r--kexi/plugins/relations/kexirelationmaindlg.h47
-rw-r--r--kexi/plugins/relations/kexirelationpartimpl.cpp85
-rw-r--r--kexi/plugins/relations/kexirelationpartimpl.h46
-rw-r--r--kexi/plugins/relations/kexirelationpartinstui.rc6
-rw-r--r--kexi/plugins/relations/kexirelationpartui.rc14
-rw-r--r--kexi/plugins/reports/Makefile.am51
-rw-r--r--kexi/plugins/reports/kexireportfactory.cpp227
-rw-r--r--kexi/plugins/reports/kexireportfactory.h62
-rw-r--r--kexi/plugins/reports/kexireportform.cpp188
-rw-r--r--kexi/plugins/reports/kexireportform.h60
-rw-r--r--kexi/plugins/reports/kexireporthandler.desktop108
-rw-r--r--kexi/plugins/reports/kexireportpart.cpp141
-rw-r--r--kexi/plugins/reports/kexireportpart.h88
-rw-r--r--kexi/plugins/reports/kexireportpartinstui.rc37
-rw-r--r--kexi/plugins/reports/kexireportpartui.rc6
-rw-r--r--kexi/plugins/reports/kexireports.cpp24
-rw-r--r--kexi/plugins/reports/kexireportview.cpp477
-rw-r--r--kexi/plugins/reports/kexireportview.h130
-rw-r--r--kexi/plugins/reports/kformdesigner_kexireportfactory.desktop53
-rw-r--r--kexi/plugins/reports/reportwidgets.cpp181
-rw-r--r--kexi/plugins/reports/reportwidgets.h117
-rw-r--r--kexi/plugins/scripting/Makefile.am1
-rw-r--r--kexi/plugins/scripting/README28
-rw-r--r--kexi/plugins/scripting/kexiapp/Makefile.am21
-rw-r--r--kexi/plugins/scripting/kexiapp/kexiappmainwindow.cpp106
-rw-r--r--kexi/plugins/scripting/kexiapp/kexiappmainwindow.h91
-rw-r--r--kexi/plugins/scripting/kexiapp/kexiappmodule.cpp97
-rw-r--r--kexi/plugins/scripting/kexiapp/kexiappmodule.h76
-rw-r--r--kexi/plugins/scripting/kexiapp/kexiapppart.cpp46
-rw-r--r--kexi/plugins/scripting/kexiapp/kexiapppart.h56
-rw-r--r--kexi/plugins/scripting/kexidb.doxyfile324
-rw-r--r--kexi/plugins/scripting/kexidb/Makefile.am30
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbconnection.cpp221
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbconnection.h194
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbconnectiondata.cpp112
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbconnectiondata.h126
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbcursor.cpp139
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbcursor.h159
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbdriver.cpp70
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbdriver.h114
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbdrivermanager.cpp178
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbdrivermanager.h105
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbfield.cpp147
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbfield.h148
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbfieldlist.cpp100
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbfieldlist.h104
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbmodule.cpp74
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbmodule.h69
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbparser.cpp77
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbparser.h95
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbschema.cpp197
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbschema.h134
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbtransaction.cpp52
-rw-r--r--kexi/plugins/scripting/kexidb/kexidbtransaction.h62
-rw-r--r--kexi/plugins/scripting/kexidb/readme.dox32
-rw-r--r--kexi/plugins/scripting/kexiscripting/Makefile.am37
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.cpp337
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.h124
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscripteditor.cpp104
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscripteditor.h77
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscripthandler.desktop105
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscriptpart.cpp201
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscriptpart.h100
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscriptpartinstui.rc10
-rw-r--r--kexi/plugins/scripting/kexiscripting/kexiscriptpartui.rc10
-rw-r--r--kexi/plugins/scripting/scripts/Makefile.am1
-rw-r--r--kexi/plugins/scripting/scripts/copycenter/CopyCenter.py644
-rw-r--r--kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc10
-rw-r--r--kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py646
-rw-r--r--kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py495
-rw-r--r--kexi/plugins/scripting/scripts/copycenter/Makefile.am4
-rw-r--r--kexi/plugins/scripting/scripts/copycenter/readme.html20
-rw-r--r--kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py196
-rw-r--r--kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc8
-rw-r--r--kexi/plugins/scripting/scripts/exportxhtml/Makefile.am4
-rwxr-xr-xkexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py434
-rw-r--r--kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc8
-rw-r--r--kexi/plugins/scripting/scripts/importxhtml/Makefile.am4
-rw-r--r--kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am4
-rwxr-xr-xkexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py186
-rw-r--r--kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc8
-rw-r--r--kexi/plugins/scripting/scripts/python/Makefile.am2
-rw-r--r--kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am2
-rwxr-xr-xkexi/plugins/scripting/scripts/python/kexiapp/__init__.py25
-rw-r--r--kexi/plugins/tables/Makefile.am28
-rw-r--r--kexi/plugins/tables/kexilookupcolumnpage.cpp419
-rw-r--r--kexi/plugins/tables/kexilookupcolumnpage.h88
-rw-r--r--kexi/plugins/tables/kexitabledesigner_dataview.cpp79
-rw-r--r--kexi/plugins/tables/kexitabledesigner_dataview.h49
-rw-r--r--kexi/plugins/tables/kexitabledesignercommands.cpp281
-rw-r--r--kexi/plugins/tables/kexitabledesignercommands.h188
-rw-r--r--kexi/plugins/tables/kexitabledesignerview.cpp1943
-rw-r--r--kexi/plugins/tables/kexitabledesignerview.h258
-rw-r--r--kexi/plugins/tables/kexitabledesignerview_p.cpp294
-rw-r--r--kexi/plugins/tables/kexitabledesignerview_p.h191
-rw-r--r--kexi/plugins/tables/kexitablehandler.desktop118
-rw-r--r--kexi/plugins/tables/kexitablepart.cpp313
-rw-r--r--kexi/plugins/tables/kexitablepart.h100
-rw-r--r--kexi/plugins/tables/kexitablepartinstui.rc18
-rw-r--r--kexi/plugins/tables/kexitablepartui.rc7
-rw-r--r--kexi/tests/Makefile.am6
-rw-r--r--kexi/tests/README14
-rw-r--r--kexi/tests/altertable/1.kexibin0 -> 49150 bytes
-rw-r--r--kexi/tests/altertable/Makefile.am22
-rw-r--r--kexi/tests/altertable/README200
-rw-r--r--kexi/tests/altertable/TODO3
-rw-r--r--kexi/tests/altertable/alltypes.altertable109
-rw-r--r--kexi/tests/altertable/altertable.cpp716
-rw-r--r--kexi/tests/altertable/altertable.h63
-rw-r--r--kexi/tests/altertable/defaultvalues.altertable129
-rw-r--r--kexi/tests/gui/finddialog/finddialog.pro18
-rw-r--r--kexi/tests/gui/finddialog/kexifinddialog.cpp67
-rw-r--r--kexi/tests/gui/finddialog/kexifinddialog.h66
-rw-r--r--kexi/tests/gui/finddialog/kexifinddialogbase.ui326
-rw-r--r--kexi/tests/gui/finddialog/main.cpp36
-rw-r--r--kexi/tests/newapi/Makefile.am32
-rw-r--r--kexi/tests/newapi/README59
-rw-r--r--kexi/tests/newapi/cursors_test.h60
-rw-r--r--kexi/tests/newapi/dbcreation_test.h61
-rw-r--r--kexi/tests/newapi/dr_prop_test.h41
-rw-r--r--kexi/tests/newapi/main.cpp254
-rw-r--r--kexi/tests/newapi/mysqlcursor.cpp121
-rw-r--r--kexi/tests/newapi/mysqlcursortest_create.sql41
-rw-r--r--kexi/tests/newapi/mysqlcursortest_expectedoutput128
-rw-r--r--kexi/tests/newapi/newapi.pro33
-rw-r--r--kexi/tests/newapi/parser_test.h62
-rw-r--r--kexi/tests/newapi/schema.sql38
-rw-r--r--kexi/tests/newapi/schema_test.h55
-rwxr-xr-xkexi/tests/newapi/sqltest27
-rwxr-xr-xkexi/tests/newapi/sqltest_int15
-rw-r--r--kexi/tests/newapi/statements.txt89
-rw-r--r--kexi/tests/newapi/tables_test.h117
-rw-r--r--kexi/tests/newapi/tableview_test.h60
-rw-r--r--kexi/tests/parser/Makefile.am13
-rw-r--r--kexi/tests/parser/README9
-rw-r--r--kexi/tests/parser/dbbin0 -> 17408 bytes
-rw-r--r--kexi/tests/parser/main.cpp103
-rw-r--r--kexi/tests/parser/parser.pro24
-rw-r--r--kexi/tests/startup/Makefile.am19
-rw-r--r--kexi/tests/startup/main.cpp119
-rw-r--r--kexi/tests/startup/testdb.kexis63
-rw-r--r--kexi/tests/tableview/Makefile.am16
-rw-r--r--kexi/tests/tableview/README3
-rw-r--r--kexi/tests/tableview/main.cpp53
-rw-r--r--kexi/tests/tableview/tableview.pro32
-rw-r--r--kexi/tests/tests.pro6
-rw-r--r--kexi/tests/widgets/Makefile.am14
-rw-r--r--kexi/tests/widgets/kexidbdrivercombotest.cpp76
-rw-r--r--kexi/tools/Makefile.am1
-rw-r--r--kexi/tools/add_column/Makefile.am5
-rwxr-xr-xkexi/tools/add_column/kexi_add_column114
-rw-r--r--kexi/tools/add_column/kexi_add_column_gui99
-rw-r--r--kexi/tools/add_column/kexi_add_column_gui_transl_pl.sh24
-rwxr-xr-xkexi/tools/build_tarball/build_kexi_tarball.sh232
-rw-r--r--kexi/tools/build_tarball/kexi.lsm10
-rw-r--r--kexi/tools/delete_column/Makefile.am5
-rw-r--r--kexi/tools/delete_column/README17
-rwxr-xr-xkexi/tools/delete_column/kexi_delete_column136
-rwxr-xr-xkexi/tools/delete_column/kexi_delete_column_gui82
-rw-r--r--kexi/tools/delete_column/kexi_delete_column_gui_transl_pl.sh10
-rw-r--r--kexi/tools/feedback/create_kexifeedback.sh54
-rw-r--r--kexi/tools/sql_keywords/Makefile30
-rw-r--r--kexi/tools/sql_keywords/kexi_reserved58
-rwxr-xr-xkexi/tools/sql_keywords/sql_keywords.sh299
-rw-r--r--kexi/widget/Makefile.am51
-rw-r--r--kexi/widget/kexibrowser.cpp890
-rw-r--r--kexi/widget/kexibrowser.h183
-rw-r--r--kexi/widget/kexibrowser_p.h43
-rw-r--r--kexi/widget/kexibrowseritem.cpp91
-rw-r--r--kexi/widget/kexibrowseritem.h70
-rw-r--r--kexi/widget/kexicharencodingcombobox.cpp114
-rw-r--r--kexi/widget/kexicharencodingcombobox.h48
-rw-r--r--kexi/widget/kexicustompropertyfactory.cpp110
-rw-r--r--kexi/widget/kexicustompropertyfactory.h45
-rw-r--r--kexi/widget/kexicustompropertyfactory_p.cpp108
-rw-r--r--kexi/widget/kexicustompropertyfactory_p.h71
-rw-r--r--kexi/widget/kexidataawareview.cpp383
-rw-r--r--kexi/widget/kexidataawareview.h117
-rw-r--r--kexi/widget/kexidatasourcecombobox.cpp333
-rw-r--r--kexi/widget/kexidatasourcecombobox.h88
-rw-r--r--kexi/widget/kexidatatable.cpp82
-rw-r--r--kexi/widget/kexidatatable.h80
-rw-r--r--kexi/widget/kexidbconnectionwidget.cpp407
-rw-r--r--kexi/widget/kexidbconnectionwidget.h179
-rw-r--r--kexi/widget/kexidbconnectionwidgetbase.ui464
-rw-r--r--kexi/widget/kexidbconnectionwidgetdetailsbase.ui194
-rw-r--r--kexi/widget/kexidbconnectionwidgetdetailsbase.ui.h29
-rw-r--r--kexi/widget/kexidbdrivercombobox.cpp92
-rw-r--r--kexi/widget/kexidbdrivercombobox.h102
-rw-r--r--kexi/widget/kexidswelcome.cpp90
-rw-r--r--kexi/widget/kexidswelcome.h48
-rw-r--r--kexi/widget/kexieditor.cpp261
-rw-r--r--kexi/widget/kexieditor.h120
-rw-r--r--kexi/widget/kexifieldcombobox.cpp250
-rw-r--r--kexi/widget/kexifieldcombobox.h82
-rw-r--r--kexi/widget/kexifieldlistview.cpp180
-rw-r--r--kexi/widget/kexifieldlistview.h82
-rw-r--r--kexi/widget/kexifilterdlg.cpp149
-rw-r--r--kexi/widget/kexifilterdlg.h50
-rw-r--r--kexi/widget/kexiprjtypeselector.cpp44
-rw-r--r--kexi/widget/kexiprjtypeselector.h38
-rw-r--r--kexi/widget/kexiprjtypeselectorbase.ui162
-rw-r--r--kexi/widget/kexiprjtypeselectorbase.ui.h23
-rw-r--r--kexi/widget/kexipropertyeditorview.cpp223
-rw-r--r--kexi/widget/kexipropertyeditorview.h117
-rw-r--r--kexi/widget/kexiquerydesignersqleditor.cpp35
-rw-r--r--kexi/widget/kexiquerydesignersqleditor.h39
-rw-r--r--kexi/widget/kexiqueryparameters.cpp139
-rw-r--r--kexi/widget/kexiqueryparameters.h44
-rw-r--r--kexi/widget/kexiscrollview.cpp407
-rw-r--r--kexi/widget/kexiscrollview.h93
-rw-r--r--kexi/widget/kexisectionheader.cpp162
-rw-r--r--kexi/widget/kexisectionheader.h54
-rw-r--r--kexi/widget/kexismalltoolbutton.cpp133
-rw-r--r--kexi/widget/kexismalltoolbutton.h59
-rw-r--r--kexi/widget/pixmapcollection.cpp440
-rw-r--r--kexi/widget/pixmapcollection.h162
-rw-r--r--kexi/widget/relations/Makefile.am38
-rw-r--r--kexi/widget/relations/kexirelationview.cpp639
-rw-r--r--kexi/widget/relations/kexirelationview.h167
-rw-r--r--kexi/widget/relations/kexirelationviewconnection.cpp298
-rw-r--r--kexi/widget/relations/kexirelationviewconnection.h75
-rw-r--r--kexi/widget/relations/kexirelationviewtable.cpp429
-rw-r--r--kexi/widget/relations/kexirelationviewtable.h157
-rw-r--r--kexi/widget/relations/kexirelationwidget.cpp425
-rw-r--r--kexi/widget/relations/kexirelationwidget.h137
-rw-r--r--kexi/widget/relations/r1.xpm10
-rw-r--r--kexi/widget/relations/rn.xpm9
-rw-r--r--kexi/widget/tableview/Makefile.am49
-rw-r--r--kexi/widget/tableview/autonumber.pngbin0 -> 244 bytes
-rw-r--r--kexi/widget/tableview/kexiblobtableedit.cpp595
-rw-r--r--kexi/widget/tableview/kexiblobtableedit.h170
-rw-r--r--kexi/widget/tableview/kexibooltableedit.cpp180
-rw-r--r--kexi/widget/tableview/kexibooltableedit.h87
-rw-r--r--kexi/widget/tableview/kexicelleditorfactory.cpp198
-rw-r--r--kexi/widget/tableview/kexicelleditorfactory.h79
-rw-r--r--kexi/widget/tableview/kexicomboboxbase.cpp597
-rw-r--r--kexi/widget/tableview/kexicomboboxbase.h170
-rw-r--r--kexi/widget/tableview/kexicomboboxpopup.cpp373
-rw-r--r--kexi/widget/tableview/kexicomboboxpopup.h92
-rw-r--r--kexi/widget/tableview/kexicomboboxtableedit.cpp446
-rw-r--r--kexi/widget/tableview/kexicomboboxtableedit.h166
-rw-r--r--kexi/widget/tableview/kexidataawareobjectiface.cpp2108
-rw-r--r--kexi/widget/tableview/kexidataawareobjectiface.h918
-rw-r--r--kexi/widget/tableview/kexidataawarepropertyset.cpp260
-rw-r--r--kexi/widget/tableview/kexidataawarepropertyset.h149
-rw-r--r--kexi/widget/tableview/kexidatatableview.cpp121
-rw-r--r--kexi/widget/tableview/kexidatatableview.h94
-rw-r--r--kexi/widget/tableview/kexidatetableedit.cpp290
-rw-r--r--kexi/widget/tableview/kexidatetableedit.h66
-rw-r--r--kexi/widget/tableview/kexidatetimetableedit.cpp165
-rw-r--r--kexi/widget/tableview/kexidatetimetableedit.h69
-rw-r--r--kexi/widget/tableview/kexiinputtableedit.cpp395
-rw-r--r--kexi/widget/tableview/kexiinputtableedit.h126
-rw-r--r--kexi/widget/tableview/kexitableedit.cpp237
-rw-r--r--kexi/widget/tableview/kexitableedit.h233
-rw-r--r--kexi/widget/tableview/kexitableitem.cpp62
-rw-r--r--kexi/widget/tableview/kexitableitem.h58
-rw-r--r--kexi/widget/tableview/kexitableview.cpp2607
-rw-r--r--kexi/widget/tableview/kexitableview.h639
-rw-r--r--kexi/widget/tableview/kexitableview_p.cpp67
-rw-r--r--kexi/widget/tableview/kexitableview_p.h155
-rw-r--r--kexi/widget/tableview/kexitableviewdata.cpp886
-rw-r--r--kexi/widget/tableview/kexitableviewdata.h540
-rw-r--r--kexi/widget/tableview/kexitableviewheader.cpp202
-rw-r--r--kexi/widget/tableview/kexitableviewheader.h75
-rw-r--r--kexi/widget/tableview/kexitextformatter.cpp237
-rw-r--r--kexi/widget/tableview/kexitextformatter.h64
-rw-r--r--kexi/widget/tableview/kexitimetableedit.cpp158
-rw-r--r--kexi/widget/tableview/kexitimetableedit.h64
-rw-r--r--kexi/widget/utils/Makefile.am19
-rw-r--r--kexi/widget/utils/kexiarrowtip.cpp164
-rw-r--r--kexi/widget/utils/kexiarrowtip.h56
-rw-r--r--kexi/widget/utils/kexicomboboxdropdownbutton.cpp87
-rw-r--r--kexi/widget/utils/kexicomboboxdropdownbutton.h49
-rw-r--r--kexi/widget/utils/kexicontextmenuutils.cpp283
-rw-r--r--kexi/widget/utils/kexicontextmenuutils.h112
-rw-r--r--kexi/widget/utils/kexidatetimeformatter.cpp367
-rw-r--r--kexi/widget/utils/kexidatetimeformatter.h165
-rw-r--r--kexi/widget/utils/kexidisplayutils.cpp172
-rw-r--r--kexi/widget/utils/kexidisplayutils.h57
-rw-r--r--kexi/widget/utils/kexidropdownbutton.cpp82
-rw-r--r--kexi/widget/utils/kexidropdownbutton.h45
-rw-r--r--kexi/widget/utils/kexiflowlayout.cpp452
-rw-r--r--kexi/widget/utils/kexiflowlayout.h79
-rw-r--r--kexi/widget/utils/kexigradientwidget.cpp358
-rw-r--r--kexi/widget/utils/kexigradientwidget.h247
-rw-r--r--kexi/widget/utils/kexirecordmarker.cpp307
-rw-r--r--kexi/widget/utils/kexirecordmarker.h72
-rw-r--r--kexi/widget/utils/kexirecordnavigator.cpp511
-rw-r--r--kexi/widget/utils/kexirecordnavigator.h190
-rw-r--r--kexi/widget/utils/kexisharedactionclient.cpp39
-rw-r--r--kexi/widget/utils/kexisharedactionclient.h49
-rw-r--r--kexi/widget/utils/kexitooltip.cpp76
-rw-r--r--kexi/widget/utils/kexitooltip.h47
-rw-r--r--kexi/widget/utils/klistviewitemtemplate.h50
1416 files changed, 322606 insertions, 0 deletions
diff --git a/kexi/3rdparty/Makefile.am b/kexi/3rdparty/Makefile.am
new file mode 100644
index 000000000..7ca58d15d
--- /dev/null
+++ b/kexi/3rdparty/Makefile.am
@@ -0,0 +1,6 @@
+
+
+SUBDIRS = kexisql3 kexisql
+
+#unused on !win32: kolibs
+#unused: uuid
diff --git a/kexi/3rdparty/README.3rdparty b/kexi/3rdparty/README.3rdparty
new file mode 100644
index 000000000..0cae7a0cb
--- /dev/null
+++ b/kexi/3rdparty/README.3rdparty
@@ -0,0 +1,18 @@
+Kexi 3rdparty directory contents
+--------------------------------
+
+Purpose: Any kexi-independent code goes here.
+
+kexisql/ - database engine (SQLite3) implementation taken "as-is" from SQLite project (www.sqlite.org)
+ We use kexisql name for indication that original SQLite sources can be changed
+ by Kexi programmers from-version-to-version without notice. "original"
+ SQLite db implementation library won't be in conflict with kexisql lib
+ (we use other naming).
+kexisql2/ - database engine (SQLite2). As above, but this is older version provided for handling older,
+ incompatible file format.
+
+
+Currently unused:
+
+uuid/ - unique number generator from e2fsprogs, used for creating unique
+ identifers across databases and for local objects
diff --git a/kexi/3rdparty/configure.in.in b/kexi/3rdparty/configure.in.in
new file mode 100644
index 000000000..5c594549d
--- /dev/null
+++ b/kexi/3rdparty/configure.in.in
@@ -0,0 +1,60 @@
+dnl This is copied from gpsim-0.21.1/acinclude.m4, which was itself
+dnl copied from the NcFTP distribution.
+dnl Modified by David Faure <faure@kde.org> for kexi's purposes.
+dnl
+dnl Original author Mike Gleason mgleason@NcFTP.com
+dnl
+dnl
+AC_DEFUN([kexi_LIB_READLINE], [
+AC_MSG_CHECKING([for Readline library])
+
+kexi_cv_lib_readline=no
+ac_save_LIBS="$LIBS"
+# Note: $LIBCURSES is permitted to be empty.
+for LIBREADLINE in "-lreadline" "-lreadline $LIBCURSES" "-lreadline -ltermcap" "-lreadline -lncurses" "-lreadline -lcurses"
+do
+ LIBS="$ac_save_LIBS $LIBREADLINE"
+ AC_TRY_RUN([
+ /* program */
+#include <stdio.h>
+#include <stdlib.h>
+#include <readline/readline.h>
+
+main(int argc, char **argv)
+{
+ /* Note: don't actually call readline, since it may block;
+ * We just want to see if it (dynamic) linked in okay.
+ */
+ if (argc == 0) /* never true */
+ readline(0);
+ exit(0);
+}
+],[
+ # action if true
+ kexi_cv_lib_readline=yes
+],[
+ # action if false
+ kexi_cv_lib_readline=no
+],[
+ # action if cross compiling
+ kexi_cv_lib_readline=no
+])
+
+ if test "$kexi_cv_lib_readline" = yes ; then break ; fi
+done
+# restore LIBS
+LIBS="$ac_save_LIBS"
+
+if test "$kexi_cv_lib_readline" = no ; then
+ LIBREADLINE=""
+ AC_MSG_RESULT("not found - kexisql will not be compiled")
+else
+ AC_MSG_RESULT($LIBREADLINE)
+ AC_DEFINE(HAVE_READLINE, 1, [define if you have libreadline available])
+fi
+
+AM_CONDITIONAL(have_readline, test "$kexi_cv_lib_readline" = "yes")
+AC_SUBST(LIBREADLINE)
+])
+
+kexi_LIB_READLINE
diff --git a/kexi/3rdparty/kexisql/Makefile.am b/kexi/3rdparty/kexisql/Makefile.am
new file mode 100644
index 000000000..22a0a17b8
--- /dev/null
+++ b/kexi/3rdparty/kexisql/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = tool src
diff --git a/kexi/3rdparty/kexisql/Makefile.msvc b/kexi/3rdparty/kexisql/Makefile.msvc
new file mode 100644
index 000000000..0be28d92a
--- /dev/null
+++ b/kexi/3rdparty/kexisql/Makefile.msvc
@@ -0,0 +1,167 @@
+#############################################################################
+# Makefile for building: sqlite2
+#############################################################################
+
+####### Compiler, tools and options
+
+CC = cl
+CXX = cl
+LEX = flex
+YACC = byacc
+CFLAGS = -nologo -Zm200 -W3 -MDd -Z7 -GX -GR -DTHREADSAFE=1
+CXXFLAGS = -nologo -Zm200 /GR /GX /GZ /TP -W3 -MDd -Z7 -GX -GR -DUNICODE
+LEXFLAGS =
+YACCFLAGS =-d
+INCPATH = -Isrc
+LINK = link
+LFLAGS = /NOLOGO /FORCE:MULTIPLE /DEBUG /SUBSYSTEM:console /DEF:kexisql.def /DLL
+VERSION=2.8.2
+LIBS = "kernel32.lib" "user32.lib" "gdi32.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "imm32.lib" "winmm.lib" "wsock32.lib" "winspool.lib"
+IDC = $(QTDIR)\bin\idc.exe
+IDL = midl
+ZIP = zip -r -9
+COPY_FILE = copy
+COPY_DIR = copy
+DEL_FILE = del
+DEL_DIR = rmdir
+MOVE = move
+CHK_DIR_EXISTS = if not exist
+MKDIR = mkdir
+
+####### Files
+
+HEADERS =
+SOURCES = \
+src/attach.c \
+src/auth.c \
+src/btree.c \
+src/btree_rb.c \
+src/build.c \
+src/copy.c \
+src/date.c \
+src/delete.c \
+src/expr.c \
+src/func.c \
+src/hash.c \
+src/insert.c \
+src/main.c \
+src/os.c \
+src/pager.c \
+src/pragma.c \
+src/printf.c \
+src/random.c \
+src/select.c \
+src/table.c \
+src/tokenize.c \
+src/trigger.c \
+src/update.c \
+src/util.c \
+src/vacuum.c \
+src/vdbe.c \
+src/vdbeaux.c \
+src/where.c \
+src/opcodes.c \
+src/parse.c \
+src/shell.c
+
+OBJECTS = \
+obj/attach.obj \
+obj/auth.obj \
+obj/btree.obj \
+obj/btree_rb.obj \
+obj/build.obj \
+obj/copy.obj \
+obj/date.obj \
+obj/delete.obj \
+obj/expr.obj \
+obj/func.obj \
+obj/hash.obj \
+obj/insert.obj \
+obj/main.obj \
+obj/os.obj \
+obj/pager.obj \
+obj/pragma.obj \
+obj/printf.obj \
+obj/random.obj \
+obj/select.obj \
+obj/table.obj \
+obj/tokenize.obj \
+obj/trigger.obj \
+obj/update.obj \
+obj/util.obj \
+obj/vacuum.obj \
+obj/vdbe.obj \
+obj/vdbeaux.obj \
+obj/where.obj \
+obj/opcodes.obj \
+obj/parse.obj \
+obj/shell.obj
+
+FORMS =
+UICDECLS =
+UICIMPLS =
+SRCMOC =
+OBJMOC =
+DIST =
+TARGET = $(KDEDIR)/bin/kexisql2_d.dll
+
+####### Implicit rules
+
+.SUFFIXES: .c .cpp .cc .cxx .C
+
+{.}.cpp{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.cc{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.cxx{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.C{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.c{obj\}.obj::
+ $(CC) -c $(CFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{src\}.c{obj\}.obj::
+ $(CC) -c $(CFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+####### Build rules
+
+all: $(TARGET) sqlite2
+
+$(TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC)
+ $(LINK) $(LFLAGS) /OUT:$(TARGET) @<<
+ $(OBJECTS) $(OBJMOC) $(LIBS)
+<<
+
+sqlite2: obj/shell.obj $(KDEDIR)/bin/kexisql2.lib
+ link $(READLINE_FLAGS) $(LIBPTHREAD) \
+ /OUT:$(KDEDIR)/bin/ksqlite2.exe obj/shell.obj $(KDEDIR)/bin/kexisql2.lib $(LIBREADLINE)
+
+mocables: $(SRCMOC)
+uicables: $(UICIMPLS) $(UICDECLS)
+
+clean:
+ -del obj\*.obj
+
+distclean: clean
+ -del $(TARGET)
+
+install: all
+
+uninstall:
+
diff --git a/kexi/3rdparty/kexisql/kexisql.def b/kexi/3rdparty/kexisql/kexisql.def
new file mode 100644
index 000000000..681aa63e2
--- /dev/null
+++ b/kexi/3rdparty/kexisql/kexisql.def
@@ -0,0 +1,40 @@
+EXPORTS
+sqlite_open
+sqlite_close
+sqlite_exec
+sqlite_last_insert_rowid
+sqlite_error_string
+sqlite_interrupt
+sqlite_complete
+sqlite_busy_handler
+sqlite_busy_timeout
+sqlite_get_table
+sqlite_free_table
+sqlite_mprintf
+sqlite_vmprintf
+sqlite_exec_printf
+sqlite_exec_vprintf
+sqlite_get_table_printf
+sqlite_get_table_vprintf
+sqlite_freemem
+sqlite_libversion
+sqlite_libencoding
+sqlite_changes
+sqlite_create_function
+sqlite_create_aggregate
+sqlite_function_type
+sqlite_user_data
+sqlite_aggregate_context
+sqlite_aggregate_count
+sqlite_set_result_string
+sqlite_set_result_int
+sqlite_set_result_double
+sqlite_set_result_error
+sqliteMalloc
+sqliteFree
+sqliteRealloc
+sqlite_set_authorizer
+sqlite_trace
+sqlite_compile
+sqlite_step
+sqlite_finalize
diff --git a/kexi/3rdparty/kexisql/src/Makefile.am b/kexi/3rdparty/kexisql/src/Makefile.am
new file mode 100644
index 000000000..0678eeceb
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/Makefile.am
@@ -0,0 +1,20 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexisql2.la
+
+bin_PROGRAMS = ksqlite2
+
+INCLUDES = $(all_includes)
+
+libkexisql2_la_SOURCES = attach.c auth.c btree.c btree_rb.c build.c date.c expr.c func.c hash.c \
+insert.c os.c pager.c parse.c pragma.c printf.c random.c select.c copy.c delete.c encode.c \
+table.c tokenize.c trigger.c update.c util.c vacuum.c vdbe.c vdbeaux.c where.c opcodes.c main.c
+
+AM_CFLAGS = -DNO_TCL=1
+
+ksqlite2_SOURCES = shell.c
+ksqlite2_LDADD = libkexisql2.la $(LIBREADLINE)
+
+libkexisql2_la_LIBADD =
+libkexisql2_la_LDFLAGS = -version-info 2:8 $(all_libraries) --no-undefined
+
diff --git a/kexi/3rdparty/kexisql/src/attach.c b/kexi/3rdparty/kexisql/src/attach.c
new file mode 100644
index 000000000..b41134bee
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/auth.c b/kexi/3rdparty/kexisql/src/auth.c
new file mode 100644
index 000000000..61a3ad945
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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 */
+
+ 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{
+ /* This must be an attempt to read the NEW or OLD pseudo-tables
+ ** of a trigger.
+ */
+ TriggerStack *pStack; /* The stack of current triggers */
+ pStack = pParse->trigStack;
+ assert( pStack!=0 );
+ 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( 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/kexi/3rdparty/kexisql/src/btree.c b/kexi/3rdparty/kexisql/src/btree.c
new file mode 100644
index 000000000..02e01249f
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+**
+** 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/kexi/3rdparty/kexisql/src/btree.h b/kexi/3rdparty/kexisql/src/btree.h
new file mode 100644
index 000000000..6152b308f
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/btree_rb.c b/kexi/3rdparty/kexisql/src/btree_rb.c
new file mode 100644
index 000000000..3954fe6d4
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+**
+** 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/kexi/3rdparty/kexisql/src/build.c b/kexi/3rdparty/kexisql/src/build.c
new file mode 100644
index 000000000..1dece406e
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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 = sqliteStrNDup(pName->z, pName->n);
+ }
+
+ /* 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/kexi/3rdparty/kexisql/src/copy.c b/kexi/3rdparty/kexisql/src/copy.c
new file mode 100644
index 000000000..46b869031
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/date.c b/kexi/3rdparty/kexisql/src/date.c
new file mode 100644
index 000000000..e48c9f337
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+**
+** 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/kexi/3rdparty/kexisql/src/delete.c b/kexi/3rdparty/kexisql/src/delete.c
new file mode 100644
index 000000000..543754228
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/encode.c b/kexi/3rdparty/kexisql/src/encode.c
new file mode 100644
index 000000000..899901b52
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/encode.c
@@ -0,0 +1,254 @@
+/*
+** 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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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++);
+ 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/kexi/3rdparty/kexisql/src/expr.c b/kexi/3rdparty/kexisql/src/expr.c
new file mode 100644
index 000000000..b4fb936d6
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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 = sqliteStrDup(p->token.z);
+ 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/kexi/3rdparty/kexisql/src/func.c b/kexi/3rdparty/kexisql/src/func.c
new file mode 100644
index 000000000..1e176258c
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/hash.c b/kexi/3rdparty/kexisql/src/hash.c
new file mode 100644
index 000000000..0aefe0ca8
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/hash.h b/kexi/3rdparty/kexisql/src/hash.h
new file mode 100644
index 000000000..e1c1bb8ec
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/insert.c b/kexi/3rdparty/kexisql/src/insert.c
new file mode 100644
index 000000000..660a2a568
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/main.c b/kexi/3rdparty/kexisql/src/main.c
new file mode 100644
index 000000000..c883f2eac
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/opcodes.c b/kexi/3rdparty/kexisql/src/opcodes.c
new file mode 100644
index 000000000..0907e0e79
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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/kexi/3rdparty/kexisql/src/opcodes.h b/kexi/3rdparty/kexisql/src/opcodes.h
new file mode 100644
index 000000000..35e050697
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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/kexi/3rdparty/kexisql/src/os.c b/kexi/3rdparty/kexisql/src/os.c
new file mode 100644
index 000000000..165769242
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/os.c
@@ -0,0 +1,1845 @@
+/*
+** 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
+}
+
+/*
+** 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];
+ 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/kexi/3rdparty/kexisql/src/os.h b/kexi/3rdparty/kexisql/src/os.h
new file mode 100644
index 000000000..d1395841d
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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__) || defined(__BORLANDC__)
+# 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) || defined(__BORLANDC__)
+ 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/kexi/3rdparty/kexisql/src/pager.c b/kexi/3rdparty/kexisql/src/pager.c
new file mode 100644
index 000000000..0ce24086e
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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 ){
+ 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/kexi/3rdparty/kexisql/src/pager.h b/kexi/3rdparty/kexisql/src/pager.h
new file mode 100644
index 000000000..b816a66fe
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+
+/*
+** 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/kexi/3rdparty/kexisql/src/parse.c b/kexi/3rdparty/kexisql/src/parse.c
new file mode 100644
index 000000000..463536915
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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/kexi/3rdparty/kexisql/src/parse.h b/kexi/3rdparty/kexisql/src/parse.h
new file mode 100644
index 000000000..188a336c8
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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/kexi/3rdparty/kexisql/src/parse.out b/kexi/3rdparty/kexisql/src/parse.out
new file mode 100644
index 000000000..0f8b03c1e
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/parse.out
@@ -0,0 +1,10575 @@
+ State 0:
+ input ::= * cmdlist
+ cmdlist ::= * ecmd
+ cmdlist ::= * cmdlist ecmd
+ ecmd ::= * explain cmdx SEMI
+ ecmd ::= * SEMI
+ explain ::= * EXPLAIN
+ (7) explain ::= *
+
+ EXPLAIN shift 553
+ SEMI shift 552
+ cmdlist shift 1
+ ecmd shift 554
+ explain shift 3
+ input accept
+ {default} reduce 7
+
+State 1:
+ (0) input ::= cmdlist *
+ cmdlist ::= cmdlist * ecmd
+ ecmd ::= * explain cmdx SEMI
+ ecmd ::= * SEMI
+ explain ::= * EXPLAIN
+ (7) explain ::= *
+
+ $ reduce 0
+ EXPLAIN shift 553
+ SEMI shift 552
+ ecmd shift 2
+ explain shift 3
+ {default} reduce 7
+
+State 2:
+ (2) cmdlist ::= cmdlist ecmd *
+
+ {default} reduce 2
+
+State 3:
+ ecmd ::= explain * cmdx SEMI
+ cmdx ::= * cmd
+ cmd ::= * BEGIN trans_opt onconf
+ cmd ::= * COMMIT trans_opt
+ cmd ::= * END trans_opt
+ cmd ::= * ROLLBACK trans_opt
+ cmd ::= * create_table create_table_args
+ create_table ::= * CREATE temp TABLE nm
+ cmd ::= * DROP TABLE nm
+ cmd ::= * CREATE temp VIEW nm AS select
+ cmd ::= * DROP VIEW nm
+ cmd ::= * select
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ cmd ::= * DELETE FROM nm dbnm where_opt
+ cmd ::= * UPDATE orconf nm dbnm SET setlist where_opt
+ cmd ::= * insert_cmd INTO nm dbnm inscollist_opt VALUES LP itemlist RP
+ cmd ::= * insert_cmd INTO nm dbnm inscollist_opt select
+ insert_cmd ::= * INSERT orconf
+ insert_cmd ::= * REPLACE
+ cmd ::= * CREATE temp uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf
+ cmd ::= * DROP INDEX nm dbnm
+ cmd ::= * COPY orconf nm dbnm FROM nm USING DELIMITERS STRING
+ cmd ::= * COPY orconf nm dbnm FROM nm
+ cmd ::= * VACUUM
+ cmd ::= * VACUUM nm
+ cmd ::= * PRAGMA ids EQ nm
+ cmd ::= * PRAGMA ids EQ ON
+ cmd ::= * PRAGMA ids EQ plus_num
+ cmd ::= * PRAGMA ids EQ minus_num
+ cmd ::= * PRAGMA ids LP nm RP
+ cmd ::= * PRAGMA ids
+ cmd ::= * CREATE trigger_decl BEGIN trigger_cmd_list END
+ cmd ::= * DROP TRIGGER nm dbnm
+ cmd ::= * ATTACH database_kw_opt ids AS nm
+ cmd ::= * DETACH database_kw_opt nm
+
+ ATTACH shift 543
+ BEGIN shift 7
+ COMMIT shift 23
+ COPY shift 515
+ CREATE shift 388
+ DELETE shift 490
+ DETACH shift 549
+ DROP shift 478
+ END shift 25
+ INSERT shift 512
+ PRAGMA shift 526
+ REPLACE shift 514
+ ROLLBACK shift 27
+ SELECT shift 73
+ UPDATE shift 495
+ VACUUM shift 524
+ cmd shift 6
+ cmdx shift 4
+ create_table shift 29
+ insert_cmd shift 502
+ oneselect shift 69
+ select shift 489
+
+State 4:
+ ecmd ::= explain cmdx * SEMI
+
+ SEMI shift 5
+
+State 5:
+ (3) ecmd ::= explain cmdx SEMI *
+
+ {default} reduce 3
+
+State 6:
+ (5) cmdx ::= cmd *
+
+ SEMI reduce 5
+
+State 7:
+ cmd ::= BEGIN * trans_opt onconf
+ (9) trans_opt ::= *
+ trans_opt ::= * TRANSACTION
+ trans_opt ::= * TRANSACTION nm
+
+ TRANSACTION shift 18
+ trans_opt shift 8
+ {default} reduce 9
+
+State 8:
+ cmd ::= BEGIN trans_opt * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+
+ ON shift 10
+ SEMI reduce 88
+ onconf shift 9
+
+State 9:
+ (8) cmd ::= BEGIN trans_opt onconf *
+
+ SEMI reduce 8
+
+State 10:
+ onconf ::= ON * CONFLICT resolvetype
+
+ CONFLICT shift 11
+
+State 11:
+ onconf ::= ON CONFLICT * resolvetype
+ resolvetype ::= * ROLLBACK
+ resolvetype ::= * ABORT
+ resolvetype ::= * FAIL
+ resolvetype ::= * IGNORE
+ resolvetype ::= * REPLACE
+
+ ABORT shift 14
+ FAIL shift 15
+ IGNORE shift 16
+ REPLACE shift 17
+ ROLLBACK shift 13
+ resolvetype shift 12
+
+State 12:
+ (89) onconf ::= ON CONFLICT resolvetype *
+
+ {default} reduce 89
+
+State 13:
+ (92) resolvetype ::= ROLLBACK *
+
+ {default} reduce 92
+
+State 14:
+ (93) resolvetype ::= ABORT *
+
+ {default} reduce 93
+
+State 15:
+ (94) resolvetype ::= FAIL *
+
+ {default} reduce 94
+
+State 16:
+ (95) resolvetype ::= IGNORE *
+
+ {default} reduce 95
+
+State 17:
+ (96) resolvetype ::= REPLACE *
+
+ {default} reduce 96
+
+State 18:
+ (10) trans_opt ::= TRANSACTION *
+ trans_opt ::= TRANSACTION * nm
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 19
+ {default} reduce 10
+
+State 19:
+ (11) trans_opt ::= TRANSACTION nm *
+
+ {default} reduce 11
+
+State 20:
+ (28) nm ::= ID *
+
+ {default} reduce 28
+
+State 21:
+ (29) nm ::= STRING *
+
+ {default} reduce 29
+
+State 22:
+ (30) nm ::= JOIN_KW *
+
+ {default} reduce 30
+
+State 23:
+ (9) trans_opt ::= *
+ trans_opt ::= * TRANSACTION
+ trans_opt ::= * TRANSACTION nm
+ cmd ::= COMMIT * trans_opt
+
+ SEMI reduce 9
+ TRANSACTION shift 18
+ trans_opt shift 24
+
+State 24:
+ (12) cmd ::= COMMIT trans_opt *
+
+ SEMI reduce 12
+
+State 25:
+ (9) trans_opt ::= *
+ trans_opt ::= * TRANSACTION
+ trans_opt ::= * TRANSACTION nm
+ cmd ::= END * trans_opt
+
+ SEMI reduce 9
+ TRANSACTION shift 18
+ trans_opt shift 26
+
+State 26:
+ (13) cmd ::= END trans_opt *
+
+ SEMI reduce 13
+
+State 27:
+ (9) trans_opt ::= *
+ trans_opt ::= * TRANSACTION
+ trans_opt ::= * TRANSACTION nm
+ cmd ::= ROLLBACK * trans_opt
+
+ SEMI reduce 9
+ TRANSACTION shift 18
+ trans_opt shift 28
+
+State 28:
+ (14) cmd ::= ROLLBACK trans_opt *
+
+ SEMI reduce 14
+
+State 29:
+ cmd ::= create_table * create_table_args
+ create_table_args ::= * LP columnlist conslist_opt RP
+ create_table_args ::= * AS select
+
+ AS shift 386
+ LP shift 31
+ create_table_args shift 30
+
+State 30:
+ (15) cmd ::= create_table create_table_args *
+
+ SEMI reduce 15
+
+State 31:
+ create_table_args ::= LP * columnlist conslist_opt RP
+ columnlist ::= * columnlist COMMA column
+ columnlist ::= * column
+ column ::= * columnid type carglist
+ columnid ::= * nm
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ column shift 385
+ columnid shift 37
+ columnlist shift 32
+ nm shift 351
+
+State 32:
+ create_table_args ::= LP columnlist * conslist_opt RP
+ columnlist ::= columnlist * COMMA column
+ (76) conslist_opt ::= *
+ conslist_opt ::= * COMMA conslist
+
+ COMMA shift 35
+ RP reduce 76
+ conslist_opt shift 33
+
+State 33:
+ create_table_args ::= LP columnlist conslist_opt * RP
+
+ RP shift 34
+
+State 34:
+ (19) create_table_args ::= LP columnlist conslist_opt RP *
+
+ SEMI reduce 19
+
+State 35:
+ columnlist ::= columnlist COMMA * column
+ column ::= * columnid type carglist
+ columnid ::= * nm
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ conslist_opt ::= COMMA * conslist
+ conslist ::= * conslist COMMA tcons
+ conslist ::= * conslist tcons
+ conslist ::= * tcons
+ tcons ::= * CONSTRAINT nm
+ tcons ::= * PRIMARY KEY LP idxlist RP onconf
+ tcons ::= * UNIQUE LP idxlist RP onconf
+ tcons ::= * CHECK expr onconf
+ tcons ::= * FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt
+
+ CHECK shift 368
+ CONSTRAINT shift 355
+ FOREIGN shift 371
+ ID shift 20
+ JOIN_KW shift 22
+ PRIMARY shift 357
+ STRING shift 21
+ UNIQUE shift 363
+ column shift 36
+ columnid shift 37
+ conslist shift 352
+ nm shift 351
+ tcons shift 384
+
+State 36:
+ (21) columnlist ::= columnlist COMMA column *
+
+ {default} reduce 21
+
+State 37:
+ column ::= columnid * type carglist
+ ids ::= * ID
+ ids ::= * STRING
+ (31) type ::= *
+ type ::= * typename
+ type ::= * typename LP signed RP
+ type ::= * typename LP signed COMMA signed RP
+ typename ::= * ids
+ typename ::= * typename ids
+
+ ID shift 256
+ STRING shift 257
+ ids shift 350
+ type shift 38
+ typename shift 342
+ {default} reduce 31
+
+State 38:
+ column ::= columnid type * carglist
+ carglist ::= * carglist carg
+ (41) carglist ::= *
+
+ carglist shift 39
+ {default} reduce 41
+
+State 39:
+ (23) column ::= columnid type carglist *
+ carglist ::= carglist * carg
+ carg ::= * CONSTRAINT nm ccons
+ carg ::= * ccons
+ carg ::= * DEFAULT STRING
+ carg ::= * DEFAULT ID
+ carg ::= * DEFAULT INTEGER
+ carg ::= * DEFAULT PLUS INTEGER
+ carg ::= * DEFAULT MINUS INTEGER
+ carg ::= * DEFAULT FLOAT
+ carg ::= * DEFAULT PLUS FLOAT
+ carg ::= * DEFAULT MINUS FLOAT
+ carg ::= * DEFAULT NULL
+ ccons ::= * NULL onconf
+ ccons ::= * NOT NULL onconf
+ ccons ::= * PRIMARY KEY sortorder onconf
+ ccons ::= * UNIQUE onconf
+ ccons ::= * CHECK LP expr RP onconf
+ ccons ::= * REFERENCES nm idxlist_opt refargs
+ ccons ::= * defer_subclause
+ ccons ::= * COLLATE id
+ defer_subclause ::= * NOT DEFERRABLE init_deferred_pred_opt
+ defer_subclause ::= * DEFERRABLE init_deferred_pred_opt
+
+ CHECK shift 62
+ COLLATE shift 325
+ CONSTRAINT shift 41
+ DEFAULT shift 330
+ DEFERRABLE shift 327
+ NOT shift 46
+ NULL shift 44
+ PRIMARY shift 54
+ REFERENCES shift 302
+ UNIQUE shift 60
+ carg shift 40
+ ccons shift 329
+ defer_subclause shift 324
+ {default} reduce 23
+
+State 40:
+ (40) carglist ::= carglist carg *
+
+ {default} reduce 40
+
+State 41:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ carg ::= CONSTRAINT * nm ccons
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 42
+
+State 42:
+ carg ::= CONSTRAINT nm * ccons
+ ccons ::= * NULL onconf
+ ccons ::= * NOT NULL onconf
+ ccons ::= * PRIMARY KEY sortorder onconf
+ ccons ::= * UNIQUE onconf
+ ccons ::= * CHECK LP expr RP onconf
+ ccons ::= * REFERENCES nm idxlist_opt refargs
+ ccons ::= * defer_subclause
+ ccons ::= * COLLATE id
+ defer_subclause ::= * NOT DEFERRABLE init_deferred_pred_opt
+ defer_subclause ::= * DEFERRABLE init_deferred_pred_opt
+
+ CHECK shift 62
+ COLLATE shift 325
+ DEFERRABLE shift 327
+ NOT shift 46
+ NULL shift 44
+ PRIMARY shift 54
+ REFERENCES shift 302
+ UNIQUE shift 60
+ ccons shift 43
+ defer_subclause shift 324
+
+State 43:
+ (42) carg ::= CONSTRAINT nm ccons *
+
+ {default} reduce 42
+
+State 44:
+ ccons ::= NULL * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+
+ ON shift 10
+ onconf shift 45
+ {default} reduce 88
+
+State 45:
+ (53) ccons ::= NULL onconf *
+
+ {default} reduce 53
+
+State 46:
+ ccons ::= NOT * NULL onconf
+ defer_subclause ::= NOT * DEFERRABLE init_deferred_pred_opt
+
+ DEFERRABLE shift 49
+ NULL shift 47
+
+State 47:
+ ccons ::= NOT NULL * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+
+ ON shift 10
+ onconf shift 48
+ {default} reduce 88
+
+State 48:
+ (54) ccons ::= NOT NULL onconf *
+
+ {default} reduce 54
+
+State 49:
+ defer_subclause ::= NOT DEFERRABLE * init_deferred_pred_opt
+ (73) init_deferred_pred_opt ::= *
+ init_deferred_pred_opt ::= * INITIALLY DEFERRED
+ init_deferred_pred_opt ::= * INITIALLY IMMEDIATE
+
+ INITIALLY shift 51
+ init_deferred_pred_opt shift 50
+ {default} reduce 73
+
+State 50:
+ (71) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt *
+
+ {default} reduce 71
+
+State 51:
+ init_deferred_pred_opt ::= INITIALLY * DEFERRED
+ init_deferred_pred_opt ::= INITIALLY * IMMEDIATE
+
+ DEFERRED shift 52
+ IMMEDIATE shift 53
+
+State 52:
+ (74) init_deferred_pred_opt ::= INITIALLY DEFERRED *
+
+ {default} reduce 74
+
+State 53:
+ (75) init_deferred_pred_opt ::= INITIALLY IMMEDIATE *
+
+ {default} reduce 75
+
+State 54:
+ ccons ::= PRIMARY * KEY sortorder onconf
+
+ KEY shift 55
+
+State 55:
+ ccons ::= PRIMARY KEY * sortorder onconf
+ sortorder ::= * ASC
+ sortorder ::= * DESC
+ (143) sortorder ::= *
+
+ ASC shift 58
+ DESC shift 59
+ sortorder shift 56
+ {default} reduce 143
+
+State 56:
+ ccons ::= PRIMARY KEY sortorder * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+
+ ON shift 10
+ onconf shift 57
+ {default} reduce 88
+
+State 57:
+ (55) ccons ::= PRIMARY KEY sortorder onconf *
+
+ {default} reduce 55
+
+State 58:
+ (141) sortorder ::= ASC *
+
+ {default} reduce 141
+
+State 59:
+ (142) sortorder ::= DESC *
+
+ {default} reduce 142
+
+State 60:
+ ccons ::= UNIQUE * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+
+ ON shift 10
+ onconf shift 61
+ {default} reduce 88
+
+State 61:
+ (56) ccons ::= UNIQUE onconf *
+
+ {default} reduce 56
+
+State 62:
+ ccons ::= CHECK * LP expr RP onconf
+
+ LP shift 63
+
+State 63:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ ccons ::= CHECK LP * expr RP onconf
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 299
+ nm shift 107
+
+State 64:
+ (28) nm ::= ID *
+ (172) expr ::= ID *
+ expr ::= ID * LP exprlist RP
+ expr ::= ID * LP STAR RP
+
+ DOT reduce 28
+ LP shift 65
+ {default} reduce 172
+
+State 65:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= ID LP * exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= ID LP * STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ exprlist ::= * exprlist COMMA expritem
+ exprlist ::= * expritem
+ expritem ::= * expr
+ (230) expritem ::= *
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STAR shift 297
+ STRING shift 66
+ expr shift 172
+ expritem shift 219
+ exprlist shift 295
+ nm shift 107
+ {default} reduce 230
+
+State 66:
+ (29) nm ::= STRING *
+ (179) expr ::= STRING *
+
+ DOT reduce 29
+ {default} reduce 179
+
+State 67:
+ (30) nm ::= JOIN_KW *
+ (173) expr ::= JOIN_KW *
+
+ DOT reduce 30
+ {default} reduce 173
+
+State 68:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ expr ::= * LP expr RP
+ expr ::= LP * expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= LP * select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ SELECT shift 73
+ STRING shift 66
+ expr shift 293
+ nm shift 107
+ oneselect shift 69
+ select shift 70
+
+State 69:
+ (101) select ::= oneselect *
+
+ {default} reduce 101
+
+State 70:
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+ expr ::= LP select * RP
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ RP shift 292
+ UNION shift 167
+ multiselect_op shift 71
+
+State 71:
+ select ::= select multiselect_op * oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+
+ SELECT shift 73
+ oneselect shift 72
+
+State 72:
+ (102) select ::= select multiselect_op oneselect *
+
+ {default} reduce 102
+
+State 73:
+ oneselect ::= SELECT * distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ distinct ::= * DISTINCT
+ distinct ::= * ALL
+ (110) distinct ::= *
+
+ ALL shift 291
+ DISTINCT shift 290
+ distinct shift 74
+ {default} reduce 110
+
+State 74:
+ oneselect ::= SELECT distinct * selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ sclp ::= * selcollist COMMA
+ (112) sclp ::= *
+ selcollist ::= * sclp expr as
+ selcollist ::= * sclp STAR
+ selcollist ::= * sclp nm DOT STAR
+
+ sclp shift 283
+ selcollist shift 75
+ {default} reduce 112
+
+State 75:
+ oneselect ::= SELECT distinct selcollist * from where_opt groupby_opt having_opt orderby_opt limit_opt
+ sclp ::= selcollist * COMMA
+ (119) from ::= *
+ from ::= * FROM seltablist
+
+ COMMA shift 241
+ FROM shift 242
+ from shift 76
+ {default} reduce 119
+
+State 76:
+ oneselect ::= SELECT distinct selcollist from * where_opt groupby_opt having_opt orderby_opt limit_opt
+ (155) where_opt ::= *
+ where_opt ::= * WHERE expr
+
+ WHERE shift 239
+ where_opt shift 77
+ {default} reduce 155
+
+State 77:
+ oneselect ::= SELECT distinct selcollist from where_opt * groupby_opt having_opt orderby_opt limit_opt
+ (146) groupby_opt ::= *
+ groupby_opt ::= * GROUP BY exprlist
+
+ GROUP shift 236
+ groupby_opt shift 78
+ {default} reduce 146
+
+State 78:
+ oneselect ::= SELECT distinct selcollist from where_opt groupby_opt * having_opt orderby_opt limit_opt
+ (148) having_opt ::= *
+ having_opt ::= * HAVING expr
+
+ HAVING shift 234
+ having_opt shift 79
+ {default} reduce 148
+
+State 79:
+ oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt * orderby_opt limit_opt
+ (136) orderby_opt ::= *
+ orderby_opt ::= * ORDER BY sortlist
+
+ ORDER shift 93
+ orderby_opt shift 80
+ {default} reduce 136
+
+State 80:
+ oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt * limit_opt
+ (150) limit_opt ::= *
+ limit_opt ::= * LIMIT signed
+ limit_opt ::= * LIMIT signed OFFSET signed
+ limit_opt ::= * LIMIT signed COMMA signed
+
+ LIMIT shift 82
+ limit_opt shift 81
+ {default} reduce 150
+
+State 81:
+ (107) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt *
+
+ {default} reduce 107
+
+State 82:
+ signed ::= * INTEGER
+ signed ::= * PLUS INTEGER
+ signed ::= * MINUS INTEGER
+ limit_opt ::= LIMIT * signed
+ limit_opt ::= LIMIT * signed OFFSET signed
+ limit_opt ::= LIMIT * signed COMMA signed
+
+ INTEGER shift 83
+ MINUS shift 86
+ PLUS shift 84
+ signed shift 88
+
+State 83:
+ (37) signed ::= INTEGER *
+
+ {default} reduce 37
+
+State 84:
+ signed ::= PLUS * INTEGER
+
+ INTEGER shift 85
+
+State 85:
+ (38) signed ::= PLUS INTEGER *
+
+ {default} reduce 38
+
+State 86:
+ signed ::= MINUS * INTEGER
+
+ INTEGER shift 87
+
+State 87:
+ (39) signed ::= MINUS INTEGER *
+
+ {default} reduce 39
+
+State 88:
+ (151) limit_opt ::= LIMIT signed *
+ limit_opt ::= LIMIT signed * OFFSET signed
+ limit_opt ::= LIMIT signed * COMMA signed
+
+ COMMA shift 91
+ OFFSET shift 89
+ {default} reduce 151
+
+State 89:
+ signed ::= * INTEGER
+ signed ::= * PLUS INTEGER
+ signed ::= * MINUS INTEGER
+ limit_opt ::= LIMIT signed OFFSET * signed
+
+ INTEGER shift 83
+ MINUS shift 86
+ PLUS shift 84
+ signed shift 90
+
+State 90:
+ (152) limit_opt ::= LIMIT signed OFFSET signed *
+
+ {default} reduce 152
+
+State 91:
+ signed ::= * INTEGER
+ signed ::= * PLUS INTEGER
+ signed ::= * MINUS INTEGER
+ limit_opt ::= LIMIT signed COMMA * signed
+
+ INTEGER shift 83
+ MINUS shift 86
+ PLUS shift 84
+ signed shift 92
+
+State 92:
+ (153) limit_opt ::= LIMIT signed COMMA signed *
+
+ {default} reduce 153
+
+State 93:
+ orderby_opt ::= ORDER * BY sortlist
+
+ BY shift 94
+
+State 94:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ orderby_opt ::= ORDER BY * sortlist
+ sortlist ::= * sortlist COMMA sortitem collate sortorder
+ sortlist ::= * sortitem collate sortorder
+ sortitem ::= * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 103
+ nm shift 107
+ sortitem shift 231
+ sortlist shift 95
+
+State 95:
+ (137) orderby_opt ::= ORDER BY sortlist *
+ sortlist ::= sortlist * COMMA sortitem collate sortorder
+
+ COMMA shift 96
+ {default} reduce 137
+
+State 96:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ sortlist ::= sortlist COMMA * sortitem collate sortorder
+ sortitem ::= * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 103
+ nm shift 107
+ sortitem shift 97
+
+State 97:
+ sortlist ::= sortlist COMMA sortitem * collate sortorder
+ (144) collate ::= *
+ collate ::= * COLLATE id
+
+ COLLATE shift 100
+ collate shift 98
+ {default} reduce 144
+
+State 98:
+ sortlist ::= sortlist COMMA sortitem collate * sortorder
+ sortorder ::= * ASC
+ sortorder ::= * DESC
+ (143) sortorder ::= *
+
+ ASC shift 58
+ DESC shift 59
+ sortorder shift 99
+ {default} reduce 143
+
+State 99:
+ (138) sortlist ::= sortlist COMMA sortitem collate sortorder *
+
+ {default} reduce 138
+
+State 100:
+ id ::= * ID
+ collate ::= COLLATE * id
+
+ ID shift 101
+ id shift 102
+
+State 101:
+ (25) id ::= ID *
+
+ {default} reduce 25
+
+State 102:
+ (145) collate ::= COLLATE id *
+
+ {default} reduce 145
+
+State 103:
+ (140) sortitem ::= expr *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 140
+
+State 104:
+ (176) expr ::= expr ORACLE_OUTER_JOIN *
+
+ {default} reduce 176
+
+State 105:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= expr AND * expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 112
+ nm shift 107
+
+State 106:
+ (171) expr ::= NULL *
+
+ {default} reduce 171
+
+State 107:
+ expr ::= nm * DOT nm
+ expr ::= nm * DOT nm DOT nm
+
+ DOT shift 108
+
+State 108:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= nm DOT * nm
+ expr ::= nm DOT * nm DOT nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 109
+
+State 109:
+ (174) expr ::= nm DOT nm *
+ expr ::= nm DOT nm * DOT nm
+
+ DOT shift 110
+ {default} reduce 174
+
+State 110:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= nm DOT nm DOT * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 111
+
+State 111:
+ (175) expr ::= nm DOT nm DOT nm *
+
+ {default} reduce 175
+
+State 112:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ (182) expr ::= expr AND expr *
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 182
+
+State 113:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= expr OR * expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 114
+ nm shift 107
+
+State 114:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ (183) expr ::= expr OR expr *
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 183
+
+State 115:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= expr LT * expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 116
+ nm shift 107
+
+State 116:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ (184) expr ::= expr LT expr *
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ LSHIFT shift 131
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 184
+
+State 117:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= expr GT * expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 118
+ nm shift 107
+
+State 118:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ (185) expr ::= expr GT expr *
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ LSHIFT shift 131
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 185
+
+State 119:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= expr LE * expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 120
+ nm shift 107
+
+State 120:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ (186) expr ::= expr LE expr *
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ LSHIFT shift 131
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 186
+
+State 121:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= expr GE * expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 122
+ nm shift 107
+
+State 122:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ (187) expr ::= expr GE expr *
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ LSHIFT shift 131
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 187
+
+State 123:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= expr NE * expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 124
+ nm shift 107
+
+State 124:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ (188) expr ::= expr NE expr *
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ GE shift 121
+ GT shift 117
+ LE shift 119
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 188
+
+State 125:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= expr EQ * expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 126
+ nm shift 107
+
+State 126:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ (189) expr ::= expr EQ expr *
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ GE shift 121
+ GT shift 117
+ LE shift 119
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 189
+
+State 127:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= expr BITAND * expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 128
+ nm shift 107
+
+State 128:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ (190) expr ::= expr BITAND expr *
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 190
+
+State 129:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= expr BITOR * expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 130
+ nm shift 107
+
+State 130:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ (191) expr ::= expr BITOR expr *
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 191
+
+State 131:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= expr LSHIFT * expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 132
+ nm shift 107
+
+State 132:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ (192) expr ::= expr LSHIFT expr *
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 192
+
+State 133:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= expr RSHIFT * expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 134
+ nm shift 107
+
+State 134:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ (193) expr ::= expr RSHIFT expr *
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 193
+
+State 135:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= expr likeop * expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 136
+ nm shift 107
+
+State 136:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ (194) expr ::= expr likeop expr *
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ GE shift 121
+ GT shift 117
+ LE shift 119
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 194
+
+State 137:
+ expr ::= expr NOT * likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr NOT * NULL
+ expr ::= expr NOT * BETWEEN expr AND expr
+ expr ::= expr NOT * IN LP exprlist RP
+ expr ::= expr NOT * IN LP select RP
+
+ BETWEEN shift 221
+ GLOB shift 141
+ IN shift 225
+ LIKE shift 140
+ NULL shift 220
+ likeop shift 138
+
+State 138:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= expr NOT likeop * expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 139
+ nm shift 107
+
+State 139:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ (195) expr ::= expr NOT likeop expr *
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ GE shift 121
+ GT shift 117
+ LE shift 119
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 195
+
+State 140:
+ (196) likeop ::= LIKE *
+
+ {default} reduce 196
+
+State 141:
+ (197) likeop ::= GLOB *
+
+ {default} reduce 197
+
+State 142:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= expr PLUS * expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 143
+ nm shift 107
+
+State 143:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ (198) expr ::= expr PLUS expr *
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ ORACLE_OUTER_JOIN shift 104
+ REM shift 150
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 198
+
+State 144:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= expr MINUS * expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 145
+ nm shift 107
+
+State 145:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ (199) expr ::= expr MINUS expr *
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ ORACLE_OUTER_JOIN shift 104
+ REM shift 150
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 199
+
+State 146:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= expr STAR * expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 147
+ nm shift 107
+
+State 147:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ (200) expr ::= expr STAR expr *
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ ORACLE_OUTER_JOIN shift 104
+ likeop shift 135
+ {default} reduce 200
+
+State 148:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= expr SLASH * expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 149
+ nm shift 107
+
+State 149:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ (201) expr ::= expr SLASH expr *
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ ORACLE_OUTER_JOIN shift 104
+ likeop shift 135
+ {default} reduce 201
+
+State 150:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= expr REM * expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 151
+ nm shift 107
+
+State 151:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ (202) expr ::= expr REM expr *
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ CONCAT shift 152
+ ORACLE_OUTER_JOIN shift 104
+ likeop shift 135
+ {default} reduce 202
+
+State 152:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= expr CONCAT * expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 153
+ nm shift 107
+
+State 153:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ (203) expr ::= expr CONCAT expr *
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ ORACLE_OUTER_JOIN shift 104
+ likeop shift 135
+ {default} reduce 203
+
+State 154:
+ (204) expr ::= expr ISNULL *
+
+ {default} reduce 204
+
+State 155:
+ expr ::= expr IS * NULL
+ expr ::= expr IS * NOT NULL
+
+ NOT shift 157
+ NULL shift 156
+
+State 156:
+ (205) expr ::= expr IS NULL *
+
+ {default} reduce 205
+
+State 157:
+ expr ::= expr IS NOT * NULL
+
+ NULL shift 158
+
+State 158:
+ (208) expr ::= expr IS NOT NULL *
+
+ {default} reduce 208
+
+State 159:
+ (206) expr ::= expr NOTNULL *
+
+ {default} reduce 206
+
+State 160:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= expr BETWEEN * expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 161
+ nm shift 107
+
+State 161:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr BETWEEN expr * AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 162
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+
+State 162:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= expr AND * expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= expr BETWEEN expr AND * expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 163
+ nm shift 107
+
+State 163:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ (182) expr ::= expr AND expr *
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ (214) expr ::= expr BETWEEN expr AND expr *
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ GE shift 121
+ GT shift 117
+ LE shift 119
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 214
+
+State 164:
+ expr ::= expr IN * LP exprlist RP
+ expr ::= expr IN * LP select RP
+
+ LP shift 165
+
+State 165:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= expr IN LP * exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= expr IN LP * select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ exprlist ::= * exprlist COMMA expritem
+ exprlist ::= * expritem
+ expritem ::= * expr
+ (230) expritem ::= *
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ SELECT shift 73
+ STRING shift 66
+ expr shift 172
+ expritem shift 219
+ exprlist shift 215
+ nm shift 107
+ oneselect shift 69
+ select shift 166
+ {default} reduce 230
+
+State 166:
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+ expr ::= expr IN LP select * RP
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ RP shift 171
+ UNION shift 167
+ multiselect_op shift 71
+
+State 167:
+ (103) multiselect_op ::= UNION *
+ multiselect_op ::= UNION * ALL
+
+ ALL shift 168
+ SELECT reduce 103
+
+State 168:
+ (104) multiselect_op ::= UNION ALL *
+
+ SELECT reduce 104
+
+State 169:
+ (105) multiselect_op ::= INTERSECT *
+
+ SELECT reduce 105
+
+State 170:
+ (106) multiselect_op ::= EXCEPT *
+
+ SELECT reduce 106
+
+State 171:
+ (217) expr ::= expr IN LP select RP *
+
+ {default} reduce 217
+
+State 172:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+ (229) expritem ::= expr *
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 229
+
+State 173:
+ (177) expr ::= INTEGER *
+
+ {default} reduce 177
+
+State 174:
+ (178) expr ::= FLOAT *
+
+ {default} reduce 178
+
+State 175:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= NOT * expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 176
+ nm shift 107
+
+State 176:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ (209) expr ::= NOT expr *
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 209
+
+State 177:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= BITNOT * expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 178
+ nm shift 107
+
+State 178:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ (210) expr ::= BITNOT expr *
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ ORACLE_OUTER_JOIN shift 104
+ likeop shift 135
+ {default} reduce 210
+
+State 179:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= MINUS * expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 180
+ nm shift 107
+
+State 180:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ (211) expr ::= MINUS expr *
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ ORACLE_OUTER_JOIN shift 104
+ likeop shift 135
+ {default} reduce 211
+
+State 181:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= PLUS * expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 182
+ nm shift 107
+
+State 182:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ (212) expr ::= PLUS expr *
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ ORACLE_OUTER_JOIN shift 104
+ likeop shift 135
+ {default} reduce 212
+
+State 183:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= CASE * case_operand case_exprlist case_else END
+ case_operand ::= * expr
+ (226) case_operand ::= *
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ WHEN reduce 226
+ case_operand shift 185
+ expr shift 184
+ nm shift 107
+
+State 184:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+ (225) case_operand ::= expr *
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ WHEN reduce 225
+ likeop shift 135
+
+State 185:
+ expr ::= CASE case_operand * case_exprlist case_else END
+ case_exprlist ::= * case_exprlist WHEN expr THEN expr
+ case_exprlist ::= * WHEN expr THEN expr
+
+ WHEN shift 211
+ case_exprlist shift 186
+
+State 186:
+ expr ::= CASE case_operand case_exprlist * case_else END
+ case_exprlist ::= case_exprlist * WHEN expr THEN expr
+ case_else ::= * ELSE expr
+ (224) case_else ::= *
+
+ ELSE shift 209
+ END reduce 224
+ WHEN shift 189
+ case_else shift 187
+
+State 187:
+ expr ::= CASE case_operand case_exprlist case_else * END
+
+ END shift 188
+
+State 188:
+ (220) expr ::= CASE case_operand case_exprlist case_else END *
+
+ {default} reduce 220
+
+State 189:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ case_exprlist ::= case_exprlist WHEN * expr THEN expr
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 190
+ nm shift 107
+
+State 190:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+ case_exprlist ::= case_exprlist WHEN expr * THEN expr
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ THEN shift 191
+ likeop shift 135
+
+State 191:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ case_exprlist ::= case_exprlist WHEN expr THEN * expr
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 192
+ nm shift 107
+
+State 192:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+ (221) case_exprlist ::= case_exprlist WHEN expr THEN expr *
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 221
+
+State 193:
+ expr ::= RAISE * LP IGNORE RP
+ expr ::= RAISE * LP ROLLBACK COMMA nm RP
+ expr ::= RAISE * LP ABORT COMMA nm RP
+ expr ::= RAISE * LP FAIL COMMA nm RP
+
+ LP shift 194
+
+State 194:
+ expr ::= RAISE LP * IGNORE RP
+ expr ::= RAISE LP * ROLLBACK COMMA nm RP
+ expr ::= RAISE LP * ABORT COMMA nm RP
+ expr ::= RAISE LP * FAIL COMMA nm RP
+
+ ABORT shift 201
+ FAIL shift 205
+ IGNORE shift 195
+ ROLLBACK shift 197
+
+State 195:
+ expr ::= RAISE LP IGNORE * RP
+
+ RP shift 196
+
+State 196:
+ (278) expr ::= RAISE LP IGNORE RP *
+
+ {default} reduce 278
+
+State 197:
+ expr ::= RAISE LP ROLLBACK * COMMA nm RP
+
+ COMMA shift 198
+
+State 198:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= RAISE LP ROLLBACK COMMA * nm RP
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 199
+
+State 199:
+ expr ::= RAISE LP ROLLBACK COMMA nm * RP
+
+ RP shift 200
+
+State 200:
+ (279) expr ::= RAISE LP ROLLBACK COMMA nm RP *
+
+ {default} reduce 279
+
+State 201:
+ expr ::= RAISE LP ABORT * COMMA nm RP
+
+ COMMA shift 202
+
+State 202:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= RAISE LP ABORT COMMA * nm RP
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 203
+
+State 203:
+ expr ::= RAISE LP ABORT COMMA nm * RP
+
+ RP shift 204
+
+State 204:
+ (280) expr ::= RAISE LP ABORT COMMA nm RP *
+
+ {default} reduce 280
+
+State 205:
+ expr ::= RAISE LP FAIL * COMMA nm RP
+
+ COMMA shift 206
+
+State 206:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= RAISE LP FAIL COMMA * nm RP
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 207
+
+State 207:
+ expr ::= RAISE LP FAIL COMMA nm * RP
+
+ RP shift 208
+
+State 208:
+ (281) expr ::= RAISE LP FAIL COMMA nm RP *
+
+ {default} reduce 281
+
+State 209:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ case_else ::= ELSE * expr
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 210
+ nm shift 107
+
+State 210:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+ (223) case_else ::= ELSE expr *
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ END reduce 223
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+
+State 211:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ case_exprlist ::= WHEN * expr THEN expr
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 212
+ nm shift 107
+
+State 212:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+ case_exprlist ::= WHEN expr * THEN expr
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ THEN shift 213
+ likeop shift 135
+
+State 213:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ case_exprlist ::= WHEN expr THEN * expr
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 214
+ nm shift 107
+
+State 214:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+ (222) case_exprlist ::= WHEN expr THEN expr *
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 222
+
+State 215:
+ expr ::= expr IN LP exprlist * RP
+ exprlist ::= exprlist * COMMA expritem
+
+ COMMA shift 217
+ RP shift 216
+
+State 216:
+ (216) expr ::= expr IN LP exprlist RP *
+
+ {default} reduce 216
+
+State 217:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ exprlist ::= exprlist COMMA * expritem
+ expritem ::= * expr
+ (230) expritem ::= *
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 172
+ expritem shift 218
+ nm shift 107
+ {default} reduce 230
+
+State 218:
+ (227) exprlist ::= exprlist COMMA expritem *
+
+ {default} reduce 227
+
+State 219:
+ (228) exprlist ::= expritem *
+
+ {default} reduce 228
+
+State 220:
+ (207) expr ::= expr NOT NULL *
+
+ {default} reduce 207
+
+State 221:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= expr NOT BETWEEN * expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 222
+ nm shift 107
+
+State 222:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr NOT BETWEEN expr * AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 223
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+
+State 223:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= expr AND * expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= expr NOT BETWEEN expr AND * expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 224
+ nm shift 107
+
+State 224:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ (182) expr ::= expr AND expr *
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ (215) expr ::= expr NOT BETWEEN expr AND expr *
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 215
+
+State 225:
+ expr ::= expr NOT IN * LP exprlist RP
+ expr ::= expr NOT IN * LP select RP
+
+ LP shift 226
+
+State 226:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= expr NOT IN LP * exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= expr NOT IN LP * select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ exprlist ::= * exprlist COMMA expritem
+ exprlist ::= * expritem
+ expritem ::= * expr
+ (230) expritem ::= *
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ SELECT shift 73
+ STRING shift 66
+ expr shift 172
+ expritem shift 219
+ exprlist shift 229
+ nm shift 107
+ oneselect shift 69
+ select shift 227
+ {default} reduce 230
+
+State 227:
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+ expr ::= expr NOT IN LP select * RP
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ RP shift 228
+ UNION shift 167
+ multiselect_op shift 71
+
+State 228:
+ (219) expr ::= expr NOT IN LP select RP *
+
+ {default} reduce 219
+
+State 229:
+ expr ::= expr NOT IN LP exprlist * RP
+ exprlist ::= exprlist * COMMA expritem
+
+ COMMA shift 217
+ RP shift 230
+
+State 230:
+ (218) expr ::= expr NOT IN LP exprlist RP *
+
+ {default} reduce 218
+
+State 231:
+ sortlist ::= sortitem * collate sortorder
+ (144) collate ::= *
+ collate ::= * COLLATE id
+
+ COLLATE shift 100
+ collate shift 232
+ {default} reduce 144
+
+State 232:
+ sortlist ::= sortitem collate * sortorder
+ sortorder ::= * ASC
+ sortorder ::= * DESC
+ (143) sortorder ::= *
+
+ ASC shift 58
+ DESC shift 59
+ sortorder shift 233
+ {default} reduce 143
+
+State 233:
+ (139) sortlist ::= sortitem collate sortorder *
+
+ {default} reduce 139
+
+State 234:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ having_opt ::= HAVING * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 235
+ nm shift 107
+
+State 235:
+ (149) having_opt ::= HAVING expr *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 149
+
+State 236:
+ groupby_opt ::= GROUP * BY exprlist
+
+ BY shift 237
+
+State 237:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ groupby_opt ::= GROUP BY * exprlist
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ exprlist ::= * exprlist COMMA expritem
+ exprlist ::= * expritem
+ expritem ::= * expr
+ (230) expritem ::= *
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 172
+ expritem shift 219
+ exprlist shift 238
+ nm shift 107
+ {default} reduce 230
+
+State 238:
+ (147) groupby_opt ::= GROUP BY exprlist *
+ exprlist ::= exprlist * COMMA expritem
+
+ COMMA shift 217
+ {default} reduce 147
+
+State 239:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ where_opt ::= WHERE * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 240
+ nm shift 107
+
+State 240:
+ (156) where_opt ::= WHERE expr *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 156
+
+State 241:
+ (111) sclp ::= selcollist COMMA *
+
+ {default} reduce 111
+
+State 242:
+ from ::= FROM * seltablist
+ stl_prefix ::= * seltablist joinop
+ (122) stl_prefix ::= *
+ seltablist ::= * stl_prefix nm dbnm as on_opt using_opt
+ seltablist ::= * stl_prefix LP select RP as on_opt using_opt
+
+ seltablist shift 243
+ stl_prefix shift 253
+ {default} reduce 122
+
+State 243:
+ (120) from ::= FROM seltablist *
+ stl_prefix ::= seltablist * joinop
+ joinop ::= * COMMA
+ joinop ::= * JOIN
+ joinop ::= * JOIN_KW JOIN
+ joinop ::= * JOIN_KW nm JOIN
+ joinop ::= * JOIN_KW nm nm JOIN
+
+ COMMA shift 245
+ JOIN shift 246
+ JOIN_KW shift 247
+ joinop shift 244
+ {default} reduce 120
+
+State 244:
+ (121) stl_prefix ::= seltablist joinop *
+
+ {default} reduce 121
+
+State 245:
+ (127) joinop ::= COMMA *
+
+ {default} reduce 127
+
+State 246:
+ (128) joinop ::= JOIN *
+
+ {default} reduce 128
+
+State 247:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ joinop ::= JOIN_KW * JOIN
+ joinop ::= JOIN_KW * nm JOIN
+ joinop ::= JOIN_KW * nm nm JOIN
+
+ ID shift 20
+ JOIN shift 248
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 249
+
+State 248:
+ (129) joinop ::= JOIN_KW JOIN *
+
+ {default} reduce 129
+
+State 249:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ joinop ::= JOIN_KW nm * JOIN
+ joinop ::= JOIN_KW nm * nm JOIN
+
+ ID shift 20
+ JOIN shift 250
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 251
+
+State 250:
+ (130) joinop ::= JOIN_KW nm JOIN *
+
+ {default} reduce 130
+
+State 251:
+ joinop ::= JOIN_KW nm nm * JOIN
+
+ JOIN shift 252
+
+State 252:
+ (131) joinop ::= JOIN_KW nm nm JOIN *
+
+ {default} reduce 131
+
+State 253:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ seltablist ::= stl_prefix * nm dbnm as on_opt using_opt
+ seltablist ::= stl_prefix * LP select RP as on_opt using_opt
+
+ ID shift 20
+ JOIN_KW shift 22
+ LP shift 277
+ STRING shift 21
+ nm shift 254
+
+State 254:
+ seltablist ::= stl_prefix nm * dbnm as on_opt using_opt
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+
+ DOT shift 275
+ dbnm shift 255
+ {default} reduce 125
+
+State 255:
+ ids ::= * ID
+ ids ::= * STRING
+ as ::= * AS nm
+ as ::= * ids
+ (118) as ::= *
+ seltablist ::= stl_prefix nm dbnm * as on_opt using_opt
+
+ AS shift 258
+ ID shift 256
+ STRING shift 257
+ as shift 261
+ ids shift 260
+ {default} reduce 118
+
+State 256:
+ (26) ids ::= ID *
+
+ {default} reduce 26
+
+State 257:
+ (27) ids ::= STRING *
+
+ {default} reduce 27
+
+State 258:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ as ::= AS * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 259
+
+State 259:
+ (116) as ::= AS nm *
+
+ {default} reduce 116
+
+State 260:
+ (117) as ::= ids *
+
+ {default} reduce 117
+
+State 261:
+ seltablist ::= stl_prefix nm dbnm as * on_opt using_opt
+ on_opt ::= * ON expr
+ (133) on_opt ::= *
+
+ ON shift 273
+ on_opt shift 262
+ {default} reduce 133
+
+State 262:
+ seltablist ::= stl_prefix nm dbnm as on_opt * using_opt
+ using_opt ::= * USING LP idxlist RP
+ (135) using_opt ::= *
+
+ USING shift 264
+ using_opt shift 263
+ {default} reduce 135
+
+State 263:
+ (123) seltablist ::= stl_prefix nm dbnm as on_opt using_opt *
+
+ {default} reduce 123
+
+State 264:
+ using_opt ::= USING * LP idxlist RP
+
+ LP shift 265
+
+State 265:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ using_opt ::= USING LP * idxlist RP
+ idxlist ::= * idxlist COMMA idxitem
+ idxlist ::= * idxitem
+ idxitem ::= * nm sortorder
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ idxitem shift 272
+ idxlist shift 266
+ nm shift 270
+
+State 266:
+ using_opt ::= USING LP idxlist * RP
+ idxlist ::= idxlist * COMMA idxitem
+
+ COMMA shift 268
+ RP shift 267
+
+State 267:
+ (134) using_opt ::= USING LP idxlist RP *
+
+ {default} reduce 134
+
+State 268:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ idxlist ::= idxlist COMMA * idxitem
+ idxitem ::= * nm sortorder
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ idxitem shift 269
+ nm shift 270
+
+State 269:
+ (236) idxlist ::= idxlist COMMA idxitem *
+
+ {default} reduce 236
+
+State 270:
+ sortorder ::= * ASC
+ sortorder ::= * DESC
+ (143) sortorder ::= *
+ idxitem ::= nm * sortorder
+
+ ASC shift 58
+ DESC shift 59
+ sortorder shift 271
+ {default} reduce 143
+
+State 271:
+ (238) idxitem ::= nm sortorder *
+
+ {default} reduce 238
+
+State 272:
+ (237) idxlist ::= idxitem *
+
+ {default} reduce 237
+
+State 273:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ on_opt ::= ON * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 274
+ nm shift 107
+
+State 274:
+ (132) on_opt ::= ON expr *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 132
+
+State 275:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ dbnm ::= DOT * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 276
+
+State 276:
+ (126) dbnm ::= DOT nm *
+
+ {default} reduce 126
+
+State 277:
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ seltablist ::= stl_prefix LP * select RP as on_opt using_opt
+
+ SELECT shift 73
+ oneselect shift 69
+ select shift 278
+
+State 278:
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+ seltablist ::= stl_prefix LP select * RP as on_opt using_opt
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ RP shift 279
+ UNION shift 167
+ multiselect_op shift 71
+
+State 279:
+ ids ::= * ID
+ ids ::= * STRING
+ as ::= * AS nm
+ as ::= * ids
+ (118) as ::= *
+ seltablist ::= stl_prefix LP select RP * as on_opt using_opt
+
+ AS shift 258
+ ID shift 256
+ STRING shift 257
+ as shift 280
+ ids shift 260
+ {default} reduce 118
+
+State 280:
+ seltablist ::= stl_prefix LP select RP as * on_opt using_opt
+ on_opt ::= * ON expr
+ (133) on_opt ::= *
+
+ ON shift 273
+ on_opt shift 281
+ {default} reduce 133
+
+State 281:
+ seltablist ::= stl_prefix LP select RP as on_opt * using_opt
+ using_opt ::= * USING LP idxlist RP
+ (135) using_opt ::= *
+
+ USING shift 264
+ using_opt shift 282
+ {default} reduce 135
+
+State 282:
+ (124) seltablist ::= stl_prefix LP select RP as on_opt using_opt *
+
+ {default} reduce 124
+
+State 283:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ selcollist ::= sclp * expr as
+ selcollist ::= sclp * STAR
+ selcollist ::= sclp * nm DOT STAR
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STAR shift 286
+ STRING shift 66
+ expr shift 284
+ nm shift 287
+
+State 284:
+ ids ::= * ID
+ ids ::= * STRING
+ selcollist ::= sclp expr * as
+ as ::= * AS nm
+ as ::= * ids
+ (118) as ::= *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ AS shift 258
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ ID shift 256
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ STRING shift 257
+ as shift 285
+ ids shift 260
+ likeop shift 135
+ {default} reduce 118
+
+State 285:
+ (113) selcollist ::= sclp expr as *
+
+ {default} reduce 113
+
+State 286:
+ (114) selcollist ::= sclp STAR *
+
+ {default} reduce 114
+
+State 287:
+ selcollist ::= sclp nm * DOT STAR
+ expr ::= nm * DOT nm
+ expr ::= nm * DOT nm DOT nm
+
+ DOT shift 288
+
+State 288:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ selcollist ::= sclp nm DOT * STAR
+ expr ::= nm DOT * nm
+ expr ::= nm DOT * nm DOT nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STAR shift 289
+ STRING shift 21
+ nm shift 109
+
+State 289:
+ (115) selcollist ::= sclp nm DOT STAR *
+
+ {default} reduce 115
+
+State 290:
+ (108) distinct ::= DISTINCT *
+
+ {default} reduce 108
+
+State 291:
+ (109) distinct ::= ALL *
+
+ {default} reduce 109
+
+State 292:
+ (213) expr ::= LP select RP *
+
+ {default} reduce 213
+
+State 293:
+ expr ::= LP expr * RP
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RP shift 294
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+
+State 294:
+ (170) expr ::= LP expr RP *
+
+ {default} reduce 170
+
+State 295:
+ expr ::= ID LP exprlist * RP
+ exprlist ::= exprlist * COMMA expritem
+
+ COMMA shift 217
+ RP shift 296
+
+State 296:
+ (180) expr ::= ID LP exprlist RP *
+
+ {default} reduce 180
+
+State 297:
+ expr ::= ID LP STAR * RP
+
+ RP shift 298
+
+State 298:
+ (181) expr ::= ID LP STAR RP *
+
+ {default} reduce 181
+
+State 299:
+ ccons ::= CHECK LP expr * RP onconf
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RP shift 300
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+
+State 300:
+ ccons ::= CHECK LP expr RP * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+
+ ON shift 10
+ onconf shift 301
+ {default} reduce 88
+
+State 301:
+ (57) ccons ::= CHECK LP expr RP onconf *
+
+ {default} reduce 57
+
+State 302:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ ccons ::= REFERENCES * nm idxlist_opt refargs
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 303
+
+State 303:
+ ccons ::= REFERENCES nm * idxlist_opt refargs
+ (234) idxlist_opt ::= *
+ idxlist_opt ::= * LP idxlist RP
+
+ LP shift 321
+ idxlist_opt shift 304
+ {default} reduce 234
+
+State 304:
+ ccons ::= REFERENCES nm idxlist_opt * refargs
+ (61) refargs ::= *
+ refargs ::= * refargs refarg
+
+ refargs shift 305
+ {default} reduce 61
+
+State 305:
+ (58) ccons ::= REFERENCES nm idxlist_opt refargs *
+ refargs ::= refargs * refarg
+ refarg ::= * MATCH nm
+ refarg ::= * ON DELETE refact
+ refarg ::= * ON UPDATE refact
+ refarg ::= * ON INSERT refact
+
+ MATCH shift 307
+ ON shift 309
+ refarg shift 306
+ {default} reduce 58
+
+State 306:
+ (62) refargs ::= refargs refarg *
+
+ {default} reduce 62
+
+State 307:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ refarg ::= MATCH * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 308
+
+State 308:
+ (63) refarg ::= MATCH nm *
+
+ {default} reduce 63
+
+State 309:
+ refarg ::= ON * DELETE refact
+ refarg ::= ON * UPDATE refact
+ refarg ::= ON * INSERT refact
+
+ DELETE shift 310
+ INSERT shift 319
+ UPDATE shift 317
+
+State 310:
+ refarg ::= ON DELETE * refact
+ refact ::= * SET NULL
+ refact ::= * SET DEFAULT
+ refact ::= * CASCADE
+ refact ::= * RESTRICT
+
+ CASCADE shift 315
+ RESTRICT shift 316
+ SET shift 312
+ refact shift 311
+
+State 311:
+ (64) refarg ::= ON DELETE refact *
+
+ {default} reduce 64
+
+State 312:
+ refact ::= SET * NULL
+ refact ::= SET * DEFAULT
+
+ DEFAULT shift 314
+ NULL shift 313
+
+State 313:
+ (67) refact ::= SET NULL *
+
+ {default} reduce 67
+
+State 314:
+ (68) refact ::= SET DEFAULT *
+
+ {default} reduce 68
+
+State 315:
+ (69) refact ::= CASCADE *
+
+ {default} reduce 69
+
+State 316:
+ (70) refact ::= RESTRICT *
+
+ {default} reduce 70
+
+State 317:
+ refarg ::= ON UPDATE * refact
+ refact ::= * SET NULL
+ refact ::= * SET DEFAULT
+ refact ::= * CASCADE
+ refact ::= * RESTRICT
+
+ CASCADE shift 315
+ RESTRICT shift 316
+ SET shift 312
+ refact shift 318
+
+State 318:
+ (65) refarg ::= ON UPDATE refact *
+
+ {default} reduce 65
+
+State 319:
+ refarg ::= ON INSERT * refact
+ refact ::= * SET NULL
+ refact ::= * SET DEFAULT
+ refact ::= * CASCADE
+ refact ::= * RESTRICT
+
+ CASCADE shift 315
+ RESTRICT shift 316
+ SET shift 312
+ refact shift 320
+
+State 320:
+ (66) refarg ::= ON INSERT refact *
+
+ {default} reduce 66
+
+State 321:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ idxlist_opt ::= LP * idxlist RP
+ idxlist ::= * idxlist COMMA idxitem
+ idxlist ::= * idxitem
+ idxitem ::= * nm sortorder
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ idxitem shift 272
+ idxlist shift 322
+ nm shift 270
+
+State 322:
+ idxlist_opt ::= LP idxlist * RP
+ idxlist ::= idxlist * COMMA idxitem
+
+ COMMA shift 268
+ RP shift 323
+
+State 323:
+ (235) idxlist_opt ::= LP idxlist RP *
+
+ {default} reduce 235
+
+State 324:
+ (59) ccons ::= defer_subclause *
+
+ {default} reduce 59
+
+State 325:
+ id ::= * ID
+ ccons ::= COLLATE * id
+
+ ID shift 101
+ id shift 326
+
+State 326:
+ (60) ccons ::= COLLATE id *
+
+ {default} reduce 60
+
+State 327:
+ defer_subclause ::= DEFERRABLE * init_deferred_pred_opt
+ (73) init_deferred_pred_opt ::= *
+ init_deferred_pred_opt ::= * INITIALLY DEFERRED
+ init_deferred_pred_opt ::= * INITIALLY IMMEDIATE
+
+ INITIALLY shift 51
+ init_deferred_pred_opt shift 328
+ {default} reduce 73
+
+State 328:
+ (72) defer_subclause ::= DEFERRABLE init_deferred_pred_opt *
+
+ {default} reduce 72
+
+State 329:
+ (43) carg ::= ccons *
+
+ {default} reduce 43
+
+State 330:
+ carg ::= DEFAULT * STRING
+ carg ::= DEFAULT * ID
+ carg ::= DEFAULT * INTEGER
+ carg ::= DEFAULT * PLUS INTEGER
+ carg ::= DEFAULT * MINUS INTEGER
+ carg ::= DEFAULT * FLOAT
+ carg ::= DEFAULT * PLUS FLOAT
+ carg ::= DEFAULT * MINUS FLOAT
+ carg ::= DEFAULT * NULL
+
+ FLOAT shift 340
+ ID shift 332
+ INTEGER shift 333
+ MINUS shift 337
+ NULL shift 341
+ PLUS shift 334
+ STRING shift 331
+
+State 331:
+ (44) carg ::= DEFAULT STRING *
+
+ {default} reduce 44
+
+State 332:
+ (45) carg ::= DEFAULT ID *
+
+ {default} reduce 45
+
+State 333:
+ (46) carg ::= DEFAULT INTEGER *
+
+ {default} reduce 46
+
+State 334:
+ carg ::= DEFAULT PLUS * INTEGER
+ carg ::= DEFAULT PLUS * FLOAT
+
+ FLOAT shift 336
+ INTEGER shift 335
+
+State 335:
+ (47) carg ::= DEFAULT PLUS INTEGER *
+
+ {default} reduce 47
+
+State 336:
+ (50) carg ::= DEFAULT PLUS FLOAT *
+
+ {default} reduce 50
+
+State 337:
+ carg ::= DEFAULT MINUS * INTEGER
+ carg ::= DEFAULT MINUS * FLOAT
+
+ FLOAT shift 339
+ INTEGER shift 338
+
+State 338:
+ (48) carg ::= DEFAULT MINUS INTEGER *
+
+ {default} reduce 48
+
+State 339:
+ (51) carg ::= DEFAULT MINUS FLOAT *
+
+ {default} reduce 51
+
+State 340:
+ (49) carg ::= DEFAULT FLOAT *
+
+ {default} reduce 49
+
+State 341:
+ (52) carg ::= DEFAULT NULL *
+
+ {default} reduce 52
+
+State 342:
+ ids ::= * ID
+ ids ::= * STRING
+ (32) type ::= typename *
+ type ::= typename * LP signed RP
+ type ::= typename * LP signed COMMA signed RP
+ typename ::= typename * ids
+
+ ID shift 256
+ LP shift 343
+ STRING shift 257
+ ids shift 349
+ {default} reduce 32
+
+State 343:
+ type ::= typename LP * signed RP
+ type ::= typename LP * signed COMMA signed RP
+ signed ::= * INTEGER
+ signed ::= * PLUS INTEGER
+ signed ::= * MINUS INTEGER
+
+ INTEGER shift 83
+ MINUS shift 86
+ PLUS shift 84
+ signed shift 344
+
+State 344:
+ type ::= typename LP signed * RP
+ type ::= typename LP signed * COMMA signed RP
+
+ COMMA shift 346
+ RP shift 345
+
+State 345:
+ (33) type ::= typename LP signed RP *
+
+ {default} reduce 33
+
+State 346:
+ type ::= typename LP signed COMMA * signed RP
+ signed ::= * INTEGER
+ signed ::= * PLUS INTEGER
+ signed ::= * MINUS INTEGER
+
+ INTEGER shift 83
+ MINUS shift 86
+ PLUS shift 84
+ signed shift 347
+
+State 347:
+ type ::= typename LP signed COMMA signed * RP
+
+ RP shift 348
+
+State 348:
+ (34) type ::= typename LP signed COMMA signed RP *
+
+ {default} reduce 34
+
+State 349:
+ (36) typename ::= typename ids *
+
+ {default} reduce 36
+
+State 350:
+ (35) typename ::= ids *
+
+ {default} reduce 35
+
+State 351:
+ (24) columnid ::= nm *
+
+ {default} reduce 24
+
+State 352:
+ (77) conslist_opt ::= COMMA conslist *
+ conslist ::= conslist * COMMA tcons
+ conslist ::= conslist * tcons
+ tcons ::= * CONSTRAINT nm
+ tcons ::= * PRIMARY KEY LP idxlist RP onconf
+ tcons ::= * UNIQUE LP idxlist RP onconf
+ tcons ::= * CHECK expr onconf
+ tcons ::= * FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt
+
+ CHECK shift 368
+ COMMA shift 353
+ CONSTRAINT shift 355
+ FOREIGN shift 371
+ PRIMARY shift 357
+ RP reduce 77
+ UNIQUE shift 363
+ tcons shift 383
+
+State 353:
+ conslist ::= conslist COMMA * tcons
+ tcons ::= * CONSTRAINT nm
+ tcons ::= * PRIMARY KEY LP idxlist RP onconf
+ tcons ::= * UNIQUE LP idxlist RP onconf
+ tcons ::= * CHECK expr onconf
+ tcons ::= * FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt
+
+ CHECK shift 368
+ CONSTRAINT shift 355
+ FOREIGN shift 371
+ PRIMARY shift 357
+ UNIQUE shift 363
+ tcons shift 354
+
+State 354:
+ (78) conslist ::= conslist COMMA tcons *
+
+ {default} reduce 78
+
+State 355:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ tcons ::= CONSTRAINT * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 356
+
+State 356:
+ (81) tcons ::= CONSTRAINT nm *
+
+ {default} reduce 81
+
+State 357:
+ tcons ::= PRIMARY * KEY LP idxlist RP onconf
+
+ KEY shift 358
+
+State 358:
+ tcons ::= PRIMARY KEY * LP idxlist RP onconf
+
+ LP shift 359
+
+State 359:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ tcons ::= PRIMARY KEY LP * idxlist RP onconf
+ idxlist ::= * idxlist COMMA idxitem
+ idxlist ::= * idxitem
+ idxitem ::= * nm sortorder
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ idxitem shift 272
+ idxlist shift 360
+ nm shift 270
+
+State 360:
+ tcons ::= PRIMARY KEY LP idxlist * RP onconf
+ idxlist ::= idxlist * COMMA idxitem
+
+ COMMA shift 268
+ RP shift 361
+
+State 361:
+ tcons ::= PRIMARY KEY LP idxlist RP * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+
+ ON shift 10
+ onconf shift 362
+ {default} reduce 88
+
+State 362:
+ (82) tcons ::= PRIMARY KEY LP idxlist RP onconf *
+
+ {default} reduce 82
+
+State 363:
+ tcons ::= UNIQUE * LP idxlist RP onconf
+
+ LP shift 364
+
+State 364:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ tcons ::= UNIQUE LP * idxlist RP onconf
+ idxlist ::= * idxlist COMMA idxitem
+ idxlist ::= * idxitem
+ idxitem ::= * nm sortorder
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ idxitem shift 272
+ idxlist shift 365
+ nm shift 270
+
+State 365:
+ tcons ::= UNIQUE LP idxlist * RP onconf
+ idxlist ::= idxlist * COMMA idxitem
+
+ COMMA shift 268
+ RP shift 366
+
+State 366:
+ tcons ::= UNIQUE LP idxlist RP * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+
+ ON shift 10
+ onconf shift 367
+ {default} reduce 88
+
+State 367:
+ (83) tcons ::= UNIQUE LP idxlist RP onconf *
+
+ {default} reduce 83
+
+State 368:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ tcons ::= CHECK * expr onconf
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 369
+ nm shift 107
+
+State 369:
+ tcons ::= CHECK expr * onconf
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ ON shift 10
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ onconf shift 370
+ {default} reduce 88
+
+State 370:
+ (84) tcons ::= CHECK expr onconf *
+
+ {default} reduce 84
+
+State 371:
+ tcons ::= FOREIGN * KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt
+
+ KEY shift 372
+
+State 372:
+ tcons ::= FOREIGN KEY * LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt
+
+ LP shift 373
+
+State 373:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ tcons ::= FOREIGN KEY LP * idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt
+ idxlist ::= * idxlist COMMA idxitem
+ idxlist ::= * idxitem
+ idxitem ::= * nm sortorder
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ idxitem shift 272
+ idxlist shift 374
+ nm shift 270
+
+State 374:
+ tcons ::= FOREIGN KEY LP idxlist * RP REFERENCES nm idxlist_opt refargs defer_subclause_opt
+ idxlist ::= idxlist * COMMA idxitem
+
+ COMMA shift 268
+ RP shift 375
+
+State 375:
+ tcons ::= FOREIGN KEY LP idxlist RP * REFERENCES nm idxlist_opt refargs defer_subclause_opt
+
+ REFERENCES shift 376
+
+State 376:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ tcons ::= FOREIGN KEY LP idxlist RP REFERENCES * nm idxlist_opt refargs defer_subclause_opt
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 377
+
+State 377:
+ tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm * idxlist_opt refargs defer_subclause_opt
+ (234) idxlist_opt ::= *
+ idxlist_opt ::= * LP idxlist RP
+
+ LP shift 321
+ idxlist_opt shift 378
+ {default} reduce 234
+
+State 378:
+ (61) refargs ::= *
+ refargs ::= * refargs refarg
+ tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt * refargs defer_subclause_opt
+
+ refargs shift 379
+ {default} reduce 61
+
+State 379:
+ refargs ::= refargs * refarg
+ refarg ::= * MATCH nm
+ refarg ::= * ON DELETE refact
+ refarg ::= * ON UPDATE refact
+ refarg ::= * ON INSERT refact
+ defer_subclause ::= * NOT DEFERRABLE init_deferred_pred_opt
+ defer_subclause ::= * DEFERRABLE init_deferred_pred_opt
+ tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs * defer_subclause_opt
+ (86) defer_subclause_opt ::= *
+ defer_subclause_opt ::= * defer_subclause
+
+ DEFERRABLE shift 327
+ MATCH shift 307
+ NOT shift 380
+ ON shift 309
+ defer_subclause shift 382
+ defer_subclause_opt shift 381
+ refarg shift 306
+ {default} reduce 86
+
+State 380:
+ defer_subclause ::= NOT * DEFERRABLE init_deferred_pred_opt
+
+ DEFERRABLE shift 49
+
+State 381:
+ (85) tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt *
+
+ {default} reduce 85
+
+State 382:
+ (87) defer_subclause_opt ::= defer_subclause *
+
+ {default} reduce 87
+
+State 383:
+ (79) conslist ::= conslist tcons *
+
+ {default} reduce 79
+
+State 384:
+ (80) conslist ::= tcons *
+
+ {default} reduce 80
+
+State 385:
+ (22) columnlist ::= column *
+
+ {default} reduce 22
+
+State 386:
+ create_table_args ::= AS * select
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+
+ SELECT shift 73
+ oneselect shift 69
+ select shift 387
+
+State 387:
+ (20) create_table_args ::= AS select *
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ SEMI reduce 20
+ UNION shift 167
+ multiselect_op shift 71
+
+State 388:
+ create_table ::= CREATE * temp TABLE nm
+ temp ::= * TEMP
+ (18) temp ::= *
+ cmd ::= CREATE * temp VIEW nm AS select
+ cmd ::= CREATE * temp uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf
+ cmd ::= CREATE * trigger_decl BEGIN trigger_cmd_list END
+ trigger_decl ::= * temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause
+
+ TEMP shift 434
+ temp shift 389
+ trigger_decl shift 435
+ {default} reduce 18
+
+State 389:
+ create_table ::= CREATE temp * TABLE nm
+ cmd ::= CREATE temp * VIEW nm AS select
+ cmd ::= CREATE temp * uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf
+ uniqueflag ::= * UNIQUE
+ (233) uniqueflag ::= *
+ trigger_decl ::= temp * TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause
+
+ INDEX reduce 233
+ TABLE shift 390
+ TRIGGER shift 407
+ UNIQUE shift 406
+ VIEW shift 392
+ uniqueflag shift 396
+
+State 390:
+ create_table ::= CREATE temp TABLE * nm
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 391
+
+State 391:
+ (16) create_table ::= CREATE temp TABLE nm *
+
+ {default} reduce 16
+
+State 392:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= CREATE temp VIEW * nm AS select
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 393
+
+State 393:
+ cmd ::= CREATE temp VIEW nm * AS select
+
+ AS shift 394
+
+State 394:
+ cmd ::= CREATE temp VIEW nm AS * select
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+
+ SELECT shift 73
+ oneselect shift 69
+ select shift 395
+
+State 395:
+ (98) cmd ::= CREATE temp VIEW nm AS select *
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ SEMI reduce 98
+ UNION shift 167
+ multiselect_op shift 71
+
+State 396:
+ cmd ::= CREATE temp uniqueflag * INDEX nm ON nm dbnm LP idxlist RP onconf
+
+ INDEX shift 397
+
+State 397:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= CREATE temp uniqueflag INDEX * nm ON nm dbnm LP idxlist RP onconf
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 398
+
+State 398:
+ cmd ::= CREATE temp uniqueflag INDEX nm * ON nm dbnm LP idxlist RP onconf
+
+ ON shift 399
+
+State 399:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= CREATE temp uniqueflag INDEX nm ON * nm dbnm LP idxlist RP onconf
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 400
+
+State 400:
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+ cmd ::= CREATE temp uniqueflag INDEX nm ON nm * dbnm LP idxlist RP onconf
+
+ DOT shift 275
+ LP reduce 125
+ dbnm shift 401
+
+State 401:
+ cmd ::= CREATE temp uniqueflag INDEX nm ON nm dbnm * LP idxlist RP onconf
+
+ LP shift 402
+
+State 402:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= CREATE temp uniqueflag INDEX nm ON nm dbnm LP * idxlist RP onconf
+ idxlist ::= * idxlist COMMA idxitem
+ idxlist ::= * idxitem
+ idxitem ::= * nm sortorder
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ idxitem shift 272
+ idxlist shift 403
+ nm shift 270
+
+State 403:
+ cmd ::= CREATE temp uniqueflag INDEX nm ON nm dbnm LP idxlist * RP onconf
+ idxlist ::= idxlist * COMMA idxitem
+
+ COMMA shift 268
+ RP shift 404
+
+State 404:
+ (88) onconf ::= *
+ onconf ::= * ON CONFLICT resolvetype
+ cmd ::= CREATE temp uniqueflag INDEX nm ON nm dbnm LP idxlist RP * onconf
+
+ ON shift 10
+ SEMI reduce 88
+ onconf shift 405
+
+State 405:
+ (231) cmd ::= CREATE temp uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf *
+
+ SEMI reduce 231
+
+State 406:
+ (232) uniqueflag ::= UNIQUE *
+
+ INDEX reduce 232
+
+State 407:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ trigger_decl ::= temp TRIGGER * nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 408
+
+State 408:
+ trigger_decl ::= temp TRIGGER nm * trigger_time trigger_event ON nm dbnm foreach_clause when_clause
+ trigger_time ::= * BEFORE
+ trigger_time ::= * AFTER
+ trigger_time ::= * INSTEAD OF
+ (261) trigger_time ::= *
+
+ AFTER shift 431
+ BEFORE shift 430
+ INSTEAD shift 432
+ trigger_time shift 409
+ {default} reduce 261
+
+State 409:
+ trigger_decl ::= temp TRIGGER nm trigger_time * trigger_event ON nm dbnm foreach_clause when_clause
+ trigger_event ::= * DELETE
+ trigger_event ::= * INSERT
+ trigger_event ::= * UPDATE
+ trigger_event ::= * UPDATE OF inscollist
+
+ DELETE shift 422
+ INSERT shift 423
+ UPDATE shift 424
+ trigger_event shift 410
+
+State 410:
+ trigger_decl ::= temp TRIGGER nm trigger_time trigger_event * ON nm dbnm foreach_clause when_clause
+
+ ON shift 411
+
+State 411:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ trigger_decl ::= temp TRIGGER nm trigger_time trigger_event ON * nm dbnm foreach_clause when_clause
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 412
+
+State 412:
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+ trigger_decl ::= temp TRIGGER nm trigger_time trigger_event ON nm * dbnm foreach_clause when_clause
+
+ DOT shift 275
+ dbnm shift 413
+ {default} reduce 125
+
+State 413:
+ trigger_decl ::= temp TRIGGER nm trigger_time trigger_event ON nm dbnm * foreach_clause when_clause
+ (266) foreach_clause ::= *
+ foreach_clause ::= * FOR EACH ROW
+ foreach_clause ::= * FOR EACH STATEMENT
+
+ FOR shift 418
+ foreach_clause shift 414
+ {default} reduce 266
+
+State 414:
+ trigger_decl ::= temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause * when_clause
+ (269) when_clause ::= *
+ when_clause ::= * WHEN expr
+
+ BEGIN reduce 269
+ WHEN shift 416
+ when_clause shift 415
+
+State 415:
+ (257) trigger_decl ::= temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause *
+
+ BEGIN reduce 257
+
+State 416:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ when_clause ::= WHEN * expr
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 417
+ nm shift 107
+
+State 417:
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+ (270) when_clause ::= WHEN expr *
+
+ AND shift 105
+ BEGIN reduce 270
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+
+State 418:
+ foreach_clause ::= FOR * EACH ROW
+ foreach_clause ::= FOR * EACH STATEMENT
+
+ EACH shift 419
+
+State 419:
+ foreach_clause ::= FOR EACH * ROW
+ foreach_clause ::= FOR EACH * STATEMENT
+
+ ROW shift 420
+ STATEMENT shift 421
+
+State 420:
+ (267) foreach_clause ::= FOR EACH ROW *
+
+ {default} reduce 267
+
+State 421:
+ (268) foreach_clause ::= FOR EACH STATEMENT *
+
+ {default} reduce 268
+
+State 422:
+ (262) trigger_event ::= DELETE *
+
+ ON reduce 262
+
+State 423:
+ (263) trigger_event ::= INSERT *
+
+ ON reduce 263
+
+State 424:
+ (264) trigger_event ::= UPDATE *
+ trigger_event ::= UPDATE * OF inscollist
+
+ OF shift 425
+ ON reduce 264
+
+State 425:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ inscollist ::= * inscollist COMMA nm
+ inscollist ::= * nm
+ trigger_event ::= UPDATE OF * inscollist
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ inscollist shift 426
+ nm shift 429
+
+State 426:
+ inscollist ::= inscollist * COMMA nm
+ (265) trigger_event ::= UPDATE OF inscollist *
+
+ COMMA shift 427
+ ON reduce 265
+
+State 427:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ inscollist ::= inscollist COMMA * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 428
+
+State 428:
+ (168) inscollist ::= inscollist COMMA nm *
+
+ {default} reduce 168
+
+State 429:
+ (169) inscollist ::= nm *
+
+ {default} reduce 169
+
+State 430:
+ (258) trigger_time ::= BEFORE *
+
+ {default} reduce 258
+
+State 431:
+ (259) trigger_time ::= AFTER *
+
+ {default} reduce 259
+
+State 432:
+ trigger_time ::= INSTEAD * OF
+
+ OF shift 433
+
+State 433:
+ (260) trigger_time ::= INSTEAD OF *
+
+ {default} reduce 260
+
+State 434:
+ (17) temp ::= TEMP *
+
+ {default} reduce 17
+
+State 435:
+ cmd ::= CREATE trigger_decl * BEGIN trigger_cmd_list END
+
+ BEGIN shift 436
+
+State 436:
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ cmd ::= CREATE trigger_decl BEGIN * trigger_cmd_list END
+ trigger_cmd_list ::= * trigger_cmd SEMI trigger_cmd_list
+ (272) trigger_cmd_list ::= *
+ trigger_cmd ::= * UPDATE orconf nm SET setlist where_opt
+ trigger_cmd ::= * INSERT orconf INTO nm inscollist_opt VALUES LP itemlist RP
+ trigger_cmd ::= * INSERT orconf INTO nm inscollist_opt select
+ trigger_cmd ::= * DELETE FROM nm where_opt
+ trigger_cmd ::= * select
+
+ DELETE shift 474
+ END reduce 272
+ INSERT shift 458
+ SELECT shift 73
+ UPDATE shift 443
+ oneselect shift 69
+ select shift 437
+ trigger_cmd shift 440
+ trigger_cmd_list shift 438
+
+State 437:
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+ (277) trigger_cmd ::= select *
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ SEMI reduce 277
+ UNION shift 167
+ multiselect_op shift 71
+
+State 438:
+ cmd ::= CREATE trigger_decl BEGIN trigger_cmd_list * END
+
+ END shift 439
+
+State 439:
+ (256) cmd ::= CREATE trigger_decl BEGIN trigger_cmd_list END *
+
+ SEMI reduce 256
+
+State 440:
+ trigger_cmd_list ::= trigger_cmd * SEMI trigger_cmd_list
+
+ SEMI shift 441
+
+State 441:
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ trigger_cmd_list ::= * trigger_cmd SEMI trigger_cmd_list
+ trigger_cmd_list ::= trigger_cmd SEMI * trigger_cmd_list
+ (272) trigger_cmd_list ::= *
+ trigger_cmd ::= * UPDATE orconf nm SET setlist where_opt
+ trigger_cmd ::= * INSERT orconf INTO nm inscollist_opt VALUES LP itemlist RP
+ trigger_cmd ::= * INSERT orconf INTO nm inscollist_opt select
+ trigger_cmd ::= * DELETE FROM nm where_opt
+ trigger_cmd ::= * select
+
+ DELETE shift 474
+ END reduce 272
+ INSERT shift 458
+ SELECT shift 73
+ UPDATE shift 443
+ oneselect shift 69
+ select shift 437
+ trigger_cmd shift 440
+ trigger_cmd_list shift 442
+
+State 442:
+ (271) trigger_cmd_list ::= trigger_cmd SEMI trigger_cmd_list *
+
+ END reduce 271
+
+State 443:
+ (90) orconf ::= *
+ orconf ::= * OR resolvetype
+ trigger_cmd ::= UPDATE * orconf nm SET setlist where_opt
+
+ OR shift 444
+ orconf shift 446
+ {default} reduce 90
+
+State 444:
+ orconf ::= OR * resolvetype
+ resolvetype ::= * ROLLBACK
+ resolvetype ::= * ABORT
+ resolvetype ::= * FAIL
+ resolvetype ::= * IGNORE
+ resolvetype ::= * REPLACE
+
+ ABORT shift 14
+ FAIL shift 15
+ IGNORE shift 16
+ REPLACE shift 17
+ ROLLBACK shift 13
+ resolvetype shift 445
+
+State 445:
+ (91) orconf ::= OR resolvetype *
+
+ {default} reduce 91
+
+State 446:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ trigger_cmd ::= UPDATE orconf * nm SET setlist where_opt
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 447
+
+State 447:
+ trigger_cmd ::= UPDATE orconf nm * SET setlist where_opt
+
+ SET shift 448
+
+State 448:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ setlist ::= * setlist COMMA nm EQ expr
+ setlist ::= * nm EQ expr
+ trigger_cmd ::= UPDATE orconf nm SET * setlist where_opt
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 455
+ setlist shift 449
+
+State 449:
+ (155) where_opt ::= *
+ where_opt ::= * WHERE expr
+ setlist ::= setlist * COMMA nm EQ expr
+ trigger_cmd ::= UPDATE orconf nm SET setlist * where_opt
+
+ COMMA shift 450
+ SEMI reduce 155
+ WHERE shift 239
+ where_opt shift 454
+
+State 450:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ setlist ::= setlist COMMA * nm EQ expr
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 451
+
+State 451:
+ setlist ::= setlist COMMA nm * EQ expr
+
+ EQ shift 452
+
+State 452:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ setlist ::= setlist COMMA nm EQ * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 453
+ nm shift 107
+
+State 453:
+ (158) setlist ::= setlist COMMA nm EQ expr *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 158
+
+State 454:
+ (273) trigger_cmd ::= UPDATE orconf nm SET setlist where_opt *
+
+ SEMI reduce 273
+
+State 455:
+ setlist ::= nm * EQ expr
+
+ EQ shift 456
+
+State 456:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ setlist ::= nm EQ * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 457
+ nm shift 107
+
+State 457:
+ (159) setlist ::= nm EQ expr *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 159
+
+State 458:
+ (90) orconf ::= *
+ orconf ::= * OR resolvetype
+ trigger_cmd ::= INSERT * orconf INTO nm inscollist_opt VALUES LP itemlist RP
+ trigger_cmd ::= INSERT * orconf INTO nm inscollist_opt select
+
+ INTO reduce 90
+ OR shift 444
+ orconf shift 459
+
+State 459:
+ trigger_cmd ::= INSERT orconf * INTO nm inscollist_opt VALUES LP itemlist RP
+ trigger_cmd ::= INSERT orconf * INTO nm inscollist_opt select
+
+ INTO shift 460
+
+State 460:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ trigger_cmd ::= INSERT orconf INTO * nm inscollist_opt VALUES LP itemlist RP
+ trigger_cmd ::= INSERT orconf INTO * nm inscollist_opt select
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 461
+
+State 461:
+ (166) inscollist_opt ::= *
+ inscollist_opt ::= * LP inscollist RP
+ trigger_cmd ::= INSERT orconf INTO nm * inscollist_opt VALUES LP itemlist RP
+ trigger_cmd ::= INSERT orconf INTO nm * inscollist_opt select
+
+ LP shift 462
+ inscollist_opt shift 465
+ {default} reduce 166
+
+State 462:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ inscollist_opt ::= LP * inscollist RP
+ inscollist ::= * inscollist COMMA nm
+ inscollist ::= * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ inscollist shift 463
+ nm shift 429
+
+State 463:
+ inscollist_opt ::= LP inscollist * RP
+ inscollist ::= inscollist * COMMA nm
+
+ COMMA shift 427
+ RP shift 464
+
+State 464:
+ (167) inscollist_opt ::= LP inscollist RP *
+
+ {default} reduce 167
+
+State 465:
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ trigger_cmd ::= INSERT orconf INTO nm inscollist_opt * VALUES LP itemlist RP
+ trigger_cmd ::= INSERT orconf INTO nm inscollist_opt * select
+
+ SELECT shift 73
+ VALUES shift 467
+ oneselect shift 69
+ select shift 466
+
+State 466:
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+ (275) trigger_cmd ::= INSERT orconf INTO nm inscollist_opt select *
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ SEMI reduce 275
+ UNION shift 167
+ multiselect_op shift 71
+
+State 467:
+ trigger_cmd ::= INSERT orconf INTO nm inscollist_opt VALUES * LP itemlist RP
+
+ LP shift 468
+
+State 468:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ itemlist ::= * itemlist COMMA expr
+ itemlist ::= * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ trigger_cmd ::= INSERT orconf INTO nm inscollist_opt VALUES LP * itemlist RP
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 473
+ itemlist shift 469
+ nm shift 107
+
+State 469:
+ itemlist ::= itemlist * COMMA expr
+ trigger_cmd ::= INSERT orconf INTO nm inscollist_opt VALUES LP itemlist * RP
+
+ COMMA shift 470
+ RP shift 472
+
+State 470:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ itemlist ::= itemlist COMMA * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 471
+ nm shift 107
+
+State 471:
+ (164) itemlist ::= itemlist COMMA expr *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 164
+
+State 472:
+ (274) trigger_cmd ::= INSERT orconf INTO nm inscollist_opt VALUES LP itemlist RP *
+
+ SEMI reduce 274
+
+State 473:
+ (165) itemlist ::= expr *
+ expr ::= expr * ORACLE_OUTER_JOIN
+ expr ::= expr * AND expr
+ expr ::= expr * OR expr
+ expr ::= expr * LT expr
+ expr ::= expr * GT expr
+ expr ::= expr * LE expr
+ expr ::= expr * GE expr
+ expr ::= expr * NE expr
+ expr ::= expr * EQ expr
+ expr ::= expr * BITAND expr
+ expr ::= expr * BITOR expr
+ expr ::= expr * LSHIFT expr
+ expr ::= expr * RSHIFT expr
+ expr ::= expr * likeop expr
+ expr ::= expr * NOT likeop expr
+ likeop ::= * LIKE
+ likeop ::= * GLOB
+ expr ::= expr * PLUS expr
+ expr ::= expr * MINUS expr
+ expr ::= expr * STAR expr
+ expr ::= expr * SLASH expr
+ expr ::= expr * REM expr
+ expr ::= expr * CONCAT expr
+ expr ::= expr * ISNULL
+ expr ::= expr * IS NULL
+ expr ::= expr * NOTNULL
+ expr ::= expr * NOT NULL
+ expr ::= expr * IS NOT NULL
+ expr ::= expr * BETWEEN expr AND expr
+ expr ::= expr * NOT BETWEEN expr AND expr
+ expr ::= expr * IN LP exprlist RP
+ expr ::= expr * IN LP select RP
+ expr ::= expr * NOT IN LP exprlist RP
+ expr ::= expr * NOT IN LP select RP
+
+ AND shift 105
+ BETWEEN shift 160
+ BITAND shift 127
+ BITOR shift 129
+ CONCAT shift 152
+ EQ shift 125
+ GE shift 121
+ GLOB shift 141
+ GT shift 117
+ IN shift 164
+ IS shift 155
+ ISNULL shift 154
+ LE shift 119
+ LIKE shift 140
+ LSHIFT shift 131
+ LT shift 115
+ MINUS shift 144
+ NE shift 123
+ NOT shift 137
+ NOTNULL shift 159
+ OR shift 113
+ ORACLE_OUTER_JOIN shift 104
+ PLUS shift 142
+ REM shift 150
+ RSHIFT shift 133
+ SLASH shift 148
+ STAR shift 146
+ likeop shift 135
+ {default} reduce 165
+
+State 474:
+ trigger_cmd ::= DELETE * FROM nm where_opt
+
+ FROM shift 475
+
+State 475:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ trigger_cmd ::= DELETE FROM * nm where_opt
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 476
+
+State 476:
+ (155) where_opt ::= *
+ where_opt ::= * WHERE expr
+ trigger_cmd ::= DELETE FROM nm * where_opt
+
+ SEMI reduce 155
+ WHERE shift 239
+ where_opt shift 477
+
+State 477:
+ (276) trigger_cmd ::= DELETE FROM nm where_opt *
+
+ SEMI reduce 276
+
+State 478:
+ cmd ::= DROP * TABLE nm
+ cmd ::= DROP * VIEW nm
+ cmd ::= DROP * INDEX nm dbnm
+ cmd ::= DROP * TRIGGER nm dbnm
+
+ INDEX shift 483
+ TABLE shift 479
+ TRIGGER shift 486
+ VIEW shift 481
+
+State 479:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= DROP TABLE * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 480
+
+State 480:
+ (97) cmd ::= DROP TABLE nm *
+
+ SEMI reduce 97
+
+State 481:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= DROP VIEW * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 482
+
+State 482:
+ (99) cmd ::= DROP VIEW nm *
+
+ SEMI reduce 99
+
+State 483:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= DROP INDEX * nm dbnm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 484
+
+State 484:
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+ cmd ::= DROP INDEX nm * dbnm
+
+ DOT shift 275
+ SEMI reduce 125
+ dbnm shift 485
+
+State 485:
+ (239) cmd ::= DROP INDEX nm dbnm *
+
+ SEMI reduce 239
+
+State 486:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= DROP TRIGGER * nm dbnm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 487
+
+State 487:
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+ cmd ::= DROP TRIGGER nm * dbnm
+
+ DOT shift 275
+ SEMI reduce 125
+ dbnm shift 488
+
+State 488:
+ (282) cmd ::= DROP TRIGGER nm dbnm *
+
+ SEMI reduce 282
+
+State 489:
+ (100) cmd ::= select *
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ SEMI reduce 100
+ UNION shift 167
+ multiselect_op shift 71
+
+State 490:
+ cmd ::= DELETE * FROM nm dbnm where_opt
+
+ FROM shift 491
+
+State 491:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= DELETE FROM * nm dbnm where_opt
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 492
+
+State 492:
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+ cmd ::= DELETE FROM nm * dbnm where_opt
+
+ DOT shift 275
+ dbnm shift 493
+ {default} reduce 125
+
+State 493:
+ cmd ::= DELETE FROM nm dbnm * where_opt
+ (155) where_opt ::= *
+ where_opt ::= * WHERE expr
+
+ SEMI reduce 155
+ WHERE shift 239
+ where_opt shift 494
+
+State 494:
+ (154) cmd ::= DELETE FROM nm dbnm where_opt *
+
+ SEMI reduce 154
+
+State 495:
+ (90) orconf ::= *
+ orconf ::= * OR resolvetype
+ cmd ::= UPDATE * orconf nm dbnm SET setlist where_opt
+
+ OR shift 444
+ orconf shift 496
+ {default} reduce 90
+
+State 496:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= UPDATE orconf * nm dbnm SET setlist where_opt
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 497
+
+State 497:
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+ cmd ::= UPDATE orconf nm * dbnm SET setlist where_opt
+
+ DOT shift 275
+ SET reduce 125
+ dbnm shift 498
+
+State 498:
+ cmd ::= UPDATE orconf nm dbnm * SET setlist where_opt
+
+ SET shift 499
+
+State 499:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= UPDATE orconf nm dbnm SET * setlist where_opt
+ setlist ::= * setlist COMMA nm EQ expr
+ setlist ::= * nm EQ expr
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 455
+ setlist shift 500
+
+State 500:
+ (155) where_opt ::= *
+ where_opt ::= * WHERE expr
+ cmd ::= UPDATE orconf nm dbnm SET setlist * where_opt
+ setlist ::= setlist * COMMA nm EQ expr
+
+ COMMA shift 450
+ SEMI reduce 155
+ WHERE shift 239
+ where_opt shift 501
+
+State 501:
+ (157) cmd ::= UPDATE orconf nm dbnm SET setlist where_opt *
+
+ SEMI reduce 157
+
+State 502:
+ cmd ::= insert_cmd * INTO nm dbnm inscollist_opt VALUES LP itemlist RP
+ cmd ::= insert_cmd * INTO nm dbnm inscollist_opt select
+
+ INTO shift 503
+
+State 503:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= insert_cmd INTO * nm dbnm inscollist_opt VALUES LP itemlist RP
+ cmd ::= insert_cmd INTO * nm dbnm inscollist_opt select
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 504
+
+State 504:
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+ cmd ::= insert_cmd INTO nm * dbnm inscollist_opt VALUES LP itemlist RP
+ cmd ::= insert_cmd INTO nm * dbnm inscollist_opt select
+
+ DOT shift 275
+ dbnm shift 505
+ {default} reduce 125
+
+State 505:
+ cmd ::= insert_cmd INTO nm dbnm * inscollist_opt VALUES LP itemlist RP
+ cmd ::= insert_cmd INTO nm dbnm * inscollist_opt select
+ (166) inscollist_opt ::= *
+ inscollist_opt ::= * LP inscollist RP
+
+ LP shift 462
+ inscollist_opt shift 506
+ {default} reduce 166
+
+State 506:
+ select ::= * oneselect
+ select ::= * select multiselect_op oneselect
+ oneselect ::= * SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt
+ cmd ::= insert_cmd INTO nm dbnm inscollist_opt * VALUES LP itemlist RP
+ cmd ::= insert_cmd INTO nm dbnm inscollist_opt * select
+
+ SELECT shift 73
+ VALUES shift 508
+ oneselect shift 69
+ select shift 507
+
+State 507:
+ select ::= select * multiselect_op oneselect
+ multiselect_op ::= * UNION
+ multiselect_op ::= * UNION ALL
+ multiselect_op ::= * INTERSECT
+ multiselect_op ::= * EXCEPT
+ (161) cmd ::= insert_cmd INTO nm dbnm inscollist_opt select *
+
+ EXCEPT shift 170
+ INTERSECT shift 169
+ SEMI reduce 161
+ UNION shift 167
+ multiselect_op shift 71
+
+State 508:
+ cmd ::= insert_cmd INTO nm dbnm inscollist_opt VALUES * LP itemlist RP
+
+ LP shift 509
+
+State 509:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= insert_cmd INTO nm dbnm inscollist_opt VALUES LP * itemlist RP
+ itemlist ::= * itemlist COMMA expr
+ itemlist ::= * expr
+ expr ::= * LP expr RP
+ expr ::= * NULL
+ expr ::= * ID
+ expr ::= * JOIN_KW
+ expr ::= * nm DOT nm
+ expr ::= * nm DOT nm DOT nm
+ expr ::= * expr ORACLE_OUTER_JOIN
+ expr ::= * INTEGER
+ expr ::= * FLOAT
+ expr ::= * STRING
+ expr ::= * ID LP exprlist RP
+ expr ::= * ID LP STAR RP
+ expr ::= * expr AND expr
+ expr ::= * expr OR expr
+ expr ::= * expr LT expr
+ expr ::= * expr GT expr
+ expr ::= * expr LE expr
+ expr ::= * expr GE expr
+ expr ::= * expr NE expr
+ expr ::= * expr EQ expr
+ expr ::= * expr BITAND expr
+ expr ::= * expr BITOR expr
+ expr ::= * expr LSHIFT expr
+ expr ::= * expr RSHIFT expr
+ expr ::= * expr likeop expr
+ expr ::= * expr NOT likeop expr
+ expr ::= * expr PLUS expr
+ expr ::= * expr MINUS expr
+ expr ::= * expr STAR expr
+ expr ::= * expr SLASH expr
+ expr ::= * expr REM expr
+ expr ::= * expr CONCAT expr
+ expr ::= * expr ISNULL
+ expr ::= * expr IS NULL
+ expr ::= * expr NOTNULL
+ expr ::= * expr NOT NULL
+ expr ::= * expr IS NOT NULL
+ expr ::= * NOT expr
+ expr ::= * BITNOT expr
+ expr ::= * MINUS expr
+ expr ::= * PLUS expr
+ expr ::= * LP select RP
+ expr ::= * expr BETWEEN expr AND expr
+ expr ::= * expr NOT BETWEEN expr AND expr
+ expr ::= * expr IN LP exprlist RP
+ expr ::= * expr IN LP select RP
+ expr ::= * expr NOT IN LP exprlist RP
+ expr ::= * expr NOT IN LP select RP
+ expr ::= * CASE case_operand case_exprlist case_else END
+ expr ::= * RAISE LP IGNORE RP
+ expr ::= * RAISE LP ROLLBACK COMMA nm RP
+ expr ::= * RAISE LP ABORT COMMA nm RP
+ expr ::= * RAISE LP FAIL COMMA nm RP
+
+ BITNOT shift 177
+ CASE shift 183
+ FLOAT shift 174
+ ID shift 64
+ INTEGER shift 173
+ JOIN_KW shift 67
+ LP shift 68
+ MINUS shift 179
+ NOT shift 175
+ NULL shift 106
+ PLUS shift 181
+ RAISE shift 193
+ STRING shift 66
+ expr shift 473
+ itemlist shift 510
+ nm shift 107
+
+State 510:
+ cmd ::= insert_cmd INTO nm dbnm inscollist_opt VALUES LP itemlist * RP
+ itemlist ::= itemlist * COMMA expr
+
+ COMMA shift 470
+ RP shift 511
+
+State 511:
+ (160) cmd ::= insert_cmd INTO nm dbnm inscollist_opt VALUES LP itemlist RP *
+
+ SEMI reduce 160
+
+State 512:
+ (90) orconf ::= *
+ orconf ::= * OR resolvetype
+ insert_cmd ::= INSERT * orconf
+
+ INTO reduce 90
+ OR shift 444
+ orconf shift 513
+
+State 513:
+ (162) insert_cmd ::= INSERT orconf *
+
+ INTO reduce 162
+
+State 514:
+ (163) insert_cmd ::= REPLACE *
+
+ INTO reduce 163
+
+State 515:
+ (90) orconf ::= *
+ orconf ::= * OR resolvetype
+ cmd ::= COPY * orconf nm dbnm FROM nm USING DELIMITERS STRING
+ cmd ::= COPY * orconf nm dbnm FROM nm
+
+ OR shift 444
+ orconf shift 516
+ {default} reduce 90
+
+State 516:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= COPY orconf * nm dbnm FROM nm USING DELIMITERS STRING
+ cmd ::= COPY orconf * nm dbnm FROM nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 517
+
+State 517:
+ (125) dbnm ::= *
+ dbnm ::= * DOT nm
+ cmd ::= COPY orconf nm * dbnm FROM nm USING DELIMITERS STRING
+ cmd ::= COPY orconf nm * dbnm FROM nm
+
+ DOT shift 275
+ FROM reduce 125
+ dbnm shift 518
+
+State 518:
+ cmd ::= COPY orconf nm dbnm * FROM nm USING DELIMITERS STRING
+ cmd ::= COPY orconf nm dbnm * FROM nm
+
+ FROM shift 519
+
+State 519:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= COPY orconf nm dbnm FROM * nm USING DELIMITERS STRING
+ cmd ::= COPY orconf nm dbnm FROM * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 520
+
+State 520:
+ cmd ::= COPY orconf nm dbnm FROM nm * USING DELIMITERS STRING
+ (241) cmd ::= COPY orconf nm dbnm FROM nm *
+
+ SEMI reduce 241
+ USING shift 521
+
+State 521:
+ cmd ::= COPY orconf nm dbnm FROM nm USING * DELIMITERS STRING
+
+ DELIMITERS shift 522
+
+State 522:
+ cmd ::= COPY orconf nm dbnm FROM nm USING DELIMITERS * STRING
+
+ STRING shift 523
+
+State 523:
+ (240) cmd ::= COPY orconf nm dbnm FROM nm USING DELIMITERS STRING *
+
+ SEMI reduce 240
+
+State 524:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ (242) cmd ::= VACUUM *
+ cmd ::= VACUUM * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ SEMI reduce 242
+ STRING shift 21
+ nm shift 525
+
+State 525:
+ (243) cmd ::= VACUUM nm *
+
+ SEMI reduce 243
+
+State 526:
+ ids ::= * ID
+ ids ::= * STRING
+ cmd ::= PRAGMA * ids EQ nm
+ cmd ::= PRAGMA * ids EQ ON
+ cmd ::= PRAGMA * ids EQ plus_num
+ cmd ::= PRAGMA * ids EQ minus_num
+ cmd ::= PRAGMA * ids LP nm RP
+ cmd ::= PRAGMA * ids
+
+ ID shift 256
+ STRING shift 257
+ ids shift 527
+
+State 527:
+ cmd ::= PRAGMA ids * EQ nm
+ cmd ::= PRAGMA ids * EQ ON
+ cmd ::= PRAGMA ids * EQ plus_num
+ cmd ::= PRAGMA ids * EQ minus_num
+ cmd ::= PRAGMA ids * LP nm RP
+ (249) cmd ::= PRAGMA ids *
+
+ EQ shift 528
+ LP shift 540
+ SEMI reduce 249
+
+State 528:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= PRAGMA ids EQ * nm
+ cmd ::= PRAGMA ids EQ * ON
+ cmd ::= PRAGMA ids EQ * plus_num
+ cmd ::= PRAGMA ids EQ * minus_num
+ plus_num ::= * plus_opt number
+ minus_num ::= * MINUS number
+ plus_opt ::= * PLUS
+ (255) plus_opt ::= *
+
+ ID shift 20
+ JOIN_KW shift 22
+ MINUS shift 537
+ ON shift 530
+ PLUS shift 539
+ STRING shift 21
+ minus_num shift 532
+ nm shift 529
+ plus_num shift 531
+ plus_opt shift 533
+ {default} reduce 255
+
+State 529:
+ (244) cmd ::= PRAGMA ids EQ nm *
+
+ SEMI reduce 244
+
+State 530:
+ (245) cmd ::= PRAGMA ids EQ ON *
+
+ SEMI reduce 245
+
+State 531:
+ (246) cmd ::= PRAGMA ids EQ plus_num *
+
+ SEMI reduce 246
+
+State 532:
+ (247) cmd ::= PRAGMA ids EQ minus_num *
+
+ SEMI reduce 247
+
+State 533:
+ plus_num ::= plus_opt * number
+ number ::= * INTEGER
+ number ::= * FLOAT
+
+ FLOAT shift 536
+ INTEGER shift 535
+ number shift 534
+
+State 534:
+ (250) plus_num ::= plus_opt number *
+
+ SEMI reduce 250
+
+State 535:
+ (252) number ::= INTEGER *
+
+ SEMI reduce 252
+
+State 536:
+ (253) number ::= FLOAT *
+
+ SEMI reduce 253
+
+State 537:
+ minus_num ::= MINUS * number
+ number ::= * INTEGER
+ number ::= * FLOAT
+
+ FLOAT shift 536
+ INTEGER shift 535
+ number shift 538
+
+State 538:
+ (251) minus_num ::= MINUS number *
+
+ SEMI reduce 251
+
+State 539:
+ (254) plus_opt ::= PLUS *
+
+ {default} reduce 254
+
+State 540:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= PRAGMA ids LP * nm RP
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 541
+
+State 541:
+ cmd ::= PRAGMA ids LP nm * RP
+
+ RP shift 542
+
+State 542:
+ (248) cmd ::= PRAGMA ids LP nm RP *
+
+ SEMI reduce 248
+
+State 543:
+ cmd ::= ATTACH * database_kw_opt ids AS nm
+ database_kw_opt ::= * DATABASE
+ (285) database_kw_opt ::= *
+
+ DATABASE shift 548
+ database_kw_opt shift 544
+ {default} reduce 285
+
+State 544:
+ ids ::= * ID
+ ids ::= * STRING
+ cmd ::= ATTACH database_kw_opt * ids AS nm
+
+ ID shift 256
+ STRING shift 257
+ ids shift 545
+
+State 545:
+ cmd ::= ATTACH database_kw_opt ids * AS nm
+
+ AS shift 546
+
+State 546:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= ATTACH database_kw_opt ids AS * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 547
+
+State 547:
+ (283) cmd ::= ATTACH database_kw_opt ids AS nm *
+
+ SEMI reduce 283
+
+State 548:
+ (284) database_kw_opt ::= DATABASE *
+
+ {default} reduce 284
+
+State 549:
+ database_kw_opt ::= * DATABASE
+ (285) database_kw_opt ::= *
+ cmd ::= DETACH * database_kw_opt nm
+
+ DATABASE shift 548
+ database_kw_opt shift 550
+ {default} reduce 285
+
+State 550:
+ nm ::= * ID
+ nm ::= * STRING
+ nm ::= * JOIN_KW
+ cmd ::= DETACH database_kw_opt * nm
+
+ ID shift 20
+ JOIN_KW shift 22
+ STRING shift 21
+ nm shift 551
+
+State 551:
+ (286) cmd ::= DETACH database_kw_opt nm *
+
+ SEMI reduce 286
+
+State 552:
+ (4) ecmd ::= SEMI *
+
+ {default} reduce 4
+
+State 553:
+ (6) explain ::= EXPLAIN *
+
+ {default} reduce 6
+
+State 554:
+ (1) cmdlist ::= ecmd *
+
+ {default} reduce 1
+
diff --git a/kexi/3rdparty/kexisql/src/parse.y b/kexi/3rdparty/kexisql/src/parse.y
new file mode 100644
index 000000000..29bb493b4
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/parse.y
@@ -0,0 +1,866 @@
+/*
+** 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 SQLite's grammar for SQL. Process this file
+** using the lemon parser generator to generate C code that runs
+** the parser. Lemon will also generate a header file containing
+** numeric codes for all of the tokens.
+**
+** @(#) $Id: parse.y 410099 2005-05-06 17:52:07Z staniek $
+*/
+%token_prefix TK_
+%token_type {Token}
+%default_type {Token}
+%extra_argument {Parse *pParse}
+%syntax_error {
+ if( pParse->zErrMsg==0 ){
+ if( TOKEN.z[0] ){
+ sqliteSetNString(&pParse->zErrMsg,
+ "near \"", -1, TOKEN.z, TOKEN.n, "\": syntax error", -1, 0);
+ }else{
+ sqliteSetString(&pParse->zErrMsg, "incomplete SQL statement", 0);
+ }
+ }
+ pParse->nErr++;
+}
+%name sqliteParser
+%include {
+#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; };
+
+} // end %include
+
+// These are extra tokens used by the lexer but never seen by the
+// parser. We put them in a rule so that the parser generator will
+// add them to the parse.h output file.
+//
+%nonassoc END_OF_FILE ILLEGAL SPACE UNCLOSED_STRING COMMENT FUNCTION
+ COLUMN AGG_FUNCTION.
+
+// Input is zero or more commands.
+input ::= cmdlist.
+
+// A list of commands is zero or more commands
+//
+cmdlist ::= ecmd.
+cmdlist ::= cmdlist ecmd.
+ecmd ::= explain cmdx SEMI.
+ecmd ::= SEMI.
+cmdx ::= cmd. { sqliteExec(pParse); }
+explain ::= EXPLAIN. { sqliteBeginParse(pParse, 1); }
+explain ::= . { sqliteBeginParse(pParse, 0); }
+
+///////////////////// Begin and end transactions. ////////////////////////////
+//
+
+cmd ::= BEGIN trans_opt onconf(R). {sqliteBeginTransaction(pParse,R);}
+trans_opt ::= .
+trans_opt ::= TRANSACTION.
+trans_opt ::= TRANSACTION nm.
+cmd ::= COMMIT trans_opt. {sqliteCommitTransaction(pParse);}
+cmd ::= END trans_opt. {sqliteCommitTransaction(pParse);}
+cmd ::= ROLLBACK trans_opt. {sqliteRollbackTransaction(pParse);}
+
+///////////////////// The CREATE TABLE statement ////////////////////////////
+//
+cmd ::= create_table create_table_args.
+create_table ::= CREATE(X) temp(T) TABLE nm(Y). {
+ sqliteStartTable(pParse,&X,&Y,T,0);
+}
+%type temp {int}
+temp(A) ::= TEMP. {A = 1;}
+temp(A) ::= . {A = 0;}
+create_table_args ::= LP columnlist conslist_opt RP(X). {
+ sqliteEndTable(pParse,&X,0);
+}
+create_table_args ::= AS select(S). {
+ sqliteEndTable(pParse,0,S);
+ sqliteSelectDelete(S);
+}
+columnlist ::= columnlist COMMA column.
+columnlist ::= column.
+
+// About the only information used for a column is the name of the
+// column. The type is always just "text". But the code will accept
+// an elaborate typename. Perhaps someday we'll do something with it.
+//
+column ::= columnid type carglist.
+columnid ::= nm(X). {sqliteAddColumn(pParse,&X);}
+
+// An IDENTIFIER can be a generic identifier, or one of several
+// keywords. Any non-standard keyword can also be an identifier.
+//
+%type id {Token}
+id(A) ::= ID(X). {A = X;}
+
+// The following directive causes tokens ABORT, AFTER, ASC, etc. to
+// fallback to ID if they will not parse as their original value.
+// This obviates the need for the "id" nonterminal.
+//
+%fallback ID
+ ABORT AFTER ASC ATTACH BEFORE BEGIN CASCADE CLUSTER CONFLICT
+ COPY DATABASE DEFERRED DELIMITERS DESC DETACH EACH END EXPLAIN FAIL FOR
+ IGNORE IMMEDIATE INITIALLY INSTEAD MATCH KEY
+ OF OFFSET PRAGMA RAISE REPLACE RESTRICT ROW STATEMENT
+ TEMP TRIGGER VACUUM VIEW.
+
+// And "ids" is an identifer-or-string.
+//
+%type ids {Token}
+ids(A) ::= ID(X). {A = X;}
+ids(A) ::= STRING(X). {A = X;}
+
+// The name of a column or table can be any of the following:
+//
+%type nm {Token}
+nm(A) ::= ID(X). {A = X;}
+nm(A) ::= STRING(X). {A = X;}
+nm(A) ::= JOIN_KW(X). {A = X;}
+
+type ::= .
+type ::= typename(X). {sqliteAddColumnType(pParse,&X,&X);}
+type ::= typename(X) LP signed RP(Y). {sqliteAddColumnType(pParse,&X,&Y);}
+type ::= typename(X) LP signed COMMA signed RP(Y).
+ {sqliteAddColumnType(pParse,&X,&Y);}
+%type typename {Token}
+typename(A) ::= ids(X). {A = X;}
+typename(A) ::= typename(X) ids. {A = X;}
+%type signed {int}
+signed(A) ::= INTEGER(X). { A = atoi(X.z); }
+signed(A) ::= PLUS INTEGER(X). { A = atoi(X.z); }
+signed(A) ::= MINUS INTEGER(X). { A = -atoi(X.z); }
+carglist ::= carglist carg.
+carglist ::= .
+carg ::= CONSTRAINT nm ccons.
+carg ::= ccons.
+carg ::= DEFAULT STRING(X). {sqliteAddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT ID(X). {sqliteAddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT INTEGER(X). {sqliteAddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT PLUS INTEGER(X). {sqliteAddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT MINUS INTEGER(X). {sqliteAddDefaultValue(pParse,&X,1);}
+carg ::= DEFAULT FLOAT(X). {sqliteAddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT PLUS FLOAT(X). {sqliteAddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT MINUS FLOAT(X). {sqliteAddDefaultValue(pParse,&X,1);}
+carg ::= DEFAULT NULL.
+
+// In addition to the type name, we also care about the primary key and
+// UNIQUE constraints.
+//
+ccons ::= NULL onconf.
+ccons ::= NOT NULL onconf(R). {sqliteAddNotNull(pParse, R);}
+ccons ::= PRIMARY KEY sortorder onconf(R). {sqliteAddPrimaryKey(pParse,0,R);}
+ccons ::= UNIQUE onconf(R). {sqliteCreateIndex(pParse,0,0,0,R,0,0,0);}
+ccons ::= CHECK LP expr RP onconf.
+ccons ::= REFERENCES nm(T) idxlist_opt(TA) refargs(R).
+ {sqliteCreateForeignKey(pParse,0,&T,TA,R);}
+ccons ::= defer_subclause(D). {sqliteDeferForeignKey(pParse,D);}
+ccons ::= COLLATE id(C). {
+ sqliteAddCollateType(pParse, sqliteCollateType(C.z, C.n));
+}
+
+// The next group of rules parses the arguments to a REFERENCES clause
+// that determine if the referential integrity checking is deferred or
+// or immediate and which determine what action to take if a ref-integ
+// check fails.
+//
+%type refargs {int}
+refargs(A) ::= . { A = OE_Restrict * 0x010101; }
+refargs(A) ::= refargs(X) refarg(Y). { A = (X & Y.mask) | Y.value; }
+%type refarg {struct {int value; int mask;}}
+refarg(A) ::= MATCH nm. { A.value = 0; A.mask = 0x000000; }
+refarg(A) ::= ON DELETE refact(X). { A.value = X; A.mask = 0x0000ff; }
+refarg(A) ::= ON UPDATE refact(X). { A.value = X<<8; A.mask = 0x00ff00; }
+refarg(A) ::= ON INSERT refact(X). { A.value = X<<16; A.mask = 0xff0000; }
+%type refact {int}
+refact(A) ::= SET NULL. { A = OE_SetNull; }
+refact(A) ::= SET DEFAULT. { A = OE_SetDflt; }
+refact(A) ::= CASCADE. { A = OE_Cascade; }
+refact(A) ::= RESTRICT. { A = OE_Restrict; }
+%type defer_subclause {int}
+defer_subclause(A) ::= NOT DEFERRABLE init_deferred_pred_opt(X). {A = X;}
+defer_subclause(A) ::= DEFERRABLE init_deferred_pred_opt(X). {A = X;}
+%type init_deferred_pred_opt {int}
+init_deferred_pred_opt(A) ::= . {A = 0;}
+init_deferred_pred_opt(A) ::= INITIALLY DEFERRED. {A = 1;}
+init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE. {A = 0;}
+
+// For the time being, the only constraint we care about is the primary
+// key and UNIQUE. Both create indices.
+//
+conslist_opt ::= .
+conslist_opt ::= COMMA conslist.
+conslist ::= conslist COMMA tcons.
+conslist ::= conslist tcons.
+conslist ::= tcons.
+tcons ::= CONSTRAINT nm.
+tcons ::= PRIMARY KEY LP idxlist(X) RP onconf(R).
+ {sqliteAddPrimaryKey(pParse,X,R);}
+tcons ::= UNIQUE LP idxlist(X) RP onconf(R).
+ {sqliteCreateIndex(pParse,0,0,X,R,0,0,0);}
+tcons ::= CHECK expr onconf.
+tcons ::= FOREIGN KEY LP idxlist(FA) RP
+ REFERENCES nm(T) idxlist_opt(TA) refargs(R) defer_subclause_opt(D). {
+ sqliteCreateForeignKey(pParse, FA, &T, TA, R);
+ sqliteDeferForeignKey(pParse, D);
+}
+%type defer_subclause_opt {int}
+defer_subclause_opt(A) ::= . {A = 0;}
+defer_subclause_opt(A) ::= defer_subclause(X). {A = X;}
+
+// The following is a non-standard extension that allows us to declare the
+// default behavior when there is a constraint conflict.
+//
+%type onconf {int}
+%type orconf {int}
+%type resolvetype {int}
+onconf(A) ::= . { A = OE_Default; }
+onconf(A) ::= ON CONFLICT resolvetype(X). { A = X; }
+orconf(A) ::= . { A = OE_Default; }
+orconf(A) ::= OR resolvetype(X). { A = X; }
+resolvetype(A) ::= ROLLBACK. { A = OE_Rollback; }
+resolvetype(A) ::= ABORT. { A = OE_Abort; }
+resolvetype(A) ::= FAIL. { A = OE_Fail; }
+resolvetype(A) ::= IGNORE. { A = OE_Ignore; }
+resolvetype(A) ::= REPLACE. { A = OE_Replace; }
+
+////////////////////////// The DROP TABLE /////////////////////////////////////
+//
+cmd ::= DROP TABLE nm(X). {sqliteDropTable(pParse,&X,0);}
+
+///////////////////// The CREATE VIEW statement /////////////////////////////
+//
+cmd ::= CREATE(X) temp(T) VIEW nm(Y) AS select(S). {
+ sqliteCreateView(pParse, &X, &Y, S, T);
+}
+cmd ::= DROP VIEW nm(X). {
+ sqliteDropTable(pParse, &X, 1);
+}
+
+//////////////////////// The SELECT statement /////////////////////////////////
+//
+cmd ::= select(X). {
+ sqliteSelect(pParse, X, SRT_Callback, 0, 0, 0, 0);
+ sqliteSelectDelete(X);
+}
+
+%type select {Select*}
+%destructor select {sqliteSelectDelete($$);}
+%type oneselect {Select*}
+%destructor oneselect {sqliteSelectDelete($$);}
+
+select(A) ::= oneselect(X). {A = X;}
+select(A) ::= select(X) multiselect_op(Y) oneselect(Z). {
+ if( Z ){
+ Z->op = Y;
+ Z->pPrior = X;
+ }
+ A = Z;
+}
+%type multiselect_op {int}
+multiselect_op(A) ::= UNION. {A = TK_UNION;}
+multiselect_op(A) ::= UNION ALL. {A = TK_ALL;}
+multiselect_op(A) ::= INTERSECT. {A = TK_INTERSECT;}
+multiselect_op(A) ::= EXCEPT. {A = TK_EXCEPT;}
+oneselect(A) ::= SELECT distinct(D) selcollist(W) from(X) where_opt(Y)
+ groupby_opt(P) having_opt(Q) orderby_opt(Z) limit_opt(L). {
+ A = sqliteSelectNew(W,X,Y,P,Q,Z,D,L.limit,L.offset);
+}
+
+// The "distinct" nonterminal is true (1) if the DISTINCT keyword is
+// present and false (0) if it is not.
+//
+%type distinct {int}
+distinct(A) ::= DISTINCT. {A = 1;}
+distinct(A) ::= ALL. {A = 0;}
+distinct(A) ::= . {A = 0;}
+
+// selcollist is a list of expressions that are to become the return
+// values of the SELECT statement. The "*" in statements like
+// "SELECT * FROM ..." is encoded as a special expression with an
+// opcode of TK_ALL.
+//
+%type selcollist {ExprList*}
+%destructor selcollist {sqliteExprListDelete($$);}
+%type sclp {ExprList*}
+%destructor sclp {sqliteExprListDelete($$);}
+sclp(A) ::= selcollist(X) COMMA. {A = X;}
+sclp(A) ::= . {A = 0;}
+selcollist(A) ::= sclp(P) expr(X) as(Y). {
+ A = sqliteExprListAppend(P,X,Y.n?&Y:0);
+}
+selcollist(A) ::= sclp(P) STAR. {
+ A = sqliteExprListAppend(P, sqliteExpr(TK_ALL, 0, 0, 0), 0);
+}
+selcollist(A) ::= sclp(P) nm(X) DOT STAR. {
+ Expr *pRight = sqliteExpr(TK_ALL, 0, 0, 0);
+ Expr *pLeft = sqliteExpr(TK_ID, 0, 0, &X);
+ A = sqliteExprListAppend(P, sqliteExpr(TK_DOT, pLeft, pRight, 0), 0);
+}
+
+// An option "AS <id>" phrase that can follow one of the expressions that
+// define the result set, or one of the tables in the FROM clause.
+//
+%type as {Token}
+as(X) ::= AS nm(Y). { X = Y; }
+as(X) ::= ids(Y). { X = Y; }
+as(X) ::= . { X.n = 0; }
+
+
+%type seltablist {SrcList*}
+%destructor seltablist {sqliteSrcListDelete($$);}
+%type stl_prefix {SrcList*}
+%destructor stl_prefix {sqliteSrcListDelete($$);}
+%type from {SrcList*}
+%destructor from {sqliteSrcListDelete($$);}
+
+// A complete FROM clause.
+//
+from(A) ::= . {A = sqliteMalloc(sizeof(*A));}
+from(A) ::= FROM seltablist(X). {A = X;}
+
+// "seltablist" is a "Select Table List" - the content of the FROM clause
+// in a SELECT statement. "stl_prefix" is a prefix of this list.
+//
+stl_prefix(A) ::= seltablist(X) joinop(Y). {
+ A = X;
+ if( A && A->nSrc>0 ) A->a[A->nSrc-1].jointype = Y;
+}
+stl_prefix(A) ::= . {A = 0;}
+seltablist(A) ::= stl_prefix(X) nm(Y) dbnm(D) as(Z) on_opt(N) using_opt(U). {
+ A = sqliteSrcListAppend(X,&Y,&D);
+ if( Z.n ) sqliteSrcListAddAlias(A,&Z);
+ if( N ){
+ if( A && A->nSrc>1 ){ A->a[A->nSrc-2].pOn = N; }
+ else { sqliteExprDelete(N); }
+ }
+ if( U ){
+ if( A && A->nSrc>1 ){ A->a[A->nSrc-2].pUsing = U; }
+ else { sqliteIdListDelete(U); }
+ }
+}
+seltablist(A) ::= stl_prefix(X) LP select(S) RP as(Z) on_opt(N) using_opt(U). {
+ A = sqliteSrcListAppend(X,0,0);
+ A->a[A->nSrc-1].pSelect = S;
+ if( Z.n ) sqliteSrcListAddAlias(A,&Z);
+ if( N ){
+ if( A && A->nSrc>1 ){ A->a[A->nSrc-2].pOn = N; }
+ else { sqliteExprDelete(N); }
+ }
+ if( U ){
+ if( A && A->nSrc>1 ){ A->a[A->nSrc-2].pUsing = U; }
+ else { sqliteIdListDelete(U); }
+ }
+}
+
+%type dbnm {Token}
+dbnm(A) ::= . {A.z=0; A.n=0;}
+dbnm(A) ::= DOT nm(X). {A = X;}
+
+%type joinop {int}
+%type joinop2 {int}
+joinop(X) ::= COMMA. { X = JT_INNER; }
+joinop(X) ::= JOIN. { X = JT_INNER; }
+joinop(X) ::= JOIN_KW(A) JOIN. { X = sqliteJoinType(pParse,&A,0,0); }
+joinop(X) ::= JOIN_KW(A) nm(B) JOIN. { X = sqliteJoinType(pParse,&A,&B,0); }
+joinop(X) ::= JOIN_KW(A) nm(B) nm(C) JOIN.
+ { X = sqliteJoinType(pParse,&A,&B,&C); }
+
+%type on_opt {Expr*}
+%destructor on_opt {sqliteExprDelete($$);}
+on_opt(N) ::= ON expr(E). {N = E;}
+on_opt(N) ::= . {N = 0;}
+
+%type using_opt {IdList*}
+%destructor using_opt {sqliteIdListDelete($$);}
+using_opt(U) ::= USING LP idxlist(L) RP. {U = L;}
+using_opt(U) ::= . {U = 0;}
+
+
+%type orderby_opt {ExprList*}
+%destructor orderby_opt {sqliteExprListDelete($$);}
+%type sortlist {ExprList*}
+%destructor sortlist {sqliteExprListDelete($$);}
+%type sortitem {Expr*}
+%destructor sortitem {sqliteExprDelete($$);}
+
+orderby_opt(A) ::= . {A = 0;}
+orderby_opt(A) ::= ORDER BY sortlist(X). {A = X;}
+sortlist(A) ::= sortlist(X) COMMA sortitem(Y) collate(C) sortorder(Z). {
+ A = sqliteExprListAppend(X,Y,0);
+ if( A ) A->a[A->nExpr-1].sortOrder = C+Z;
+}
+sortlist(A) ::= sortitem(Y) collate(C) sortorder(Z). {
+ A = sqliteExprListAppend(0,Y,0);
+ if( A ) A->a[0].sortOrder = C+Z;
+}
+sortitem(A) ::= expr(X). {A = X;}
+
+%type sortorder {int}
+%type collate {int}
+
+sortorder(A) ::= ASC. {A = SQLITE_SO_ASC;}
+sortorder(A) ::= DESC. {A = SQLITE_SO_DESC;}
+sortorder(A) ::= . {A = SQLITE_SO_ASC;}
+collate(C) ::= . {C = SQLITE_SO_UNK;}
+collate(C) ::= COLLATE id(X). {C = sqliteCollateType(X.z, X.n);}
+
+%type groupby_opt {ExprList*}
+%destructor groupby_opt {sqliteExprListDelete($$);}
+groupby_opt(A) ::= . {A = 0;}
+groupby_opt(A) ::= GROUP BY exprlist(X). {A = X;}
+
+%type having_opt {Expr*}
+%destructor having_opt {sqliteExprDelete($$);}
+having_opt(A) ::= . {A = 0;}
+having_opt(A) ::= HAVING expr(X). {A = X;}
+
+%type limit_opt {struct LimitVal}
+limit_opt(A) ::= . {A.limit = -1; A.offset = 0;}
+limit_opt(A) ::= LIMIT signed(X). {A.limit = X; A.offset = 0;}
+limit_opt(A) ::= LIMIT signed(X) OFFSET signed(Y).
+ {A.limit = X; A.offset = Y;}
+limit_opt(A) ::= LIMIT signed(X) COMMA signed(Y).
+ {A.limit = Y; A.offset = X;}
+
+/////////////////////////// The DELETE statement /////////////////////////////
+//
+cmd ::= DELETE FROM nm(X) dbnm(D) where_opt(Y). {
+ sqliteDeleteFrom(pParse, sqliteSrcListAppend(0,&X,&D), Y);
+}
+
+%type where_opt {Expr*}
+%destructor where_opt {sqliteExprDelete($$);}
+
+where_opt(A) ::= . {A = 0;}
+where_opt(A) ::= WHERE expr(X). {A = X;}
+
+%type setlist {ExprList*}
+%destructor setlist {sqliteExprListDelete($$);}
+
+////////////////////////// The UPDATE command ////////////////////////////////
+//
+cmd ::= UPDATE orconf(R) nm(X) dbnm(D) SET setlist(Y) where_opt(Z).
+ {sqliteUpdate(pParse,sqliteSrcListAppend(0,&X,&D),Y,Z,R);}
+
+setlist(A) ::= setlist(Z) COMMA nm(X) EQ expr(Y).
+ {A = sqliteExprListAppend(Z,Y,&X);}
+setlist(A) ::= nm(X) EQ expr(Y). {A = sqliteExprListAppend(0,Y,&X);}
+
+////////////////////////// The INSERT command /////////////////////////////////
+//
+cmd ::= insert_cmd(R) INTO nm(X) dbnm(D) inscollist_opt(F)
+ VALUES LP itemlist(Y) RP.
+ {sqliteInsert(pParse, sqliteSrcListAppend(0,&X,&D), Y, 0, F, R);}
+cmd ::= insert_cmd(R) INTO nm(X) dbnm(D) inscollist_opt(F) select(S).
+ {sqliteInsert(pParse, sqliteSrcListAppend(0,&X,&D), 0, S, F, R);}
+
+%type insert_cmd {int}
+insert_cmd(A) ::= INSERT orconf(R). {A = R;}
+insert_cmd(A) ::= REPLACE. {A = OE_Replace;}
+
+
+%type itemlist {ExprList*}
+%destructor itemlist {sqliteExprListDelete($$);}
+
+itemlist(A) ::= itemlist(X) COMMA expr(Y). {A = sqliteExprListAppend(X,Y,0);}
+itemlist(A) ::= expr(X). {A = sqliteExprListAppend(0,X,0);}
+
+%type inscollist_opt {IdList*}
+%destructor inscollist_opt {sqliteIdListDelete($$);}
+%type inscollist {IdList*}
+%destructor inscollist {sqliteIdListDelete($$);}
+
+inscollist_opt(A) ::= . {A = 0;}
+inscollist_opt(A) ::= LP inscollist(X) RP. {A = X;}
+inscollist(A) ::= inscollist(X) COMMA nm(Y). {A = sqliteIdListAppend(X,&Y);}
+inscollist(A) ::= nm(Y). {A = sqliteIdListAppend(0,&Y);}
+
+/////////////////////////// Expression Processing /////////////////////////////
+//
+%left OR.
+%left AND.
+%right NOT.
+%left EQ NE ISNULL NOTNULL IS LIKE GLOB BETWEEN IN.
+%left GT GE LT LE.
+%left BITAND BITOR LSHIFT RSHIFT.
+%left PLUS MINUS.
+%left STAR SLASH REM.
+%left CONCAT.
+%right UMINUS UPLUS BITNOT.
+%right ORACLE_OUTER_JOIN.
+
+%type expr {Expr*}
+%destructor expr {sqliteExprDelete($$);}
+
+expr(A) ::= LP(B) expr(X) RP(E). {A = X; sqliteExprSpan(A,&B,&E); }
+expr(A) ::= NULL(X). {A = sqliteExpr(TK_NULL, 0, 0, &X);}
+expr(A) ::= ID(X). {A = sqliteExpr(TK_ID, 0, 0, &X);}
+expr(A) ::= JOIN_KW(X). {A = sqliteExpr(TK_ID, 0, 0, &X);}
+expr(A) ::= nm(X) DOT nm(Y). {
+ Expr *temp1 = sqliteExpr(TK_ID, 0, 0, &X);
+ Expr *temp2 = sqliteExpr(TK_ID, 0, 0, &Y);
+ A = sqliteExpr(TK_DOT, temp1, temp2, 0);
+}
+expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). {
+ Expr *temp1 = sqliteExpr(TK_ID, 0, 0, &X);
+ Expr *temp2 = sqliteExpr(TK_ID, 0, 0, &Y);
+ Expr *temp3 = sqliteExpr(TK_ID, 0, 0, &Z);
+ Expr *temp4 = sqliteExpr(TK_DOT, temp2, temp3, 0);
+ A = sqliteExpr(TK_DOT, temp1, temp4, 0);
+}
+expr(A) ::= expr(B) ORACLE_OUTER_JOIN.
+ {A = B; ExprSetProperty(A,EP_Oracle8Join);}
+expr(A) ::= INTEGER(X). {A = sqliteExpr(TK_INTEGER, 0, 0, &X);}
+expr(A) ::= FLOAT(X). {A = sqliteExpr(TK_FLOAT, 0, 0, &X);}
+expr(A) ::= STRING(X). {A = sqliteExpr(TK_STRING, 0, 0, &X);}
+expr(A) ::= ID(X) LP exprlist(Y) RP(E). {
+ A = sqliteExprFunction(Y, &X);
+ sqliteExprSpan(A,&X,&E);
+}
+expr(A) ::= ID(X) LP STAR RP(E). {
+ A = sqliteExprFunction(0, &X);
+ sqliteExprSpan(A,&X,&E);
+}
+expr(A) ::= expr(X) AND expr(Y). {A = sqliteExpr(TK_AND, X, Y, 0);}
+expr(A) ::= expr(X) OR expr(Y). {A = sqliteExpr(TK_OR, X, Y, 0);}
+expr(A) ::= expr(X) LT expr(Y). {A = sqliteExpr(TK_LT, X, Y, 0);}
+expr(A) ::= expr(X) GT expr(Y). {A = sqliteExpr(TK_GT, X, Y, 0);}
+expr(A) ::= expr(X) LE expr(Y). {A = sqliteExpr(TK_LE, X, Y, 0);}
+expr(A) ::= expr(X) GE expr(Y). {A = sqliteExpr(TK_GE, X, Y, 0);}
+expr(A) ::= expr(X) NE expr(Y). {A = sqliteExpr(TK_NE, X, Y, 0);}
+expr(A) ::= expr(X) EQ expr(Y). {A = sqliteExpr(TK_EQ, X, Y, 0);}
+expr(A) ::= expr(X) BITAND expr(Y). {A = sqliteExpr(TK_BITAND, X, Y, 0);}
+expr(A) ::= expr(X) BITOR expr(Y). {A = sqliteExpr(TK_BITOR, X, Y, 0);}
+expr(A) ::= expr(X) LSHIFT expr(Y). {A = sqliteExpr(TK_LSHIFT, X, Y, 0);}
+expr(A) ::= expr(X) RSHIFT expr(Y). {A = sqliteExpr(TK_RSHIFT, X, Y, 0);}
+expr(A) ::= expr(X) likeop(OP) expr(Y). [LIKE] {
+ ExprList *pList = sqliteExprListAppend(0, Y, 0);
+ pList = sqliteExprListAppend(pList, X, 0);
+ A = sqliteExprFunction(pList, 0);
+ if( A ) A->op = OP;
+ sqliteExprSpan(A, &X->span, &Y->span);
+}
+expr(A) ::= expr(X) NOT likeop(OP) expr(Y). [LIKE] {
+ ExprList *pList = sqliteExprListAppend(0, Y, 0);
+ pList = sqliteExprListAppend(pList, X, 0);
+ A = sqliteExprFunction(pList, 0);
+ if( A ) A->op = OP;
+ A = sqliteExpr(TK_NOT, A, 0, 0);
+ sqliteExprSpan(A,&X->span,&Y->span);
+}
+%type likeop {int}
+likeop(A) ::= LIKE. {A = TK_LIKE;}
+likeop(A) ::= GLOB. {A = TK_GLOB;}
+expr(A) ::= expr(X) PLUS expr(Y). {A = sqliteExpr(TK_PLUS, X, Y, 0);}
+expr(A) ::= expr(X) MINUS expr(Y). {A = sqliteExpr(TK_MINUS, X, Y, 0);}
+expr(A) ::= expr(X) STAR expr(Y). {A = sqliteExpr(TK_STAR, X, Y, 0);}
+expr(A) ::= expr(X) SLASH expr(Y). {A = sqliteExpr(TK_SLASH, X, Y, 0);}
+expr(A) ::= expr(X) REM expr(Y). {A = sqliteExpr(TK_REM, X, Y, 0);}
+expr(A) ::= expr(X) CONCAT expr(Y). {A = sqliteExpr(TK_CONCAT, X, Y, 0);}
+expr(A) ::= expr(X) ISNULL(E). {
+ A = sqliteExpr(TK_ISNULL, X, 0, 0);
+ sqliteExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) IS NULL(E). {
+ A = sqliteExpr(TK_ISNULL, X, 0, 0);
+ sqliteExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) NOTNULL(E). {
+ A = sqliteExpr(TK_NOTNULL, X, 0, 0);
+ sqliteExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) NOT NULL(E). {
+ A = sqliteExpr(TK_NOTNULL, X, 0, 0);
+ sqliteExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) IS NOT NULL(E). {
+ A = sqliteExpr(TK_NOTNULL, X, 0, 0);
+ sqliteExprSpan(A,&X->span,&E);
+}
+expr(A) ::= NOT(B) expr(X). {
+ A = sqliteExpr(TK_NOT, X, 0, 0);
+ sqliteExprSpan(A,&B,&X->span);
+}
+expr(A) ::= BITNOT(B) expr(X). {
+ A = sqliteExpr(TK_BITNOT, X, 0, 0);
+ sqliteExprSpan(A,&B,&X->span);
+}
+expr(A) ::= MINUS(B) expr(X). [UMINUS] {
+ A = sqliteExpr(TK_UMINUS, X, 0, 0);
+ sqliteExprSpan(A,&B,&X->span);
+}
+expr(A) ::= PLUS(B) expr(X). [UPLUS] {
+ A = sqliteExpr(TK_UPLUS, X, 0, 0);
+ sqliteExprSpan(A,&B,&X->span);
+}
+expr(A) ::= LP(B) select(X) RP(E). {
+ A = sqliteExpr(TK_SELECT, 0, 0, 0);
+ if( A ) A->pSelect = X;
+ sqliteExprSpan(A,&B,&E);
+}
+expr(A) ::= expr(W) BETWEEN expr(X) AND expr(Y). {
+ ExprList *pList = sqliteExprListAppend(0, X, 0);
+ pList = sqliteExprListAppend(pList, Y, 0);
+ A = sqliteExpr(TK_BETWEEN, W, 0, 0);
+ if( A ) A->pList = pList;
+ sqliteExprSpan(A,&W->span,&Y->span);
+}
+expr(A) ::= expr(W) NOT BETWEEN expr(X) AND expr(Y). {
+ ExprList *pList = sqliteExprListAppend(0, X, 0);
+ pList = sqliteExprListAppend(pList, Y, 0);
+ A = sqliteExpr(TK_BETWEEN, W, 0, 0);
+ if( A ) A->pList = pList;
+ A = sqliteExpr(TK_NOT, A, 0, 0);
+ sqliteExprSpan(A,&W->span,&Y->span);
+}
+expr(A) ::= expr(X) IN LP exprlist(Y) RP(E). {
+ A = sqliteExpr(TK_IN, X, 0, 0);
+ if( A ) A->pList = Y;
+ sqliteExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) IN LP select(Y) RP(E). {
+ A = sqliteExpr(TK_IN, X, 0, 0);
+ if( A ) A->pSelect = Y;
+ sqliteExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) NOT IN LP exprlist(Y) RP(E). {
+ A = sqliteExpr(TK_IN, X, 0, 0);
+ if( A ) A->pList = Y;
+ A = sqliteExpr(TK_NOT, A, 0, 0);
+ sqliteExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) NOT IN LP select(Y) RP(E). {
+ A = sqliteExpr(TK_IN, X, 0, 0);
+ if( A ) A->pSelect = Y;
+ A = sqliteExpr(TK_NOT, A, 0, 0);
+ sqliteExprSpan(A,&X->span,&E);
+}
+
+/* CASE expressions */
+expr(A) ::= CASE(C) case_operand(X) case_exprlist(Y) case_else(Z) END(E). {
+ A = sqliteExpr(TK_CASE, X, Z, 0);
+ if( A ) A->pList = Y;
+ sqliteExprSpan(A, &C, &E);
+}
+%type case_exprlist {ExprList*}
+%destructor case_exprlist {sqliteExprListDelete($$);}
+case_exprlist(A) ::= case_exprlist(X) WHEN expr(Y) THEN expr(Z). {
+ A = sqliteExprListAppend(X, Y, 0);
+ A = sqliteExprListAppend(A, Z, 0);
+}
+case_exprlist(A) ::= WHEN expr(Y) THEN expr(Z). {
+ A = sqliteExprListAppend(0, Y, 0);
+ A = sqliteExprListAppend(A, Z, 0);
+}
+%type case_else {Expr*}
+case_else(A) ::= ELSE expr(X). {A = X;}
+case_else(A) ::= . {A = 0;}
+%type case_operand {Expr*}
+case_operand(A) ::= expr(X). {A = X;}
+case_operand(A) ::= . {A = 0;}
+
+%type exprlist {ExprList*}
+%destructor exprlist {sqliteExprListDelete($$);}
+%type expritem {Expr*}
+%destructor expritem {sqliteExprDelete($$);}
+
+exprlist(A) ::= exprlist(X) COMMA expritem(Y).
+ {A = sqliteExprListAppend(X,Y,0);}
+exprlist(A) ::= expritem(X). {A = sqliteExprListAppend(0,X,0);}
+expritem(A) ::= expr(X). {A = X;}
+expritem(A) ::= . {A = 0;}
+
+///////////////////////////// The CREATE INDEX command ///////////////////////
+//
+cmd ::= CREATE(S) temp(T) uniqueflag(U) INDEX nm(X)
+ ON nm(Y) dbnm(D) LP idxlist(Z) RP(E) onconf(R). {
+ SrcList *pSrc = sqliteSrcListAppend(0, &Y, &D);
+ if( U!=OE_None ) U = R;
+ if( U==OE_Default) U = OE_Abort;
+ sqliteCreateIndex(pParse, &X, pSrc, Z, U, T, &S, &E);
+}
+
+%type uniqueflag {int}
+uniqueflag(A) ::= UNIQUE. { A = OE_Abort; }
+uniqueflag(A) ::= . { A = OE_None; }
+
+%type idxlist {IdList*}
+%destructor idxlist {sqliteIdListDelete($$);}
+%type idxlist_opt {IdList*}
+%destructor idxlist_opt {sqliteIdListDelete($$);}
+%type idxitem {Token}
+
+idxlist_opt(A) ::= . {A = 0;}
+idxlist_opt(A) ::= LP idxlist(X) RP. {A = X;}
+idxlist(A) ::= idxlist(X) COMMA idxitem(Y). {A = sqliteIdListAppend(X,&Y);}
+idxlist(A) ::= idxitem(Y). {A = sqliteIdListAppend(0,&Y);}
+idxitem(A) ::= nm(X) sortorder. {A = X;}
+
+///////////////////////////// The DROP INDEX command /////////////////////////
+//
+
+cmd ::= DROP INDEX nm(X) dbnm(Y). {
+ sqliteDropIndex(pParse, sqliteSrcListAppend(0,&X,&Y));
+}
+
+
+///////////////////////////// The COPY command ///////////////////////////////
+//
+cmd ::= COPY orconf(R) nm(X) dbnm(D) FROM nm(Y) USING DELIMITERS STRING(Z).
+ {sqliteCopy(pParse,sqliteSrcListAppend(0,&X,&D),&Y,&Z,R);}
+cmd ::= COPY orconf(R) nm(X) dbnm(D) FROM nm(Y).
+ {sqliteCopy(pParse,sqliteSrcListAppend(0,&X,&D),&Y,0,R);}
+
+///////////////////////////// The VACUUM command /////////////////////////////
+//
+cmd ::= VACUUM. {sqliteVacuum(pParse,0);}
+cmd ::= VACUUM nm(X). {sqliteVacuum(pParse,&X);}
+
+///////////////////////////// The PRAGMA command /////////////////////////////
+//
+cmd ::= PRAGMA ids(X) EQ nm(Y). {sqlitePragma(pParse,&X,&Y,0);}
+cmd ::= PRAGMA ids(X) EQ ON(Y). {sqlitePragma(pParse,&X,&Y,0);}
+cmd ::= PRAGMA ids(X) EQ plus_num(Y). {sqlitePragma(pParse,&X,&Y,0);}
+cmd ::= PRAGMA ids(X) EQ minus_num(Y). {sqlitePragma(pParse,&X,&Y,1);}
+cmd ::= PRAGMA ids(X) LP nm(Y) RP. {sqlitePragma(pParse,&X,&Y,0);}
+cmd ::= PRAGMA ids(X). {sqlitePragma(pParse,&X,&X,0);}
+plus_num(A) ::= plus_opt number(X). {A = X;}
+minus_num(A) ::= MINUS number(X). {A = X;}
+number(A) ::= INTEGER(X). {A = X;}
+number(A) ::= FLOAT(X). {A = X;}
+plus_opt ::= PLUS.
+plus_opt ::= .
+
+//////////////////////////// The CREATE TRIGGER command /////////////////////
+
+cmd ::= CREATE(A) trigger_decl BEGIN trigger_cmd_list(S) END(Z). {
+ Token all;
+ all.z = A.z;
+ all.n = (Z.z - A.z) + Z.n;
+ sqliteFinishTrigger(pParse, S, &all);
+}
+
+trigger_decl ::= temp(T) TRIGGER nm(B) trigger_time(C) trigger_event(D)
+ ON nm(E) dbnm(DB) foreach_clause(F) when_clause(G). {
+ SrcList *pTab = sqliteSrcListAppend(0, &E, &DB);
+ sqliteBeginTrigger(pParse, &B, C, D.a, D.b, pTab, F, G, T);
+}
+
+%type trigger_time {int}
+trigger_time(A) ::= BEFORE. { A = TK_BEFORE; }
+trigger_time(A) ::= AFTER. { A = TK_AFTER; }
+trigger_time(A) ::= INSTEAD OF. { A = TK_INSTEAD;}
+trigger_time(A) ::= . { A = TK_BEFORE; }
+
+%type trigger_event {struct TrigEvent}
+%destructor trigger_event {sqliteIdListDelete($$.b);}
+trigger_event(A) ::= DELETE. { A.a = TK_DELETE; A.b = 0; }
+trigger_event(A) ::= INSERT. { A.a = TK_INSERT; A.b = 0; }
+trigger_event(A) ::= UPDATE. { A.a = TK_UPDATE; A.b = 0;}
+trigger_event(A) ::= UPDATE OF inscollist(X). {A.a = TK_UPDATE; A.b = X; }
+
+%type foreach_clause {int}
+foreach_clause(A) ::= . { A = TK_ROW; }
+foreach_clause(A) ::= FOR EACH ROW. { A = TK_ROW; }
+foreach_clause(A) ::= FOR EACH STATEMENT. { A = TK_STATEMENT; }
+
+%type when_clause {Expr *}
+when_clause(A) ::= . { A = 0; }
+when_clause(A) ::= WHEN expr(X). { A = X; }
+
+%type trigger_cmd_list {TriggerStep *}
+%destructor trigger_cmd_list {sqliteDeleteTriggerStep($$);}
+trigger_cmd_list(A) ::= trigger_cmd(X) SEMI trigger_cmd_list(Y). {
+ X->pNext = Y;
+ A = X;
+}
+trigger_cmd_list(A) ::= . { A = 0; }
+
+%type trigger_cmd {TriggerStep *}
+%destructor trigger_cmd {sqliteDeleteTriggerStep($$);}
+// UPDATE
+trigger_cmd(A) ::= UPDATE orconf(R) nm(X) SET setlist(Y) where_opt(Z).
+ { A = sqliteTriggerUpdateStep(&X, Y, Z, R); }
+
+// INSERT
+trigger_cmd(A) ::= INSERT orconf(R) INTO nm(X) inscollist_opt(F)
+ VALUES LP itemlist(Y) RP.
+{A = sqliteTriggerInsertStep(&X, F, Y, 0, R);}
+
+trigger_cmd(A) ::= INSERT orconf(R) INTO nm(X) inscollist_opt(F) select(S).
+ {A = sqliteTriggerInsertStep(&X, F, 0, S, R);}
+
+// DELETE
+trigger_cmd(A) ::= DELETE FROM nm(X) where_opt(Y).
+ {A = sqliteTriggerDeleteStep(&X, Y);}
+
+// SELECT
+trigger_cmd(A) ::= select(X). {A = sqliteTriggerSelectStep(X); }
+
+// The special RAISE expression that may occur in trigger programs
+expr(A) ::= RAISE(X) LP IGNORE RP(Y). {
+ A = sqliteExpr(TK_RAISE, 0, 0, 0);
+ A->iColumn = OE_Ignore;
+ sqliteExprSpan(A, &X, &Y);
+}
+expr(A) ::= RAISE(X) LP ROLLBACK COMMA nm(Z) RP(Y). {
+ A = sqliteExpr(TK_RAISE, 0, 0, &Z);
+ A->iColumn = OE_Rollback;
+ sqliteExprSpan(A, &X, &Y);
+}
+expr(A) ::= RAISE(X) LP ABORT COMMA nm(Z) RP(Y). {
+ A = sqliteExpr(TK_RAISE, 0, 0, &Z);
+ A->iColumn = OE_Abort;
+ sqliteExprSpan(A, &X, &Y);
+}
+expr(A) ::= RAISE(X) LP FAIL COMMA nm(Z) RP(Y). {
+ A = sqliteExpr(TK_RAISE, 0, 0, &Z);
+ A->iColumn = OE_Fail;
+ sqliteExprSpan(A, &X, &Y);
+}
+
+//////////////////////// DROP TRIGGER statement //////////////////////////////
+cmd ::= DROP TRIGGER nm(X) dbnm(D). {
+ sqliteDropTrigger(pParse,sqliteSrcListAppend(0,&X,&D));
+}
+
+//////////////////////// ATTACH DATABASE file AS name /////////////////////////
+cmd ::= ATTACH database_kw_opt ids(F) AS nm(D). {
+ sqliteAttach(pParse, &F, &D);
+}
+
+database_kw_opt ::= DATABASE.
+database_kw_opt ::= .
+
+//////////////////////// DETACH DATABASE name /////////////////////////////////
+cmd ::= DETACH database_kw_opt nm(D). {
+ sqliteDetach(pParse, &D);
+}
diff --git a/kexi/3rdparty/kexisql/src/patches/shell.c.patch b/kexi/3rdparty/kexisql/src/patches/shell.c.patch
new file mode 100644
index 000000000..0d7d2dae8
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/patches/shell.c.patch
@@ -0,0 +1,217 @@
+--- shell.c.orig/shell.c 2004-07-22 22:08:28.000000000 +0200
++++ shell.c 2005-05-04 18:47:40.000000000 +0200
+@@ -12,7 +12,7 @@
+ ** This file contains code to implement the "sqlite" command line
+ ** utility for accessing SQLite databases.
+ **
+-** $Id: shell.c,v 1.93 2004/03/17 23:42:13 drh Exp $
++** $Id: shell.c 409279 2005-05-04 15:23:02Z staniek $
+ */
+ #include <stdlib.h>
+ #include <string.h>
+@@ -20,11 +20,19 @@
+ #include "sqlite.h"
+ #include <ctype.h>
+
++#if defined(_WIN32)
++# include <io.h>
++#endif
++
+ #if !defined(_WIN32) && !defined(WIN32) && !defined(__MACOS__)
+ # include <signal.h>
+ # include <pwd.h>
+ # include <unistd.h>
+ # include <sys/types.h>
++
++#define strnicmp strncasecmp
++#define stricmp strcasecmp
++
+ #endif
+
+ #ifdef __MACOS__
+@@ -47,6 +55,12 @@
+ # define stifle_history(X)
+ #endif
+
++/* js */
++char verboseDump = 0;
++int nObjects = 0; /* used to count progress */
++int curObjects = 0;
++/* \js */
++
+ /* Make sure isatty() has a prototype.
+ */
+ extern int isatty();
+@@ -80,7 +94,28 @@
+ /*
+ ** Determines if a string is a number of not.
+ */
+-extern int sqliteIsNumber(const char*);
++/* extern int sqliteIsNumber(const char*); */
++static int isNumber(const unsigned 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;
++}
++
+
+ /*
+ ** This routine reads a line of text from standard input, stores
+@@ -392,7 +427,7 @@
+ char *zSep = i>0 ? ",": "";
+ if( azArg[i]==0 ){
+ fprintf(p->out,"%sNULL",zSep);
+- }else if( sqliteIsNumber(azArg[i]) ){
++ }else if( isNumber(azArg[i]) ){
+ fprintf(p->out,"%s%s",zSep, azArg[i]);
+ }else{
+ if( zSep[0] ) fprintf(p->out,"%s",zSep);
+@@ -452,10 +487,14 @@
+ */
+ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
+ struct callback_data *p = (struct callback_data *)pArg;
++ struct callback_data d2;
+ if( nArg!=3 ) return 1;
+ fprintf(p->out, "%s;\n", azArg[2]);
+ if( strcmp(azArg[1],"table")==0 ){
+- struct callback_data d2;
++/* js */
++ if (verboseDump)
++ fprintf(stderr, "%%%d\n", (++curObjects * 100 / nObjects));
++/* \js */
+ d2 = *p;
+ d2.mode = MODE_Insert;
+ d2.zDestTable = 0;
+@@ -542,6 +581,14 @@
+ int rc = 0;
+ char *azArg[50];
+
++/* js */
++ sqlite_vm *pVm;
++ char *errMsg;
++ int ncolumns; /* OUT: Number of columns in result */
++ const char **pazValue; /* OUT: Column data */
++ const char **pazColName; /* OUT: Column names and datatypes */
++/* \js */
++
+ /* Parse the input line into tokens.
+ */
+ while( zLine[i] && nArg<ArraySize(azArg) ){
+@@ -586,6 +633,24 @@
+ if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
+ char *zErrMsg = 0;
+ open_db(p);
++
++/* js */
++if (SQLITE_OK!=sqlite_compile(p->db, "SELECT COUNT(1) FROM sqlite_master",
++0, &pVm, &errMsg)) {
++ fprintf(stderr, "%s\n", errMsg);
++ exit(1);
++}
++if (SQLITE_ROW!=sqlite_step( pVm, &ncolumns, &pazValue, &pazColName )) {
++ exit(1);
++}
++nObjects = atoi(pazValue[0]);
++
++if (SQLITE_OK!=sqlite_finalize( pVm, &errMsg)) {
++ fprintf(stderr, "%s\n", errMsg);
++ exit(1);
++}
++/* \js */
++
+ fprintf(p->out, "BEGIN TRANSACTION;\n");
+ if( nArg==1 ){
+ sqlite_exec(p->db,
+@@ -812,8 +877,9 @@
+ data.showHeader = 0;
+ data.mode = MODE_Semi;
+ if( nArg>1 ){
+- extern int sqliteStrICmp(const char*,const char*);
+- if( sqliteStrICmp(azArg[1],"sqlite_master")==0 ){
++/* extern int sqliteStrICmp(const char*,const char*);
++ if( sqliteStrICmp(azArg[1],"sqlite_master")==0 ){*/
++ if( stricmp(azArg[1],"sqlite_master")==0 ){
+ char *new_argv[2], *new_colv[2];
+ new_argv[0] = "CREATE TABLE sqlite_master (\n"
+ " type text,\n"
+@@ -826,7 +892,8 @@
+ new_colv[0] = "sql";
+ new_colv[1] = 0;
+ callback(&data, 1, new_argv, new_colv);
+- }else if( sqliteStrICmp(azArg[1],"sqlite_temp_master")==0 ){
++/* }else if( sqliteStrICmp(azArg[1],"sqlite_temp_master")==0 ){*/
++ }else if( stricmp(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"
+@@ -997,10 +1064,11 @@
+ ** as is the Oracle "/".
+ */
+ static int _is_command_terminator(const char *zLine){
+- extern int sqliteStrNICmp(const char*,const char*,int);
++/* 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]) ){
++/* if( sqliteStrNICmp(zLine,"go",2)==0 && _all_whitespace(&zLine[2]) ){ */
++ if( strnicmp(zLine,"go",2)==0 && _all_whitespace(&zLine[2]) ){
+ return 1; /* SQL Server */
+ }
+ return 0;
+@@ -1210,7 +1278,7 @@
+ const char *zInitFile = 0;
+ char *zFirstCmd = 0;
+ int i;
+- extern int sqliteOsFileExists(const char*);
++/* extern int sqliteOsFileExists(const char*); */
+
+ #ifdef __MACOS__
+ argc = ccommand(&argv);
+@@ -1257,7 +1325,7 @@
+ ** files from being created if a user mistypes the database name argument
+ ** to the sqlite command-line tool.
+ */
+- if( sqliteOsFileExists(data.zDbFilename) ){
++ if( access(data.zDbFilename, 0)==0 ){
+ open_db(&data);
+ }
+
+@@ -1297,8 +1365,10 @@
+ }else if( strcmp(z,"-echo")==0 ){
+ data.echoOn = 1;
+ }else if( strcmp(z,"-version")==0 ){
+- printf("%s\n", sqlite_version);
++ printf("%s\n", sqlite_libversion());
+ return 1;
++ }else if( strcmp(z,"-verbose-dump")==0 ){
++ verboseDump = 1;
+ }else if( strcmp(z,"-help")==0 ){
+ usage(1);
+ }else{
+@@ -1330,9 +1400,9 @@
+ char *zHome;
+ char *zHistory = 0;
+ printf(
+- "SQLite version %s\n"
++ "SQLite version %s (bundled with Kexi)\n"
+ "Enter \".help\" for instructions\n",
+- sqlite_version
++ sqlite_libversion()
+ );
+ zHome = find_home_dir();
+ if( zHome && (zHistory = malloc(strlen(zHome)+20))!=0 ){
diff --git a/kexi/3rdparty/kexisql/src/pragma.c b/kexi/3rdparty/kexisql/src/pragma.c
new file mode 100644
index 000000000..4f1026573
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/printf.c b/kexi/3rdparty/kexisql/src/printf.c
new file mode 100644
index 000000000..f867d62af
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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];
+ {
+ register char *cset; /* Use registers for speed */
+ register 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 ){
+ register 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 ){
+ register 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/kexi/3rdparty/kexisql/src/random.c b/kexi/3rdparty/kexisql/src/random.c
new file mode 100644
index 000000000..b3d08a124
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/select.c b/kexi/3rdparty/kexisql/src/select.c
new file mode 100644
index 000000000..7ae02d330
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/shell.c b/kexi/3rdparty/kexisql/src/shell.c
new file mode 100644
index 000000000..6dbed415b
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/shell.c
@@ -0,0 +1,1424 @@
+/*
+** 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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "sqlite.h"
+#include <ctype.h>
+
+#if defined(_WIN32)
+# include <io.h>
+#endif
+
+#if !defined(_WIN32) && !defined(WIN32) && !defined(__MACOS__)
+# include <signal.h>
+# include <pwd.h>
+# include <unistd.h>
+# include <sys/types.h>
+
+#define strnicmp strncasecmp
+#define stricmp strcasecmp
+
+#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
+
+/* js */
+char verboseDump = 0;
+int nObjects = 0; /* used to count progress */
+int curObjects = 0;
+/* \js */
+
+/* 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*); */
+static int isNumber(const unsigned 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;
+}
+
+
+/*
+** 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( isNumber(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;
+ struct callback_data d2;
+ if( nArg!=3 ) return 1;
+ fprintf(p->out, "%s;\n", azArg[2]);
+ if( strcmp(azArg[1],"table")==0 ){
+/* js */
+ if (verboseDump)
+ fprintf(stderr, "%%%d\n", (++curObjects * 100 / nObjects));
+/* \js */
+ 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];
+
+/* js */
+ sqlite_vm *pVm;
+ char *errMsg;
+ int ncolumns; /* OUT: Number of columns in result */
+ const char **pazValue; /* OUT: Column data */
+ const char **pazColName; /* OUT: Column names and datatypes */
+/* \js */
+
+ /* 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);
+
+/* js */
+if (SQLITE_OK!=sqlite_compile(p->db, "SELECT COUNT(1) FROM sqlite_master",
+0, &pVm, &errMsg)) {
+ fprintf(stderr, "%s\n", errMsg);
+ exit(1);
+}
+if (SQLITE_ROW!=sqlite_step( pVm, &ncolumns, &pazValue, &pazColName )) {
+ exit(1);
+}
+nObjects = atoi(pazValue[0]);
+
+if (SQLITE_OK!=sqlite_finalize( pVm, &errMsg)) {
+ fprintf(stderr, "%s\n", errMsg);
+ exit(1);
+}
+/* \js */
+
+ 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), name",
+ 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), name",
+ 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,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 ){*/
+ if( stricmp(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 ){*/
+ }else if( stricmp(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]) ){ */
+ if( strnicmp(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( access(data.zDbFilename, 0)==0 ){
+ 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_libversion());
+ return 1;
+ }else if( strcmp(z,"-verbose-dump")==0 ){
+ verboseDump = 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 (bundled with Kexi)\n"
+ "Enter \".help\" for instructions\n",
+ sqlite_libversion()
+ );
+ 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/kexi/3rdparty/kexisql/src/sqlite.h b/kexi/3rdparty/kexisql/src/sqlite.h
new file mode 100644
index 000000000..dd227cd00
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/sqlite.h
@@ -0,0 +1,868 @@
+/*
+** 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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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.15"
+
+/*
+** 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
+
+/*
+** 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 contraint 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 statment 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
+** descripts (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" milleseconds of sleeping have been done. After
+** "ms" milleseconds 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 implmentation 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 cancelled, 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 represeted 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 rekeyed */
+ 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/kexi/3rdparty/kexisql/src/sqliteInt.h b/kexi/3rdparty/kexisql/src/sqliteInt.h
new file mode 100644
index 000000000..dd0710f9d
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/sqliteInt.h
@@ -0,0 +1,1270 @@
+/*
+** 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 410297 2005-05-07 13:34:19Z staniek $
+*/
+#include "sqliteconfig.h"
+#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 **, const 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/kexi/3rdparty/kexisql/src/sqliteconfig.h b/kexi/3rdparty/kexisql/src/sqliteconfig.h
new file mode 100644
index 000000000..d34a85a41
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/sqliteconfig.h
@@ -0,0 +1,7 @@
+#ifdef _WIN32
+# define SQLITE_PTR_SZ 4
+#else
+# include "../../../../config.h"
+# define SQLITE_PTR_SZ SIZEOF_CHAR_P
+#endif
+
diff --git a/kexi/3rdparty/kexisql/src/table.c b/kexi/3rdparty/kexisql/src/table.c
new file mode 100644
index 000000000..48c852d48
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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/kexi/3rdparty/kexisql/src/tclsqlite.c b/kexi/3rdparty/kexisql/src/tclsqlite.c
new file mode 100644
index 000000000..fdc2f5e88
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/tclsqlite.c
@@ -0,0 +1,1293 @@
+/*
+** 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.
+**
+*************************************************************************
+** A TCL Interface to SQLite
+**
+** $Id: tclsqlite.c 410099 2005-05-06 17:52:07Z staniek $
+*/
+#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
+
+#include "sqliteInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/*
+** If TCL uses UTF-8 and SQLite is configured to use iso8859, then we
+** have to do a translation when going between the two. Set the
+** UTF_TRANSLATION_NEEDED macro to indicate that we need to do
+** this translation.
+*/
+#if defined(TCL_UTF_MAX) && !defined(SQLITE_UTF8)
+# define UTF_TRANSLATION_NEEDED 1
+#endif
+
+/*
+** New SQL functions can be created as TCL scripts. Each such function
+** is described by an instance of the following structure.
+*/
+typedef struct SqlFunc SqlFunc;
+struct SqlFunc {
+ Tcl_Interp *interp; /* The TCL interpret to execute the function */
+ char *zScript; /* The script to be run */
+ SqlFunc *pNext; /* Next function on the list of them all */
+};
+
+/*
+** There is one instance of this structure for each SQLite database
+** that has been opened by the SQLite TCL interface.
+*/
+typedef struct SqliteDb SqliteDb;
+struct SqliteDb {
+ sqlite *db; /* The "real" database structure */
+ Tcl_Interp *interp; /* The interpreter used for this database */
+ char *zBusy; /* The busy callback routine */
+ char *zCommit; /* The commit hook callback routine */
+ char *zTrace; /* The trace callback routine */
+ char *zProgress; /* The progress callback routine */
+ char *zAuth; /* The authorization callback routine */
+ SqlFunc *pFunc; /* List of SQL functions */
+ int rc; /* Return code of most recent sqlite_exec() */
+};
+
+/*
+** An instance of this structure passes information thru the sqlite
+** logic from the original TCL command into the callback routine.
+*/
+typedef struct CallbackData CallbackData;
+struct CallbackData {
+ Tcl_Interp *interp; /* The TCL interpreter */
+ char *zArray; /* The array into which data is written */
+ Tcl_Obj *pCode; /* The code to execute for each row */
+ int once; /* Set for first callback only */
+ int tcl_rc; /* Return code from TCL script */
+ int nColName; /* Number of entries in the azColName[] array */
+ char **azColName; /* Column names translated to UTF-8 */
+};
+
+#ifdef UTF_TRANSLATION_NEEDED
+/*
+** Called for each row of the result.
+**
+** This version is used when TCL expects UTF-8 data but the database
+** uses the ISO8859 format. A translation must occur from ISO8859 into
+** UTF-8.
+*/
+static int DbEvalCallback(
+ void *clientData, /* An instance of CallbackData */
+ int nCol, /* Number of columns in the result */
+ char ** azCol, /* Data for each column */
+ char ** azN /* Name for each column */
+){
+ CallbackData *cbData = (CallbackData*)clientData;
+ int i, rc;
+ Tcl_DString dCol;
+ Tcl_DStringInit(&dCol);
+ if( cbData->azColName==0 ){
+ assert( cbData->once );
+ cbData->once = 0;
+ if( cbData->zArray[0] ){
+ Tcl_SetVar2(cbData->interp, cbData->zArray, "*", "", 0);
+ }
+ cbData->azColName = malloc( nCol*sizeof(char*) );
+ if( cbData->azColName==0 ){ return 1; }
+ cbData->nColName = nCol;
+ for(i=0; i<nCol; i++){
+ Tcl_ExternalToUtfDString(NULL, azN[i], -1, &dCol);
+ cbData->azColName[i] = malloc( Tcl_DStringLength(&dCol) + 1 );
+ if( cbData->azColName[i] ){
+ strcpy(cbData->azColName[i], Tcl_DStringValue(&dCol));
+ }else{
+ return 1;
+ }
+ if( cbData->zArray[0] ){
+ Tcl_SetVar2(cbData->interp, cbData->zArray, "*",
+ Tcl_DStringValue(&dCol), TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
+ if( azN[nCol]!=0 ){
+ Tcl_DString dType;
+ Tcl_DStringInit(&dType);
+ Tcl_DStringAppend(&dType, "typeof:", -1);
+ Tcl_DStringAppend(&dType, Tcl_DStringValue(&dCol), -1);
+ Tcl_DStringFree(&dCol);
+ Tcl_ExternalToUtfDString(NULL, azN[i+nCol], -1, &dCol);
+ Tcl_SetVar2(cbData->interp, cbData->zArray,
+ Tcl_DStringValue(&dType), Tcl_DStringValue(&dCol),
+ TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
+ Tcl_DStringFree(&dType);
+ }
+ }
+
+ Tcl_DStringFree(&dCol);
+ }
+ }
+ if( azCol!=0 ){
+ if( cbData->zArray[0] ){
+ for(i=0; i<nCol; i++){
+ char *z = azCol[i];
+ if( z==0 ) z = "";
+ Tcl_DStringInit(&dCol);
+ Tcl_ExternalToUtfDString(NULL, z, -1, &dCol);
+ Tcl_SetVar2(cbData->interp, cbData->zArray, cbData->azColName[i],
+ Tcl_DStringValue(&dCol), 0);
+ Tcl_DStringFree(&dCol);
+ }
+ }else{
+ for(i=0; i<nCol; i++){
+ char *z = azCol[i];
+ if( z==0 ) z = "";
+ Tcl_DStringInit(&dCol);
+ Tcl_ExternalToUtfDString(NULL, z, -1, &dCol);
+ Tcl_SetVar(cbData->interp, cbData->azColName[i],
+ Tcl_DStringValue(&dCol), 0);
+ Tcl_DStringFree(&dCol);
+ }
+ }
+ }
+ rc = Tcl_EvalObj(cbData->interp, cbData->pCode);
+ if( rc==TCL_CONTINUE ) rc = TCL_OK;
+ cbData->tcl_rc = rc;
+ return rc!=TCL_OK;
+}
+#endif /* UTF_TRANSLATION_NEEDED */
+
+#ifndef UTF_TRANSLATION_NEEDED
+/*
+** Called for each row of the result.
+**
+** This version is used when either of the following is true:
+**
+** (1) This version of TCL uses UTF-8 and the data in the
+** SQLite database is already in the UTF-8 format.
+**
+** (2) This version of TCL uses ISO8859 and the data in the
+** SQLite database is already in the ISO8859 format.
+*/
+static int DbEvalCallback(
+ void *clientData, /* An instance of CallbackData */
+ int nCol, /* Number of columns in the result */
+ char ** azCol, /* Data for each column */
+ char ** azN /* Name for each column */
+){
+ CallbackData *cbData = (CallbackData*)clientData;
+ int i, rc;
+ if( azCol==0 || (cbData->once && cbData->zArray[0]) ){
+ Tcl_SetVar2(cbData->interp, cbData->zArray, "*", "", 0);
+ for(i=0; i<nCol; i++){
+ Tcl_SetVar2(cbData->interp, cbData->zArray, "*", azN[i],
+ TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
+ if( azN[nCol] ){
+ char *z = sqlite_mprintf("typeof:%s", azN[i]);
+ Tcl_SetVar2(cbData->interp, cbData->zArray, z, azN[i+nCol],
+ TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
+ sqlite_freemem(z);
+ }
+ }
+ cbData->once = 0;
+ }
+ if( azCol!=0 ){
+ if( cbData->zArray[0] ){
+ for(i=0; i<nCol; i++){
+ char *z = azCol[i];
+ if( z==0 ) z = "";
+ Tcl_SetVar2(cbData->interp, cbData->zArray, azN[i], z, 0);
+ }
+ }else{
+ for(i=0; i<nCol; i++){
+ char *z = azCol[i];
+ if( z==0 ) z = "";
+ Tcl_SetVar(cbData->interp, azN[i], z, 0);
+ }
+ }
+ }
+ rc = Tcl_EvalObj(cbData->interp, cbData->pCode);
+ if( rc==TCL_CONTINUE ) rc = TCL_OK;
+ cbData->tcl_rc = rc;
+ return rc!=TCL_OK;
+}
+#endif
+
+/*
+** This is an alternative callback for database queries. Instead
+** of invoking a TCL script to handle the result, this callback just
+** appends each column of the result to a list. After the query
+** is complete, the list is returned.
+*/
+static int DbEvalCallback2(
+ void *clientData, /* An instance of CallbackData */
+ int nCol, /* Number of columns in the result */
+ char ** azCol, /* Data for each column */
+ char ** azN /* Name for each column */
+){
+ Tcl_Obj *pList = (Tcl_Obj*)clientData;
+ int i;
+ if( azCol==0 ) return 0;
+ for(i=0; i<nCol; i++){
+ Tcl_Obj *pElem;
+ if( azCol[i] && *azCol[i] ){
+#ifdef UTF_TRANSLATION_NEEDED
+ Tcl_DString dCol;
+ Tcl_DStringInit(&dCol);
+ Tcl_ExternalToUtfDString(NULL, azCol[i], -1, &dCol);
+ pElem = Tcl_NewStringObj(Tcl_DStringValue(&dCol), -1);
+ Tcl_DStringFree(&dCol);
+#else
+ pElem = Tcl_NewStringObj(azCol[i], -1);
+#endif
+ }else{
+ pElem = Tcl_NewObj();
+ }
+ Tcl_ListObjAppendElement(0, pList, pElem);
+ }
+ return 0;
+}
+
+/*
+** This is a second alternative callback for database queries. A the
+** first column of the first row of the result is made the TCL result.
+*/
+static int DbEvalCallback3(
+ void *clientData, /* An instance of CallbackData */
+ int nCol, /* Number of columns in the result */
+ char ** azCol, /* Data for each column */
+ char ** azN /* Name for each column */
+){
+ Tcl_Interp *interp = (Tcl_Interp*)clientData;
+ Tcl_Obj *pElem;
+ if( azCol==0 ) return 1;
+ if( nCol==0 ) return 1;
+#ifdef UTF_TRANSLATION_NEEDED
+ {
+ Tcl_DString dCol;
+ Tcl_DStringInit(&dCol);
+ Tcl_ExternalToUtfDString(NULL, azCol[0], -1, &dCol);
+ pElem = Tcl_NewStringObj(Tcl_DStringValue(&dCol), -1);
+ Tcl_DStringFree(&dCol);
+ }
+#else
+ pElem = Tcl_NewStringObj(azCol[0], -1);
+#endif
+ Tcl_SetObjResult(interp, pElem);
+ return 1;
+}
+
+/*
+** Called when the command is deleted.
+*/
+static void DbDeleteCmd(void *db){
+ SqliteDb *pDb = (SqliteDb*)db;
+ sqlite_close(pDb->db);
+ while( pDb->pFunc ){
+ SqlFunc *pFunc = pDb->pFunc;
+ pDb->pFunc = pFunc->pNext;
+ Tcl_Free((char*)pFunc);
+ }
+ if( pDb->zBusy ){
+ Tcl_Free(pDb->zBusy);
+ }
+ if( pDb->zTrace ){
+ Tcl_Free(pDb->zTrace);
+ }
+ if( pDb->zAuth ){
+ Tcl_Free(pDb->zAuth);
+ }
+ Tcl_Free((char*)pDb);
+}
+
+/*
+** This routine is called when a database file is locked while trying
+** to execute SQL.
+*/
+static int DbBusyHandler(void *cd, const char *zTable, int nTries){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ int rc;
+ char zVal[30];
+ char *zCmd;
+ Tcl_DString cmd;
+
+ Tcl_DStringInit(&cmd);
+ Tcl_DStringAppend(&cmd, pDb->zBusy, -1);
+ Tcl_DStringAppendElement(&cmd, zTable);
+ sprintf(zVal, " %d", nTries);
+ Tcl_DStringAppend(&cmd, zVal, -1);
+ zCmd = Tcl_DStringValue(&cmd);
+ rc = Tcl_Eval(pDb->interp, zCmd);
+ Tcl_DStringFree(&cmd);
+ if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){
+ return 0;
+ }
+ return 1;
+}
+
+/*
+** This routine is invoked as the 'progress callback' for the database.
+*/
+static int DbProgressHandler(void *cd){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ int rc;
+
+ assert( pDb->zProgress );
+ rc = Tcl_Eval(pDb->interp, pDb->zProgress);
+ if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** This routine is called by the SQLite trace handler whenever a new
+** block of SQL is executed. The TCL script in pDb->zTrace is executed.
+*/
+static void DbTraceHandler(void *cd, const char *zSql){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ Tcl_DString str;
+
+ Tcl_DStringInit(&str);
+ Tcl_DStringAppend(&str, pDb->zTrace, -1);
+ Tcl_DStringAppendElement(&str, zSql);
+ Tcl_Eval(pDb->interp, Tcl_DStringValue(&str));
+ Tcl_DStringFree(&str);
+ Tcl_ResetResult(pDb->interp);
+}
+
+/*
+** This routine is called when a transaction is committed. The
+** TCL script in pDb->zCommit is executed. If it returns non-zero or
+** if it throws an exception, the transaction is rolled back instead
+** of being committed.
+*/
+static int DbCommitHandler(void *cd){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ int rc;
+
+ rc = Tcl_Eval(pDb->interp, pDb->zCommit);
+ if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** This routine is called to evaluate an SQL function implemented
+** using TCL script.
+*/
+static void tclSqlFunc(sqlite_func *context, int argc, const char **argv){
+ SqlFunc *p = sqlite_user_data(context);
+ Tcl_DString cmd;
+ int i;
+ int rc;
+
+ Tcl_DStringInit(&cmd);
+ Tcl_DStringAppend(&cmd, p->zScript, -1);
+ for(i=0; i<argc; i++){
+ Tcl_DStringAppendElement(&cmd, argv[i] ? argv[i] : "");
+ }
+ rc = Tcl_Eval(p->interp, Tcl_DStringValue(&cmd));
+ if( rc ){
+ sqlite_set_result_error(context, Tcl_GetStringResult(p->interp), -1);
+ }else{
+ sqlite_set_result_string(context, Tcl_GetStringResult(p->interp), -1);
+ }
+}
+#ifndef SQLITE_OMIT_AUTHORIZATION
+/*
+** This is the authentication function. It appends the authentication
+** type code and the two arguments to zCmd[] then invokes the result
+** on the interpreter. The reply is examined to determine if the
+** authentication fails or succeeds.
+*/
+static int auth_callback(
+ void *pArg,
+ int code,
+ const char *zArg1,
+ const char *zArg2,
+ const char *zArg3,
+ const char *zArg4
+){
+ char *zCode;
+ Tcl_DString str;
+ int rc;
+ const char *zReply;
+ SqliteDb *pDb = (SqliteDb*)pArg;
+
+ switch( code ){
+ case SQLITE_COPY : zCode="SQLITE_COPY"; break;
+ case SQLITE_CREATE_INDEX : zCode="SQLITE_CREATE_INDEX"; break;
+ case SQLITE_CREATE_TABLE : zCode="SQLITE_CREATE_TABLE"; break;
+ case SQLITE_CREATE_TEMP_INDEX : zCode="SQLITE_CREATE_TEMP_INDEX"; break;
+ case SQLITE_CREATE_TEMP_TABLE : zCode="SQLITE_CREATE_TEMP_TABLE"; break;
+ case SQLITE_CREATE_TEMP_TRIGGER: zCode="SQLITE_CREATE_TEMP_TRIGGER"; break;
+ case SQLITE_CREATE_TEMP_VIEW : zCode="SQLITE_CREATE_TEMP_VIEW"; break;
+ case SQLITE_CREATE_TRIGGER : zCode="SQLITE_CREATE_TRIGGER"; break;
+ case SQLITE_CREATE_VIEW : zCode="SQLITE_CREATE_VIEW"; break;
+ case SQLITE_DELETE : zCode="SQLITE_DELETE"; break;
+ case SQLITE_DROP_INDEX : zCode="SQLITE_DROP_INDEX"; break;
+ case SQLITE_DROP_TABLE : zCode="SQLITE_DROP_TABLE"; break;
+ case SQLITE_DROP_TEMP_INDEX : zCode="SQLITE_DROP_TEMP_INDEX"; break;
+ case SQLITE_DROP_TEMP_TABLE : zCode="SQLITE_DROP_TEMP_TABLE"; break;
+ case SQLITE_DROP_TEMP_TRIGGER : zCode="SQLITE_DROP_TEMP_TRIGGER"; break;
+ case SQLITE_DROP_TEMP_VIEW : zCode="SQLITE_DROP_TEMP_VIEW"; break;
+ case SQLITE_DROP_TRIGGER : zCode="SQLITE_DROP_TRIGGER"; break;
+ case SQLITE_DROP_VIEW : zCode="SQLITE_DROP_VIEW"; break;
+ case SQLITE_INSERT : zCode="SQLITE_INSERT"; break;
+ case SQLITE_PRAGMA : zCode="SQLITE_PRAGMA"; break;
+ case SQLITE_READ : zCode="SQLITE_READ"; break;
+ case SQLITE_SELECT : zCode="SQLITE_SELECT"; break;
+ case SQLITE_TRANSACTION : zCode="SQLITE_TRANSACTION"; break;
+ case SQLITE_UPDATE : zCode="SQLITE_UPDATE"; break;
+ case SQLITE_ATTACH : zCode="SQLITE_ATTACH"; break;
+ case SQLITE_DETACH : zCode="SQLITE_DETACH"; break;
+ default : zCode="????"; break;
+ }
+ Tcl_DStringInit(&str);
+ Tcl_DStringAppend(&str, pDb->zAuth, -1);
+ Tcl_DStringAppendElement(&str, zCode);
+ Tcl_DStringAppendElement(&str, zArg1 ? zArg1 : "");
+ Tcl_DStringAppendElement(&str, zArg2 ? zArg2 : "");
+ Tcl_DStringAppendElement(&str, zArg3 ? zArg3 : "");
+ Tcl_DStringAppendElement(&str, zArg4 ? zArg4 : "");
+ rc = Tcl_GlobalEval(pDb->interp, Tcl_DStringValue(&str));
+ Tcl_DStringFree(&str);
+ zReply = Tcl_GetStringResult(pDb->interp);
+ if( strcmp(zReply,"SQLITE_OK")==0 ){
+ rc = SQLITE_OK;
+ }else if( strcmp(zReply,"SQLITE_DENY")==0 ){
+ rc = SQLITE_DENY;
+ }else if( strcmp(zReply,"SQLITE_IGNORE")==0 ){
+ rc = SQLITE_IGNORE;
+ }else{
+ rc = 999;
+ }
+ return rc;
+}
+#endif /* SQLITE_OMIT_AUTHORIZATION */
+
+/*
+** The "sqlite" command below creates a new Tcl command for each
+** connection it opens to an SQLite database. This routine is invoked
+** whenever one of those connection-specific commands is executed
+** in Tcl. For example, if you run Tcl code like this:
+**
+** sqlite db1 "my_database"
+** db1 close
+**
+** The first command opens a connection to the "my_database" database
+** and calls that connection "db1". The second command causes this
+** subroutine to be invoked.
+*/
+static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ int choice;
+ int rc = TCL_OK;
+ static const char *DB_strs[] = {
+ "authorizer", "busy", "changes",
+ "close", "commit_hook", "complete",
+ "errorcode", "eval", "function",
+ "last_insert_rowid", "last_statement_changes", "onecolumn",
+ "progress", "rekey", "timeout",
+ "trace",
+ 0
+ };
+ enum DB_enum {
+ DB_AUTHORIZER, DB_BUSY, DB_CHANGES,
+ DB_CLOSE, DB_COMMIT_HOOK, DB_COMPLETE,
+ DB_ERRORCODE, DB_EVAL, DB_FUNCTION,
+ DB_LAST_INSERT_ROWID, DB_LAST_STATEMENT_CHANGES, DB_ONECOLUMN,
+ DB_PROGRESS, DB_REKEY, DB_TIMEOUT,
+ DB_TRACE
+ };
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
+ return TCL_ERROR;
+ }
+ if( Tcl_GetIndexFromObj(interp, objv[1], DB_strs, "option", 0, &choice) ){
+ return TCL_ERROR;
+ }
+
+ switch( (enum DB_enum)choice ){
+
+ /* $db authorizer ?CALLBACK?
+ **
+ ** Invoke the given callback to authorize each SQL operation as it is
+ ** compiled. 5 arguments are appended to the callback before it is
+ ** invoked:
+ **
+ ** (1) The authorization type (ex: SQLITE_CREATE_TABLE, SQLITE_INSERT, ...)
+ ** (2) First descriptive name (depends on authorization type)
+ ** (3) Second descriptive name
+ ** (4) Name of the database (ex: "main", "temp")
+ ** (5) Name of trigger that is doing the access
+ **
+ ** The callback should return on of the following strings: SQLITE_OK,
+ ** SQLITE_IGNORE, or SQLITE_DENY. Any other return value is an error.
+ **
+ ** If this method is invoked with no arguments, the current authorization
+ ** callback string is returned.
+ */
+ case DB_AUTHORIZER: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+ }else if( objc==2 ){
+ if( pDb->zAuth ){
+ Tcl_AppendResult(interp, pDb->zAuth, 0);
+ }
+ }else{
+ char *zAuth;
+ int len;
+ if( pDb->zAuth ){
+ Tcl_Free(pDb->zAuth);
+ }
+ zAuth = Tcl_GetStringFromObj(objv[2], &len);
+ if( zAuth && len>0 ){
+ pDb->zAuth = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zAuth, zAuth);
+ }else{
+ pDb->zAuth = 0;
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( pDb->zAuth ){
+ pDb->interp = interp;
+ sqlite_set_authorizer(pDb->db, auth_callback, pDb);
+ }else{
+ sqlite_set_authorizer(pDb->db, 0, 0);
+ }
+#endif
+ }
+ break;
+ }
+
+ /* $db busy ?CALLBACK?
+ **
+ ** Invoke the given callback if an SQL statement attempts to open
+ ** a locked database file.
+ */
+ case DB_BUSY: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "CALLBACK");
+ return TCL_ERROR;
+ }else if( objc==2 ){
+ if( pDb->zBusy ){
+ Tcl_AppendResult(interp, pDb->zBusy, 0);
+ }
+ }else{
+ char *zBusy;
+ int len;
+ if( pDb->zBusy ){
+ Tcl_Free(pDb->zBusy);
+ }
+ zBusy = Tcl_GetStringFromObj(objv[2], &len);
+ if( zBusy && len>0 ){
+ pDb->zBusy = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zBusy, zBusy);
+ }else{
+ pDb->zBusy = 0;
+ }
+ if( pDb->zBusy ){
+ pDb->interp = interp;
+ sqlite_busy_handler(pDb->db, DbBusyHandler, pDb);
+ }else{
+ sqlite_busy_handler(pDb->db, 0, 0);
+ }
+ }
+ break;
+ }
+
+ /* $db progress ?N CALLBACK?
+ **
+ ** Invoke the given callback every N virtual machine opcodes while executing
+ ** queries.
+ */
+ case DB_PROGRESS: {
+ if( objc==2 ){
+ if( pDb->zProgress ){
+ Tcl_AppendResult(interp, pDb->zProgress, 0);
+ }
+ }else if( objc==4 ){
+ char *zProgress;
+ int len;
+ int N;
+ if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &N) ){
+ return TCL_ERROR;
+ };
+ if( pDb->zProgress ){
+ Tcl_Free(pDb->zProgress);
+ }
+ zProgress = Tcl_GetStringFromObj(objv[3], &len);
+ if( zProgress && len>0 ){
+ pDb->zProgress = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zProgress, zProgress);
+ }else{
+ pDb->zProgress = 0;
+ }
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ if( pDb->zProgress ){
+ pDb->interp = interp;
+ sqlite_progress_handler(pDb->db, N, DbProgressHandler, pDb);
+ }else{
+ sqlite_progress_handler(pDb->db, 0, 0, 0);
+ }
+#endif
+ }else{
+ Tcl_WrongNumArgs(interp, 2, objv, "N CALLBACK");
+ return TCL_ERROR;
+ }
+ break;
+ }
+
+ /*
+ ** $db changes
+ **
+ ** Return the number of rows that were modified, inserted, or deleted by
+ ** the most recent "eval".
+ */
+ case DB_CHANGES: {
+ Tcl_Obj *pResult;
+ int nChange;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ nChange = sqlite_changes(pDb->db);
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetIntObj(pResult, nChange);
+ break;
+ }
+
+ /*
+ ** $db last_statement_changes
+ **
+ ** Return the number of rows that were modified, inserted, or deleted by
+ ** the last statment to complete execution (excluding changes due to
+ ** triggers)
+ */
+ case DB_LAST_STATEMENT_CHANGES: {
+ Tcl_Obj *pResult;
+ int lsChange;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ lsChange = sqlite_last_statement_changes(pDb->db);
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetIntObj(pResult, lsChange);
+ break;
+ }
+
+ /* $db close
+ **
+ ** Shutdown the database
+ */
+ case DB_CLOSE: {
+ Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0));
+ break;
+ }
+
+ /* $db commit_hook ?CALLBACK?
+ **
+ ** Invoke the given callback just before committing every SQL transaction.
+ ** If the callback throws an exception or returns non-zero, then the
+ ** transaction is aborted. If CALLBACK is an empty string, the callback
+ ** is disabled.
+ */
+ case DB_COMMIT_HOOK: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+ }else if( objc==2 ){
+ if( pDb->zCommit ){
+ Tcl_AppendResult(interp, pDb->zCommit, 0);
+ }
+ }else{
+ char *zCommit;
+ int len;
+ if( pDb->zCommit ){
+ Tcl_Free(pDb->zCommit);
+ }
+ zCommit = Tcl_GetStringFromObj(objv[2], &len);
+ if( zCommit && len>0 ){
+ pDb->zCommit = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zCommit, zCommit);
+ }else{
+ pDb->zCommit = 0;
+ }
+ if( pDb->zCommit ){
+ pDb->interp = interp;
+ sqlite_commit_hook(pDb->db, DbCommitHandler, pDb);
+ }else{
+ sqlite_commit_hook(pDb->db, 0, 0);
+ }
+ }
+ break;
+ }
+
+ /* $db complete SQL
+ **
+ ** Return TRUE if SQL is a complete SQL statement. Return FALSE if
+ ** additional lines of input are needed. This is similar to the
+ ** built-in "info complete" command of Tcl.
+ */
+ case DB_COMPLETE: {
+ Tcl_Obj *pResult;
+ int isComplete;
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "SQL");
+ return TCL_ERROR;
+ }
+ isComplete = sqlite_complete( Tcl_GetStringFromObj(objv[2], 0) );
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetBooleanObj(pResult, isComplete);
+ break;
+ }
+
+ /*
+ ** $db errorcode
+ **
+ ** Return the numeric error code that was returned by the most recent
+ ** call to sqlite_exec().
+ */
+ case DB_ERRORCODE: {
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(pDb->rc));
+ break;
+ }
+
+ /*
+ ** $db eval $sql ?array { ...code... }?
+ **
+ ** The SQL statement in $sql is evaluated. For each row, the values are
+ ** placed in elements of the array named "array" and ...code... is executed.
+ ** If "array" and "code" are omitted, then no callback is every invoked.
+ ** If "array" is an empty string, then the values are placed in variables
+ ** that have the same name as the fields extracted by the query.
+ */
+ case DB_EVAL: {
+ CallbackData cbData;
+ char *zErrMsg;
+ char *zSql;
+#ifdef UTF_TRANSLATION_NEEDED
+ Tcl_DString dSql;
+ int i;
+#endif
+
+ if( objc!=5 && objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME CODE?");
+ return TCL_ERROR;
+ }
+ pDb->interp = interp;
+ zSql = Tcl_GetStringFromObj(objv[2], 0);
+#ifdef UTF_TRANSLATION_NEEDED
+ Tcl_DStringInit(&dSql);
+ Tcl_UtfToExternalDString(NULL, zSql, -1, &dSql);
+ zSql = Tcl_DStringValue(&dSql);
+#endif
+ Tcl_IncrRefCount(objv[2]);
+ if( objc==5 ){
+ cbData.interp = interp;
+ cbData.once = 1;
+ cbData.zArray = Tcl_GetStringFromObj(objv[3], 0);
+ cbData.pCode = objv[4];
+ cbData.tcl_rc = TCL_OK;
+ cbData.nColName = 0;
+ cbData.azColName = 0;
+ zErrMsg = 0;
+ Tcl_IncrRefCount(objv[3]);
+ Tcl_IncrRefCount(objv[4]);
+ rc = sqlite_exec(pDb->db, zSql, DbEvalCallback, &cbData, &zErrMsg);
+ Tcl_DecrRefCount(objv[4]);
+ Tcl_DecrRefCount(objv[3]);
+ if( cbData.tcl_rc==TCL_BREAK ){ cbData.tcl_rc = TCL_OK; }
+ }else{
+ Tcl_Obj *pList = Tcl_NewObj();
+ cbData.tcl_rc = TCL_OK;
+ rc = sqlite_exec(pDb->db, zSql, DbEvalCallback2, pList, &zErrMsg);
+ Tcl_SetObjResult(interp, pList);
+ }
+ pDb->rc = rc;
+ if( rc==SQLITE_ABORT ){
+ if( zErrMsg ) free(zErrMsg);
+ rc = cbData.tcl_rc;
+ }else if( zErrMsg ){
+ Tcl_SetResult(interp, zErrMsg, TCL_VOLATILE);
+ free(zErrMsg);
+ rc = TCL_ERROR;
+ }else if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, sqlite_error_string(rc), 0);
+ rc = TCL_ERROR;
+ }else{
+ }
+ Tcl_DecrRefCount(objv[2]);
+#ifdef UTF_TRANSLATION_NEEDED
+ Tcl_DStringFree(&dSql);
+ if( objc==5 && cbData.azColName ){
+ for(i=0; i<cbData.nColName; i++){
+ if( cbData.azColName[i] ) free(cbData.azColName[i]);
+ }
+ free(cbData.azColName);
+ cbData.azColName = 0;
+ }
+#endif
+ return rc;
+ }
+
+ /*
+ ** $db function NAME SCRIPT
+ **
+ ** Create a new SQL function called NAME. Whenever that function is
+ ** called, invoke SCRIPT to evaluate the function.
+ */
+ case DB_FUNCTION: {
+ SqlFunc *pFunc;
+ char *zName;
+ char *zScript;
+ int nScript;
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "NAME SCRIPT");
+ return TCL_ERROR;
+ }
+ zName = Tcl_GetStringFromObj(objv[2], 0);
+ zScript = Tcl_GetStringFromObj(objv[3], &nScript);
+ pFunc = (SqlFunc*)Tcl_Alloc( sizeof(*pFunc) + nScript + 1 );
+ if( pFunc==0 ) return TCL_ERROR;
+ pFunc->interp = interp;
+ pFunc->pNext = pDb->pFunc;
+ pFunc->zScript = (char*)&pFunc[1];
+ strcpy(pFunc->zScript, zScript);
+ sqlite_create_function(pDb->db, zName, -1, tclSqlFunc, pFunc);
+ sqlite_function_type(pDb->db, zName, SQLITE_NUMERIC);
+ break;
+ }
+
+ /*
+ ** $db last_insert_rowid
+ **
+ ** Return an integer which is the ROWID for the most recent insert.
+ */
+ case DB_LAST_INSERT_ROWID: {
+ Tcl_Obj *pResult;
+ int rowid;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ rowid = sqlite_last_insert_rowid(pDb->db);
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetIntObj(pResult, rowid);
+ break;
+ }
+
+ /*
+ ** $db onecolumn SQL
+ **
+ ** Return a single column from a single row of the given SQL query.
+ */
+ case DB_ONECOLUMN: {
+ char *zSql;
+ char *zErrMsg = 0;
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "SQL");
+ return TCL_ERROR;
+ }
+ zSql = Tcl_GetStringFromObj(objv[2], 0);
+ rc = sqlite_exec(pDb->db, zSql, DbEvalCallback3, interp, &zErrMsg);
+ if( rc==SQLITE_ABORT ){
+ rc = SQLITE_OK;
+ }else if( zErrMsg ){
+ Tcl_SetResult(interp, zErrMsg, TCL_VOLATILE);
+ free(zErrMsg);
+ rc = TCL_ERROR;
+ }else if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, sqlite_error_string(rc), 0);
+ rc = TCL_ERROR;
+ }
+ break;
+ }
+
+ /*
+ ** $db rekey KEY
+ **
+ ** Change the encryption key on the currently open database.
+ */
+ case DB_REKEY: {
+ int nKey;
+ void *pKey;
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "KEY");
+ return TCL_ERROR;
+ }
+ pKey = Tcl_GetByteArrayFromObj(objv[2], &nKey);
+#ifdef SQLITE_HAS_CODEC
+ rc = sqlite_rekey(pDb->db, pKey, nKey);
+ if( rc ){
+ Tcl_AppendResult(interp, sqlite_error_string(rc), 0);
+ rc = TCL_ERROR;
+ }
+#endif
+ break;
+ }
+
+ /*
+ ** $db timeout MILLESECONDS
+ **
+ ** Delay for the number of milliseconds specified when a file is locked.
+ */
+ case DB_TIMEOUT: {
+ int ms;
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "MILLISECONDS");
+ return TCL_ERROR;
+ }
+ if( Tcl_GetIntFromObj(interp, objv[2], &ms) ) return TCL_ERROR;
+ sqlite_busy_timeout(pDb->db, ms);
+ break;
+ }
+
+ /* $db trace ?CALLBACK?
+ **
+ ** Make arrangements to invoke the CALLBACK routine for each SQL statement
+ ** that is executed. The text of the SQL is appended to CALLBACK before
+ ** it is executed.
+ */
+ case DB_TRACE: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+ }else if( objc==2 ){
+ if( pDb->zTrace ){
+ Tcl_AppendResult(interp, pDb->zTrace, 0);
+ }
+ }else{
+ char *zTrace;
+ int len;
+ if( pDb->zTrace ){
+ Tcl_Free(pDb->zTrace);
+ }
+ zTrace = Tcl_GetStringFromObj(objv[2], &len);
+ if( zTrace && len>0 ){
+ pDb->zTrace = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zTrace, zTrace);
+ }else{
+ pDb->zTrace = 0;
+ }
+ if( pDb->zTrace ){
+ pDb->interp = interp;
+ sqlite_trace(pDb->db, DbTraceHandler, pDb);
+ }else{
+ sqlite_trace(pDb->db, 0, 0);
+ }
+ }
+ break;
+ }
+
+ } /* End of the SWITCH statement */
+ return rc;
+}
+
+/*
+** sqlite DBNAME FILENAME ?MODE? ?-key KEY?
+**
+** This is the main Tcl command. When the "sqlite" Tcl command is
+** invoked, this routine runs to process that command.
+**
+** The first argument, DBNAME, is an arbitrary name for a new
+** database connection. This command creates a new command named
+** DBNAME that is used to control that connection. The database
+** connection is deleted when the DBNAME command is deleted.
+**
+** The second argument is the name of the directory that contains
+** the sqlite database that is to be accessed.
+**
+** For testing purposes, we also support the following:
+**
+** sqlite -encoding
+**
+** Return the encoding used by LIKE and GLOB operators. Choices
+** are UTF-8 and iso8859.
+**
+** sqlite -version
+**
+** Return the version number of the SQLite library.
+**
+** sqlite -tcl-uses-utf
+**
+** Return "1" if compiled with a Tcl uses UTF-8. Return "0" if
+** not. Used by tests to make sure the library was compiled
+** correctly.
+*/
+static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
+ int mode;
+ SqliteDb *p;
+ void *pKey = 0;
+ int nKey = 0;
+ const char *zArg;
+ char *zErrMsg;
+ const char *zFile;
+ char zBuf[80];
+ if( objc==2 ){
+ zArg = Tcl_GetStringFromObj(objv[1], 0);
+ if( strcmp(zArg,"-encoding")==0 ){
+ Tcl_AppendResult(interp,sqlite_encoding,0);
+ return TCL_OK;
+ }
+ if( strcmp(zArg,"-version")==0 ){
+ Tcl_AppendResult(interp,sqlite_version,0);
+ return TCL_OK;
+ }
+ if( strcmp(zArg,"-has-codec")==0 ){
+#ifdef SQLITE_HAS_CODEC
+ Tcl_AppendResult(interp,"1",0);
+#else
+ Tcl_AppendResult(interp,"0",0);
+#endif
+ return TCL_OK;
+ }
+ if( strcmp(zArg,"-tcl-uses-utf")==0 ){
+#ifdef TCL_UTF_MAX
+ Tcl_AppendResult(interp,"1",0);
+#else
+ Tcl_AppendResult(interp,"0",0);
+#endif
+ return TCL_OK;
+ }
+ }
+ if( objc==5 || objc==6 ){
+ zArg = Tcl_GetStringFromObj(objv[objc-2], 0);
+ if( strcmp(zArg,"-key")==0 ){
+ pKey = Tcl_GetByteArrayFromObj(objv[objc-1], &nKey);
+ objc -= 2;
+ }
+ }
+ if( objc!=3 && objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv,
+#ifdef SQLITE_HAS_CODEC
+ "HANDLE FILENAME ?-key CODEC-KEY?"
+#else
+ "HANDLE FILENAME ?MODE?"
+#endif
+ );
+ return TCL_ERROR;
+ }
+ if( objc==3 ){
+ mode = 0666;
+ }else if( Tcl_GetIntFromObj(interp, objv[3], &mode)!=TCL_OK ){
+ return TCL_ERROR;
+ }
+ zErrMsg = 0;
+ p = (SqliteDb*)Tcl_Alloc( sizeof(*p) );
+ if( p==0 ){
+ Tcl_SetResult(interp, "malloc failed", TCL_STATIC);
+ return TCL_ERROR;
+ }
+ memset(p, 0, sizeof(*p));
+ zFile = Tcl_GetStringFromObj(objv[2], 0);
+#ifdef SQLITE_HAS_CODEC
+ p->db = sqlite_open_encrypted(zFile, pKey, nKey, 0, &zErrMsg);
+#else
+ p->db = sqlite_open(zFile, mode, &zErrMsg);
+#endif
+ if( p->db==0 ){
+ Tcl_SetResult(interp, zErrMsg, TCL_VOLATILE);
+ Tcl_Free((char*)p);
+ free(zErrMsg);
+ return TCL_ERROR;
+ }
+ zArg = Tcl_GetStringFromObj(objv[1], 0);
+ Tcl_CreateObjCommand(interp, zArg, DbObjCmd, (char*)p, DbDeleteCmd);
+
+ /* The return value is the value of the sqlite* pointer
+ */
+ sprintf(zBuf, "%p", p->db);
+ if( strncmp(zBuf,"0x",2) ){
+ sprintf(zBuf, "0x%p", p->db);
+ }
+ Tcl_AppendResult(interp, zBuf, 0);
+
+ /* If compiled with SQLITE_TEST turned on, then register the "md5sum"
+ ** SQL function.
+ */
+#ifdef SQLITE_TEST
+ {
+ extern void Md5_Register(sqlite*);
+ Md5_Register(p->db);
+ }
+#endif
+ return TCL_OK;
+}
+
+/*
+** Provide a dummy Tcl_InitStubs if we are using this as a static
+** library.
+*/
+#ifndef USE_TCL_STUBS
+# undef Tcl_InitStubs
+# define Tcl_InitStubs(a,b,c)
+#endif
+
+/*
+** Initialize this module.
+**
+** This Tcl module contains only a single new Tcl command named "sqlite".
+** (Hence there is no namespace. There is no point in using a namespace
+** if the extension only supplies one new name!) The "sqlite" command is
+** used to open a new SQLite database. See the DbMain() routine above
+** for additional information.
+*/
+int Sqlite_Init(Tcl_Interp *interp){
+ Tcl_InitStubs(interp, "8.0", 0);
+ Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0);
+ Tcl_PkgProvide(interp, "sqlite", "2.0");
+ return TCL_OK;
+}
+int Tclsqlite_Init(Tcl_Interp *interp){
+ Tcl_InitStubs(interp, "8.0", 0);
+ Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0);
+ Tcl_PkgProvide(interp, "sqlite", "2.0");
+ return TCL_OK;
+}
+int Sqlite_SafeInit(Tcl_Interp *interp){
+ return TCL_OK;
+}
+int Tclsqlite_SafeInit(Tcl_Interp *interp){
+ return TCL_OK;
+}
+
+#if 0
+/*
+** If compiled using mktclapp, this routine runs to initialize
+** everything.
+*/
+int Et_AppInit(Tcl_Interp *interp){
+ return Sqlite_Init(interp);
+}
+#endif
+/***************************************************************************
+** The remaining code is only included if the TCLSH macro is defined to
+** be an integer greater than 0
+*/
+#if defined(TCLSH) && TCLSH>0
+
+/*
+** If the macro TCLSH is defined and is one, then put in code for the
+** "main" routine that implement a interactive shell into which the user
+** can type TCL commands.
+*/
+#if TCLSH==1
+static char zMainloop[] =
+ "set line {}\n"
+ "while {![eof stdin]} {\n"
+ "if {$line!=\"\"} {\n"
+ "puts -nonewline \"> \"\n"
+ "} else {\n"
+ "puts -nonewline \"% \"\n"
+ "}\n"
+ "flush stdout\n"
+ "append line [gets stdin]\n"
+ "if {[info complete $line]} {\n"
+ "if {[catch {uplevel #0 $line} result]} {\n"
+ "puts stderr \"Error: $result\"\n"
+ "} elseif {$result!=\"\"} {\n"
+ "puts $result\n"
+ "}\n"
+ "set line {}\n"
+ "} else {\n"
+ "append line \\n\n"
+ "}\n"
+ "}\n"
+;
+#endif /* TCLSH==1 */
+
+int Libsqlite_Init( Tcl_Interp *interp) {
+#ifdef TCL_THREADS
+ if (Thread_Init(interp) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+#endif
+ Sqlite_Init(interp);
+#ifdef SQLITE_TEST
+ {
+ extern int Sqlitetest1_Init(Tcl_Interp*);
+ extern int Sqlitetest2_Init(Tcl_Interp*);
+ extern int Sqlitetest3_Init(Tcl_Interp*);
+ extern int Md5_Init(Tcl_Interp*);
+ Sqlitetest1_Init(interp);
+ Sqlitetest2_Init(interp);
+ Sqlitetest3_Init(interp);
+ Md5_Init(interp);
+ Tcl_StaticPackage(interp, "sqlite", Libsqlite_Init, Libsqlite_Init);
+ }
+#endif
+ return TCL_OK;
+}
+
+#define TCLSH_MAIN main /* Needed to fake out mktclapp */
+#if TCLSH==1
+int TCLSH_MAIN(int argc, char **argv){
+#ifndef TCL_THREADS
+ Tcl_Interp *interp;
+ Tcl_FindExecutable(argv[0]);
+ interp = Tcl_CreateInterp();
+ Libsqlite_Init(interp);
+ if( argc>=2 ){
+ int i;
+ Tcl_SetVar(interp,"argv0",argv[1],TCL_GLOBAL_ONLY);
+ Tcl_SetVar(interp,"argv", "", TCL_GLOBAL_ONLY);
+ for(i=2; i<argc; i++){
+ Tcl_SetVar(interp, "argv", argv[i],
+ TCL_GLOBAL_ONLY | TCL_LIST_ELEMENT | TCL_APPEND_VALUE);
+ }
+ if( Tcl_EvalFile(interp, argv[1])!=TCL_OK ){
+ const char *zInfo = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
+ if( zInfo==0 ) zInfo = interp->result;
+ fprintf(stderr,"%s: %s\n", *argv, zInfo);
+ return TCL_ERROR;
+ }
+ }else{
+ Tcl_GlobalEval(interp, zMainloop);
+ }
+ return 0;
+#else
+ Tcl_Main(argc, argv, Libsqlite_Init);
+#endif /* TCL_THREADS */
+ return 0;
+}
+#endif /* TCLSH==1 */
+
+
+/*
+** If the macro TCLSH is set to 2, then implement a space analysis tool.
+*/
+#if TCLSH==2
+static char zAnalysis[] =
+#include "spaceanal_tcl.h"
+;
+
+int main(int argc, char **argv){
+ Tcl_Interp *interp;
+ int i;
+ Tcl_FindExecutable(argv[0]);
+ interp = Tcl_CreateInterp();
+ Libsqlite_Init(interp);
+ Tcl_SetVar(interp,"argv0",argv[0],TCL_GLOBAL_ONLY);
+ Tcl_SetVar(interp,"argv", "", TCL_GLOBAL_ONLY);
+ for(i=1; i<argc; i++){
+ Tcl_SetVar(interp, "argv", argv[i],
+ TCL_GLOBAL_ONLY | TCL_LIST_ELEMENT | TCL_APPEND_VALUE);
+ }
+ if( Tcl_GlobalEval(interp, zAnalysis)!=TCL_OK ){
+ const char *zInfo = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
+ if( zInfo==0 ) zInfo = interp->result;
+ fprintf(stderr,"%s: %s\n", *argv, zInfo);
+ return TCL_ERROR;
+ }
+ return 0;
+}
+#endif /* TCLSH==2 */
+
+#endif /* TCLSH */
+
+#endif /* NO_TCL */
diff --git a/kexi/3rdparty/kexisql/src/threadtest.c b/kexi/3rdparty/kexisql/src/threadtest.c
new file mode 100644
index 000000000..89c79a4c7
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/threadtest.c
@@ -0,0 +1,279 @@
+/*
+** 2002 January 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 implements a simple standalone program used to test whether
+** or not the SQLite library is threadsafe.
+**
+** Testing the thread safety of SQLite is difficult because there are very
+** few places in the code that are even potentially unsafe, and those
+** places execute for very short periods of time. So even if the library
+** is compiled with its mutexes disabled, it is likely to work correctly
+** in a multi-threaded program most of the time.
+*/
+#include "sqlite.h"
+#include <pthread.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+** Enable for tracing
+*/
+static int verbose = 0;
+
+/*
+** Come here to die.
+*/
+static void Exit(int rc){
+ exit(rc);
+}
+
+extern char *sqlite_mprintf(const char *zFormat, ...);
+extern char *sqlite_vmprintf(const char *zFormat, va_list);
+
+/*
+** When a lock occurs, yield.
+*/
+static int db_is_locked(void *NotUsed, const char *zNotUsed, int iNotUsed){
+ /* sched_yield(); */
+ if( verbose ) printf("BUSY %s\n", (char*)NotUsed);
+ usleep(100);
+ return 1;
+}
+
+/*
+** Used to accumulate query results by db_query()
+*/
+struct QueryResult {
+ const char *zFile; /* Filename - used for error reporting */
+ int nElem; /* Number of used entries in azElem[] */
+ int nAlloc; /* Number of slots allocated for azElem[] */
+ char **azElem; /* The result of the query */
+};
+
+/*
+** The callback function for db_query
+*/
+static int db_query_callback(
+ void *pUser, /* Pointer to the QueryResult structure */
+ int nArg, /* Number of columns in this result row */
+ char **azArg, /* Text of data in all columns */
+ char **NotUsed /* Names of the columns */
+){
+ struct QueryResult *pResult = (struct QueryResult*)pUser;
+ int i;
+ if( pResult->nElem + nArg >= pResult->nAlloc ){
+ if( pResult->nAlloc==0 ){
+ pResult->nAlloc = nArg+1;
+ }else{
+ pResult->nAlloc = pResult->nAlloc*2 + nArg + 1;
+ }
+ pResult->azElem = realloc( pResult->azElem, pResult->nAlloc*sizeof(char*));
+ if( pResult->azElem==0 ){
+ fprintf(stdout,"%s: malloc failed\n", pResult->zFile);
+ return 1;
+ }
+ }
+ if( azArg==0 ) return 0;
+ for(i=0; i<nArg; i++){
+ pResult->azElem[pResult->nElem++] =
+ sqlite_mprintf("%s",azArg[i] ? azArg[i] : "");
+ }
+ return 0;
+}
+
+/*
+** Execute a query against the database. NULL values are returned
+** as an empty string. The list is terminated by a single NULL pointer.
+*/
+char **db_query(sqlite *db, const char *zFile, const char *zFormat, ...){
+ char *zSql;
+ int rc;
+ char *zErrMsg = 0;
+ va_list ap;
+ struct QueryResult sResult;
+ va_start(ap, zFormat);
+ zSql = sqlite_vmprintf(zFormat, ap);
+ va_end(ap);
+ memset(&sResult, 0, sizeof(sResult));
+ sResult.zFile = zFile;
+ if( verbose ) printf("QUERY %s: %s\n", zFile, zSql);
+ rc = sqlite_exec(db, zSql, db_query_callback, &sResult, &zErrMsg);
+ if( rc==SQLITE_SCHEMA ){
+ if( zErrMsg ) free(zErrMsg);
+ rc = sqlite_exec(db, zSql, db_query_callback, &sResult, &zErrMsg);
+ }
+ if( verbose ) printf("DONE %s %s\n", zFile, zSql);
+ if( zErrMsg ){
+ fprintf(stdout,"%s: query failed: %s - %s\n", zFile, zSql, zErrMsg);
+ free(zErrMsg);
+ free(zSql);
+ Exit(1);
+ }
+ sqlite_freemem(zSql);
+ if( sResult.azElem==0 ){
+ db_query_callback(&sResult, 0, 0, 0);
+ }
+ sResult.azElem[sResult.nElem] = 0;
+ return sResult.azElem;
+}
+
+/*
+** Execute an SQL statement.
+*/
+void db_execute(sqlite *db, const char *zFile, const char *zFormat, ...){
+ char *zSql;
+ int rc;
+ char *zErrMsg = 0;
+ va_list ap;
+ va_start(ap, zFormat);
+ zSql = sqlite_vmprintf(zFormat, ap);
+ va_end(ap);
+ if( verbose ) printf("EXEC %s: %s\n", zFile, zSql);
+ rc = sqlite_exec(db, zSql, 0, 0, &zErrMsg);
+ if( rc==SQLITE_SCHEMA ){
+ if( zErrMsg ) free(zErrMsg);
+ rc = sqlite_exec(db, zSql, 0, 0, &zErrMsg);
+ }
+ if( verbose ) printf("DONE %s: %s\n", zFile, zSql);
+ if( zErrMsg ){
+ fprintf(stdout,"%s: command failed: %s - %s\n", zFile, zSql, zErrMsg);
+ free(zErrMsg);
+ sqlite_freemem(zSql);
+ Exit(1);
+ }
+ sqlite_freemem(zSql);
+}
+
+/*
+** Free the results of a db_query() call.
+*/
+void db_query_free(char **az){
+ int i;
+ for(i=0; az[i]; i++){
+ sqlite_freemem(az[i]);
+ }
+ free(az);
+}
+
+/*
+** Check results
+*/
+void db_check(const char *zFile, const char *zMsg, char **az, ...){
+ va_list ap;
+ int i;
+ char *z;
+ va_start(ap, az);
+ for(i=0; (z = va_arg(ap, char*))!=0; i++){
+ if( az[i]==0 || strcmp(az[i],z)!=0 ){
+ fprintf(stdout,"%s: %s: bad result in column %d: %s\n",
+ zFile, zMsg, i+1, az[i]);
+ db_query_free(az);
+ Exit(1);
+ }
+ }
+ va_end(ap);
+ db_query_free(az);
+}
+
+pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t sig = PTHREAD_COND_INITIALIZER;
+int thread_cnt = 0;
+
+static void *worker_bee(void *pArg){
+ const char *zFilename = (char*)pArg;
+ char *azErr;
+ int i, cnt;
+ int t = atoi(zFilename);
+ char **az;
+ sqlite *db;
+
+ pthread_mutex_lock(&lock);
+ thread_cnt++;
+ pthread_mutex_unlock(&lock);
+ printf("%s: START\n", zFilename);
+ fflush(stdout);
+ for(cnt=0; cnt<10; cnt++){
+ db = sqlite_open(&zFilename[2], 0, &azErr);
+ if( db==0 ){
+ fprintf(stdout,"%s: can't open\n", zFilename);
+ Exit(1);
+ }
+ sqlite_busy_handler(db, db_is_locked, zFilename);
+ db_execute(db, zFilename, "CREATE TABLE t%d(a,b,c);", t);
+ for(i=1; i<=100; i++){
+ db_execute(db, zFilename, "INSERT INTO t%d VALUES(%d,%d,%d);",
+ t, i, i*2, i*i);
+ }
+ az = db_query(db, zFilename, "SELECT count(*) FROM t%d", t);
+ db_check(zFilename, "tX size", az, "100", 0);
+ az = db_query(db, zFilename, "SELECT avg(b) FROM t%d", t);
+ db_check(zFilename, "tX avg", az, "101", 0);
+ db_execute(db, zFilename, "DELETE FROM t%d WHERE a>50", t);
+ az = db_query(db, zFilename, "SELECT avg(b) FROM t%d", t);
+ db_check(zFilename, "tX avg2", az, "51", 0);
+ for(i=1; i<=50; i++){
+ char z1[30], z2[30];
+ az = db_query(db, zFilename, "SELECT b, c FROM t%d WHERE a=%d", t, i);
+ sprintf(z1, "%d", i*2);
+ sprintf(z2, "%d", i*i);
+ db_check(zFilename, "readback", az, z1, z2, 0);
+ }
+ db_execute(db, zFilename, "DROP TABLE t%d;", t);
+ sqlite_close(db);
+ }
+ printf("%s: END\n", zFilename);
+ /* unlink(zFilename); */
+ fflush(stdout);
+ pthread_mutex_lock(&lock);
+ thread_cnt--;
+ if( thread_cnt<=0 ){
+ pthread_cond_signal(&sig);
+ }
+ pthread_mutex_unlock(&lock);
+ return 0;
+}
+
+int main(int argc, char **argv){
+ char *zFile;
+ int i, n;
+ pthread_t id;
+ if( argc>2 && strcmp(argv[1], "-v")==0 ){
+ verbose = 1;
+ argc--;
+ argv++;
+ }
+ if( argc<2 || (n=atoi(argv[1]))<1 ) n = 10;
+ for(i=0; i<n; i++){
+ char zBuf[200];
+ sprintf(zBuf, "testdb-%d", (i+1)/2);
+ unlink(zBuf);
+ }
+ for(i=0; i<n; i++){
+ zFile = sqlite_mprintf("%d.testdb-%d", i%2+1, (i+2)/2);
+ unlink(zFile);
+ pthread_create(&id, 0, worker_bee, (void*)zFile);
+ pthread_detach(id);
+ }
+ pthread_mutex_lock(&lock);
+ while( thread_cnt>0 ){
+ pthread_cond_wait(&sig, &lock);
+ }
+ pthread_mutex_unlock(&lock);
+ for(i=0; i<n; i++){
+ char zBuf[200];
+ sprintf(zBuf, "testdb-%d", (i+1)/2);
+ unlink(zBuf);
+ }
+ return 0;
+}
diff --git a/kexi/3rdparty/kexisql/src/tokenize.c b/kexi/3rdparty/kexisql/src/tokenize.c
new file mode 100644
index 000000000..68cb9f992
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/trigger.c b/kexi/3rdparty/kexisql/src/trigger.c
new file mode 100644
index 000000000..8442bb5dd
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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/kexi/3rdparty/kexisql/src/update.c b/kexi/3rdparty/kexisql/src/update.c
new file mode 100644
index 000000000..1dc32826f
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/util.c b/kexi/3rdparty/kexisql/src/util.c
new file mode 100644
index 000000000..09a13c7b1
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/util.c
@@ -0,0 +1,1135 @@
+/*
+** 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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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, const char *zFirst, ...){
+ va_list ap;
+ int nByte;
+ const char *z;
+ char *zResult;
+
+ if( pz==0 ) return;
+ nByte = strlen(zFirst) + 1;
+ va_start(ap, zFirst);
+ while( (z = va_arg(ap, const char*))!=0 ){
+ nByte += strlen(z);
+ }
+ va_end(ap);
+ sqliteFree(*pz);
+ *pz = zResult = sqliteMallocRaw( nByte );
+ if( zResult==0 ){
+ return;
+ }
+ strcpy(zResult, zFirst);
+ zResult += strlen(zResult);
+ va_start(ap, zFirst);
+ 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){
+ register 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){
+ register 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){
+ register 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){
+ register 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/kexi/3rdparty/kexisql/src/vacuum.c b/kexi/3rdparty/kexisql/src/vacuum.c
new file mode 100644
index 000000000..be01ad957
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/vacuum.c
@@ -0,0 +1,327 @@
+/*
+** 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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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;
+}
+
+/*
+** This callback is used to transfer PRAGMA settings from one database
+** to the other. The value in argv[0] should be passed to a pragma
+** identified by ((vacuumStruct*)pArg)->zPragma.
+*/
+static int vacuumCallback3(void *pArg, int argc, char **argv, char **NotUsed){
+ vacuumStruct *p = (vacuumStruct*)pArg;
+ char zBuf[200];
+ assert( argc==1 );
+ if( argv==0 ) return 0;
+ assert( argv[0]!=0 );
+ assert( strlen(p->zPragma)<100 );
+ assert( strlen(argv[0])<30 );
+ sprintf(zBuf,"PRAGMA %s=%s;", p->zPragma, argv[0]);
+ p->rc = execsql(p->pzErrMsg, p->dbNew, zBuf);
+ return p->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 */
+
+ /* These are all of the pragmas that need to be transferred over
+ ** to the new database */
+ static const char *zPragma[] = {
+ "default_synchronous",
+ "default_cache_size",
+ /* "default_temp_store", */
+ };
+
+ 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;
+ for(i=0; rc==SQLITE_OK && i<sizeof(zPragma)/sizeof(zPragma[0]); i++){
+ char zBuf[200];
+ assert( strlen(zPragma[i])<100 );
+ sprintf(zBuf, "PRAGMA %s;", zPragma[i]);
+ sVac.zPragma = zPragma[i];
+ rc = sqlite_exec(db, zBuf, vacuumCallback3, &sVac, &zErrMsg);
+ }
+ 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 ){
+ 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/kexi/3rdparty/kexisql/src/vdbe.c b/kexi/3rdparty/kexisql/src/vdbe.c
new file mode 100644
index 000000000..fb52ef388
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/vdbe.c
@@ -0,0 +1,4917 @@
+/*
+** 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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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;
+ }
+ 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/kexi/3rdparty/kexisql/src/vdbe.h b/kexi/3rdparty/kexisql/src/vdbe.h
new file mode 100644
index 000000000..fcb96949e
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/src/vdbeInt.h b/kexi/3rdparty/kexisql/src/vdbeInt.h
new file mode 100644
index 000000000..79b6b51a5
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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/kexi/3rdparty/kexisql/src/vdbeaux.c b/kexi/3rdparty/kexisql/src/vdbeaux.c
new file mode 100644
index 000000000..c206bad4a
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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/kexi/3rdparty/kexisql/src/where.c b/kexi/3rdparty/kexisql/src/where.c
new file mode 100644
index 000000000..714972f87
--- /dev/null
+++ b/kexi/3rdparty/kexisql/src/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 410099 2005-05-06 17:52:07Z staniek $
+*/
+#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/kexi/3rdparty/kexisql/tool/Makefile.am b/kexi/3rdparty/kexisql/tool/Makefile.am
new file mode 100644
index 000000000..1e5bd027b
--- /dev/null
+++ b/kexi/3rdparty/kexisql/tool/Makefile.am
@@ -0,0 +1 @@
+#add something here ;)
diff --git a/kexi/3rdparty/kexisql/tool/diffdb.c b/kexi/3rdparty/kexisql/tool/diffdb.c
new file mode 100644
index 000000000..0537d3872
--- /dev/null
+++ b/kexi/3rdparty/kexisql/tool/diffdb.c
@@ -0,0 +1,44 @@
+/*
+** A utility for printing the differences between two SQLite database files.
+*/
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+
+#define PAGESIZE 1024
+static int db1 = -1;
+static int db2 = -1;
+
+int main(int argc, char **argv){
+ int iPg;
+ unsigned char a1[PAGESIZE], a2[PAGESIZE];
+ if( argc!=3 ){
+ fprintf(stderr,"Usage: %s FILENAME FILENAME\n", argv[0]);
+ exit(1);
+ }
+ db1 = open(argv[1], O_RDONLY);
+ if( db1<0 ){
+ fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]);
+ exit(1);
+ }
+ db2 = open(argv[2], O_RDONLY);
+ if( db2<0 ){
+ fprintf(stderr,"%s: can't open %s\n", argv[0], argv[2]);
+ exit(1);
+ }
+ iPg = 1;
+ while( read(db1, a1, PAGESIZE)==PAGESIZE && read(db2,a2,PAGESIZE)==PAGESIZE ){
+ if( memcmp(a1,a2,PAGESIZE) ){
+ printf("Page %d\n", iPg);
+ }
+ iPg++;
+ }
+ printf("%d pages checked\n", iPg-1);
+ close(db1);
+ close(db2);
+}
diff --git a/kexi/3rdparty/kexisql/tool/lemon.c b/kexi/3rdparty/kexisql/tool/lemon.c
new file mode 100644
index 000000000..b801e6b39
--- /dev/null
+++ b/kexi/3rdparty/kexisql/tool/lemon.c
@@ -0,0 +1,4117 @@
+/*
+** This file contains all sources (including headers) to the LEMON
+** LALR(1) parser generator. The sources have been combined into a
+** single file to make it easy to include LEMON in the source tree
+** and Makefile of another program.
+**
+** The author of this program disclaims copyright.
+*/
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+extern void qsort();
+extern double strtod();
+extern long strtol();
+extern void free();
+extern int access();
+extern int atoi();
+
+#ifndef __WIN32__
+# if defined(_WIN32) || defined(WIN32)
+# define __WIN32__
+# endif
+#endif
+
+/* #define PRIVATE static */
+#define PRIVATE
+
+#ifdef TEST
+#define MAXRHS 5 /* Set low to exercise exception code */
+#else
+#define MAXRHS 1000
+#endif
+
+char *msort();
+extern void *malloc();
+
+/******** From the file "action.h" *************************************/
+struct action *Action_new();
+struct action *Action_sort();
+void Action_add();
+
+/********* From the file "assert.h" ************************************/
+void myassert();
+#ifndef NDEBUG
+# define assert(X) if(!(X))myassert(__FILE__,__LINE__)
+#else
+# define assert(X)
+#endif
+
+/********** From the file "build.h" ************************************/
+void FindRulePrecedences();
+void FindFirstSets();
+void FindStates();
+void FindLinks();
+void FindFollowSets();
+void FindActions();
+
+/********* From the file "configlist.h" *********************************/
+void Configlist_init(/* void */);
+struct config *Configlist_add(/* struct rule *, int */);
+struct config *Configlist_addbasis(/* struct rule *, int */);
+void Configlist_closure(/* void */);
+void Configlist_sort(/* void */);
+void Configlist_sortbasis(/* void */);
+struct config *Configlist_return(/* void */);
+struct config *Configlist_basis(/* void */);
+void Configlist_eat(/* struct config * */);
+void Configlist_reset(/* void */);
+
+/********* From the file "error.h" ***************************************/
+void ErrorMsg(const char *, int,const char *, ...);
+
+/****** From the file "option.h" ******************************************/
+struct s_options {
+ enum { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR,
+ OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR} type;
+ char *label;
+ char *arg;
+ char *message;
+};
+int OptInit(/* char**,struct s_options*,FILE* */);
+int OptNArgs(/* void */);
+char *OptArg(/* int */);
+void OptErr(/* int */);
+void OptPrint(/* void */);
+
+/******** From the file "parse.h" *****************************************/
+void Parse(/* struct lemon *lemp */);
+
+/********* From the file "plink.h" ***************************************/
+struct plink *Plink_new(/* void */);
+void Plink_add(/* struct plink **, struct config * */);
+void Plink_copy(/* struct plink **, struct plink * */);
+void Plink_delete(/* struct plink * */);
+
+/********** From the file "report.h" *************************************/
+void Reprint(/* struct lemon * */);
+void ReportOutput(/* struct lemon * */);
+void ReportTable(/* struct lemon * */);
+void ReportHeader(/* struct lemon * */);
+void CompressTables(/* struct lemon * */);
+
+/********** From the file "set.h" ****************************************/
+void SetSize(/* int N */); /* All sets will be of size N */
+char *SetNew(/* void */); /* A new set for element 0..N */
+void SetFree(/* char* */); /* Deallocate a set */
+
+int SetAdd(/* char*,int */); /* Add element to a set */
+int SetUnion(/* char *A,char *B */); /* A <- A U B, thru element N */
+
+#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */
+
+/********** From the file "struct.h" *************************************/
+/*
+** Principal data structures for the LEMON parser generator.
+*/
+
+typedef enum {B_FALSE=0, B_TRUE} Boolean;
+
+/* Symbols (terminals and nonterminals) of the grammar are stored
+** in the following: */
+struct symbol {
+ char *name; /* Name of the symbol */
+ int index; /* Index number for this symbol */
+ enum {
+ TERMINAL,
+ NONTERMINAL
+ } type; /* Symbols are all either TERMINALS or NTs */
+ struct rule *rule; /* Linked list of rules of this (if an NT) */
+ struct symbol *fallback; /* fallback token in case this token doesn't parse */
+ int prec; /* Precedence if defined (-1 otherwise) */
+ enum e_assoc {
+ LEFT,
+ RIGHT,
+ NONE,
+ UNK
+ } assoc; /* Associativity if predecence is defined */
+ char *firstset; /* First-set for all rules of this symbol */
+ Boolean lambda; /* True if NT and can generate an empty string */
+ char *destructor; /* Code which executes whenever this symbol is
+ ** popped from the stack during error processing */
+ int destructorln; /* Line number of destructor code */
+ char *datatype; /* The data type of information held by this
+ ** object. Only used if type==NONTERMINAL */
+ int dtnum; /* The data type number. In the parser, the value
+ ** stack is a union. The .yy%d element of this
+ ** union is the correct data type for this object */
+};
+
+/* Each production rule in the grammar is stored in the following
+** structure. */
+struct rule {
+ struct symbol *lhs; /* Left-hand side of the rule */
+ char *lhsalias; /* Alias for the LHS (NULL if none) */
+ int ruleline; /* Line number for the rule */
+ int nrhs; /* Number of RHS symbols */
+ struct symbol **rhs; /* The RHS symbols */
+ char **rhsalias; /* An alias for each RHS symbol (NULL if none) */
+ int line; /* Line number at which code begins */
+ char *code; /* The code executed when this rule is reduced */
+ struct symbol *precsym; /* Precedence symbol for this rule */
+ int index; /* An index number for this rule */
+ Boolean canReduce; /* True if this rule is ever reduced */
+ struct rule *nextlhs; /* Next rule with the same LHS */
+ struct rule *next; /* Next rule in the global list */
+};
+
+/* A configuration is a production rule of the grammar together with
+** a mark (dot) showing how much of that rule has been processed so far.
+** Configurations also contain a follow-set which is a list of terminal
+** symbols which are allowed to immediately follow the end of the rule.
+** Every configuration is recorded as an instance of the following: */
+struct config {
+ struct rule *rp; /* The rule upon which the configuration is based */
+ int dot; /* The parse point */
+ char *fws; /* Follow-set for this configuration only */
+ struct plink *fplp; /* Follow-set forward propagation links */
+ struct plink *bplp; /* Follow-set backwards propagation links */
+ struct state *stp; /* Pointer to state which contains this */
+ enum {
+ COMPLETE, /* The status is used during followset and */
+ INCOMPLETE /* shift computations */
+ } status;
+ struct config *next; /* Next configuration in the state */
+ struct config *bp; /* The next basis configuration */
+};
+
+/* Every shift or reduce operation is stored as one of the following */
+struct action {
+ struct symbol *sp; /* The look-ahead symbol */
+ enum e_action {
+ SHIFT,
+ ACCEPT,
+ REDUCE,
+ ERROR,
+ CONFLICT, /* Was a reduce, but part of a conflict */
+ SH_RESOLVED, /* Was a shift. Precedence resolved conflict */
+ RD_RESOLVED, /* Was reduce. Precedence resolved conflict */
+ NOT_USED /* Deleted by compression */
+ } type;
+ union {
+ struct state *stp; /* The new state, if a shift */
+ struct rule *rp; /* The rule, if a reduce */
+ } x;
+ struct action *next; /* Next action for this state */
+ struct action *collide; /* Next action with the same hash */
+};
+
+/* Each state of the generated parser's finite state machine
+** is encoded as an instance of the following structure. */
+struct state {
+ struct config *bp; /* The basis configurations for this state */
+ struct config *cfp; /* All configurations in this set */
+ int index; /* Sequencial number for this state */
+ struct action *ap; /* Array of actions for this state */
+ int naction; /* Number of actions for this state */
+ int tabstart; /* First index of the action table */
+ int tabdfltact; /* Default action */
+};
+
+/* A followset propagation link indicates that the contents of one
+** configuration followset should be propagated to another whenever
+** the first changes. */
+struct plink {
+ struct config *cfp; /* The configuration to which linked */
+ struct plink *next; /* The next propagate link */
+};
+
+/* The state vector for the entire parser generator is recorded as
+** follows. (LEMON uses no global variables and makes little use of
+** static variables. Fields in the following structure can be thought
+** of as begin global variables in the program.) */
+struct lemon {
+ struct state **sorted; /* Table of states sorted by state number */
+ struct rule *rule; /* List of all rules */
+ int nstate; /* Number of states */
+ int nrule; /* Number of rules */
+ int nsymbol; /* Number of terminal and nonterminal symbols */
+ int nterminal; /* Number of terminal symbols */
+ struct symbol **symbols; /* Sorted array of pointers to symbols */
+ int errorcnt; /* Number of errors */
+ struct symbol *errsym; /* The error symbol */
+ char *name; /* Name of the generated parser */
+ char *arg; /* Declaration of the 3th argument to parser */
+ char *tokentype; /* Type of terminal symbols in the parser stack */
+ char *vartype; /* The default type of non-terminal symbols */
+ char *start; /* Name of the start symbol for the grammar */
+ char *stacksize; /* Size of the parser stack */
+ char *include; /* Code to put at the start of the C file */
+ int includeln; /* Line number for start of include code */
+ char *error; /* Code to execute when an error is seen */
+ int errorln; /* Line number for start of error code */
+ char *overflow; /* Code to execute on a stack overflow */
+ int overflowln; /* Line number for start of overflow code */
+ char *failure; /* Code to execute on parser failure */
+ int failureln; /* Line number for start of failure code */
+ char *accept; /* Code to execute when the parser excepts */
+ int acceptln; /* Line number for the start of accept code */
+ char *extracode; /* Code appended to the generated file */
+ int extracodeln; /* Line number for the start of the extra code */
+ char *tokendest; /* Code to execute to destroy token data */
+ int tokendestln; /* Line number for token destroyer code */
+ char *vardest; /* Code for the default non-terminal destructor */
+ int vardestln; /* Line number for default non-term destructor code*/
+ char *filename; /* Name of the input file */
+ char *outname; /* Name of the current output file */
+ char *tokenprefix; /* A prefix added to token names in the .h file */
+ int nconflict; /* Number of parsing conflicts */
+ int tablesize; /* Size of the parse tables */
+ int basisflag; /* Print only basis configurations */
+ int has_fallback; /* True if any %fallback is seen in the grammer */
+ char *argv0; /* Name of the program */
+};
+
+#define MemoryCheck(X) if((X)==0){ \
+ extern void memory_error(); \
+ memory_error(); \
+}
+
+/**************** From the file "table.h" *********************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+** "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file! Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+/* Routines for handling a strings */
+
+char *Strsafe();
+
+void Strsafe_init(/* void */);
+int Strsafe_insert(/* char * */);
+char *Strsafe_find(/* char * */);
+
+/* Routines for handling symbols of the grammar */
+
+struct symbol *Symbol_new();
+int Symbolcmpp(/* struct symbol **, struct symbol ** */);
+void Symbol_init(/* void */);
+int Symbol_insert(/* struct symbol *, char * */);
+struct symbol *Symbol_find(/* char * */);
+struct symbol *Symbol_Nth(/* int */);
+int Symbol_count(/* */);
+struct symbol **Symbol_arrayof(/* */);
+
+/* Routines to manage the state table */
+
+int Configcmp(/* struct config *, struct config * */);
+struct state *State_new();
+void State_init(/* void */);
+int State_insert(/* struct state *, struct config * */);
+struct state *State_find(/* struct config * */);
+struct state **State_arrayof(/* */);
+
+/* Routines used for efficiency in Configlist_add */
+
+void Configtable_init(/* void */);
+int Configtable_insert(/* struct config * */);
+struct config *Configtable_find(/* struct config * */);
+void Configtable_clear(/* int(*)(struct config *) */);
+/****************** From the file "action.c" *******************************/
+/*
+** Routines processing parser actions in the LEMON parser generator.
+*/
+
+/* Allocate a new parser action */
+struct action *Action_new(){
+ static struct action *freelist = 0;
+ struct action *new;
+
+ if( freelist==0 ){
+ int i;
+ int amt = 100;
+ freelist = (struct action *)malloc( sizeof(struct action)*amt );
+ if( freelist==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new parser action.");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+ freelist[amt-1].next = 0;
+ }
+ new = freelist;
+ freelist = freelist->next;
+ return new;
+}
+
+/* Compare two actions */
+static int actioncmp(ap1,ap2)
+struct action *ap1;
+struct action *ap2;
+{
+ int rc;
+ rc = ap1->sp->index - ap2->sp->index;
+ if( rc==0 ) rc = (int)ap1->type - (int)ap2->type;
+ if( rc==0 ){
+ assert( ap1->type==REDUCE || ap1->type==RD_RESOLVED || ap1->type==CONFLICT);
+ assert( ap2->type==REDUCE || ap2->type==RD_RESOLVED || ap2->type==CONFLICT);
+ rc = ap1->x.rp->index - ap2->x.rp->index;
+ }
+ return rc;
+}
+
+/* Sort parser actions */
+struct action *Action_sort(ap)
+struct action *ap;
+{
+ ap = (struct action *)msort(ap,&ap->next,actioncmp);
+ return ap;
+}
+
+void Action_add(app,type,sp,arg)
+struct action **app;
+enum e_action type;
+struct symbol *sp;
+char *arg;
+{
+ struct action *new;
+ new = Action_new();
+ new->next = *app;
+ *app = new;
+ new->type = type;
+ new->sp = sp;
+ if( type==SHIFT ){
+ new->x.stp = (struct state *)arg;
+ }else{
+ new->x.rp = (struct rule *)arg;
+ }
+}
+/********************** From the file "assert.c" ****************************/
+/*
+** A more efficient way of handling assertions.
+*/
+void myassert(file,line)
+char *file;
+int line;
+{
+ fprintf(stderr,"Assertion failed on line %d of file \"%s\"\n",line,file);
+ exit(1);
+}
+/********************** From the file "build.c" *****************************/
+/*
+** Routines to construction the finite state machine for the LEMON
+** parser generator.
+*/
+
+/* Find a precedence symbol of every rule in the grammar.
+**
+** Those rules which have a precedence symbol coded in the input
+** grammar using the "[symbol]" construct will already have the
+** rp->precsym field filled. Other rules take as their precedence
+** symbol the first RHS symbol with a defined precedence. If there
+** are not RHS symbols with a defined precedence, the precedence
+** symbol field is left blank.
+*/
+void FindRulePrecedences(xp)
+struct lemon *xp;
+{
+ struct rule *rp;
+ for(rp=xp->rule; rp; rp=rp->next){
+ if( rp->precsym==0 ){
+ int i;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]->prec>=0 ){
+ rp->precsym = rp->rhs[i];
+ break;
+ }
+ }
+ }
+ }
+ return;
+}
+
+/* Find all nonterminals which will generate the empty string.
+** Then go back and compute the first sets of every nonterminal.
+** The first set is the set of all terminal symbols which can begin
+** a string generated by that nonterminal.
+*/
+void FindFirstSets(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct rule *rp;
+ int progress;
+
+ for(i=0; i<lemp->nsymbol; i++){
+ lemp->symbols[i]->lambda = B_FALSE;
+ }
+ for(i=lemp->nterminal; i<lemp->nsymbol; i++){
+ lemp->symbols[i]->firstset = SetNew();
+ }
+
+ /* First compute all lambdas */
+ do{
+ progress = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->lhs->lambda ) continue;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]->lambda==B_FALSE ) break;
+ }
+ if( i==rp->nrhs ){
+ rp->lhs->lambda = B_TRUE;
+ progress = 1;
+ }
+ }
+ }while( progress );
+
+ /* Now compute all first sets */
+ do{
+ struct symbol *s1, *s2;
+ progress = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ s1 = rp->lhs;
+ for(i=0; i<rp->nrhs; i++){
+ s2 = rp->rhs[i];
+ if( s2->type==TERMINAL ){
+ progress += SetAdd(s1->firstset,s2->index);
+ break;
+ }else if( s1==s2 ){
+ if( s1->lambda==B_FALSE ) break;
+ }else{
+ progress += SetUnion(s1->firstset,s2->firstset);
+ if( s2->lambda==B_FALSE ) break;
+ }
+ }
+ }
+ }while( progress );
+ return;
+}
+
+/* Compute all LR(0) states for the grammar. Links
+** are added to between some states so that the LR(1) follow sets
+** can be computed later.
+*/
+PRIVATE struct state *getstate(/* struct lemon * */); /* forward reference */
+void FindStates(lemp)
+struct lemon *lemp;
+{
+ struct symbol *sp;
+ struct rule *rp;
+
+ Configlist_init();
+
+ /* Find the start symbol */
+ if( lemp->start ){
+ sp = Symbol_find(lemp->start);
+ if( sp==0 ){
+ ErrorMsg(lemp->filename,0,
+"The specified start symbol \"%s\" is not \
+in a nonterminal of the grammar. \"%s\" will be used as the start \
+symbol instead.",lemp->start,lemp->rule->lhs->name);
+ lemp->errorcnt++;
+ sp = lemp->rule->lhs;
+ }
+ }else{
+ sp = lemp->rule->lhs;
+ }
+
+ /* Make sure the start symbol doesn't occur on the right-hand side of
+ ** any rule. Report an error if it does. (YACC would generate a new
+ ** start symbol in this case.) */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ int i;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]==sp ){
+ ErrorMsg(lemp->filename,0,
+"The start symbol \"%s\" occurs on the \
+right-hand side of a rule. This will result in a parser which \
+does not work properly.",sp->name);
+ lemp->errorcnt++;
+ }
+ }
+ }
+
+ /* The basis configuration set for the first state
+ ** is all rules which have the start symbol as their
+ ** left-hand side */
+ for(rp=sp->rule; rp; rp=rp->nextlhs){
+ struct config *newcfp;
+ newcfp = Configlist_addbasis(rp,0);
+ SetAdd(newcfp->fws,0);
+ }
+
+ /* Compute the first state. All other states will be
+ ** computed automatically during the computation of the first one.
+ ** The returned pointer to the first state is not used. */
+ (void)getstate(lemp);
+ return;
+}
+
+/* Return a pointer to a state which is described by the configuration
+** list which has been built from calls to Configlist_add.
+*/
+PRIVATE void buildshifts(/* struct lemon *, struct state * */); /* Forwd ref */
+PRIVATE struct state *getstate(lemp)
+struct lemon *lemp;
+{
+ struct config *cfp, *bp;
+ struct state *stp;
+
+ /* Extract the sorted basis of the new state. The basis was constructed
+ ** by prior calls to "Configlist_addbasis()". */
+ Configlist_sortbasis();
+ bp = Configlist_basis();
+
+ /* Get a state with the same basis */
+ stp = State_find(bp);
+ if( stp ){
+ /* A state with the same basis already exists! Copy all the follow-set
+ ** propagation links from the state under construction into the
+ ** preexisting state, then return a pointer to the preexisting state */
+ struct config *x, *y;
+ for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){
+ Plink_copy(&y->bplp,x->bplp);
+ Plink_delete(x->fplp);
+ x->fplp = x->bplp = 0;
+ }
+ cfp = Configlist_return();
+ Configlist_eat(cfp);
+ }else{
+ /* This really is a new state. Construct all the details */
+ Configlist_closure(lemp); /* Compute the configuration closure */
+ Configlist_sort(); /* Sort the configuration closure */
+ cfp = Configlist_return(); /* Get a pointer to the config list */
+ stp = State_new(); /* A new state structure */
+ MemoryCheck(stp);
+ stp->bp = bp; /* Remember the configuration basis */
+ stp->cfp = cfp; /* Remember the configuration closure */
+ stp->index = lemp->nstate++; /* Every state gets a sequence number */
+ stp->ap = 0; /* No actions, yet. */
+ State_insert(stp,stp->bp); /* Add to the state table */
+ buildshifts(lemp,stp); /* Recursively compute successor states */
+ }
+ return stp;
+}
+
+/* Construct all successor states to the given state. A "successor"
+** state is any state which can be reached by a shift action.
+*/
+PRIVATE void buildshifts(lemp,stp)
+struct lemon *lemp;
+struct state *stp; /* The state from which successors are computed */
+{
+ struct config *cfp; /* For looping thru the config closure of "stp" */
+ struct config *bcfp; /* For the inner loop on config closure of "stp" */
+ struct config *new; /* */
+ struct symbol *sp; /* Symbol following the dot in configuration "cfp" */
+ struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */
+ struct state *newstp; /* A pointer to a successor state */
+
+ /* Each configuration becomes complete after it contibutes to a successor
+ ** state. Initially, all configurations are incomplete */
+ for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE;
+
+ /* Loop through all configurations of the state "stp" */
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */
+ if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */
+ Configlist_reset(); /* Reset the new config set */
+ sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */
+
+ /* For every configuration in the state "stp" which has the symbol "sp"
+ ** following its dot, add the same configuration to the basis set under
+ ** construction but with the dot shifted one symbol to the right. */
+ for(bcfp=cfp; bcfp; bcfp=bcfp->next){
+ if( bcfp->status==COMPLETE ) continue; /* Already used */
+ if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */
+ bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */
+ if( bsp!=sp ) continue; /* Must be same as for "cfp" */
+ bcfp->status = COMPLETE; /* Mark this config as used */
+ new = Configlist_addbasis(bcfp->rp,bcfp->dot+1);
+ Plink_add(&new->bplp,bcfp);
+ }
+
+ /* Get a pointer to the state described by the basis configuration set
+ ** constructed in the preceding loop */
+ newstp = getstate(lemp);
+
+ /* The state "newstp" is reached from the state "stp" by a shift action
+ ** on the symbol "sp" */
+ Action_add(&stp->ap,SHIFT,sp,newstp);
+ }
+}
+
+/*
+** Construct the propagation links
+*/
+void FindLinks(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct config *cfp, *other;
+ struct state *stp;
+ struct plink *plp;
+
+ /* Housekeeping detail:
+ ** Add to every propagate link a pointer back to the state to
+ ** which the link is attached. */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ cfp->stp = stp;
+ }
+ }
+
+ /* Convert all backlinks into forward links. Only the forward
+ ** links are used in the follow-set computation. */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ for(plp=cfp->bplp; plp; plp=plp->next){
+ other = plp->cfp;
+ Plink_add(&other->fplp,cfp);
+ }
+ }
+ }
+}
+
+/* Compute all followsets.
+**
+** A followset is the set of all symbols which can come immediately
+** after a configuration.
+*/
+void FindFollowSets(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct config *cfp;
+ struct plink *plp;
+ int progress;
+ int change;
+
+ for(i=0; i<lemp->nstate; i++){
+ for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+ cfp->status = INCOMPLETE;
+ }
+ }
+
+ do{
+ progress = 0;
+ for(i=0; i<lemp->nstate; i++){
+ for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+ if( cfp->status==COMPLETE ) continue;
+ for(plp=cfp->fplp; plp; plp=plp->next){
+ change = SetUnion(plp->cfp->fws,cfp->fws);
+ if( change ){
+ plp->cfp->status = INCOMPLETE;
+ progress = 1;
+ }
+ }
+ cfp->status = COMPLETE;
+ }
+ }
+ }while( progress );
+}
+
+static int resolve_conflict();
+
+/* Compute the reduce actions, and resolve conflicts.
+*/
+void FindActions(lemp)
+struct lemon *lemp;
+{
+ int i,j;
+ struct config *cfp;
+ struct state *stp;
+ struct symbol *sp;
+ struct rule *rp;
+
+ /* Add all of the reduce actions
+ ** A reduce action is added for each element of the followset of
+ ** a configuration which has its dot at the extreme right.
+ */
+ for(i=0; i<lemp->nstate; i++){ /* Loop over all states */
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */
+ if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */
+ for(j=0; j<lemp->nterminal; j++){
+ if( SetFind(cfp->fws,j) ){
+ /* Add a reduce action to the state "stp" which will reduce by the
+ ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */
+ Action_add(&stp->ap,REDUCE,lemp->symbols[j],cfp->rp);
+ }
+ }
+ }
+ }
+ }
+
+ /* Add the accepting token */
+ if( lemp->start ){
+ sp = Symbol_find(lemp->start);
+ if( sp==0 ) sp = lemp->rule->lhs;
+ }else{
+ sp = lemp->rule->lhs;
+ }
+ /* Add to the first state (which is always the starting state of the
+ ** finite state machine) an action to ACCEPT if the lookahead is the
+ ** start nonterminal. */
+ Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0);
+
+ /* Resolve conflicts */
+ for(i=0; i<lemp->nstate; i++){
+ struct action *ap, *nap;
+ struct state *stp;
+ stp = lemp->sorted[i];
+ assert( stp->ap );
+ stp->ap = Action_sort(stp->ap);
+ for(ap=stp->ap; ap && ap->next; ap=ap->next){
+ for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){
+ /* The two actions "ap" and "nap" have the same lookahead.
+ ** Figure out which one should be used */
+ lemp->nconflict += resolve_conflict(ap,nap,lemp->errsym);
+ }
+ }
+ }
+
+ /* Report an error for each rule that can never be reduced. */
+ for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = B_FALSE;
+ for(i=0; i<lemp->nstate; i++){
+ struct action *ap;
+ for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE ) ap->x.rp->canReduce = B_TRUE;
+ }
+ }
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->canReduce ) continue;
+ ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n");
+ lemp->errorcnt++;
+ }
+}
+
+/* Resolve a conflict between the two given actions. If the
+** conflict can't be resolve, return non-zero.
+**
+** NO LONGER TRUE:
+** To resolve a conflict, first look to see if either action
+** is on an error rule. In that case, take the action which
+** is not associated with the error rule. If neither or both
+** actions are associated with an error rule, then try to
+** use precedence to resolve the conflict.
+**
+** If either action is a SHIFT, then it must be apx. This
+** function won't work if apx->type==REDUCE and apy->type==SHIFT.
+*/
+static int resolve_conflict(apx,apy,errsym)
+struct action *apx;
+struct action *apy;
+struct symbol *errsym; /* The error symbol (if defined. NULL otherwise) */
+{
+ struct symbol *spx, *spy;
+ int errcnt = 0;
+ assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */
+ if( apx->type==SHIFT && apy->type==REDUCE ){
+ spx = apx->sp;
+ spy = apy->x.rp->precsym;
+ if( spy==0 || spx->prec<0 || spy->prec<0 ){
+ /* Not enough precedence information. */
+ apy->type = CONFLICT;
+ errcnt++;
+ }else if( spx->prec>spy->prec ){ /* Lower precedence wins */
+ apy->type = RD_RESOLVED;
+ }else if( spx->prec<spy->prec ){
+ apx->type = SH_RESOLVED;
+ }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */
+ apy->type = RD_RESOLVED; /* associativity */
+ }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */
+ apx->type = SH_RESOLVED;
+ }else{
+ assert( spx->prec==spy->prec && spx->assoc==NONE );
+ apy->type = CONFLICT;
+ errcnt++;
+ }
+ }else if( apx->type==REDUCE && apy->type==REDUCE ){
+ spx = apx->x.rp->precsym;
+ spy = apy->x.rp->precsym;
+ if( spx==0 || spy==0 || spx->prec<0 ||
+ spy->prec<0 || spx->prec==spy->prec ){
+ apy->type = CONFLICT;
+ errcnt++;
+ }else if( spx->prec>spy->prec ){
+ apy->type = RD_RESOLVED;
+ }else if( spx->prec<spy->prec ){
+ apx->type = RD_RESOLVED;
+ }
+ }else{
+ assert(
+ apx->type==SH_RESOLVED ||
+ apx->type==RD_RESOLVED ||
+ apx->type==CONFLICT ||
+ apy->type==SH_RESOLVED ||
+ apy->type==RD_RESOLVED ||
+ apy->type==CONFLICT
+ );
+ /* The REDUCE/SHIFT case cannot happen because SHIFTs come before
+ ** REDUCEs on the list. If we reach this point it must be because
+ ** the parser conflict had already been resolved. */
+ }
+ return errcnt;
+}
+/********************* From the file "configlist.c" *************************/
+/*
+** Routines to processing a configuration list and building a state
+** in the LEMON parser generator.
+*/
+
+static struct config *freelist = 0; /* List of free configurations */
+static struct config *current = 0; /* Top of list of configurations */
+static struct config **currentend = 0; /* Last on list of configs */
+static struct config *basis = 0; /* Top of list of basis configs */
+static struct config **basisend = 0; /* End of list of basis configs */
+
+/* Return a pointer to a new configuration */
+PRIVATE struct config *newconfig(){
+ struct config *new;
+ if( freelist==0 ){
+ int i;
+ int amt = 3;
+ freelist = (struct config *)malloc( sizeof(struct config)*amt );
+ if( freelist==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new configuration.");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+ freelist[amt-1].next = 0;
+ }
+ new = freelist;
+ freelist = freelist->next;
+ return new;
+}
+
+/* The configuration "old" is no longer used */
+PRIVATE void deleteconfig(old)
+struct config *old;
+{
+ old->next = freelist;
+ freelist = old;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_init(){
+ current = 0;
+ currentend = &current;
+ basis = 0;
+ basisend = &basis;
+ Configtable_init();
+ return;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_reset(){
+ current = 0;
+ currentend = &current;
+ basis = 0;
+ basisend = &basis;
+ Configtable_clear(0);
+ return;
+}
+
+/* Add another configuration to the configuration list */
+struct config *Configlist_add(rp,dot)
+struct rule *rp; /* The rule */
+int dot; /* Index into the RHS of the rule where the dot goes */
+{
+ struct config *cfp, model;
+
+ assert( currentend!=0 );
+ model.rp = rp;
+ model.dot = dot;
+ cfp = Configtable_find(&model);
+ if( cfp==0 ){
+ cfp = newconfig();
+ cfp->rp = rp;
+ cfp->dot = dot;
+ cfp->fws = SetNew();
+ cfp->stp = 0;
+ cfp->fplp = cfp->bplp = 0;
+ cfp->next = 0;
+ cfp->bp = 0;
+ *currentend = cfp;
+ currentend = &cfp->next;
+ Configtable_insert(cfp);
+ }
+ return cfp;
+}
+
+/* Add a basis configuration to the configuration list */
+struct config *Configlist_addbasis(rp,dot)
+struct rule *rp;
+int dot;
+{
+ struct config *cfp, model;
+
+ assert( basisend!=0 );
+ assert( currentend!=0 );
+ model.rp = rp;
+ model.dot = dot;
+ cfp = Configtable_find(&model);
+ if( cfp==0 ){
+ cfp = newconfig();
+ cfp->rp = rp;
+ cfp->dot = dot;
+ cfp->fws = SetNew();
+ cfp->stp = 0;
+ cfp->fplp = cfp->bplp = 0;
+ cfp->next = 0;
+ cfp->bp = 0;
+ *currentend = cfp;
+ currentend = &cfp->next;
+ *basisend = cfp;
+ basisend = &cfp->bp;
+ Configtable_insert(cfp);
+ }
+ return cfp;
+}
+
+/* Compute the closure of the configuration list */
+void Configlist_closure(lemp)
+struct lemon *lemp;
+{
+ struct config *cfp, *newcfp;
+ struct rule *rp, *newrp;
+ struct symbol *sp, *xsp;
+ int i, dot;
+
+ assert( currentend!=0 );
+ for(cfp=current; cfp; cfp=cfp->next){
+ rp = cfp->rp;
+ dot = cfp->dot;
+ if( dot>=rp->nrhs ) continue;
+ sp = rp->rhs[dot];
+ if( sp->type==NONTERMINAL ){
+ if( sp->rule==0 && sp!=lemp->errsym ){
+ ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.",
+ sp->name);
+ lemp->errorcnt++;
+ }
+ for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){
+ newcfp = Configlist_add(newrp,0);
+ for(i=dot+1; i<rp->nrhs; i++){
+ xsp = rp->rhs[i];
+ if( xsp->type==TERMINAL ){
+ SetAdd(newcfp->fws,xsp->index);
+ break;
+ }else{
+ SetUnion(newcfp->fws,xsp->firstset);
+ if( xsp->lambda==B_FALSE ) break;
+ }
+ }
+ if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp);
+ }
+ }
+ }
+ return;
+}
+
+/* Sort the configuration list */
+void Configlist_sort(){
+ current = (struct config *)msort(current,&(current->next),Configcmp);
+ currentend = 0;
+ return;
+}
+
+/* Sort the basis configuration list */
+void Configlist_sortbasis(){
+ basis = (struct config *)msort(current,&(current->bp),Configcmp);
+ basisend = 0;
+ return;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_return(){
+ struct config *old;
+ old = current;
+ current = 0;
+ currentend = 0;
+ return old;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_basis(){
+ struct config *old;
+ old = basis;
+ basis = 0;
+ basisend = 0;
+ return old;
+}
+
+/* Free all elements of the given configuration list */
+void Configlist_eat(cfp)
+struct config *cfp;
+{
+ struct config *nextcfp;
+ for(; cfp; cfp=nextcfp){
+ nextcfp = cfp->next;
+ assert( cfp->fplp==0 );
+ assert( cfp->bplp==0 );
+ if( cfp->fws ) SetFree(cfp->fws);
+ deleteconfig(cfp);
+ }
+ return;
+}
+/***************** From the file "error.c" *********************************/
+/*
+** Code for printing error message.
+*/
+
+/* Find a good place to break "msg" so that its length is at least "min"
+** but no more than "max". Make the point as close to max as possible.
+*/
+static int findbreak(msg,min,max)
+char *msg;
+int min;
+int max;
+{
+ int i,spot;
+ char c;
+ for(i=spot=min; i<=max; i++){
+ c = msg[i];
+ if( c=='\t' ) msg[i] = ' ';
+ if( c=='\n' ){ msg[i] = ' '; spot = i; break; }
+ if( c==0 ){ spot = i; break; }
+ if( c=='-' && i<max-1 ) spot = i+1;
+ if( c==' ' ) spot = i;
+ }
+ return spot;
+}
+
+/*
+** The error message is split across multiple lines if necessary. The
+** splits occur at a space, if there is a space available near the end
+** of the line.
+*/
+#define ERRMSGSIZE 10000 /* Hope this is big enough. No way to error check */
+#define LINEWIDTH 79 /* Max width of any output line */
+#define PREFIXLIMIT 30 /* Max width of the prefix on each line */
+void ErrorMsg(const char *filename, int lineno, const char *format, ...){
+ char errmsg[ERRMSGSIZE];
+ char prefix[PREFIXLIMIT+10];
+ int errmsgsize;
+ int prefixsize;
+ int availablewidth;
+ va_list ap;
+ int end, restart, base;
+
+ va_start(ap, format);
+ /* Prepare a prefix to be prepended to every output line */
+ if( lineno>0 ){
+ sprintf(prefix,"%.*s:%d: ",PREFIXLIMIT-10,filename,lineno);
+ }else{
+ sprintf(prefix,"%.*s: ",PREFIXLIMIT-10,filename);
+ }
+ prefixsize = strlen(prefix);
+ availablewidth = LINEWIDTH - prefixsize;
+
+ /* Generate the error message */
+ vsprintf(errmsg,format,ap);
+ va_end(ap);
+ errmsgsize = strlen(errmsg);
+ /* Remove trailing '\n's from the error message. */
+ while( errmsgsize>0 && errmsg[errmsgsize-1]=='\n' ){
+ errmsg[--errmsgsize] = 0;
+ }
+
+ /* Print the error message */
+ base = 0;
+ while( errmsg[base]!=0 ){
+ end = restart = findbreak(&errmsg[base],0,availablewidth);
+ restart += base;
+ while( errmsg[restart]==' ' ) restart++;
+ fprintf(stdout,"%s%.*s\n",prefix,end,&errmsg[base]);
+ base = restart;
+ }
+}
+/**************** From the file "main.c" ************************************/
+/*
+** Main program file for the LEMON parser generator.
+*/
+
+/* Report an out-of-memory condition and abort. This function
+** is used mostly by the "MemoryCheck" macro in struct.h
+*/
+void memory_error(){
+ fprintf(stderr,"Out of memory. Aborting...\n");
+ exit(1);
+}
+
+
+/* The main program. Parse the command line and do it... */
+int main(argc,argv)
+int argc;
+char **argv;
+{
+ static int version = 0;
+ static int rpflag = 0;
+ static int basisflag = 0;
+ static int compress = 0;
+ static int quiet = 0;
+ static int statistics = 0;
+ static int mhflag = 0;
+ static struct s_options options[] = {
+ {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."},
+ {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."},
+ {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."},
+ {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file"},
+ {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."},
+ {OPT_FLAG, "s", (char*)&statistics, "Print parser stats to standard output."},
+ {OPT_FLAG, "x", (char*)&version, "Print the version number."},
+ {OPT_FLAG,0,0,0}
+ };
+ int i;
+ struct lemon lem;
+
+ OptInit(argv,options,stderr);
+ if( version ){
+ printf("Lemon version 1.0\n");
+ exit(0);
+ }
+ if( OptNArgs()!=1 ){
+ fprintf(stderr,"Exactly one filename argument is required.\n");
+ exit(1);
+ }
+ lem.errorcnt = 0;
+
+ /* Initialize the machine */
+ Strsafe_init();
+ Symbol_init();
+ State_init();
+ lem.argv0 = argv[0];
+ lem.filename = OptArg(0);
+ lem.basisflag = basisflag;
+ lem.has_fallback = 0;
+ lem.nconflict = 0;
+ lem.name = lem.include = lem.arg = lem.tokentype = lem.start = 0;
+ lem.vartype = 0;
+ lem.stacksize = 0;
+ lem.error = lem.overflow = lem.failure = lem.accept = lem.tokendest =
+ lem.tokenprefix = lem.outname = lem.extracode = 0;
+ lem.vardest = 0;
+ lem.tablesize = 0;
+ Symbol_new("$");
+ lem.errsym = Symbol_new("error");
+
+ /* Parse the input file */
+ Parse(&lem);
+ if( lem.errorcnt ) exit(lem.errorcnt);
+ if( lem.rule==0 ){
+ fprintf(stderr,"Empty grammar.\n");
+ exit(1);
+ }
+
+ /* Count and index the symbols of the grammar */
+ lem.nsymbol = Symbol_count();
+ Symbol_new("{default}");
+ lem.symbols = Symbol_arrayof();
+ qsort(lem.symbols,lem.nsymbol+1,sizeof(struct symbol*),
+ (int(*)())Symbolcmpp);
+ for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+ for(i=1; isupper(lem.symbols[i]->name[0]); i++);
+ lem.nterminal = i;
+
+ /* Generate a reprint of the grammar, if requested on the command line */
+ if( rpflag ){
+ Reprint(&lem);
+ }else{
+ /* Initialize the size for all follow and first sets */
+ SetSize(lem.nterminal);
+
+ /* Find the precedence for every production rule (that has one) */
+ FindRulePrecedences(&lem);
+
+ /* Compute the lambda-nonterminals and the first-sets for every
+ ** nonterminal */
+ FindFirstSets(&lem);
+
+ /* Compute all LR(0) states. Also record follow-set propagation
+ ** links so that the follow-set can be computed later */
+ lem.nstate = 0;
+ FindStates(&lem);
+ lem.sorted = State_arrayof();
+
+ /* Tie up loose ends on the propagation links */
+ FindLinks(&lem);
+
+ /* Compute the follow set of every reducible configuration */
+ FindFollowSets(&lem);
+
+ /* Compute the action tables */
+ FindActions(&lem);
+
+ /* Compress the action tables */
+ if( compress==0 ) CompressTables(&lem);
+
+ /* Generate a report of the parser generated. (the "y.output" file) */
+ if( !quiet ) ReportOutput(&lem);
+
+ /* Generate the source code for the parser */
+ ReportTable(&lem, mhflag);
+
+ /* Produce a header file for use by the scanner. (This step is
+ ** omitted if the "-m" option is used because makeheaders will
+ ** generate the file for us.) */
+ if( !mhflag ) ReportHeader(&lem);
+ }
+ if( statistics ){
+ printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n",
+ lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule);
+ printf(" %d states, %d parser table entries, %d conflicts\n",
+ lem.nstate, lem.tablesize, lem.nconflict);
+ }
+ if( lem.nconflict ){
+ fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict);
+ }
+ exit(lem.errorcnt + lem.nconflict);
+}
+/******************** From the file "msort.c" *******************************/
+/*
+** A generic merge-sort program.
+**
+** USAGE:
+** Let "ptr" be a pointer to some structure which is at the head of
+** a null-terminated list. Then to sort the list call:
+**
+** ptr = msort(ptr,&(ptr->next),cmpfnc);
+**
+** In the above, "cmpfnc" is a pointer to a function which compares
+** two instances of the structure and returns an integer, as in
+** strcmp. The second argument is a pointer to the pointer to the
+** second element of the linked list. This address is used to compute
+** the offset to the "next" field within the structure. The offset to
+** the "next" field must be constant for all structures in the list.
+**
+** The function returns a new pointer which is the head of the list
+** after sorting.
+**
+** ALGORITHM:
+** Merge-sort.
+*/
+
+/*
+** Return a pointer to the next structure in the linked list.
+*/
+#define NEXT(A) (*(char**)(((unsigned long)A)+offset))
+
+/*
+** Inputs:
+** a: A sorted, null-terminated linked list. (May be null).
+** b: A sorted, null-terminated linked list. (May be null).
+** cmp: A pointer to the comparison function.
+** offset: Offset in the structure to the "next" field.
+**
+** Return Value:
+** A pointer to the head of a sorted list containing the elements
+** of both a and b.
+**
+** Side effects:
+** The "next" pointers for elements in the lists a and b are
+** changed.
+*/
+static char *merge(a,b,cmp,offset)
+char *a;
+char *b;
+int (*cmp)();
+int offset;
+{
+ char *ptr, *head;
+
+ if( a==0 ){
+ head = b;
+ }else if( b==0 ){
+ head = a;
+ }else{
+ if( (*cmp)(a,b)<0 ){
+ ptr = a;
+ a = NEXT(a);
+ }else{
+ ptr = b;
+ b = NEXT(b);
+ }
+ head = ptr;
+ while( a && b ){
+ if( (*cmp)(a,b)<0 ){
+ NEXT(ptr) = a;
+ ptr = a;
+ a = NEXT(a);
+ }else{
+ NEXT(ptr) = b;
+ ptr = b;
+ b = NEXT(b);
+ }
+ }
+ if( a ) NEXT(ptr) = a;
+ else NEXT(ptr) = b;
+ }
+ return head;
+}
+
+/*
+** Inputs:
+** list: Pointer to a singly-linked list of structures.
+** next: Pointer to pointer to the second element of the list.
+** cmp: A comparison function.
+**
+** Return Value:
+** A pointer to the head of a sorted list containing the elements
+** orginally in list.
+**
+** Side effects:
+** The "next" pointers for elements in list are changed.
+*/
+#define LISTSIZE 30
+char *msort(list,next,cmp)
+char *list;
+char **next;
+int (*cmp)();
+{
+ unsigned long offset;
+ char *ep;
+ char *set[LISTSIZE];
+ int i;
+ offset = (unsigned long)next - (unsigned long)list;
+ for(i=0; i<LISTSIZE; i++) set[i] = 0;
+ while( list ){
+ ep = list;
+ list = NEXT(list);
+ NEXT(ep) = 0;
+ for(i=0; i<LISTSIZE-1 && set[i]!=0; i++){
+ ep = merge(ep,set[i],cmp,offset);
+ set[i] = 0;
+ }
+ set[i] = ep;
+ }
+ ep = 0;
+ for(i=0; i<LISTSIZE; i++) if( set[i] ) ep = merge(ep,set[i],cmp,offset);
+ return ep;
+}
+/************************ From the file "option.c" **************************/
+static char **argv;
+static struct s_options *op;
+static FILE *errstream;
+
+#define ISOPT(X) ((X)[0]=='-'||(X)[0]=='+'||strchr((X),'=')!=0)
+
+/*
+** Print the command line with a carrot pointing to the k-th character
+** of the n-th field.
+*/
+static void errline(n,k,err)
+int n;
+int k;
+FILE *err;
+{
+ int spcnt, i;
+ spcnt = 0;
+ if( argv[0] ) fprintf(err,"%s",argv[0]);
+ spcnt = strlen(argv[0]) + 1;
+ for(i=1; i<n && argv[i]; i++){
+ fprintf(err," %s",argv[i]);
+ spcnt += strlen(argv[i]+1);
+ }
+ spcnt += k;
+ for(; argv[i]; i++) fprintf(err," %s",argv[i]);
+ if( spcnt<20 ){
+ fprintf(err,"\n%*s^-- here\n",spcnt,"");
+ }else{
+ fprintf(err,"\n%*shere --^\n",spcnt-7,"");
+ }
+}
+
+/*
+** Return the index of the N-th non-switch argument. Return -1
+** if N is out of range.
+*/
+static int argindex(n)
+int n;
+{
+ int i;
+ int dashdash = 0;
+ if( argv!=0 && *argv!=0 ){
+ for(i=1; argv[i]; i++){
+ if( dashdash || !ISOPT(argv[i]) ){
+ if( n==0 ) return i;
+ n--;
+ }
+ if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+ }
+ }
+ return -1;
+}
+
+static char emsg[] = "Command line syntax error: ";
+
+/*
+** Process a flag command line argument.
+*/
+static int handleflags(i,err)
+int i;
+FILE *err;
+{
+ int v;
+ int errcnt = 0;
+ int j;
+ for(j=0; op[j].label; j++){
+ if( strcmp(&argv[i][1],op[j].label)==0 ) break;
+ }
+ v = argv[i][0]=='-' ? 1 : 0;
+ if( op[j].label==0 ){
+ if( err ){
+ fprintf(err,"%sundefined option.\n",emsg);
+ errline(i,1,err);
+ }
+ errcnt++;
+ }else if( op[j].type==OPT_FLAG ){
+ *((int*)op[j].arg) = v;
+ }else if( op[j].type==OPT_FFLAG ){
+ (*(void(*)())(op[j].arg))(v);
+ }else{
+ if( err ){
+ fprintf(err,"%smissing argument on switch.\n",emsg);
+ errline(i,1,err);
+ }
+ errcnt++;
+ }
+ return errcnt;
+}
+
+/*
+** Process a command line switch which has an argument.
+*/
+static int handleswitch(i,err)
+int i;
+FILE *err;
+{
+ int lv = 0;
+ double dv = 0.0;
+ char *sv = 0, *end;
+ char *cp;
+ int j;
+ int errcnt = 0;
+ cp = strchr(argv[i],'=');
+ *cp = 0;
+ for(j=0; op[j].label; j++){
+ if( strcmp(argv[i],op[j].label)==0 ) break;
+ }
+ *cp = '=';
+ if( op[j].label==0 ){
+ if( err ){
+ fprintf(err,"%sundefined option.\n",emsg);
+ errline(i,0,err);
+ }
+ errcnt++;
+ }else{
+ cp++;
+ switch( op[j].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ if( err ){
+ fprintf(err,"%soption requires an argument.\n",emsg);
+ errline(i,0,err);
+ }
+ errcnt++;
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ dv = strtod(cp,&end);
+ if( *end ){
+ if( err ){
+ fprintf(err,"%sillegal character in floating-point argument.\n",emsg);
+ errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+ }
+ errcnt++;
+ }
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ lv = strtol(cp,&end,0);
+ if( *end ){
+ if( err ){
+ fprintf(err,"%sillegal character in integer argument.\n",emsg);
+ errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+ }
+ errcnt++;
+ }
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ sv = cp;
+ break;
+ }
+ switch( op[j].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ break;
+ case OPT_DBL:
+ *(double*)(op[j].arg) = dv;
+ break;
+ case OPT_FDBL:
+ (*(void(*)())(op[j].arg))(dv);
+ break;
+ case OPT_INT:
+ *(int*)(op[j].arg) = lv;
+ break;
+ case OPT_FINT:
+ (*(void(*)())(op[j].arg))((int)lv);
+ break;
+ case OPT_STR:
+ *(char**)(op[j].arg) = sv;
+ break;
+ case OPT_FSTR:
+ (*(void(*)())(op[j].arg))(sv);
+ break;
+ }
+ }
+ return errcnt;
+}
+
+int OptInit(a,o,err)
+char **a;
+struct s_options *o;
+FILE *err;
+{
+ int errcnt = 0;
+ argv = a;
+ op = o;
+ errstream = err;
+ if( argv && *argv && op ){
+ int i;
+ for(i=1; argv[i]; i++){
+ if( argv[i][0]=='+' || argv[i][0]=='-' ){
+ errcnt += handleflags(i,err);
+ }else if( strchr(argv[i],'=') ){
+ errcnt += handleswitch(i,err);
+ }
+ }
+ }
+ if( errcnt>0 ){
+ fprintf(err,"Valid command line options for \"%s\" are:\n",*a);
+ OptPrint();
+ exit(1);
+ }
+ return 0;
+}
+
+int OptNArgs(){
+ int cnt = 0;
+ int dashdash = 0;
+ int i;
+ if( argv!=0 && argv[0]!=0 ){
+ for(i=1; argv[i]; i++){
+ if( dashdash || !ISOPT(argv[i]) ) cnt++;
+ if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+ }
+ }
+ return cnt;
+}
+
+char *OptArg(n)
+int n;
+{
+ int i;
+ i = argindex(n);
+ return i>=0 ? argv[i] : 0;
+}
+
+void OptErr(n)
+int n;
+{
+ int i;
+ i = argindex(n);
+ if( i>=0 ) errline(i,0,errstream);
+}
+
+void OptPrint(){
+ int i;
+ int max, len;
+ max = 0;
+ for(i=0; op[i].label; i++){
+ len = strlen(op[i].label) + 1;
+ switch( op[i].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ len += 9; /* length of "<integer>" */
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ len += 6; /* length of "<real>" */
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ len += 8; /* length of "<string>" */
+ break;
+ }
+ if( len>max ) max = len;
+ }
+ for(i=0; op[i].label; i++){
+ switch( op[i].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ fprintf(errstream," -%-*s %s\n",max,op[i].label,op[i].message);
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ fprintf(errstream," %s=<integer>%*s %s\n",op[i].label,
+ max-strlen(op[i].label)-9,"",op[i].message);
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ fprintf(errstream," %s=<real>%*s %s\n",op[i].label,
+ max-strlen(op[i].label)-6,"",op[i].message);
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ fprintf(errstream," %s=<string>%*s %s\n",op[i].label,
+ max-strlen(op[i].label)-8,"",op[i].message);
+ break;
+ }
+ }
+}
+/*********************** From the file "parse.c" ****************************/
+/*
+** Input file parser for the LEMON parser generator.
+*/
+
+/* The state of the parser */
+struct pstate {
+ char *filename; /* Name of the input file */
+ int tokenlineno; /* Linenumber at which current token starts */
+ int errorcnt; /* Number of errors so far */
+ char *tokenstart; /* Text of current token */
+ struct lemon *gp; /* Global state vector */
+ enum e_state {
+ INITIALIZE,
+ WAITING_FOR_DECL_OR_RULE,
+ WAITING_FOR_DECL_KEYWORD,
+ WAITING_FOR_DECL_ARG,
+ WAITING_FOR_PRECEDENCE_SYMBOL,
+ WAITING_FOR_ARROW,
+ IN_RHS,
+ LHS_ALIAS_1,
+ LHS_ALIAS_2,
+ LHS_ALIAS_3,
+ RHS_ALIAS_1,
+ RHS_ALIAS_2,
+ PRECEDENCE_MARK_1,
+ PRECEDENCE_MARK_2,
+ RESYNC_AFTER_RULE_ERROR,
+ RESYNC_AFTER_DECL_ERROR,
+ WAITING_FOR_DESTRUCTOR_SYMBOL,
+ WAITING_FOR_DATATYPE_SYMBOL,
+ WAITING_FOR_FALLBACK_ID
+ } state; /* The state of the parser */
+ struct symbol *fallback; /* The fallback token */
+ struct symbol *lhs; /* Left-hand side of current rule */
+ char *lhsalias; /* Alias for the LHS */
+ int nrhs; /* Number of right-hand side symbols seen */
+ struct symbol *rhs[MAXRHS]; /* RHS symbols */
+ char *alias[MAXRHS]; /* Aliases for each RHS symbol (or NULL) */
+ struct rule *prevrule; /* Previous rule parsed */
+ char *declkeyword; /* Keyword of a declaration */
+ char **declargslot; /* Where the declaration argument should be put */
+ int *decllnslot; /* Where the declaration linenumber is put */
+ enum e_assoc declassoc; /* Assign this association to decl arguments */
+ int preccounter; /* Assign this precedence to decl arguments */
+ struct rule *firstrule; /* Pointer to first rule in the grammar */
+ struct rule *lastrule; /* Pointer to the most recently parsed rule */
+};
+
+/* Parse a single token */
+static void parseonetoken(psp)
+struct pstate *psp;
+{
+ char *x;
+ x = Strsafe(psp->tokenstart); /* Save the token permanently */
+#if 0
+ printf("%s:%d: Token=[%s] state=%d\n",psp->filename,psp->tokenlineno,
+ x,psp->state);
+#endif
+ switch( psp->state ){
+ case INITIALIZE:
+ psp->prevrule = 0;
+ psp->preccounter = 0;
+ psp->firstrule = psp->lastrule = 0;
+ psp->gp->nrule = 0;
+ /* Fall thru to next case */
+ case WAITING_FOR_DECL_OR_RULE:
+ if( x[0]=='%' ){
+ psp->state = WAITING_FOR_DECL_KEYWORD;
+ }else if( islower(x[0]) ){
+ psp->lhs = Symbol_new(x);
+ psp->nrhs = 0;
+ psp->lhsalias = 0;
+ psp->state = WAITING_FOR_ARROW;
+ }else if( x[0]=='{' ){
+ if( psp->prevrule==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"There is not prior rule opon which to attach the code \
+fragment which begins on this line.");
+ psp->errorcnt++;
+ }else if( psp->prevrule->code!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"Code fragment beginning on this line is not the first \
+to follow the previous rule.");
+ psp->errorcnt++;
+ }else{
+ psp->prevrule->line = psp->tokenlineno;
+ psp->prevrule->code = &x[1];
+ }
+ }else if( x[0]=='[' ){
+ psp->state = PRECEDENCE_MARK_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Token \"%s\" should be either \"%%\" or a nonterminal name.",
+ x);
+ psp->errorcnt++;
+ }
+ break;
+ case PRECEDENCE_MARK_1:
+ if( !isupper(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "The precedence symbol must be a terminal.");
+ psp->errorcnt++;
+ }else if( psp->prevrule==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "There is no prior rule to assign precedence \"[%s]\".",x);
+ psp->errorcnt++;
+ }else if( psp->prevrule->precsym!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"Precedence mark on this line is not the first \
+to follow the previous rule.");
+ psp->errorcnt++;
+ }else{
+ psp->prevrule->precsym = Symbol_new(x);
+ }
+ psp->state = PRECEDENCE_MARK_2;
+ break;
+ case PRECEDENCE_MARK_2:
+ if( x[0]!=']' ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \"]\" on precedence mark.");
+ psp->errorcnt++;
+ }
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ break;
+ case WAITING_FOR_ARROW:
+ if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+ psp->state = IN_RHS;
+ }else if( x[0]=='(' ){
+ psp->state = LHS_ALIAS_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Expected to see a \":\" following the LHS symbol \"%s\".",
+ psp->lhs->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_1:
+ if( isalpha(x[0]) ){
+ psp->lhsalias = x;
+ psp->state = LHS_ALIAS_2;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "\"%s\" is not a valid alias for the LHS \"%s\"\n",
+ x,psp->lhs->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_2:
+ if( x[0]==')' ){
+ psp->state = LHS_ALIAS_3;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_3:
+ if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+ psp->state = IN_RHS;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \"->\" following: \"%s(%s)\".",
+ psp->lhs->name,psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case IN_RHS:
+ if( x[0]=='.' ){
+ struct rule *rp;
+ rp = (struct rule *)malloc( sizeof(struct rule) +
+ sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs );
+ if( rp==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Can't allocate enough memory for this rule.");
+ psp->errorcnt++;
+ psp->prevrule = 0;
+ }else{
+ int i;
+ rp->ruleline = psp->tokenlineno;
+ rp->rhs = (struct symbol**)&rp[1];
+ rp->rhsalias = (char**)&(rp->rhs[psp->nrhs]);
+ for(i=0; i<psp->nrhs; i++){
+ rp->rhs[i] = psp->rhs[i];
+ rp->rhsalias[i] = psp->alias[i];
+ }
+ rp->lhs = psp->lhs;
+ rp->lhsalias = psp->lhsalias;
+ rp->nrhs = psp->nrhs;
+ rp->code = 0;
+ rp->precsym = 0;
+ rp->index = psp->gp->nrule++;
+ rp->nextlhs = rp->lhs->rule;
+ rp->lhs->rule = rp;
+ rp->next = 0;
+ if( psp->firstrule==0 ){
+ psp->firstrule = psp->lastrule = rp;
+ }else{
+ psp->lastrule->next = rp;
+ psp->lastrule = rp;
+ }
+ psp->prevrule = rp;
+ }
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( isalpha(x[0]) ){
+ if( psp->nrhs>=MAXRHS ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Too many symbol on RHS or rule beginning at \"%s\".",
+ x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }else{
+ psp->rhs[psp->nrhs] = Symbol_new(x);
+ psp->alias[psp->nrhs] = 0;
+ psp->nrhs++;
+ }
+ }else if( x[0]=='(' && psp->nrhs>0 ){
+ psp->state = RHS_ALIAS_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal character on RHS of rule: \"%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case RHS_ALIAS_1:
+ if( isalpha(x[0]) ){
+ psp->alias[psp->nrhs-1] = x;
+ psp->state = RHS_ALIAS_2;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n",
+ x,psp->rhs[psp->nrhs-1]->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case RHS_ALIAS_2:
+ if( x[0]==')' ){
+ psp->state = IN_RHS;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case WAITING_FOR_DECL_KEYWORD:
+ if( isalpha(x[0]) ){
+ psp->declkeyword = x;
+ psp->declargslot = 0;
+ psp->decllnslot = 0;
+ psp->state = WAITING_FOR_DECL_ARG;
+ if( strcmp(x,"name")==0 ){
+ psp->declargslot = &(psp->gp->name);
+ }else if( strcmp(x,"include")==0 ){
+ psp->declargslot = &(psp->gp->include);
+ psp->decllnslot = &psp->gp->includeln;
+ }else if( strcmp(x,"code")==0 ){
+ psp->declargslot = &(psp->gp->extracode);
+ psp->decllnslot = &psp->gp->extracodeln;
+ }else if( strcmp(x,"token_destructor")==0 ){
+ psp->declargslot = &psp->gp->tokendest;
+ psp->decllnslot = &psp->gp->tokendestln;
+ }else if( strcmp(x,"default_destructor")==0 ){
+ psp->declargslot = &psp->gp->vardest;
+ psp->decllnslot = &psp->gp->vardestln;
+ }else if( strcmp(x,"token_prefix")==0 ){
+ psp->declargslot = &psp->gp->tokenprefix;
+ }else if( strcmp(x,"syntax_error")==0 ){
+ psp->declargslot = &(psp->gp->error);
+ psp->decllnslot = &psp->gp->errorln;
+ }else if( strcmp(x,"parse_accept")==0 ){
+ psp->declargslot = &(psp->gp->accept);
+ psp->decllnslot = &psp->gp->acceptln;
+ }else if( strcmp(x,"parse_failure")==0 ){
+ psp->declargslot = &(psp->gp->failure);
+ psp->decllnslot = &psp->gp->failureln;
+ }else if( strcmp(x,"stack_overflow")==0 ){
+ psp->declargslot = &(psp->gp->overflow);
+ psp->decllnslot = &psp->gp->overflowln;
+ }else if( strcmp(x,"extra_argument")==0 ){
+ psp->declargslot = &(psp->gp->arg);
+ }else if( strcmp(x,"token_type")==0 ){
+ psp->declargslot = &(psp->gp->tokentype);
+ }else if( strcmp(x,"default_type")==0 ){
+ psp->declargslot = &(psp->gp->vartype);
+ }else if( strcmp(x,"stack_size")==0 ){
+ psp->declargslot = &(psp->gp->stacksize);
+ }else if( strcmp(x,"start_symbol")==0 ){
+ psp->declargslot = &(psp->gp->start);
+ }else if( strcmp(x,"left")==0 ){
+ psp->preccounter++;
+ psp->declassoc = LEFT;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"right")==0 ){
+ psp->preccounter++;
+ psp->declassoc = RIGHT;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"nonassoc")==0 ){
+ psp->preccounter++;
+ psp->declassoc = NONE;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"destructor")==0 ){
+ psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL;
+ }else if( strcmp(x,"type")==0 ){
+ psp->state = WAITING_FOR_DATATYPE_SYMBOL;
+ }else if( strcmp(x,"fallback")==0 ){
+ psp->fallback = 0;
+ psp->state = WAITING_FOR_FALLBACK_ID;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Unknown declaration keyword: \"%%%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal declaration keyword: \"%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case WAITING_FOR_DESTRUCTOR_SYMBOL:
+ if( !isalpha(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol name missing after %destructor keyword");
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ psp->declargslot = &sp->destructor;
+ psp->decllnslot = &sp->destructorln;
+ psp->state = WAITING_FOR_DECL_ARG;
+ }
+ break;
+ case WAITING_FOR_DATATYPE_SYMBOL:
+ if( !isalpha(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol name missing after %destructor keyword");
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ psp->declargslot = &sp->datatype;
+ psp->decllnslot = 0;
+ psp->state = WAITING_FOR_DECL_ARG;
+ }
+ break;
+ case WAITING_FOR_PRECEDENCE_SYMBOL:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( isupper(x[0]) ){
+ struct symbol *sp;
+ sp = Symbol_new(x);
+ if( sp->prec>=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol \"%s\" has already be given a precedence.",x);
+ psp->errorcnt++;
+ }else{
+ sp->prec = psp->preccounter;
+ sp->assoc = psp->declassoc;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Can't assign a precedence to \"%s\".",x);
+ psp->errorcnt++;
+ }
+ break;
+ case WAITING_FOR_DECL_ARG:
+ if( (x[0]=='{' || x[0]=='\"' || isalnum(x[0])) ){
+ if( *(psp->declargslot)!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "The argument \"%s\" to declaration \"%%%s\" is not the first.",
+ x[0]=='\"' ? &x[1] : x,psp->declkeyword);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ *(psp->declargslot) = (x[0]=='\"' || x[0]=='{') ? &x[1] : x;
+ if( psp->decllnslot ) *psp->decllnslot = psp->tokenlineno;
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal argument to %%%s: %s",psp->declkeyword,x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case WAITING_FOR_FALLBACK_ID:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( !isupper(x[0]) ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "%%fallback argument \"%s\" should be a token", x);
+ psp->errorcnt++;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ if( psp->fallback==0 ){
+ psp->fallback = sp;
+ }else if( sp->fallback ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "More than one fallback assigned to token %s", x);
+ psp->errorcnt++;
+ }else{
+ sp->fallback = psp->fallback;
+ psp->gp->has_fallback = 1;
+ }
+ }
+ break;
+ case RESYNC_AFTER_RULE_ERROR:
+/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+** break; */
+ case RESYNC_AFTER_DECL_ERROR:
+ if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+ if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD;
+ break;
+ }
+}
+
+/* In spite of its name, this function is really a scanner. It read
+** in the entire input file (all at once) then tokenizes it. Each
+** token is passed to the function "parseonetoken" which builds all
+** the appropriate data structures in the global state vector "gp".
+*/
+void Parse(gp)
+struct lemon *gp;
+{
+ struct pstate ps;
+ FILE *fp;
+ char *filebuf;
+ int filesize;
+ int lineno;
+ int c;
+ char *cp, *nextcp;
+ int startline = 0;
+
+ ps.gp = gp;
+ ps.filename = gp->filename;
+ ps.errorcnt = 0;
+ ps.state = INITIALIZE;
+
+ /* Begin by reading the input file */
+ fp = fopen(ps.filename,"rb");
+ if( fp==0 ){
+ ErrorMsg(ps.filename,0,"Can't open this file for reading.");
+ gp->errorcnt++;
+ return;
+ }
+ fseek(fp,0,2);
+ filesize = ftell(fp);
+ rewind(fp);
+ filebuf = (char *)malloc( filesize+1 );
+ if( filebuf==0 ){
+ ErrorMsg(ps.filename,0,"Can't allocate %d of memory to hold this file.",
+ filesize+1);
+ gp->errorcnt++;
+ return;
+ }
+ if( fread(filebuf,1,filesize,fp)!=filesize ){
+ ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.",
+ filesize);
+ free(filebuf);
+ gp->errorcnt++;
+ return;
+ }
+ fclose(fp);
+ filebuf[filesize] = 0;
+
+ /* Now scan the text of the input file */
+ lineno = 1;
+ for(cp=filebuf; (c= *cp)!=0; ){
+ if( c=='\n' ) lineno++; /* Keep track of the line number */
+ if( isspace(c) ){ cp++; continue; } /* Skip all white space */
+ if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */
+ cp+=2;
+ while( (c= *cp)!=0 && c!='\n' ) cp++;
+ continue;
+ }
+ if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */
+ cp+=2;
+ while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){
+ if( c=='\n' ) lineno++;
+ cp++;
+ }
+ if( c ) cp++;
+ continue;
+ }
+ ps.tokenstart = cp; /* Mark the beginning of the token */
+ ps.tokenlineno = lineno; /* Linenumber on which token begins */
+ if( c=='\"' ){ /* String literals */
+ cp++;
+ while( (c= *cp)!=0 && c!='\"' ){
+ if( c=='\n' ) lineno++;
+ cp++;
+ }
+ if( c==0 ){
+ ErrorMsg(ps.filename,startline,
+"String starting on this line is not terminated before the end of the file.");
+ ps.errorcnt++;
+ nextcp = cp;
+ }else{
+ nextcp = cp+1;
+ }
+ }else if( c=='{' ){ /* A block of C code */
+ int level;
+ cp++;
+ for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){
+ if( c=='\n' ) lineno++;
+ else if( c=='{' ) level++;
+ else if( c=='}' ) level--;
+ else if( c=='/' && cp[1]=='*' ){ /* Skip comments */
+ int prevc;
+ cp = &cp[2];
+ prevc = 0;
+ while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){
+ if( c=='\n' ) lineno++;
+ prevc = c;
+ cp++;
+ }
+ }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */
+ cp = &cp[2];
+ while( (c= *cp)!=0 && c!='\n' ) cp++;
+ if( c ) lineno++;
+ }else if( c=='\'' || c=='\"' ){ /* String a character literals */
+ int startchar, prevc;
+ startchar = c;
+ prevc = 0;
+ for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){
+ if( c=='\n' ) lineno++;
+ if( prevc=='\\' ) prevc = 0;
+ else prevc = c;
+ }
+ }
+ }
+ if( c==0 ){
+ ErrorMsg(ps.filename,ps.tokenlineno,
+"C code starting on this line is not terminated before the end of the file.");
+ ps.errorcnt++;
+ nextcp = cp;
+ }else{
+ nextcp = cp+1;
+ }
+ }else if( isalnum(c) ){ /* Identifiers */
+ while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++;
+ nextcp = cp;
+ }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */
+ cp += 3;
+ nextcp = cp;
+ }else{ /* All other (one character) operators */
+ cp++;
+ nextcp = cp;
+ }
+ c = *cp;
+ *cp = 0; /* Null terminate the token */
+ parseonetoken(&ps); /* Parse the token */
+ *cp = c; /* Restore the buffer */
+ cp = nextcp;
+ }
+ free(filebuf); /* Release the buffer after parsing */
+ gp->rule = ps.firstrule;
+ gp->errorcnt = ps.errorcnt;
+}
+/*************************** From the file "plink.c" *********************/
+/*
+** Routines processing configuration follow-set propagation links
+** in the LEMON parser generator.
+*/
+static struct plink *plink_freelist = 0;
+
+/* Allocate a new plink */
+struct plink *Plink_new(){
+ struct plink *new;
+
+ if( plink_freelist==0 ){
+ int i;
+ int amt = 100;
+ plink_freelist = (struct plink *)malloc( sizeof(struct plink)*amt );
+ if( plink_freelist==0 ){
+ fprintf(stderr,
+ "Unable to allocate memory for a new follow-set propagation link.\n");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) plink_freelist[i].next = &plink_freelist[i+1];
+ plink_freelist[amt-1].next = 0;
+ }
+ new = plink_freelist;
+ plink_freelist = plink_freelist->next;
+ return new;
+}
+
+/* Add a plink to a plink list */
+void Plink_add(plpp,cfp)
+struct plink **plpp;
+struct config *cfp;
+{
+ struct plink *new;
+ new = Plink_new();
+ new->next = *plpp;
+ *plpp = new;
+ new->cfp = cfp;
+}
+
+/* Transfer every plink on the list "from" to the list "to" */
+void Plink_copy(to,from)
+struct plink **to;
+struct plink *from;
+{
+ struct plink *nextpl;
+ while( from ){
+ nextpl = from->next;
+ from->next = *to;
+ *to = from;
+ from = nextpl;
+ }
+}
+
+/* Delete every plink on the list */
+void Plink_delete(plp)
+struct plink *plp;
+{
+ struct plink *nextpl;
+
+ while( plp ){
+ nextpl = plp->next;
+ plp->next = plink_freelist;
+ plink_freelist = plp;
+ plp = nextpl;
+ }
+}
+/*********************** From the file "report.c" **************************/
+/*
+** Procedures for generating reports and tables in the LEMON parser generator.
+*/
+
+/* Generate a filename with the given suffix. Space to hold the
+** name comes from malloc() and must be freed by the calling
+** function.
+*/
+PRIVATE char *file_makename(lemp,suffix)
+struct lemon *lemp;
+char *suffix;
+{
+ char *name;
+ char *cp;
+
+ name = malloc( strlen(lemp->filename) + strlen(suffix) + 5 );
+ if( name==0 ){
+ fprintf(stderr,"Can't allocate space for a filename.\n");
+ exit(1);
+ }
+ strcpy(name,lemp->filename);
+ cp = strrchr(name,'.');
+ if( cp ) *cp = 0;
+ strcat(name,suffix);
+ return name;
+}
+
+/* Open a file with a name based on the name of the input file,
+** but with a different (specified) suffix, and return a pointer
+** to the stream */
+PRIVATE FILE *file_open(lemp,suffix,mode)
+struct lemon *lemp;
+char *suffix;
+char *mode;
+{
+ FILE *fp;
+
+ if( lemp->outname ) free(lemp->outname);
+ lemp->outname = file_makename(lemp, suffix);
+ fp = fopen(lemp->outname,mode);
+ if( fp==0 && *mode=='w' ){
+ fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname);
+ lemp->errorcnt++;
+ return 0;
+ }
+ return fp;
+}
+
+/* Duplicate the input file without comments and without actions
+** on rules */
+void Reprint(lemp)
+struct lemon *lemp;
+{
+ struct rule *rp;
+ struct symbol *sp;
+ int i, j, maxlen, len, ncolumns, skip;
+ printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename);
+ maxlen = 10;
+ for(i=0; i<lemp->nsymbol; i++){
+ sp = lemp->symbols[i];
+ len = strlen(sp->name);
+ if( len>maxlen ) maxlen = len;
+ }
+ ncolumns = 76/(maxlen+5);
+ if( ncolumns<1 ) ncolumns = 1;
+ skip = (lemp->nsymbol + ncolumns - 1)/ncolumns;
+ for(i=0; i<skip; i++){
+ printf("//");
+ for(j=i; j<lemp->nsymbol; j+=skip){
+ sp = lemp->symbols[j];
+ assert( sp->index==j );
+ printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name);
+ }
+ printf("\n");
+ }
+ for(rp=lemp->rule; rp; rp=rp->next){
+ printf("%s",rp->lhs->name);
+/* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */
+ printf(" ::=");
+ for(i=0; i<rp->nrhs; i++){
+ printf(" %s",rp->rhs[i]->name);
+/* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */
+ }
+ printf(".");
+ if( rp->precsym ) printf(" [%s]",rp->precsym->name);
+/* if( rp->code ) printf("\n %s",rp->code); */
+ printf("\n");
+ }
+}
+
+void ConfigPrint(fp,cfp)
+FILE *fp;
+struct config *cfp;
+{
+ struct rule *rp;
+ int i;
+ rp = cfp->rp;
+ fprintf(fp,"%s ::=",rp->lhs->name);
+ for(i=0; i<=rp->nrhs; i++){
+ if( i==cfp->dot ) fprintf(fp," *");
+ if( i==rp->nrhs ) break;
+ fprintf(fp," %s",rp->rhs[i]->name);
+ }
+}
+
+/* #define TEST */
+#ifdef TEST
+/* Print a set */
+PRIVATE void SetPrint(out,set,lemp)
+FILE *out;
+char *set;
+struct lemon *lemp;
+{
+ int i;
+ char *spacer;
+ spacer = "";
+ fprintf(out,"%12s[","");
+ for(i=0; i<lemp->nterminal; i++){
+ if( SetFind(set,i) ){
+ fprintf(out,"%s%s",spacer,lemp->symbols[i]->name);
+ spacer = " ";
+ }
+ }
+ fprintf(out,"]\n");
+}
+
+/* Print a plink chain */
+PRIVATE void PlinkPrint(out,plp,tag)
+FILE *out;
+struct plink *plp;
+char *tag;
+{
+ while( plp ){
+ fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->index);
+ ConfigPrint(out,plp->cfp);
+ fprintf(out,"\n");
+ plp = plp->next;
+ }
+}
+#endif
+
+/* Print an action to the given file descriptor. Return FALSE if
+** nothing was actually printed.
+*/
+int PrintAction(struct action *ap, FILE *fp, int indent){
+ int result = 1;
+ switch( ap->type ){
+ case SHIFT:
+ fprintf(fp,"%*s shift %d",indent,ap->sp->name,ap->x.stp->index);
+ break;
+ case REDUCE:
+ fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index);
+ break;
+ case ACCEPT:
+ fprintf(fp,"%*s accept",indent,ap->sp->name);
+ break;
+ case ERROR:
+ fprintf(fp,"%*s error",indent,ap->sp->name);
+ break;
+ case CONFLICT:
+ fprintf(fp,"%*s reduce %-3d ** Parsing conflict **",
+ indent,ap->sp->name,ap->x.rp->index);
+ break;
+ case SH_RESOLVED:
+ case RD_RESOLVED:
+ case NOT_USED:
+ result = 0;
+ break;
+ }
+ return result;
+}
+
+/* Generate the "y.output" log file */
+void ReportOutput(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct state *stp;
+ struct config *cfp;
+ struct action *ap;
+ FILE *fp;
+
+ fp = file_open(lemp,".out","w");
+ if( fp==0 ) return;
+ fprintf(fp," \b");
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ fprintf(fp,"State %d:\n",stp->index);
+ if( lemp->basisflag ) cfp=stp->bp;
+ else cfp=stp->cfp;
+ while( cfp ){
+ char buf[20];
+ if( cfp->dot==cfp->rp->nrhs ){
+ sprintf(buf,"(%d)",cfp->rp->index);
+ fprintf(fp," %5s ",buf);
+ }else{
+ fprintf(fp," ");
+ }
+ ConfigPrint(fp,cfp);
+ fprintf(fp,"\n");
+#ifdef TEST
+ SetPrint(fp,cfp->fws,lemp);
+ PlinkPrint(fp,cfp->fplp,"To ");
+ PlinkPrint(fp,cfp->bplp,"From");
+#endif
+ if( lemp->basisflag ) cfp=cfp->bp;
+ else cfp=cfp->next;
+ }
+ fprintf(fp,"\n");
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( PrintAction(ap,fp,30) ) fprintf(fp,"\n");
+ }
+ fprintf(fp,"\n");
+ }
+ fclose(fp);
+ return;
+}
+
+/* Search for the file "name" which is in the same directory as
+** the exacutable */
+PRIVATE char *pathsearch(argv0,name,modemask)
+char *argv0;
+char *name;
+int modemask;
+{
+ char *pathlist;
+ char *path,*cp;
+ char c;
+ extern int access();
+
+#ifdef __WIN32__
+ cp = strrchr(argv0,'\\');
+#else
+ cp = strrchr(argv0,'/');
+#endif
+ if( cp ){
+ c = *cp;
+ *cp = 0;
+ path = (char *)malloc( strlen(argv0) + strlen(name) + 2 );
+ if( path ) sprintf(path,"%s/%s",argv0,name);
+ *cp = c;
+ }else{
+ extern char *getenv();
+ pathlist = getenv("PATH");
+ if( pathlist==0 ) pathlist = ".:/bin:/usr/bin";
+ path = (char *)malloc( strlen(pathlist)+strlen(name)+2 );
+ if( path!=0 ){
+ while( *pathlist ){
+ cp = strchr(pathlist,':');
+ if( cp==0 ) cp = &pathlist[strlen(pathlist)];
+ c = *cp;
+ *cp = 0;
+ sprintf(path,"%s/%s",pathlist,name);
+ *cp = c;
+ if( c==0 ) pathlist = "";
+ else pathlist = &cp[1];
+ if( access(path,modemask)==0 ) break;
+ }
+ }
+ }
+ return path;
+}
+
+/* Given an action, compute the integer value for that action
+** which is to be put in the action table of the generated machine.
+** Return negative if no action should be generated.
+*/
+PRIVATE int compute_action(lemp,ap)
+struct lemon *lemp;
+struct action *ap;
+{
+ int act;
+ switch( ap->type ){
+ case SHIFT: act = ap->x.stp->index; break;
+ case REDUCE: act = ap->x.rp->index + lemp->nstate; break;
+ case ERROR: act = lemp->nstate + lemp->nrule; break;
+ case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break;
+ default: act = -1; break;
+ }
+ return act;
+}
+
+#define LINESIZE 1000
+/* The next cluster of routines are for reading the template file
+** and writing the results to the generated parser */
+/* The first function transfers data from "in" to "out" until
+** a line is seen which begins with "%%". The line number is
+** tracked.
+**
+** if name!=0, then any word that begin with "Parse" is changed to
+** begin with *name instead.
+*/
+PRIVATE void tplt_xfer(name,in,out,lineno)
+char *name;
+FILE *in;
+FILE *out;
+int *lineno;
+{
+ int i, iStart;
+ char line[LINESIZE];
+ while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+ (*lineno)++;
+ iStart = 0;
+ if( name ){
+ for(i=0; line[i]; i++){
+ if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0
+ && (i==0 || !isalpha(line[i-1]))
+ ){
+ if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]);
+ fprintf(out,"%s",name);
+ i += 4;
+ iStart = i+1;
+ }
+ }
+ }
+ fprintf(out,"%s",&line[iStart]);
+ }
+}
+
+/* The next function finds the template file and opens it, returning
+** a pointer to the opened file. */
+PRIVATE FILE *tplt_open(lemp)
+struct lemon *lemp;
+{
+ static char templatename[] = "lempar.c";
+ char buf[1000];
+ FILE *in;
+ char *tpltname;
+ char *cp;
+
+ cp = strrchr(lemp->filename,'.');
+ if( cp ){
+ sprintf(buf,"%.*s.lt",(unsigned long)cp-(unsigned long)lemp->filename,lemp->filename);
+ }else{
+ sprintf(buf,"%s.lt",lemp->filename);
+ }
+ if( access(buf,004)==0 ){
+ tpltname = buf;
+ }else if( access(templatename,004)==0 ){
+ tpltname = templatename;
+ }else{
+ tpltname = pathsearch(lemp->argv0,templatename,0);
+ }
+ if( tpltname==0 ){
+ fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+ templatename);
+ lemp->errorcnt++;
+ return 0;
+ }
+ in = fopen(tpltname,"r");
+ if( in==0 ){
+ fprintf(stderr,"Can't open the template file \"%s\".\n",templatename);
+ lemp->errorcnt++;
+ return 0;
+ }
+ return in;
+}
+
+/* Print a string to the file and keep the linenumber up to date */
+PRIVATE void tplt_print(out,lemp,str,strln,lineno)
+FILE *out;
+struct lemon *lemp;
+char *str;
+int strln;
+int *lineno;
+{
+ if( str==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n",strln,lemp->filename); (*lineno)++;
+ while( *str ){
+ if( *str=='\n' ) (*lineno)++;
+ putc(*str,out);
+ str++;
+ }
+ fprintf(out,"\n#line %d \"%s\"\n",*lineno+2,lemp->outname); (*lineno)+=2;
+ return;
+}
+
+/*
+** The following routine emits code for the destructor for the
+** symbol sp
+*/
+void emit_destructor_code(out,sp,lemp,lineno)
+FILE *out;
+struct symbol *sp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp;
+
+ int linecnt = 0;
+ if( sp->type==TERMINAL ){
+ cp = lemp->tokendest;
+ if( cp==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n{",lemp->tokendestln,lemp->filename);
+ }else if( sp->destructor ){
+ cp = sp->destructor;
+ fprintf(out,"#line %d \"%s\"\n{",sp->destructorln,lemp->filename);
+ }else if( lemp->vardest ){
+ cp = lemp->vardest;
+ if( cp==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n{",lemp->vardestln,lemp->filename);
+ }
+ for(; *cp; cp++){
+ if( *cp=='$' && cp[1]=='$' ){
+ fprintf(out,"(yypminor->yy%d)",sp->dtnum);
+ cp++;
+ continue;
+ }
+ if( *cp=='\n' ) linecnt++;
+ fputc(*cp,out);
+ }
+ (*lineno) += 3 + linecnt;
+ fprintf(out,"}\n#line %d \"%s\"\n",*lineno,lemp->outname);
+ return;
+}
+
+/*
+** Return TRUE (non-zero) if the given symbol has a destructor.
+*/
+int has_destructor(sp, lemp)
+struct symbol *sp;
+struct lemon *lemp;
+{
+ int ret;
+ if( sp->type==TERMINAL ){
+ ret = lemp->tokendest!=0;
+ }else{
+ ret = lemp->vardest!=0 || sp->destructor!=0;
+ }
+ return ret;
+}
+
+/*
+** Generate code which executes when the rule "rp" is reduced. Write
+** the code to "out". Make sure lineno stays up-to-date.
+*/
+PRIVATE void emit_code(out,rp,lemp,lineno)
+FILE *out;
+struct rule *rp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp, *xp;
+ int linecnt = 0;
+ int i;
+ char lhsused = 0; /* True if the LHS element has been used */
+ char used[MAXRHS]; /* True for each RHS element which is used */
+
+ for(i=0; i<rp->nrhs; i++) used[i] = 0;
+ lhsused = 0;
+
+ /* Generate code to do the reduce action */
+ if( rp->code ){
+ fprintf(out,"#line %d \"%s\"\n{",rp->line,lemp->filename);
+ for(cp=rp->code; *cp; cp++){
+ if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){
+ char saved;
+ for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++);
+ saved = *xp;
+ *xp = 0;
+ if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){
+ fprintf(out,"yygotominor.yy%d",rp->lhs->dtnum);
+ cp = xp;
+ lhsused = 1;
+ }else{
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){
+ fprintf(out,"yymsp[%d].minor.yy%d",i-rp->nrhs+1,rp->rhs[i]->dtnum);
+ cp = xp;
+ used[i] = 1;
+ break;
+ }
+ }
+ }
+ *xp = saved;
+ }
+ if( *cp=='\n' ) linecnt++;
+ fputc(*cp,out);
+ } /* End loop */
+ (*lineno) += 3 + linecnt;
+ fprintf(out,"}\n#line %d \"%s\"\n",*lineno,lemp->outname);
+ } /* End if( rp->code ) */
+
+ /* Check to make sure the LHS has been used */
+ if( rp->lhsalias && !lhsused ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label \"%s\" for \"%s(%s)\" is never used.",
+ rp->lhsalias,rp->lhs->name,rp->lhsalias);
+ lemp->errorcnt++;
+ }
+
+ /* Generate destructor code for RHS symbols which are not used in the
+ ** reduce code */
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhsalias[i] && !used[i] ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label %s for \"%s(%s)\" is never used.",
+ rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]);
+ lemp->errorcnt++;
+ }else if( rp->rhsalias[i]==0 ){
+ if( has_destructor(rp->rhs[i],lemp) ){
+ fprintf(out," yy_destructor(%d,&yymsp[%d].minor);\n",
+ rp->rhs[i]->index,i-rp->nrhs+1); (*lineno)++;
+ }else{
+ fprintf(out," /* No destructor defined for %s */\n",
+ rp->rhs[i]->name);
+ (*lineno)++;
+ }
+ }
+ }
+ return;
+}
+
+/*
+** Print the definition of the union used for the parser's data stack.
+** This union contains fields for every possible data type for tokens
+** and nonterminals. In the process of computing and printing this
+** union, also set the ".dtnum" field of every terminal and nonterminal
+** symbol.
+*/
+void print_stack_union(out,lemp,plineno,mhflag)
+FILE *out; /* The output stream */
+struct lemon *lemp; /* The main info structure for this parser */
+int *plineno; /* Pointer to the line number */
+int mhflag; /* True if generating makeheaders output */
+{
+ int lineno = *plineno; /* The line number of the output */
+ char **types; /* A hash table of datatypes */
+ int arraysize; /* Size of the "types" array */
+ int maxdtlength; /* Maximum length of any ".datatype" field. */
+ char *stddt; /* Standardized name for a datatype */
+ int i,j; /* Loop counters */
+ int hash; /* For hashing the name of a type */
+ char *name; /* Name of the parser */
+
+ /* Allocate and initialize types[] and allocate stddt[] */
+ arraysize = lemp->nsymbol * 2;
+ types = (char**)malloc( arraysize * sizeof(char*) );
+ for(i=0; i<arraysize; i++) types[i] = 0;
+ maxdtlength = 0;
+ if( lemp->vartype ){
+ maxdtlength = strlen(lemp->vartype);
+ }
+ for(i=0; i<lemp->nsymbol; i++){
+ int len;
+ struct symbol *sp = lemp->symbols[i];
+ if( sp->datatype==0 ) continue;
+ len = strlen(sp->datatype);
+ if( len>maxdtlength ) maxdtlength = len;
+ }
+ stddt = (char*)malloc( maxdtlength*2 + 1 );
+ if( types==0 || stddt==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+
+ /* Build a hash table of datatypes. The ".dtnum" field of each symbol
+ ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is
+ ** used for terminal symbols. If there is no %default_type defined then
+ ** 0 is also used as the .dtnum value for nonterminals which do not specify
+ ** a datatype using the %type directive.
+ */
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ char *cp;
+ if( sp==lemp->errsym ){
+ sp->dtnum = arraysize+1;
+ continue;
+ }
+ if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){
+ sp->dtnum = 0;
+ continue;
+ }
+ cp = sp->datatype;
+ if( cp==0 ) cp = lemp->vartype;
+ j = 0;
+ while( isspace(*cp) ) cp++;
+ while( *cp ) stddt[j++] = *cp++;
+ while( j>0 && isspace(stddt[j-1]) ) j--;
+ stddt[j] = 0;
+ hash = 0;
+ for(j=0; stddt[j]; j++){
+ hash = hash*53 + stddt[j];
+ }
+ hash = (hash & 0x7fffffff)%arraysize;
+ while( types[hash] ){
+ if( strcmp(types[hash],stddt)==0 ){
+ sp->dtnum = hash + 1;
+ break;
+ }
+ hash++;
+ if( hash>=arraysize ) hash = 0;
+ }
+ if( types[hash]==0 ){
+ sp->dtnum = hash + 1;
+ types[hash] = (char*)malloc( strlen(stddt)+1 );
+ if( types[hash]==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+ strcpy(types[hash],stddt);
+ }
+ }
+
+ /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */
+ name = lemp->name ? lemp->name : "Parse";
+ lineno = *plineno;
+ if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }
+ fprintf(out,"#define %sTOKENTYPE %s\n",name,
+ lemp->tokentype?lemp->tokentype:"void*"); lineno++;
+ if( mhflag ){ fprintf(out,"#endif\n"); lineno++; }
+ fprintf(out,"typedef union {\n"); lineno++;
+ fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++;
+ for(i=0; i<arraysize; i++){
+ if( types[i]==0 ) continue;
+ fprintf(out," %s yy%d;\n",types[i],i+1); lineno++;
+ free(types[i]);
+ }
+ fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++;
+ free(stddt);
+ free(types);
+ fprintf(out,"} YYMINORTYPE;\n"); lineno++;
+ *plineno = lineno;
+}
+
+/*
+** Return the name of a C datatype able to represent values between
+** 0 and N, inclusive.
+*/
+static const char *minimum_size_type(int N){
+ if( N<=255 ){
+ return "unsigned char";
+ }else if( N<65535 ){
+ return "unsigned short int";
+ }else{
+ return "unsigned int";
+ }
+}
+
+/* Generate C source code for the parser */
+void ReportTable(lemp, mhflag)
+struct lemon *lemp;
+int mhflag; /* Output in makeheaders format if true */
+{
+ FILE *out, *in;
+ char line[LINESIZE];
+ int lineno;
+ struct state *stp;
+ struct action *ap;
+ struct rule *rp;
+ int i, j;
+ int tablecnt;
+ char *name;
+
+ in = tplt_open(lemp);
+ if( in==0 ) return;
+ out = file_open(lemp,".c","w");
+ if( out==0 ){
+ fclose(in);
+ return;
+ }
+ lineno = 1;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the include code, if any */
+ tplt_print(out,lemp,lemp->include,lemp->includeln,&lineno);
+ if( mhflag ){
+ char *name = file_makename(lemp, ".h");
+ fprintf(out,"#include \"%s\"\n", name); lineno++;
+ free(name);
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate #defines for all tokens */
+ if( mhflag ){
+ char *prefix;
+ fprintf(out,"#if INTERFACE\n"); lineno++;
+ if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+ else prefix = "";
+ for(i=1; i<lemp->nterminal; i++){
+ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ lineno++;
+ }
+ fprintf(out,"#endif\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the defines */
+ fprintf(out,"/* \001 */\n");
+ fprintf(out,"#define YYCODETYPE %s\n",
+ minimum_size_type(lemp->nsymbol+5)); lineno++;
+ fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++;
+ fprintf(out,"#define YYACTIONTYPE %s\n",
+ minimum_size_type(lemp->nstate+lemp->nrule+5)); lineno++;
+ print_stack_union(out,lemp,&lineno,mhflag);
+ if( lemp->stacksize ){
+ if( atoi(lemp->stacksize)<=0 ){
+ ErrorMsg(lemp->filename,0,
+"Illegal stack size: [%s]. The stack size should be an integer constant.",
+ lemp->stacksize);
+ lemp->errorcnt++;
+ lemp->stacksize = "100";
+ }
+ fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++;
+ }else{
+ fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++;
+ }
+ if( mhflag ){
+ fprintf(out,"#if INTERFACE\n"); lineno++;
+ }
+ name = lemp->name ? lemp->name : "Parse";
+ if( lemp->arg && lemp->arg[0] ){
+ int i;
+ i = strlen(lemp->arg);
+ while( i>=1 && isspace(lemp->arg[i-1]) ) i--;
+ while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--;
+ fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++;
+ fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++;
+ fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n",
+ name,lemp->arg,&lemp->arg[i]); lineno++;
+ fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n",
+ name,&lemp->arg[i],&lemp->arg[i]); lineno++;
+ }else{
+ fprintf(out,"#define %sARG_SDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_PDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
+ fprintf(out,"#define %sARG_STORE\n",name); lineno++;
+ }
+ if( mhflag ){
+ fprintf(out,"#endif\n"); lineno++;
+ }
+ fprintf(out,"#define YYNSTATE %d\n",lemp->nstate); lineno++;
+ fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++;
+ fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++;
+ fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++;
+ if( lemp->has_fallback ){
+ fprintf(out,"#define YYFALLBACK 1\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the action table.
+ **
+ ** Each entry in the action table is an element of the following
+ ** structure:
+ ** struct yyActionEntry {
+ ** YYCODETYPE lookahead;
+ ** YYCODETYPE next;
+ ** YYACTIONTYPE action;
+ ** }
+ **
+ ** The entries are grouped into hash tables, one hash table for each
+ ** parser state. The hash table has a size which is the number of
+ ** entries in that table. In case of a collision, the "next" value
+ ** contains one more than the index into the hash table of the next
+ ** entry in the collision chain. A "next" value of 0 means the end
+ ** of the chain has been reached.
+ */
+ tablecnt = 0;
+
+ /* Loop over parser states */
+ for(i=0; i<lemp->nstate; i++){
+ int tablesize; /* size of the hash table */
+ int j,k; /* Loop counter */
+ int collide[2048]; /* The collision chain for the table */
+ struct action *table[2048]; /* Build the hash table here */
+
+ /* Find the number of actions and initialize the hash table */
+ stp = lemp->sorted[i];
+ stp->tabstart = tablecnt;
+ stp->naction = 0;
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->sp->index!=lemp->nsymbol && compute_action(lemp,ap)>=0 ){
+ stp->naction++;
+ }
+ }
+ tablesize = stp->naction;
+ assert( tablesize<= sizeof(table)/sizeof(table[0]) );
+ for(j=0; j<tablesize; j++){
+ table[j] = 0;
+ collide[j] = -1;
+ }
+
+ /* Hash the actions into the hash table */
+ stp->tabdfltact = lemp->nstate + lemp->nrule;
+ for(ap=stp->ap; ap; ap=ap->next){
+ int action = compute_action(lemp,ap);
+ int h;
+ if( ap->sp->index==lemp->nsymbol ){
+ stp->tabdfltact = action;
+ }else if( action>=0 ){
+ h = ap->sp->index % tablesize;
+ ap->collide = table[h];
+ table[h] = ap;
+ }
+ }
+
+ /* Resolve collisions */
+ for(j=k=0; j<tablesize; j++){
+ if( table[j] && table[j]->collide ){
+ while( table[k] ) k++;
+ table[k] = table[j]->collide;
+ collide[j] = k;
+ table[j]->collide = 0;
+ if( k<j ) j = k-1;
+ }
+ }
+
+ /* Print the hash table */
+ if( tablesize>0 ){
+ fprintf(out,"/* State %d */\n",stp->index); lineno++;
+ }
+ for(j=0; j<tablesize; j++){
+ assert( table[j]!=0 );
+ fprintf(out," {%4d,%4d,%4d}, /* %2d: ",
+ table[j]->sp->index,
+ collide[j]+1,
+ compute_action(lemp,table[j]),
+ j+1);
+ PrintAction(table[j],out,22);
+ fprintf(out," */\n");
+ lineno++;
+ }
+
+ /* Update the table count */
+ tablecnt += tablesize;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+ lemp->tablesize = tablecnt;
+
+ /* Generate the state table
+ **
+ ** Each entry is an element of the following structure:
+ ** struct yyStateEntry {
+ ** struct yyActionEntry *hashtbl;
+ ** YYCODETYPE nEntry;
+ ** YYACTIONTYPE actionDefault;
+ ** }
+ */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ fprintf(out," { &yyActionTable[%d],%4d,%4d },\n",
+ stp->tabstart,
+ stp->naction,
+ stp->tabdfltact); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the table of fallback tokens.
+ */
+ if( lemp->has_fallback ){
+ for(i=0; i<lemp->nterminal; i++){
+ struct symbol *p = lemp->symbols[i];
+ if( p->fallback==0 ){
+ fprintf(out, " 0, /* %10s => nothing */\n", p->name);
+ }else{
+ fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index,
+ p->name, p->fallback->name);
+ }
+ lineno++;
+ }
+ }
+ tplt_xfer(lemp->name, in, out, &lineno);
+
+ /* Generate a table containing the symbolic name of every symbol
+ */
+ for(i=0; i<lemp->nsymbol; i++){
+ sprintf(line,"\"%s\",",lemp->symbols[i]->name);
+ fprintf(out," %-15s",line);
+ if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; }
+ }
+ if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate a table containing a text string that describes every
+ ** rule in the rule set of the grammer. This information is used
+ ** when tracing REDUCE actions.
+ */
+ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+ assert( rp->index==i );
+ fprintf(out," /* %3d */ \"%s ::=", i, rp->lhs->name);
+ for(j=0; j<rp->nrhs; j++) fprintf(out," %s",rp->rhs[j]->name);
+ fprintf(out,"\",\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes every time a symbol is popped from
+ ** the stack while processing errors or while destroying the parser.
+ ** (In other words, generate the %destructor actions)
+ */
+ if( lemp->tokendest ){
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type!=TERMINAL ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ }
+ for(i=0; i<lemp->nsymbol && lemp->symbols[i]->type!=TERMINAL; i++);
+ if( i<lemp->nsymbol ){
+ emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ }
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ if( lemp->vardest ){
+ struct symbol *dflt_sp = 0;
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type==TERMINAL ||
+ sp->index<=0 || sp->destructor!=0 ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ dflt_sp = sp;
+ }
+ if( dflt_sp!=0 ){
+ emit_destructor_code(out,dflt_sp,lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes whenever the parser stack overflows */
+ tplt_print(out,lemp,lemp->overflow,lemp->overflowln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the table of rule information
+ **
+ ** Note: This code depends on the fact that rules are number
+ ** sequentually beginning with 0.
+ */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ fprintf(out," { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which execution during each REDUCE action */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ fprintf(out," case %d:\n",rp->index); lineno++;
+ emit_code(out,rp,lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes if a parse fails */
+ tplt_print(out,lemp,lemp->failure,lemp->failureln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes when a syntax error occurs */
+ tplt_print(out,lemp,lemp->error,lemp->errorln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes when the parser accepts its input */
+ tplt_print(out,lemp,lemp->accept,lemp->acceptln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Append any addition code the user desires */
+ tplt_print(out,lemp,lemp->extracode,lemp->extracodeln,&lineno);
+
+ fclose(in);
+ fclose(out);
+ return;
+}
+
+/* Generate a header file for the parser */
+void ReportHeader(lemp)
+struct lemon *lemp;
+{
+ FILE *out, *in;
+ char *prefix;
+ char line[LINESIZE];
+ char pattern[LINESIZE];
+ int i;
+
+ if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+ else prefix = "";
+ in = file_open(lemp,".h","r");
+ if( in ){
+ for(i=1; i<lemp->nterminal && fgets(line,LINESIZE,in); i++){
+ sprintf(pattern,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ if( strcmp(line,pattern) ) break;
+ }
+ fclose(in);
+ if( i==lemp->nterminal ){
+ /* No change in the file. Don't rewrite it. */
+ return;
+ }
+ }
+ out = file_open(lemp,".h","w");
+ if( out ){
+ for(i=1; i<lemp->nterminal; i++){
+ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ }
+ fclose(out);
+ }
+ return;
+}
+
+/* Reduce the size of the action tables, if possible, by making use
+** of defaults.
+**
+** In this version, we take the most frequent REDUCE action and make
+** it the default. Only default a reduce if there are more than one.
+*/
+void CompressTables(lemp)
+struct lemon *lemp;
+{
+ struct state *stp;
+ struct action *ap, *ap2;
+ struct rule *rp, *rp2, *rbest;
+ int nbest, n;
+ int i;
+ int cnt;
+
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ nbest = 0;
+ rbest = 0;
+
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type!=REDUCE ) continue;
+ rp = ap->x.rp;
+ if( rp==rbest ) continue;
+ n = 1;
+ for(ap2=ap->next; ap2; ap2=ap2->next){
+ if( ap2->type!=REDUCE ) continue;
+ rp2 = ap2->x.rp;
+ if( rp2==rbest ) continue;
+ if( rp2==rp ) n++;
+ }
+ if( n>nbest ){
+ nbest = n;
+ rbest = rp;
+ }
+ }
+
+ /* Do not make a default if the number of rules to default
+ ** is not at least 2 */
+ if( nbest<2 ) continue;
+
+
+ /* Combine matching REDUCE actions into a single default */
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE && ap->x.rp==rbest ) break;
+ }
+ assert( ap );
+ ap->sp = Symbol_new("{default}");
+ for(ap=ap->next; ap; ap=ap->next){
+ if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED;
+ }
+ stp->ap = Action_sort(stp->ap);
+ }
+}
+
+/***************** From the file "set.c" ************************************/
+/*
+** Set manipulation routines for the LEMON parser generator.
+*/
+
+static int size = 0;
+
+/* Set the set size */
+void SetSize(n)
+int n;
+{
+ size = n+1;
+}
+
+/* Allocate a new set */
+char *SetNew(){
+ char *s;
+ int i;
+ s = (char*)malloc( size );
+ if( s==0 ){
+ extern void memory_error();
+ memory_error();
+ }
+ for(i=0; i<size; i++) s[i] = 0;
+ return s;
+}
+
+/* Deallocate a set */
+void SetFree(s)
+char *s;
+{
+ free(s);
+}
+
+/* Add a new element to the set. Return TRUE if the element was added
+** and FALSE if it was already there. */
+int SetAdd(s,e)
+char *s;
+int e;
+{
+ int rv;
+ rv = s[e];
+ s[e] = 1;
+ return !rv;
+}
+
+/* Add every element of s2 to s1. Return TRUE if s1 changes. */
+int SetUnion(s1,s2)
+char *s1;
+char *s2;
+{
+ int i, progress;
+ progress = 0;
+ for(i=0; i<size; i++){
+ if( s2[i]==0 ) continue;
+ if( s1[i]==0 ){
+ progress = 1;
+ s1[i] = 1;
+ }
+ }
+ return progress;
+}
+/********************** From the file "table.c" ****************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+** "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file! Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+PRIVATE int strhash(x)
+char *x;
+{
+ int h = 0;
+ while( *x) h = h*13 + *(x++);
+ return h;
+}
+
+/* Works like strdup, sort of. Save a string in malloced memory, but
+** keep strings in a table so that the same string is not in more
+** than one place.
+*/
+char *Strsafe(y)
+char *y;
+{
+ char *z;
+
+ z = Strsafe_find(y);
+ if( z==0 && (z=malloc( strlen(y)+1 ))!=0 ){
+ strcpy(z,y);
+ Strsafe_insert(z);
+ }
+ MemoryCheck(z);
+ return z;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x1".
+*/
+struct s_x1 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x1node *tbl; /* The data stored here */
+ struct s_x1node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x1".
+*/
+typedef struct s_x1node {
+ char *data; /* The data */
+ struct s_x1node *next; /* Next entry with the same hash */
+ struct s_x1node **from; /* Previous link */
+} x1node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x1 *x1a;
+
+/* Allocate a new associative array */
+void Strsafe_init(){
+ if( x1a ) return;
+ x1a = (struct s_x1*)malloc( sizeof(struct s_x1) );
+ if( x1a ){
+ x1a->size = 1024;
+ x1a->count = 0;
+ x1a->tbl = (x1node*)malloc(
+ (sizeof(x1node) + sizeof(x1node*))*1024 );
+ if( x1a->tbl==0 ){
+ free(x1a);
+ x1a = 0;
+ }else{
+ int i;
+ x1a->ht = (x1node**)&(x1a->tbl[1024]);
+ for(i=0; i<1024; i++) x1a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Strsafe_insert(data)
+char *data;
+{
+ x1node *np;
+ int h;
+ int ph;
+
+ if( x1a==0 ) return 0;
+ ph = strhash(data);
+ h = ph & (x1a->size-1);
+ np = x1a->ht[h];
+ while( np ){
+ if( strcmp(np->data,data)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x1a->count>=x1a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x1 array;
+ array.size = size = x1a->size*2;
+ array.count = x1a->count;
+ array.tbl = (x1node*)malloc(
+ (sizeof(x1node) + sizeof(x1node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x1node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x1a->count; i++){
+ x1node *oldnp, *newnp;
+ oldnp = &(x1a->tbl[i]);
+ h = strhash(oldnp->data) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x1a->tbl);
+ *x1a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x1a->size-1);
+ np = &(x1a->tbl[x1a->count++]);
+ np->data = data;
+ if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next);
+ np->next = x1a->ht[h];
+ x1a->ht[h] = np;
+ np->from = &(x1a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+char *Strsafe_find(key)
+char *key;
+{
+ int h;
+ x1node *np;
+
+ if( x1a==0 ) return 0;
+ h = strhash(key) & (x1a->size-1);
+ np = x1a->ht[h];
+ while( np ){
+ if( strcmp(np->data,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return a pointer to the (terminal or nonterminal) symbol "x".
+** Create a new symbol if this is the first time "x" has been seen.
+*/
+struct symbol *Symbol_new(x)
+char *x;
+{
+ struct symbol *sp;
+
+ sp = Symbol_find(x);
+ if( sp==0 ){
+ sp = (struct symbol *)malloc( sizeof(struct symbol) );
+ MemoryCheck(sp);
+ sp->name = Strsafe(x);
+ sp->type = isupper(*x) ? TERMINAL : NONTERMINAL;
+ sp->rule = 0;
+ sp->fallback = 0;
+ sp->prec = -1;
+ sp->assoc = UNK;
+ sp->firstset = 0;
+ sp->lambda = B_FALSE;
+ sp->destructor = 0;
+ sp->datatype = 0;
+ Symbol_insert(sp,sp->name);
+ }
+ return sp;
+}
+
+/* Compare two symbols */
+int Symbolcmpp(a,b)
+struct symbol **a;
+struct symbol **b;
+{
+ return strcmp((**a).name,(**b).name);
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x2".
+*/
+struct s_x2 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x2node *tbl; /* The data stored here */
+ struct s_x2node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x2".
+*/
+typedef struct s_x2node {
+ struct symbol *data; /* The data */
+ char *key; /* The key */
+ struct s_x2node *next; /* Next entry with the same hash */
+ struct s_x2node **from; /* Previous link */
+} x2node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x2 *x2a;
+
+/* Allocate a new associative array */
+void Symbol_init(){
+ if( x2a ) return;
+ x2a = (struct s_x2*)malloc( sizeof(struct s_x2) );
+ if( x2a ){
+ x2a->size = 128;
+ x2a->count = 0;
+ x2a->tbl = (x2node*)malloc(
+ (sizeof(x2node) + sizeof(x2node*))*128 );
+ if( x2a->tbl==0 ){
+ free(x2a);
+ x2a = 0;
+ }else{
+ int i;
+ x2a->ht = (x2node**)&(x2a->tbl[128]);
+ for(i=0; i<128; i++) x2a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Symbol_insert(data,key)
+struct symbol *data;
+char *key;
+{
+ x2node *np;
+ int h;
+ int ph;
+
+ if( x2a==0 ) return 0;
+ ph = strhash(key);
+ h = ph & (x2a->size-1);
+ np = x2a->ht[h];
+ while( np ){
+ if( strcmp(np->key,key)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x2a->count>=x2a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x2 array;
+ array.size = size = x2a->size*2;
+ array.count = x2a->count;
+ array.tbl = (x2node*)malloc(
+ (sizeof(x2node) + sizeof(x2node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x2node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x2a->count; i++){
+ x2node *oldnp, *newnp;
+ oldnp = &(x2a->tbl[i]);
+ h = strhash(oldnp->key) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->key = oldnp->key;
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x2a->tbl);
+ *x2a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x2a->size-1);
+ np = &(x2a->tbl[x2a->count++]);
+ np->key = key;
+ np->data = data;
+ if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next);
+ np->next = x2a->ht[h];
+ x2a->ht[h] = np;
+ np->from = &(x2a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct symbol *Symbol_find(key)
+char *key;
+{
+ int h;
+ x2node *np;
+
+ if( x2a==0 ) return 0;
+ h = strhash(key) & (x2a->size-1);
+ np = x2a->ht[h];
+ while( np ){
+ if( strcmp(np->key,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return the n-th data. Return NULL if n is out of range. */
+struct symbol *Symbol_Nth(n)
+int n;
+{
+ struct symbol *data;
+ if( x2a && n>0 && n<=x2a->count ){
+ data = x2a->tbl[n-1].data;
+ }else{
+ data = 0;
+ }
+ return data;
+}
+
+/* Return the size of the array */
+int Symbol_count()
+{
+ return x2a ? x2a->count : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc. Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct symbol **Symbol_arrayof()
+{
+ struct symbol **array;
+ int i,size;
+ if( x2a==0 ) return 0;
+ size = x2a->count;
+ array = (struct symbol **)malloc( sizeof(struct symbol *)*size );
+ if( array ){
+ for(i=0; i<size; i++) array[i] = x2a->tbl[i].data;
+ }
+ return array;
+}
+
+/* Compare two configurations */
+int Configcmp(a,b)
+struct config *a;
+struct config *b;
+{
+ int x;
+ x = a->rp->index - b->rp->index;
+ if( x==0 ) x = a->dot - b->dot;
+ return x;
+}
+
+/* Compare two states */
+PRIVATE int statecmp(a,b)
+struct config *a;
+struct config *b;
+{
+ int rc;
+ for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){
+ rc = a->rp->index - b->rp->index;
+ if( rc==0 ) rc = a->dot - b->dot;
+ }
+ if( rc==0 ){
+ if( a ) rc = 1;
+ if( b ) rc = -1;
+ }
+ return rc;
+}
+
+/* Hash a state */
+PRIVATE int statehash(a)
+struct config *a;
+{
+ int h=0;
+ while( a ){
+ h = h*571 + a->rp->index*37 + a->dot;
+ a = a->bp;
+ }
+ return h;
+}
+
+/* Allocate a new state structure */
+struct state *State_new()
+{
+ struct state *new;
+ new = (struct state *)malloc( sizeof(struct state) );
+ MemoryCheck(new);
+ return new;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x3".
+*/
+struct s_x3 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x3node *tbl; /* The data stored here */
+ struct s_x3node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x3".
+*/
+typedef struct s_x3node {
+ struct state *data; /* The data */
+ struct config *key; /* The key */
+ struct s_x3node *next; /* Next entry with the same hash */
+ struct s_x3node **from; /* Previous link */
+} x3node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x3 *x3a;
+
+/* Allocate a new associative array */
+void State_init(){
+ if( x3a ) return;
+ x3a = (struct s_x3*)malloc( sizeof(struct s_x3) );
+ if( x3a ){
+ x3a->size = 128;
+ x3a->count = 0;
+ x3a->tbl = (x3node*)malloc(
+ (sizeof(x3node) + sizeof(x3node*))*128 );
+ if( x3a->tbl==0 ){
+ free(x3a);
+ x3a = 0;
+ }else{
+ int i;
+ x3a->ht = (x3node**)&(x3a->tbl[128]);
+ for(i=0; i<128; i++) x3a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int State_insert(data,key)
+struct state *data;
+struct config *key;
+{
+ x3node *np;
+ int h;
+ int ph;
+
+ if( x3a==0 ) return 0;
+ ph = statehash(key);
+ h = ph & (x3a->size-1);
+ np = x3a->ht[h];
+ while( np ){
+ if( statecmp(np->key,key)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x3a->count>=x3a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x3 array;
+ array.size = size = x3a->size*2;
+ array.count = x3a->count;
+ array.tbl = (x3node*)malloc(
+ (sizeof(x3node) + sizeof(x3node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x3node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x3a->count; i++){
+ x3node *oldnp, *newnp;
+ oldnp = &(x3a->tbl[i]);
+ h = statehash(oldnp->key) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->key = oldnp->key;
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x3a->tbl);
+ *x3a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x3a->size-1);
+ np = &(x3a->tbl[x3a->count++]);
+ np->key = key;
+ np->data = data;
+ if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next);
+ np->next = x3a->ht[h];
+ x3a->ht[h] = np;
+ np->from = &(x3a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct state *State_find(key)
+struct config *key;
+{
+ int h;
+ x3node *np;
+
+ if( x3a==0 ) return 0;
+ h = statehash(key) & (x3a->size-1);
+ np = x3a->ht[h];
+ while( np ){
+ if( statecmp(np->key,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc. Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct state **State_arrayof()
+{
+ struct state **array;
+ int i,size;
+ if( x3a==0 ) return 0;
+ size = x3a->count;
+ array = (struct state **)malloc( sizeof(struct state *)*size );
+ if( array ){
+ for(i=0; i<size; i++) array[i] = x3a->tbl[i].data;
+ }
+ return array;
+}
+
+/* Hash a configuration */
+PRIVATE int confighash(a)
+struct config *a;
+{
+ int h=0;
+ h = h*571 + a->rp->index*37 + a->dot;
+ return h;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x4".
+*/
+struct s_x4 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x4node *tbl; /* The data stored here */
+ struct s_x4node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x4".
+*/
+typedef struct s_x4node {
+ struct config *data; /* The data */
+ struct s_x4node *next; /* Next entry with the same hash */
+ struct s_x4node **from; /* Previous link */
+} x4node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x4 *x4a;
+
+/* Allocate a new associative array */
+void Configtable_init(){
+ if( x4a ) return;
+ x4a = (struct s_x4*)malloc( sizeof(struct s_x4) );
+ if( x4a ){
+ x4a->size = 64;
+ x4a->count = 0;
+ x4a->tbl = (x4node*)malloc(
+ (sizeof(x4node) + sizeof(x4node*))*64 );
+ if( x4a->tbl==0 ){
+ free(x4a);
+ x4a = 0;
+ }else{
+ int i;
+ x4a->ht = (x4node**)&(x4a->tbl[64]);
+ for(i=0; i<64; i++) x4a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Configtable_insert(data)
+struct config *data;
+{
+ x4node *np;
+ int h;
+ int ph;
+
+ if( x4a==0 ) return 0;
+ ph = confighash(data);
+ h = ph & (x4a->size-1);
+ np = x4a->ht[h];
+ while( np ){
+ if( Configcmp(np->data,data)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x4a->count>=x4a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x4 array;
+ array.size = size = x4a->size*2;
+ array.count = x4a->count;
+ array.tbl = (x4node*)malloc(
+ (sizeof(x4node) + sizeof(x4node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x4node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x4a->count; i++){
+ x4node *oldnp, *newnp;
+ oldnp = &(x4a->tbl[i]);
+ h = confighash(oldnp->data) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x4a->tbl);
+ *x4a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x4a->size-1);
+ np = &(x4a->tbl[x4a->count++]);
+ np->data = data;
+ if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next);
+ np->next = x4a->ht[h];
+ x4a->ht[h] = np;
+ np->from = &(x4a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct config *Configtable_find(key)
+struct config *key;
+{
+ int h;
+ x4node *np;
+
+ if( x4a==0 ) return 0;
+ h = confighash(key) & (x4a->size-1);
+ np = x4a->ht[h];
+ while( np ){
+ if( Configcmp(np->data,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Remove all data from the table. Pass each data to the function "f"
+** as it is removed. ("f" may be null to avoid this step.) */
+void Configtable_clear(f)
+int(*f)(/* struct config * */);
+{
+ int i;
+ if( x4a==0 || x4a->count==0 ) return;
+ if( f ) for(i=0; i<x4a->count; i++) (*f)(x4a->tbl[i].data);
+ for(i=0; i<x4a->size; i++) x4a->ht[i] = 0;
+ x4a->count = 0;
+ return;
+}
diff --git a/kexi/3rdparty/kexisql/tool/lempar.c b/kexi/3rdparty/kexisql/tool/lempar.c
new file mode 100644
index 000000000..5604fe10d
--- /dev/null
+++ b/kexi/3rdparty/kexisql/tool/lempar.c
@@ -0,0 +1,657 @@
+/* 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>
+%%
+/* 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.
+** ParseTOKENTYPE 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 ParseTOKENTYPE. The entry in the union
+** for base tokens is called "yy0".
+** YYSTACKDEPTH is the maximum depth of the parser's stack.
+** ParseARG_SDECL A static variable declaration for the %extra_argument
+** ParseARG_PDECL A parameter declaration for the %extra_argument
+** ParseARG_STORE Code to store %extra_argument into yypParser
+** ParseARG_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 YY_NO_ACTION (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
+/* Next is the action table. Each entry in this table contains
+**
+** + An integer which is the number representing the look-ahead
+** token
+**
+** + An integer indicating what action to take. Number (N) between
+** 0 and YYNSTATE-1 mean shift the look-ahead and go to state N.
+** Numbers between YYNSTATE and YYNSTATE+YYNRULE-1 mean reduce by
+** rule N-YYNSTATE. Number YYNSTATE+YYNRULE means that a syntax
+** error has occurred. Number YYNSTATE+YYNRULE+1 means the parser
+** accepts its input.
+**
+** + A pointer to the next entry with the same hash value.
+**
+** The action table is really a series of hash tables. Each hash
+** table contains a number of entries which is a power of two. The
+** "state" table (which follows) contains information about the starting
+** point and size of each hash table.
+*/
+struct yyActionEntry {
+ YYCODETYPE lookahead; /* The value of the look-ahead token */
+ YYCODETYPE next; /* Next entry + 1. Zero at end of collision chain */
+ YYACTIONTYPE action; /* Action to take for this look-ahead */
+};
+typedef struct yyActionEntry yyActionEntry;
+static const yyActionEntry yyActionTable[] = {
+%%
+};
+
+/* The state table contains information needed to look up the correct
+** action in the action table, given the current state of the parser.
+** Information needed includes:
+**
+** + A pointer to the start of the action hash table in yyActionTable.
+**
+** + The number of entries in the action hash table.
+**
+** + The default action. This is the action to take if no entry for
+** the given look-ahead is found in the action hash table.
+*/
+struct yyStateEntry {
+ const yyActionEntry *hashtbl; /* Start of the hash table in yyActionTable */
+ YYCODETYPE nEntry; /* Number of entries in action hash table */
+ YYACTIONTYPE actionDefault; /* Default action if look-ahead not found */
+};
+typedef struct yyStateEntry yyStateEntry;
+static const yyStateEntry yyStateTable[] = {
+%%
+};
+
+/* 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[] = {
+%%
+};
+#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 */
+ yyStackEntry *yytop; /* Pointer to the top stack element */
+ ParseARG_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 ParseTrace(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[] = {
+%%
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *yyRuleName[] = {
+%%
+};
+#endif /* NDEBUG */
+
+/*
+** This function returns the symbolic name associated with a token
+** value.
+*/
+const char *ParseTokenName(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 Parse and ParseFree.
+*/
+void *ParseAlloc(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.
+ */
+%%
+ 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;
+
+ if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+ if( yyTraceFILE && pParser->yyidx>=0 ){
+ fprintf(yyTraceFILE,"%sPopping %s\n",
+ yyTracePrompt,
+ yyTokenName[pParser->yytop->major]);
+ }
+#endif
+ yymajor = pParser->yytop->major;
+ yy_destructor( yymajor, &pParser->yytop->minor);
+ pParser->yyidx--;
+ pParser->yytop--;
+ 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 ParseAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+void ParseFree(
+ 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 look-ahead token.
+**
+** 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_parser_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ const yyStateEntry *pState; /* Appropriate entry in the state table */
+ const yyActionEntry *pAction; /* Action appropriate for the look-ahead */
+ int iFallback; /* Fallback token */
+
+ /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */
+ pState = &yyStateTable[pParser->yytop->stateno];
+ if( pState->nEntry==0 ){
+ return pState->actionDefault;
+ }else if( iLookAhead!=YYNOCODE ){
+ pAction = &pState->hashtbl[iLookAhead % pState->nEntry];
+ while( 1 ){
+ if( pAction->lookahead==iLookAhead ) return pAction->action;
+ if( pAction->next==0 ) break;
+ pAction = &pState->hashtbl[pAction->next-1];
+ }
+#ifdef YYFALLBACK
+ 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_parser_action(pParser, iFallback);
+ }
+#endif
+ }else if( pState->hashtbl->lookahead!=YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ return pState->actionDefault;
+}
+
+/*
+** 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 */
+){
+ yypParser->yyidx++;
+ yypParser->yytop++;
+ if( yypParser->yyidx>=YYSTACKDEPTH ){
+ ParseARG_FETCH;
+ yypParser->yyidx--;
+ yypParser->yytop--;
+#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 */
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
+ return;
+ }
+ yypParser->yytop->stateno = yyNewState;
+ yypParser->yytop->major = yyMajor;
+ yypParser->yytop->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[] = {
+%%
+};
+
+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 */
+ ParseARG_FETCH;
+ yymsp = yypParser->yytop;
+#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;
+ */
+%%
+ };
+ yygoto = yyRuleInfo[yyruleno].lhs;
+ yysize = yyRuleInfo[yyruleno].nrhs;
+ yypParser->yyidx -= yysize;
+ yypParser->yytop -= yysize;
+ yyact = yy_find_parser_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 */
+){
+ ParseARG_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 */
+%%
+ ParseARG_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 */
+){
+ ParseARG_FETCH;
+#define TOKEN (yyminor.yy0)
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+ yyParser *yypParser /* The parser */
+){
+ ParseARG_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 */
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ParseAlloc" 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 Parse(
+ void *yyp, /* The parser */
+ int yymajor, /* The major token code number */
+ ParseTOKENTYPE yyminor /* The value for the token */
+ ParseARG_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->yytop = &yypParser->yystack[0];
+ yypParser->yytop->stateno = 0;
+ yypParser->yytop->major = 0;
+ }
+ yyminorunion.yy0 = yyminor;
+ yyendofinput = (yymajor==0);
+ ParseARG_STORE;
+
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+
+ do{
+ yyact = yy_find_parser_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 ){
+#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);
+ }
+ if( yypParser->yytop->major==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 &&
+ yypParser->yytop->major != YYERRORSYMBOL &&
+ (yyact = yy_find_parser_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( yypParser->yytop->major!=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/kexi/3rdparty/kexisql/tool/opcodes.sh b/kexi/3rdparty/kexisql/tool/opcodes.sh
new file mode 100755
index 000000000..7e7024ed5
--- /dev/null
+++ b/kexi/3rdparty/kexisql/tool/opcodes.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# this script has to be run after the version
+# changed
+
+#generate c file:
+echo '/* Automatically generated file. Do not edit */' >../src/opcodes.c
+echo 'char *sqliteOpcodeNames[] = { "???", ' >>../src/opcodes.c
+grep '^case OP_' ../src/vdbe.c | sed -e 's/^.*OP_/ "/' -e 's/:.*/", /' >>../src/opcodes.c
+echo '};' >>../src/opcodes.c
+
+#generate header file:
+echo '/* Automatically generated file. Do not edit */' >../src/opcodes.h
+grep '^case OP_' ../src/vdbe.c | sed -e 's/case //' -e 's/:[ {]*//' | awk '{printf "#define %-30s %3d\n", $$2, ++cnt}' >>../src/opcodes.h
diff --git a/kexi/3rdparty/kexisql3/Makefile.am b/kexi/3rdparty/kexisql3/Makefile.am
new file mode 100644
index 000000000..af437a64d
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = src
diff --git a/kexi/3rdparty/kexisql3/Makefile.msvc b/kexi/3rdparty/kexisql3/Makefile.msvc
new file mode 100644
index 000000000..0ee218955
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/Makefile.msvc
@@ -0,0 +1,233 @@
+#############################################################################
+# Makefile for building: sqlite3
+#############################################################################
+
+####### Compiler, tools and options
+
+CC = cl
+CXX = cl
+LEX = flex
+YACC = byacc
+CFLAGS = -nologo -Zm200 -W3 -MDd -Z7 -GX -GR -DTHREADSAFE=1 -D_OFF_T_DEFINED=1 -DNO_TCL=1
+CXXFLAGS = -nologo -Zm200 /GR /GX /GZ /TP -W3 -MDd -Z7 -GX -GR -DUNICODE
+LEXFLAGS =
+YACCFLAGS =-d
+INCPATH = -Isrc -I$(QTDIR)\include
+LINK = link
+LFLAGS = /NOLOGO /FORCE:MULTIPLE /DEBUG /SUBSYSTEM:console /DEF:sqlite3.def /DLL
+VERSION=3.0.7
+LIBS = "kernel32.lib" "user32.lib" "gdi32.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "imm32.lib" "winmm.lib" "wsock32.lib" "winspool.lib"
+IDL = midl
+ZIP = zip -r -9
+COPY_FILE = copy
+COPY_DIR = copy
+DEL_FILE = del
+DEL_DIR = rmdir
+MOVE = move
+CHK_DIR_EXISTS = if not exist
+MKDIR = mkdir
+
+####### Files
+
+HEADERS = \
+ src/btree.h \
+ src/hash.h \
+ src/keywordhash.h \
+ src/opcodes.h \
+ src/os.h \
+ src/os_common.h \
+ src/pager.h \
+ src/parse.h \
+ src/sqlite3.h \
+ src/sqliteInt.h \
+ src/vdbe.h \
+ src/vdbeInt.h
+
+SOURCES = \
+ src/alter.c \
+ src/analyze.c \
+ src/attach.c \
+ src/auth.c \
+ src/btree.c \
+ src/build.c \
+ src/callback.c \
+ src/complete.c \
+ src/date.c \
+ src/delete.c \
+ src/expr.c \
+ src/func.c \
+ src/hash.c \
+ src/insert.c \
+ src/legacy.c \
+ src/main.c \
+ src/opcodes.c \
+ src/os_unix.c \
+ src/os_win.c \
+ src/pager.c \
+ src/parse.c \
+ src/pragma.c \
+ src/prepare.c \
+ src/printf.c \
+ src/random.c \
+ src/select.c \
+ src/shell.c \
+ src/sqlite.h.in \
+ src/sqliteInt.h \
+ src/table.c \
+ src/tclsqlite.c \
+ src/tokenize.c \
+ src/trigger.c \
+ src/utf.c \
+ src/update.c \
+ src/util.c \
+ src/vacuum.c \
+ src/vdbe.c \
+ src/vdbe.h \
+ src/vdbeapi.c \
+ src/vdbeaux.c \
+ src/vdbefifo.c \
+ src/vdbemem.c \
+ src/vdbeInt.h \
+ src/where.c
+
+OBJECTS = \
+ obj/alter.obj \
+ obj/analyze.obj \
+ obj/attach.obj \
+ obj/auth.obj \
+ obj/btree.obj \
+ obj/build.obj \
+ obj/callback.obj \
+ obj/complete.obj \
+ obj/date.obj \
+ obj/delete.obj \
+ obj/expr.obj \
+ obj/func.obj \
+ obj/hash.obj \
+ obj/insert.obj \
+ obj/legacy.obj \
+ obj/main.obj \
+ obj/opcodes.obj \
+ obj/os_unix.obj \
+ obj/os_win.obj \
+ obj/pager.obj \
+ obj/parse.obj \
+ obj/pragma.obj \
+ obj/prepare.obj \
+ obj/printf.obj \
+ obj/random.obj \
+ obj/select.obj \
+ obj/shell.obj \
+ obj/table.obj \
+ obj/tclsqlite.obj \
+ obj/tokenize.obj \
+ obj/trigger.obj \
+ obj/utf.obj \
+ obj/update.obj \
+ obj/util.obj \
+ obj/vacuum.obj \
+ obj/vdbe.obj \
+ obj/vdbeapi.obj \
+ obj/vdbeaux.obj \
+ obj/vdbefifo.obj \
+ obj/vdbemem.obj \
+ obj/where.obj
+
+FORMS =
+UICDECLS =
+UICIMPLS =
+SRCMOC =
+OBJMOC =
+DIST =
+TARGET = $(KDEDIR)/bin/kexisql3_d.dll
+
+####### Implicit rules
+
+.SUFFIXES: .c .cpp .cc .cxx .C
+
+{moc\}.cpp{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{moc\}.cc{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{moc\}.cxx{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{moc\}.C{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{moc\}.c{obj\}.obj::
+ $(CC) -c $(CFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.cpp{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.cc{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.cxx{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.C{obj\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{.}.c{obj\}.obj::
+ $(CC) -c $(CFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+{src\}.c{obj\}.obj::
+ $(CC) -c $(CFLAGS) $(INCPATH) -Foobj\ @<<
+ $<
+<<
+
+####### Build rules
+
+all: $(TARGET) sqlite
+
+$(TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC)
+ $(LINK) $(LFLAGS) /OUT:$(TARGET) @<<
+ $(OBJECTS) $(OBJMOC) $(LIBS)
+<<
+
+sqlite: src/shell.c $(KDEDIR)/bin/kexisql3_d.lib
+ link $(READLINE_FLAGS) $(LIBPTHREAD) \
+ /OUT:$(KDEDIR)/bin/ksqlite.exe obj/shell.obj $(KDEDIR)/bin/kexisql3_d.lib $(LIBREADLINE)
+
+mocables: $(SRCMOC)
+uicables: $(UICIMPLS) $(UICDECLS)
+
+uiclean:
+mocclean:
+clean:
+ -del obj\*.obj
+ -del sqlite.pdb
+
+distclean: clean
+ -del $(TARGET)
+
+####### Install
+
+install: all
+
+uninstall:
+
diff --git a/kexi/3rdparty/kexisql3/Makefile.msvc.release b/kexi/3rdparty/kexisql3/Makefile.msvc.release
new file mode 100644
index 000000000..a6a9fd73c
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/Makefile.msvc.release
@@ -0,0 +1,233 @@
+#############################################################################
+# Makefile for building: sqlite3
+#############################################################################
+
+####### Compiler, tools and options
+
+CC = cl
+CXX = cl
+LEX = flex
+YACC = byacc
+CFLAGS = -nologo -Zm200 -W3 -MD -Z7 -GX -GR -DTHREADSAFE=1 -D_OFF_T_DEFINED=1 -DNO_TCL=1
+CXXFLAGS = -nologo -Zm200 /GR /GX /GZ /TP -W3 -MD -Z7 -GX -GR -DUNICODE
+LEXFLAGS =
+YACCFLAGS =-d
+INCPATH = -Isrc -I$(QTDIR)\include
+LINK = link
+LFLAGS = /NOLOGO /FORCE:MULTIPLE /SUBSYSTEM:windows /DEF:sqlite3.def /DLL
+VERSION=3.0.7
+LIBS = "kernel32.lib" "user32.lib" "gdi32.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "imm32.lib" "winmm.lib" "wsock32.lib" "winspool.lib"
+IDL = midl
+ZIP = zip -r -9
+COPY_FILE = copy
+COPY_DIR = copy
+DEL_FILE = del
+DEL_DIR = rmdir
+MOVE = move
+CHK_DIR_EXISTS = if not exist
+MKDIR = mkdir
+
+####### Files
+
+HEADERS = \
+ src/btree.h \
+ src/hash.h \
+ src/keywordhash.h \
+ src/opcodes.h \
+ src/os.h \
+ src/os_common.h \
+ src/pager.h \
+ src/parse.h \
+ src/sqlite3.h \
+ src/sqliteInt.h \
+ src/vdbe.h \
+ src/vdbeInt.h
+
+SOURCES = \
+ src/alter.c \
+ src/analyze.c \
+ src/attach.c \
+ src/auth.c \
+ src/btree.c \
+ src/build.c \
+ src/callback.c \
+ src/complete.c \
+ src/date.c \
+ src/delete.c \
+ src/expr.c \
+ src/func.c \
+ src/hash.c \
+ src/insert.c \
+ src/legacy.c \
+ src/main.c \
+ src/opcodes.c \
+ src/os_unix.c \
+ src/os_win.c \
+ src/pager.c \
+ src/parse.c \
+ src/pragma.c \
+ src/prepare.c \
+ src/printf.c \
+ src/random.c \
+ src/select.c \
+ src/shell.c \
+ src/sqlite.h.in \
+ src/sqliteInt.h \
+ src/table.c \
+ src/tclsqlite.c \
+ src/tokenize.c \
+ src/trigger.c \
+ src/utf.c \
+ src/update.c \
+ src/util.c \
+ src/vacuum.c \
+ src/vdbe.c \
+ src/vdbe.h \
+ src/vdbeapi.c \
+ src/vdbeaux.c \
+ src/vdbefifo.c \
+ src/vdbemem.c \
+ src/vdbeInt.h \
+ src/where.c
+
+OBJECTS = \
+ obj_rel/alter.obj \
+ obj_rel/analyze.obj \
+ obj_rel/attach.obj \
+ obj_rel/auth.obj \
+ obj_rel/btree.obj \
+ obj_rel/build.obj \
+ obj_rel/callback.obj \
+ obj_rel/complete.obj \
+ obj_rel/date.obj \
+ obj_rel/delete.obj \
+ obj_rel/expr.obj \
+ obj_rel/func.obj \
+ obj_rel/hash.obj \
+ obj_rel/insert.obj \
+ obj_rel/legacy.obj \
+ obj_rel/main.obj \
+ obj_rel/opcodes.obj \
+ obj_rel/os_unix.obj \
+ obj_rel/os_win.obj \
+ obj_rel/pager.obj \
+ obj_rel/parse.obj \
+ obj_rel/pragma.obj \
+ obj_rel/prepare.obj \
+ obj_rel/printf.obj \
+ obj_rel/random.obj \
+ obj_rel/select.obj \
+ obj_rel/shell.obj \
+ obj_rel/table.obj \
+ obj_rel/tclsqlite.obj \
+ obj_rel/tokenize.obj \
+ obj_rel/trigger.obj \
+ obj_rel/utf.obj \
+ obj_rel/update.obj \
+ obj_rel/util.obj \
+ obj_rel/vacuum.obj \
+ obj_rel/vdbe.obj \
+ obj_rel/vdbeapi.obj \
+ obj_rel/vdbeaux.obj \
+ obj_rel/vdbefifo.obj \
+ obj_rel/vdbemem.obj \
+ obj_rel/where.obj
+
+FORMS =
+UICDECLS =
+UICIMPLS =
+SRCMOC =
+OBJMOC =
+DIST =
+TARGET = $(KDEDIR)/bin/kexisql3.dll
+
+####### Implicit rules
+
+.SUFFIXES: .c .cpp .cc .cxx .C
+
+{moc\}.cpp{obj_rel\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{moc\}.cc{obj_rel\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{moc\}.cxx{obj_rel\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{moc\}.C{obj_rel\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{moc\}.c{obj_rel\}.obj::
+ $(CC) -c $(CFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{.}.cpp{obj_rel\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{.}.cc{obj_rel\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{.}.cxx{obj_rel\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{.}.C{obj_rel\}.obj::
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{.}.c{obj_rel\}.obj::
+ $(CC) -c $(CFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+{src\}.c{obj_rel\}.obj::
+ $(CC) -c $(CFLAGS) $(INCPATH) -Foobj_rel\ @<<
+ $<
+<<
+
+####### Build rules
+
+all: $(TARGET) sqlite
+
+$(TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC)
+ $(LINK) $(LFLAGS) /OUT:$(TARGET) @<<
+ $(OBJECTS) $(OBJMOC) $(LIBS)
+<<
+
+sqlite: src/shell.c $(KDEDIR)/bin/kexisql3.lib
+ link $(READLINE_FLAGS) $(LIBPTHREAD) \
+ /OUT:$(KDEDIR)/bin/ksqlite.exe obj_rel/shell.obj $(KDEDIR)/bin/kexisql3.lib $(LIBREADLINE)
+
+mocables: $(SRCMOC)
+uicables: $(UICIMPLS) $(UICDECLS)
+
+uiclean:
+mocclean:
+clean:
+ -del obj_rel\*.obj
+ -del sqlite.pdb
+
+distclean: clean
+ -del $(TARGET)
+
+####### Install
+
+install: all
+
+uninstall:
+
diff --git a/kexi/3rdparty/kexisql3/README b/kexi/3rdparty/kexisql3/README
new file mode 100644
index 000000000..326f3aa1a
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/README
@@ -0,0 +1,37 @@
+This directory contains source code to
+
+ SQLite: An Embeddable SQL Database Engine
+
+To compile the project, first create a directory in which to place
+the build products. It is recommended, but not required, that the
+build directory be separate from the source directory. Cd into the
+build directory and then from the build directory run the configure
+script found at the root of the source tree. Then run "make".
+
+For example:
+
+ tar xzf sqlite.tar.gz ;# Unpack the source tree into "sqlite"
+ mkdir bld ;# Build will occur in a sibling directory
+ cd bld ;# Change to the build directory
+ ../sqlite/configure ;# Run the configure script
+ make ;# Run the makefile.
+
+The configure script uses autoconf 2.50 and libtool. If the configure
+script does not work out for you, there is a generic makefile named
+"Makefile.linux-gcc" in the top directory of the source tree that you
+can copy and edit to suite your needs. Comments on the generic makefile
+show what changes are needed.
+
+The linux binaries on the website are created using the generic makefile,
+not the configure script. The configure script is unmaintained. (You
+can volunteer to take over maintenance of the configure script, if you want!)
+The windows binaries on the website are created using MinGW32 configured
+as a cross-compiler running under Linux. For details, see the ./publish.sh
+script at the top-level of the source tree.
+
+Contacts:
+
+ http://www.sqlite.org/
+ http://www.hwaci.com/sw/sqlite/
+ http://groups.yahoo.com/group/sqlite/
+ drh@hwaci.com
diff --git a/kexi/3rdparty/kexisql3/mkdll.sh b/kexi/3rdparty/kexisql3/mkdll.sh
new file mode 100755
index 000000000..bad8619f3
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/mkdll.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# This script is used to compile SQLite into a DLL.
+#
+# Two separate DLLs are generated. "sqlite3.dll" is the core
+# library. "tclsqlite3.dll" contains the TCL bindings and is the
+# library that is loaded into TCL in order to run SQLite.
+#
+make target_source
+cd tsrc
+PATH=$PATH:/opt/mingw/bin
+TCLDIR=/home/drh/tcltk/846/win/846win
+TCLSTUBLIB=$TCLDIR/libtcl84stub.a
+OPTS='-DUSE_TCL_STUBS=1 -DNDEBUG=1 -DTHREADSAFE=1'
+CC="i386-mingw32msvc-gcc -O2 $OPTS -I. -I$TCLDIR"
+rm shell.c
+for i in *.c; do
+ CMD="$CC -c $i"
+ echo $CMD
+ $CMD
+done
+echo 'EXPORTS' >tclsqlite3.def
+echo 'Tclsqlite3_Init' >>tclsqlite3.def
+echo 'Sqlite3_Init' >>tclsqlite3.def
+i386-mingw32msvc-dllwrap \
+ --def tclsqlite3.def -v --export-all \
+ --driver-name i386-mingw32msvc-gcc \
+ --dlltool-name i386-mingw32msvc-dlltool \
+ --as i386-mingw32msvc-as \
+ --target i386-mingw32 \
+ -dllname tclsqlite3.dll -lmsvcrt *.o $TCLSTUBLIB
+i386-mingw32msvc-strip tclsqlite3.dll
+rm tclsqlite.o
+i386-mingw32msvc-dllwrap \
+ --def sqlite3.def -v --export-all \
+ --driver-name i386-mingw32msvc-gcc \
+ --dlltool-name i386-mingw32msvc-dlltool \
+ --as i386-mingw32msvc-as \
+ --target i386-mingw32 \
+ -dllname sqlite3.dll -lmsvcrt *.o
+i386-mingw32msvc-strip sqlite3.dll
+cd ..
diff --git a/kexi/3rdparty/kexisql3/mkso.sh b/kexi/3rdparty/kexisql3/mkso.sh
new file mode 100755
index 000000000..9da593bfb
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/mkso.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# This script is used to compile SQLite into a shared library on Linux.
+#
+# Two separate shared libraries are generated. "sqlite3.so" is the core
+# library. "tclsqlite3.so" contains the TCL bindings and is the
+# library that is loaded into TCL in order to run SQLite.
+#
+make target_source
+cd tsrc
+rm shell.c
+TCLDIR=/home/drh/tcltk/846/linux/846linux
+TCLSTUBLIB=$TCLDIR/libtclstub8.4g.a
+OPTS='-DUSE_TCL_STUBS=1 -DNDEBUG=1'
+for i in *.c; do
+ CMD="cc -fPIC $OPTS -O2 -I. -I$TCLDIR -c $i"
+ echo $CMD
+ $CMD
+done
+echo gcc -shared *.o $TCLSTUBLIB -o tclsqlite3.so
+gcc -shared *.o $TCLSTUBLIB -o tclsqlite3.so
+strip tclsqlite3.so
+rm tclsqlite.c tclsqlite.o
+echo gcc -shared *.o -o sqlite3.so
+gcc -shared *.o -o sqlite3.so
+strip sqlite3.so
+cd ..
diff --git a/kexi/3rdparty/kexisql3/publish.sh b/kexi/3rdparty/kexisql3/publish.sh
new file mode 100755
index 000000000..7c45550bb
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/publish.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+#
+# This script is used to compile SQLite and all its documentation and
+# ship everything up to the SQLite website. This script will only work
+# on the system "zadok" at the Hwaci offices. But others might find
+# the script useful as an example.
+#
+
+# Set srcdir to the name of the directory that contains the publish.sh
+# script.
+#
+srcdir=`echo "$0" | sed 's%\(^.*\)/[^/][^/]*$%\1%'`
+
+# Get the makefile.
+#
+cp $srcdir/Makefile.linux-gcc ./Makefile
+chmod +x $srcdir/install-sh
+
+# Get the current version number - needed to help build filenames
+#
+VERS=`cat $srcdir/VERSION`
+VERSW=`sed 's/\./_/g' $srcdir/VERSION`
+
+# Start by building an sqlite shell for linux.
+#
+make clean
+make sqlite3
+strip sqlite3
+mv sqlite3 sqlite3-$VERS.bin
+gzip sqlite3-$VERS.bin
+mv sqlite3-$VERS.bin.gz doc
+
+# Build a source archive useful for windows.
+#
+make target_source
+cd tsrc
+zip ../doc/sqlite-source-$VERSW.zip *
+cd ..
+
+# Build the sqlite.so and tclsqlite.so shared libraries
+# under Linux
+#
+. $srcdir/mkso.sh
+cd tsrc
+mv tclsqlite3.so tclsqlite-$VERS.so
+gzip tclsqlite-$VERS.so
+mv tclsqlite-$VERS.so.gz ../doc
+mv sqlite3.so sqlite-$VERS.so
+gzip sqlite-$VERS.so
+mv sqlite-$VERS.so.gz ../doc
+cd ..
+
+# Build the tclsqlite3.dll and sqlite3.dll shared libraries.
+#
+. $srcdir/mkdll.sh
+cd tsrc
+echo zip ../doc/tclsqlite-$VERSW.zip tclsqlite3.dll
+zip ../doc/tclsqlite-$VERSW.zip tclsqlite3.dll
+echo zip ../doc/sqlitedll-$VERSW.zip sqlite3.dll sqlite3.def
+zip ../doc/sqlitedll-$VERSW.zip sqlite3.dll sqlite3.def
+cd ..
+
+# Build the sqlite.exe executable for windows.
+#
+make target_source
+cd tsrc
+rm tclsqlite.c
+OPTS='-DSTATIC_BUILD=1 -DNDEBUG=1'
+i386-mingw32msvc-gcc -O2 $OPTS -I. -I$TCLDIR *.c -o sqlite3.exe
+zip ../doc/sqlite-$VERSW.zip sqlite3.exe
+cd ..
+
+# Construct a tarball of the source tree
+#
+ORIGIN=`pwd`
+cd $srcdir
+cd ..
+EXCLUDE=`find sqlite -print | grep CVS | sed 's,^, --exclude ,'`
+tar czf $ORIGIN/doc/sqlite-$VERS.tar.gz $EXCLUDE sqlite
+cd $ORIGIN
+
+#
+# Build RPMS (binary) and Source RPM
+#
+
+# Make sure we are properly setup to build RPMs
+#
+echo "%HOME %{expand:%%(cd; pwd)}" > $HOME/.rpmmacros
+echo "%_topdir %{HOME}/rpm" >> $HOME/.rpmmacros
+mkdir $HOME/rpm
+mkdir $HOME/rpm/BUILD
+mkdir $HOME/rpm/SOURCES
+mkdir $HOME/rpm/RPMS
+mkdir $HOME/rpm/SRPMS
+mkdir $HOME/rpm/SPECS
+
+# create the spec file from the template
+sed s/SQLITE_VERSION/$VERS/g $srcdir/spec.template > $HOME/rpm/SPECS/sqlite.spec
+
+# copy the source tarball to the rpm directory
+cp doc/sqlite-$VERS.tar.gz $HOME/rpm/SOURCES/.
+
+# build all the rpms
+rpm -ba $HOME/rpm/SPECS/sqlite.spec >& rpm-$vers.log
+
+# copy the RPMs into the build directory.
+mv $HOME/rpm/RPMS/i386/sqlite*-$vers*.rpm doc
+mv $HOME/rpm/SRPMS/sqlite-$vers*.rpm doc
+
+# Build the website
+#
+#cp $srcdir/../historical/* doc
+make doc
diff --git a/kexi/3rdparty/kexisql3/spec.template b/kexi/3rdparty/kexisql3/spec.template
new file mode 100644
index 000000000..24c5eaebb
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/spec.template
@@ -0,0 +1,62 @@
+%define name sqlite
+%define version SQLITE_VERSION
+%define release 1
+
+Name: %{name}
+Summary: SQLite is a C library that implements an embeddable SQL database engine
+Version: %{version}
+Release: %{release}
+Source: %{name}-%{version}.tar.gz
+Group: System/Libraries
+URL: http://www.hwaci.com/sw/sqlite/
+License: Public Domain
+BuildRoot: %{_tmppath}/%{name}-%{version}-root
+
+%description
+SQLite is a C library that implements an embeddable SQL database engine.
+Programs that link with the SQLite library can have SQL database access
+without running a separate RDBMS process. The distribution comes with a
+standalone command-line access program (sqlite) that can be used to
+administer an SQLite database and which serves as an example of how to
+use the SQLite library.
+
+%package -n %{name}-devel
+Summary: Header files and libraries for developing apps which will use sqlite
+Group: Development/C
+Requires: %{name} = %{version}-%{release}
+
+%description -n %{name}-devel
+The sqlite-devel package contains the header files and libraries needed
+to develop programs that use the sqlite database library.
+
+%prep
+%setup -q -n %{name}
+
+%build
+CFLAGS="%optflags -DNDEBUG=1" CXXFLAGS="%optflags -DNDEBUG=1" ./configure --prefix=%{_prefix}
+
+make
+make doc
+
+%install
+install -d $RPM_BUILD_ROOT/%{_prefix}
+install -d $RPM_BUILD_ROOT/%{_prefix}/bin
+install -d $RPM_BUILD_ROOT/%{_prefix}/include
+install -d $RPM_BUILD_ROOT/%{_prefix}/lib
+make install prefix=$RPM_BUILD_ROOT/%{_prefix}
+
+%clean
+rm -fr $RPM_BUILD_ROOT
+
+%files
+%defattr(-, root, root)
+%{_libdir}/*.so*
+%{_bindir}/*
+
+%files -n %{name}-devel
+%defattr(-, root, root)
+%{_libdir}/pkgconfig/sqlite3.pc
+%{_libdir}/*.a
+%{_libdir}/*.la
+%{_includedir}/*
+%doc doc/*
diff --git a/kexi/3rdparty/kexisql3/sqlite3.1 b/kexi/3rdparty/kexisql3/sqlite3.1
new file mode 100755
index 000000000..785995bf6
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/sqlite3.1
@@ -0,0 +1,229 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH SQLITE3 1 "Mon Apr 15 23:49:17 2002"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+.B sqlite3
+\- A command line interface for SQLite version 3
+
+.SH SYNOPSIS
+.B sqlite3
+.RI [ options ]
+.RI [ databasefile ]
+.RI [ SQL ]
+
+.SH SUMMARY
+.PP
+.B sqlite3
+is a terminal-based front-end to the SQLite library that can evaluate
+queries interactively and display the results in multiple formats.
+.B sqlite3
+can also be used within shell scripts and other applications to provide
+batch processing features.
+
+.SH DESCRIPTION
+To start a
+.B sqlite3
+interactive session, invoke the
+.B sqlite3
+command and optionally provide the name of a database file. If the
+database file does not exist, it will be created. If the database file
+does exist, it will be opened.
+
+For example, to create a new database file named "mydata.db", create
+a table named "memos" and insert a couple of records into that table:
+.sp
+$
+.B sqlite3 mydata.db
+.br
+SQLite version 3.1.3
+.br
+Enter ".help" for instructions
+.br
+sqlite>
+.B create table memos(text, priority INTEGER);
+.br
+sqlite>
+.B insert into memos values('deliver project description', 10);
+.br
+sqlite>
+.B insert into memos values('lunch with Christine', 100);
+.br
+sqlite>
+.B select * from memos;
+.br
+deliver project description|10
+.br
+lunch with Christine|100
+.br
+sqlite>
+.sp
+
+If no database name is supplied, the ATTACH sql command can be used
+to attach to existing or create new database files. ATTACH can also
+be used to attach to multiple databases within the same interactive
+session. This is useful for migrating data between databases,
+possibly changing the schema along the way.
+
+Optionally, a SQL statement or set of SQL statements can be supplied as
+a single argument. Multiple statements should be separated by
+semi-colons.
+
+For example:
+.sp
+$
+.B sqlite3 -line mydata.db 'select * from memos where priority > 20;'
+.br
+ text = lunch with Christine
+.br
+priority = 100
+.br
+.sp
+
+.SS SQLITE META-COMMANDS
+.PP
+The interactive interpreter offers a set of meta-commands that can be
+used to control the output format, examine the currently attached
+database files, or perform administrative operations upon the
+attached databases (such as rebuilding indices). Meta-commands are
+always prefixed with a dot (.).
+
+A list of available meta-commands can be viewed at any time by issuing
+the '.help' command. For example:
+.sp
+sqlite>
+.B .help
+.nf
+.cc |
+.databases List names and files of attached databases
+.dump ?TABLE? ... Dump the database in an SQL text format
+.echo ON|OFF Turn command echo on or off
+.exit Exit this program
+.explain ON|OFF Turn output mode suitable for EXPLAIN on or off.
+.header(s) ON|OFF Turn display of headers on or off
+.help Show this message
+.import FILE TABLE Import data from FILE into TABLE
+.indices TABLE Show names of all indices on TABLE
+.mode MODE ?TABLE? Set output mode where MODE is one of:
+ csv Comma-separated values
+ column Left-aligned columns. (See .width)
+ html HTML <table> code
+ insert SQL insert statements for TABLE
+ line One value per line
+ list Values delimited by .separator string
+ tabs Tab-separated values
+ tcl TCL list elements
+.nullvalue STRING Print STRING in place of NULL values
+.output FILENAME Send output to FILENAME
+.output stdout Send output to the screen
+.prompt MAIN CONTINUE Replace the standard prompts
+.quit Exit this program
+.read FILENAME Execute SQL in FILENAME
+.schema ?TABLE? Show the CREATE statements
+.separator STRING Change separator used by output mode and .import
+.show Show the current values for various settings
+.tables ?PATTERN? List names of tables matching a LIKE pattern
+.timeout MS Try opening locked tables for MS milliseconds
+.width NUM NUM ... Set column widths for "column" mode
+sqlite>
+|cc .
+.sp
+.fi
+
+.SH OPTIONS
+.B sqlite3
+has the following options:
+.TP
+.BI \-init\ file
+Read and execute commands from
+.I file
+, which can contain a mix of SQL statements and meta-commands.
+.TP
+.B \-echo
+Print commands before execution.
+.TP
+.B \-[no]header
+Turn headers on or off.
+.TP
+.B \-column
+Query results will be displayed in a table like form, using
+whitespace characters to separate the columns and align the
+output.
+.TP
+.B \-html
+Query results will be output as simple HTML tables.
+.TP
+.B \-line
+Query results will be displayed with one value per line, rows
+separated by a blank line. Designed to be easily parsed by
+scripts or other programs
+.TP
+.B \-list
+Query results will be displayed with the separator (|, by default)
+character between each field value. The default.
+.TP
+.BI \-separator\ separator
+Set output field separator. Default is '|'.
+.TP
+.BI \-nullvalue\ string
+Set string used to represent NULL values. Default is ''
+(empty string).
+.TP
+.B \-version
+Show SQLite version.
+.TP
+.B \-help
+Show help on options and exit.
+
+
+.SH INIT FILE
+.B sqlite3
+reads an initialization file to set the configuration of the
+interactive environment. Throughout initialization, any previously
+specified setting can be overridden. The sequence of initialization is
+as follows:
+
+o The default configuration is established as follows:
+
+.sp
+.nf
+.cc |
+mode = LIST
+separator = "|"
+main prompt = "sqlite> "
+continue prompt = " ...> "
+|cc .
+.sp
+.fi
+
+o If the file
+.B ~/.sqliterc
+exists, it is processed first.
+can be found in the user's home directory, it is
+read and processed. It should generally only contain meta-commands.
+
+o If the -init option is present, the specified file is processed.
+
+o All other command line options are processed.
+
+.SH SEE ALSO
+http://www.sqlite.org/
+.br
+The sqlite-doc package
+.SH AUTHOR
+This manual page was originally written by Andreas Rottmann
+<rotty@debian.org>, for the Debian GNU/Linux system (but may be used
+by others). It was subsequently revised by Bill Bumgarner <bbum@mac.com>.
diff --git a/kexi/3rdparty/kexisql3/sqlite3.def b/kexi/3rdparty/kexisql3/sqlite3.def
new file mode 100644
index 000000000..4d2ea8fff
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/sqlite3.def
@@ -0,0 +1,99 @@
+EXPORTS
+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_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_decltype
+sqlite3_column_decltype16
+sqlite3_column_double
+sqlite3_column_int
+sqlite3_column_int64
+sqlite3_column_name
+sqlite3_column_name16
+sqlite3_column_text
+sqlite3_column_text16
+sqlite3_column_type
+sqlite3_commit_hook
+sqlite3_complete
+sqlite3_complete16
+sqlite3_create_collation
+sqlite3_create_collation16
+sqlite3_create_function
+sqlite3_create_function16
+sqlite3_data_count
+sqlite3_db_handle
+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
+sqlite3_global_recover
+sqlite3_interrupt
+sqlite3_last_insert_rowid
+sqlite3_libversion
+sqlite3_libversion_number
+sqlite3_mprintf
+sqlite3_open
+sqlite3_open16
+sqlite3_prepare
+sqlite3_prepare16
+sqlite3_progress_handler
+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_set_authorizer
+sqlite3_set_auxdata
+sqlite3_snprintf
+sqlite3_step
+sqlite3_total_changes
+sqlite3_trace
+sqlite3_transfer_bindings
+sqlite3_user_data
+sqlite3_value_blob
+sqlite3_value_bytes
+sqlite3_value_bytes16
+sqlite3_value_double
+sqlite3_value_int
+sqlite3_value_int64
+sqlite3_value_text
+sqlite3_value_text16
+sqlite3_value_text16be
+sqlite3_value_text16le
+sqlite3_value_type
+sqlite3_vmprintf
+sqlite3_is_readonly
+sqlite_set_verbose_vacuum
diff --git a/kexi/3rdparty/kexisql3/src/Makefile.am b/kexi/3rdparty/kexisql3/src/Makefile.am
new file mode 100644
index 000000000..e989a2a84
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/Makefile.am
@@ -0,0 +1,61 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexisql3.la
+
+bin_PROGRAMS = ksqlite
+
+bin_SCRIPTS = ./ksqlite2to3
+
+INCLUDES = $(all_includes)
+
+libkexisql3_la_SOURCES = \
+ alter.c \
+ analyze.c \
+ attach.c \
+ auth.c \
+ btree.c \
+ btree.h \
+ build.c \
+ callback.c \
+ complete.c \
+ date.c \
+ delete.c \
+ expr.c \
+ func.c \
+ hash.c \
+ insert.c \
+ legacy.c \
+ main.c \
+ opcodes.c \
+ os_unix.c \
+ pager.c \
+ parse.c \
+ pragma.c \
+ prepare.c \
+ printf.c \
+ random.c \
+ select.c \
+ table.c \
+ tokenize.c \
+ trigger.c \
+ utf.c \
+ update.c \
+ util.c \
+ vacuum.c \
+ vdbe.c \
+ vdbeapi.c \
+ vdbeaux.c \
+ vdbefifo.c \
+ vdbemem.c \
+ where.c
+
+#tclsqlite.c
+
+AM_CFLAGS = -DNO_TCL=1
+
+ksqlite_SOURCES = shell.c
+ksqlite_LDADD = libkexisql3.la $(LIBREADLINE)
+
+libkexisql3_la_LIBADD =
+libkexisql3_la_LDFLAGS = -version-info 3:2 $(all_libraries) --no-undefined
+
diff --git a/kexi/3rdparty/kexisql3/src/alter.c b/kexi/3rdparty/kexisql3/src/alter.c
new file mode 100644
index 000000000..eba01e296
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/alter.c
@@ -0,0 +1,557 @@
+/*
+** 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.8 2005/08/19 19:14:13 drh Exp $
+*/
+#include "sqliteInt.h"
+#include <ctype.h>
+
+/*
+** 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 second
+** 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;
+ char const *zCsr = zSql;
+ int len = 0;
+ char *zRet;
+
+ /* The principle used to locate the table name in the CREATE TABLE
+ ** statement is that the table name is the first token that is immediatedly
+ ** followed by a left parenthesis - TK_LP.
+ */
+ if( zSql ){
+ do {
+ /* 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 it's 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 );
+ } while( token!=TK_LP );
+
+ zRet = sqlite3MPrintf("%.*s%Q%s", tname.z - zSql, zSql,
+ zTableName, tname.z+tname.n);
+ sqlite3_result_text(context, zRet, -1, sqlite3FreeX);
+ }
+}
+
+#ifndef SQLITE_OMIT_TRIGGER
+/* This function is used by SQL generated to implement the ALTER TABLE
+** 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 second 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;
+ char const *zCsr = zSql;
+ int len = 0;
+ char *zRet;
+
+ /* 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 {
+ /* 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 it's 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("%.*s%Q%s", tname.z - zSql, zSql,
+ zTableName, tname.z+tname.n);
+ sqlite3_result_text(context, zRet, -1, sqlite3FreeX);
+ }
+}
+#endif /* !SQLITE_OMIT_TRIGGER */
+
+/*
+** Register built-in functions used to help implement ALTER TABLE
+*/
+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++){
+ sqlite3_create_function(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;
+ if( pTab->iDb!=1 ){
+ for( pTrig=pTab->pTrigger; pTrig; pTrig=pTrig->pNext ){
+ if( pTrig->iDb==1 ){
+ if( !zWhere ){
+ zWhere = sqlite3MPrintf("name=%Q", pTrig->name);
+ }else{
+ tmp = zWhere;
+ zWhere = sqlite3MPrintf("%s OR name=%Q", zWhere, pTrig->name);
+ sqliteFree(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;
+#ifndef SQLITE_OMIT_TRIGGER
+ Trigger *pTrig;
+#endif
+
+ v = sqlite3GetVdbe(pParse);
+ if( !v ) return;
+ iDb = pTab->iDb;
+
+#ifndef SQLITE_OMIT_TRIGGER
+ /* Drop any table triggers from the internal schema. */
+ for(pTrig=pTab->pTrigger; pTrig; pTrig=pTrig->pNext){
+ assert( pTrig->iDb==iDb || pTrig->iDb==1 );
+ sqlite3VdbeOp3(v, OP_DropTrigger, pTrig->iDb, 0, pTrig->name, 0);
+ }
+#endif
+
+ /* Drop the table and index from the internal schema */
+ sqlite3VdbeOp3(v, OP_DropTable, iDb, 0, pTab->zName, 0);
+
+ /* Reload the table, index and permanent trigger schemas. */
+ zWhere = sqlite3MPrintf("tbl_name=%Q", zName);
+ if( !zWhere ) return;
+ sqlite3VdbeOp3(v, OP_ParseSchema, iDb, 0, zWhere, P3_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)) ){
+ sqlite3VdbeOp3(v, OP_ParseSchema, 1, 0, zWhere, P3_DYNAMIC);
+ }
+#endif
+}
+
+/*
+** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy"
+** command.
+*/
+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 */
+ Vdbe *v;
+#ifndef SQLITE_OMIT_TRIGGER
+ char *zWhere = 0; /* Where clause to locate temp triggers */
+#endif
+
+ if( sqlite3_malloc_failed ) goto exit_rename_table;
+ assert( pSrc->nSrc==1 );
+
+ pTab = sqlite3LocateTable(pParse, pSrc->a[0].zName, pSrc->a[0].zDatabase);
+ if( !pTab ) goto exit_rename_table;
+ iDb = pTab->iDb;
+ zDb = db->aDb[iDb].zName;
+
+ /* Get a NULL terminated version of the new table name. */
+ zName = sqlite3NameFromToken(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_AUTHORIZATION
+ /* Invoke the authorization callback. */
+ if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
+ goto exit_rename_table;
+ }
+#endif
+
+ /* Begin a transaction and code the VerifyCookie for database iDb.
+ ** Then modify the schema cookie (since the ALTER TABLE modifies the
+ ** schema).
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ){
+ goto exit_rename_table;
+ }
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3ChangeCookie(db, v, iDb);
+
+ /* 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,10) "
+ "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, strlen(pTab->zName), pTab->zName
+ );
+
+#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 %Q.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)) ){
+ sqlite3NestedParse(pParse,
+ "UPDATE sqlite_temp_master SET "
+ "sql = sqlite_rename_trigger(sql, %Q), "
+ "tbl_name = %Q "
+ "WHERE %s;", zName, zName, zWhere);
+ sqliteFree(zWhere);
+ }
+#endif
+
+ /* Drop and reload the internal table schema. */
+ reloadTableSchema(pParse, pTab, zName);
+
+exit_rename_table:
+ sqlite3SrcListDelete(pSrc);
+ sqliteFree(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.
+*/
+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 */
+ Vdbe *v;
+
+ if( pParse->nErr ) return;
+ pNew = pParse->pNewTable;
+ assert( pNew );
+
+ iDb = pNew->iDb;
+ zDb = pParse->db->aDb[iDb].zName;
+ zTab = pNew->zName;
+ pCol = &pNew->aCol[pNew->nCol-1];
+ pDflt = pCol->pDflt;
+ pTab = sqlite3FindTable(pParse->db, zTab, zDb);
+ assert( pTab );
+
+ /* 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(pDflt, SQLITE_UTF8, SQLITE_AFF_NONE, &pVal) ){
+ /* malloc() has failed */
+ return;
+ }
+ if( !pVal ){
+ sqlite3ErrorMsg(pParse, "Cannot add a column with non-constant default");
+ return;
+ }
+ sqlite3ValueFree(pVal);
+ }
+
+ /* Modify the CREATE TABLE statement. */
+ zCol = sqliteStrNDup(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 %Q.%s SET "
+ "sql = substr(sql,1,%d) || ', ' || %Q || substr(sql,%d,length(sql)) "
+ "WHERE type = 'table' AND name = %Q",
+ zDb, SCHEMA_TABLE(iDb), pNew->addColOffset, zCol, pNew->addColOffset+1,
+ zTab
+ );
+ sqliteFree(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.
+ */
+ if( (v=sqlite3GetVdbe(pParse)) ){
+ int f = (pDflt?3:2);
+
+ /* Only set the file format to $f if it is currently less than $f. */
+ sqlite3VdbeAddOp(v, OP_ReadCookie, iDb, 1);
+ sqlite3VdbeAddOp(v, OP_Integer, f, 0);
+ sqlite3VdbeAddOp(v, OP_Ge, 0, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeAddOp(v, OP_Integer, f, 0);
+ sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 1);
+ }
+
+ /* 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.
+*/
+void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){
+ Table *pNew;
+ Table *pTab;
+ Vdbe *v;
+ int iDb;
+ int i;
+ int nAlloc;
+
+
+ /* Look up the table being altered. */
+ assert( pParse->pNewTable==0 );
+ if( sqlite3_malloc_failed ) goto exit_begin_add_column;
+ pTab = sqlite3LocateTable(pParse, pSrc->a[0].zName, pSrc->a[0].zDatabase);
+ if( !pTab ) goto exit_begin_add_column;
+
+ /* 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 = pTab->iDb;
+
+ /* Put a copy of the Table struct in Parse.pNewTable for the
+ ** sqlite3AddColumn() function and friends to modify.
+ */
+ pNew = (Table *)sqliteMalloc(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 *)sqliteMalloc(sizeof(Column)*nAlloc);
+ pNew->zName = sqliteStrDup(pTab->zName);
+ if( !pNew->aCol || !pNew->zName ){
+ 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 = sqliteStrDup(pCol->zName);
+ pCol->zType = 0;
+ pCol->pDflt = 0;
+ }
+ pNew->iDb = iDb;
+ 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->db, v, iDb);
+
+exit_begin_add_column:
+ sqlite3SrcListDelete(pSrc);
+ return;
+}
+#endif /* SQLITE_ALTER_TABLE */
diff --git a/kexi/3rdparty/kexisql3/src/analyze.c b/kexi/3rdparty/kexisql3/src/analyze.c
new file mode 100644
index 000000000..67d80d534
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/analyze.c
@@ -0,0 +1,386 @@
+/*
+** 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.9 2005/09/20 17:42:23 drh Exp $
+*/
+#ifndef SQLITE_OMIT_ANALYZE
+#include "sqliteInt.h"
+
+/*
+** 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;
+ Table *pStat;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+
+ 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 on the top of the stack. 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 = 0; /* 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;
+ sqlite3VdbeAddOp(v, OP_Clear, pStat->tnum, iDb);
+ }
+
+ /* Open the sqlite_stat1 table for writing.
+ */
+ sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
+ sqlite3VdbeAddOp(v, OP_OpenWrite, iStatCur, iRootPage);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, iStatCur, 3);
+}
+
+/*
+** 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 */
+
+ v = sqlite3GetVdbe(pParse);
+ if( pTab==0 || pTab->pIndex==0 ){
+ /* Do no analysis for tables that have no indices */
+ return;
+ }
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0,
+ pParse->db->aDb[pTab->iDb].zName ) ){
+ return;
+ }
+#endif
+
+ iIdxCur = pParse->nTab;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ /* Open a cursor to the index to be analyzed
+ */
+ sqlite3VdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ VdbeComment((v, "# %s", pIdx->zName));
+ sqlite3VdbeOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum,
+ (char*)&pIdx->keyInfo, P3_KEYINFO);
+ nCol = pIdx->nColumn;
+ if( iMem+nCol*2>=pParse->nMem ){
+ pParse->nMem = iMem+nCol*2+1;
+ }
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, iIdxCur, nCol+1);
+
+ /* 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++){
+ sqlite3VdbeAddOp(v, OP_MemInt, 0, iMem+i);
+ }
+ for(i=0; i<nCol; i++){
+ sqlite3VdbeAddOp(v, OP_MemNull, iMem+nCol+i+1, 0);
+ }
+
+ /* Do the analysis.
+ */
+ endOfLoop = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp(v, OP_Rewind, iIdxCur, endOfLoop);
+ topOfLoop = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp(v, OP_MemIncr, iMem, 0);
+ for(i=0; i<nCol; i++){
+ sqlite3VdbeAddOp(v, OP_Column, iIdxCur, i);
+ sqlite3VdbeAddOp(v, OP_MemLoad, iMem+nCol+i+1, 0);
+ sqlite3VdbeAddOp(v, OP_Ne, 0x100, 0);
+ }
+ sqlite3VdbeAddOp(v, OP_Goto, 0, endOfLoop);
+ for(i=0; i<nCol; i++){
+ addr = sqlite3VdbeAddOp(v, OP_MemIncr, iMem+i+1, 0);
+ sqlite3VdbeChangeP2(v, topOfLoop + 3*i + 3, addr);
+ sqlite3VdbeAddOp(v, OP_Column, iIdxCur, i);
+ sqlite3VdbeAddOp(v, OP_MemStore, iMem+nCol+i+1, 1);
+ }
+ sqlite3VdbeResolveLabel(v, endOfLoop);
+ sqlite3VdbeAddOp(v, OP_Next, iIdxCur, topOfLoop);
+ sqlite3VdbeAddOp(v, OP_Close, iIdxCur, 0);
+
+ /* Store the results.
+ **
+ ** The result is a single row of the sqlite_stmt1 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.
+ */
+ sqlite3VdbeAddOp(v, OP_MemLoad, iMem, 0);
+ addr = sqlite3VdbeAddOp(v, OP_IfNot, 0, 0);
+ sqlite3VdbeAddOp(v, OP_NewRowid, iStatCur, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->zName, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pIdx->zName, 0);
+ sqlite3VdbeAddOp(v, OP_MemLoad, iMem, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, " ", 0);
+ for(i=0; i<nCol; i++){
+ sqlite3VdbeAddOp(v, OP_MemLoad, iMem, 0);
+ sqlite3VdbeAddOp(v, OP_MemLoad, iMem+i+1, 0);
+ sqlite3VdbeAddOp(v, OP_Add, 0, 0);
+ sqlite3VdbeAddOp(v, OP_AddImm, -1, 0);
+ sqlite3VdbeAddOp(v, OP_MemLoad, iMem+i+1, 0);
+ sqlite3VdbeAddOp(v, OP_Divide, 0, 0);
+ if( i==nCol-1 ){
+ sqlite3VdbeAddOp(v, OP_Concat, nCol*2-1, 0);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Dup, 1, 0);
+ }
+ }
+ sqlite3VdbeOp3(v, OP_MakeRecord, 3, 0, "ttt", 0);
+ sqlite3VdbeAddOp(v, OP_Insert, iStatCur, 0);
+ 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);
+ sqlite3VdbeAddOp(v, OP_LoadAnalysis, iDb, 0);
+}
+
+/*
+** Generate code that will do an analysis of an entire database
+*/
+static void analyzeDatabase(Parse *pParse, int iDb){
+ sqlite3 *db = pParse->db;
+ HashElem *k;
+ int iStatCur;
+ int iMem;
+
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ iStatCur = pParse->nTab++;
+ openStatTable(pParse, iDb, iStatCur, 0);
+ iMem = pParse->nMem;
+ for(k=sqliteHashFirst(&db->aDb[iDb].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 );
+ iDb = pTab->iDb;
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ iStatCur = pParse->nTab++;
+ openStatTable(pParse, iDb, iStatCur, pTab->zName);
+ analyzeOneTable(pParse, pTab, iStatCur, pParse->nMem);
+ 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.
+*/
+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. */
+ 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(pName1);
+ pTab = sqlite3LocateTable(pParse, z, 0);
+ sqliteFree(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(pTableName);
+ pTab = sqlite3LocateTable(pParse, z, zDb);
+ sqliteFree(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.
+*/
+void sqlite3AnalysisLoad(sqlite3 *db, int iDb){
+ analysisInfo sInfo;
+ HashElem *i;
+ char *zSql;
+
+ /* Clear any prior statistics */
+ for(i=sqliteHashFirst(&db->aDb[iDb].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;
+ }
+
+
+ /* Load new statistics out of the sqlite_stat1 table */
+ zSql = sqlite3MPrintf("SELECT idx, stat FROM %Q.sqlite_stat1",
+ sInfo.zDatabase);
+ sqlite3SafetyOff(db);
+ sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0);
+ sqlite3SafetyOn(db);
+ sqliteFree(zSql);
+}
+
+
+#endif /* SQLITE_OMIT_ANALYZE */
diff --git a/kexi/3rdparty/kexisql3/src/attach.c b/kexi/3rdparty/kexisql3/src/attach.c
new file mode 100644
index 000000000..42d579887
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/attach.c
@@ -0,0 +1,352 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#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 sqlite3Attach(
+ Parse *pParse, /* The parser context */
+ Token *pFilename, /* Name of database file */
+ Token *pDbname, /* Name of the database to use internally */
+ int keyType, /* 0: no key. 1: TEXT, 2: BLOB */
+ Token *pKey /* Text of the key for keytype 1 and 2 */
+){
+ Db *aNew;
+ int rc, i;
+ char *zFile = 0;
+ char *zName = 0;
+ sqlite3 *db;
+ Vdbe *v;
+
+ v = sqlite3GetVdbe(pParse);
+ if( !v ) return;
+ sqlite3VdbeAddOp(v, OP_Expire, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Halt, 0, 0);
+ if( pParse->explain ) return;
+ db = pParse->db;
+ if( db->nDb>=MAX_ATTACHED+2 ){
+ sqlite3ErrorMsg(pParse, "too many attached databases - max %d",
+ MAX_ATTACHED);
+ pParse->rc = SQLITE_ERROR;
+ return;
+ }
+
+ if( !db->autoCommit ){
+ sqlite3ErrorMsg(pParse, "cannot ATTACH database within transaction");
+ pParse->rc = SQLITE_ERROR;
+ return;
+ }
+
+ zFile = sqlite3NameFromToken(pFilename);
+ if( zFile==0 ){
+ goto attach_end;
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( sqlite3AuthCheck(pParse, SQLITE_ATTACH, zFile, 0, 0)!=SQLITE_OK ){
+ goto attach_end;
+ }
+#endif /* SQLITE_OMIT_AUTHORIZATION */
+
+ zName = sqlite3NameFromToken(pDbname);
+ if( zName==0 ){
+ goto attach_end;
+ }
+ for(i=0; i<db->nDb; i++){
+ char *z = db->aDb[i].zName;
+ if( z && sqlite3StrICmp(z, zName)==0 ){
+ sqlite3ErrorMsg(pParse, "database %s is already in use", zName);
+ pParse->rc = SQLITE_ERROR;
+ goto attach_end;
+ }
+ }
+
+ if( db->aDb==db->aDbStatic ){
+ aNew = sqliteMalloc( sizeof(db->aDb[0])*3 );
+ if( aNew==0 ){
+ goto attach_end;
+ }
+ memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2);
+ }else{
+ aNew = sqliteRealloc(db->aDb, sizeof(db->aDb[0])*(db->nDb+1) );
+ if( aNew==0 ){
+ goto attach_end;
+ }
+ }
+ db->aDb = aNew;
+ aNew = &db->aDb[db->nDb++];
+ memset(aNew, 0, sizeof(*aNew));
+ sqlite3HashInit(&aNew->tblHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&aNew->idxHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&aNew->trigHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&aNew->aFKey, SQLITE_HASH_STRING, 1);
+ aNew->zName = zName;
+ zName = 0;
+ aNew->safety_level = 3;
+ rc = sqlite3BtreeFactory(db, zFile, 0, MAX_PAGES, &aNew->pBt, 0 /*!exclusive*/, 1/*allowReadonly*/);
+ if( rc ){
+ sqlite3ErrorMsg(pParse, "unable to open database: %s", zFile);
+ }
+#if SQLITE_HAS_CODEC
+ {
+ extern int sqlite3CodecAttach(sqlite3*, int, void*, int);
+ char *zKey;
+ int nKey;
+ if( keyType==0 ){
+ /* No key specified. Use the key from the main database */
+ extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*);
+ sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey);
+ }else if( keyType==1 ){
+ /* Key specified as text */
+ zKey = sqlite3NameFromToken(pKey);
+ nKey = strlen(zKey);
+ }else{
+ /* Key specified as a BLOB */
+ char *zTemp;
+ assert( keyType==2 );
+ pKey->z++;
+ pKey->n--;
+ zTemp = sqlite3NameFromToken(pKey);
+ zKey = sqlite3HexToBlob(zTemp);
+ sqliteFree(zTemp);
+ }
+ sqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
+ if( keyType ){
+ sqliteFree(zKey);
+ }
+ }
+#endif
+ db->flags &= ~SQLITE_Initialized;
+ if( pParse->nErr==0 && rc==SQLITE_OK ){
+ rc = sqlite3ReadSchema(pParse);
+ }
+ if( rc ){
+ int i = db->nDb - 1;
+ assert( i>=2 );
+ if( db->aDb[i].pBt ){
+ sqlite3BtreeClose(db->aDb[i].pBt);
+ db->aDb[i].pBt = 0;
+ }
+ sqlite3ResetInternalSchema(db, 0);
+ assert( pParse->nErr>0 ); /* Always set by sqlite3ReadSchema() */
+ if( pParse->rc==SQLITE_OK ){
+ pParse->rc = SQLITE_ERROR;
+ }
+ }
+
+attach_end:
+ sqliteFree(zFile);
+ sqliteFree(zName);
+}
+
+/*
+** 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 sqlite3Detach(Parse *pParse, Token *pDbname){
+ int i;
+ sqlite3 *db;
+ Vdbe *v;
+ Db *pDb = 0;
+ char *zName;
+
+ v = sqlite3GetVdbe(pParse);
+ if( !v ) return;
+ sqlite3VdbeAddOp(v, OP_Expire, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Halt, 0, 0);
+ if( pParse->explain ) return;
+ db = pParse->db;
+ zName = sqlite3NameFromToken(pDbname);
+ if( zName==0 ) return;
+ 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 ){
+ sqlite3ErrorMsg(pParse, "no such database: %z", zName);
+ return;
+ }
+ if( i<2 ){
+ sqlite3ErrorMsg(pParse, "cannot detach database %z", zName);
+ return;
+ }
+ sqliteFree(zName);
+ if( !db->autoCommit ){
+ sqlite3ErrorMsg(pParse, "cannot DETACH database within transaction");
+ pParse->rc = SQLITE_ERROR;
+ return;
+ }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( sqlite3AuthCheck(pParse,SQLITE_DETACH,db->aDb[i].zName,0,0)!=SQLITE_OK ){
+ return;
+ }
+#endif /* SQLITE_OMIT_AUTHORIZATION */
+ sqlite3BtreeClose(pDb->pBt);
+ pDb->pBt = 0;
+ sqlite3ResetInternalSchema(db, 0);
+}
+
+/*
+** 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 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.
+*/
+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 = sqliteStrDup(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)
+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;
+}
+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;
+}
+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
+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
diff --git a/kexi/3rdparty/kexisql3/src/auth.c b/kexi/3rdparty/kexisql3/src/auth.c
new file mode 100644
index 000000000..e432acb52
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/auth.c
@@ -0,0 +1,225 @@
+/*
+** 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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#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_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.
+*/
+int sqlite3_set_authorizer(
+ sqlite3 *db,
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+ void *pArg
+){
+ db->xAuth = xAuth;
+ db->pAuthArg = pArg;
+ sqlite3ExpirePreparedStatements(db);
+ 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.
+*/
+void sqlite3AuthRead(
+ Parse *pParse, /* The parser context */
+ Expr *pExpr, /* The expression to check authorization on */
+ SrcList *pTabList /* All table that pExpr might refer to */
+){
+ sqlite3 *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;
+ if( pExpr->op==TK_AS ) return;
+ assert( pExpr->op==TK_COLUMN );
+ 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;
+ }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 ){
+ 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.
+*/
+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. */
+ if( db->init.busy ){
+ 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.
+*/
+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
+*/
+void sqlite3AuthContextPop(AuthContext *pContext){
+ if( pContext->pParse ){
+ pContext->pParse->zAuthContext = pContext->zAuthContext;
+ pContext->pParse = 0;
+ }
+}
+
+#endif /* SQLITE_OMIT_AUTHORIZATION */
diff --git a/kexi/3rdparty/kexisql3/src/btree.c b/kexi/3rdparty/kexisql3/src/btree.c
new file mode 100644
index 000000000..472d63a60
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/btree.c
@@ -0,0 +1,5841 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+**
+** 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". 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+1) 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 more
+** than once within the same second. This counter, together with the
+** modification time of the file, 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 area 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+1) 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 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
+*/
+#include "sqliteInt.h"
+#include "pager.h"
+#include "btree.h"
+#include "os.h"
+#include <assert.h>
+
+/* 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 3 bytes. Such small cells will be
+** exceedingly rare, but they are possible.
+*/
+#define MX_CELL(pBt) ((pBt->pageSize-8)/3)
+
+/* Forward declarations */
+typedef struct MemPage MemPage;
+
+/*
+** 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
+static const char zMagicHeader[] = SQLITE_FILE_HEADER;
+
+/*
+** Page type flags. An ORed combination of these flags appear as the
+** first byte 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.
+*/
+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 Btree.maxLocal or Btree.maxLeaf */
+ u16 minLocal; /* Copy of Btree.minLocal or Btree.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];
+ struct Btree *pBt; /* Pointer back to BTree structure */
+ u8 *aData; /* Pointer back to the start of the page */
+ 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)
+
+/*
+** Everything we need to know about an open database
+*/
+struct Btree {
+ Pager *pPager; /* The page cache */
+ BtCursor *pCursor; /* A list of all open cursors */
+ MemPage *pPage1; /* First page of the database */
+ u8 inTrans; /* True if a transaction is in progress */
+ 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 database supports auto-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 */
+ BusyHandler *pBusyHandler; /* Callback for when there is lock contention */
+};
+typedef Btree Bt;
+
+/*
+** Btree.inTrans may take one of the following values.
+*/
+#define TRANS_NONE 0
+#define TRANS_READ 1
+#define TRANS_WRITE 2
+
+/*
+** 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 */
+ 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 in the BTree.
+** The entry is identified by its MemPage and the index in
+** MemPage.aCell[] of the entry.
+*/
+struct BtCursor {
+ Btree *pBt; /* The Btree to which this cursor belongs */
+ BtCursor *pNext, *pPrev; /* Forms a linked list of all cursors */
+ int (*xCompare)(void*,int,const void*,int,const void*); /* Key comp func */
+ void *pArg; /* First arg to xCompare() */
+ 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 isValid; /* TRUE if points to a valid entry */
+};
+
+/*
+** The TRACE macro will print high-level status information about the
+** btree operation when the global variable sqlite3_btree_trace is
+** enabled.
+*/
+#if SQLITE_TEST
+# define TRACE(X) if( sqlite3_btree_trace )\
+ { sqlite3DebugPrintf X; fflush(stdout); }
+#else
+# define TRACE(X)
+#endif
+int sqlite3_btree_trace=0; /* True to enable tracing */
+
+/*
+** Forward declaration
+*/
+static int checkReadLocks(Btree*,Pgno,BtCursor*);
+
+/*
+** Read or write a two- and four-byte big-endian integer values.
+*/
+static u32 get2byte(unsigned char *p){
+ return (p[0]<<8) | p[1];
+}
+static u32 get4byte(unsigned char *p){
+ return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
+}
+static void put2byte(unsigned char *p, u32 v){
+ p[0] = v>>8;
+ p[1] = v;
+}
+static void put4byte(unsigned char *p, u32 v){
+ p[0] = v>>24;
+ p[1] = v>>16;
+ p[2] = v>>8;
+ p[3] = v;
+}
+
+/*
+** 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.
+*/
+#define getVarint sqlite3GetVarint
+#define getVarint32 sqlite3GetVarint32
+#define putVarint sqlite3PutVarint
+
+/* 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).
+*/
+#define PENDING_BYTE_PAGE(pBt) ((PENDING_BYTE/(pBt)->pageSize)+1)
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+/*
+** 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(pgsz, pgno) (((pgno-2)/(pgsz/5+1))*(pgsz/5+1)+2)
+#define PTRMAP_PTROFFSET(pgsz, pgno) (((pgno-2)%(pgsz/5+1)-1)*5)
+#define PTRMAP_ISPAGE(pgsz, pgno) (PTRMAP_PAGENO(pgsz,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
+
+/*
+** 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(Btree *pBt, Pgno key, u8 eType, Pgno parent){
+ u8 *pPtrmap; /* The pointer map page */
+ Pgno iPtrmap; /* The pointer map page number */
+ int offset; /* Offset in pointer map page */
+ int rc;
+
+ assert( pBt->autoVacuum );
+ if( key==0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ iPtrmap = PTRMAP_PAGENO(pBt->usableSize, key);
+ rc = sqlite3pager_get(pBt->pPager, iPtrmap, (void **)&pPtrmap);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ offset = PTRMAP_PTROFFSET(pBt->usableSize, key);
+
+ if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){
+ TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent));
+ rc = sqlite3pager_write(pPtrmap);
+ if( rc==SQLITE_OK ){
+ pPtrmap[offset] = eType;
+ put4byte(&pPtrmap[offset+1], parent);
+ }
+ }
+
+ sqlite3pager_unref(pPtrmap);
+ 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(Btree *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
+ int iPtrmap; /* Pointer map page index */
+ u8 *pPtrmap; /* Pointer map page data */
+ int offset; /* Offset of entry in pointer map */
+ int rc;
+
+ iPtrmap = PTRMAP_PAGENO(pBt->usableSize, key);
+ rc = sqlite3pager_get(pBt->pPager, iPtrmap, (void **)&pPtrmap);
+ if( rc!=0 ){
+ return rc;
+ }
+
+ offset = PTRMAP_PTROFFSET(pBt->usableSize, key);
+ if( pEType ) *pEType = pPtrmap[offset];
+ if( pPgno ) *pPgno = get4byte(&pPtrmap[offset+1]);
+
+ sqlite3pager_unref(pPtrmap);
+ 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.
+*/
+static u8 *findCell(MemPage *pPage, int iCell){
+ u8 *data = pPage->aData;
+ assert( iCell>=0 );
+ assert( iCell<get2byte(&data[pPage->hdrOffset+3]) );
+ return data + get2byte(&data[pPage->cellOffset+2*iCell]);
+}
+
+/*
+** This a more complex version of findCell() that works for
+** pages that do contain overflow cells. See insert
+*/
+static u8 *findOverflowCell(MemPage *pPage, int iCell){
+ int i;
+ 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. parseCell() takes a cell index
+** as the second argument and parseCellPtr() takes a pointer to the
+** body of the cell as its second argument.
+*/
+static void parseCellPtr(
+ 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 */
+
+ 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;
+ }
+ n += getVarint(&pCell[n], (u64 *)&pInfo->nKey);
+ pInfo->nHeader = n;
+ pInfo->nData = nPayload;
+ if( !pPage->intKey ){
+ nPayload += pInfo->nKey;
+ }
+ 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;
+ }
+}
+static void parseCell(
+ MemPage *pPage, /* Page containing the cell */
+ int iCell, /* The cell index. First cell is 0 */
+ CellInfo *pInfo /* Fill in this structure */
+){
+ parseCellPtr(pPage, findCell(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 int cellSize(MemPage *pPage, int iCell){
+ CellInfo info;
+ parseCell(pPage, iCell, &info);
+ return info.nSize;
+}
+#endif
+static int cellSizePtr(MemPage *pPage, u8 *pCell){
+ CellInfo info;
+ parseCellPtr(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;
+ parseCellPtr(pPage, pCell, &info);
+ 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;
+ pCell = findOverflowCell(pPage, iCell);
+ return ptrmapPutOvflPtr(pPage, pCell);
+}
+#endif
+
+
+/*
+** Do sanity checking on a page. Throw an exception if anything is
+** not right.
+**
+** This routine is used for internal error checking only. It is omitted
+** from most builds.
+*/
+#if defined(BTREE_DEBUG) && !defined(NDEBUG) && 0
+static void _pageIntegrity(MemPage *pPage){
+ int usableSize;
+ u8 *data;
+ int i, j, idx, c, pc, hdr, nFree;
+ int cellOffset;
+ int nCell, cellLimit;
+ u8 *used;
+
+ used = sqliteMallocRaw( pPage->pBt->pageSize );
+ if( used==0 ) return;
+ usableSize = pPage->pBt->usableSize;
+ assert( pPage->aData==&((unsigned char*)pPage)[-pPage->pBt->pageSize] );
+ hdr = pPage->hdrOffset;
+ assert( hdr==(pPage->pgno==1 ? 100 : 0) );
+ assert( pPage->pgno==sqlite3pager_pagenumber(pPage->aData) );
+ c = pPage->aData[hdr];
+ if( pPage->isInit ){
+ assert( pPage->leaf == ((c & PTF_LEAF)!=0) );
+ assert( pPage->zeroData == ((c & PTF_ZERODATA)!=0) );
+ assert( pPage->leafData == ((c & PTF_LEAFDATA)!=0) );
+ assert( pPage->intKey == ((c & (PTF_INTKEY|PTF_LEAFDATA))!=0) );
+ assert( pPage->hasData ==
+ !(pPage->zeroData || (!pPage->leaf && pPage->leafData)) );
+ assert( pPage->cellOffset==pPage->hdrOffset+12-4*pPage->leaf );
+ assert( pPage->nCell = get2byte(&pPage->aData[hdr+3]) );
+ }
+ data = pPage->aData;
+ memset(used, 0, usableSize);
+ for(i=0; i<hdr+10-pPage->leaf*4; i++) used[i] = 1;
+ nFree = 0;
+ pc = get2byte(&data[hdr+1]);
+ while( pc ){
+ int size;
+ assert( pc>0 && pc<usableSize-4 );
+ size = get2byte(&data[pc+2]);
+ assert( pc+size<=usableSize );
+ nFree += size;
+ for(i=pc; i<pc+size; i++){
+ assert( used[i]==0 );
+ used[i] = 1;
+ }
+ pc = get2byte(&data[pc]);
+ }
+ idx = 0;
+ nCell = get2byte(&data[hdr+3]);
+ cellLimit = get2byte(&data[hdr+5]);
+ assert( pPage->isInit==0
+ || pPage->nFree==nFree+data[hdr+7]+cellLimit-(cellOffset+2*nCell) );
+ cellOffset = pPage->cellOffset;
+ for(i=0; i<nCell; i++){
+ int size;
+ pc = get2byte(&data[cellOffset+2*i]);
+ assert( pc>0 && pc<usableSize-4 );
+ size = cellSize(pPage, &data[pc]);
+ assert( pc+size<=usableSize );
+ for(j=pc; j<pc+size; j++){
+ assert( used[j]==0 );
+ used[j] = 1;
+ }
+ }
+ for(i=cellOffset+2*nCell; i<cellimit; i++){
+ assert( used[i]==0 );
+ used[i] = 1;
+ }
+ nFree = 0;
+ for(i=0; i<usableSize; i++){
+ assert( used[i]<=1 );
+ if( used[i]==0 ) nFree++;
+ }
+ assert( nFree==data[hdr+7] );
+ sqliteFree(used);
+}
+#define pageIntegrity(X) _pageIntegrity(X)
+#else
+# define pageIntegrity(X)
+#endif
+
+/*
+** 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 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( sqlite3pager_iswriteable(pPage->aData) );
+ assert( pPage->pBt!=0 );
+ assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE );
+ assert( pPage->nOverflow==0 );
+ temp = sqliteMalloc( pPage->pBt->pageSize );
+ if( temp==0 ) return SQLITE_NOMEM;
+ 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);
+ sqliteFree(temp);
+ 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( sqlite3pager_iswriteable(data) );
+ assert( pPage->pBt );
+ 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( sqlite3pager_iswriteable(data) );
+ assert( start>=pPage->hdrOffset+6+(pPage->leaf?0:4) );
+ assert( (start + size)<=pPage->pBt->usableSize );
+ if( size<4 ) size = 4;
+
+ /* 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){
+ Btree *pBt; /* A copy of pPage->pBt */
+
+ assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) );
+ 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.
+*/
+static int initPage(
+ 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 */
+ Btree *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( pPage->pgno==sqlite3pager_pagenumber(pPage->aData) );
+ assert( pPage->aData == &((unsigned char*)pPage)[-pBt->pageSize] );
+ 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;
+ sqlite3pager_ref(pParent->aData);
+ }
+ 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;
+ pageIntegrity(pPage);
+ 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;
+ Btree *pBt = pPage->pBt;
+ int hdr = pPage->hdrOffset;
+ int first;
+
+ assert( sqlite3pager_pagenumber(data)==pPage->pgno );
+ assert( &data[pBt->pageSize] == (unsigned char*)pPage );
+ assert( sqlite3pager_iswriteable(data) );
+ 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;
+ pageIntegrity(pPage);
+}
+
+/*
+** Get a page from the pager. Initialize the MemPage.pBt and
+** MemPage.aData elements if needed.
+*/
+static int getPage(Btree *pBt, Pgno pgno, MemPage **ppPage){
+ int rc;
+ unsigned char *aData;
+ MemPage *pPage;
+ rc = sqlite3pager_get(pBt->pPager, pgno, (void**)&aData);
+ if( rc ) return rc;
+ pPage = (MemPage*)&aData[pBt->pageSize];
+ pPage->aData = aData;
+ 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
+** getPage() and initPage().
+*/
+static int getAndInitPage(
+ Btree *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;
+ if( pgno==0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ rc = getPage(pBt, pgno, ppPage);
+ if( rc==SQLITE_OK && (*ppPage)->isInit==0 ){
+ rc = initPage(*ppPage, pParent);
+ }
+ return rc;
+}
+
+/*
+** Release a MemPage. This should be called once for each prior
+** call to getPage.
+*/
+static void releasePage(MemPage *pPage){
+ if( pPage ){
+ assert( pPage->aData );
+ assert( pPage->pBt );
+ assert( &pPage->aData[pPage->pBt->pageSize]==(unsigned char*)pPage );
+ sqlite3pager_unref(pPage->aData);
+ }
+}
+
+/*
+** 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, int pageSize){
+ MemPage *pPage;
+ assert( (pageSize & 7)==0 );
+ pPage = (MemPage*)&((char*)pData)[pageSize];
+ if( pPage->pParent ){
+ MemPage *pParent = pPage->pParent;
+ 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(void *pData, int pageSize){
+ MemPage *pPage;
+ assert( (pageSize & 7)==0 );
+ pPage = (MemPage*)&((char*)pData)[pageSize];
+ if( pPage->isInit ){
+ pPage->isInit = 0;
+ initPage(pPage, pPage->pParent);
+ }
+}
+
+/*
+** 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.
+*/
+int sqlite3BtreeOpen(
+ const char *zFilename, /* Name of the file containing the BTree database */
+ Btree **ppBtree, /* Pointer to new Btree object written here */
+ int flags, /* Options */
+ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
+ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+){
+ Btree *pBt;
+ int rc;
+ int nReserve;
+ unsigned char zDbHeader[100];
+
+ /*
+ ** 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 );
+ assert( sizeof(u64)==8 );
+ assert( sizeof(u32)==4 );
+ assert( sizeof(u16)==2 );
+ assert( sizeof(Pgno)==4 );
+
+ pBt = sqliteMalloc( sizeof(*pBt) );
+ if( pBt==0 ){
+ *ppBtree = 0;
+ return SQLITE_NOMEM;
+ }
+ rc = sqlite3pager_open(&pBt->pPager, zFilename, EXTRA_SIZE, flags,
+ exclusiveFlag, allowReadonly);
+ if( rc!=SQLITE_OK ){
+ if( pBt->pPager ) sqlite3pager_close(pBt->pPager);
+ sqliteFree(pBt);
+ *ppBtree = 0;
+ return rc;
+ }
+ sqlite3pager_set_destructor(pBt->pPager, pageDestructor);
+ sqlite3pager_set_reiniter(pBt->pPager, pageReinit);
+ pBt->pCursor = 0;
+ pBt->pPage1 = 0;
+ pBt->readOnly = sqlite3pager_isreadonly(pBt->pPager);
+ sqlite3pager_read_fileheader(pBt->pPager, sizeof(zDbHeader), zDbHeader);
+ pBt->pageSize = get2byte(&zDbHeader[16]);
+ if( pBt->pageSize<512 || pBt->pageSize>SQLITE_MAX_PAGE_SIZE
+ || ((pBt->pageSize-1)&pBt->pageSize)!=0 ){
+ pBt->pageSize = SQLITE_DEFAULT_PAGE_SIZE;
+ 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
+ ** do not set the auto-vacuum flag, 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. Respect the auto-vacuum
+ ** default in this case.
+ */
+#ifndef SQLITE_OMIT_MEMORYDB
+ if( zFilename && strcmp(zFilename,":memory:") ){
+#else
+ if( zFilename ){
+#endif
+ pBt->autoVacuum = SQLITE_DEFAULT_AUTOVACUUM;
+ }
+#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);
+#endif
+ }
+ pBt->usableSize = pBt->pageSize - nReserve;
+ assert( (pBt->pageSize & 7)==0 ); /* 8-byte alignment of pageSize */
+ sqlite3pager_set_pagesize(pBt->pPager, pBt->pageSize);
+ *ppBtree = pBt;
+ return SQLITE_OK;
+}
+
+/*
+** Close an open database and invalidate all cursors.
+*/
+int sqlite3BtreeClose(Btree *pBt){
+ while( pBt->pCursor ){
+ sqlite3BtreeCloseCursor(pBt->pCursor);
+ }
+ sqlite3pager_close(pBt->pPager);
+ sqliteFree(pBt);
+ return SQLITE_OK;
+}
+
+/*
+** Change the busy handler callback function.
+*/
+int sqlite3BtreeSetBusyHandler(Btree *pBt, BusyHandler *pHandler){
+ pBt->pBusyHandler = pHandler;
+ sqlite3pager_set_busyhandler(pBt->pPager, pHandler);
+ 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.
+*/
+int sqlite3BtreeSetCacheSize(Btree *pBt, int mxPage){
+ sqlite3pager_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.
+*/
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+int sqlite3BtreeSetSafetyLevel(Btree *pBt, int level){
+ sqlite3pager_set_safety_level(pBt->pPager, level);
+ 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.
+*/
+int sqlite3BtreeSyncDisabled(Btree *pBt){
+ assert( pBt && pBt->pPager );
+ return sqlite3pager_nosync(pBt->pPager);
+}
+
+#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.
+*/
+int sqlite3BtreeSetPageSize(Btree *pBt, int pageSize, int nReserve){
+ if( pBt->pageSizeFixed ){
+ 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 );
+ pBt->pageSize = sqlite3pager_set_pagesize(pBt->pPager, pageSize);
+ }
+ pBt->usableSize = pBt->pageSize - nReserve;
+ return SQLITE_OK;
+}
+
+/*
+** Return the currently defined page size
+*/
+int sqlite3BtreeGetPageSize(Btree *pBt){
+ return pBt->pageSize;
+}
+int sqlite3BtreeGetReserve(Btree *pBt){
+ return pBt->pageSize - pBt->usableSize;
+}
+#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.
+*/
+int sqlite3BtreeSetAutoVacuum(Btree *pBt, int autoVacuum){
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ return SQLITE_READONLY;
+#else
+ if( pBt->pageSizeFixed ){
+ return SQLITE_READONLY;
+ }
+ pBt->autoVacuum = (autoVacuum?1:0);
+ return SQLITE_OK;
+#endif
+}
+
+/*
+** Return the value of the 'auto-vacuum' property. If auto-vacuum is
+** enabled 1 is returned. Otherwise 0.
+*/
+int sqlite3BtreeGetAutoVacuum(Btree *pBt){
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ return 0;
+#else
+ return pBt->autoVacuum;
+#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. SQLITE_PROTOCOL is returned
+** if there is a locking protocol violation.
+*/
+static int lockBtree(Btree *pBt){
+ int rc, pageSize;
+ MemPage *pPage1;
+ if( pBt->pPage1 ) return SQLITE_OK;
+ rc = getPage(pBt, 1, &pPage1);
+ 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;
+ if( sqlite3pager_pagecount(pBt->pPager)>0 ){
+ u8 *page1 = pPage1->aData;
+ if( memcmp(page1, zMagicHeader, 16)!=0 ){
+ goto page1_init_failed;
+ }
+ if( page1[18]>1 || page1[19]>1 ){
+ goto page1_init_failed;
+ }
+ pageSize = get2byte(&page1[16]);
+ if( ((pageSize-1)&pageSize)!=0 ){
+ goto page1_init_failed;
+ }
+ assert( (pageSize & 7)==0 );
+ pBt->pageSize = pageSize;
+ pBt->usableSize = pageSize - page1[20];
+ if( pBt->usableSize<500 ){
+ goto page1_init_failed;
+ }
+ 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);
+#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 *pBt){
+ int rc = SQLITE_OK;
+ if( pBt->inTrans==TRANS_NONE ){
+ rc = sqlite3BtreeBeginTrans(pBt, 0);
+ pBt->inTrans = TRANS_NONE;
+ }
+ 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==TRANS_NONE && pBt->pCursor==0 && pBt->pPage1!=0 ){
+ if( pBt->pPage1->aData==0 ){
+ MemPage *pPage = pBt->pPage1;
+ pPage->aData = &((char*)pPage)[-pBt->pageSize];
+ pPage->pBt = pBt;
+ pPage->pgno = 1;
+ }
+ releasePage(pBt->pPage1);
+ pBt->pPage1 = 0;
+ pBt->inStmt = 0;
+ }
+}
+
+/*
+** Create a new database by initializing the first page of the
+** file.
+*/
+static int newDatabase(Btree *pBt){
+ MemPage *pP1;
+ unsigned char *data;
+ int rc;
+ if( sqlite3pager_pagecount(pBt->pPager)>0 ) return SQLITE_OK;
+ pP1 = pBt->pPage1;
+ assert( pP1!=0 );
+ data = pP1->aData;
+ rc = sqlite3pager_write(data);
+ 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
+ if( pBt->autoVacuum ){
+ put4byte(&data[36 + 4*4], 1);
+ }
+#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.
+*/
+int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag){
+ int rc = SQLITE_OK;
+
+ /* 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( pBt->inTrans==TRANS_WRITE || (pBt->inTrans==TRANS_READ && !wrflag) ){
+ return SQLITE_OK;
+ }
+
+ /* Write transactions are not possible on a read-only database */
+ if( pBt->readOnly && wrflag ){
+ return SQLITE_READONLY;
+ }
+
+ do {
+ if( pBt->pPage1==0 ){
+ rc = lockBtree(pBt);
+ }
+
+ if( rc==SQLITE_OK && wrflag ){
+ rc = sqlite3pager_begin(pBt->pPage1->aData, wrflag>1);
+ if( rc==SQLITE_OK ){
+ rc = newDatabase(pBt);
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ pBt->inTrans = (wrflag?TRANS_WRITE:TRANS_READ);
+ if( wrflag ) pBt->inStmt = 0;
+ }else{
+ unlockBtreeIfUnused(pBt);
+ }
+ }while( rc==SQLITE_BUSY && pBt->inTrans==TRANS_NONE &&
+ sqlite3InvokeBusyHandler(pBt->pBusyHandler) );
+ 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 = SQLITE_OK; /* Return code */
+ Btree *pBt = pPage->pBt;
+ int isInitOrig = pPage->isInit;
+ Pgno pgno = pPage->pgno;
+
+ initPage(pPage, 0);
+ 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){
+ 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;
+
+ initPage(pPage, 0);
+ nCell = pPage->nCell;
+
+ for(i=0; i<nCell; i++){
+ u8 *pCell = findCell(pPage, i);
+ if( eType==PTRMAP_OVERFLOW1 ){
+ CellInfo info;
+ parseCellPtr(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(
+ Btree *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 );
+
+ /* Move page iDbPage from it's 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 = sqlite3pager_movepage(pPager, pDbPage->aData, 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 = getPage(pBt, iPtrPage, &pPtrPage);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = sqlite3pager_write(pPtrPage->aData);
+ 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 autoVacuumCommit(). */
+static int allocatePage(Btree *, MemPage **, Pgno *, Pgno, u8);
+
+/*
+** This routine is called prior to sqlite3pager_commit when a transaction
+** is commited for an auto-vacuum database.
+*/
+static int autoVacuumCommit(Btree *pBt, Pgno *nTrunc){
+ Pager *pPager = pBt->pPager;
+ Pgno nFreeList; /* Number of pages remaining on the free-list. */
+ int nPtrMap; /* Number of pointer-map pages deallocated */
+ Pgno origSize; /* Pages in the database file */
+ Pgno finSize; /* Pages in the database file after truncation */
+ int rc; /* Return code */
+ u8 eType;
+ int pgsz = pBt->pageSize; /* Page size for this database */
+ Pgno iDbPage; /* The database page to move */
+ MemPage *pDbMemPage = 0; /* "" */
+ Pgno iPtrPage; /* The page that contains a pointer to iDbPage */
+ Pgno iFreePage; /* The free-list page to move iDbPage to */
+ MemPage *pFreeMemPage = 0; /* "" */
+
+#ifndef NDEBUG
+ int nRef = *sqlite3pager_stats(pPager);
+#endif
+
+ assert( pBt->autoVacuum );
+ if( PTRMAP_ISPAGE(pgsz, sqlite3pager_pagecount(pPager)) ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+
+ /* Figure out how many free-pages are in the database. If there are no
+ ** free pages, then auto-vacuum is a no-op.
+ */
+ nFreeList = get4byte(&pBt->pPage1->aData[36]);
+ if( nFreeList==0 ){
+ *nTrunc = 0;
+ return SQLITE_OK;
+ }
+
+ origSize = sqlite3pager_pagecount(pPager);
+ nPtrMap = (nFreeList-origSize+PTRMAP_PAGENO(pgsz, origSize)+pgsz/5)/(pgsz/5);
+ finSize = origSize - nFreeList - nPtrMap;
+ if( origSize>=PENDING_BYTE_PAGE(pBt) && finSize<=PENDING_BYTE_PAGE(pBt) ){
+ finSize--;
+ if( PTRMAP_ISPAGE(pBt->usableSize, finSize) ){
+ finSize--;
+ }
+ }
+ TRACE(("AUTOVACUUM: Begin (db size %d->%d)\n", origSize, finSize));
+
+ /* Variable 'finSize' will be the size of the file in pages after
+ ** the auto-vacuum has completed (the current file size minus the number
+ ** of pages on the free list). Loop through the pages that lie beyond
+ ** this mark, and if they are not already on the free list, move them
+ ** to a free page earlier in the file (somewhere before finSize).
+ */
+ for( iDbPage=finSize+1; iDbPage<=origSize; iDbPage++ ){
+ /* If iDbPage is a pointer map page, or the pending-byte page, skip it. */
+ if( PTRMAP_ISPAGE(pgsz, iDbPage) || iDbPage==PENDING_BYTE_PAGE(pBt) ){
+ continue;
+ }
+
+ rc = ptrmapGet(pBt, iDbPage, &eType, &iPtrPage);
+ if( rc!=SQLITE_OK ) goto autovacuum_out;
+ if( eType==PTRMAP_ROOTPAGE ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto autovacuum_out;
+ }
+
+ /* If iDbPage is free, do not swap it. */
+ if( eType==PTRMAP_FREEPAGE ){
+ continue;
+ }
+ rc = getPage(pBt, iDbPage, &pDbMemPage);
+ if( rc!=SQLITE_OK ) goto autovacuum_out;
+
+ /* Find the next page in the free-list that is not already at the end
+ ** of the file. A page can be pulled off the free list using the
+ ** allocatePage() routine.
+ */
+ do{
+ if( pFreeMemPage ){
+ releasePage(pFreeMemPage);
+ pFreeMemPage = 0;
+ }
+ rc = allocatePage(pBt, &pFreeMemPage, &iFreePage, 0, 0);
+ if( rc!=SQLITE_OK ){
+ releasePage(pDbMemPage);
+ goto autovacuum_out;
+ }
+ assert( iFreePage<=origSize );
+ }while( iFreePage>finSize );
+ releasePage(pFreeMemPage);
+ pFreeMemPage = 0;
+
+ rc = relocatePage(pBt, pDbMemPage, eType, iPtrPage, iFreePage);
+ releasePage(pDbMemPage);
+ if( rc!=SQLITE_OK ) goto autovacuum_out;
+ }
+
+ /* The entire free-list has been swapped to the end of the file. So
+ ** truncate the database file to finSize pages and consider the
+ ** free-list empty.
+ */
+ rc = sqlite3pager_write(pBt->pPage1->aData);
+ if( rc!=SQLITE_OK ) goto autovacuum_out;
+ put4byte(&pBt->pPage1->aData[32], 0);
+ put4byte(&pBt->pPage1->aData[36], 0);
+ if( rc!=SQLITE_OK ) goto autovacuum_out;
+ *nTrunc = finSize;
+
+autovacuum_out:
+ assert( nRef==*sqlite3pager_stats(pPager) );
+ if( rc!=SQLITE_OK ){
+ sqlite3pager_rollback(pPager);
+ }
+ return rc;
+}
+#endif
+
+/*
+** 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.
+*/
+int sqlite3BtreeCommit(Btree *pBt){
+ int rc = SQLITE_OK;
+ if( pBt->inTrans==TRANS_WRITE ){
+ rc = sqlite3pager_commit(pBt->pPager);
+ }
+ pBt->inTrans = TRANS_NONE;
+ pBt->inStmt = 0;
+ unlockBtreeIfUnused(pBt);
+ 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.
+*/
+static int countWriteCursors(Btree *pBt){
+ BtCursor *pCur;
+ int r = 0;
+ for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+ if( pCur->wrFlag ) r++;
+ }
+ return r;
+}
+#endif
+
+#ifdef SQLITE_TEST
+/*
+** Print debugging information about all cursors to standard output.
+*/
+void sqlite3BtreeCursorList(Btree *pBt){
+ BtCursor *pCur;
+ for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+ MemPage *pPage = pCur->pPage;
+ char *zMode = pCur->wrFlag ? "rw" : "ro";
+ sqlite3DebugPrintf("CURSOR %p rooted at %4d(%s) currently at %d.%d%s\n",
+ pCur, pCur->pgnoRoot, zMode,
+ pPage ? pPage->pgno : 0, pCur->idx,
+ pCur->isValid ? "" : " eof"
+ );
+ }
+}
+#endif
+
+/*
+** 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.
+*/
+int sqlite3BtreeRollback(Btree *pBt){
+ int rc = SQLITE_OK;
+ MemPage *pPage1;
+ if( pBt->inTrans==TRANS_WRITE ){
+ rc = sqlite3pager_rollback(pBt->pPager);
+ /* The rollback may have destroyed the pPage1->aData value. So
+ ** call getPage() on page 1 again to make sure pPage1->aData is
+ ** set correctly. */
+ if( getPage(pBt, 1, &pPage1)==SQLITE_OK ){
+ releasePage(pPage1);
+ }
+ assert( countWriteCursors(pBt)==0 );
+ }
+ pBt->inTrans = TRANS_NONE;
+ pBt->inStmt = 0;
+ unlockBtreeIfUnused(pBt);
+ 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.
+*/
+int sqlite3BtreeBeginStmt(Btree *pBt){
+ int rc;
+ if( (pBt->inTrans!=TRANS_WRITE) || pBt->inStmt ){
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ rc = pBt->readOnly ? SQLITE_OK : sqlite3pager_stmt_begin(pBt->pPager);
+ pBt->inStmt = 1;
+ return rc;
+}
+
+
+/*
+** Commit the statment subtransaction currently in progress. If no
+** subtransaction is active, this is a no-op.
+*/
+int sqlite3BtreeCommitStmt(Btree *pBt){
+ int rc;
+ if( pBt->inStmt && !pBt->readOnly ){
+ rc = sqlite3pager_stmt_commit(pBt->pPager);
+ }else{
+ rc = SQLITE_OK;
+ }
+ pBt->inStmt = 0;
+ 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.
+*/
+int sqlite3BtreeRollbackStmt(Btree *pBt){
+ int rc;
+ if( pBt->inStmt==0 || pBt->readOnly ) return SQLITE_OK;
+ rc = sqlite3pager_stmt_rollback(pBt->pPager);
+ assert( countWriteCursors(pBt)==0 );
+ pBt->inStmt = 0;
+ return rc;
+}
+
+/*
+** Default key comparison function to be used if no comparison function
+** is specified on the sqlite3BtreeCursor() call.
+*/
+static int dfltCompare(
+ void *NotUsed, /* User data is not used */
+ int n1, const void *p1, /* First key to compare */
+ int n2, const void *p2 /* Second key to compare */
+){
+ int c;
+ c = memcmp(p1, p2, n1<n2 ? n1 : n2);
+ if( c==0 ){
+ c = n1 - n2;
+ }
+ return c;
+}
+
+/*
+** 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 sqlite3BtreeNext() 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.
+**
+** The comparison function must be logically the same for every cursor
+** on a particular table. Changing the comparison function will result
+** in incorrect operations. If the comparison function is NULL, a
+** default comparison function is used. The comparison function is
+** always ignored for INTKEY tables.
+*/
+int sqlite3BtreeCursor(
+ Btree *pBt, /* The btree */
+ int iTable, /* Root page of table to open */
+ int wrFlag, /* 1 to write. 0 read-only */
+ int (*xCmp)(void*,int,const void*,int,const void*), /* Key Comparison func */
+ void *pArg, /* First arg to xCompare() */
+ BtCursor **ppCur /* Write new cursor here */
+){
+ int rc;
+ BtCursor *pCur;
+
+ *ppCur = 0;
+ if( wrFlag ){
+ if( pBt->readOnly ){
+ return SQLITE_READONLY;
+ }
+ if( checkReadLocks(pBt, iTable, 0) ){
+ return SQLITE_LOCKED;
+ }
+ }
+ if( pBt->pPage1==0 ){
+ rc = lockBtreeWithRetry(pBt);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ pCur = sqliteMallocRaw( sizeof(*pCur) );
+ if( pCur==0 ){
+ rc = SQLITE_NOMEM;
+ goto create_cursor_exception;
+ }
+ pCur->pgnoRoot = (Pgno)iTable;
+ pCur->pPage = 0; /* For exit-handler, in case getAndInitPage() fails. */
+ if( iTable==1 && sqlite3pager_pagecount(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;
+ }
+ pCur->xCompare = xCmp ? xCmp : dfltCompare;
+ pCur->pArg = pArg;
+ pCur->pBt = pBt;
+ pCur->wrFlag = wrFlag;
+ pCur->idx = 0;
+ memset(&pCur->info, 0, sizeof(pCur->info));
+ pCur->pNext = pBt->pCursor;
+ if( pCur->pNext ){
+ pCur->pNext->pPrev = pCur;
+ }
+ pCur->pPrev = 0;
+ pBt->pCursor = pCur;
+ pCur->isValid = 0;
+ *ppCur = pCur;
+ return SQLITE_OK;
+
+create_cursor_exception:
+ if( pCur ){
+ releasePage(pCur->pPage);
+ sqliteFree(pCur);
+ }
+ unlockBtreeIfUnused(pBt);
+ return rc;
+}
+
+#if 0 /* Not Used */
+/*
+** Change the value of the comparison function used by a cursor.
+*/
+void sqlite3BtreeSetCompare(
+ BtCursor *pCur, /* The cursor to whose comparison function is changed */
+ int(*xCmp)(void*,int,const void*,int,const void*), /* New comparison func */
+ void *pArg /* First argument to xCmp() */
+){
+ pCur->xCompare = xCmp ? xCmp : dfltCompare;
+ pCur->pArg = pArg;
+}
+#endif
+
+/*
+** Close a cursor. The read lock on the database file is released
+** when the last cursor is closed.
+*/
+int sqlite3BtreeCloseCursor(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;
+ }
+ releasePage(pCur->pPage);
+ 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 ){
+ sqlite3pager_ref(pTempCur->pPage->aData);
+ }
+}
+
+/*
+** Delete a temporary cursor such as was made by the CreateTemporaryCursor()
+** function above.
+*/
+static void releaseTempCursor(BtCursor *pCur){
+ if( pCur->pPage ){
+ sqlite3pager_unref(pCur->pPage->aData);
+ }
+}
+
+/*
+** Make sure the BtCursor.info field of the given cursor is valid.
+** If it is not already valid, call parseCell() 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 parseCell().
+*/
+static void getCellInfo(BtCursor *pCur){
+ if( pCur->info.nSize==0 ){
+ parseCell(pCur->pPage, pCur->idx, &pCur->info);
+ }else{
+#ifndef NDEBUG
+ CellInfo info;
+ memset(&info, 0, sizeof(info));
+ parseCell(pCur->pPage, pCur->idx, &info);
+ assert( memcmp(&info, &pCur->info, sizeof(info))==0 );
+#endif
+ }
+}
+
+/*
+** 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.
+*/
+int sqlite3BtreeKeySize(BtCursor *pCur, i64 *pSize){
+ if( !pCur->isValid ){
+ *pSize = 0;
+ }else{
+ getCellInfo(pCur);
+ *pSize = pCur->info.nKey;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** 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.
+*/
+int sqlite3BtreeDataSize(BtCursor *pCur, u32 *pSize){
+ if( !pCur->isValid ){
+ /* Not pointing at a valid entry - set *pSize to 0. */
+ *pSize = 0;
+ }else{
+ getCellInfo(pCur);
+ *pSize = pCur->info.nData;
+ }
+ 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. Data might appear
+** on the main page or be scattered out on multiple overflow pages.
+*/
+static int getPayload(
+ 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 */
+){
+ unsigned char *aPayload;
+ Pgno nextPage;
+ int rc;
+ MemPage *pPage;
+ Btree *pBt;
+ int ovflSize;
+ u32 nKey;
+
+ assert( pCur!=0 && pCur->pPage!=0 );
+ assert( pCur->isValid );
+ pBt = pCur->pBt;
+ pPage = pCur->pPage;
+ pageIntegrity(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;
+ }
+ assert( offset>=0 );
+ if( skipKey ){
+ offset += nKey;
+ }
+ if( offset+amt > nKey+pCur->info.nData ){
+ return SQLITE_ERROR;
+ }
+ if( offset<pCur->info.nLocal ){
+ int a = amt;
+ if( a+offset>pCur->info.nLocal ){
+ a = pCur->info.nLocal - offset;
+ }
+ memcpy(pBuf, &aPayload[offset], a);
+ if( a==amt ){
+ return SQLITE_OK;
+ }
+ offset = 0;
+ pBuf += a;
+ amt -= a;
+ }else{
+ offset -= pCur->info.nLocal;
+ }
+ ovflSize = pBt->usableSize - 4;
+ if( amt>0 ){
+ nextPage = get4byte(&aPayload[pCur->info.nLocal]);
+ while( amt>0 && nextPage ){
+ rc = sqlite3pager_get(pBt->pPager, nextPage, (void**)&aPayload);
+ if( rc!=0 ){
+ return rc;
+ }
+ nextPage = get4byte(aPayload);
+ if( offset<ovflSize ){
+ int a = amt;
+ if( a + offset > ovflSize ){
+ a = ovflSize - offset;
+ }
+ memcpy(pBuf, &aPayload[offset+4], a);
+ offset = 0;
+ amt -= a;
+ pBuf += a;
+ }else{
+ offset -= ovflSize;
+ }
+ sqlite3pager_unref(aPayload);
+ }
+ }
+
+ if( amt>0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** 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.
+*/
+int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
+ assert( pCur->isValid );
+ 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 );
+ return getPayload(pCur, offset, amt, (unsigned char*)pBuf, 0);
+}
+
+/*
+** 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.
+*/
+int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
+ assert( pCur->isValid );
+ assert( pCur->pPage!=0 );
+ assert( pCur->idx>=0 && pCur->idx<pCur->pPage->nCell );
+ return getPayload(pCur, offset, amt, pBuf, 1);
+}
+
+/*
+** 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 getPayload() 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->isValid );
+ pPage = pCur->pPage;
+ pageIntegrity(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.
+**
+** These routines is used to get quick access to key and data
+** in the common case where no overflow pages are used.
+*/
+const void *sqlite3BtreeKeyFetch(BtCursor *pCur, int *pAmt){
+ return (const void*)fetchPayload(pCur, pAmt, 0);
+}
+const void *sqlite3BtreeDataFetch(BtCursor *pCur, int *pAmt){
+ return (const void*)fetchPayload(pCur, pAmt, 1);
+}
+
+
+/*
+** 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;
+ Btree *pBt = pCur->pBt;
+
+ assert( pCur->isValid );
+ rc = getAndInitPage(pBt, newPgno, &pNewPage, pCur->pPage);
+ if( rc ) return rc;
+ pageIntegrity(pNewPage);
+ pNewPage->idxParent = pCur->idx;
+ pOldPage = pCur->pPage;
+ pOldPage->idxShift = 0;
+ releasePage(pOldPage);
+ pCur->pPage = pNewPage;
+ pCur->idx = 0;
+ pCur->info.nSize = 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.
+*/
+static int isRootPage(MemPage *pPage){
+ MemPage *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.
+*/
+static void moveToParent(BtCursor *pCur){
+ MemPage *pParent;
+ MemPage *pPage;
+ int idxParent;
+
+ assert( pCur->isValid );
+ pPage = pCur->pPage;
+ assert( pPage!=0 );
+ assert( !isRootPage(pPage) );
+ pageIntegrity(pPage);
+ pParent = pPage->pParent;
+ assert( pParent!=0 );
+ pageIntegrity(pParent);
+ idxParent = pPage->idxParent;
+ sqlite3pager_ref(pParent->aData);
+ releasePage(pPage);
+ pCur->pPage = pParent;
+ pCur->info.nSize = 0;
+ assert( pParent->idxShift==0 );
+ pCur->idx = idxParent;
+}
+
+/*
+** Move the cursor to the root page
+*/
+static int moveToRoot(BtCursor *pCur){
+ MemPage *pRoot;
+ int rc;
+ Btree *pBt = pCur->pBt;
+
+ rc = getAndInitPage(pBt, pCur->pgnoRoot, &pRoot, 0);
+ if( rc ){
+ pCur->isValid = 0;
+ return rc;
+ }
+ releasePage(pCur->pPage);
+ pageIntegrity(pRoot);
+ pCur->pPage = pRoot;
+ pCur->idx = 0;
+ pCur->info.nSize = 0;
+ if( pRoot->nCell==0 && !pRoot->leaf ){
+ Pgno subpage;
+ assert( pRoot->pgno==1 );
+ subpage = get4byte(&pRoot->aData[pRoot->hdrOffset+8]);
+ assert( subpage>0 );
+ pCur->isValid = 1;
+ rc = moveToChild(pCur, subpage);
+ }
+ pCur->isValid = pCur->pPage->nCell>0;
+ return rc;
+}
+
+/*
+** 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;
+ MemPage *pPage;
+
+ assert( pCur->isValid );
+ while( !(pPage = pCur->pPage)->leaf ){
+ assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
+ pgno = get4byte(findCell(pPage, pCur->idx));
+ 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;
+ MemPage *pPage;
+
+ assert( pCur->isValid );
+ while( !(pPage = pCur->pPage)->leaf ){
+ pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]);
+ pCur->idx = pPage->nCell;
+ rc = moveToChild(pCur, pgno);
+ if( rc ) return rc;
+ }
+ pCur->idx = pPage->nCell - 1;
+ pCur->info.nSize = 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.
+*/
+int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){
+ int rc;
+ rc = moveToRoot(pCur);
+ if( rc ) return rc;
+ if( pCur->isValid==0 ){
+ assert( pCur->pPage->nCell==0 );
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ 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.
+*/
+int sqlite3BtreeLast(BtCursor *pCur, int *pRes){
+ int rc;
+ rc = moveToRoot(pCur);
+ if( rc ) return rc;
+ if( pCur->isValid==0 ){
+ assert( pCur->pPage->nCell==0 );
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ assert( pCur->isValid );
+ *pRes = 0;
+ rc = moveToRightmost(pCur);
+ return rc;
+}
+
+/* Move the cursor so that it points to an entry near pKey/nKey.
+** Return a success code.
+**
+** For INTKEY tables, only the nKey parameter is used. pKey is
+** ignored. For other tables, nKey is the number of bytes of data
+** in nKey. The comparison function specified when the cursor was
+** created is used to compare keys.
+**
+** 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.
+*/
+int sqlite3BtreeMoveto(BtCursor *pCur, const void *pKey, i64 nKey, int *pRes){
+ int rc;
+ rc = moveToRoot(pCur);
+ if( rc ) return rc;
+ assert( pCur->pPage );
+ assert( pCur->pPage->isInit );
+ if( pCur->isValid==0 ){
+ *pRes = -1;
+ assert( pCur->pPage->nCell==0 );
+ return SQLITE_OK;
+ }
+ 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 && pKey==0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ pageIntegrity(pPage);
+ while( lwr<=upr ){
+ void *pCellKey;
+ i64 nCellKey;
+ pCur->idx = (lwr+upr)/2;
+ pCur->info.nSize = 0;
+ sqlite3BtreeKeySize(pCur, &nCellKey);
+ if( pPage->intKey ){
+ if( nCellKey<nKey ){
+ c = -1;
+ }else if( nCellKey>nKey ){
+ c = +1;
+ }else{
+ c = 0;
+ }
+ }else{
+ int available;
+ pCellKey = (void *)fetchPayload(pCur, &available, 0);
+ if( available>=nCellKey ){
+ c = pCur->xCompare(pCur->pArg, nCellKey, pCellKey, nKey, pKey);
+ }else{
+ pCellKey = sqliteMallocRaw( nCellKey );
+ if( pCellKey==0 ) return SQLITE_NOMEM;
+ rc = sqlite3BtreeKey(pCur, 0, nCellKey, (void *)pCellKey);
+ c = pCur->xCompare(pCur->pArg, nCellKey, pCellKey, nKey, pKey);
+ sqliteFree(pCellKey);
+ if( rc ) return rc;
+ }
+ }
+ if( c==0 ){
+ if( pPage->leafData && !pPage->leaf ){
+ lwr = pCur->idx;
+ upr = lwr - 1;
+ break;
+ }else{
+ 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( 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;
+ return SQLITE_OK;
+ }
+ pCur->idx = lwr;
+ pCur->info.nSize = 0;
+ rc = moveToChild(pCur, chldPg);
+ if( rc ){
+ return rc;
+ }
+ }
+ /* NOT REACHED */
+}
+
+/*
+** 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.
+*/
+int sqlite3BtreeEof(BtCursor *pCur){
+ return pCur->isValid==0;
+}
+
+/*
+** 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.
+*/
+int sqlite3BtreeNext(BtCursor *pCur, int *pRes){
+ int rc;
+ MemPage *pPage = pCur->pPage;
+
+ assert( pRes!=0 );
+ if( pCur->isValid==0 ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ assert( pPage->isInit );
+ assert( pCur->idx<pPage->nCell );
+
+ pCur->idx++;
+ pCur->info.nSize = 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( isRootPage(pPage) ){
+ *pRes = 1;
+ pCur->isValid = 0;
+ return SQLITE_OK;
+ }
+ moveToParent(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.
+*/
+int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){
+ int rc;
+ Pgno pgno;
+ MemPage *pPage;
+ if( pCur->isValid==0 ){
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+
+ 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( isRootPage(pPage) ){
+ pCur->isValid = 0;
+ *pRes = 1;
+ return SQLITE_OK;
+ }
+ moveToParent(pCur);
+ pPage = pCur->pPage;
+ }
+ pCur->idx--;
+ pCur->info.nSize = 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, sqlite3pager_write()
+** has already been called on the new page.) The new page has also
+** been referenced and the calling routine is responsible for calling
+** sqlite3pager_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 sqlite3pager_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.
+**
+** 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 allocatePage(
+ Btree *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 */
+
+ pPage1 = pBt->pPage1;
+ n = get4byte(&pPage1->aData[36]);
+ if( n>0 ){
+ /* There are pages on the freelist. Reuse one of those pages. */
+ MemPage *pTrunk = 0;
+ Pgno iTrunk;
+ MemPage *pPrevTrunk = 0;
+ 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 ){
+ 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 = sqlite3pager_write(pPage1->aData);
+ 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 = getPage(pBt, iTrunk, &pTrunk);
+ if( rc ){
+ releasePage(pPrevTrunk);
+ return rc;
+ }
+
+ /* TODO: This should move to after the loop? */
+ rc = sqlite3pager_write(pTrunk->aData);
+ if( rc ){
+ releasePage(pTrunk);
+ releasePage(pPrevTrunk);
+ return rc;
+ }
+
+ 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 );
+ *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 */
+ return SQLITE_CORRUPT_BKPT;
+#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;
+ 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 = getPage(pBt, iNewTrunk, &pNewTrunk);
+ if( rc!=SQLITE_OK ){
+ releasePage(pTrunk);
+ releasePage(pPrevTrunk);
+ return rc;
+ }
+ rc = sqlite3pager_write(pNewTrunk->aData);
+ if( rc!=SQLITE_OK ){
+ releasePage(pNewTrunk);
+ releasePage(pTrunk);
+ releasePage(pPrevTrunk);
+ return rc;
+ }
+ memcpy(&pNewTrunk->aData[0], &pTrunk->aData[0], 4);
+ put4byte(&pNewTrunk->aData[4], k-1);
+ memcpy(&pNewTrunk->aData[8], &pTrunk->aData[12], (k-1)*4);
+ if( !pPrevTrunk ){
+ put4byte(&pPage1->aData[32], iNewTrunk);
+ }else{
+ put4byte(&pPrevTrunk->aData[0], iNewTrunk);
+ }
+ releasePage(pNewTrunk);
+ }
+ 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;
+ 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>sqlite3pager_pagecount(pBt->pPager) ){
+ /* Free page off the end of the file */
+ return SQLITE_CORRUPT_BKPT;
+ }
+ 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 = getPage(pBt, *pPgno, ppPage);
+ if( rc==SQLITE_OK ){
+ sqlite3pager_dont_rollback((*ppPage)->aData);
+ rc = sqlite3pager_write((*ppPage)->aData);
+ if( rc!=SQLITE_OK ){
+ releasePage(*ppPage);
+ }
+ }
+ searchList = 0;
+ }
+ }
+ releasePage(pPrevTrunk);
+ }while( searchList );
+ releasePage(pTrunk);
+ }else{
+ /* There are no pages on the freelist, so create a new page at the
+ ** end of the file */
+ *pPgno = sqlite3pager_pagecount(pBt->pPager) + 1;
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum && PTRMAP_ISPAGE(pBt->usableSize, *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)++;
+ }
+#endif
+
+ assert( *pPgno!=PENDING_BYTE_PAGE(pBt) );
+ rc = getPage(pBt, *pPgno, ppPage);
+ if( rc ) return rc;
+ rc = sqlite3pager_write((*ppPage)->aData);
+ if( rc!=SQLITE_OK ){
+ releasePage(*ppPage);
+ }
+ TRACE(("ALLOCATE: %d from end of file\n", *pPgno));
+ }
+
+ assert( *pPgno!=PENDING_BYTE_PAGE(pBt) );
+ return rc;
+}
+
+/*
+** Add a page of the database file to the freelist.
+**
+** sqlite3pager_unref() is NOT called for pPage.
+*/
+static int freePage(MemPage *pPage){
+ Btree *pBt = pPage->pBt;
+ MemPage *pPage1 = pBt->pPage1;
+ int rc, n, k;
+
+ /* Prepare the page for freeing */
+ assert( pPage->pgno>1 );
+ pPage->isInit = 0;
+ releasePage(pPage->pParent);
+ pPage->pParent = 0;
+
+ /* Increment the free page count on pPage1 */
+ rc = sqlite3pager_write(pPage1->aData);
+ if( rc ) return rc;
+ n = get4byte(&pPage1->aData[36]);
+ put4byte(&pPage1->aData[36], n+1);
+
+#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 = sqlite3pager_write(pPage->aData);
+ 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 = getPage(pBt, get4byte(&pPage1->aData[32]), &pTrunk);
+ 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 = sqlite3pager_write(pPage->aData);
+ if( rc ) return rc;
+ 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{
+ /* Add the newly freed page as a leaf on the current trunk */
+ rc = sqlite3pager_write(pTrunk->aData);
+ if( rc ) return rc;
+ put4byte(&pTrunk->aData[4], k+1);
+ put4byte(&pTrunk->aData[8+k*4], pPage->pgno);
+ sqlite3pager_dont_write(pBt->pPager, pPage->pgno);
+ 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){
+ Btree *pBt = pPage->pBt;
+ CellInfo info;
+ Pgno ovflPgno;
+ int rc;
+
+ parseCellPtr(pPage, pCell, &info);
+ if( info.iOverflow==0 ){
+ return SQLITE_OK; /* No overflow pages. Return without doing anything */
+ }
+ ovflPgno = get4byte(&pCell[info.iOverflow]);
+ while( ovflPgno!=0 ){
+ MemPage *pOvfl;
+ if( ovflPgno>sqlite3pager_pagecount(pBt->pPager) ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ rc = getPage(pBt, ovflPgno, &pOvfl);
+ if( rc ) return rc;
+ ovflPgno = get4byte(pOvfl->aData);
+ rc = freePage(pOvfl);
+ sqlite3pager_unref(pOvfl->aData);
+ 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 *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;
+ Btree *pBt = pPage->pBt;
+ Pgno pgnoOvfl = 0;
+ int nHeader;
+ CellInfo info;
+
+ /* Fill in the header. */
+ nHeader = 0;
+ if( !pPage->leaf ){
+ nHeader += 4;
+ }
+ if( pPage->hasData ){
+ nHeader += putVarint(&pCell[nHeader], nData);
+ }else{
+ nData = 0;
+ }
+ nHeader += putVarint(&pCell[nHeader], *(u64*)&nKey);
+ parseCellPtr(pPage, pCell, &info);
+ assert( info.nHeader==nHeader );
+ assert( info.nKey==nKey );
+ assert( info.nData==nData );
+
+ /* Fill in the payload */
+ nPayload = nData;
+ 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 ){
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ Pgno pgnoPtrmap = pgnoOvfl; /* Overflow page pointer-map entry page */
+#endif
+ rc = allocatePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl, 0);
+#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. The entry for the first overflow page will be
+ ** added later, by the insertCell() routine.
+ */
+ if( pBt->autoVacuum && pgnoPtrmap!=0 && rc==SQLITE_OK ){
+ rc = ptrmapPut(pBt, pgnoOvfl, PTRMAP_OVERFLOW2, pgnoPtrmap);
+ }
+#endif
+ if( rc ){
+ releasePage(pToRelease);
+ /* clearCell(pPage, pCell); */
+ 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( n>nSrc ) n = nSrc;
+ memcpy(pPayload, pSrc, 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(Btree *pBt, Pgno pgno, MemPage *pNewParent, int idx){
+ MemPage *pThis;
+ unsigned char *aData;
+
+ if( pgno==0 ) return SQLITE_OK;
+ assert( pBt->pPager!=0 );
+ aData = sqlite3pager_lookup(pBt->pPager, pgno);
+ if( aData ){
+ pThis = (MemPage*)&aData[pBt->pageSize];
+ assert( pThis->aData==aData );
+ if( pThis->isInit ){
+ if( pThis->pParent!=pNewParent ){
+ if( pThis->pParent ) sqlite3pager_unref(pThis->pParent->aData);
+ pThis->pParent = pNewParent;
+ if( pNewParent ) sqlite3pager_ref(pNewParent->aData);
+ }
+ pThis->idxParent = idx;
+ }
+ sqlite3pager_unref(aData);
+ }
+
+#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;
+ Btree *pBt = pPage->pBt;
+ int rc = SQLITE_OK;
+
+ if( pPage->leaf ) return SQLITE_OK;
+
+ for(i=0; i<pPage->nCell; i++){
+ u8 *pCell = findCell(pPage, i);
+ if( !pPage->leaf ){
+ rc = reparentPage(pBt, get4byte(pCell), pPage, i);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ }
+ if( !pPage->leaf ){
+ 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( sqlite3pager_iswriteable(pPage->aData) );
+ 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( sqlite3pager_iswriteable(pPage->aData) );
+ 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{
+ 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 ){
+ int 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;
+ pageIntegrity(pPage);
+#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;
+ parseCellPtr(pPage, pCell, &info);
+ if( (info.nData+(pPage->intKey?0:info.nKey))>info.nLocal ){
+ Pgno pgnoOvfl = get4byte(&pCell[info.iOverflow]);
+ int 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 */
+ int *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 );
+ 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;
+ int szCell;
+ CellInfo info;
+ Btree *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 */
+
+ /* Allocate a new page. Insert the overflow cell from pPage
+ ** into it. Then remove the overflow cell from pPage.
+ */
+ rc = allocatePage(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;
+ sqlite3pager_ref(pParent->aData);
+
+ /* 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 );
+ parseCellPtr(pPage, findCell(pPage, pPage->nCell-1), &info);
+ rc = fillInCell(pParent, parentCell, 0, info.nKey, 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 ){
+ return rc;
+ }
+ rc = ptrmapPutOvfl(pNew, 0);
+ if( rc!=SQLITE_OK ){
+ 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 */
+
+/*
+** 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 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 */
+ Btree *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[] */
+ int idxDiv[NB]; /* Indices of divider cells in pParent */
+ 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 */
+ int *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
+
+ /*
+ ** Find the parent page.
+ */
+ assert( pPage->isInit );
+ assert( sqlite3pager_iswriteable(pPage->aData) );
+ pBt = pPage->pBt;
+ pParent = pPage->pParent;
+ sqlite3pager_write(pParent->aData);
+ assert( pParent );
+ 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
+
+ /*
+ ** 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==sqlite3pager_pagenumber(pPage->aData) );
+ 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;
+ sqlite3pager_ref(pParent->aData);
+
+ /*
+ ** 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] = 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 2 in order to preserve 8-byte
+ ** alignment */
+ nMaxCells = (nMaxCells + 1)&~1;
+
+ /*
+ ** Allocate space for memory structures
+ */
+ apCell = sqliteMallocRaw(
+ nMaxCells*sizeof(u8*) /* apCell */
+ + nMaxCells*sizeof(int) /* szCell */
+ + ROUND8(sizeof(MemPage))*NB /* aCopy */
+ + pBt->pageSize*(5+NB) /* aSpace */
+ + (ISAUTOVACUUM ? nMaxCells : 0) /* aFrom */
+ );
+ if( apCell==0 ){
+ rc = SQLITE_NOMEM;
+ goto balance_cleanup;
+ }
+ szCell = (int*)&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][pBt->pageSize];
+ p->aData = &((u8*)p)[-pBt->pageSize];
+ memcpy(p->aData, apOld[i]->aData, pBt->pageSize + sizeof(MemPage));
+ /* The memcpy() above changes the value of p->aData so we have to
+ ** set it again. */
+ p->aData = &((u8*)p)[-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 ){
+ int 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 );
+ }
+ 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 = sqlite3pager_write(pNew->aData);
+ if( rc ) goto balance_cleanup;
+ }else{
+ rc = allocatePage(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--;
+ parseCellPtr(pNew, apCell[j], &info);
+ pCell = &aSpace[iSpace];
+ fillInCell(pParent, pCell, 0, info.nKey, 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 );
+ }
+ 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 );
+ 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 );
+ /* assert( pPage->isInit ); // No! pPage might have been added to freelist */
+ /* pageIntegrity(pPage); // No! pPage might have been added to freelist */
+ rc = balance(pParent, 0);
+
+ /*
+ ** Cleanup before returning.
+ */
+balance_cleanup:
+ sqliteFree(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 */
+ Btree *pBt; /* The main BTree structure */
+ int mxCellPerPage; /* Maximum number of cells per page */
+ u8 **apCell; /* All cells from pages being balanced */
+ int *szCell; /* Local size of all cells */
+
+ assert( pPage->pParent==0 );
+ assert( pPage->nCell==0 );
+ pBt = pPage->pBt;
+ mxCellPerPage = MX_CELL(pBt);
+ apCell = sqliteMallocRaw( mxCellPerPage*(sizeof(u8*)+sizeof(int)) );
+ if( apCell==0 ) return SQLITE_NOMEM;
+ szCell = (int*)&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<=sqlite3pager_pagecount(pPage->pBt->pPager) );
+ rc = getPage(pPage->pBt, pgnoChild, &pChild);
+ if( rc ) goto end_shallow_balance;
+ if( pPage->pgno==1 ){
+ rc = initPage(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 = initPage(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
+ if( rc!=SQLITE_OK ) goto end_shallow_balance;
+ releasePage(pChild);
+ }
+end_shallow_balance:
+ sqliteFree(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 */
+ Btree *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;
+ rc = allocatePage(pBt, &pChild, &pgnoChild, pPage->pgno, 0);
+ if( rc ) return rc;
+ assert( sqlite3pager_iswriteable(pChild->aData) );
+ 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 = initPage(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;
+ if( pPage->pParent==0 ){
+ if( 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 other than pExclude were opened with
+** wrFlag==0 then this routine returns SQLITE_LOCKED. If all
+** cursors that point to pgnoRoot 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 pExclude 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(Btree *pBt, Pgno pgnoRoot, BtCursor *pExclude){
+ BtCursor *p;
+ for(p=pBt->pCursor; p; p=p->pNext){
+ if( p->pgnoRoot!=pgnoRoot || p==pExclude ) continue;
+ if( p->wrFlag==0 ) return SQLITE_LOCKED;
+ if( p->pPage->pgno!=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 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.
+*/
+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 rc;
+ int loc;
+ int szNew;
+ MemPage *pPage;
+ Btree *pBt = pCur->pBt;
+ unsigned char *oldCell;
+ unsigned char *newCell = 0;
+
+ if( pBt->inTrans!=TRANS_WRITE ){
+ /* 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(pBt, pCur->pgnoRoot, pCur) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+ rc = sqlite3BtreeMoveto(pCur, pKey, nKey, &loc);
+ if( rc ) 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 );
+ rc = sqlite3pager_write(pPage->aData);
+ if( rc ) return rc;
+ newCell = sqliteMallocRaw( MX_CELL_SIZE(pBt) );
+ if( newCell==0 ) return SQLITE_NOMEM;
+ rc = fillInCell(pPage, newCell, pKey, nKey, pData, nData, &szNew);
+ if( rc ) goto end_insert;
+ assert( szNew==cellSizePtr(pPage, newCell) );
+ assert( szNew<=MX_CELL_SIZE(pBt) );
+ if( loc==0 && pCur->isValid ){
+ int szOld;
+ assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
+ 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;
+ }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:
+ sqliteFree(newCell);
+ return rc;
+}
+
+/*
+** Delete the entry that the cursor is pointing to. The cursor
+** is left pointing at a random location.
+*/
+int sqlite3BtreeDelete(BtCursor *pCur){
+ MemPage *pPage = pCur->pPage;
+ unsigned char *pCell;
+ int rc;
+ Pgno pgnoChild = 0;
+ Btree *pBt = pCur->pBt;
+
+ assert( pPage->isInit );
+ if( pBt->inTrans!=TRANS_WRITE ){
+ /* 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(pBt, pCur->pgnoRoot, pCur) ){
+ return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ }
+ rc = sqlite3pager_write(pPage->aData);
+ if( rc ) return rc;
+
+ /* Locate the cell within it's 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 szNext;
+ int notUsed;
+ unsigned char *tempCell = 0;
+ assert( !pPage->leafData );
+ getTempCursor(pCur, &leafCur);
+ rc = sqlite3BtreeNext(&leafCur, &notUsed);
+ if( rc!=SQLITE_OK ){
+ if( rc!=SQLITE_NOMEM ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3pager_write(leafCur.pPage->aData);
+ }
+ if( rc==SQLITE_OK ){
+ 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 );
+ tempCell = sqliteMallocRaw( MX_CELL_SIZE(pBt) );
+ 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);
+ }
+ sqliteFree(tempCell);
+ releaseTempCursor(&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
+*/
+int sqlite3BtreeCreateTable(Btree *pBt, int *piTable, int flags){
+ MemPage *pRoot;
+ Pgno pgnoRoot;
+ int rc;
+ if( pBt->inTrans!=TRANS_WRITE ){
+ /* Must start a transaction first */
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ assert( !pBt->readOnly );
+
+ /* It is illegal to create a table if any cursors are open on the
+ ** database. This is because in auto-vacuum mode the backend may
+ ** need to move a database page to make room for the new root-page.
+ ** If an open cursor was using the page a problem would occur.
+ */
+ if( pBt->pCursor ){
+ return SQLITE_LOCKED;
+ }
+
+#ifdef SQLITE_OMIT_AUTOVACUUM
+ rc = allocatePage(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. */
+
+ /* 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(pBt, 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.
+ */
+ if( pgnoRoot==PTRMAP_PAGENO(pBt->usableSize, 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 = allocatePage(pBt, &pPageMove, &pgnoMove, pgnoRoot, 1);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ if( pgnoMove!=pgnoRoot ){
+ u8 eType;
+ Pgno iPtrPage;
+
+ releasePage(pPageMove);
+ rc = getPage(pBt, pgnoRoot, &pRoot);
+ 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 = sqlite3pager_write(pRoot->aData);
+ if( rc!=SQLITE_OK ){
+ releasePage(pRoot);
+ return rc;
+ }
+ rc = relocatePage(pBt, pRoot, eType, iPtrPage, pgnoMove);
+ releasePage(pRoot);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = getPage(pBt, pgnoRoot, &pRoot);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = sqlite3pager_write(pRoot->aData);
+ 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(pBt, 4, pgnoRoot);
+ if( rc ){
+ releasePage(pRoot);
+ return rc;
+ }
+
+ }else{
+ rc = allocatePage(pBt, &pRoot, &pgnoRoot, 1, 0);
+ if( rc ) return rc;
+ }
+#endif
+ assert( sqlite3pager_iswriteable(pRoot->aData) );
+ zeroPage(pRoot, flags | PTF_LEAF);
+ sqlite3pager_unref(pRoot->aData);
+ *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, /* 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;
+
+ if( pgno>sqlite3pager_pagecount(pBt->pPager) ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+
+ rc = getAndInitPage(pBt, pgno, &pPage, pParent);
+ if( rc ) goto cleardatabasepage_out;
+ rc = sqlite3pager_write(pPage->aData);
+ 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{
+ 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.
+*/
+int sqlite3BtreeClearTable(Btree *pBt, int iTable){
+ int rc;
+ BtCursor *pCur;
+ if( pBt->inTrans!=TRANS_WRITE ){
+ 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, 0);
+ if( rc ){
+ sqlite3BtreeRollback(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 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.
+*/
+int sqlite3BtreeDropTable(Btree *pBt, int iTable, int *piMoved){
+ int rc;
+ MemPage *pPage = 0;
+
+ if( pBt->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 = getPage(pBt, (Pgno)iTable, &pPage);
+ if( rc ) return rc;
+ rc = sqlite3BtreeClearTable(pBt, 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(pBt, 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 = getPage(pBt, maxRootPgno, &pMove);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = relocatePage(pBt, pMove, PTRMAP_ROOTPAGE, 0, iTable);
+ releasePage(pMove);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ rc = getPage(pBt, maxRootPgno, &pMove);
+ 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->usableSize, maxRootPgno) ){
+ maxRootPgno--;
+ }
+ assert( maxRootPgno!=PENDING_BYTE_PAGE(pBt) );
+
+ rc = sqlite3BtreeUpdateMeta(pBt, 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;
+}
+
+
+/*
+** 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].
+*/
+int sqlite3BtreeGetMeta(Btree *pBt, int idx, u32 *pMeta){
+ int rc;
+ unsigned char *pP1;
+
+ assert( idx>=0 && idx<=15 );
+ rc = sqlite3pager_get(pBt->pPager, 1, (void**)&pP1);
+ if( rc ) return rc;
+ *pMeta = get4byte(&pP1[36 + idx*4]);
+ sqlite3pager_unref(pP1);
+
+ /* 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
+
+ return SQLITE_OK;
+}
+
+/*
+** Write meta-information back into the database. Meta[0] is
+** read-only and may not be written.
+*/
+int sqlite3BtreeUpdateMeta(Btree *pBt, int idx, u32 iMeta){
+ unsigned char *pP1;
+ int rc;
+ assert( idx>=1 && idx<=15 );
+ if( pBt->inTrans!=TRANS_WRITE ){
+ return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
+ }
+ assert( pBt->pPage1!=0 );
+ pP1 = pBt->pPage1->aData;
+ rc = sqlite3pager_write(pP1);
+ if( rc ) return rc;
+ put4byte(&pP1[36 + idx*4], iMeta);
+ return SQLITE_OK;
+}
+
+/*
+** Return the flag byte at the beginning of the page that the cursor
+** is currently pointing to.
+*/
+int sqlite3BtreeFlags(BtCursor *pCur){
+ MemPage *pPage = pCur->pPage;
+ return pPage ? pPage->aData[pPage->hdrOffset] : 0;
+}
+
+#ifdef SQLITE_DEBUG
+/*
+** Print a disassembly of the given page on standard output. This routine
+** is used for debugging and testing only.
+*/
+static int btreePageDump(Btree *pBt, int pgno, int recursive, MemPage *pParent){
+ int rc;
+ MemPage *pPage;
+ int i, j, c;
+ int nFree;
+ u16 idx;
+ int hdr;
+ int nCell;
+ int isInit;
+ unsigned char *data;
+ char range[20];
+ unsigned char payload[20];
+
+ rc = getPage(pBt, (Pgno)pgno, &pPage);
+ isInit = pPage->isInit;
+ if( pPage->isInit==0 ){
+ initPage(pPage, pParent);
+ }
+ if( rc ){
+ return rc;
+ }
+ hdr = pPage->hdrOffset;
+ data = pPage->aData;
+ c = data[hdr];
+ pPage->intKey = (c & (PTF_INTKEY|PTF_LEAFDATA))!=0;
+ pPage->zeroData = (c & PTF_ZERODATA)!=0;
+ pPage->leafData = (c & PTF_LEAFDATA)!=0;
+ pPage->leaf = (c & PTF_LEAF)!=0;
+ pPage->hasData = !(pPage->zeroData || (!pPage->leaf && pPage->leafData));
+ nCell = get2byte(&data[hdr+3]);
+ sqlite3DebugPrintf("PAGE %d: flags=0x%02x frag=%d parent=%d\n", pgno,
+ data[hdr], data[hdr+7],
+ (pPage->isInit && pPage->pParent) ? pPage->pParent->pgno : 0);
+ assert( hdr == (pgno==1 ? 100 : 0) );
+ idx = hdr + 12 - pPage->leaf*4;
+ for(i=0; i<nCell; i++){
+ CellInfo info;
+ Pgno child;
+ unsigned char *pCell;
+ int sz;
+ int addr;
+
+ addr = get2byte(&data[idx + 2*i]);
+ pCell = &data[addr];
+ parseCellPtr(pPage, pCell, &info);
+ sz = info.nSize;
+ sprintf(range,"%d..%d", addr, addr+sz-1);
+ if( pPage->leaf ){
+ child = 0;
+ }else{
+ child = get4byte(pCell);
+ }
+ sz = info.nData;
+ if( !pPage->intKey ) sz += info.nKey;
+ if( sz>sizeof(payload)-1 ) sz = sizeof(payload)-1;
+ memcpy(payload, &pCell[info.nHeader], sz);
+ for(j=0; j<sz; j++){
+ if( payload[j]<0x20 || payload[j]>0x7f ) payload[j] = '.';
+ }
+ payload[sz] = 0;
+ sqlite3DebugPrintf(
+ "cell %2d: i=%-10s chld=%-4d nk=%-4lld nd=%-4d payload=%s\n",
+ i, range, child, info.nKey, info.nData, payload
+ );
+ }
+ if( !pPage->leaf ){
+ sqlite3DebugPrintf("right_child: %d\n", get4byte(&data[hdr+8]));
+ }
+ nFree = 0;
+ i = 0;
+ idx = get2byte(&data[hdr+1]);
+ while( idx>0 && idx<pPage->pBt->usableSize ){
+ int sz = get2byte(&data[idx+2]);
+ sprintf(range,"%d..%d", idx, idx+sz-1);
+ nFree += sz;
+ sqlite3DebugPrintf("freeblock %2d: i=%-10s size=%-4d total=%d\n",
+ i, range, sz, nFree);
+ idx = get2byte(&data[idx]);
+ i++;
+ }
+ if( idx!=0 ){
+ sqlite3DebugPrintf("ERROR: next freeblock index out of range: %d\n", idx);
+ }
+ if( recursive && !pPage->leaf ){
+ for(i=0; i<nCell; i++){
+ unsigned char *pCell = findCell(pPage, i);
+ btreePageDump(pBt, get4byte(pCell), 1, pPage);
+ idx = get2byte(pCell);
+ }
+ btreePageDump(pBt, get4byte(&data[hdr+8]), 1, pPage);
+ }
+ pPage->isInit = isInit;
+ sqlite3pager_unref(data);
+ fflush(stdout);
+ return SQLITE_OK;
+}
+int sqlite3BtreePageDump(Btree *pBt, int pgno, int recursive){
+ return btreePageDump(pBt, pgno, recursive, 0);
+}
+#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] = Cell size (local payload + header)
+** aResult[4] = Number of free bytes on this page
+** aResult[5] = Number of free blocks on the page
+** aResult[6] = Total payload size (local + overflow)
+** aResult[7] = Header size in bytes
+** aResult[8] = Local payload size
+** aResult[9] = Parent page number
+**
+** This routine is used for testing and debugging only.
+*/
+int sqlite3BtreeCursorInfo(BtCursor *pCur, int *aResult, int upCnt){
+ int cnt, idx;
+ MemPage *pPage = pCur->pPage;
+ BtCursor tmpCur;
+
+ pageIntegrity(pPage);
+ assert( pPage->isInit );
+ getTempCursor(pCur, &tmpCur);
+ while( upCnt-- ){
+ moveToParent(&tmpCur);
+ }
+ pPage = tmpCur.pPage;
+ pageIntegrity(pPage);
+ aResult[0] = sqlite3pager_pagenumber(pPage->aData);
+ assert( aResult[0]==pPage->pgno );
+ aResult[1] = tmpCur.idx;
+ aResult[2] = pPage->nCell;
+ if( tmpCur.idx>=0 && tmpCur.idx<pPage->nCell ){
+ getCellInfo(&tmpCur);
+ aResult[3] = tmpCur.info.nSize;
+ aResult[6] = tmpCur.info.nData;
+ aResult[7] = tmpCur.info.nHeader;
+ aResult[8] = tmpCur.info.nLocal;
+ }else{
+ aResult[3] = 0;
+ aResult[6] = 0;
+ aResult[7] = 0;
+ aResult[8] = 0;
+ }
+ aResult[4] = pPage->nFree;
+ cnt = 0;
+ idx = get2byte(&pPage->aData[pPage->hdrOffset+1]);
+ while( idx>0 && idx<pPage->pBt->usableSize ){
+ cnt++;
+ idx = get2byte(&pPage->aData[idx]);
+ }
+ aResult[5] = cnt;
+ if( pPage->pParent==0 || isRootPage(pPage) ){
+ aResult[9] = 0;
+ }else{
+ aResult[9] = pPage->pParent->pgno;
+ }
+ releaseTempCursor(&tmpCur);
+ return SQLITE_OK;
+}
+#endif
+
+/*
+** Return the pager associated with a BTree. This routine is used for
+** testing and debugging only.
+*/
+Pager *sqlite3BtreePager(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. */
+};
+
+#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;
+ va_start(ap, zFormat);
+ zMsg2 = sqlite3VMPrintf(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);
+ sqliteFree(zOld);
+ }else{
+ sqlite3SetString(&pCheck->zErrMsg, zMsg1, zMsg2, (char*)0);
+ }
+ sqliteFree(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 ){
+ unsigned char *pOvfl;
+ 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( sqlite3pager_get(pCheck->pPager, (Pgno)iPage, (void**)&pOvfl) ){
+ checkAppendMsg(pCheck, zContext, "failed to get page %d", iPage);
+ break;
+ }
+ if( isFreeList ){
+ int n = get4byte(&pOvfl[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(&pOvfl[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(pOvfl);
+ checkPtrmap(pCheck, i, PTRMAP_OVERFLOW2, iPage, zContext);
+ }
+ }
+#endif
+ iPage = get4byte(pOvfl);
+ sqlite3pager_unref(pOvfl);
+ }
+}
+#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 */
+ 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, cnt;
+ int hdr, cellStart;
+ int nCell;
+ u8 *data;
+ BtCursor cur;
+ Btree *pBt;
+ int usableSize;
+ char zContext[100];
+ char *hit;
+
+ sprintf(zContext, "Page %d: ", iPage);
+
+ /* Check that the page exists
+ */
+ cur.pBt = pBt = pCheck->pBt;
+ usableSize = pBt->usableSize;
+ if( iPage==0 ) return 0;
+ if( checkRef(pCheck, iPage, zParentContext) ) return 0;
+ if( (rc = getPage(pBt, (Pgno)iPage, &pPage))!=0 ){
+ checkAppendMsg(pCheck, zContext,
+ "unable to get the page. error code=%d", rc);
+ return 0;
+ }
+ if( (rc = initPage(pPage, pParent))!=0 ){
+ checkAppendMsg(pCheck, zContext, "initPage() returns error code %d", rc);
+ releasePage(pPage);
+ return 0;
+ }
+
+ /* Check out all the cells.
+ */
+ depth = 0;
+ cur.pPage = pPage;
+ for(i=0; i<pPage->nCell; i++){
+ u8 *pCell;
+ int sz;
+ CellInfo info;
+
+ /* Check payload overflow pages
+ */
+ sprintf(zContext, "On tree page %d cell %d: ", iPage, i);
+ pCell = findCell(pPage,i);
+ parseCellPtr(pPage, pCell, &info);
+ sz = info.nData;
+ if( !pPage->intKey ) sz += info.nKey;
+ 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,0,0,0,0);
+ if( i>0 && d2!=depth ){
+ checkAppendMsg(pCheck, zContext, "Child page depth differs");
+ }
+ depth = d2;
+ }
+ }
+ if( !pPage->leaf ){
+ pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]);
+ sprintf(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,0,0,0,0);
+ }
+
+ /* Check for complete coverage of the page
+ */
+ data = pPage->aData;
+ hdr = pPage->hdrOffset;
+ hit = sqliteMalloc( 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]);
+ int 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);
+ }
+ }
+ sqliteFree(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.
+*/
+char *sqlite3BtreeIntegrityCheck(Btree *pBt, int *aRoot, int nRoot){
+ int i;
+ int nRef;
+ IntegrityCk sCheck;
+
+ nRef = *sqlite3pager_stats(pBt->pPager);
+ if( lockBtreeWithRetry(pBt)!=SQLITE_OK ){
+ return sqliteStrDup("Unable to acquire a read lock on the database");
+ }
+ sCheck.pBt = pBt;
+ sCheck.pPager = pBt->pPager;
+ sCheck.nPage = sqlite3pager_pagecount(sCheck.pPager);
+ if( sCheck.nPage==0 ){
+ unlockBtreeIfUnused(pBt);
+ return 0;
+ }
+ sCheck.anRef = sqliteMallocRaw( (sCheck.nPage+1)*sizeof(sCheck.anRef[0]) );
+ if( !sCheck.anRef ){
+ unlockBtreeIfUnused(pBt);
+ return sqlite3MPrintf("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; 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: ", 0,0,0,0);
+ }
+
+ /* Make sure every page in the file is referenced
+ */
+ for(i=1; i<=sCheck.nPage; 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->usableSize, i)!=i || !pBt->autoVacuum) ){
+ checkAppendMsg(&sCheck, 0, "Page %d is never used", i);
+ }
+ if( sCheck.anRef[i]!=0 &&
+ (PTRMAP_PAGENO(pBt->usableSize, 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 != *sqlite3pager_stats(pBt->pPager) ){
+ checkAppendMsg(&sCheck, 0,
+ "Outstanding page count goes from %d to %d during this analysis",
+ nRef, *sqlite3pager_stats(pBt->pPager)
+ );
+ }
+
+ /* Clean up and report errors.
+ */
+ sqliteFree(sCheck.anRef);
+ return sCheck.zErrMsg;
+}
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+/*
+** Return the full pathname of the underlying database file.
+*/
+const char *sqlite3BtreeGetFilename(Btree *pBt){
+ assert( pBt->pPager!=0 );
+ return sqlite3pager_filename(pBt->pPager);
+}
+
+/*
+** Return the pathname of the directory that contains the database file.
+*/
+const char *sqlite3BtreeGetDirname(Btree *pBt){
+ assert( pBt->pPager!=0 );
+ return sqlite3pager_dirname(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.
+*/
+const char *sqlite3BtreeGetJournalname(Btree *pBt){
+ assert( pBt->pPager!=0 );
+ return sqlite3pager_journalname(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 pBtFrom may be reduced by this operation.
+** If anything goes wrong, the transaction on pBtFrom is rolled back.
+*/
+int sqlite3BtreeCopyFile(Btree *pBtTo, Btree *pBtFrom){
+ int rc = SQLITE_OK;
+ Pgno i, nPage, nToPage, iSkip;
+
+ if( pBtTo->inTrans!=TRANS_WRITE || pBtFrom->inTrans!=TRANS_WRITE ){
+ return SQLITE_ERROR;
+ }
+ if( pBtTo->pCursor ) return SQLITE_BUSY;
+ nToPage = sqlite3pager_pagecount(pBtTo->pPager);
+ nPage = sqlite3pager_pagecount(pBtFrom->pPager);
+ iSkip = PENDING_BYTE_PAGE(pBtTo);
+ for(i=1; rc==SQLITE_OK && i<=nPage; i++){
+ void *pPage;
+ if( i==iSkip ) continue;
+ rc = sqlite3pager_get(pBtFrom->pPager, i, &pPage);
+ if( rc ) break;
+ rc = sqlite3pager_overwrite(pBtTo->pPager, i, pPage);
+ if( rc ) break;
+ sqlite3pager_unref(pPage);
+ }
+ for(i=nPage+1; rc==SQLITE_OK && i<=nToPage; i++){
+ void *pPage;
+ if( i==iSkip ) continue;
+ rc = sqlite3pager_get(pBtTo->pPager, i, &pPage);
+ if( rc ) break;
+ rc = sqlite3pager_write(pPage);
+ sqlite3pager_unref(pPage);
+ sqlite3pager_dont_write(pBtTo->pPager, i);
+ }
+ if( !rc && nPage<nToPage ){
+ rc = sqlite3pager_truncate(pBtTo->pPager, nPage);
+ }
+ if( rc ){
+ sqlite3BtreeRollback(pBtTo);
+ }
+ return rc;
+}
+#endif /* SQLITE_OMIT_VACUUM */
+
+/*
+** Return non-zero if a transaction is active.
+*/
+int sqlite3BtreeIsInTrans(Btree *pBt){
+ return (pBt && (pBt->inTrans==TRANS_WRITE));
+}
+
+/*
+** Return non-zero if a statement transaction is active.
+*/
+int sqlite3BtreeIsInStmt(Btree *pBt){
+ return (pBt && pBt->inStmt);
+}
+
+/*
+** 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.
+*/
+int sqlite3BtreeSync(Btree *pBt, const char *zMaster){
+ if( pBt->inTrans==TRANS_WRITE ){
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ Pgno nTrunc = 0;
+ if( pBt->autoVacuum ){
+ int rc = autoVacuumCommit(pBt, &nTrunc);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ return sqlite3pager_sync(pBt->pPager, zMaster, nTrunc);
+#endif
+ return sqlite3pager_sync(pBt->pPager, zMaster, 0);
+ }
+ return SQLITE_OK;
+}
+
+#ifndef SQLITE_OMIT_GLOBALRECOVER
+/*
+** Reset the btree and underlying pager after a malloc() failure. Any
+** transaction that was active when malloc() failed is rolled back.
+*/
+int sqlite3BtreeReset(Btree *pBt){
+ if( pBt->pCursor ) return SQLITE_BUSY;
+ pBt->inTrans = TRANS_NONE;
+ unlockBtreeIfUnused(pBt);
+ return sqlite3pager_reset(pBt->pPager);
+}
+#endif
+
+/* js */
+int sqlite3_is_readonly(sqlite3* db) {
+ return db->aDb->pBt->readOnly;
+}
diff --git a/kexi/3rdparty/kexisql3/src/btree.h b/kexi/3rdparty/kexisql3/src/btree.h
new file mode 100644
index 000000000..a83b96425
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/btree.h
@@ -0,0 +1,146 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#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
+
+/*
+** Forward declarations of structure
+*/
+typedef struct Btree Btree;
+typedef struct BtCursor BtCursor;
+
+
+int sqlite3BtreeOpen(
+ const char *zFilename, /* Name of database file to open */
+ Btree **, /* Return open Btree* here */
+ int flags, /* Flags */
+ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
+ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+);
+
+/* 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 */
+
+int sqlite3BtreeClose(Btree*);
+int sqlite3BtreeSetBusyHandler(Btree*,BusyHandler*);
+int sqlite3BtreeSetCacheSize(Btree*,int);
+int sqlite3BtreeSetSafetyLevel(Btree*,int);
+int sqlite3BtreeSyncDisabled(Btree*);
+int sqlite3BtreeSetPageSize(Btree*,int,int);
+int sqlite3BtreeGetPageSize(Btree*);
+int sqlite3BtreeGetReserve(Btree*);
+int sqlite3BtreeSetAutoVacuum(Btree *, int);
+int sqlite3BtreeGetAutoVacuum(Btree *);
+int sqlite3BtreeBeginTrans(Btree*,int);
+int sqlite3BtreeCommit(Btree*);
+int sqlite3BtreeRollback(Btree*);
+int sqlite3BtreeBeginStmt(Btree*);
+int sqlite3BtreeCommitStmt(Btree*);
+int sqlite3BtreeRollbackStmt(Btree*);
+int sqlite3BtreeCreateTable(Btree*, int*, int flags);
+int sqlite3BtreeIsInTrans(Btree*);
+int sqlite3BtreeIsInStmt(Btree*);
+int sqlite3BtreeSync(Btree*, const char *zMaster);
+int sqlite3BtreeReset(Btree *);
+
+const char *sqlite3BtreeGetFilename(Btree *);
+const char *sqlite3BtreeGetDirname(Btree *);
+const char *sqlite3BtreeGetJournalname(Btree *);
+int sqlite3BtreeCopyFile(Btree *, 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 */
+
+int sqlite3BtreeDropTable(Btree*, int, int*);
+int sqlite3BtreeClearTable(Btree*, int);
+int sqlite3BtreeGetMeta(Btree*, int idx, u32 *pValue);
+int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value);
+
+int sqlite3BtreeCursor(
+ Btree*, /* BTree containing table to open */
+ int iTable, /* Index of root page */
+ int wrFlag, /* 1 for writing. 0 for read-only */
+ int(*)(void*,int,const void*,int,const void*), /* Key comparison function */
+ void*, /* First argument to compare function */
+ BtCursor **ppCursor /* Returned cursor */
+);
+
+void sqlite3BtreeSetCompare(
+ BtCursor *,
+ int(*)(void*,int,const void*,int,const void*),
+ void*
+);
+
+int sqlite3BtreeCloseCursor(BtCursor*);
+int sqlite3BtreeMoveto(BtCursor*, const void *pKey, i64 nKey, int *pRes);
+int sqlite3BtreeDelete(BtCursor*);
+int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey,
+ const void *pData, int nData);
+int sqlite3BtreeFirst(BtCursor*, int *pRes);
+int sqlite3BtreeLast(BtCursor*, int *pRes);
+int sqlite3BtreeNext(BtCursor*, int *pRes);
+int sqlite3BtreeEof(BtCursor*);
+int sqlite3BtreeFlags(BtCursor*);
+int sqlite3BtreePrevious(BtCursor*, int *pRes);
+int sqlite3BtreeKeySize(BtCursor*, i64 *pSize);
+int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*);
+const void *sqlite3BtreeKeyFetch(BtCursor*, int *pAmt);
+const void *sqlite3BtreeDataFetch(BtCursor*, int *pAmt);
+int sqlite3BtreeDataSize(BtCursor*, u32 *pSize);
+int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*);
+
+char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot);
+struct Pager *sqlite3BtreePager(Btree*);
+
+
+#ifdef SQLITE_TEST
+int sqlite3BtreeCursorInfo(BtCursor*, int*, int);
+void sqlite3BtreeCursorList(Btree*);
+#endif
+
+#ifdef SQLITE_DEBUG
+int sqlite3BtreePageDump(Btree*, int, int recursive);
+#else
+#define sqlite3BtreePageDump(X,Y,Z) SQLITE_OK
+#endif
+
+#endif /* _BTREE_H_ */
diff --git a/kexi/3rdparty/kexisql3/src/build.c b/kexi/3rdparty/kexisql3/src/build.c
new file mode 100644
index 000000000..429213dab
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/build.c
@@ -0,0 +1,2938 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include "sqliteInt.h"
+#include <ctype.h>
+
+/*
+** This routine is called when a new SQL statement is beginning to
+** be parsed. Initialize the pParse structure as needed.
+*/
+void sqlite3BeginParse(Parse *pParse, int explainFlag){
+ pParse->explain = explainFlag;
+ pParse->nVar = 0;
+}
+
+/*
+** 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.
+*/
+void sqlite3FinishCoding(Parse *pParse){
+ sqlite3 *db;
+ Vdbe *v;
+
+ if( sqlite3_malloc_failed ) return;
+ if( pParse->nested ) 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
+ */
+ db = pParse->db;
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp(v, OP_Halt, 0, 0);
+
+ /* 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;
+ sqlite3VdbeAddOp(v, OP_Transaction, iDb, (mask & pParse->writeMask)!=0);
+ sqlite3VdbeAddOp(v, OP_VerifyCookie, iDb, pParse->cookieValue[iDb]);
+ }
+ sqlite3VdbeAddOp(v, OP_Goto, 0, pParse->cookieGoto);
+ }
+
+#ifndef SQLITE_OMIT_TRACE
+ /* Add a No-op that contains the complete text of the compiled SQL
+ ** statement as its P3 argument. This does not change the functionality
+ ** of the program.
+ **
+ ** This is used to implement sqlite3_trace().
+ */
+ sqlite3VdbeOp3(v, OP_Noop, 0, 0, pParse->zSql, pParse->zTail-pParse->zSql);
+#endif /* SQLITE_OMIT_TRACE */
+ }
+
+
+ /* Get the VDBE program ready for execution
+ */
+ if( v && pParse->nErr==0 ){
+ FILE *trace = (db->flags & SQLITE_VdbeTrace)!=0 ? stdout : 0;
+ sqlite3VdbeTrace(v, trace);
+ 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.
+*/
+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(zFormat, ap);
+ va_end(ap);
+ if( zSql==0 ){
+ return; /* A malloc must have failed */
+ }
+ pParse->nested++;
+ memcpy(saveBuf, &pParse->nVar, SAVE_SZ);
+ memset(&pParse->nVar, 0, SAVE_SZ);
+ sqlite3RunParser(pParse, zSql, 0);
+ sqliteFree(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().
+*/
+Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){
+ Table *p = 0;
+ int i;
+ assert( zName!=0 );
+ assert( (db->flags & SQLITE_Initialized) || db->init.busy );
+ 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].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.
+*/
+Table *sqlite3LocateTable(Parse *pParse, const char *zName, const char *zDbase){
+ 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 ){
+ if( zDbase ){
+ sqlite3ErrorMsg(pParse, "no such table: %s.%s", zDbase, zName);
+ }else{
+ sqlite3ErrorMsg(pParse, "no such table: %s", 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.
+*/
+Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){
+ Index *p = 0;
+ int i;
+ assert( (db->flags & SQLITE_Initialized) || db->init.busy );
+ for(i=OMIT_TEMPDB; i<db->nDb; i++){
+ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
+ if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zName) ) continue;
+ p = sqlite3HashFind(&db->aDb[j].idxHash, zName, strlen(zName)+1);
+ if( p ) break;
+ }
+ return p;
+}
+
+/*
+** Reclaim the memory used by an index
+*/
+static void freeIndex(Index *p){
+ sqliteFree(p->zColAff);
+ sqliteFree(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(sqlite3 *db, Index *p){
+ Index *pOld;
+
+ assert( db!=0 && p->zName!=0 );
+ pOld = sqlite3HashInsert(&db->aDb[p->iDb].idxHash, p->zName,
+ strlen(p->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.
+*/
+void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){
+ Index *pIndex;
+ int len;
+
+ len = strlen(zIdxName);
+ pIndex = sqlite3HashInsert(&db->aDb[iDb].idxHash, 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.
+*/
+void sqlite3ResetInternalSchema(sqlite3 *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;
+ sqlite3HashInit(&pDb->trigHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashClear(&pDb->aFKey);
+ sqlite3HashClear(&pDb->idxHash);
+ for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
+ sqlite3DeleteTrigger((Trigger*)sqliteHashData(pElem));
+ }
+ sqlite3HashClear(&temp2);
+ sqlite3HashInit(&pDb->tblHash, SQLITE_HASH_STRING, 0);
+ for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
+ Table *pTab = sqliteHashData(pElem);
+ sqlite3DeleteTable(db, pTab);
+ }
+ sqlite3HashClear(&temp1);
+ pDb->pSeqTab = 0;
+ 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 sqlite3RollbackInternalChanges(sqlite3 *db){
+ if( db->flags & SQLITE_InternChanges ){
+ sqlite3ResetInternalSchema(db, 0);
+ }
+}
+
+/*
+** This routine is called when a commit occurs.
+*/
+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++){
+ sqliteFree(pCol->zName);
+ sqlite3ExprDelete(pCol->pDflt);
+ sqliteFree(pCol->zType);
+ }
+ sqliteFree(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.
+**
+** 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 sqlite3DeleteTable(sqlite3 *db, 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->iDb==pTable->iDb || (pTable->iDb==0 && pIndex->iDb==1) );
+ sqliteDeleteIndex(db, pIndex);
+ }
+
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ /* 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( sqlite3HashFind(&db->aDb[pTable->iDb].aFKey,
+ pFKey->zTo, strlen(pFKey->zTo)+1)!=pFKey );
+ sqliteFree(pFKey);
+ }
+#endif
+
+ /* Delete the Table structure itself.
+ */
+ sqliteResetColumnNames(pTable);
+ sqliteFree(pTable->zName);
+ sqliteFree(pTable->zColAff);
+ sqlite3SelectDelete(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.
+*/
+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->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->aFKey, pF1->zTo, nTo);
+ if( pF2==pF1 ){
+ sqlite3HashInsert(&pDb->aFKey, pF1->zTo, nTo, pF1->pNextTo);
+ }else{
+ while( pF2 && pF2->pNextTo!=pF1 ){ pF2=pF2->pNextTo; }
+ if( pF2 ){
+ pF2->pNextTo = pF1->pNextTo;
+ }
+ }
+ }
+#endif
+ sqlite3DeleteTable(db, 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.
+*/
+char *sqlite3NameFromToken(Token *pName){
+ char *zName;
+ if( pName ){
+ zName = sqliteStrNDup(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.
+*/
+void sqlite3OpenMasterTable(Vdbe *v, int iDb){
+ sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
+ sqlite3VdbeAddOp(v, OP_OpenWrite, 0, MASTER_ROOT);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, 0, 5); /* sqlite_master has 5 columns */
+}
+
+/*
+** 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.
+*/
+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(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;
+ }
+ }
+ sqliteFree(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.
+*/
+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.
+*/
+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
+** 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 sqlite3EndTable() routine
+** is called to complete the construction of the new table record.
+*/
+void sqlite3StartTable(
+ Parse *pParse, /* Parser context */
+ Token *pStart, /* The "CREATE" token */
+ 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 */
+){
+ 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(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( 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.
+ */
+ if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ goto begin_table_error;
+ }
+ pTable = sqlite3FindTable(db, zName, db->aDb[iDb].zName);
+ if( pTable ){
+ 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 = sqliteMalloc( sizeof(Table) );
+ if( pTable==0 ){
+ pParse->rc = SQLITE_NOMEM;
+ pParse->nErr++;
+ goto begin_table_error;
+ }
+ pTable->zName = zName;
+ pTable->nCol = 0;
+ pTable->aCol = 0;
+ pTable->iPKey = -1;
+ pTable->pIndex = 0;
+ pTable->iDb = iDb;
+ pTable->nRef = 1;
+ if( pParse->pNewTable ) sqlite3DeleteTable(db, 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 ){
+ db->aDb[iDb].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 lbl;
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+
+ /* If the file format and encoding in the database have not been set,
+ ** set them now.
+ */
+ sqlite3VdbeAddOp(v, OP_ReadCookie, iDb, 1); /* file_format */
+ lbl = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp(v, OP_If, 0, lbl);
+ sqlite3VdbeAddOp(v, OP_Integer, db->file_format, 0);
+ sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 1);
+ sqlite3VdbeAddOp(v, OP_Integer, db->enc, 0);
+ sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 4);
+ sqlite3VdbeResolveLabel(v, lbl);
+
+ /* 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.
+ */
+#ifndef SQLITE_OMIT_VIEW
+ if( isView ){
+ sqlite3VdbeAddOp(v, OP_Integer, 0, 0);
+ }else
+#endif
+ {
+ sqlite3VdbeAddOp(v, OP_CreateTable, iDb, 0);
+ }
+ sqlite3OpenMasterTable(v, iDb);
+ sqlite3VdbeAddOp(v, OP_NewRowid, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Null, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Insert, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Close, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Pull, 1, 0);
+ }
+
+ /* Normal (non-error) return. */
+ return;
+
+ /* If an error occurs, we jump here */
+begin_table_error:
+ sqliteFree(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.
+*/
+void sqlite3AddColumn(Parse *pParse, Token *pName){
+ Table *p;
+ int i;
+ char *z;
+ Column *pCol;
+ if( (p = pParse->pNewTable)==0 ) return;
+ z = sqlite3NameFromToken(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);
+ sqliteFree(z);
+ return;
+ }
+ }
+ if( (p->nCol & 0x7)==0 ){
+ Column *aNew;
+ aNew = sqliteRealloc( p->aCol, (p->nCol+8)*sizeof(p->aCol[0]));
+ if( aNew==0 ){
+ sqliteFree(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;
+ pCol->pColl = pParse->db->pDfltColl;
+ 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 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
+**
+** If none of the substrings in the above table are found,
+** SQLITE_AFF_NUMERIC is returned.
+*/
+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_NONE;
+ }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.
+*/
+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];
+ sqliteFree(pCol->zType);
+ pCol->zType = sqlite3NameFromToken(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.
+*/
+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{
+ sqlite3ExprDelete(pCol->pDflt);
+ pCol->pDflt = sqlite3ExprDup(pExpr);
+ }
+ }
+ 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.
+*/
+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 */
+){
+ Table *pTab = pParse->pNewTable;
+ char *zType = 0;
+ int iCol = -1, i;
+ if( pTab==0 ) 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 ){
+ 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);
+ pList = 0;
+ }
+
+primary_key_exit:
+ sqlite3ExprListDelete(pList);
+ return;
+}
+
+/*
+** Set the collation function of the most recently parsed table column
+** to the CollSeq given.
+*/
+void sqlite3AddCollateType(Parse *pParse, const char *zType, int nType){
+ Table *p;
+ Index *pIdx;
+ CollSeq *pColl;
+ int i;
+
+ if( (p = pParse->pNewTable)==0 ) return;
+ i = p->nCol-1;
+
+ pColl = sqlite3LocateCollSeq(pParse, zType, nType);
+ p->aCol[i].pColl = pColl;
+
+ /* 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->keyInfo.aColl[0] = pColl;
+ }
+}
+
+/*
+** Call sqlite3CheckCollSeq() for all collating sequences in an index,
+** in order to verify that all the necessary collating sequences are
+** loaded.
+*/
+int sqlite3CheckIndexCollSeq(Parse *pParse, Index *pIdx){
+ if( pIdx ){
+ int i;
+ for(i=0; i<pIdx->nColumn; i++){
+ if( sqlite3CheckCollSeq(pParse, pIdx->keyInfo.aColl[i]) ){
+ return SQLITE_ERROR;
+ }
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** 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.
+*/
+CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName, int nName){
+ sqlite3 *db = pParse->db;
+ u8 enc = db->enc;
+ u8 initbusy = db->init.busy;
+
+ CollSeq *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.
+*/
+void sqlite3ChangeCookie(sqlite3 *db, Vdbe *v, int iDb){
+ sqlite3VdbeAddOp(v, OP_Integer, db->aDb[iDb].schema_cookie+1, 0);
+ sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 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.
+**
+** 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(Table *p){
+ 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 = sqliteMallocRaw( n );
+ if( zStmt==0 ) return 0;
+ strcpy(zStmt, !OMIT_TEMPDB&&p->iDb==1 ? "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++){
+ strcpy(&zStmt[k], zSep);
+ k += strlen(&zStmt[k]);
+ zSep = zSep2;
+ identPut(zStmt, &k, pCol->zName);
+ if( (z = pCol->zType)!=0 ){
+ zStmt[k++] = ' ';
+ strcpy(&zStmt[k], z);
+ k += strlen(z);
+ }
+ }
+ 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 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.
+*/
+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;
+
+ if( (pEnd==0 && pSelect==0) || pParse->nErr || sqlite3_malloc_failed ) return;
+ p = pParse->pNewTable;
+ if( p==0 ) return;
+
+ assert( !db->init.busy || !pSelect );
+
+ /* 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;
+
+ sqlite3VdbeAddOp(v, OP_Close, 0, 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.
+ */
+ if( pSelect ){
+ Table *pSelTab;
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Integer, p->iDb, 0);
+ sqlite3VdbeAddOp(v, OP_OpenWrite, 1, 0);
+ pParse->nTab = 2;
+ sqlite3Select(pParse, pSelect, SRT_Table, 1, 0, 0, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Close, 1, 0);
+ 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(0, pSelTab);
+ }
+ }
+
+ /* Compute the complete text of the CREATE statement */
+ if( pSelect ){
+ zStmt = createTableStmt(p);
+ }else{
+ n = pEnd->z - pParse->sNameToken.z + 1;
+ zStmt = sqlite3MPrintf("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=#0, sql=%Q "
+ "WHERE rowid=#1",
+ db->aDb[p->iDb].zName, SCHEMA_TABLE(p->iDb),
+ zType,
+ p->zName,
+ p->zName,
+ zStmt
+ );
+ sqliteFree(zStmt);
+ sqlite3ChangeCookie(db, v, p->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[p->iDb];
+ if( pDb->pSeqTab==0 ){
+ sqlite3NestedParse(pParse,
+ "CREATE TABLE %Q.sqlite_sequence(name,seq)",
+ pDb->zName
+ );
+ }
+ }
+#endif
+
+ /* Reparse everything to update our internal data structures */
+ sqlite3VdbeOp3(v, OP_ParseSchema, p->iDb, 0,
+ sqlite3MPrintf("tbl_name='%q'",p->zName), P3_DYNAMIC);
+ }
+
+
+ /* Add the table to the in-memory representation of the database.
+ */
+ if( db->init.busy && pParse->nErr==0 ){
+ Table *pOld;
+ FKey *pFKey;
+ Db *pDb = &db->aDb[p->iDb];
+ pOld = sqlite3HashInsert(&pDb->tblHash, p->zName, strlen(p->zName)+1, p);
+ if( pOld ){
+ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */
+ return;
+ }
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ for(pFKey=p->pFKey; pFKey; pFKey=pFKey->pNextFrom){
+ int nTo = strlen(pFKey->zTo) + 1;
+ pFKey->pNextTo = sqlite3HashFind(&pDb->aFKey, pFKey->zTo, nTo);
+ sqlite3HashInsert(&pDb->aFKey, pFKey->zTo, nTo, pFKey);
+ }
+#endif
+ pParse->pNewTable = 0;
+ db->nTable++;
+ db->flags |= SQLITE_InternChanges;
+
+#ifndef SQLITE_OMIT_ALTERTABLE
+ if( !p->pSelect ){
+ assert( !pSelect && pCons && pEnd );
+ if( pCons->z==0 ) pCons = pEnd;
+ p->addColOffset = 13 + (pCons->z - pParse->sNameToken.z);
+ }
+#endif
+ }
+}
+
+#ifndef SQLITE_OMIT_VIEW
+/*
+** The parser calls this routine in order to create a new VIEW
+*/
+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 */
+){
+ Table *p;
+ int n;
+ const unsigned char *z;
+ Token sEnd;
+ DbFixer sFix;
+ Token *pName;
+
+ if( pParse->nVar>0 ){
+ sqlite3ErrorMsg(pParse, "parameters are not allowed in views");
+ sqlite3SelectDelete(pSelect);
+ return;
+ }
+ sqlite3StartTable(pParse, pBegin, pName1, pName2, isTemp, 1);
+ p = pParse->pNewTable;
+ if( p==0 || pParse->nErr ){
+ sqlite3SelectDelete(pSelect);
+ return;
+ }
+ sqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ if( sqlite3FixInit(&sFix, pParse, p->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(pSelect);
+ sqlite3SelectDelete(pSelect);
+ if( !pParse->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 */
+
+#ifndef SQLITE_OMIT_VIEW
+/*
+** 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 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 */
+
+ 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 0
+ if( pTable->nCol<0 ){
+ sqlite3ErrorMsg(pParse, "view %s is circularly defined", pTable->zName);
+ return 1;
+ }
+#endif
+ 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(pTable->pSelect);
+ n = pParse->nTab;
+ sqlite3SrcListAssignCursors(pParse, pSel->pSrc);
+ pTable->nCol = -1;
+ pSelTab = sqlite3ResultSetOfSelect(pParse, 0, pSel);
+ pParse->nTab = n;
+ if( pSelTab ){
+ assert( pTable->aCol==0 );
+ pTable->nCol = pSelTab->nCol;
+ pTable->aCol = pSelTab->aCol;
+ pSelTab->nCol = 0;
+ pSelTab->aCol = 0;
+ sqlite3DeleteTable(0, pSelTab);
+ DbSetProperty(pParse->db, pTable->iDb, DB_UnresetViews);
+ }else{
+ pTable->nCol = 0;
+ nErr++;
+ }
+ sqlite3SelectDelete(pSel);
+ return nErr;
+}
+#endif /* SQLITE_OMIT_VIEW */
+
+#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].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.
+*/
+#ifndef SQLITE_OMIT_AUTOVACUUM
+void sqlite3RootPageMoved(Db *pDb, int iFrom, int iTo){
+ HashElem *pElem;
+
+ for(pElem=sqliteHashFirst(&pDb->tblHash); pElem; pElem=sqliteHashNext(pElem)){
+ Table *pTab = sqliteHashData(pElem);
+ if( pTab->tnum==iFrom ){
+ pTab->tnum = iTo;
+ return;
+ }
+ }
+ for(pElem=sqliteHashFirst(&pDb->idxHash); pElem; pElem=sqliteHashNext(pElem)){
+ Index *pIdx = sqliteHashData(pElem);
+ if( pIdx->tnum==iFrom ){
+ pIdx->tnum = iTo;
+ return;
+ }
+ }
+ assert(0);
+}
+#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);
+ sqlite3VdbeAddOp(v, OP_Destroy, iTable, iDb);
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ /* OP_Destroy pushes an integer onto the stack. 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 "#0" 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 #0 AND rootpage=#0",
+ pParse->db->aDb[iDb].zName, SCHEMA_TABLE(iDb), iTable);
+#endif
+}
+
+/*
+** 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;
+ destroyRootPage(pParse, pTab->tnum, pTab->iDb);
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ destroyRootPage(pParse, pIdx->tnum, pIdx->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->iDb==pTab->iDb );
+ if( (iDestroyed==0 || (iIdx<iDestroyed)) && iIdx>iLargest ){
+ iLargest = iIdx;
+ }
+ }
+ if( iLargest==0 ) return;
+ destroyRootPage(pParse, iLargest, pTab->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.
+*/
+void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView){
+ Table *pTab;
+ Vdbe *v;
+ sqlite3 *db = pParse->db;
+ int iDb;
+
+ if( pParse->nErr || sqlite3_malloc_failed ) goto exit_drop_table;
+ assert( pName->nSrc==1 );
+ pTab = sqlite3LocateTable(pParse, pName->a[0].zName, pName->a[0].zDatabase);
+
+ if( pTab==0 ) goto exit_drop_table;
+ iDb = pTab->iDb;
+ assert( iDb>=0 && iDb<db->nDb );
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code;
+ const char *zTab = SCHEMA_TABLE(pTab->iDb);
+ const char *zDb = db->aDb[pTab->iDb].zName;
+ 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;
+ }
+ }else{
+ if( !OMIT_TEMPDB && iDb==1 ){
+ code = SQLITE_DROP_TEMP_TABLE;
+ }else{
+ code = SQLITE_DROP_TABLE;
+ }
+ }
+ if( sqlite3AuthCheck(pParse, code, pTab->zName, 0, 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].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;
+ int iDb = pTab->iDb;
+ Db *pDb = &db->aDb[iDb];
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+
+ /* 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->iDb==iDb || pTrigger->iDb==1 );
+ sqlite3DropTriggerPtr(pParse, pTrigger, 1);
+ 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);
+ if( !isView ){
+ destroyTable(pParse, pTab);
+ }
+
+ /* Remove the table entry from SQLite's internal schema and modify
+ ** the schema cookie.
+ */
+ sqlite3VdbeOp3(v, OP_DropTable, iDb, 0, pTab->zName, 0);
+ sqlite3ChangeCookie(db, v, 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.
+*/
+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 ) 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 = 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( 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:
+ sqliteFree(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.
+*/
+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 memory cell 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 */
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0,
+ pParse->db->aDb[pIndex->iDb].zName ) ){
+ return;
+ }
+#endif
+
+ /* Ensure all the required collation sequences are available. This
+ ** routine will invoke the collation-needed callback if necessary (and
+ ** if one has been registered).
+ */
+ if( sqlite3CheckIndexCollSeq(pParse, pIndex) ){
+ return;
+ }
+
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+ if( memRootPage>=0 ){
+ sqlite3VdbeAddOp(v, OP_MemLoad, memRootPage, 0);
+ tnum = 0;
+ }else{
+ tnum = pIndex->tnum;
+ sqlite3VdbeAddOp(v, OP_Clear, tnum, pIndex->iDb);
+ }
+ sqlite3VdbeAddOp(v, OP_Integer, pIndex->iDb, 0);
+ sqlite3VdbeOp3(v, OP_OpenWrite, iIdx, tnum,
+ (char*)&pIndex->keyInfo, P3_KEYINFO);
+ sqlite3OpenTableForReading(v, iTab, pTab);
+ addr1 = sqlite3VdbeAddOp(v, OP_Rewind, iTab, 0);
+ sqlite3GenerateIndexKey(v, pIndex, iTab);
+ if( pIndex->onError!=OE_None ){
+ int curaddr = sqlite3VdbeCurrentAddr(v);
+ int addr2 = curaddr+4;
+ sqlite3VdbeChangeP2(v, curaddr-1, addr2);
+ sqlite3VdbeAddOp(v, OP_Rowid, iTab, 0);
+ sqlite3VdbeAddOp(v, OP_AddImm, 1, 0);
+ sqlite3VdbeAddOp(v, OP_IsUnique, iIdx, addr2);
+ sqlite3VdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, OE_Abort,
+ "indexed columns are not unique", P3_STATIC);
+ assert( addr2==sqlite3VdbeCurrentAddr(v) );
+ }
+ sqlite3VdbeAddOp(v, OP_IdxInsert, iIdx, 0);
+ sqlite3VdbeAddOp(v, OP_Next, iTab, addr1+1);
+ sqlite3VdbeJumpHere(v, addr1);
+ sqlite3VdbeAddOp(v, OP_Close, iTab, 0);
+ sqlite3VdbeAddOp(v, OP_Close, iIdx, 0);
+}
+
+/*
+** 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.
+*/
+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 a CREATE TABLE statement */
+ Token *pEnd /* The ")" that closes the CREATE INDEX statement */
+){
+ Table *pTab = 0; /* Table to be indexed */
+ Index *pIndex = 0; /* 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 */
+ sqlite3 *db = pParse->db;
+
+ int iDb; /* Index of the database that is being written */
+ Token *pName = 0; /* Unqualified name of the index to create */
+
+ if( pParse->nErr || sqlite3_malloc_failed ) 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.
+ */
+ pTab = sqlite3SrcListLookup(pParse, pTblName);
+ if( pName2 && pName2->n==0 && pTab && pTab->iDb==1 ){
+ 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, pTblName->a[0].zName,
+ pTblName->a[0].zDatabase);
+ if( !pTab ) goto exit_create_index;
+ assert( iDb==pTab->iDb );
+ }else{
+ assert( pName==0 );
+ pTab = pParse->pNewTable;
+ iDb = pTab->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
+
+ /*
+ ** 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(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( sqlite3FindIndex(db, zName, db->aDb[iDb].zName)!=0 ){
+ sqlite3ErrorMsg(pParse, "index %s already exists", zName);
+ goto exit_create_index;
+ }
+ if( sqlite3FindTable(db, zName, 0)!=0 ){
+ sqlite3ErrorMsg(pParse, "there is already a table named %s", zName);
+ goto exit_create_index;
+ }
+ }
+ }else{
+ char zBuf[30];
+ int n;
+ Index *pLoop;
+ for(pLoop=pTab->pIndex, n=1; pLoop; pLoop=pLoop->pNext, n++){}
+ sprintf(zBuf,"_%d",n);
+ zName = 0;
+ sqlite3SetString(&zName, "sqlite_autoindex_", pTab->zName, zBuf, (char*)0);
+ if( zName==0 ) goto exit_create_index;
+ }
+
+ /* Check for authorization to create an index.
+ */
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ const char *zDb = db->aDb[iDb].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 = pTab->aCol[pTab->nCol-1].zName;
+ nullId.n = strlen(nullId.z);
+ pList = sqlite3ExprListAppend(0, 0, &nullId);
+ if( pList==0 ) goto exit_create_index;
+ }
+
+ /*
+ ** Allocate the index structure.
+ */
+ pIndex = sqliteMalloc( sizeof(Index) + strlen(zName) + 1 + sizeof(int) +
+ (sizeof(int)*2 + sizeof(CollSeq*))*pList->nExpr );
+ if( sqlite3_malloc_failed ) goto exit_create_index;
+ pIndex->aiColumn = (int*)&pIndex->keyInfo.aColl[pList->nExpr];
+ pIndex->aiRowEst = (unsigned*)&pIndex->aiColumn[pList->nExpr];
+ pIndex->zName = (char*)&pIndex->aiRowEst[pList->nExpr+1];
+ strcpy(pIndex->zName, zName);
+ pIndex->pTable = pTab;
+ pIndex->nColumn = pList->nExpr;
+ pIndex->onError = onError;
+ pIndex->autoIndex = pName==0;
+ pIndex->iDb = 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->nExpr; i++){
+ for(j=0; j<pTab->nCol; j++){
+ if( sqlite3StrICmp(pList->a[i].zName, pTab->aCol[j].zName)==0 ) break;
+ }
+ if( j>=pTab->nCol ){
+ sqlite3ErrorMsg(pParse, "table %s has no column named %s",
+ pTab->zName, pList->a[i].zName);
+ goto exit_create_index;
+ }
+ pIndex->aiColumn[i] = j;
+ if( pList->a[i].pExpr ){
+ assert( pList->a[i].pExpr->pColl );
+ pIndex->keyInfo.aColl[i] = pList->a[i].pExpr->pColl;
+ }else{
+ pIndex->keyInfo.aColl[i] = pTab->aCol[j].pColl;
+ }
+ assert( pIndex->keyInfo.aColl[i] );
+ if( !db->init.busy &&
+ sqlite3CheckCollSeq(pParse, pIndex->keyInfo.aColl[i])
+ ){
+ goto exit_create_index;
+ }
+ }
+ pIndex->keyInfo.nField = pList->nExpr;
+ 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++){
+ if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break;
+ if( pIdx->keyInfo.aColl[k]!=pIndex->keyInfo.aColl[k] ) 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(&db->aDb[pIndex->iDb].idxHash,
+ pIndex->zName, strlen(pIndex->zName)+1, pIndex);
+ if( p ){
+ assert( p==pIndex ); /* Malloc must have failed */
+ 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);
+ sqlite3VdbeAddOp(v, OP_CreateIndex, iDb, 0);
+ sqlite3VdbeAddOp(v, OP_MemStore, iMem, 0);
+
+ /* 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("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,#0,%Q);",
+ db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
+ pIndex->zName,
+ pTab->zName,
+ zStmt
+ );
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteFree(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(db, v, iDb);
+ sqlite3VdbeOp3(v, OP_ParseSchema, iDb, 0,
+ sqlite3MPrintf("name='%q'", pIndex->zName), P3_DYNAMIC);
+ sqlite3VdbeAddOp(v, OP_Expire, 0, 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);
+ sqliteFree(zName);
+ return;
+}
+
+/*
+** 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.
+*/
+void sqlite3DefaultRowEst(Index *pIdx){
+ unsigned *a = pIdx->aiRowEst;
+ int i;
+ assert( a!=0 );
+ a[0] = 1000000;
+ for(i=pIdx->nColumn; i>=1; i--){
+ a[i] = 10;
+ }
+ if( pIdx->onError!=OE_None ){
+ a[pIdx->nColumn] = 1;
+ }
+}
+
+/*
+** This routine will drop an existing named index. This routine
+** implements the DROP INDEX statement.
+*/
+void sqlite3DropIndex(Parse *pParse, SrcList *pName){
+ Index *pIndex;
+ Vdbe *v;
+ sqlite3 *db = pParse->db;
+
+ if( pParse->nErr || sqlite3_malloc_failed ){
+ 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 ){
+ 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;
+ }
+#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( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
+ goto exit_drop_index;
+ }
+ if( !OMIT_TEMPDB && pIndex->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 ){
+ int iDb = pIndex->iDb;
+ sqlite3NestedParse(pParse,
+ "DELETE FROM %Q.%s WHERE name=%Q",
+ db->aDb[iDb].zName, SCHEMA_TABLE(iDb),
+ pIndex->zName
+ );
+ sqlite3ChangeCookie(db, v, iDb);
+ destroyRootPage(pParse, pIndex->tnum, iDb);
+ sqlite3VdbeOp3(v, OP_DropIndex, iDb, 0, pIndex->zName, 0);
+ }
+
+exit_drop_index:
+ sqlite3SrcListDelete(pName);
+}
+
+/*
+** ppArray points into a structure where there is an array pointer
+** followed by two integers. The first integer is the
+** number of elements in the structure array. The second integer
+** is the number of allocated slots in the array.
+**
+** In other words, the structure looks something like this:
+**
+** struct Example1 {
+** struct subElem *aEntry;
+** int nEntry;
+** int nAlloc;
+** }
+**
+** The pnEntry parameter points to the equivalent of Example1.nEntry.
+**
+** This routine allocates a new slot in the array, zeros it out,
+** and returns its index. If malloc fails a negative number is returned.
+**
+** szEntry is the sizeof of a single array entry. initSize is the
+** number of array entries allocated on the initial allocation.
+*/
+int sqlite3ArrayAllocate(void **ppArray, int szEntry, int initSize){
+ char *p;
+ int *an = (int*)&ppArray[1];
+ if( an[0]>=an[1] ){
+ void *pNew;
+ int newSize;
+ newSize = an[1]*2 + initSize;
+ pNew = sqliteRealloc(*ppArray, newSize*szEntry);
+ if( pNew==0 ){
+ return -1;
+ }
+ an[1] = newSize;
+ *ppArray = pNew;
+ }
+ p = *ppArray;
+ memset(&p[an[0]*szEntry], 0, szEntry);
+ return an[0]++;
+}
+
+/*
+** 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 *sqlite3IdListAppend(IdList *pList, Token *pToken){
+ int i;
+ if( pList==0 ){
+ pList = sqliteMalloc( sizeof(IdList) );
+ if( pList==0 ) return 0;
+ pList->nAlloc = 0;
+ }
+ i = sqlite3ArrayAllocate((void**)&pList->a, sizeof(pList->a[0]), 5);
+ if( i<0 ){
+ sqlite3IdListDelete(pList);
+ return 0;
+ }
+ pList->a[i].zName = sqlite3NameFromToken(pToken);
+ return pList;
+}
+
+/*
+** Delete an IdList.
+*/
+void sqlite3IdListDelete(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 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(A,B,0);
+**
+** Then B is a table name and the database name is unspecified. If called
+** like this:
+**
+** sqlite3SrcListAppend(A,B,C);
+**
+** Then C is the table name and B is the database name.
+*/
+SrcList *sqlite3SrcListAppend(SrcList *pList, Token *pTable, Token *pDatabase){
+ struct SrcList_item *pItem;
+ 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 ){
+ 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(pTable);
+ pItem->zDatabase = sqlite3NameFromToken(pDatabase);
+ pItem->iCursor = -1;
+ pList->nSrc++;
+ return pList;
+}
+
+/*
+** Assign cursors to all tables in a SrcList
+*/
+void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
+ int i;
+ struct SrcList_item *pItem;
+ 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);
+ }
+ }
+}
+
+/*
+** Add an alias to the last identifier on the given identifier list.
+*/
+void sqlite3SrcListAddAlias(SrcList *pList, Token *pToken){
+ if( pList && pList->nSrc>0 ){
+ pList->a[pList->nSrc-1].zAlias = sqlite3NameFromToken(pToken);
+ }
+}
+
+/*
+** Delete an entire SrcList including all its substructure.
+*/
+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++){
+ sqliteFree(pItem->zDatabase);
+ sqliteFree(pItem->zName);
+ sqliteFree(pItem->zAlias);
+ sqlite3DeleteTable(0, pItem->pTab);
+ sqlite3SelectDelete(pItem->pSelect);
+ sqlite3ExprDelete(pItem->pOn);
+ sqlite3IdListDelete(pItem->pUsing);
+ }
+ sqliteFree(pList);
+}
+
+/*
+** Begin a transaction
+*/
+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 || sqlite3_malloc_failed ) 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++){
+ sqlite3VdbeAddOp(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
+ }
+ }
+ sqlite3VdbeAddOp(v, OP_AutoCommit, 0, 0);
+}
+
+/*
+** Commit a transaction
+*/
+void sqlite3CommitTransaction(Parse *pParse){
+ sqlite3 *db;
+ Vdbe *v;
+
+ if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return;
+ if( pParse->nErr || sqlite3_malloc_failed ) return;
+ if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ) return;
+
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp(v, OP_AutoCommit, 1, 0);
+ }
+}
+
+/*
+** Rollback a transaction
+*/
+void sqlite3RollbackTransaction(Parse *pParse){
+ sqlite3 *db;
+ Vdbe *v;
+
+ if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return;
+ if( pParse->nErr || sqlite3_malloc_failed ) return;
+ if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ) return;
+
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp(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.
+*/
+static int sqlite3OpenTempDatabase(Parse *pParse){
+ sqlite3 *db = pParse->db;
+ if( db->aDb[1].pBt==0 && !pParse->explain ){
+ int rc = sqlite3BtreeFactory(db, 0, 0, MAX_PAGES, &db->aDb[1].pBt, 0 /*!exclusive*/, 1/*allowReadonly*/);
+ if( rc!=SQLITE_OK ){
+ sqlite3ErrorMsg(pParse, "unable to open a temporary database "
+ "file for storing temporary tables");
+ pParse->rc = rc;
+ return 1;
+ }
+ if( db->flags & !db->autoCommit ){
+ rc = sqlite3BtreeBeginTrans(db->aDb[1].pBt, 1);
+ if( rc!=SQLITE_OK ){
+ sqlite3ErrorMsg(pParse, "unable to get a write lock on "
+ "the temporary database file");
+ pParse->rc = rc;
+ return 1;
+ }
+ }
+ }
+ 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.
+*/
+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 = sqlite3VdbeAddOp(v, OP_Goto, 0, 0)+1;
+ }
+ if( iDb>=0 ){
+ assert( iDb<db->nDb );
+ assert( db->aDb[iDb].pBt!=0 || iDb==1 );
+ assert( iDb<32 );
+ mask = 1<<iDb;
+ if( (pParse->cookieMask & mask)==0 ){
+ pParse->cookieMask |= mask;
+ pParse->cookieValue[iDb] = db->aDb[iDb].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.
+*/
+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 ){
+ sqlite3VdbeAddOp(v, OP_Statement, iDb, 0);
+ }
+ 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(CollSeq *pColl, Index *pIndex){
+ int n = pIndex->keyInfo.nField;
+ CollSeq **pp = pIndex->keyInfo.aColl;
+ while( n-- ){
+ if( *pp==pColl ) return 1;
+ pp++;
+ }
+ 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, CollSeq *pColl){
+ Index *pIndex; /* An index associated with pTab */
+
+ for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){
+ if( pColl==0 || collationMatch(pColl,pIndex) ){
+ sqlite3BeginWriteOperation(pParse, 0, pTab->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, CollSeq *pColl){
+ 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++){
+ if( pDb==0 ) continue;
+ for(k=sqliteHashFirst(&pDb->tblHash); k; k=sqliteHashNext(k)){
+ pTab = (Table*)sqliteHashData(k);
+ reindexTable(pParse, pTab, pColl);
+ }
+ }
+}
+#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
+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 ){
+ pColl = sqlite3FindCollSeq(db, db->enc, pName1->z, pName1->n, 0);
+ if( pColl ){
+ reindexDatabases(pParse, pColl);
+ return;
+ }
+ }
+ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pObjName);
+ if( iDb<0 ) return;
+ z = sqlite3NameFromToken(pObjName);
+ zDb = db->aDb[iDb].zName;
+ pTab = sqlite3FindTable(db, z, zDb);
+ if( pTab ){
+ reindexTable(pParse, pTab, 0);
+ sqliteFree(z);
+ return;
+ }
+ pIndex = sqlite3FindIndex(db, z, zDb);
+ sqliteFree(z);
+ if( pIndex ){
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3RefillIndex(pParse, pIndex, -1);
+ return;
+ }
+ sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed");
+}
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/callback.c b/kexi/3rdparty/kexisql3/src/callback.c
new file mode 100644
index 000000000..8b585a11f
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/callback.c
@@ -0,0 +1,305 @@
+/*
+** 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.3 2005/08/14 01:20:38 drh Exp $
+*/
+
+#include "sqliteInt.h"
+
+/*
+** 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 = sqliteStrNDup(zName, nName);
+ if( !zExternal ) return;
+ db->xCollNeeded(db->pCollNeededArg, db, (int)db->enc, zExternal);
+ sqliteFree(zExternal);
+ }
+#ifndef SQLITE_OMIT_UTF16
+ if( db->xCollNeeded16 ){
+ char const *zExternal;
+ sqlite3_value *pTmp = sqlite3GetTransientValue(db);
+ sqlite3ValueSetStr(pTmp, -1, zName, SQLITE_UTF8, SQLITE_STATIC);
+ zExternal = sqlite3ValueText(pTmp, SQLITE_UTF16NATIVE);
+ if( !zExternal ) return;
+ db->xCollNeeded16(db->pCollNeededArg, db, (int)db->enc, zExternal);
+ }
+#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));
+ 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.
+*/
+CollSeq *sqlite3GetCollSeq(
+ sqlite3* db,
+ CollSeq *pColl,
+ const char *zName,
+ int nName
+){
+ CollSeq *p;
+
+ p = pColl;
+ if( !p ){
+ p = sqlite3FindCollSeq(db, db->enc, 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, db->enc, 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.
+*/
+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;
+ }
+ }
+ 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 = sqliteMalloc( 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 || (sqlite3_malloc_failed && pDel==pColl) );
+ sqliteFree(pDel);
+ }
+ }
+ 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.
+*/
+CollSeq *sqlite3FindCollSeq(
+ sqlite3 *db,
+ u8 enc,
+ const char *zName,
+ int nName,
+ int create
+){
+ CollSeq *pColl = findCollSeqEntry(db, zName, nName, create);
+ 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.
+*/
+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 = sqliteMalloc(sizeof(*pBest)+nName)) ){
+ 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) ){
+ sqliteFree(pBest);
+ return 0;
+ }
+ }
+
+ if( pBest && (pBest->xStep || pBest->xFunc || createFlag) ){
+ return pBest;
+ }
+ return 0;
+}
diff --git a/kexi/3rdparty/kexisql3/src/complete.c b/kexi/3rdparty/kexisql3/src/complete.c
new file mode 100644
index 000000000..4cc76b501
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/complete.c
@@ -0,0 +1,263 @@
+/*
+** 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.1 2005/08/14 17:53:21 drh Exp $
+*/
+#include "sqliteInt.h"
+#ifndef SQLITE_OMIT_COMPLETE
+
+/*
+** This is defined in tokenize.c. We just have to import the definition.
+*/
+extern const char sqlite3IsIdChar[];
+#define IdChar(C) (((c=C)&0x80)!=0 || (c>0x1f && sqlite3IsIdChar[c-0x20]))
+
+
+/*
+** 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.
+*/
+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.
+*/
+int sqlite3_complete16(const void *zSql){
+ sqlite3_value *pVal;
+ char const *zSql8;
+ int rc = 0;
+
+ pVal = sqlite3ValueNew();
+ sqlite3ValueSetStr(pVal, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zSql8 = sqlite3ValueText(pVal, SQLITE_UTF8);
+ if( zSql8 ){
+ rc = sqlite3_complete(zSql8);
+ }
+ sqlite3ValueFree(pVal);
+ return rc;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+#endif /* SQLITE_OMIT_COMPLETE */
diff --git a/kexi/3rdparty/kexisql3/src/date.c b/kexi/3rdparty/kexisql3/src/date.c
new file mode 100644
index 000000000..eccab6c9d
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/date.c
@@ -0,0 +1,996 @@
+/*
+** 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 653457 2007-04-13 11:18:02Z scripty $
+**
+** 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 "sqliteInt.h"
+#include "os.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(*(u8*)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.
+*/
+#define getValue sqlite3AtoF
+
+/*
+** 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(*(u8*)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(*(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;
+ 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(*(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(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;
+ sqlite3OsCurrentTime(&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;
+ 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;
+ sqlite3OsEnterMutex();
+ 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;
+ sqlite3OsLeaveMutex();
+ 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);
+ 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(*(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.
+*/
+static int isDate(int argc, sqlite3_value **argv, DateTime *p){
+ int i;
+ if( argc==0 ) return 1;
+ if( SQLITE_NULL==sqlite3_value_type(argv[0]) ||
+ parseDateOrTime(sqlite3_value_text(argv[0]), p) ) return 1;
+ for(i=1; i<argc; i++){
+ if( SQLITE_NULL==sqlite3_value_type(argv[i]) ||
+ parseModifier(sqlite3_value_text(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(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ DateTime x;
+ if( isDate(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(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));
+ 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(argc, argv, &x)==0 ){
+ char zBuf[100];
+ computeHMS(&x);
+ sprintf(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(argc, argv, &x)==0 ){
+ char zBuf[100];
+ computeYMD(&x);
+ sprintf(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;
+ int n, i, j;
+ char *z;
+ const char *zFmt = sqlite3_value_text(argv[0]);
+ char zBuf[100];
+ if( zFmt==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;
+ sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT);
+ if( z!=zBuf ){
+ sqliteFree(z);
+ }
+}
+
+/*
+** current_time()
+**
+** This function returns the same value as time('now').
+*/
+static void ctimeFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ sqlite3_value *pVal = sqlite3ValueNew();
+ if( pVal ){
+ sqlite3ValueSetStr(pVal, -1, "now", SQLITE_UTF8, SQLITE_STATIC);
+ timeFunc(context, 1, &pVal);
+ sqlite3ValueFree(pVal);
+ }
+}
+
+/*
+** current_date()
+**
+** This function returns the same value as date('now').
+*/
+static void cdateFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ sqlite3_value *pVal = sqlite3ValueNew();
+ if( pVal ){
+ sqlite3ValueSetStr(pVal, -1, "now", SQLITE_UTF8, SQLITE_STATIC);
+ dateFunc(context, 1, &pVal);
+ sqlite3ValueFree(pVal);
+ }
+}
+
+/*
+** current_timestamp()
+**
+** This function returns the same value as datetime('now').
+*/
+static void ctimestampFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ sqlite3_value *pVal = sqlite3ValueNew();
+ if( pVal ){
+ sqlite3ValueSetStr(pVal, -1, "now", SQLITE_UTF8, SQLITE_STATIC);
+ datetimeFunc(context, 1, &pVal);
+ sqlite3ValueFree(pVal);
+ }
+}
+#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);
+ char zBuf[20];
+
+ time(&t);
+#ifdef SQLITE_TEST
+ {
+ extern int sqlite3_current_time; /* See os_XXX.c */
+ if( sqlite3_current_time ){
+ t = sqlite3_current_time;
+ }
+ }
+#endif
+
+ sqlite3OsEnterMutex();
+ strftime(zBuf, 20, zFormat, gmtime(&t));
+ sqlite3OsLeaveMutex();
+
+ 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.
+*/
+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++){
+ sqlite3_create_function(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++){
+ sqlite3_create_function(db, aFuncs[i].zName, 0, SQLITE_UTF8,
+ aFuncs[i].zFormat, currentTimeFunc, 0, 0);
+ }
+#endif
+}
diff --git a/kexi/3rdparty/kexisql3/src/delete.c b/kexi/3rdparty/kexisql3/src/delete.c
new file mode 100644
index 000000000..ce66fd98e
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/delete.c
@@ -0,0 +1,445 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#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 *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, pItem->zName, pItem->zDatabase);
+ sqlite3DeleteTable(pParse->db, 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;
+*/
+int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){
+ if( pTab->readOnly && (pParse->db->flags & SQLITE_WriteSchema)==0
+ && pParse->nested==0 ){
+ 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.
+*/
+void sqlite3OpenTableForReading(
+ Vdbe *v, /* Generate code into this VDBE */
+ int iCur, /* The cursor number of the table */
+ Table *pTab /* The table to be opened */
+){
+ sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ VdbeComment((v, "# %s", pTab->zName));
+ sqlite3VdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, pTab->nCol);
+}
+
+
+/*
+** Generate code for a DELETE FROM statement.
+**
+** DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
+** \________/ \________________/
+** pTabList pWhere
+*/
+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 */
+
+#ifndef SQLITE_OMIT_TRIGGER
+ int isView; /* True if attempting to delete from a view */
+ int triggers_exist = 0; /* True if any triggers exist */
+#endif
+
+ sContext.pParse = 0;
+ if( pParse->nErr || sqlite3_malloc_failed ){
+ 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 = 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;
+ }
+ assert( pTab->iDb<db->nDb );
+ zDb = db->aDb[pTab->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( isView && 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++;
+ }
+
+ /* Resolve the column names in the WHERE clause.
+ */
+ assert( pTabList->nSrc==1 );
+ iCur = pTabList->a[0].iCursor = pParse->nTab++;
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ sNC.pSrcList = pTabList;
+ if( sqlite3ExprResolveNames(&sNC, pWhere) ){
+ goto delete_from_cleanup;
+ }
+
+ /* 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, pTab->iDb);
+
+ /* If we are trying to delete from a view, realize that view into
+ ** a ephemeral table.
+ */
+ if( isView ){
+ Select *pView = sqlite3SelectDup(pTab->pSelect);
+ sqlite3Select(pParse, pView, SRT_VirtualTab, iCur, 0, 0, 0, 0);
+ sqlite3SelectDelete(pView);
+ }
+
+ /* Initialize the counter of the number of rows deleted, if
+ ** we are counting rows.
+ */
+ if( db->flags & SQLITE_CountRows ){
+ sqlite3VdbeAddOp(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 && !triggers_exist ){
+ if( db->flags & SQLITE_CountRows ){
+ /* If counting rows deleted, just count the total number of
+ ** entries in the table. */
+ int endOfLoop = sqlite3VdbeMakeLabel(v);
+ int addr;
+ if( !isView ){
+ sqlite3OpenTableForReading(v, iCur, pTab);
+ }
+ sqlite3VdbeAddOp(v, OP_Rewind, iCur, sqlite3VdbeCurrentAddr(v)+2);
+ addr = sqlite3VdbeAddOp(v, OP_AddImm, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Next, iCur, addr);
+ sqlite3VdbeResolveLabel(v, endOfLoop);
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }
+ if( !isView ){
+ sqlite3VdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb);
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ sqlite3VdbeAddOp(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{
+ /* Ensure all required collation sequences are available. */
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ if( sqlite3CheckIndexCollSeq(pParse, pIdx) ){
+ goto delete_from_cleanup;
+ }
+ }
+
+ /* Begin the database scan
+ */
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0);
+ if( pWInfo==0 ) goto delete_from_cleanup;
+
+ /* Remember the rowid of every item to be deleted.
+ */
+ sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_FifoWrite, 0, 0);
+ if( db->flags & SQLITE_CountRows ){
+ sqlite3VdbeAddOp(v, OP_AddImm, 1, 0);
+ }
+
+ /* End the database scan loop.
+ */
+ sqlite3WhereEnd(pWInfo);
+
+ /* Open the pseudo-table used to store OLD if there are triggers.
+ */
+ if( triggers_exist ){
+ sqlite3VdbeAddOp(v, OP_OpenPseudo, oldIdx, 0);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, oldIdx, pTab->nCol);
+ }
+
+ /* 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);
+
+ /* This is the beginning of the delete loop when there are
+ ** row triggers.
+ */
+ if( triggers_exist ){
+ addr = sqlite3VdbeAddOp(v, OP_FifoRead, 0, end);
+ if( !isView ){
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ sqlite3OpenTableForReading(v, iCur, pTab);
+ }
+ sqlite3VdbeAddOp(v, OP_MoveGe, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_RowData, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_Insert, oldIdx, 0);
+ if( !isView ){
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }
+
+ (void)sqlite3CodeRowTrigger(pParse, TK_DELETE, 0, TRIGGER_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_FifoRead 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.
+ */
+ sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite);
+
+ /* This is the beginning of the delete loop when there are no
+ ** row triggers */
+ if( !triggers_exist ){
+ addr = sqlite3VdbeAddOp(v, OP_FifoRead, 0, end);
+ }
+
+ /* Delete the row */
+ sqlite3GenerateRowDelete(db, v, pTab, iCur, pParse->nested==0);
+ }
+
+ /* If there are row triggers, close all cursors then invoke
+ ** the AFTER triggers
+ */
+ if( triggers_exist ){
+ if( !isView ){
+ for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
+ sqlite3VdbeAddOp(v, OP_Close, iCur + i, pIdx->tnum);
+ }
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }
+ (void)sqlite3CodeRowTrigger(pParse, TK_DELETE, 0, TRIGGER_AFTER, pTab, -1,
+ oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default,
+ addr);
+ }
+
+ /* End of the delete loop */
+ sqlite3VdbeAddOp(v, OP_Goto, 0, addr);
+ sqlite3VdbeResolveLabel(v, end);
+
+ /* Close the cursors after the loop if there are no row triggers */
+ if( !triggers_exist ){
+ for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
+ sqlite3VdbeAddOp(v, OP_Close, iCur + i, pIdx->tnum);
+ }
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }
+ }
+
+ /*
+ ** 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 ){
+ sqlite3VdbeAddOp(v, OP_Callback, 1, 0);
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, "rows deleted", P3_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 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 sqlite3GenerateRowDelete(
+ sqlite3 *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 = sqlite3VdbeAddOp(v, OP_NotExists, iCur, 0);
+ sqlite3GenerateRowIndexDelete(db, v, pTab, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0));
+ 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.
+*/
+void sqlite3GenerateRowIndexDelete(
+ sqlite3 *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){
+ if( aIdxUsed!=0 && aIdxUsed[i-1]==0 ) continue;
+ sqlite3GenerateIndexKey(v, pIdx, iCur);
+ sqlite3VdbeAddOp(v, OP_IdxDelete, iCur+i, 0);
+ }
+}
+
+/*
+** Generate code that will assemble an index key and put it on the top
+** of the tack. 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.
+*/
+void sqlite3GenerateIndexKey(
+ Vdbe *v, /* Generate code into this VDBE */
+ Index *pIdx, /* The index for which to generate a key */
+ int iCur /* Cursor number for the pIdx->pTable table */
+){
+ int j;
+ Table *pTab = pIdx->pTable;
+
+ sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
+ for(j=0; j<pIdx->nColumn; j++){
+ int idx = pIdx->aiColumn[j];
+ if( idx==pTab->iPKey ){
+ sqlite3VdbeAddOp(v, OP_Dup, j, 0);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Column, iCur, idx);
+ sqlite3ColumnDefault(v, pTab, idx);
+ }
+ }
+ sqlite3VdbeAddOp(v, OP_MakeIdxRec, pIdx->nColumn, 0);
+ sqlite3IndexAffinityStr(v, pIdx);
+}
diff --git a/kexi/3rdparty/kexisql3/src/expr.c b/kexi/3rdparty/kexisql3/src/expr.c
new file mode 100644
index 000000000..45f7fc2fa
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/expr.c
@@ -0,0 +1,2253 @@
+/*
+** 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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#include "sqliteInt.h"
+#include <ctype.h>
+
+/*
+** 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);
+*/
+char sqlite3ExprAffinity(Expr *pExpr){
+ int op = pExpr->op;
+ if( op==TK_AS ){
+ return sqlite3ExprAffinity(pExpr->pLeft);
+ }
+ 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;
+}
+
+/*
+** Return the default collation sequence for the expression pExpr. If
+** there is no default collation type, return 0.
+*/
+CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
+ CollSeq *pColl = 0;
+ if( pExpr ){
+ pColl = pExpr->pColl;
+ if( (pExpr->op==TK_AS || pExpr->op==TK_CAST) && !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.
+*/
+char sqlite3CompareAffinity(Expr *pExpr, char aff2){
+ char aff1 = sqlite3ExprAffinity(pExpr);
+ if( aff1 && aff2 ){
+ /* Both sides of the comparison are columns. If one has numeric or
+ ** integer affinity, use that. Otherwise use no affinity.
+ */
+ if( aff1==SQLITE_AFF_INTEGER || aff2==SQLITE_AFF_INTEGER ){
+ return SQLITE_AFF_INTEGER;
+ }else if( aff1==SQLITE_AFF_NUMERIC || aff2==SQLITE_AFF_NUMERIC ){
+ 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_NUMERIC; // Ticket #805 */
+ 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_NUMERIC;
+ }
+ 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.
+*/
+int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){
+ char aff = comparisonAffinity(pExpr);
+ return
+ (aff==SQLITE_AFF_NONE) ||
+ (aff==SQLITE_AFF_NUMERIC && idx_affinity==SQLITE_AFF_INTEGER) ||
+ (aff==SQLITE_AFF_INTEGER && idx_affinity==SQLITE_AFF_NUMERIC) ||
+ (aff==idx_affinity);
+}
+
+/*
+** Return the P1 value that should be used for a binary comparison
+** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2.
+** If jumpIfNull is true, then set the low byte of the returned
+** P1 value to tell the opcode to jump if either expression
+** evaluates to NULL.
+*/
+static int binaryCompareP1(Expr *pExpr1, Expr *pExpr2, int jumpIfNull){
+ char aff = sqlite3ExprAffinity(pExpr2);
+ return ((int)sqlite3CompareAffinity(pExpr1, aff))+(jumpIfNull?0x100:0);
+}
+
+/*
+** 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.
+*/
+static CollSeq* binaryCompareCollSeq(Parse *pParse, Expr *pLeft, Expr *pRight){
+ CollSeq *pColl = sqlite3ExprCollSeq(pParse, pLeft);
+ if( !pColl ){
+ pColl = sqlite3ExprCollSeq(pParse, pRight);
+ }
+ return pColl;
+}
+
+/*
+** 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 dest, /* Jump here if true. */
+ int jumpIfNull /* If true, jump if either operand is NULL */
+){
+ int p1 = binaryCompareP1(pLeft, pRight, jumpIfNull);
+ CollSeq *p3 = binaryCompareCollSeq(pParse, pLeft, pRight);
+ return sqlite3VdbeOp3(pParse->pVdbe, opcode, p1, dest, (void*)p3, P3_COLLSEQ);
+}
+
+/*
+** 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 *sqlite3Expr(int op, Expr *pLeft, Expr *pRight, const Token *pToken){
+ Expr *pNew;
+ pNew = sqliteMalloc( 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 && pRight ){
+ sqlite3ExprSpan(pNew, &pLeft->span, &pRight->span);
+ }
+ return pNew;
+}
+
+/*
+** When doing a nested parse, you can include terms in an expression
+** that look like this: #0 #1 #2 ... These terms refer to elements
+** on the stack. "#0" means the top of the stack.
+** "#1" means the next down on the stack. And so forth.
+**
+** 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.
+*/
+Expr *sqlite3RegisterExpr(Parse *pParse, Token *pToken){
+ Vdbe *v = pParse->pVdbe;
+ Expr *p;
+ int depth;
+ if( pParse->nested==0 ){
+ sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", pToken);
+ return 0;
+ }
+ if( v==0 ) return 0;
+ p = sqlite3Expr(TK_REGISTER, 0, 0, pToken);
+ if( p==0 ){
+ return 0; /* Malloc failed */
+ }
+ depth = atoi(&pToken->z[1]);
+ p->iTable = pParse->nMem++;
+ sqlite3VdbeAddOp(v, OP_Dup, depth, 0);
+ sqlite3VdbeAddOp(v, OP_MemStore, p->iTable, 1);
+ return p;
+}
+
+/*
+** Join two expressions using an AND operator. If either expression is
+** NULL, then just return the other expression.
+*/
+Expr *sqlite3ExprAnd(Expr *pLeft, Expr *pRight){
+ if( pLeft==0 ){
+ return pRight;
+ }else if( pRight==0 ){
+ return pLeft;
+ }else{
+ return sqlite3Expr(TK_AND, pLeft, pRight, 0);
+ }
+}
+
+/*
+** Set the Expr.span field of the given expression to span all
+** text between the two given tokens.
+*/
+void sqlite3ExprSpan(Expr *pExpr, Token *pLeft, Token *pRight){
+ assert( pRight!=0 );
+ assert( pLeft!=0 );
+ if( !sqlite3_malloc_failed && 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.
+*/
+Expr *sqlite3ExprFunction(ExprList *pList, Token *pToken){
+ Expr *pNew;
+ pNew = sqliteMalloc( sizeof(Expr) );
+ if( pNew==0 ){
+ sqlite3ExprListDelete(pList); /* Avoid leaking memory 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;
+}
+
+/*
+** 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.
+*/
+void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){
+ Token *pToken;
+ 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(&pToken->z[1]);
+ if( i<1 || i>SQLITE_MAX_VARIABLE_NUMBER ){
+ sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d",
+ SQLITE_MAX_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;
+ sqlite3ReallocOrFree((void**)&pParse->apVarExpr,
+ pParse->nVarExprAlloc*sizeof(pParse->apVarExpr[0]) );
+ }
+ if( !sqlite3_malloc_failed ){
+ assert( pParse->apVarExpr!=0 );
+ pParse->apVarExpr[pParse->nVarExpr++] = pExpr;
+ }
+ }
+ }
+}
+
+/*
+** Recursively delete an expression tree.
+*/
+void sqlite3ExprDelete(Expr *p){
+ if( p==0 ) return;
+ if( p->span.dyn ) sqliteFree((char*)p->span.z);
+ if( p->token.dyn ) sqliteFree((char*)p->token.z);
+ sqlite3ExprDelete(p->pLeft);
+ sqlite3ExprDelete(p->pRight);
+ sqlite3ExprListDelete(p->pList);
+ sqlite3SelectDelete(p->pSelect);
+ sqliteFree(p);
+}
+
+/*
+** The Expr.token field might be a string literal that is quoted.
+** If so, remove the quotation marks.
+*/
+void sqlite3DequoteExpr(Expr *p){
+ if( ExprHasAnyProperty(p, EP_Dequoted) ){
+ return;
+ }
+ ExprSetProperty(p, EP_Dequoted);
+ if( p->token.dyn==0 ){
+ sqlite3TokenCopy(&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.
+*/
+Expr *sqlite3ExprDup(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 = sqlite3ExprDup(p->pLeft);
+ pNew->pRight = sqlite3ExprDup(p->pRight);
+ pNew->pList = sqlite3ExprListDup(p->pList);
+ pNew->pSelect = sqlite3SelectDup(p->pSelect);
+ pNew->pTab = p->pTab;
+ return pNew;
+}
+void sqlite3TokenCopy(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 *sqlite3ExprListDup(ExprList *p){
+ ExprList *pNew;
+ struct ExprList_item *pItem, *pOldItem;
+ 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;
+ }
+ pOldItem = p->a;
+ for(i=0; i<p->nExpr; i++, pItem++, pOldItem++){
+ Expr *pNewExpr, *pOldExpr;
+ pItem->pExpr = pNewExpr = sqlite3ExprDup(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(&pNewExpr->span, &pOldExpr->span);
+ }
+ assert( pNewExpr==0 || pNewExpr->span.z!=0
+ || pOldExpr->span.z==0 || sqlite3_malloc_failed );
+ pItem->zName = sqliteStrDup(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)
+SrcList *sqlite3SrcListDup(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];
+ Table *pTab;
+ pNewItem->zDatabase = sqliteStrDup(pOldItem->zDatabase);
+ pNewItem->zName = sqliteStrDup(pOldItem->zName);
+ pNewItem->zAlias = sqliteStrDup(pOldItem->zAlias);
+ pNewItem->jointype = pOldItem->jointype;
+ pNewItem->iCursor = pOldItem->iCursor;
+ pTab = pNewItem->pTab = pOldItem->pTab;
+ if( pTab ){
+ pTab->nRef++;
+ }
+ pNewItem->pSelect = sqlite3SelectDup(pOldItem->pSelect);
+ pNewItem->pOn = sqlite3ExprDup(pOldItem->pOn);
+ pNewItem->pUsing = sqlite3IdListDup(pOldItem->pUsing);
+ pNewItem->colUsed = pOldItem->colUsed;
+ }
+ return pNew;
+}
+IdList *sqlite3IdListDup(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 ){
+ sqliteFree(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 = sqliteStrDup(pOldItem->zName);
+ pNewItem->idx = pOldItem->idx;
+ }
+ return pNew;
+}
+Select *sqlite3SelectDup(Select *p){
+ Select *pNew;
+ if( p==0 ) return 0;
+ pNew = sqliteMallocRaw( sizeof(*p) );
+ if( pNew==0 ) return 0;
+ pNew->isDistinct = p->isDistinct;
+ pNew->pEList = sqlite3ExprListDup(p->pEList);
+ pNew->pSrc = sqlite3SrcListDup(p->pSrc);
+ pNew->pWhere = sqlite3ExprDup(p->pWhere);
+ pNew->pGroupBy = sqlite3ExprListDup(p->pGroupBy);
+ pNew->pHaving = sqlite3ExprDup(p->pHaving);
+ pNew->pOrderBy = sqlite3ExprListDup(p->pOrderBy);
+ pNew->op = p->op;
+ pNew->pPrior = sqlite3SelectDup(p->pPrior);
+ pNew->pLimit = sqlite3ExprDup(p->pLimit);
+ pNew->pOffset = sqlite3ExprDup(p->pOffset);
+ pNew->iLimit = -1;
+ pNew->iOffset = -1;
+ pNew->isResolved = p->isResolved;
+ pNew->isAgg = p->isAgg;
+ pNew->usesVirt = 0;
+ pNew->disallowOrderBy = 0;
+ pNew->pRightmost = 0;
+ pNew->addrOpenVirt[0] = -1;
+ pNew->addrOpenVirt[1] = -1;
+ pNew->addrOpenVirt[2] = -1;
+ return pNew;
+}
+#else
+Select *sqlite3SelectDup(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.
+*/
+ExprList *sqlite3ExprListAppend(ExprList *pList, Expr *pExpr, Token *pName){
+ if( pList==0 ){
+ pList = sqliteMalloc( 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 = sqliteRealloc(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(pName);
+ pItem->pExpr = pExpr;
+ }
+ return pList;
+
+no_mem:
+ /* Avoid leaking memory if malloc has failed. */
+ sqlite3ExprDelete(pExpr);
+ sqlite3ExprListDelete(pList);
+ return 0;
+}
+
+/*
+** Delete an entire expression list.
+*/
+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);
+ sqliteFree(pItem->zName);
+ }
+ sqliteFree(pList->a);
+ sqliteFree(pList);
+}
+
+/*
+** Walk an expression tree. Call xFunc for each node visited.
+**
+** 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);
+ 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){
+ switch( pExpr->op ){
+ /* Consider functions to be constant if all their arguments are constant
+ ** and *pArg==2 */
+ case TK_FUNCTION:
+ if( *((int*)pArg)==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:
+#endif
+ *((int*)pArg) = 0;
+ return 2;
+ case TK_IN:
+ if( pExpr->pSelect ){
+ *((int*)pArg) = 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.
+*/
+int sqlite3ExprIsConstant(Expr *p){
+ int isConst = 1;
+ walkExprTree(p, exprNodeIsConstant, &isConst);
+ return isConst;
+}
+
+/*
+** 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.
+*/
+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.
+*/
+int sqlite3ExprIsInteger(Expr *p, int *pValue){
+ switch( p->op ){
+ case TK_INTEGER: {
+ if( sqlite3GetInt32(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.
+*/
+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 */
+
+ assert( pColumnToken && pColumnToken->z ); /* The Z in X.Y.Z cannot be NULL */
+ zDb = sqlite3NameFromToken(pDbToken);
+ zTab = sqlite3NameFromToken(pTableToken);
+ zCol = sqlite3NameFromToken(pColumnToken);
+ if( sqlite3_malloc_failed ){
+ goto lookupname_end;
+ }
+
+ pExpr->iTable = -1;
+ while( pNC && cnt==0 ){
+ SrcList *pSrcList = pNC->pSrcList;
+ ExprList *pEList = pNC->pEList;
+
+ /* assert( zTab==0 || pEList==0 ); */
+ if( pSrcList ){
+ for(i=0, pItem=pSrcList->a; i<pSrcList->nSrc; i++, pItem++){
+ Table *pTab = pItem->pTab;
+ Column *pCol;
+
+ if( pTab==0 ) continue;
+ 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[pTab->iDb].zName, zDb)!=0 ){
+ continue;
+ }
+ }
+ }
+ if( 0==(cntTab++) ){
+ pExpr->iTable = pItem->iCursor;
+ pExpr->iDb = pTab->iDb;
+ pMatch = pItem;
+ }
+ for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){
+ if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
+ IdList *pUsing;
+ cnt++;
+ pExpr->iTable = pItem->iCursor;
+ pMatch = pItem;
+ pExpr->iDb = pTab->iDb;
+ /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */
+ pExpr->iColumn = j==pTab->iPKey ? -1 : j;
+ pExpr->affinity = pTab->aCol[j].affinity;
+ pExpr->pColl = pTab->aCol[j].pColl;
+ if( pItem->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++;
+ }
+ if( (pUsing = pItem->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;
+ if( pTriggerStack->newIdx != -1 && sqlite3StrICmp("new", zTab) == 0 ){
+ pExpr->iTable = pTriggerStack->newIdx;
+ assert( pTriggerStack->pTab );
+ pTab = pTriggerStack->pTab;
+ }else if( pTriggerStack->oldIdx != -1 && sqlite3StrICmp("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( sqlite3StrICmp(pCol->zName, zCol)==0 ){
+ cnt++;
+ pExpr->iColumn = j==pTab->iPKey ? -1 : j;
+ pExpr->affinity = pTab->aCol[j].affinity;
+ pExpr->pColl = pTab->aCol[j].pColl;
+ pExpr->pTab = pTab;
+ 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!=0 && zTab==0 ){
+ for(j=0; j<pEList->nExpr; j++){
+ char *zAs = pEList->a[j].zName;
+ if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){
+ assert( pExpr->pLeft==0 && pExpr->pRight==0 );
+ pExpr->op = TK_AS;
+ pExpr->iColumn = j;
+ pExpr->pLeft = sqlite3ExprDup(pEList->a[j].pExpr);
+ cnt = 1;
+ 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]=='"' ){
+ 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 ){
+ sqlite3SetString(&z, zDb, ".", zTab, ".", zCol, 0);
+ }else if( zTab ){
+ sqlite3SetString(&z, zTab, ".", zCol, 0);
+ }else{
+ z = sqliteStrDup(zCol);
+ }
+ sqlite3ErrorMsg(pParse, zErr, z);
+ sqliteFree(z);
+ 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;
+ if( n>=sizeof(Bitmask)*8 ){
+ n = sizeof(Bitmask)*8-1;
+ }
+ assert( pMatch->iCursor==pExpr->iTable );
+ pMatch->colUsed |= 1<<n;
+ }
+
+lookupname_end:
+ /* Clean up and return
+ */
+ sqliteFree(zDb);
+ sqliteFree(zTab);
+ sqlite3ExprDelete(pExpr->pLeft);
+ pExpr->pLeft = 0;
+ sqlite3ExprDelete(pExpr->pRight);
+ pExpr->pRight = 0;
+ pExpr->op = TK_COLUMN;
+lookupname_end_2:
+ sqliteFree(zCol);
+ if( cnt==1 ){
+ assert( pNC!=0 );
+ sqlite3AuthRead(pParse, pExpr, 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;
+ SrcList *pSrcList;
+ Parse *pParse;
+
+ if( pExpr==0 ) return 1;
+ assert( pNC!=0 );
+ pSrcList = pNC->pSrcList;
+ pParse = pNC->pParse;
+
+ if( ExprHasAnyProperty(pExpr, EP_Resolved) ) return 1;
+ ExprSetProperty(pExpr, EP_Resolved);
+#ifndef NDEBUG
+ if( pSrcList ){
+ int i;
+ for(i=0; i<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 nId; /* Number of characters in function name */
+ const char *zId; /* The function name. */
+ FuncDef *pDef; /* Information about the function */
+ int enc = pParse->db->enc; /* The database encoding */
+
+ zId = 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;
+ }
+ 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;
+ sqlite3SelectResolve(pParse, pExpr->pSelect, pNC);
+ assert( pNC->nRef>=nRef );
+ if( nRef!=pNC->nRef ){
+ ExprSetProperty(pExpr, EP_VarSelect);
+ }
+ }
+ }
+ }
+ 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.
+*/
+int sqlite3ExprResolveNames(
+ NameContext *pNC, /* Namespace to resolve expressions in. */
+ Expr *pExpr /* The expression to be analyzed. */
+){
+ int savedHasAgg;
+ if( pExpr==0 ) return 0;
+ savedHasAgg = pNC->hasAgg;
+ pNC->hasAgg = 0;
+ walkExprTree(pExpr, nameResolverStep, pNC);
+ 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 */
+};
+
+
+/*
+** Generate code for subqueries and IN operators.
+**
+** IN operators 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.
+*/
+#ifndef SQLITE_OMIT_SUBQUERY
+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++;
+ sqlite3VdbeAddOp(v, OP_MemLoad, mem, 0);
+ testAddr = sqlite3VdbeAddOp(v, OP_If, 0, 0);
+ assert( testAddr>0 || sqlite3_malloc_failed );
+ sqlite3VdbeAddOp(v, OP_MemInt, 1, mem);
+ }
+
+ switch( pExpr->op ){
+ case TK_IN: {
+ char affinity;
+ KeyInfo keyInfo;
+ int addr; /* Address of OP_OpenVirtual 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 = sqlite3VdbeAddOp(v, OP_OpenVirtual, pExpr->iTable, 0);
+ memset(&keyInfo, 0, sizeof(keyInfo));
+ keyInfo.nField = 1;
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, pExpr->iTable, 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.
+ */
+ int iParm = pExpr->iTable + (((int)affinity)<<16);
+ ExprList *pEList;
+ assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable );
+ sqlite3Select(pParse, pExpr->pSelect, SRT_Set, iParm, 0, 0, 0, 0);
+ pEList = pExpr->pSelect->pEList;
+ if( pEList && pEList->nExpr>0 ){
+ keyInfo.aColl[0] = binaryCompareCollSeq(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;
+
+ if( !affinity ){
+ affinity = SQLITE_AFF_NUMERIC;
+ }
+ keyInfo.aColl[0] = pExpr->pLeft->pColl;
+
+ /* Loop through each expression in <exprlist>. */
+ 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>0 && !sqlite3ExprIsConstant(pE2) ){
+ VdbeOp *aOp = sqlite3VdbeGetOp(v, testAddr-1);
+ int i;
+ for(i=0; i<3; i++){
+ aOp[i].opcode = OP_Noop;
+ }
+ testAddr = 0;
+ }
+
+ /* Evaluate the expression and insert it into the temp table */
+ sqlite3ExprCode(pParse, pE2);
+ sqlite3VdbeOp3(v, OP_MakeRecord, 1, 0, &affinity, 1);
+ sqlite3VdbeAddOp(v, OP_IdxInsert, pExpr->iTable, 0);
+ }
+ }
+ sqlite3VdbeChangeP3(v, addr, (void *)&keyInfo, P3_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.
+ */
+ int sop;
+ Select *pSel;
+
+ pExpr->iColumn = pParse->nMem++;
+ pSel = pExpr->pSelect;
+ if( pExpr->op==TK_SELECT ){
+ sop = SRT_Mem;
+ }else{
+ static const Token one = { "1", 0, 1 };
+ sop = SRT_Exists;
+ sqlite3ExprListDelete(pSel->pEList);
+ pSel->pEList = sqlite3ExprListAppend(0,
+ sqlite3Expr(TK_INTEGER, 0, 0, &one), 0);
+ }
+ sqlite3Select(pParse, pSel, sop, pExpr->iColumn, 0, 0, 0, 0);
+ break;
+ }
+ }
+
+ if( testAddr ){
+ sqlite3VdbeJumpHere(v, testAddr);
+ }
+ return;
+}
+#endif /* SQLITE_OMIT_SUBQUERY */
+
+/*
+** Generate an instruction that will put the integer describe by
+** text z[0..n-1] on the stack.
+*/
+static void codeInteger(Vdbe *v, const char *z, int n){
+ int i;
+ if( sqlite3GetInt32(z, &i) ){
+ sqlite3VdbeAddOp(v, OP_Integer, i, 0);
+ }else if( sqlite3FitsIn64Bits(z) ){
+ sqlite3VdbeOp3(v, OP_Int64, 0, 0, z, n);
+ }else{
+ sqlite3VdbeOp3(v, OP_Real, 0, 0, z, n);
+ }
+}
+
+/*
+** Generate code into the current Vdbe to evaluate the given
+** expression and leave the result on the top of stack.
+**
+** 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.
+*/
+void sqlite3ExprCode(Parse *pParse, Expr *pExpr){
+ Vdbe *v = pParse->pVdbe;
+ int op;
+ if( v==0 ) return;
+ if( pExpr==0 ){
+ sqlite3VdbeAddOp(v, OP_Null, 0, 0);
+ return;
+ }
+ op = pExpr->op;
+ switch( op ){
+ case TK_AGG_COLUMN: {
+ AggInfo *pAggInfo = pExpr->pAggInfo;
+ struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg];
+ if( !pAggInfo->directMode ){
+ sqlite3VdbeAddOp(v, OP_MemLoad, pCol->iMem, 0);
+ break;
+ }else if( pAggInfo->useSortingIdx ){
+ sqlite3VdbeAddOp(v, OP_Column, pAggInfo->sortingIdx,
+ pCol->iSorterColumn);
+ break;
+ }
+ /* Otherwise, fall thru into the TK_COLUMN case */
+ }
+ case TK_COLUMN: {
+ if( pExpr->iColumn>=0 ){
+ sqlite3VdbeAddOp(v, OP_Column, pExpr->iTable, pExpr->iColumn);
+ sqlite3ColumnDefault(v, pExpr->pTab, pExpr->iColumn);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Rowid, pExpr->iTable, 0);
+ }
+ break;
+ }
+ case TK_INTEGER: {
+ codeInteger(v, pExpr->token.z, pExpr->token.n);
+ break;
+ }
+ case TK_FLOAT:
+ case TK_STRING: {
+ assert( TK_FLOAT==OP_Real );
+ assert( TK_STRING==OP_String8 );
+ sqlite3DequoteExpr(pExpr);
+ sqlite3VdbeOp3(v, op, 0, 0, pExpr->token.z, pExpr->token.n);
+ break;
+ }
+ case TK_NULL: {
+ sqlite3VdbeAddOp(v, OP_Null, 0, 0);
+ break;
+ }
+#ifndef SQLITE_OMIT_BLOB_LITERAL
+ case TK_BLOB: {
+ int n;
+ const char *z;
+ assert( TK_BLOB==OP_HexBlob );
+ n = pExpr->token.n - 3;
+ z = pExpr->token.z + 2;
+ assert( n>=0 );
+ if( n==0 ){
+ z = "";
+ }
+ sqlite3VdbeOp3(v, op, 0, 0, z, n);
+ break;
+ }
+#endif
+ case TK_VARIABLE: {
+ sqlite3VdbeAddOp(v, OP_Variable, pExpr->iTable, 0);
+ if( pExpr->token.n>1 ){
+ sqlite3VdbeChangeP3(v, -1, pExpr->token.z, pExpr->token.n);
+ }
+ break;
+ }
+ case TK_REGISTER: {
+ sqlite3VdbeAddOp(v, OP_MemLoad, pExpr->iTable, 0);
+ break;
+ }
+#ifndef SQLITE_OMIT_CAST
+ case TK_CAST: {
+ /* Expressions of the form: CAST(pLeft AS token) */
+ int aff, op;
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ aff = sqlite3AffinityType(&pExpr->token);
+ switch( aff ){
+ case SQLITE_AFF_INTEGER: op = OP_ToInt; break;
+ case SQLITE_AFF_NUMERIC: op = OP_ToNumeric; break;
+ case SQLITE_AFF_TEXT: op = OP_ToText; break;
+ case SQLITE_AFF_NONE: op = OP_ToBlob; break;
+ }
+ sqlite3VdbeAddOp(v, op, 0, 0);
+ 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 );
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ sqlite3ExprCode(pParse, pExpr->pRight);
+ codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, 0, 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 );
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ sqlite3ExprCode(pParse, pExpr->pRight);
+ sqlite3VdbeAddOp(v, op, 0, 0);
+ break;
+ }
+ case TK_UMINUS: {
+ Expr *pLeft = pExpr->pLeft;
+ assert( pLeft );
+ if( pLeft->op==TK_FLOAT || pLeft->op==TK_INTEGER ){
+ Token *p = &pLeft->token;
+ char *z = sqliteMalloc( p->n + 2 );
+ sprintf(z, "-%.*s", p->n, p->z);
+ if( pLeft->op==TK_FLOAT ){
+ sqlite3VdbeOp3(v, OP_Real, 0, 0, z, p->n+1);
+ }else{
+ codeInteger(v, z, p->n+1);
+ }
+ sqliteFree(z);
+ break;
+ }
+ /* Fall through into TK_NOT */
+ }
+ case TK_BITNOT:
+ case TK_NOT: {
+ assert( TK_BITNOT==OP_BitNot );
+ assert( TK_NOT==OP_Not );
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ sqlite3VdbeAddOp(v, op, 0, 0);
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ int dest;
+ assert( TK_ISNULL==OP_IsNull );
+ assert( TK_NOTNULL==OP_NotNull );
+ sqlite3VdbeAddOp(v, OP_Integer, 1, 0);
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ dest = sqlite3VdbeCurrentAddr(v) + 2;
+ sqlite3VdbeAddOp(v, op, 1, dest);
+ sqlite3VdbeAddOp(v, OP_AddImm, -1, 0);
+ break;
+ }
+ case TK_AGG_FUNCTION: {
+ AggInfo *pInfo = pExpr->pAggInfo;
+ sqlite3VdbeAddOp(v, OP_MemLoad, pInfo->aFunc[pExpr->iAgg].iMem, 0);
+ 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;
+ u8 enc = pParse->db->enc;
+ CollSeq *pColl = 0;
+ zId = pExpr->token.z;
+ nId = pExpr->token.n;
+ pDef = sqlite3FindFunction(pParse->db, zId, nId, nExpr, enc, 0);
+ assert( pDef!=0 );
+ nExpr = sqlite3ExprCodeExprList(pParse, pList);
+ 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;
+ sqlite3VdbeOp3(v, OP_CollSeq, 0, 0, (char *)pColl, P3_COLLSEQ);
+ }
+ sqlite3VdbeOp3(v, OP_Function, constMask, nExpr, (char*)pDef, P3_FUNCDEF);
+ break;
+ }
+#ifndef SQLITE_OMIT_SUBQUERY
+ case TK_EXISTS:
+ case TK_SELECT: {
+ sqlite3CodeSubselect(pParse, pExpr);
+ sqlite3VdbeAddOp(v, OP_MemLoad, pExpr->iColumn, 0);
+ VdbeComment((v, "# load subquery result"));
+ break;
+ }
+ case TK_IN: {
+ int addr;
+ char affinity;
+ sqlite3CodeSubselect(pParse, pExpr);
+
+ /* Figure out the affinity to use to create a key from the results
+ ** of the expression. affinityStr stores a static string suitable for
+ ** P3 of OP_MakeRecord.
+ */
+ affinity = comparisonAffinity(pExpr);
+
+ sqlite3VdbeAddOp(v, OP_Integer, 1, 0);
+
+ /* Code the <expr> from "<expr> IN (...)". The temporary table
+ ** pExpr->iTable contains the values that make up the (...) set.
+ */
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ addr = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp(v, OP_NotNull, -1, addr+4); /* addr + 0 */
+ sqlite3VdbeAddOp(v, OP_Pop, 2, 0);
+ sqlite3VdbeAddOp(v, OP_Null, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, addr+7);
+ sqlite3VdbeOp3(v, OP_MakeRecord, 1, 0, &affinity, 1); /* addr + 4 */
+ sqlite3VdbeAddOp(v, OP_Found, pExpr->iTable, addr+7);
+ sqlite3VdbeAddOp(v, OP_AddImm, -1, 0); /* addr + 6 */
+
+ break;
+ }
+#endif
+ case TK_BETWEEN: {
+ Expr *pLeft = pExpr->pLeft;
+ struct ExprList_item *pLItem = pExpr->pList->a;
+ Expr *pRight = pLItem->pExpr;
+ sqlite3ExprCode(pParse, pLeft);
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ sqlite3ExprCode(pParse, pRight);
+ codeCompare(pParse, pLeft, pRight, OP_Ge, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Pull, 1, 0);
+ pLItem++;
+ pRight = pLItem->pExpr;
+ sqlite3ExprCode(pParse, pRight);
+ codeCompare(pParse, pLeft, pRight, OP_Le, 0, 0);
+ sqlite3VdbeAddOp(v, OP_And, 0, 0);
+ break;
+ }
+ case TK_UPLUS:
+ case TK_AS: {
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ break;
+ }
+ case TK_CASE: {
+ int expr_end_label;
+ int jumpInst;
+ int nExpr;
+ int i;
+ ExprList *pEList;
+ struct ExprList_item *aListelem;
+
+ assert(pExpr->pList);
+ assert((pExpr->pList->nExpr % 2) == 0);
+ assert(pExpr->pList->nExpr > 0);
+ pEList = pExpr->pList;
+ aListelem = pEList->a;
+ nExpr = pEList->nExpr;
+ expr_end_label = sqlite3VdbeMakeLabel(v);
+ if( pExpr->pLeft ){
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ }
+ for(i=0; i<nExpr; i=i+2){
+ sqlite3ExprCode(pParse, aListelem[i].pExpr);
+ if( pExpr->pLeft ){
+ sqlite3VdbeAddOp(v, OP_Dup, 1, 1);
+ jumpInst = codeCompare(pParse, pExpr->pLeft, aListelem[i].pExpr,
+ OP_Ne, 0, 1);
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ }else{
+ jumpInst = sqlite3VdbeAddOp(v, OP_IfNot, 1, 0);
+ }
+ sqlite3ExprCode(pParse, aListelem[i+1].pExpr);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, expr_end_label);
+ sqlite3VdbeJumpHere(v, jumpInst);
+ }
+ if( pExpr->pLeft ){
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ }
+ if( pExpr->pRight ){
+ sqlite3ExprCode(pParse, pExpr->pRight);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Null, 0, 0);
+ }
+ sqlite3VdbeResolveLabel(v, expr_end_label);
+ break;
+ }
+#ifndef SQLITE_OMIT_TRIGGER
+ case TK_RAISE: {
+ if( !pParse->trigStack ){
+ sqlite3ErrorMsg(pParse,
+ "RAISE() may only be used within a trigger-program");
+ return;
+ }
+ if( pExpr->iColumn!=OE_Ignore ){
+ assert( pExpr->iColumn==OE_Rollback ||
+ pExpr->iColumn == OE_Abort ||
+ pExpr->iColumn == OE_Fail );
+ sqlite3DequoteExpr(pExpr);
+ sqlite3VdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, pExpr->iColumn,
+ pExpr->token.z, pExpr->token.n);
+ } else {
+ assert( pExpr->iColumn == OE_Ignore );
+ sqlite3VdbeAddOp(v, OP_ContextPop, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, pParse->trigStack->ignoreJump);
+ VdbeComment((v, "# raise(IGNORE)"));
+ }
+ }
+#endif
+ break;
+ }
+}
+
+#ifndef SQLITE_OMIT_TRIGGER
+/*
+** Generate code that evalutes the given expression and leaves the result
+** on the stack. See also sqlite3ExprCode().
+**
+** This routine might also cache the result and modify the pExpr tree
+** so that it will make use of the cached result on subsequent evaluations
+** rather than evaluate the whole expression again. Trivial expressions are
+** not cached. If the expression is cached, its result is stored in a
+** memory location.
+*/
+void sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr){
+ Vdbe *v = pParse->pVdbe;
+ int iMem;
+ int addr1, addr2;
+ if( v==0 ) return;
+ addr1 = sqlite3VdbeCurrentAddr(v);
+ sqlite3ExprCode(pParse, pExpr);
+ addr2 = sqlite3VdbeCurrentAddr(v);
+ if( addr2>addr1+1 || sqlite3VdbeGetOp(v, addr1)->opcode==OP_Function ){
+ iMem = pExpr->iTable = pParse->nMem++;
+ sqlite3VdbeAddOp(v, OP_MemStore, iMem, 0);
+ pExpr->op = TK_REGISTER;
+ }
+}
+#endif
+
+/*
+** Generate code that pushes the value of every element of the given
+** expression list onto the stack.
+**
+** Return the number of elements pushed onto the stack.
+*/
+int sqlite3ExprCodeExprList(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList /* The expression list to be coded */
+){
+ struct ExprList_item *pItem;
+ int i, n;
+ if( pList==0 ) return 0;
+ n = pList->nExpr;
+ for(pItem=pList->a, i=n; i>0; i--, pItem++){
+ sqlite3ExprCode(pParse, pItem->pExpr);
+ }
+ 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 true.
+**
+** 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.
+*/
+void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
+ Vdbe *v = pParse->pVdbe;
+ int op = 0;
+ if( v==0 || pExpr==0 ) return;
+ op = pExpr->op;
+ switch( op ){
+ case TK_AND: {
+ int d2 = sqlite3VdbeMakeLabel(v);
+ sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2, !jumpIfNull);
+ sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ sqlite3VdbeResolveLabel(v, d2);
+ break;
+ }
+ case TK_OR: {
+ sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
+ sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ break;
+ }
+ case TK_NOT: {
+ 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 );
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ sqlite3ExprCode(pParse, pExpr->pRight);
+ codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, dest, jumpIfNull);
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ assert( TK_ISNULL==OP_IsNull );
+ assert( TK_NOTNULL==OP_NotNull );
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ sqlite3VdbeAddOp(v, op, 1, dest);
+ break;
+ }
+ case TK_BETWEEN: {
+ /* The expression "x BETWEEN y AND z" is implemented as:
+ **
+ ** 1 IF (x < y) GOTO 3
+ ** 2 IF (x <= z) GOTO <dest>
+ ** 3 ...
+ */
+ int addr;
+ Expr *pLeft = pExpr->pLeft;
+ Expr *pRight = pExpr->pList->a[0].pExpr;
+ sqlite3ExprCode(pParse, pLeft);
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ sqlite3ExprCode(pParse, pRight);
+ addr = codeCompare(pParse, pLeft, pRight, OP_Lt, 0, !jumpIfNull);
+
+ pRight = pExpr->pList->a[1].pExpr;
+ sqlite3ExprCode(pParse, pRight);
+ codeCompare(pParse, pLeft, pRight, OP_Le, dest, jumpIfNull);
+
+ sqlite3VdbeAddOp(v, OP_Integer, 0, 0);
+ sqlite3VdbeJumpHere(v, addr);
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ break;
+ }
+ default: {
+ sqlite3ExprCode(pParse, pExpr);
+ sqlite3VdbeAddOp(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 sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
+ Vdbe *v = pParse->pVdbe;
+ int op = 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: {
+ sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
+ sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
+ break;
+ }
+ case TK_OR: {
+ int d2 = sqlite3VdbeMakeLabel(v);
+ sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2, !jumpIfNull);
+ sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
+ 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: {
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ sqlite3ExprCode(pParse, pExpr->pRight);
+ codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, dest, jumpIfNull);
+ break;
+ }
+ case TK_ISNULL:
+ case TK_NOTNULL: {
+ sqlite3ExprCode(pParse, pExpr->pLeft);
+ sqlite3VdbeAddOp(v, op, 1, dest);
+ break;
+ }
+ case TK_BETWEEN: {
+ /* The expression is "x BETWEEN y AND z". It is implemented as:
+ **
+ ** 1 IF (x >= y) GOTO 3
+ ** 2 GOTO <dest>
+ ** 3 IF (x > z) GOTO <dest>
+ */
+ int addr;
+ Expr *pLeft = pExpr->pLeft;
+ Expr *pRight = pExpr->pList->a[0].pExpr;
+ sqlite3ExprCode(pParse, pLeft);
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ sqlite3ExprCode(pParse, pRight);
+ addr = sqlite3VdbeCurrentAddr(v);
+ codeCompare(pParse, pLeft, pRight, OP_Ge, addr+3, !jumpIfNull);
+
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, dest);
+ pRight = pExpr->pList->a[1].pExpr;
+ sqlite3ExprCode(pParse, pRight);
+ codeCompare(pParse, pLeft, pRight, OP_Gt, dest, jumpIfNull);
+ break;
+ }
+ default: {
+ sqlite3ExprCode(pParse, pExpr);
+ sqlite3VdbeAddOp(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 sqlite3ExprCompare(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( (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->token.z ){
+ if( pB->token.z==0 ) return 0;
+ if( pB->token.n!=pA->token.n ) return 0;
+ if( sqlite3StrNICmp(pA->token.z, 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(AggInfo *pInfo){
+ int i;
+ i = sqlite3ArrayAllocate((void**)&pInfo->aCol, sizeof(pInfo->aCol[0]), 3);
+ if( i<0 ){
+ return -1;
+ }
+ 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(AggInfo *pInfo){
+ int i;
+ i = sqlite3ArrayAllocate((void**)&pInfo->aFunc, sizeof(pInfo->aFunc[0]), 2);
+ if( i<0 ){
+ return -1;
+ }
+ 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_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.
+ */
+ pCol = pAggInfo->aCol;
+ for(i=0; i<pAggInfo->nColumn; i++, pCol++){
+ if( pCol->iTable==pExpr->iTable &&
+ pCol->iColumn==pExpr->iColumn ){
+ break;
+ }
+ }
+ if( i>=pAggInfo->nColumn && (i = addAggInfoColumn(pAggInfo))>=0 ){
+ pCol = &pAggInfo->aCol[i];
+ 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 = i;
+ 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 = pParse->db->enc;
+ i = addAggInfoFunc(pAggInfo);
+ if( i>=0 ){
+ pItem = &pAggInfo->aFunc[i];
+ pItem->pExpr = pExpr;
+ pItem->iMem = pParse->nMem++;
+ pItem->pFunc = sqlite3FindFunction(pParse->db,
+ 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().
+**
+** If errors are seen, leave an error message in zErrMsg and return
+** the number of errors.
+*/
+int sqlite3ExprAnalyzeAggregates(NameContext *pNC, Expr *pExpr){
+ int nErr = pNC->pParse->nErr;
+ walkExprTree(pExpr, analyzeAggregate, pNC);
+ return pNC->pParse->nErr - nErr;
+}
+
+/*
+** Call sqlite3ExprAnalyzeAggregates() for every expression in an
+** expression list. Return the number of errors.
+**
+** If an error is found, the analysis is cut short.
+*/
+int sqlite3ExprAnalyzeAggList(NameContext *pNC, ExprList *pList){
+ struct ExprList_item *pItem;
+ int i;
+ int nErr = 0;
+ if( pList ){
+ for(pItem=pList->a, i=0; nErr==0 && i<pList->nExpr; i++, pItem++){
+ nErr += sqlite3ExprAnalyzeAggregates(pNC, pItem->pExpr);
+ }
+ }
+ return nErr;
+}
diff --git a/kexi/3rdparty/kexisql3/src/func.c b/kexi/3rdparty/kexisql3/src/func.c
new file mode 100644
index 000000000..9917f4e29
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/func.c
@@ -0,0 +1,1118 @@
+/*
+** 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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#include "sqliteInt.h"
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "vdbeInt.h"
+#include "os.h"
+
+/*
+** 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 char *z = sqlite3_value_text(argv[0]);
+ for(len=0; *z; z++){ if( (0xc0&*z)!=0x80 ) len++; }
+ 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 ) iVal = iVal * -1;
+ 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 * -1.0;
+ sqlite3_result_double(context, rVal);
+ break;
+ }
+ }
+}
+
+/*
+** Implementation of the substr() function
+*/
+static void substrFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *z;
+ const char *z2;
+ int i;
+ int p1, p2, len;
+
+ assert( argc==3 );
+ z = sqlite3_value_text(argv[0]);
+ if( z==0 ) return;
+ p1 = sqlite3_value_int(argv[1]);
+ p2 = sqlite3_value_int(argv[2]);
+ for(len=0, z2=z; *z2; z2++){ if( (0xc0&*z2)!=0x80 ) len++; }
+ if( p1<0 ){
+ p1 += len;
+ if( p1<0 ){
+ p2 += p1;
+ p1 = 0;
+ }
+ }else if( p1>0 ){
+ p1--;
+ }
+ if( p1+p2>len ){
+ p2 = len-p1;
+ }
+ 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++; }
+ if( p2<0 ) p2 = 0;
+ sqlite3_result_text(context, &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( SQLITE_NULL==sqlite3_value_type(argv[0]) ) return;
+ r = sqlite3_value_double(argv[0]);
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%.*f",n,r);
+ sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+}
+
+/*
+** Implementation of the upper() and lower() SQL functions.
+*/
+static void upperFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+ unsigned char *z;
+ int i;
+ if( argc<1 || SQLITE_NULL==sqlite3_value_type(argv[0]) ) return;
+ z = sqliteMalloc(sqlite3_value_bytes(argv[0])+1);
+ if( z==0 ) return;
+ strcpy(z, sqlite3_value_text(argv[0]));
+ for(i=0; z[i]; i++){
+ z[i] = toupper(z[i]);
+ }
+ sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT);
+ sqliteFree(z);
+}
+static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+ unsigned char *z;
+ int i;
+ if( argc<1 || SQLITE_NULL==sqlite3_value_type(argv[0]) ) return;
+ z = sqliteMalloc(sqlite3_value_bytes(argv[0])+1);
+ if( z==0 ) return;
+ strcpy(z, sqlite3_value_text(argv[0]));
+ for(i=0; z[i]; i++){
+ z[i] = tolower(z[i]);
+ }
+ sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT);
+ sqliteFree(z);
+}
+
+/*
+** 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
+){
+ int r;
+ sqlite3Randomness(sizeof(r), &r);
+ sqlite3_result_int(context, r);
+}
+
+/*
+** 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_user_data(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_user_data(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_user_data(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;
+};
+
+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 };
+
+/*
+** 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) sqlite3ReadUtf8(X)
+
+
+/*
+** 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 */
+){
+ register int c;
+ int invert;
+ int seen;
+ int c2;
+ 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 = *zPattern)!=0 ){
+ if( !prevEscape && c==matchAll ){
+ while( (c=zPattern[1]) == matchAll || c == matchOne ){
+ if( c==matchOne ){
+ if( *zString==0 ) return 0;
+ sqliteNextChar(zString);
+ }
+ zPattern++;
+ }
+ if( c && esc && sqlite3ReadUtf8(&zPattern[1])==esc ){
+ u8 const *zTemp = &zPattern[1];
+ sqliteNextChar(zTemp);
+ c = *zTemp;
+ }
+ if( c==0 ) return 1;
+ if( c==matchSet ){
+ assert( esc==0 ); /* This is GLOB, not LIKE */
+ while( *zString && patternCompare(&zPattern[1],zString,pInfo,esc)==0 ){
+ sqliteNextChar(zString);
+ }
+ return *zString!=0;
+ }else{
+ while( (c2 = *zString)!=0 ){
+ if( noCase ){
+ c2 = sqlite3UpperToLower[c2];
+ c = sqlite3UpperToLower[c];
+ while( c2 != 0 && c2 != c ){ c2 = sqlite3UpperToLower[*++zString]; }
+ }else{
+ while( c2 != 0 && c2 != c ){ c2 = *++zString; }
+ }
+ if( c2==0 ) return 0;
+ if( patternCompare(&zPattern[1],zString,pInfo,esc) ) return 1;
+ sqliteNextChar(zString);
+ }
+ return 0;
+ }
+ }else if( !prevEscape && c==matchOne ){
+ if( *zString==0 ) return 0;
+ sqliteNextChar(zString);
+ zPattern++;
+ }else if( c==matchSet ){
+ int prior_c = 0;
+ assert( esc==0 ); /* This only occurs for GLOB, not LIKE */
+ 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++;
+ }else if( esc && !prevEscape && sqlite3ReadUtf8(zPattern)==esc){
+ prevEscape = 1;
+ sqliteNextChar(zPattern);
+ }else{
+ if( noCase ){
+ if( sqlite3UpperToLower[c] != sqlite3UpperToLower[*zString] ) return 0;
+ }else{
+ if( c != *zString ) return 0;
+ }
+ zPattern++;
+ zString++;
+ 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
+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 = sqlite3_value_text(argv[0]);
+ const unsigned char *zB = sqlite3_value_text(argv[1]);
+ int escape = 0;
+ 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( sqlite3utf8CharLen(zEsc, -1)!=1 ){
+ sqlite3_result_error(context,
+ "ESCAPE expression must be a single character", -1);
+ return;
+ }
+ escape = sqlite3ReadUtf8(zEsc);
+ }
+ if( zA && zB ){
+ struct compareInfo *pInfo = sqlite3_user_data(context);
+#ifdef SQLITE_TEST
+ sqlite3_like_count++;
+#endif
+ sqlite3_result_int(context, patternCompare(zA, zB, 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);
+}
+
+
+/*
+** 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: {
+ static const char hexdigits[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+ char *zText = 0;
+ int nBlob = sqlite3_value_bytes(argv[0]);
+ char const *zBlob = sqlite3_value_blob(argv[0]);
+
+ zText = (char *)sqliteMalloc((2*nBlob)+4);
+ if( !zText ){
+ sqlite3_result_error(context, "out of memory", -1);
+ }else{
+ 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);
+ sqliteFree(zText);
+ }
+ break;
+ }
+ case SQLITE_TEXT: {
+ int i,j,n;
+ const char *zArg = sqlite3_value_text(argv[0]);
+ char *z;
+
+ for(i=n=0; zArg[i]; i++){ if( zArg[i]=='\'' ) n++; }
+ z = sqliteMalloc( i+n+3 );
+ if( z==0 ) return;
+ 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, SQLITE_TRANSIENT);
+ sqliteFree(z);
+ }
+ }
+}
+
+#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]);
+ 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;
+ sqlite3_result_text(context, zResult, 4, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_text(context, "?000", 4, SQLITE_STATIC);
+ }
+}
+#endif
+
+#ifdef SQLITE_TEST
+/*
+** This function generates a string of random characters. Used for
+** generating test data.
+*/
+static void randStr(sqlite3_context *context, int argc, sqlite3_value **argv){
+ static const unsigned char zSrc[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ ".-!,:*^+=_|?/<> ";
+ int iMin, iMax, n, r, i;
+ unsigned char zBuf[1000];
+ if( argc>=1 ){
+ iMin = sqlite3_value_int(argv[0]);
+ if( iMin<0 ) iMin = 0;
+ if( iMin>=sizeof(zBuf) ) iMin = sizeof(zBuf)-1;
+ }else{
+ iMin = 1;
+ }
+ if( argc>=2 ){
+ iMax = sqlite3_value_int(argv[1]);
+ if( iMax<iMin ) iMax = iMin;
+ if( iMax>=sizeof(zBuf) ) iMax = sizeof(zBuf)-1;
+ }else{
+ iMax = 50;
+ }
+ n = iMin;
+ if( iMax>iMin ){
+ sqlite3Randomness(sizeof(r), &r);
+ r &= 0x7fffffff;
+ n += r%(iMax + 1 - iMin);
+ }
+ assert( n<sizeof(zBuf) );
+ sqlite3Randomness(n, zBuf);
+ for(i=0; i<n; i++){
+ zBuf[i] = zSrc[zBuf[i]%(sizeof(zSrc)-1)];
+ }
+ zBuf[n] = 0;
+ sqlite3_result_text(context, zBuf, n, SQLITE_TRANSIENT);
+}
+#endif /* SQLITE_TEST */
+
+#ifdef SQLITE_TEST
+/*
+** The following two SQL functions are used to test returning a text
+** result with a destructor. Function 'test_destructor' takes one argument
+** and returns the same argument interpreted as TEXT. A destructor is
+** passed with the sqlite3_result_text() call.
+**
+** SQL function 'test_destructor_count' returns the number of outstanding
+** allocations made by 'test_destructor';
+**
+** WARNING: Not threadsafe.
+*/
+static int test_destructor_count_var = 0;
+static void destructor(void *p){
+ char *zVal = (char *)p;
+ assert(zVal);
+ zVal--;
+ sqliteFree(zVal);
+ test_destructor_count_var--;
+}
+static void test_destructor(
+ sqlite3_context *pCtx,
+ int nArg,
+ sqlite3_value **argv
+){
+ char *zVal;
+ int len;
+ sqlite3 *db = sqlite3_user_data(pCtx);
+
+ test_destructor_count_var++;
+ assert( nArg==1 );
+ if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ len = sqlite3ValueBytes(argv[0], db->enc);
+ zVal = sqliteMalloc(len+3);
+ zVal[len] = 0;
+ zVal[len-1] = 0;
+ assert( zVal );
+ zVal++;
+ memcpy(zVal, sqlite3ValueText(argv[0], db->enc), len);
+ if( db->enc==SQLITE_UTF8 ){
+ sqlite3_result_text(pCtx, zVal, -1, destructor);
+#ifndef SQLITE_OMIT_UTF16
+ }else if( db->enc==SQLITE_UTF16LE ){
+ sqlite3_result_text16le(pCtx, zVal, -1, destructor);
+ }else{
+ sqlite3_result_text16be(pCtx, zVal, -1, destructor);
+#endif /* SQLITE_OMIT_UTF16 */
+ }
+}
+static void test_destructor_count(
+ sqlite3_context *pCtx,
+ int nArg,
+ sqlite3_value **argv
+){
+ sqlite3_result_int(pCtx, test_destructor_count_var);
+}
+#endif /* SQLITE_TEST */
+
+#ifdef SQLITE_TEST
+/*
+** Routines for testing the sqlite3_get_auxdata() and sqlite3_set_auxdata()
+** interface.
+**
+** The test_auxdata() SQL function attempts to register each of its arguments
+** as auxiliary data. If there are no prior registrations of aux data for
+** that argument (meaning the argument is not a constant or this is its first
+** call) then the result for that argument is 0. If there is a prior
+** registration, the result for that argument is 1. The overall result
+** is the individual argument results separated by spaces.
+*/
+static void free_test_auxdata(void *p) {sqliteFree(p);}
+static void test_auxdata(
+ sqlite3_context *pCtx,
+ int nArg,
+ sqlite3_value **argv
+){
+ int i;
+ char *zRet = sqliteMalloc(nArg*2);
+ if( !zRet ) return;
+ for(i=0; i<nArg; i++){
+ char const *z = sqlite3_value_text(argv[i]);
+ if( z ){
+ char *zAux = sqlite3_get_auxdata(pCtx, i);
+ if( zAux ){
+ zRet[i*2] = '1';
+ if( strcmp(zAux, z) ){
+ sqlite3_result_error(pCtx, "Auxilary data corruption", -1);
+ return;
+ }
+ }else{
+ zRet[i*2] = '0';
+ zAux = sqliteStrDup(z);
+ sqlite3_set_auxdata(pCtx, i, zAux, free_test_auxdata);
+ }
+ zRet[i*2+1] = ' ';
+ }
+ }
+ sqlite3_result_text(pCtx, zRet, 2*nArg-1, free_test_auxdata);
+}
+#endif /* SQLITE_TEST */
+
+#ifdef SQLITE_TEST
+/*
+** A function to test error reporting from user functions. This function
+** returns a copy of it's first argument as an error.
+*/
+static void test_error(
+ sqlite3_context *pCtx,
+ int nArg,
+ sqlite3_value **argv
+){
+ sqlite3_result_error(pCtx, sqlite3_value_text(argv[0]), 0);
+}
+#endif /* SQLITE_TEST */
+
+/*
+** 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 */
+ u8 seenFloat; /* True if there has been any floating point value */
+};
+
+/*
+** Routines used to compute the sum or average.
+*/
+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_type(argv[0]);
+ if( p && type!=SQLITE_NULL ){
+ p->sum += sqlite3_value_double(argv[0]);
+ p->cnt++;
+ if( type==SQLITE_FLOAT ){
+ p->seenFloat = 1;
+ }
+ }
+}
+static void sumFinalize(sqlite3_context *context){
+ SumCtx *p;
+ p = sqlite3_aggregate_context(context, 0);
+ if( p && p->cnt>0 ){
+ if( p->seenFloat ){
+ sqlite3_result_double(context, p->sum);
+ }else{
+ sqlite3_result_int64(context, (i64)p->sum);
+ }
+ }
+}
+static void avgFinalize(sqlite3_context *context){
+ SumCtx *p;
+ p = sqlite3_aggregate_context(context, 0);
+ if( p && p->cnt>0 ){
+ sqlite3_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 */
+};
+
+/*
+** 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(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_int(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)==(void *)-1)?1: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);
+ }
+}
+
+
+/*
+** 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 sqlite3RegisterBuiltinFunctions(sqlite3 *db){
+ static const struct {
+ char *zName;
+ signed char nArg;
+ u8 argType; /* 0: none. 1: db 2: (-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, 2, SQLITE_UTF8, 1, minmaxFunc },
+ { "max", 0, 2, SQLITE_UTF8, 1, 0 },
+ { "typeof", 1, 0, SQLITE_UTF8, 0, typeofFunc },
+ { "length", 1, 0, SQLITE_UTF8, 0, lengthFunc },
+ { "substr", 3, 0, SQLITE_UTF8, 0, substrFunc },
+#ifndef SQLITE_OMIT_UTF16
+ { "substr", 3, 0, SQLITE_UTF16LE, 0, sqlite3utf16Substr },
+#endif
+ { "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 },
+ { "ifnull", 2, 0, SQLITE_UTF8, 1, ifnullFunc },
+ { "random", -1, 0, SQLITE_UTF8, 0, randomFunc },
+ { "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, 1, SQLITE_UTF8, 0, last_insert_rowid },
+ { "changes", 0, 1, SQLITE_UTF8, 0, changes },
+ { "total_changes", 0, 1, SQLITE_UTF8, 0, total_changes },
+#ifdef SQLITE_SOUNDEX
+ { "soundex", 1, 0, SQLITE_UTF8, 0, soundexFunc},
+#endif
+#ifdef SQLITE_TEST
+ { "randstr", 2, 0, SQLITE_UTF8, 0, randStr },
+ { "test_destructor", 1, 1, SQLITE_UTF8, 0, test_destructor},
+ { "test_destructor_count", 0, 0, SQLITE_UTF8, 0, test_destructor_count},
+ { "test_auxdata", -1, 0, SQLITE_UTF8, 0, test_auxdata},
+ { "test_error", 1, 0, SQLITE_UTF8, 0, test_error},
+#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, 2, 1, minmaxStep, minMaxFinalize },
+ { "sum", 1, 0, 0, sumStep, sumFinalize },
+ { "avg", 1, 0, 0, sumStep, avgFinalize },
+ { "count", 0, 0, 0, countStep, countFinalize },
+ { "count", 1, 0, 0, countStep, countFinalize },
+ };
+ int i;
+
+ for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
+ void *pArg = 0;
+ switch( aFuncs[i].argType ){
+ case 1: pArg = db; break;
+ case 2: pArg = (void *)(-1); break;
+ }
+ sqlite3_create_function(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
+ for(i=0; i<sizeof(aAggs)/sizeof(aAggs[0]); i++){
+ void *pArg = 0;
+ switch( aAggs[i].argType ){
+ case 1: pArg = db; break;
+ case 2: pArg = (void *)(-1); break;
+ }
+ sqlite3_create_function(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);
+#ifdef SQLITE_SSE
+ 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.
+*/
+void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){
+ struct compareInfo *pInfo;
+ if( caseSensitive ){
+ pInfo = (struct compareInfo*)&likeInfoAlt;
+ }else{
+ pInfo = (struct compareInfo*)&likeInfoNorm;
+ }
+ sqlite3_create_function(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0);
+ sqlite3_create_function(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0);
+ sqlite3_create_function(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.
+*/
+int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){
+ FuncDef *pDef;
+ if( pExpr->op!=TK_FUNCTION ){
+ return 0;
+ }
+ if( pExpr->pList->nExpr!=2 ){
+ return 0;
+ }
+ pDef = sqlite3FindFunction(db, 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;
+}
diff --git a/kexi/3rdparty/kexisql3/src/hash.c b/kexi/3rdparty/kexisql3/src/hash.c
new file mode 100644
index 000000000..8950fa7e3
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/hash.c
@@ -0,0 +1,387 @@
+/*
+** 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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#include "sqliteInt.h"
+#include <assert.h>
+
+/* 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.
+*/
+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.
+*/
+void sqlite3HashClear(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;
+}
+
+#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 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 */
+ 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;
+ 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 && 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 *sqlite3HashFind(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 *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);
+ 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>0 );
+ assert( (pH->htsize & (pH->htsize-1))==0 );
+ h = hraw & (pH->htsize-1);
+ insertElement(pH, &pH->ht[h], new_elem);
+ new_elem->data = data;
+ return 0;
+}
diff --git a/kexi/3rdparty/kexisql3/src/hash.h b/kexi/3rdparty/kexisql3/src/hash.h
new file mode 100644
index 000000000..4f4c20e86
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#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 // 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.
+*/
+void sqlite3HashInit(Hash*, int keytype, int copyKey);
+void *sqlite3HashInsert(Hash*, const void *pKey, int nKey, void *pData);
+void *sqlite3HashFind(const Hash*, const void *pKey, int nKey);
+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_ */
diff --git a/kexi/3rdparty/kexisql3/src/insert.c b/kexi/3rdparty/kexisql3/src/insert.c
new file mode 100644
index 000000000..28d4236eb
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/insert.c
@@ -0,0 +1,1107 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include "sqliteInt.h"
+
+/*
+** Set P3 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
+** ------------------------------
+** 'n' NUMERIC
+** 'i' INTEGER
+** 't' TEXT
+** 'o' NONE
+*/
+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;
+ pIdx->zColAff = (char *)sqliteMalloc(pIdx->nColumn+1);
+ if( !pIdx->zColAff ){
+ return;
+ }
+ for(n=0; n<pIdx->nColumn; n++){
+ pIdx->zColAff[n] = pTab->aCol[pIdx->aiColumn[n]].affinity;
+ }
+ pIdx->zColAff[pIdx->nColumn] = '\0';
+ }
+
+ sqlite3VdbeChangeP3(v, -1, pIdx->zColAff, 0);
+}
+
+/*
+** Set P3 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
+** ------------------------------
+** 'n' NUMERIC
+** 'i' INTEGER
+** 't' TEXT
+** 'o' NONE
+*/
+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;
+
+ zColAff = (char *)sqliteMalloc(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;
+ }
+
+ sqlite3VdbeChangeP3(v, -1, pTab->zColAff, 0);
+}
+
+/*
+** Return non-zero if SELECT statement p opens the table with rootpage
+** iTab in database iDb. This is used to see if a statement of the form
+** "INSERT INTO <iDb, iTab> SELECT ..." can run without using temporary
+** table for the results of the SELECT.
+**
+** No checking is done for sub-selects that are part of expressions.
+*/
+static int selectReadsTable(Select *p, int iDb, int iTab){
+ int i;
+ struct SrcList_item *pItem;
+ if( p->pSrc==0 ) return 0;
+ for(i=0, pItem=p->pSrc->a; i<p->pSrc->nSrc; i++, pItem++){
+ if( pItem->pSelect ){
+ if( selectReadsTable(pItem->pSelect, iDb, iTab) ) return 1;
+ }else{
+ if( pItem->pTab->iDb==iDb && pItem->pTab->tnum==iTab ) return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** 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 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 */
+){
+ 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 = 0; /* VDBE Cursor number for pTab */
+ int iCont=0,iBreak=0; /* Beginning and end of the loop over srcTab */
+ sqlite3 *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 = 0; /* Store SELECT results in intermediate table */
+ int srcTab = 0; /* Data comes from this temporary cursor if >=0 */
+ 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 iCntMem = 0; /* Memory cell used for the row counter */
+ int newIdx = -1; /* Cursor for the NEW table */
+ Db *pDb; /* The database containing table being inserted into */
+ int counterMem = 0; /* Memory cell holding AUTOINCREMENT counter */
+
+#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
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ int counterRowid; /* Memory cell holding rowid of autoinc counter */
+#endif
+
+ if( pParse->nErr || sqlite3_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 = sqlite3SrcListLookup(pParse, pTabList);
+ if( pTab==0 ){
+ goto insert_cleanup;
+ }
+ assert( pTab->iDb<db->nDb );
+ pDb = &db->aDb[pTab->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;
+ }
+ if( pTab==0 ) goto insert_cleanup;
+
+ /* If pTab is really a view, make sure it has been initialized.
+ */
+ if( isView && sqlite3ViewGetColumnNames(pParse, pTab) ){
+ goto insert_cleanup;
+ }
+
+ /* Ensure all required collation sequences are available. */
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ if( sqlite3CheckIndexCollSeq(pParse, pIdx) ){
+ 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, pTab->iDb);
+
+ /* if there are row triggers, allocate a temp table for new.* references. */
+ if( triggers_exist ){
+ newIdx = pParse->nTab++;
+ }
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ /* If this is an AUTOINCREMENT table, look up the sequence number in the
+ ** sqlite_sequence table and store it in memory cell counterMem. Also
+ ** remember the rowid of the sqlite_sequence table entry in memory cell
+ ** counterRowid.
+ */
+ if( pTab->autoInc ){
+ int iCur = pParse->nTab;
+ int base = sqlite3VdbeCurrentAddr(v);
+ counterRowid = pParse->nMem++;
+ counterMem = pParse->nMem++;
+ sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqlite3VdbeAddOp(v, OP_OpenRead, iCur, pDb->pSeqTab->tnum);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, 2);
+ sqlite3VdbeAddOp(v, OP_Rewind, iCur, base+13);
+ sqlite3VdbeAddOp(v, OP_Column, iCur, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->zName, 0);
+ sqlite3VdbeAddOp(v, OP_Ne, 28417, base+12);
+ sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_MemStore, counterRowid, 1);
+ sqlite3VdbeAddOp(v, OP_Column, iCur, 1);
+ sqlite3VdbeAddOp(v, OP_MemStore, counterMem, 1);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, base+13);
+ sqlite3VdbeAddOp(v, OP_Next, iCur, base+4);
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }
+#endif /* SQLITE_OMIT_AUTOINCREMENT */
+
+ /* 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 = sqlite3VdbeAddOp(v, OP_Goto, 0, 0);
+ iSelectLoop = sqlite3VdbeCurrentAddr(v);
+ iInsertBlock = sqlite3VdbeMakeLabel(v);
+
+ /* Resolve the expressions in the SELECT statement and execute it. */
+ rc = sqlite3Select(pParse, pSelect, SRT_Subroutine, iInsertBlock,0,0,0,0);
+ if( rc || pParse->nErr || sqlite3_malloc_failed ) goto insert_cleanup;
+
+ iCleanup = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp(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 || selectReadsTable(pSelect, pTab->iDb, pTab->tnum) ){
+ 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++;
+ sqlite3VdbeResolveLabel(v, iInsertBlock);
+ sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, 0);
+ sqlite3TableAffinityStr(v, pTab);
+ sqlite3VdbeAddOp(v, OP_NewRowid, srcTab, 0);
+ sqlite3VdbeAddOp(v, OP_Pull, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Insert, srcTab, 0);
+ sqlite3VdbeAddOp(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.
+ */
+ sqlite3VdbeJumpHere(v, iInitCode);
+ sqlite3VdbeAddOp(v, OP_OpenVirtual, srcTab, 0);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, srcTab, nColumn);
+ sqlite3VdbeAddOp(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;
+ assert( pList!=0 );
+ srcTab = -1;
+ useTempTable = 0;
+ assert( pList );
+ nColumn = pList->nExpr;
+ 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( pColumn==0 && nColumn!=pTab->nCol ){
+ 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 ){
+ keyColumn = pTab->iPKey;
+ }
+
+ /* Open the temp table for FOR EACH ROW triggers
+ */
+ if( triggers_exist ){
+ sqlite3VdbeAddOp(v, OP_OpenPseudo, newIdx, 0);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, newIdx, pTab->nCol);
+ }
+
+ /* Initialize the count of rows to be inserted
+ */
+ if( db->flags & SQLITE_CountRows ){
+ iCntMem = pParse->nMem++;
+ sqlite3VdbeAddOp(v, OP_MemInt, 0, iCntMem);
+ }
+
+ /* Open tables and indices if there are no row triggers */
+ if( !triggers_exist ){
+ base = pParse->nTab;
+ sqlite3OpenTableAndIndices(pParse, pTab, base, OP_OpenWrite);
+ }
+
+ /* 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);
+ sqlite3VdbeAddOp(v, OP_Rewind, srcTab, iBreak);
+ iCont = sqlite3VdbeCurrentAddr(v);
+ }else if( pSelect ){
+ sqlite3VdbeAddOp(v, OP_Goto, 0, iSelectLoop);
+ sqlite3VdbeResolveLabel(v, iInsertBlock);
+ }
+
+ /* Run the BEFORE and INSTEAD OF triggers, if there are any
+ */
+ endOfLoop = sqlite3VdbeMakeLabel(v);
+ if( triggers_exist & TRIGGER_BEFORE ){
+
+ /* 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 ){
+ sqlite3VdbeAddOp(v, OP_Integer, -1, 0);
+ }else if( useTempTable ){
+ sqlite3VdbeAddOp(v, OP_Column, srcTab, keyColumn);
+ }else{
+ assert( pSelect==0 ); /* Otherwise useTempTable is true */
+ sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr);
+ sqlite3VdbeAddOp(v, OP_NotNull, -1, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Integer, -1, 0);
+ sqlite3VdbeAddOp(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 ){
+ sqlite3ExprCode(pParse, pTab->aCol[i].pDflt);
+ }else if( useTempTable ){
+ sqlite3VdbeAddOp(v, OP_Column, srcTab, j);
+ }else{
+ assert( pSelect==0 ); /* Otherwise useTempTable is true */
+ sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr);
+ }
+ }
+ sqlite3VdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
+
+ /* 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);
+ }
+ sqlite3VdbeAddOp(v, OP_Insert, newIdx, 0);
+
+ /* Fire BEFORE or INSTEAD OF triggers */
+ if( sqlite3CodeRowTrigger(pParse, TK_INSERT, 0, TRIGGER_BEFORE, pTab,
+ newIdx, -1, onError, endOfLoop) ){
+ goto insert_cleanup;
+ }
+ }
+
+ /* If any triggers exists, the opening of tables and indices is deferred
+ ** until now.
+ */
+ if( triggers_exist && !isView ){
+ base = pParse->nTab;
+ sqlite3OpenTableAndIndices(pParse, pTab, base, OP_OpenWrite);
+ }
+
+ /* 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( keyColumn>=0 ){
+ if( useTempTable ){
+ sqlite3VdbeAddOp(v, OP_Column, srcTab, keyColumn);
+ }else if( pSelect ){
+ sqlite3VdbeAddOp(v, OP_Dup, nColumn - keyColumn - 1, 1);
+ }else{
+ sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr);
+ }
+ /* If the PRIMARY KEY expression is NULL, then use OP_NewRowid
+ ** to generate a unique primary key value.
+ */
+ sqlite3VdbeAddOp(v, OP_NotNull, -1, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ sqlite3VdbeAddOp(v, OP_NewRowid, base, counterMem);
+ sqlite3VdbeAddOp(v, OP_MustBeInt, 0, 0);
+ }else{
+ sqlite3VdbeAddOp(v, OP_NewRowid, base, counterMem);
+ }
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ if( pTab->autoInc ){
+ sqlite3VdbeAddOp(v, OP_MemMax, counterMem, 0);
+ }
+#endif /* SQLITE_OMIT_AUTOINCREMENT */
+
+ /* 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. */
+ sqlite3VdbeAddOp(v, OP_Null, 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 ){
+ sqlite3ExprCode(pParse, pTab->aCol[i].pDflt);
+ }else if( useTempTable ){
+ sqlite3VdbeAddOp(v, OP_Column, srcTab, j);
+ }else if( pSelect ){
+ sqlite3VdbeAddOp(v, OP_Dup, i+nColumn-j, 1);
+ }else{
+ sqlite3ExprCode(pParse, pList->a[j].pExpr);
+ }
+ }
+
+ /* Generate code to check constraints and generate index keys and
+ ** do the insertion.
+ */
+ sqlite3GenerateConstraintChecks(pParse, pTab, base, 0, keyColumn>=0,
+ 0, onError, endOfLoop);
+ sqlite3CompleteInsertion(pParse, pTab, base, 0,0,0,
+ (triggers_exist & TRIGGER_AFTER)!=0 ? newIdx : -1);
+ }
+
+ /* Update the count of rows that are inserted
+ */
+ if( (db->flags & SQLITE_CountRows)!=0 ){
+ sqlite3VdbeAddOp(v, OP_MemIncr, iCntMem, 0);
+ }
+
+ if( triggers_exist ){
+ /* Close all tables opened */
+ if( !isView ){
+ sqlite3VdbeAddOp(v, OP_Close, base, 0);
+ for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
+ sqlite3VdbeAddOp(v, OP_Close, idx+base, 0);
+ }
+ }
+
+ /* Code AFTER triggers */
+ if( sqlite3CodeRowTrigger(pParse, TK_INSERT, 0, TRIGGER_AFTER, pTab,
+ newIdx, -1, onError, endOfLoop) ){
+ goto insert_cleanup;
+ }
+ }
+
+ /* The bottom of the loop, if the data source is a SELECT statement
+ */
+ sqlite3VdbeResolveLabel(v, endOfLoop);
+ if( useTempTable ){
+ sqlite3VdbeAddOp(v, OP_Next, srcTab, iCont);
+ sqlite3VdbeResolveLabel(v, iBreak);
+ sqlite3VdbeAddOp(v, OP_Close, srcTab, 0);
+ }else if( pSelect ){
+ sqlite3VdbeAddOp(v, OP_Pop, nColumn, 0);
+ sqlite3VdbeAddOp(v, OP_Return, 0, 0);
+ sqlite3VdbeResolveLabel(v, iCleanup);
+ }
+
+ if( !triggers_exist ){
+ /* Close all tables opened */
+ sqlite3VdbeAddOp(v, OP_Close, base, 0);
+ for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
+ sqlite3VdbeAddOp(v, OP_Close, idx+base, 0);
+ }
+ }
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+ /* Update the sqlite_sequence table by storing the content of the
+ ** counter value in memory counterMem back into the sqlite_sequence
+ ** table.
+ */
+ if( pTab->autoInc ){
+ int iCur = pParse->nTab;
+ int base = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqlite3VdbeAddOp(v, OP_OpenWrite, iCur, pDb->pSeqTab->tnum);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, 2);
+ sqlite3VdbeAddOp(v, OP_MemLoad, counterRowid, 0);
+ sqlite3VdbeAddOp(v, OP_NotNull, -1, base+7);
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ sqlite3VdbeAddOp(v, OP_NewRowid, iCur, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->zName, 0);
+ sqlite3VdbeAddOp(v, OP_MemLoad, counterMem, 0);
+ sqlite3VdbeAddOp(v, OP_MakeRecord, 2, 0);
+ sqlite3VdbeAddOp(v, OP_Insert, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }
+#endif
+
+ /*
+ ** 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 ){
+ sqlite3VdbeAddOp(v, OP_MemLoad, iCntMem, 0);
+ sqlite3VdbeAddOp(v, OP_Callback, 1, 0);
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, "rows inserted", P3_STATIC);
+ }
+
+insert_cleanup:
+ sqlite3SrcListDelete(pTabList);
+ sqlite3ExprListDelete(pList);
+ sqlite3SelectDelete(pSelect);
+ sqlite3IdListDelete(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 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.
+**
+** 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 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 and rowidChng 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
+** 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 "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 sqlite3GenerateConstraintChecks(
+ 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 rowidChng, /* 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=0, jumpInst2;
+ int hasTwoRowids = (isUpdate && rowidChng);
+
+ v = sqlite3GetVdbe(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( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+ if( onError==OE_Replace && pTab->aCol[i].pDflt==0 ){
+ onError = OE_Abort;
+ }
+ sqlite3VdbeAddOp(v, OP_Dup, nCol-1-i, 1);
+ addr = sqlite3VdbeAddOp(v, OP_NotNull, 1, 0);
+ 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;
+ sqlite3VdbeAddOp(v, OP_Halt, SQLITE_CONSTRAINT, onError);
+ sqlite3SetString(&zMsg, pTab->zName, ".", pTab->aCol[i].zName,
+ " may not be NULL", (char*)0);
+ sqlite3VdbeChangeP3(v, -1, zMsg, P3_DYNAMIC);
+ break;
+ }
+ case OE_Ignore: {
+ sqlite3VdbeAddOp(v, OP_Pop, nCol+1+hasTwoRowids, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ case OE_Replace: {
+ sqlite3ExprCode(pParse, pTab->aCol[i].pDflt);
+ sqlite3VdbeAddOp(v, OP_Push, nCol-i, 0);
+ break;
+ }
+ }
+ sqlite3VdbeJumpHere(v, addr);
+ }
+
+ /* 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( rowidChng ){
+ onError = pTab->keyConf;
+ if( overrideError!=OE_Default ){
+ onError = overrideError;
+ }else if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+
+ if( isUpdate ){
+ sqlite3VdbeAddOp(v, OP_Dup, nCol+1, 1);
+ sqlite3VdbeAddOp(v, OP_Dup, nCol+1, 1);
+ jumpInst1 = sqlite3VdbeAddOp(v, OP_Eq, 0, 0);
+ }
+ sqlite3VdbeAddOp(v, OP_Dup, nCol, 1);
+ jumpInst2 = sqlite3VdbeAddOp(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: {
+ sqlite3VdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, onError,
+ "PRIMARY KEY must be unique", P3_STATIC);
+ break;
+ }
+ case OE_Replace: {
+ sqlite3GenerateRowIndexDelete(pParse->db, v, pTab, base, 0);
+ if( isUpdate ){
+ sqlite3VdbeAddOp(v, OP_Dup, nCol+hasTwoRowids, 1);
+ sqlite3VdbeAddOp(v, OP_MoveGe, base, 0);
+ }
+ seenReplace = 1;
+ break;
+ }
+ case OE_Ignore: {
+ assert( seenReplace==0 );
+ sqlite3VdbeAddOp(v, OP_Pop, nCol+1+hasTwoRowids, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ }
+ sqlite3VdbeJumpHere(v, jumpInst2);
+ if( isUpdate ){
+ sqlite3VdbeJumpHere(v, jumpInst1);
+ sqlite3VdbeAddOp(v, OP_Dup, nCol+1, 1);
+ sqlite3VdbeAddOp(v, OP_MoveGe, 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 */
+ sqlite3VdbeAddOp(v, OP_Dup, nCol+extra, 1);
+ for(i=0; i<pIdx->nColumn; i++){
+ int idx = pIdx->aiColumn[i];
+ if( idx==pTab->iPKey ){
+ sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol+1, 1);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1);
+ }
+ }
+ jumpInst1 = sqlite3VdbeAddOp(v, OP_MakeIdxRec, pIdx->nColumn, 0);
+ sqlite3IndexAffinityStr(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( 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 */
+ sqlite3VdbeAddOp(v, OP_Dup, extra+nCol+1+hasTwoRowids, 1);
+ jumpInst2 = sqlite3VdbeAddOp(v, OP_IsUnique, base+iCur+1, 0);
+
+ /* 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];
+ 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");
+ sqlite3VdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, onError, zErrMsg, 0);
+ break;
+ }
+ case OE_Ignore: {
+ assert( seenReplace==0 );
+ sqlite3VdbeAddOp(v, OP_Pop, nCol+extra+3+hasTwoRowids, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, ignoreDest);
+ break;
+ }
+ case OE_Replace: {
+ sqlite3GenerateRowDelete(pParse->db, v, pTab, base, 0);
+ if( isUpdate ){
+ sqlite3VdbeAddOp(v, OP_Dup, nCol+extra+1+hasTwoRowids, 1);
+ sqlite3VdbeAddOp(v, OP_MoveGe, base, 0);
+ }
+ seenReplace = 1;
+ break;
+ }
+ }
+#if NULL_DISTINCT_FOR_UNIQUE
+ sqlite3VdbeJumpHere(v, jumpInst1);
+#endif
+ sqlite3VdbeJumpHere(v, jumpInst2);
+ }
+}
+
+/*
+** This routine generates code to finish the INSERT or UPDATE operation
+** that was started by a prior call to sqlite3GenerateConstraintChecks.
+** The stack must contain keys for all active indices followed by data
+** and the rowid 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 sqlite3GenerateConstraintChecks.
+*/
+void sqlite3CompleteInsertion(
+ 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 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 i;
+ Vdbe *v;
+ int nIdx;
+ Index *pIdx;
+ int pik_flags;
+
+ 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( aIdxUsed && aIdxUsed[i]==0 ) continue;
+ sqlite3VdbeAddOp(v, OP_IdxInsert, base+i+1, 0);
+ }
+ sqlite3VdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
+ sqlite3TableAffinityStr(v, pTab);
+#ifndef SQLITE_OMIT_TRIGGER
+ if( newIdx>=0 ){
+ sqlite3VdbeAddOp(v, OP_Dup, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Dup, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Insert, newIdx, 0);
+ }
+#endif
+ if( pParse->nested ){
+ pik_flags = 0;
+ }else{
+ pik_flags = (OPFLAG_NCHANGE|(isUpdate?0:OPFLAG_LASTROWID));
+ }
+ sqlite3VdbeAddOp(v, OP_Insert, base, pik_flags);
+
+ if( isUpdate && rowidChng ){
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ }
+}
+
+/*
+** Generate code that will open 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.
+*/
+void sqlite3OpenTableAndIndices(
+ Parse *pParse, /* Parsing context */
+ Table *pTab, /* Table to be opened */
+ int base, /* Cursor number assigned to the table */
+ int op /* OP_OpenRead or OP_OpenWrite */
+){
+ int i;
+ Index *pIdx;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ assert( v!=0 );
+ sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ VdbeComment((v, "# %s", pTab->zName));
+ sqlite3VdbeAddOp(v, op, base, pTab->tnum);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, base, pTab->nCol);
+ for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ sqlite3VdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ VdbeComment((v, "# %s", pIdx->zName));
+ sqlite3VdbeOp3(v, op, i+base, pIdx->tnum,
+ (char*)&pIdx->keyInfo, P3_KEYINFO);
+ }
+ if( pParse->nTab<=base+i ){
+ pParse->nTab = base+i;
+ }
+}
diff --git a/kexi/3rdparty/kexisql3/src/kexisql.h b/kexi/3rdparty/kexisql3/src/kexisql.h
new file mode 100644
index 000000000..cec87f9e8
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/kexisql.h
@@ -0,0 +1,36 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KEXISQL_H_
+#define _KEXISQL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** Return 1 if for the database was opened in read-only mode.
+*/
+int sqlite3_is_readonly(sqlite3*);
+
+#ifdef __cplusplus
+} /* End of the 'extern "C"' block */
+#endif
+
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/keywordhash.h b/kexi/3rdparty/kexisql3/src/keywordhash.h
new file mode 100644
index 000000000..9beb5fbd2
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/keywordhash.h
@@ -0,0 +1,97 @@
+/* Hash score: 158 */
+static int keywordCode(const char *z, int n){
+ static const char zText[535] =
+ "ABORTABLEFTEMPORARYADDATABASELECTHENDEFAULTRANSACTIONATURALTER"
+ "AISEACHECKEYAFTEREFERENCESCAPELSEXCEPTRIGGEREGEXPLAINITIALLYANALYZE"
+ "XCLUSIVEXISTSTATEMENTANDEFERRABLEATTACHAVINGLOBEFOREIGNOREINDEX"
+ "AUTOINCREMENTBEGINNERENAMEBETWEENOTNULLIKEBYCASCADEFERREDELETE"
+ "CASECASTCOLLATECOLUMNCOMMITCONFLICTCONSTRAINTERSECTCREATECROSS"
+ "CURRENT_DATECURRENT_TIMESTAMPLANDESCDETACHDISTINCTDROPRAGMATCH"
+ "FAILIMITFROMFULLGROUPDATEIMMEDIATEINSERTINSTEADINTOFFSETISNULL"
+ "JOINORDEREPLACEOUTERESTRICTPRIMARYQUERYRIGHTROLLBACKROWHENUNION"
+ "UNIQUEUSINGVACUUMVALUESVIEWHERE";
+ static const unsigned char aHash[127] = {
+ 91, 80, 106, 90, 0, 4, 0, 0, 113, 0, 83, 0, 0,
+ 94, 44, 76, 92, 0, 105, 108, 96, 0, 0, 10, 0, 0,
+ 112, 0, 109, 102, 0, 28, 48, 0, 41, 0, 0, 65, 71,
+ 0, 63, 19, 0, 104, 36, 103, 0, 107, 74, 0, 0, 33,
+ 0, 61, 37, 0, 8, 0, 114, 38, 12, 0, 77, 40, 25,
+ 66, 0, 0, 31, 81, 53, 30, 50, 20, 88, 0, 34, 0,
+ 75, 26, 0, 72, 0, 0, 0, 64, 47, 67, 22, 87, 29,
+ 69, 86, 0, 1, 0, 9, 100, 58, 18, 0, 111, 82, 98,
+ 54, 6, 85, 0, 0, 49, 93, 0, 101, 0, 70, 0, 0,
+ 15, 0, 115, 51, 56, 0, 2, 55, 0, 110,
+ };
+ static const unsigned char aNext[115] = {
+ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0,
+ 0, 11, 0, 0, 0, 0, 5, 13, 0, 7, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0,
+ 0, 0, 16, 0, 23, 52, 0, 0, 0, 0, 45, 0, 59,
+ 0, 0, 0, 0, 0, 0, 0, 0, 73, 42, 0, 24, 60,
+ 21, 0, 79, 0, 0, 68, 0, 0, 84, 46, 0, 0, 0,
+ 0, 0, 0, 0, 39, 95, 97, 0, 0, 99, 0, 32, 0,
+ 14, 27, 78, 0, 57, 89, 0, 35, 0, 62, 0,
+ };
+ static const unsigned char aLen[115] = {
+ 5, 5, 4, 4, 9, 2, 3, 8, 2, 6, 4, 3, 7,
+ 11, 2, 7, 5, 5, 4, 5, 3, 5, 10, 6, 4, 6,
+ 7, 6, 7, 9, 3, 7, 9, 6, 9, 3, 10, 6, 6,
+ 4, 6, 3, 7, 6, 7, 5, 13, 2, 2, 5, 5, 6,
+ 7, 3, 7, 4, 4, 2, 7, 3, 8, 6, 4, 4, 7,
+ 6, 6, 8, 10, 9, 6, 5, 12, 12, 17, 4, 4, 6,
+ 8, 2, 4, 6, 5, 4, 5, 4, 4, 5, 6, 9, 6,
+ 7, 4, 2, 6, 3, 6, 4, 5, 7, 5, 8, 7, 5,
+ 5, 8, 3, 4, 5, 6, 5, 6, 6, 4, 5,
+ };
+ static const unsigned short int aOffset[115] = {
+ 0, 4, 7, 10, 10, 14, 19, 21, 26, 27, 32, 34, 36,
+ 42, 51, 52, 57, 61, 65, 67, 71, 74, 78, 86, 91, 94,
+ 99, 105, 108, 113, 118, 122, 128, 136, 141, 150, 152, 162, 167,
+ 172, 175, 177, 177, 181, 185, 187, 192, 194, 196, 205, 208, 212,
+ 218, 224, 224, 227, 230, 234, 236, 237, 241, 248, 254, 258, 262,
+ 269, 275, 281, 289, 296, 305, 311, 316, 328, 328, 344, 348, 352,
+ 358, 359, 366, 369, 373, 378, 381, 386, 390, 394, 397, 403, 412,
+ 418, 425, 428, 428, 431, 434, 440, 444, 448, 455, 459, 467, 474,
+ 479, 484, 492, 494, 498, 503, 509, 514, 520, 526, 529,
+ };
+ static const unsigned char aCode[115] = {
+ TK_ABORT, TK_TABLE, TK_JOIN_KW, TK_TEMP, TK_TEMP,
+ TK_OR, TK_ADD, TK_DATABASE, TK_AS, TK_SELECT,
+ TK_THEN, TK_END, TK_DEFAULT, TK_TRANSACTION,TK_ON,
+ TK_JOIN_KW, TK_ALTER, TK_RAISE, TK_EACH, TK_CHECK,
+ TK_KEY, TK_AFTER, TK_REFERENCES, TK_ESCAPE, TK_ELSE,
+ TK_EXCEPT, TK_TRIGGER, TK_LIKE_KW, TK_EXPLAIN, TK_INITIALLY,
+ TK_ALL, TK_ANALYZE, TK_EXCLUSIVE, TK_EXISTS, TK_STATEMENT,
+ TK_AND, TK_DEFERRABLE, TK_ATTACH, TK_HAVING, TK_LIKE_KW,
+ TK_BEFORE, TK_FOR, TK_FOREIGN, TK_IGNORE, TK_REINDEX,
+ TK_INDEX, TK_AUTOINCR, TK_TO, TK_IN, TK_BEGIN,
+ TK_JOIN_KW, TK_RENAME, TK_BETWEEN, TK_NOT, TK_NOTNULL,
+ TK_NULL, TK_LIKE_KW, TK_BY, TK_CASCADE, TK_ASC,
+ TK_DEFERRED, TK_DELETE, TK_CASE, TK_CAST, TK_COLLATE,
+ TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_CONSTRAINT, TK_INTERSECT,
+ TK_CREATE, TK_JOIN_KW, TK_CTIME_KW, TK_CTIME_KW, TK_CTIME_KW,
+ TK_PLAN, TK_DESC, TK_DETACH, TK_DISTINCT, TK_IS,
+ TK_DROP, TK_PRAGMA, TK_MATCH, TK_FAIL, TK_LIMIT,
+ TK_FROM, TK_JOIN_KW, TK_GROUP, TK_UPDATE, TK_IMMEDIATE,
+ TK_INSERT, TK_INSTEAD, TK_INTO, TK_OF, TK_OFFSET,
+ TK_SET, TK_ISNULL, TK_JOIN, TK_ORDER, TK_REPLACE,
+ TK_JOIN_KW, TK_RESTRICT, TK_PRIMARY, TK_QUERY, TK_JOIN_KW,
+ TK_ROLLBACK, TK_ROW, TK_WHEN, TK_UNION, TK_UNIQUE,
+ TK_USING, TK_VACUUM, TK_VALUES, TK_VIEW, TK_WHERE,
+ };
+ int h, i;
+ if( n<2 ) return TK_ID;
+ h = ((sqlite3UpperToLower[((unsigned char*)z)[0]]*4) ^
+ (sqlite3UpperToLower[((unsigned char*)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;
+}
+int sqlite3KeywordCode(const char *z, int n){
+ return keywordCode(z, n);
+}
diff --git a/kexi/3rdparty/kexisql3/src/ksqlite2to3 b/kexi/3rdparty/kexisql3/src/ksqlite2to3
new file mode 100755
index 000000000..faa2f459b
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/ksqlite2to3
@@ -0,0 +1,38 @@
+#!/bin/sh
+# This file is part of the KDE project
+# Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.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 of the License, or (at your option) any later version.
+
+# Performs migration from SQLite2 format to SQLite3 format
+# usage: ksqlite2to3 <sqlite2-db-file>
+
+if test $# -lt 1 ; then
+ echo "usage: ksqlite2to3 <sqlite2-db-file>"
+ exit 1
+fi
+
+basename=`basename $0`
+temp=`mktemp -q "$basename"-XXXXXX 2> /dev/null || date +%y%m%d%H%M2`
+dbfile=$1
+dir=`dirname $dbfile`
+
+ksqlite2 $dbfile .quit 2> /dev/null
+if test $? -ne 0 ; then
+ echo "This file is not in SQLite2 format"
+ rm -f "$dir/$temp"
+ exit 1
+fi
+
+ksqlite2 -verbose-dump $dbfile .dump | ksqlite "$dir/$temp"
+if test $? -ne 0 ; then
+ echo "Error during converting"
+ rm -f "$dir/$temp"
+ exit 2
+fi
+
+mv "$dir/$temp" $dbfile 2> /dev/null || exit 3
+
diff --git a/kexi/3rdparty/kexisql3/src/legacy.c b/kexi/3rdparty/kexisql3/src/legacy.c
new file mode 100644
index 000000000..60721b59b
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/legacy.c
@@ -0,0 +1,138 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+
+/*
+** 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 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 nChange = 0;
+ int nCallback;
+
+ if( zSql==0 ) return SQLITE_OK;
+ 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);
+ if( rc!=SQLITE_OK ){
+ if( pStmt ) sqlite3_finalize(pStmt);
+ continue;
+ }
+ if( !pStmt ){
+ /* this happens for a comment or white-space */
+ zSql = zLeftover;
+ continue;
+ }
+
+ db->nChange += nChange;
+ nCallback = 0;
+
+ nCol = sqlite3_column_count(pStmt);
+ azCols = sqliteMalloc(2*nCol*sizeof(const char *));
+ if( nCol && !azCols ){
+ rc = SQLITE_NOMEM;
+ goto exec_out;
+ }
+
+ 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 ){
+ for(i=0; i<nCol; i++){
+ azCols[i] = (char *)sqlite3_column_name(pStmt, i);
+ }
+ nCallback++;
+ }
+ if( rc==SQLITE_ROW ){
+ azVals = &azCols[nCol];
+ for(i=0; i<nCol; i++){
+ azVals[i] = (char *)sqlite3_column_text(pStmt, i);
+ }
+ }
+ if( xCallback(pArg, nCol, azVals, azCols) ){
+ rc = SQLITE_ABORT;
+ goto exec_out;
+ }
+ }
+
+ if( rc!=SQLITE_ROW ){
+ rc = sqlite3_finalize(pStmt);
+ pStmt = 0;
+ if( db->pVdbe==0 ){
+ nChange = db->nChange;
+ }
+ if( rc!=SQLITE_SCHEMA ){
+ nRetry = 0;
+ zSql = zLeftover;
+ while( isspace((unsigned char)zSql[0]) ) zSql++;
+ }
+ break;
+ }
+ }
+
+ sqliteFree(azCols);
+ azCols = 0;
+ }
+
+exec_out:
+ if( pStmt ) sqlite3_finalize(pStmt);
+ if( azCols ) sqliteFree(azCols);
+
+ if( sqlite3_malloc_failed ){
+ rc = SQLITE_NOMEM;
+ }
+ if( rc!=SQLITE_OK && rc==sqlite3_errcode(db) && pzErrMsg ){
+ *pzErrMsg = malloc(1+strlen(sqlite3_errmsg(db)));
+ if( *pzErrMsg ){
+ strcpy(*pzErrMsg, sqlite3_errmsg(db));
+ }
+ }else if( pzErrMsg ){
+ *pzErrMsg = 0;
+ }
+
+ return rc;
+}
diff --git a/kexi/3rdparty/kexisql3/src/main.c b/kexi/3rdparty/kexisql3/src/main.c
new file mode 100644
index 000000000..83356f433
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/main.c
@@ -0,0 +1,1079 @@
+/*
+** 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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+
+/* (jstaniek) used by sqlite_set_verbose_vacuum and in vacuum.c */
+int g_verbose_vacuum = 0;
+void sqlite_set_verbose_vacuum(int set){
+ g_verbose_vacuum=set;
+}
+
+/*
+** The following constant value is used by the SQLITE_BIGENDIAN and
+** SQLITE_LITTLEENDIAN macros.
+*/
+const int sqlite3one = 1;
+
+#ifndef SQLITE_OMIT_GLOBALRECOVER
+/*
+** Linked list of all open database handles. This is used by the
+** sqlite3_global_recover() function. Entries are added to the list
+** by openDatabase() and removed by sqlite3_close().
+*/
+static sqlite3 *pDbList = 0;
+#endif
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Return the transient sqlite3_value object used for encoding conversions
+** during SQL compilation.
+*/
+sqlite3_value *sqlite3GetTransientValue(sqlite3 *db){
+ if( !db->pValue ){
+ db->pValue = sqlite3ValueNew();
+ }
+ return db->pValue;
+}
+#endif
+
+/*
+** The version of the library
+*/
+const char rcsid3[] = "@(#) \044Id: SQLite version " SQLITE_VERSION " $";
+const char sqlite3_version[] = SQLITE_VERSION;
+const char *sqlite3_libversion(void){ return sqlite3_version; }
+int sqlite3_libversion_number(void){ return SQLITE_VERSION_NUMBER; }
+
+/*
+** This is the default collating function named "BINARY" which is always
+** available.
+*/
+static int binCollFunc(
+ void *NotUsed,
+ 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 ){
+ 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_int64 sqlite3_last_insert_rowid(sqlite3 *db){
+ return db->lastRowid;
+}
+
+/*
+** Return the number of changes in the most recent call to sqlite3_exec().
+*/
+int sqlite3_changes(sqlite3 *db){
+ return db->nChange;
+}
+
+/*
+** Return the number of changes since the database handle was opened.
+*/
+int sqlite3_total_changes(sqlite3 *db){
+ return db->nTotalChange;
+}
+
+/*
+** Close an existing SQLite database
+*/
+int sqlite3_close(sqlite3 *db){
+ HashElem *i;
+ int j;
+
+ if( !db ){
+ return SQLITE_OK;
+ }
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+
+#ifdef SQLITE_SSE
+ sqlite3_finalize(db->pFetch);
+#endif
+
+ /* If there are any outstanding VMs, return SQLITE_BUSY. */
+ if( db->pVdbe ){
+ sqlite3Error(db, SQLITE_BUSY,
+ "Unable to close due to unfinalised statements");
+ return SQLITE_BUSY;
+ }
+ assert( !sqlite3SafetyCheck(db) );
+
+ /* FIX ME: db->magic may be set to SQLITE_MAGIC_CLOSED if the database
+ ** cannot be opened for some reason. So this routine needs to run in
+ ** that case. But maybe there should be an extra magic value for the
+ ** "failed to open" state.
+ */
+ if( db->magic!=SQLITE_MAGIC_CLOSED && sqlite3SafetyOn(db) ){
+ /* printf("DID NOT CLOSE\n"); fflush(stdout); */
+ return SQLITE_ERROR;
+ }
+
+ for(j=0; j<db->nDb; j++){
+ struct Db *pDb = &db->aDb[j];
+ if( pDb->pBt ){
+ sqlite3BtreeClose(pDb->pBt);
+ pDb->pBt = 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;
+ sqliteFree(pFunc);
+ }
+ }
+
+ for(i=sqliteHashFirst(&db->aCollSeq); i; i=sqliteHashNext(i)){
+ CollSeq *pColl = (CollSeq *)sqliteHashData(i);
+ sqliteFree(pColl);
+ }
+ sqlite3HashClear(&db->aCollSeq);
+
+ sqlite3HashClear(&db->aFunc);
+ sqlite3Error(db, SQLITE_OK, 0); /* Deallocates any cached error strings. */
+ if( db->pValue ){
+ sqlite3ValueFree(db->pValue);
+ }
+ if( db->pErr ){
+ sqlite3ValueFree(db->pErr);
+ }
+
+#ifndef SQLITE_OMIT_GLOBALRECOVER
+ {
+ sqlite3 *pPrev;
+ sqlite3OsEnterMutex();
+ pPrev = pDbList;
+ while( pPrev && pPrev->pNext!=db ){
+ pPrev = pPrev->pNext;
+ }
+ if( pPrev ){
+ pPrev->pNext = db->pNext;
+ }else{
+ assert( pDbList==db );
+ pDbList = db->pNext;
+ }
+ sqlite3OsLeaveMutex();
+ }
+#endif
+
+ db->magic = SQLITE_MAGIC_ERROR;
+ sqliteFree(db);
+ return SQLITE_OK;
+}
+
+/*
+** Rollback all database files.
+*/
+void sqlite3RollbackAll(sqlite3 *db){
+ int i;
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt ){
+ sqlite3BtreeRollback(db->aDb[i].pBt);
+ db->aDb[i].inTrans = 0;
+ }
+ }
+ sqlite3ResetInternalSchema(db, 0);
+}
+
+/*
+** Return a static string that describes the kind of error specified in the
+** argument.
+*/
+const char *sqlite3ErrStr(int rc){
+ const char *z;
+ switch( rc ){
+ 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_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_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 or column index out of range"; break;
+ case SQLITE_NOTADB: z = "file is encrypted or is not a database";break;
+ /* js */
+ case SQLITE_CANTOPEN_WITH_LOCKED_READWRITE:
+ z = "unable to open with locked read/write access"; break;
+ case SQLITE_CANTOPEN_WITH_LOCKED_WRITE:
+ z = "unable to open with locked write access"; 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 SQLITE_MIN_SLEEP_MS==1
+ 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]))
+ int timeout = ((sqlite3 *)ptr)->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(delay);
+ return 1;
+#else
+ int timeout = ((sqlite3 *)ptr)->busyTimeout;
+ if( (count+1)*1000 > timeout ){
+ return 0;
+ }
+ sqlite3OsSleep(1000);
+ 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.
+*/
+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.
+*/
+int sqlite3_busy_handler(
+ sqlite3 *db,
+ int (*xBusy)(void*,int),
+ void *pArg
+){
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+ db->busyHandler.xFunc = xBusy;
+ db->busyHandler.pArg = pArg;
+ db->busyHandler.nBusy = 0;
+ 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.
+*/
+void sqlite3_progress_handler(
+ sqlite3 *db,
+ int nOps,
+ int (*xProgress)(void*),
+ void *pArg
+){
+ if( !sqlite3SafetyCheck(db) ){
+ 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.
+*/
+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.
+*/
+void sqlite3_interrupt(sqlite3 *db){
+ if( !sqlite3SafetyCheck(db) ){
+ db->flags |= SQLITE_Interrupt;
+ }
+}
+
+/*
+** Windows systems should call this routine to free memory that
+** is returned in the in the errmsg parameter of sqlite3_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.
+*/
+void sqlite3_free(char *p){ free(p); }
+
+/*
+** Create new user functions.
+*/
+int sqlite3_create_function(
+ 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;
+
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+ if( zFunctionName==0 ||
+ (xFunc && (xFinal || xStep)) ||
+ (!xFunc && (xFinal && !xStep)) ||
+ (!xFunc && (!xFinal && xStep)) ||
+ (nArg<-1 || nArg>127) ||
+ (255<(nName = strlen(zFunctionName))) ){
+ 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 = sqlite3_create_function(db, zFunctionName, nArg, SQLITE_UTF8,
+ pUserData, xFunc, xStep, xFinal);
+ if( rc!=SQLITE_OK ) return rc;
+ rc = sqlite3_create_function(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");
+ return SQLITE_BUSY;
+ }else{
+ sqlite3ExpirePreparedStatements(db);
+ }
+ }
+
+ p = sqlite3FindFunction(db, zFunctionName, nName, nArg, enc, 1);
+ if( p==0 ) return SQLITE_NOMEM;
+ p->flags = 0;
+ p->xFunc = xFunc;
+ p->xStep = xStep;
+ p->xFinalize = xFinal;
+ p->pUserData = pUserData;
+ return SQLITE_OK;
+}
+#ifndef SQLITE_OMIT_UTF16
+int sqlite3_create_function16(
+ sqlite3 *db,
+ const void *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pUserData,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*)
+){
+ int rc;
+ char const *zFunc8;
+ sqlite3_value *pTmp;
+
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+ pTmp = sqlite3GetTransientValue(db);
+ sqlite3ValueSetStr(pTmp, -1, zFunctionName, SQLITE_UTF16NATIVE,SQLITE_STATIC);
+ zFunc8 = sqlite3ValueText(pTmp, SQLITE_UTF8);
+
+ if( !zFunc8 ){
+ return SQLITE_NOMEM;
+ }
+ rc = sqlite3_create_function(db, zFunc8, nArg, eTextRep,
+ pUserData, xFunc, xStep, xFinal);
+ return rc;
+}
+#endif
+
+#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.
+*/
+void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){
+ void *pOld = db->pTraceArg;
+ db->xTrace = xTrace;
+ db->pTraceArg = pArg;
+ 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.
+*/
+void *sqlite3_profile(
+ sqlite3 *db,
+ void (*xProfile)(void*,const char*,sqlite_uint64),
+ void *pArg
+){
+ void *pOld = db->pProfileArg;
+ db->xProfile = xProfile;
+ db->pProfileArg = pArg;
+ return pOld;
+}
+#endif /* SQLITE_OMIT_TRACE */
+
+/*** EXPERIMENTAL ***
+**
+** Register a function to be invoked when a transaction comments.
+** If either function returns non-zero, then the commit becomes a
+** rollback.
+*/
+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 = 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 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
+*/
+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 */
+ Btree **ppBtree, /* Pointer to new Btree object written here */
+ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
+ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+){
+ int btree_flags = 0;
+ int rc;
+
+ assert( ppBtree != 0);
+ if( omitJournal ){
+ btree_flags |= BTREE_OMIT_JOURNAL;
+ }
+ if( db->flags & SQLITE_NoReadlock ){
+ btree_flags |= 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 */
+ }
+
+ rc = sqlite3BtreeOpen(zFilename, ppBtree, btree_flags, exclusiveFlag, allowReadonly);
+ if( rc==SQLITE_OK ){
+ sqlite3BtreeSetBusyHandler(*ppBtree, (void*)&db->busyHandler);
+ sqlite3BtreeSetCacheSize(*ppBtree, nCache);
+ }
+ return rc;
+}
+
+/*
+** Return UTF-8 encoded English language explanation of the most recent
+** error.
+*/
+const char *sqlite3_errmsg(sqlite3 *db){
+ const char *z;
+ if( sqlite3_malloc_failed ){
+ return sqlite3ErrStr(SQLITE_NOMEM);
+ }
+ if( sqlite3SafetyCheck(db) || db->errCode==SQLITE_MISUSE ){
+ return sqlite3ErrStr(SQLITE_MISUSE);
+ }
+ z = sqlite3_value_text(db->pErr);
+ if( z==0 ){
+ z = sqlite3ErrStr(db->errCode);
+ }
+ return z;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Return UTF-16 encoded English language explanation of the most recent
+** error.
+*/
+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( sqlite3_malloc_failed ){
+ return (void *)(&outOfMemBe[SQLITE_UTF16NATIVE==SQLITE_UTF16LE?1:0]);
+ }
+ if( sqlite3SafetyCheck(db) || db->errCode==SQLITE_MISUSE ){
+ return (void *)(&misuseBe[SQLITE_UTF16NATIVE==SQLITE_UTF16LE?1:0]);
+ }
+ 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);
+ }
+ return z;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** Return the most recent error code generated by an SQLite routine.
+*/
+int sqlite3_errcode(sqlite3 *db){
+ if( sqlite3_malloc_failed ){
+ return SQLITE_NOMEM;
+ }
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+ return db->errCode;
+}
+
+/*
+** 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 */
+ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
+ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+){
+ sqlite3 *db;
+ int rc, i;
+ CollSeq *pColl;
+
+ /* Allocate the sqlite data structure */
+ db = sqliteMalloc( sizeof(sqlite3) );
+ if( db==0 ) goto opendb_out;
+ db->priorNewRowid = 0;
+ db->magic = SQLITE_MAGIC_BUSY;
+ db->nDb = 2;
+ db->aDb = db->aDbStatic;
+ db->enc = SQLITE_UTF8;
+ db->autoCommit = 1;
+ db->flags |= SQLITE_ShortColNames;
+ sqlite3HashInit(&db->aFunc, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&db->aCollSeq, SQLITE_HASH_STRING, 0);
+ for(i=0; i<db->nDb; i++){
+ sqlite3HashInit(&db->aDb[i].tblHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&db->aDb[i].idxHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&db->aDb[i].trigHash, SQLITE_HASH_STRING, 0);
+ sqlite3HashInit(&db->aDb[i].aFKey, SQLITE_HASH_STRING, 1);
+ }
+
+ /* 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.
+ */
+ if( sqlite3_create_collation(db, "BINARY", SQLITE_UTF8, 0,binCollFunc) ||
+ sqlite3_create_collation(db, "BINARY", SQLITE_UTF16, 0,binCollFunc) ||
+ !(db->pDfltColl = sqlite3FindCollSeq(db, db->enc, "BINARY", 6, 0)) ){
+ rc = db->errCode;
+ assert( rc!=SQLITE_OK );
+ db->magic = SQLITE_MAGIC_CLOSED;
+ goto opendb_out;
+ }
+
+ /* Also add a UTF-8 case-insensitive collation sequence. */
+ sqlite3_create_collation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc);
+
+ /* 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 */
+ rc = sqlite3BtreeFactory(db, zFilename, 0, MAX_PAGES, &db->aDb[0].pBt, exclusiveFlag, allowReadonly);
+ if( rc!=SQLITE_OK ){
+ sqlite3Error(db, rc, 0);
+ db->magic = SQLITE_MAGIC_CLOSED;
+ goto opendb_out;
+ }
+
+ /* 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
+
+
+ /* 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.
+ */
+ sqlite3RegisterBuiltinFunctions(db);
+ sqlite3Error(db, SQLITE_OK, 0);
+ db->magic = SQLITE_MAGIC_OPEN;
+
+opendb_out:
+ if( sqlite3_errcode(db)==SQLITE_OK && sqlite3_malloc_failed ){
+ sqlite3Error(db, SQLITE_NOMEM, 0);
+ }
+ *ppDb = db;
+#ifndef SQLITE_OMIT_GLOBALRECOVER
+ if( db ){
+ sqlite3OsEnterMutex();
+ db->pNext = pDbList;
+ pDbList = db;
+ sqlite3OsLeaveMutex();
+ }
+#endif
+ return sqlite3_errcode(db);
+}
+
+/*
+** Open a new database handle.
+*/
+int sqlite3_open(
+ const char *zFilename,
+ sqlite3 **ppDb,
+ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
+ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+){
+ return openDatabase(zFilename, ppDb, exclusiveFlag, allowReadonly);
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Open a new database handle.
+*/
+int sqlite3_open16(
+ const void *zFilename,
+ sqlite3 **ppDb,
+ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
+ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+){
+ char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */
+ int rc = SQLITE_NOMEM;
+ sqlite3_value *pVal;
+
+ assert( ppDb );
+ *ppDb = 0;
+ pVal = sqlite3ValueNew();
+ sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8);
+ if( zFilename8 ){
+ rc = openDatabase(zFilename8, ppDb, exclusiveFlag, allowReadonly);
+ if( rc==SQLITE_OK && *ppDb ){
+ sqlite3_exec(*ppDb, "PRAGMA encoding = 'UTF-16'", 0, 0, 0);
+ }
+ }
+ if( pVal ){
+ sqlite3ValueFree(pVal);
+ }
+
+ return rc;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** 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().
+*/
+int sqlite3_finalize(sqlite3_stmt *pStmt){
+ int rc;
+ if( pStmt==0 ){
+ rc = SQLITE_OK;
+ }else{
+ rc = sqlite3VdbeFinalize((Vdbe*)pStmt);
+ }
+ 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().
+*/
+int sqlite3_reset(sqlite3_stmt *pStmt){
+ int rc;
+ if( pStmt==0 ){
+ rc = SQLITE_OK;
+ }else{
+ rc = sqlite3VdbeReset((Vdbe*)pStmt);
+ sqlite3VdbeMakeReady((Vdbe*)pStmt, -1, 0, 0, 0);
+ }
+ return rc;
+}
+
+/*
+** Register a new collation sequence with the database handle db.
+*/
+int sqlite3_create_collation(
+ sqlite3* db,
+ const char *zName,
+ int enc,
+ void* pCtx,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+){
+ CollSeq *pColl;
+ int rc = SQLITE_OK;
+
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+
+ /* 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( enc==SQLITE_UTF16 ){
+ enc = SQLITE_UTF16NATIVE;
+ }
+
+ if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF16LE && enc!=SQLITE_UTF16BE ){
+ sqlite3Error(db, SQLITE_ERROR,
+ "Param 3 to sqlite3_create_collation() must be one of "
+ "SQLITE_UTF8, SQLITE_UTF16, SQLITE_UTF16LE or SQLITE_UTF16BE"
+ );
+ 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)enc, 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);
+ }
+
+ pColl = sqlite3FindCollSeq(db, (u8)enc, zName, strlen(zName), 1);
+ if( 0==pColl ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pColl->xCmp = xCompare;
+ pColl->pUser = pCtx;
+ pColl->enc = enc;
+ }
+ sqlite3Error(db, rc, 0);
+ return rc;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Register a new collation sequence with the database handle db.
+*/
+int sqlite3_create_collation16(
+ sqlite3* db,
+ const char *zName,
+ int enc,
+ void* pCtx,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+){
+ char const *zName8;
+ sqlite3_value *pTmp;
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+ pTmp = sqlite3GetTransientValue(db);
+ sqlite3ValueSetStr(pTmp, -1, zName, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zName8 = sqlite3ValueText(pTmp, SQLITE_UTF8);
+ return sqlite3_create_collation(db, zName8, enc, pCtx, xCompare);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+/*
+** Register a collation sequence factory callback with the database handle
+** db. Replace any previously installed collation sequence factory.
+*/
+int sqlite3_collation_needed(
+ sqlite3 *db,
+ void *pCollNeededArg,
+ void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*)
+){
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+ db->xCollNeeded = xCollNeeded;
+ db->xCollNeeded16 = 0;
+ db->pCollNeededArg = pCollNeededArg;
+ 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.
+*/
+int sqlite3_collation_needed16(
+ sqlite3 *db,
+ void *pCollNeededArg,
+ void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*)
+){
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+ db->xCollNeeded = 0;
+ db->xCollNeeded16 = xCollNeeded16;
+ db->pCollNeededArg = pCollNeededArg;
+ return SQLITE_OK;
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+#ifndef SQLITE_OMIT_GLOBALRECOVER
+/*
+** This function is called to recover from a malloc failure that occured
+** within SQLite.
+**
+** This function is *not* threadsafe. Calling this from within a threaded
+** application when threads other than the caller have used SQLite is
+** dangerous and will almost certainly result in malfunctions.
+*/
+int sqlite3_global_recover(){
+ int rc = SQLITE_OK;
+
+ if( sqlite3_malloc_failed ){
+ sqlite3 *db;
+ int i;
+ sqlite3_malloc_failed = 0;
+ for(db=pDbList; db; db=db->pNext ){
+ sqlite3ExpirePreparedStatements(db);
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt && (rc=sqlite3BtreeReset(pBt)) ){
+ goto recover_out;
+ }
+ }
+ db->autoCommit = 1;
+ }
+ }
+
+recover_out:
+ if( rc!=SQLITE_OK ){
+ sqlite3_malloc_failed = 1;
+ }
+ return rc;
+}
+#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 ******
+*/
+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.
+*/
+int sqlite3Corrupt(void){
+ return SQLITE_CORRUPT;
+}
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/md5.c b/kexi/3rdparty/kexisql3/src/md5.c
new file mode 100644
index 000000000..32fcb6b68
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/md5.c
@@ -0,0 +1,387 @@
+/*
+** SQLite uses this code for testing only. It is not a part of
+** the SQLite library. This file implements two new TCL commands
+** "md5" and "md5file" that compute md5 checksums on arbitrary text
+** and on complete files. These commands are used by the "testfixture"
+** program to help verify the correct operation of the SQLite library.
+**
+** The original use of these TCL commands was to test the ROLLBACK
+** feature of SQLite. First compute the MD5-checksum of the database.
+** Then make some changes but rollback the changes rather than commit
+** them. Compute a second MD5-checksum of the file and verify that the
+** two checksums are the same. Such is the original use of this code.
+** New uses may have been added since this comment was written.
+*/
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include <tcl.h>
+#include <string.h>
+#include "sqlite3.h"
+
+/*
+ * If compiled on a machine that doesn't have a 32-bit integer,
+ * you just set "uint32" to the appropriate datatype for an
+ * unsigned 32-bit integer. For example:
+ *
+ * cc -Duint32='unsigned long' md5.c
+ *
+ */
+#ifndef uint32
+# define uint32 unsigned int
+#endif
+
+struct Context {
+ uint32 buf[4];
+ uint32 bits[2];
+ unsigned char in[64];
+};
+typedef char MD5Context[88];
+
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void byteReverse (unsigned char *buf, unsigned longs){
+ uint32 t;
+ do {
+ t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
+ ((unsigned)buf[1]<<8 | buf[0]);
+ *(uint32 *)buf = t;
+ buf += 4;
+ } while (--longs);
+}
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void MD5Transform(uint32 buf[4], const uint32 in[16]){
+ register uint32 a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+static void MD5Init(MD5Context *pCtx){
+ struct Context *ctx = (struct Context *)pCtx;
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+static
+void MD5Update(MD5Context *pCtx, const unsigned char *buf, unsigned int len){
+ struct Context *ctx = (struct Context *)pCtx;
+ uint32 t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32)len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if ( t ) {
+ unsigned char *p = (unsigned char *)ctx->in + t;
+
+ t = 64-t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ buf += t;
+ len -= t;
+ }
+
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+static void MD5Final(unsigned char digest[16], MD5Context *pCtx){
+ struct Context *ctx = (struct Context *)pCtx;
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count-8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0];
+ ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ byteReverse((unsigned char *)ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
+}
+
+/*
+** Convert a digest into base-16. digest should be declared as
+** "unsigned char digest[16]" in the calling function. The MD5
+** digest is stored in the first 16 bytes. zBuf should
+** be "char zBuf[33]".
+*/
+static void DigestToBase16(unsigned char *digest, char *zBuf){
+ static char const zEncode[] = "0123456789abcdef";
+ int i, j;
+
+ for(j=i=0; i<16; i++){
+ int a = digest[i];
+ zBuf[j++] = zEncode[(a>>4)&0xf];
+ zBuf[j++] = zEncode[a & 0xf];
+ }
+ zBuf[j] = 0;
+}
+
+/*
+** A TCL command for md5. The argument is the text to be hashed. The
+** Result is the hash in base64.
+*/
+static int md5_cmd(void*cd, Tcl_Interp *interp, int argc, const char **argv){
+ MD5Context ctx;
+ unsigned char digest[16];
+
+ if( argc!=2 ){
+ Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
+ " TEXT\"", 0);
+ return TCL_ERROR;
+ }
+ MD5Init(&ctx);
+ MD5Update(&ctx, (unsigned char*)argv[1], (unsigned)strlen(argv[1]));
+ MD5Final(digest, &ctx);
+ DigestToBase16(digest, interp->result);
+ return TCL_OK;
+}
+
+/*
+** A TCL command to take the md5 hash of a file. The argument is the
+** name of the file.
+*/
+static int md5file_cmd(void*cd, Tcl_Interp*interp, int argc, const char **argv){
+ FILE *in;
+ MD5Context ctx;
+ unsigned char digest[16];
+ char zBuf[10240];
+
+ if( argc!=2 ){
+ Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
+ " FILENAME\"", 0);
+ return TCL_ERROR;
+ }
+ in = fopen(argv[1],"rb");
+ if( in==0 ){
+ Tcl_AppendResult(interp,"unable to open file \"", argv[1],
+ "\" for reading", 0);
+ return TCL_ERROR;
+ }
+ MD5Init(&ctx);
+ for(;;){
+ int n;
+ n = fread(zBuf, 1, sizeof(zBuf), in);
+ if( n<=0 ) break;
+ MD5Update(&ctx, (unsigned char*)zBuf, (unsigned)n);
+ }
+ fclose(in);
+ MD5Final(digest, &ctx);
+ DigestToBase16(digest, interp->result);
+ return TCL_OK;
+}
+
+/*
+** Register the two TCL commands above with the TCL interpreter.
+*/
+int Md5_Init(Tcl_Interp *interp){
+ Tcl_CreateCommand(interp, "md5", (Tcl_CmdProc*)md5_cmd, 0, 0);
+ Tcl_CreateCommand(interp, "md5file", (Tcl_CmdProc*)md5file_cmd, 0, 0);
+ return TCL_OK;
+}
+
+/*
+** During testing, the special md5sum() aggregate function is available.
+** inside SQLite. The following routines implement that function.
+*/
+static void md5step(sqlite3_context *context, int argc, sqlite3_value **argv){
+ MD5Context *p;
+ int i;
+ if( argc<1 ) return;
+ p = sqlite3_aggregate_context(context, sizeof(*p));
+ if( p==0 ) return;
+ if( sqlite3_aggregate_count(context)==1 ){
+ MD5Init(p);
+ }
+ for(i=0; i<argc; i++){
+ const char *zData = sqlite3_value_text(argv[i]);
+ if( zData ){
+ MD5Update(p, zData, strlen(zData));
+ }
+ }
+}
+static void md5finalize(sqlite3_context *context){
+ MD5Context *p;
+ unsigned char digest[16];
+ char zBuf[33];
+ p = sqlite3_aggregate_context(context, sizeof(*p));
+ MD5Final(digest,p);
+ DigestToBase16(digest, zBuf);
+ sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+}
+void Md5_Register(sqlite3 *db){
+ sqlite3_create_function(db, "md5sum", -1, SQLITE_UTF8, 0, 0,
+ md5step, md5finalize);
+}
diff --git a/kexi/3rdparty/kexisql3/src/opcodes.c b/kexi/3rdparty/kexisql3/src/opcodes.c
new file mode 100644
index 000000000..189cdfe04
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/opcodes.c
@@ -0,0 +1,140 @@
+/* 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)
+const char *const sqlite3OpcodeNames[] = { "?",
+ /* 1 */ "MemLoad",
+ /* 2 */ "Column",
+ /* 3 */ "SetCookie",
+ /* 4 */ "IfMemPos",
+ /* 5 */ "Sequence",
+ /* 6 */ "MoveGt",
+ /* 7 */ "RowKey",
+ /* 8 */ "OpenWrite",
+ /* 9 */ "If",
+ /* 10 */ "ToInt",
+ /* 11 */ "Pop",
+ /* 12 */ "CollSeq",
+ /* 13 */ "OpenRead",
+ /* 14 */ "Expire",
+ /* 15 */ "AutoCommit",
+ /* 16 */ "IntegrityCk",
+ /* 17 */ "Sort",
+ /* 18 */ "Function",
+ /* 19 */ "Noop",
+ /* 20 */ "Return",
+ /* 21 */ "NewRowid",
+ /* 22 */ "Variable",
+ /* 23 */ "String",
+ /* 24 */ "ParseSchema",
+ /* 25 */ "Close",
+ /* 26 */ "CreateIndex",
+ /* 27 */ "IsUnique",
+ /* 28 */ "IdxIsNull",
+ /* 29 */ "NotFound",
+ /* 30 */ "Int64",
+ /* 31 */ "MustBeInt",
+ /* 32 */ "Halt",
+ /* 33 */ "Rowid",
+ /* 34 */ "IdxLT",
+ /* 35 */ "AddImm",
+ /* 36 */ "Statement",
+ /* 37 */ "RowData",
+ /* 38 */ "MemMax",
+ /* 39 */ "Push",
+ /* 40 */ "NotExists",
+ /* 41 */ "MemIncr",
+ /* 42 */ "Gosub",
+ /* 43 */ "Integer",
+ /* 44 */ "ToNumeric",
+ /* 45 */ "MemInt",
+ /* 46 */ "Prev",
+ /* 47 */ "CreateTable",
+ /* 48 */ "Last",
+ /* 49 */ "IdxRowid",
+ /* 50 */ "MakeIdxRec",
+ /* 51 */ "ResetCount",
+ /* 52 */ "FifoWrite",
+ /* 53 */ "Callback",
+ /* 54 */ "ContextPush",
+ /* 55 */ "DropTrigger",
+ /* 56 */ "DropIndex",
+ /* 57 */ "IdxGE",
+ /* 58 */ "IdxDelete",
+ /* 59 */ "Vacuum",
+ /* 60 */ "MoveLe",
+ /* 61 */ "IfNot",
+ /* 62 */ "DropTable",
+ /* 63 */ "MakeRecord",
+ /* 64 */ "ToBlob",
+ /* 65 */ "Delete",
+ /* 66 */ "AggFinal",
+ /* 67 */ "Or",
+ /* 68 */ "And",
+ /* 69 */ "Not",
+ /* 70 */ "Dup",
+ /* 71 */ "Goto",
+ /* 72 */ "FifoRead",
+ /* 73 */ "IsNull",
+ /* 74 */ "NotNull",
+ /* 75 */ "Ne",
+ /* 76 */ "Eq",
+ /* 77 */ "Gt",
+ /* 78 */ "Le",
+ /* 79 */ "Lt",
+ /* 80 */ "Ge",
+ /* 81 */ "Clear",
+ /* 82 */ "BitAnd",
+ /* 83 */ "BitOr",
+ /* 84 */ "ShiftLeft",
+ /* 85 */ "ShiftRight",
+ /* 86 */ "Add",
+ /* 87 */ "Subtract",
+ /* 88 */ "Multiply",
+ /* 89 */ "Divide",
+ /* 90 */ "Remainder",
+ /* 91 */ "Concat",
+ /* 92 */ "Negative",
+ /* 93 */ "IdxGT",
+ /* 94 */ "BitNot",
+ /* 95 */ "String8",
+ /* 96 */ "MoveLt",
+ /* 97 */ "VerifyCookie",
+ /* 98 */ "AggStep",
+ /* 99 */ "Pull",
+ /* 100 */ "ToText",
+ /* 101 */ "SetNumColumns",
+ /* 102 */ "AbsValue",
+ /* 103 */ "Transaction",
+ /* 104 */ "ContextPop",
+ /* 105 */ "Next",
+ /* 106 */ "IdxInsert",
+ /* 107 */ "Distinct",
+ /* 108 */ "Insert",
+ /* 109 */ "Destroy",
+ /* 110 */ "ReadCookie",
+ /* 111 */ "ForceInt",
+ /* 112 */ "LoadAnalysis",
+ /* 113 */ "OpenVirtual",
+ /* 114 */ "Explain",
+ /* 115 */ "OpenPseudo",
+ /* 116 */ "Null",
+ /* 117 */ "Blob",
+ /* 118 */ "MemStore",
+ /* 119 */ "Rewind",
+ /* 120 */ "MoveGe",
+ /* 121 */ "MemMove",
+ /* 122 */ "MemNull",
+ /* 123 */ "Found",
+ /* 124 */ "NullRow",
+ /* 125 */ "NotUsed_125",
+ /* 126 */ "NotUsed_126",
+ /* 127 */ "NotUsed_127",
+ /* 128 */ "NotUsed_128",
+ /* 129 */ "NotUsed_129",
+ /* 130 */ "NotUsed_130",
+ /* 131 */ "NotUsed_131",
+ /* 132 */ "NotUsed_132",
+ /* 133 */ "Real",
+ /* 134 */ "HexBlob",
+};
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/opcodes.h b/kexi/3rdparty/kexisql3/src/opcodes.h
new file mode 100644
index 000000000..4db3ec163
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/opcodes.h
@@ -0,0 +1,149 @@
+/* Automatically generated. Do not edit */
+/* See the mkopcodeh.awk script for details */
+#define OP_MemLoad 1
+#define OP_HexBlob 134 /* same as TK_BLOB */
+#define OP_Column 2
+#define OP_SetCookie 3
+#define OP_IfMemPos 4
+#define OP_Real 133 /* same as TK_FLOAT */
+#define OP_Sequence 5
+#define OP_MoveGt 6
+#define OP_Ge 80 /* same as TK_GE */
+#define OP_RowKey 7
+#define OP_Eq 76 /* same as TK_EQ */
+#define OP_OpenWrite 8
+#define OP_NotNull 74 /* same as TK_NOTNULL */
+#define OP_If 9
+#define OP_ToInt 10
+#define OP_String8 95 /* same as TK_STRING */
+#define OP_Pop 11
+#define OP_CollSeq 12
+#define OP_OpenRead 13
+#define OP_Expire 14
+#define OP_AutoCommit 15
+#define OP_Gt 77 /* same as TK_GT */
+#define OP_IntegrityCk 16
+#define OP_Sort 17
+#define OP_Function 18
+#define OP_And 68 /* same as TK_AND */
+#define OP_Subtract 87 /* same as TK_MINUS */
+#define OP_Noop 19
+#define OP_Return 20
+#define OP_Remainder 90 /* same as TK_REM */
+#define OP_NewRowid 21
+#define OP_Multiply 88 /* same as TK_STAR */
+#define OP_Variable 22
+#define OP_String 23
+#define OP_ParseSchema 24
+#define OP_Close 25
+#define OP_CreateIndex 26
+#define OP_IsUnique 27
+#define OP_IdxIsNull 28
+#define OP_NotFound 29
+#define OP_Int64 30
+#define OP_MustBeInt 31
+#define OP_Halt 32
+#define OP_Rowid 33
+#define OP_IdxLT 34
+#define OP_AddImm 35
+#define OP_Statement 36
+#define OP_RowData 37
+#define OP_MemMax 38
+#define OP_Push 39
+#define OP_Or 67 /* same as TK_OR */
+#define OP_NotExists 40
+#define OP_MemIncr 41
+#define OP_Gosub 42
+#define OP_Divide 89 /* same as TK_SLASH */
+#define OP_Integer 43
+#define OP_ToNumeric 44
+#define OP_MemInt 45
+#define OP_Prev 46
+#define OP_Concat 91 /* same as TK_CONCAT */
+#define OP_BitAnd 82 /* same as TK_BITAND */
+#define OP_CreateTable 47
+#define OP_Last 48
+#define OP_IsNull 73 /* same as TK_ISNULL */
+#define OP_IdxRowid 49
+#define OP_MakeIdxRec 50
+#define OP_ShiftRight 85 /* same as TK_RSHIFT */
+#define OP_ResetCount 51
+#define OP_FifoWrite 52
+#define OP_Callback 53
+#define OP_ContextPush 54
+#define OP_DropTrigger 55
+#define OP_DropIndex 56
+#define OP_IdxGE 57
+#define OP_IdxDelete 58
+#define OP_Vacuum 59
+#define OP_MoveLe 60
+#define OP_IfNot 61
+#define OP_DropTable 62
+#define OP_MakeRecord 63
+#define OP_ToBlob 64
+#define OP_Delete 65
+#define OP_AggFinal 66
+#define OP_ShiftLeft 84 /* same as TK_LSHIFT */
+#define OP_Dup 70
+#define OP_Goto 71
+#define OP_FifoRead 72
+#define OP_Clear 81
+#define OP_IdxGT 93
+#define OP_MoveLt 96
+#define OP_Le 78 /* same as TK_LE */
+#define OP_VerifyCookie 97
+#define OP_AggStep 98
+#define OP_Pull 99
+#define OP_ToText 100
+#define OP_Not 69 /* same as TK_NOT */
+#define OP_SetNumColumns 101
+#define OP_AbsValue 102
+#define OP_Transaction 103
+#define OP_Negative 92 /* same as TK_UMINUS */
+#define OP_Ne 75 /* same as TK_NE */
+#define OP_ContextPop 104
+#define OP_BitOr 83 /* same as TK_BITOR */
+#define OP_Next 105
+#define OP_IdxInsert 106
+#define OP_Distinct 107
+#define OP_Lt 79 /* same as TK_LT */
+#define OP_Insert 108
+#define OP_Destroy 109
+#define OP_ReadCookie 110
+#define OP_ForceInt 111
+#define OP_LoadAnalysis 112
+#define OP_OpenVirtual 113
+#define OP_Explain 114
+#define OP_OpenPseudo 115
+#define OP_Null 116
+#define OP_Blob 117
+#define OP_Add 86 /* same as TK_PLUS */
+#define OP_MemStore 118
+#define OP_Rewind 119
+#define OP_MoveGe 120
+#define OP_BitNot 94 /* same as TK_BITNOT */
+#define OP_MemMove 121
+#define OP_MemNull 122
+#define OP_Found 123
+#define OP_NullRow 124
+
+/* The following opcode values are never used */
+#define OP_NotUsed_125 125
+#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 NOPUSH_MASK_0 65368
+#define NOPUSH_MASK_1 47898
+#define NOPUSH_MASK_2 22493
+#define NOPUSH_MASK_3 32761
+#define NOPUSH_MASK_4 65215
+#define NOPUSH_MASK_5 30719
+#define NOPUSH_MASK_6 40895
+#define NOPUSH_MASK_7 6603
+#define NOPUSH_MASK_8 0
+#define NOPUSH_MASK_9 0
diff --git a/kexi/3rdparty/kexisql3/src/os.h b/kexi/3rdparty/kexisql3/src/os.h
new file mode 100644
index 000000000..ebbd75419
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/os.h
@@ -0,0 +1,208 @@
+/*
+** 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_
+
+/*
+** 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)
+*/
+#if !defined(OS_UNIX) && !defined(OS_TEST) && !defined(OS_OTHER)
+# define OS_OTHER 0
+# ifndef OS_WIN
+# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__BORLANDC__)
+# define OS_WIN 1
+# define OS_UNIX 0
+# else
+# define OS_WIN 0
+# define OS_UNIX 1
+# endif
+# else
+# define OS_UNIX 0
+# endif
+#else
+# ifndef OS_WIN
+# define OS_WIN 0
+# endif
+#endif
+
+/*
+** Invoke the appropriate operating-system specific header file.
+*/
+#if OS_TEST
+# include "os_test.h"
+#endif
+#if OS_UNIX
+# include "os_unix.h"
+#endif
+#if OS_WIN
+# include "os_win.h"
+#endif
+
+/* os_other.c and os_other.h are not delivered with SQLite. These files
+** are place-holders that can be filled in by third-party developers to
+** implement backends to their on proprietary operating systems.
+*/
+#if OS_OTHER
+# include "os_other.h"
+#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
+
+/*
+** 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
+
+/*
+** 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 os.h so that Unix can use the same
+** byte ranges for locking. 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
+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
+
+
+int sqlite3OsDelete(const char*);
+int sqlite3OsFileExists(const char*);
+/* js: extended */
+int sqlite3OsOpenReadWrite(const char*, OsFile*, int*, int, int);
+int sqlite3OsOpenExclusive(const char*, OsFile*, int);
+int sqlite3OsOpenReadOnly(const char*, OsFile*);
+int sqlite3OsOpenDirectory(const char*, OsFile*);
+int sqlite3OsSyncDirectory(const char*);
+int sqlite3OsTempFileName(char*);
+int sqlite3OsIsDirWritable(char*);
+int sqlite3OsClose(OsFile*);
+int sqlite3OsRead(OsFile*, void*, int amt);
+int sqlite3OsWrite(OsFile*, const void*, int amt);
+int sqlite3OsSeek(OsFile*, i64 offset);
+int sqlite3OsSync(OsFile*, int);
+int sqlite3OsTruncate(OsFile*, i64 size);
+int sqlite3OsFileSize(OsFile*, i64 *pSize);
+char *sqlite3OsFullPathname(const char*);
+int sqlite3OsLock(OsFile*, int);
+int sqlite3OsUnlock(OsFile*, int);
+int sqlite3OsCheckReservedLock(OsFile *id);
+
+
+/* The interface for file I/O is above. Other miscellaneous functions
+** are below */
+
+int sqlite3OsRandomSeed(char*);
+int sqlite3OsSleep(int ms);
+int sqlite3OsCurrentTime(double*);
+void sqlite3OsEnterMutex(void);
+void sqlite3OsLeaveMutex(void);
+
+#endif /* _SQLITE_OS_H_ */
diff --git a/kexi/3rdparty/kexisql3/src/os_common.h b/kexi/3rdparty/kexisql3/src/os_common.h
new file mode 100644
index 000000000..b19ff0590
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/os_common.h
@@ -0,0 +1,123 @@
+/*
+** 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
+unsigned int sqlite3_pending_byte = 0x40000000;
+#endif
+
+int sqlite3_os_trace = 0;
+#ifdef SQLITE_DEBUG
+static int last_page = 0;
+#define SEEK(X) last_page=(X)
+#define TRACE1(X) if( sqlite3_os_trace ) sqlite3DebugPrintf(X)
+#define TRACE2(X,Y) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y)
+#define TRACE3(X,Y,Z) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z)
+#define TRACE4(X,Y,Z,A) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z,A)
+#define TRACE5(X,Y,Z,A,B) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z,A,B)
+#define TRACE6(X,Y,Z,A,B,C) if(sqlite3_os_trace) sqlite3DebugPrintf(X,Y,Z,A,B,C)
+#define TRACE7(X,Y,Z,A,B,C,D) \
+ if(sqlite3_os_trace) sqlite3DebugPrintf(X,Y,Z,A,B,C,D)
+#else
+#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)
+#define TRACE6(X,Y,Z,A,B,C)
+#define TRACE7(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
+int sqlite3_io_error_pending = 0;
+int sqlite3_diskfull_pending = 0;
+int sqlite3_diskfull = 0;
+#define SimulateIOError(A) \
+ if( sqlite3_io_error_pending ) \
+ if( sqlite3_io_error_pending-- == 1 ){ local_ioerr(); return A; }
+static void local_ioerr(){
+ sqlite3_io_error_pending = 0; /* Really just a place to set a breakpoint */
+}
+#define SimulateDiskfullError \
+ if( sqlite3_diskfull_pending ){ \
+ if( sqlite3_diskfull_pending == 1 ){ \
+ local_ioerr(); \
+ sqlite3_diskfull = 1; \
+ return SQLITE_FULL; \
+ }else{ \
+ sqlite3_diskfull_pending--; \
+ } \
+ }
+#else
+#define SimulateIOError(A)
+#define SimulateDiskfullError
+#endif
+
+/*
+** When testing, keep a count of the number of open files.
+*/
+#ifdef SQLITE_TEST
+int sqlite3_open_file_count = 0;
+#define OpenCounter(X) sqlite3_open_file_count+=(X)
+#else
+#define OpenCounter(X)
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/os_mac.c b/kexi/3rdparty/kexisql3/src/os_mac.c
new file mode 100644
index 000000000..15798b5d3
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/os_mac.c
@@ -0,0 +1,740 @@
+/*
+** 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 classic mac. Mac OS X
+** uses the os_unix.c file, not this one.
+*/
+#include "os.h" /* Must be first to enable large file support */
+#if OS_MAC /* This file used on classic mac only */
+#include "sqliteInt.h"
+
+#include <extras.h>
+#include <path2fss.h>
+#include <TextUtils.h>
+#include <FinderRegistry.h>
+#include <Folders.h>
+#include <Timer.h>
+#include <OSUtils.h>
+
+/*
+** Macros used to determine whether or not to use threads.
+*/
+#if defined(THREADSAFE) && THREADSAFE
+# include <Multiprocessing.h>
+# define SQLITE_MACOS_MULTITASKING 1
+#endif
+
+/*
+** Include code that is common to all os_*.c files
+*/
+#include "os_common.h"
+
+/*
+** Delete the named file
+*/
+int sqlite3OsDelete(const char *zFilename){
+ unlink(zFilename);
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the named file exists.
+*/
+int sqlite3OsFileExists(const char *zFilename){
+ return access(zFilename, 0)==0;
+}
+
+/*
+** 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 sqlite3OsOpenReadWrite(
+ const char *zFilename,
+ OsFile *id,
+ int *pReadonly,
+ int exclusiveFlag,
+ int allowReadonly
+){
+ 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( !sqlite3OsFileExists(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;
+}
+
+
+/*
+** 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 sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){
+ 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 = sqlite3OsFullPathname(zFilename);
+ OpenCounter(+1);
+ return SQLITE_OK;
+}
+
+/*
+** 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 sqlite3OsOpenReadOnly(const char *zFilename, OsFile *id){
+ 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;
+}
+
+/*
+** 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 sqlite3OsOpenDirectory(
+ const char *zDirname,
+ OsFile *id
+){
+ return SQLITE_OK;
+}
+
+/*
+** Create a temporary file name in zBuf. zBuf must be big enough to
+** hold at least SQLITE_TEMPNAME_SIZE characters.
+*/
+int sqlite3OsTempFileName(char *zBuf){
+ static char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ int i, j;
+ char zTempPath[SQLITE_TEMPNAME_SIZE];
+ char zdirName[32];
+ CInfoPBRec infoRec;
+ Str31 dirName;
+ memset(&infoRec, 0, sizeof(infoRec));
+ memset(zTempPath, 0, SQLITE_TEMPNAME_SIZE);
+ 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 );
+ }
+ if( *zTempPath == 0 )
+ getcwd(zTempPath, SQLITE_TEMPNAME_SIZE-24);
+ for(;;){
+ sprintf(zBuf, "%s"TEMP_FILE_PREFIX, zTempPath);
+ j = strlen(zBuf);
+ sqlite3Randomness(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( !sqlite3OsFileExists(zBuf) ) break;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Close a file.
+*/
+int sqlite3OsClose(OsFile *id){
+ 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;
+}
+
+/*
+** 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 sqlite3OsRead(OsFile *id, void *pBuf, int amt){
+ 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;
+ }
+}
+
+/*
+** Write data from a buffer into a file. Return SQLITE_OK on success
+** or some other error code on failure.
+*/
+int sqlite3OsWrite(OsFile *id, const void *pBuf, int amt){
+ 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;
+}
+
+/*
+** Move the read/write pointer in a file.
+*/
+int sqlite3OsSeek(OsFile *id, off_t offset){
+ off_t curSize;
+ SEEK(offset/1024 + 1);
+ if( sqlite3OsFileSize(id, &curSize) != SQLITE_OK ){
+ return SQLITE_IOERR;
+ }
+ if( offset >= curSize ){
+ if( sqlite3OsTruncate(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;
+ }
+}
+
+/*
+** 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 sqlite3OsSync(OsFile *id){
+# 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;
+ }
+}
+
+/*
+** Sync the directory zDirname. This is a no-op on operating systems other
+** than UNIX.
+*/
+int sqlite3OsSyncDirectory(const char *zDirname){
+ SimulateIOError(SQLITE_IOERR);
+ return SQLITE_OK;
+}
+
+/*
+** Truncate an open file to a specified size
+*/
+int sqlite3OsTruncate(OsFile *id, off_t nByte){
+ SimulateIOError(SQLITE_IOERR);
+# 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;
+ }
+}
+
+/*
+** Determine the current size of a file in bytes
+*/
+int sqlite3OsFileSize(OsFile *id, off_t *pSize){
+# ifdef _LARGE_FILE
+ if( FSGetForkSize(id->refNum, pSize) != noErr){
+# else
+ if( GetEOF(id->refNum, pSize) != noErr ){
+# endif
+ return SQLITE_IOERR;
+ }else{
+ return SQLITE_OK;
+ }
+}
+
+/*
+** 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
+** sqlite3OsReadLock() 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 sqlite3OsWriteLock().
+**
+** 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
+#define FIRST_LOCKBYTE (0x000fffff - N_LOCKBYTE)
+
+/*
+** 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 sqlite3OsReadLock(OsFile *id){
+ int rc;
+ if( id->locked>0 || id->refNumRF == -1 ){
+ rc = SQLITE_OK;
+ }else{
+ int lk;
+ OSErr res;
+ int cnt = 5;
+ ParamBlockRec params;
+ sqlite3Randomness(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;
+}
+
+/*
+** 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 sqlite3OsWriteLock(OsFile *id){
+ 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;
+}
+
+/*
+** 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 sqlite3OsUnlock(OsFile *id){
+ 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;
+}
+
+/*
+** 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 sqlite3OsRandomSeed(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 !defined(SQLITE_TEST)
+ {
+ 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 sqlite3OsSleep(int ms){
+ UInt32 finalTicks;
+ UInt32 ticks = (((UInt32)ms+16)*3)/50; /* 1/60 sec per tick */
+ Delay(ticks, &finalTicks);
+ return (int)((ticks*50)/3);
+}
+
+/*
+** Static variables used for thread synchronization
+*/
+static int inMutex = 0;
+#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 sqlite3OsEnterMutex(){
+#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 sqlite3OsLeaveMutex(){
+ assert( inMutex );
+ inMutex = 0;
+#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 *sqlite3OsFullPathname(const char *zRelative){
+ char *zFull = 0;
+ if( zRelative[0]==':' ){
+ char zBuf[_MAX_PATH+1];
+ sqlite3SetString(&zFull, getcwd(zBuf, sizeof(zBuf)), &(zRelative[1]),
+ (char*)0);
+ }else{
+ if( strchr(zRelative, ':') ){
+ sqlite3SetString(&zFull, zRelative, (char*)0);
+ }else{
+ char zBuf[_MAX_PATH+1];
+ sqlite3SetString(&zFull, getcwd(zBuf, sizeof(zBuf)), zRelative, (char*)0);
+ }
+ }
+ return zFull;
+}
+
+/*
+** The following variable, if set to a non-zero value, becomes the result
+** returned from sqlite3OsCurrentTime(). This is used for testing.
+*/
+#ifdef SQLITE_TEST
+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 sqlite3OsCurrentTime(double *prNow){
+ *prNow = 0.0; /**** FIX ME *****/
+#ifdef SQLITE_TEST
+ if( sqlite3_current_time ){
+ *prNow = sqlite3_current_time/86400.0 + 2440587.5;
+ }
+#endif
+ return 0;
+}
+
+#endif /* OS_MAC */
diff --git a/kexi/3rdparty/kexisql3/src/os_mac.h b/kexi/3rdparty/kexisql3/src/os_mac.h
new file mode 100644
index 000000000..a5f51384d
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/os_mac.h
@@ -0,0 +1,46 @@
+/*
+** 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 header file defines OS-specific features of classic Mac.
+** OS X uses the os_unix.h file, not this one.
+*/
+#ifndef _SQLITE_OS_MAC_H_
+#define _SQLITE_OS_MAC_H_
+
+
+#include <unistd.h>
+#include <Files.h>
+#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
+
+/*
+** The OsFile structure is a operating-system independing representation
+** of an open file handle. It is defined differently for each architecture.
+**
+** This is the definition for class Mac.
+*/
+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 */
+};
+
+
+#endif /* _SQLITE_OS_MAC_H_ */
diff --git a/kexi/3rdparty/kexisql3/src/os_unix.c b/kexi/3rdparty/kexisql3/src/os_unix.c
new file mode 100644
index 000000000..cf846b933
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/os_unix.c
@@ -0,0 +1,1462 @@
+/*
+** 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.
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#if OS_UNIX /* This file is used on unix only */
+
+
+#include <time.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+
+/*
+** Do not include any of the File I/O interface procedures if the
+** SQLITE_OMIT_DISKIO macro is defined (indicating that there database
+** will be in-memory only)
+*/
+#ifndef SQLITE_OMIT_DISKIO
+
+
+/*
+** 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 its DOS - what did you expect?
+*/
+#ifdef __DJGPP__
+# define fcntl(A,B,C) 0
+#endif
+
+/*
+** Include code that is common to all os_*.c files
+*/
+#include "os_common.h"
+
+/*
+** The threadid macro resolves to the thread-id or to 0. Used for
+** testing and debugging only.
+*/
+#ifdef SQLITE_UNIX_THREADS
+#define threadid pthread_self()
+#else
+#define threadid 0
+#endif
+
+/*
+** Set or check the OsFile.tid field. This field is set when an OsFile
+** is first opened. All subsequent uses of the OsFile verify that the
+** same thread is operating on the OsFile. 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.
+*/
+#ifdef SQLITE_UNIX_THREADS
+# define SET_THREADID(X) X->tid = pthread_self()
+# define CHECK_THREADID(X) (!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 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.
+**
+** 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 also
+** set to zero if we compile without threading support.
+*/
+struct lockKey {
+ dev_t dev; /* Device number */
+ ino_t ino; /* Inode number */
+#ifdef SQLITE_UNIX_THREADS
+ pthread_t tid; /* Thread ID or zero if threads cannot 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 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; /* 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 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 };
+
+
+#ifdef SQLITE_UNIX_THREADS
+/*
+** 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.
+*/
+static int threadsOverrideEachOthersLocks = -1;
+
+/*
+** 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
+** turnned 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 && 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(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_UNIX_THREADS */
+
+/*
+** Release a lockInfo structure previously allocated by findLockInfo().
+*/
+static void releaseLockInfo(struct lockInfo *pLock){
+ pLock->nRef--;
+ if( pLock->nRef==0 ){
+ sqlite3HashInsert(&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 ){
+ sqlite3HashInsert(&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.
+*/
+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 ) return 1;
+ memset(&key1, 0, sizeof(key1));
+ key1.dev = statbuf.st_dev;
+ key1.ino = statbuf.st_ino;
+#ifdef SQLITE_UNIX_THREADS
+ 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 = sqliteMallocRaw( sizeof(*pLock) );
+ if( pLock==0 ) return 1;
+ 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 );
+ sqliteFree(pLock);
+ return 1;
+ }
+ }else{
+ pLock->nRef++;
+ }
+ *ppLock = pLock;
+ pOpen = (struct openCnt*)sqlite3HashFind(&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 = sqlite3HashInsert(&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;
+}
+
+/*
+** Delete the named file
+*/
+int sqlite3OsDelete(const char *zFilename){
+ unlink(zFilename);
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the named file exists.
+*/
+int sqlite3OsFileExists(const char *zFilename){
+ return access(zFilename, 0)==0;
+}
+
+/*
+** 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 sqlite3OsOpenReadWrite(
+ const char *zFilename,
+ OsFile *id,
+ int *pReadonly,
+ int exclusiveFlag,
+ int allowReadonly
+){
+ int rc;
+ assert( !id->isOpen );
+ id->dirfd = -1;
+ SET_THREADID(id);
+ id->h = open(zFilename, O_RDWR|O_CREAT|O_LARGEFILE|O_BINARY,
+ SQLITE_DEFAULT_FILE_PERMISSIONS);
+ if( id->h<0 ){
+#ifdef EISDIR
+ if( errno==EISDIR ){
+ return SQLITE_CANTOPEN;
+ }
+#endif
+ id->h = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY);
+ if( id->h<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ *pReadonly = 1;
+ }else{
+ *pReadonly = 0;
+ }
+ sqlite3OsEnterMutex();
+ rc = findLockInfo(id->h, &id->pLock, &id->pOpen);
+ sqlite3OsLeaveMutex();
+ if( rc ){
+ close(id->h);
+ return SQLITE_NOMEM;
+ }
+ id->locktype = 0;
+ id->isOpen = 1;
+ TRACE3("OPEN %-3d %s\n", id->h, zFilename);
+ OpenCounter(+1);
+ return SQLITE_OK;
+}
+
+
+/*
+** 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 sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){
+ int rc;
+ assert( !id->isOpen );
+ if( access(zFilename, 0)==0 ){
+ return SQLITE_CANTOPEN;
+ }
+ SET_THREADID(id);
+ id->dirfd = -1;
+ id->h = open(zFilename,
+ O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW|O_LARGEFILE|O_BINARY,
+ SQLITE_DEFAULT_FILE_PERMISSIONS);
+ if( id->h<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ sqlite3OsEnterMutex();
+ rc = findLockInfo(id->h, &id->pLock, &id->pOpen);
+ sqlite3OsLeaveMutex();
+ if( rc ){
+ close(id->h);
+ unlink(zFilename);
+ return SQLITE_NOMEM;
+ }
+ id->locktype = 0;
+ id->isOpen = 1;
+ if( delFlag ){
+ unlink(zFilename);
+ }
+ TRACE3("OPEN-EX %-3d %s\n", id->h, zFilename);
+ OpenCounter(+1);
+ return SQLITE_OK;
+}
+
+/*
+** 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 sqlite3OsOpenReadOnly(const char *zFilename, OsFile *id){
+ int rc;
+ assert( !id->isOpen );
+ SET_THREADID(id);
+ id->dirfd = -1;
+ id->h = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY);
+ if( id->h<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ sqlite3OsEnterMutex();
+ rc = findLockInfo(id->h, &id->pLock, &id->pOpen);
+ sqlite3OsLeaveMutex();
+ if( rc ){
+ close(id->h);
+ return SQLITE_NOMEM;
+ }
+ id->locktype = 0;
+ id->isOpen = 1;
+ TRACE3("OPEN-RO %-3d %s\n", id->h, zFilename);
+ OpenCounter(+1);
+ return SQLITE_OK;
+}
+
+/*
+** 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 sqlite3OsOpenDirectory(
+ const char *zDirname,
+ OsFile *id
+){
+ if( !id->isOpen ){
+ /* Do not open the directory if the corresponding file is not already
+ ** open. */
+ return SQLITE_CANTOPEN;
+ }
+ SET_THREADID(id);
+ assert( id->dirfd<0 );
+ id->dirfd = open(zDirname, O_RDONLY|O_BINARY, 0);
+ if( id->dirfd<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ TRACE3("OPENDIR %-3d %s\n", id->dirfd, zDirname);
+ 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.
+*/
+char *sqlite3_temp_directory = 0;
+
+/*
+** Create a temporary file name in zBuf. zBuf must be big enough to
+** hold at least SQLITE_TEMPNAME_SIZE characters.
+*/
+int sqlite3OsTempFileName(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 = ".";
+ 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;
+ }
+ do{
+ sprintf(zBuf, "%s/"TEMP_FILE_PREFIX, zDir);
+ j = strlen(zBuf);
+ sqlite3Randomness(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;
+}
+
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+/*
+** Check that a given pathname is a directory and is writable
+**
+*/
+int sqlite3OsIsDirWritable(char *zBuf){
+ struct stat buf;
+ if( zBuf==0 ) return 0;
+ if( zBuf[0]==0 ) return 0;
+ if( stat(zBuf, &buf) ) return 0;
+ if( !S_ISDIR(buf.st_mode) ) return 0;
+ if( access(zBuf, 07) ) return 0;
+ return 1;
+}
+#endif /* SQLITE_OMIT_PAGER_PRAGMAS */
+
+/*
+** 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 sqlite3OsRead(OsFile *id, void *pBuf, int amt){
+ int got;
+ assert( id->isOpen );
+ SimulateIOError(SQLITE_IOERR);
+ TIMER_START;
+ got = read(id->h, pBuf, amt);
+ TIMER_END;
+ TRACE5("READ %-3d %5d %7d %d\n", id->h, got, last_page, TIMER_ELAPSED);
+ SEEK(0);
+ /* if( got<0 ) got = 0; */
+ if( got==amt ){
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+}
+
+/*
+** Write data from a buffer into a file. Return SQLITE_OK on success
+** or some other error code on failure.
+*/
+int sqlite3OsWrite(OsFile *id, const void *pBuf, int amt){
+ int wrote = 0;
+ assert( id->isOpen );
+ assert( amt>0 );
+ SimulateIOError(SQLITE_IOERR);
+ SimulateDiskfullError;
+ TIMER_START;
+ while( amt>0 && (wrote = write(id->h, pBuf, amt))>0 ){
+ amt -= wrote;
+ pBuf = &((char*)pBuf)[wrote];
+ }
+ TIMER_END;
+ TRACE5("WRITE %-3d %5d %7d %d\n", id->h, wrote, last_page, TIMER_ELAPSED);
+ SEEK(0);
+ if( amt>0 ){
+ return SQLITE_FULL;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Move the read/write pointer in a file.
+*/
+int sqlite3OsSeek(OsFile *id, i64 offset){
+ assert( id->isOpen );
+ SEEK(offset/1024 + 1);
+#ifdef SQLITE_TEST
+ if( offset ) SimulateDiskfullError
+#endif
+ lseek(id->h, offset, SEEK_SET);
+ 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.
+*/
+int sqlite3_sync_count = 0;
+int sqlite3_fullsync_count = 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
+
+#ifdef F_FULLFSYNC
+ if( fullSync ){
+ rc = fcntl(fd, F_FULLFSYNC, 0);
+ }else{
+ rc = 1;
+ }
+ /* If the FULLSYNC failed, try to do a normal fsync() */
+ if( rc ) rc = fsync(fd);
+
+#else /* if !defined(F_FULLSYNC) */
+#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO>0
+ if( dataOnly ){
+ rc = fdatasync(fd);
+ }else
+#endif /* _POSIX_SYNCHRONIZED_IO > 0 */
+ {
+ rc = fsync(fd);
+ }
+#endif /* defined(F_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.
+*/
+int sqlite3OsSync(OsFile *id, int dataOnly){
+ assert( id->isOpen );
+ SimulateIOError(SQLITE_IOERR);
+ TRACE2("SYNC %-3d\n", id->h);
+ if( full_fsync(id->h, id->fullSync, dataOnly) ){
+ return SQLITE_IOERR;
+ }
+ if( id->dirfd>=0 ){
+ TRACE2("DIRSYNC %-3d\n", id->dirfd);
+ full_fsync(id->dirfd, id->fullSync, 0);
+ close(id->dirfd); /* Only need to sync once, so close the directory */
+ id->dirfd = -1; /* when we are done. */
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Sync the directory zDirname. This is a no-op on operating systems other
+** than UNIX.
+**
+** This is used to make sure the master journal file has truely been deleted
+** before making changes to individual journals on a multi-database commit.
+** The F_FULLFSYNC option is not needed here.
+*/
+int sqlite3OsSyncDirectory(const char *zDirname){
+ int fd;
+ int r;
+ SimulateIOError(SQLITE_IOERR);
+ fd = open(zDirname, O_RDONLY|O_BINARY, 0);
+ TRACE3("DIRSYNC %-3d (%s)\n", fd, zDirname);
+ if( fd<0 ){
+ return SQLITE_CANTOPEN;
+ }
+ r = fsync(fd);
+ close(fd);
+ return ((r==0)?SQLITE_OK:SQLITE_IOERR);
+}
+
+/*
+** Truncate an open file to a specified size
+*/
+int sqlite3OsTruncate(OsFile *id, i64 nByte){
+ assert( id->isOpen );
+ SimulateIOError(SQLITE_IOERR);
+ return ftruncate(id->h, nByte)==0 ? SQLITE_OK : SQLITE_IOERR;
+}
+
+/*
+** Determine the current size of a file in bytes
+*/
+int sqlite3OsFileSize(OsFile *id, i64 *pSize){
+ struct stat buf;
+ assert( id->isOpen );
+ SimulateIOError(SQLITE_IOERR);
+ if( fstat(id->h, &buf)!=0 ){
+ return SQLITE_IOERR;
+ }
+ *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.
+*/
+int sqlite3OsCheckReservedLock(OsFile *id){
+ int r = 0;
+
+ assert( id->isOpen );
+ if( CHECK_THREADID(id) ) return SQLITE_MISUSE;
+ sqlite3OsEnterMutex(); /* Needed because id->pLock is shared across threads */
+
+ /* Check if a thread in this process holds such a lock */
+ if( id->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(id->h, F_GETLK, &lock);
+ if( lock.l_type!=F_UNLCK ){
+ r = 1;
+ }
+ }
+
+ sqlite3OsLeaveMutex();
+ TRACE3("TEST WR-LOCK %d %d\n", id->h, r);
+
+ return r;
+}
+
+#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
+
+/*
+** 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.
+*/
+int sqlite3OsLock(OsFile *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;
+ struct lockInfo *pLock = id->pLock;
+ struct flock lock;
+ int s;
+
+ assert( id->isOpen );
+ TRACE7("LOCK %d %s was %s(%s,%d) pid=%d\n", id->h, locktypeName(locktype),
+ locktypeName(id->locktype), locktypeName(pLock->locktype), pLock->cnt
+ ,getpid() );
+ if( CHECK_THREADID(id) ) return SQLITE_MISUSE;
+
+ /* 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( id->locktype>=locktype ){
+ TRACE3("LOCK %d %s ok (already held)\n", id->h, locktypeName(locktype));
+ return SQLITE_OK;
+ }
+
+ /* Make sure the locking sequence is correct
+ */
+ assert( id->locktype!=NO_LOCK || locktype==SHARED_LOCK );
+ assert( locktype!=PENDING_LOCK );
+ assert( locktype!=RESERVED_LOCK || id->locktype==SHARED_LOCK );
+
+ /* This mutex is needed because id->pLock is shared across threads
+ */
+ sqlite3OsEnterMutex();
+
+ /* If some thread using this PID has a lock via a different OsFile*
+ ** handle that precludes the requested lock, return BUSY.
+ */
+ if( (id->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( id->locktype==0 );
+ assert( pLock->cnt>0 );
+ id->locktype = SHARED_LOCK;
+ pLock->cnt++;
+ id->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 && id->locktype<PENDING_LOCK)
+ ){
+ lock.l_type = (locktype==SHARED_LOCK?F_RDLCK:F_WRLCK);
+ lock.l_start = PENDING_BYTE;
+ s = fcntl(id->h, F_SETLK, &lock);
+ if( s ){
+ 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(id->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(id->h, F_SETLK, &lock)!=0 ){
+ rc = SQLITE_IOERR; /* This should never happen */
+ goto end_lock;
+ }
+ if( s ){
+ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
+ }else{
+ id->locktype = SHARED_LOCK;
+ id->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!=id->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(id->h, F_SETLK, &lock);
+ if( s ){
+ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ id->locktype = locktype;
+ pLock->locktype = locktype;
+ }else if( locktype==EXCLUSIVE_LOCK ){
+ id->locktype = PENDING_LOCK;
+ pLock->locktype = PENDING_LOCK;
+ }
+
+end_lock:
+ sqlite3OsLeaveMutex();
+ TRACE4("LOCK %d %s %s\n", id->h, locktypeName(locktype),
+ rc==SQLITE_OK ? "ok" : "failed");
+ 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, this routine
+** might return SQLITE_IOERR instead of SQLITE_OK.
+*/
+int sqlite3OsUnlock(OsFile *id, int locktype){
+ struct lockInfo *pLock;
+ struct flock lock;
+ int rc = SQLITE_OK;
+
+ assert( id->isOpen );
+ TRACE7("UNLOCK %d %d was %d(%d,%d) pid=%d\n", id->h, locktype, id->locktype,
+ id->pLock->locktype, id->pLock->cnt, getpid());
+ if( CHECK_THREADID(id) ) return SQLITE_MISUSE;
+
+ assert( locktype<=SHARED_LOCK );
+ if( id->locktype<=locktype ){
+ return SQLITE_OK;
+ }
+ sqlite3OsEnterMutex();
+ pLock = id->pLock;
+ assert( pLock->cnt!=0 );
+ if( id->locktype>SHARED_LOCK ){
+ assert( pLock->locktype==id->locktype );
+ 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(id->h, F_SETLK, &lock)!=0 ){
+ /* This should never happen */
+ rc = SQLITE_IOERR;
+ }
+ }
+ 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(id->h, F_SETLK, &lock)==0 ){
+ pLock->locktype = SHARED_LOCK;
+ }else{
+ rc = SQLITE_IOERR; /* This should never happen */
+ }
+ }
+ 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;
+ if( fcntl(id->h, F_SETLK, &lock)==0 ){
+ pLock->locktype = NO_LOCK;
+ }else{
+ rc = SQLITE_IOERR; /* This should never happen */
+ }
+ }
+
+ /* 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.
+ */
+ 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;
+ }
+ }
+ sqlite3OsLeaveMutex();
+ id->locktype = locktype;
+ return rc;
+}
+
+/*
+** Close a file.
+*/
+int sqlite3OsClose(OsFile *id){
+ if( !id->isOpen ) return SQLITE_OK;
+ if( CHECK_THREADID(id) ) return SQLITE_MISUSE;
+ sqlite3OsUnlock(id, NO_LOCK);
+ if( id->dirfd>=0 ) close(id->dirfd);
+ id->dirfd = -1;
+ sqlite3OsEnterMutex();
+ 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;
+ aNew = sqliteRealloc( 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] = id->h;
+ pOpen->nPending++;
+ }
+ }else{
+ /* There are no outstanding locks so we can close the file immediately */
+ close(id->h);
+ }
+ releaseLockInfo(id->pLock);
+ releaseOpenCnt(id->pOpen);
+ sqlite3OsLeaveMutex();
+ id->isOpen = 0;
+ TRACE2("CLOSE %-3d\n", id->h);
+ OpenCounter(-1);
+ return SQLITE_OK;
+}
+
+/*
+** 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 *sqlite3OsFullPathname(const char *zRelative){
+ char *zFull = 0;
+ if( zRelative[0]=='/' ){
+ sqlite3SetString(&zFull, zRelative, (char*)0);
+ }else{
+ char *zBuf = sqliteMalloc(5000);
+ if( zBuf==0 ){
+ return 0;
+ }
+ zBuf[0] = 0;
+ sqlite3SetString(&zFull, getcwd(zBuf, 5000), "/", zRelative,
+ (char*)0);
+ sqliteFree(zBuf);
+ }
+ return zFull;
+}
+
+
+#endif /* SQLITE_OMIT_DISKIO */
+/***************************************************************************
+** Everything above deals with file I/O. Everything that follows deals
+** with other miscellanous aspects of the operating system interface
+****************************************************************************/
+
+
+/*
+** 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 sqlite3OsRandomSeed(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 !defined(SQLITE_TEST)
+ {
+ int pid, fd;
+ fd = open("/dev/urandom", O_RDONLY);
+ if( fd<0 ){
+ time((time_t*)zBuf);
+ pid = getpid();
+ memcpy(&zBuf[sizeof(time_t)], &pid, sizeof(pid));
+ }else{
+ read(fd, zBuf, 256);
+ close(fd);
+ }
+ }
+#endif
+ return SQLITE_OK;
+}
+
+/*
+** Sleep for a little while. Return the amount of time slept.
+*/
+int sqlite3OsSleep(int ms){
+#if defined(HAVE_USLEEP) && HAVE_USLEEP
+ usleep(ms*1000);
+ return ms;
+#else
+ sleep((ms+999)/1000);
+ return 1000*((ms+999)/1000);
+#endif
+}
+
+/*
+** Static variables used for thread synchronization
+*/
+static int inMutex = 0;
+#ifdef SQLITE_UNIX_THREADS
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+#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 sqlite3OsEnterMutex(){
+#ifdef SQLITE_UNIX_THREADS
+ pthread_mutex_lock(&mutex);
+#endif
+ assert( !inMutex );
+ inMutex = 1;
+}
+void sqlite3OsLeaveMutex(){
+ assert( inMutex );
+ inMutex = 0;
+#ifdef SQLITE_UNIX_THREADS
+ pthread_mutex_unlock(&mutex);
+#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
+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 sqlite3OsCurrentTime(double *prNow){
+#ifdef NO_GETTOD
+ time_t t;
+ time(&t);
+ *prNow = t/86400.0 + 2440587.5;
+#else
+ struct timeval sNow;
+ struct timezone sTz; /* Not used */
+ gettimeofday(&sNow, &sTz);
+ *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;
+}
+
+#endif /* OS_UNIX */
diff --git a/kexi/3rdparty/kexisql3/src/os_unix.h b/kexi/3rdparty/kexisql3/src/os_unix.h
new file mode 100644
index 000000000..5fdfc2ff4
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/os_unix.h
@@ -0,0 +1,116 @@
+/*
+** 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 header file defined OS-specific features for Unix.
+*/
+#ifndef _SQLITE_OS_UNIX_H_
+#define _SQLITE_OS_UNIX_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
+
+/*
+** standard include files.
+*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/*
+** 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 defined(THREADSAFE) && THREADSAFE
+# include <pthread.h>
+# define SQLITE_UNIX_THREADS 1
+#endif
+
+/*
+** The OsFile structure is a operating-system independing representation
+** of an open file handle. It is defined differently for each architecture.
+**
+** This is the definition for Unix.
+**
+** OsFile.locktype takes one of the values SHARED_LOCK, RESERVED_LOCK,
+** PENDING_LOCK or EXCLUSIVE_LOCK.
+*/
+typedef struct OsFile OsFile;
+struct OsFile {
+ struct Pager *pPager; /* The pager that owns this OsFile. Might be 0 */
+ struct openCnt *pOpen; /* Info about all open fd's on this inode */
+ struct lockInfo *pLock; /* Info about locks on this inode */
+ int h; /* The file descriptor */
+ unsigned char locktype; /* The type of lock held on this fd */
+ unsigned char isOpen; /* True if needs to be closed */
+ unsigned char fullSync; /* Use F_FULLSYNC if available */
+ int dirfd; /* File descriptor for the directory */
+#ifdef SQLITE_UNIX_THREADS
+ pthread_t tid; /* The thread authorized to use this OsFile */
+#endif
+};
+
+/*
+** A macro to set the OsFile.fullSync flag, if it exists.
+*/
+#define SET_FULLSYNC(x,y) ((x).fullSync = (y))
+
+/*
+** Maximum number of characters in a temporary file name
+*/
+#define SQLITE_TEMPNAME_SIZE 200
+
+/*
+** Minimum interval supported by sqlite3OsSleep().
+*/
+#if defined(HAVE_USLEEP) && HAVE_USLEEP
+# define SQLITE_MIN_SLEEP_MS 1
+#else
+# define SQLITE_MIN_SLEEP_MS 1000
+#endif
+
+/*
+** Default permissions when creating a new file
+*/
+#ifndef SQLITE_DEFAULT_FILE_PERMISSIONS
+# define SQLITE_DEFAULT_FILE_PERMISSIONS 0644
+#endif
+
+
+#endif /* _SQLITE_OS_UNIX_H_ */
diff --git a/kexi/3rdparty/kexisql3/src/os_win.c b/kexi/3rdparty/kexisql3/src/os_win.c
new file mode 100644
index 000000000..ec7a08405
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/os_win.c
@@ -0,0 +1,1004 @@
+/*
+** 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.
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#if OS_WIN /* This file is used for windows only */
+
+#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"
+
+/*
+** Do not include any of the File I/O interface procedures if the
+** SQLITE_OMIT_DISKIO macro is defined (indicating that there database
+** will be in-memory only)
+*/
+#ifndef SQLITE_OMIT_DISKIO
+
+/*
+** 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.
+*/
+int sqlite3_os_type = 0;
+
+/*
+** 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.
+*/
+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;
+}
+
+/*
+** Convert a UTF-8 string to UTF-32. Space to hold the returned string
+** is obtained from sqliteMalloc.
+*/
+static WCHAR *utf8ToUnicode(const char *zFilename){
+ int nByte;
+ WCHAR *zWideFilename;
+
+ if( !isNT() ){
+ return 0;
+ }
+ nByte = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0)*sizeof(WCHAR);
+ zWideFilename = sqliteMalloc( nByte*sizeof(zWideFilename[0]) );
+ if( zWideFilename==0 ){
+ return 0;
+ }
+ nByte = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nByte);
+ if( nByte==0 ){
+ sqliteFree(zWideFilename);
+ zWideFilename = 0;
+ }
+ return zWideFilename;
+}
+
+/*
+** Convert UTF-32 to UTF-8. Space to hold the returned string is
+** obtained from sqliteMalloc().
+*/
+static char *unicodeToUtf8(const WCHAR *zWideFilename){
+ int nByte;
+ char *zFilename;
+
+ nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
+ zFilename = sqliteMalloc( nByte );
+ if( zFilename==0 ){
+ return 0;
+ }
+ nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,
+ 0, 0);
+ if( nByte == 0 ){
+ sqliteFree(zFilename);
+ zFilename = 0;
+ }
+ return zFilename;
+}
+
+
+/*
+** Delete the named file
+*/
+int sqlite3OsDelete(const char *zFilename){
+ WCHAR *zWide = utf8ToUnicode(zFilename);
+ if( zWide ){
+ DeleteFileW(zWide);
+ sqliteFree(zWide);
+ }else{
+ DeleteFileA(zFilename);
+ }
+ TRACE2("DELETE \"%s\"\n", zFilename);
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the named file exists.
+*/
+int sqlite3OsFileExists(const char *zFilename){
+ int exists = 0;
+ WCHAR *zWide = utf8ToUnicode(zFilename);
+ if( zWide ){
+ exists = GetFileAttributesW(zWide) != 0xffffffff;
+ sqliteFree(zWide);
+ }else{
+ exists = GetFileAttributesA(zFilename) != 0xffffffff;
+ }
+ return exists;
+}
+
+/*
+** Attempt to open a file for both reading and writing. If that
+** fails, and allowReadonly is 1, try opening it read-only.
+** If the file does not exist, try to create it.
+**
+** Exclusive write access is required if exclusiveFlag is 1.
+** Exclusive read/write access is required if exclusiveFlag is 2.
+** In this case, if allowReadonly is also 1, only shared writing is locked.
+**
+** 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 leaves *id and *pReadonly unchanged, and returns:
+** - SQLITE_CANTOPEN_WITH_LOCKED_READWRITE if it was unable to lock the file
+** for read/write access.
+** - SQLITE_CANTOPEN_WITH_LOCKED_WRITE if it was unable to lock the file
+** for write access.
+** - SQLITE_CANTOPEN in any other case.
+*/
+int sqlite3OsOpenReadWrite(
+ const char *zFilename,
+ OsFile *id,
+ int *pReadonly,
+ int exclusiveFlag,
+ int allowReadonly
+){
+ HANDLE h;
+ int access;
+ WCHAR *zWide = utf8ToUnicode(zFilename);
+ assert( !id->isOpen );
+/* if( zWide ){
+ h = CreateFileW(zWide,
+ 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 = CreateFileW(zWide,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ if( h==INVALID_HANDLE_VALUE ){
+ sqliteFree(zWide);
+ return SQLITE_CANTOPEN;
+ }
+ *pReadonly = 1;
+ }else{
+ *pReadonly = 0;
+ }
+ sqliteFree(zWide);
+ }else*/{
+ /* js */
+ if (exclusiveFlag!=SQLITE_OPEN_READONLY) {
+ if (exclusiveFlag==SQLITE_OPEN_NO_LOCKED)
+ access = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ else if (exclusiveFlag==SQLITE_OPEN_WRITE_LOCKED)
+ access = FILE_SHARE_READ;
+ else /* SQLITE_OPEN_READ_WRITE_LOCKED */
+ access = 0;
+ if( zWide ){
+ h = CreateFileW(zWide,
+ GENERIC_READ | GENERIC_WRITE,
+ access,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ }
+ else {
+ h = CreateFileA(zFilename,
+ GENERIC_READ | GENERIC_WRITE,
+ access,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ }
+ if (!allowReadonly && GetLastError()==ERROR_SHARING_VIOLATION) {
+ sqliteFree(zWide);
+ if (exclusiveFlag==SQLITE_OPEN_NO_LOCKED)
+ return SQLITE_CANTOPEN;
+ return exclusiveFlag==SQLITE_OPEN_READ_WRITE_LOCKED
+ ? SQLITE_CANTOPEN_WITH_LOCKED_READWRITE
+ : SQLITE_CANTOPEN_WITH_LOCKED_WRITE;
+ }
+ }
+ if( exclusiveFlag==SQLITE_OPEN_READONLY || (allowReadonly && h==INVALID_HANDLE_VALUE) ){
+ /* open read only */
+ /* if (exclusiveFlag==0) */
+ access = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ /* else
+ access = FILE_SHARE_READ;*/
+ if( zWide ){
+ h = CreateFileW(zWide,
+ GENERIC_READ,
+ access,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ }
+ else {
+ h = CreateFileA(zFilename,
+ GENERIC_READ,
+ access,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ }
+ if (GetLastError()==ERROR_SHARING_VIOLATION) {
+ sqliteFree(zWide);
+ return SQLITE_CANTOPEN;
+ }
+ if( h!=INVALID_HANDLE_VALUE ){
+ *pReadonly = 1;
+ }
+ }else{
+ *pReadonly = 0;
+ }
+ sqliteFree(zWide);
+ if( h==INVALID_HANDLE_VALUE ){
+ return SQLITE_CANTOPEN;
+ }
+ }
+ id->h = h;
+ id->locktype = NO_LOCK;
+ id->sharedLockByte = 0;
+ id->isOpen = 1;
+ OpenCounter(+1);
+ TRACE3("OPEN R/W %d \"%s\"\n", h, zFilename);
+ return SQLITE_OK;
+}
+
+
+/*
+** 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 sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){
+ HANDLE h;
+ int fileflags;
+ WCHAR *zWide = utf8ToUnicode(zFilename);
+ assert( !id->isOpen );
+ if( delFlag ){
+ fileflags = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_RANDOM_ACCESS
+ | FILE_FLAG_DELETE_ON_CLOSE;
+ }else{
+ fileflags = FILE_FLAG_RANDOM_ACCESS;
+ }
+ if( zWide ){
+ h = CreateFileW(zWide,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ fileflags,
+ NULL
+ );
+ sqliteFree(zWide);
+ }else{
+ h = CreateFileA(zFilename,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ fileflags,
+ NULL
+ );
+ }
+ if( h==INVALID_HANDLE_VALUE ){
+ return SQLITE_CANTOPEN;
+ }
+ id->h = h;
+ id->locktype = NO_LOCK;
+ id->sharedLockByte = 0;
+ id->isOpen = 1;
+ OpenCounter(+1);
+ TRACE3("OPEN EX %d \"%s\"\n", h, zFilename);
+ return SQLITE_OK;
+}
+
+/*
+** 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 sqlite3OsOpenReadOnly(const char *zFilename, OsFile *id){
+ HANDLE h;
+ WCHAR *zWide = utf8ToUnicode(zFilename);
+ assert( !id->isOpen );
+ if( zWide ){
+ h = CreateFileW(zWide,
+ GENERIC_READ,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ NULL
+ );
+ sqliteFree(zWide);
+ }else{
+ h = CreateFileA(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->locktype = NO_LOCK;
+ id->sharedLockByte = 0;
+ id->isOpen = 1;
+ OpenCounter(+1);
+ TRACE3("OPEN RO %d \"%s\"\n", h, zFilename);
+ return SQLITE_OK;
+}
+
+/*
+** 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 sqlite3OsOpenDirectory(
+ const char *zDirname,
+ OsFile *id
+){
+ 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.
+*/
+char *sqlite3_temp_directory = 0;
+
+/*
+** Create a temporary file name in zBuf. zBuf must be big enough to
+** hold at least SQLITE_TEMPNAME_SIZE characters.
+*/
+int sqlite3OsTempFileName(char *zBuf){
+ static char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ int i, j;
+ char zTempPath[SQLITE_TEMPNAME_SIZE];
+ if( sqlite3_temp_directory ){
+ strncpy(zTempPath, sqlite3_temp_directory, SQLITE_TEMPNAME_SIZE-30);
+ zTempPath[SQLITE_TEMPNAME_SIZE-30] = 0;
+ }else if( isNT() ){
+ char *zMulti;
+ WCHAR zWidePath[SQLITE_TEMPNAME_SIZE];
+ GetTempPathW(SQLITE_TEMPNAME_SIZE-30, zWidePath);
+ zMulti = unicodeToUtf8(zWidePath);
+ if( zMulti ){
+ strncpy(zTempPath, zMulti, SQLITE_TEMPNAME_SIZE-30);
+ zTempPath[SQLITE_TEMPNAME_SIZE-30] = 0;
+ sqliteFree(zMulti);
+ }
+ }else{
+ GetTempPathA(SQLITE_TEMPNAME_SIZE-30, zTempPath);
+ }
+ for(i=strlen(zTempPath); i>0 && zTempPath[i-1]=='\\'; i--){}
+ zTempPath[i] = 0;
+ for(;;){
+ sprintf(zBuf, "%s\\"TEMP_FILE_PREFIX, zTempPath);
+ j = strlen(zBuf);
+ sqlite3Randomness(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( !sqlite3OsFileExists(zBuf) ) break;
+ }
+ TRACE2("TEMP FILENAME: %s\n", zBuf);
+ return SQLITE_OK;
+}
+
+/*
+** Close a file.
+*/
+int sqlite3OsClose(OsFile *id){
+ if( id->isOpen ){
+ TRACE2("CLOSE %d\n", id->h);
+ CloseHandle(id->h);
+ OpenCounter(-1);
+ id->isOpen = 0;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** 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 sqlite3OsRead(OsFile *id, void *pBuf, int amt){
+ DWORD got;
+ assert( id->isOpen );
+ SimulateIOError(SQLITE_IOERR);
+ TRACE3("READ %d lock=%d\n", id->h, id->locktype);
+ if( !ReadFile(id->h, pBuf, amt, &got, 0) ){
+ got = 0;
+ }
+ if( got==(DWORD)amt ){
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+}
+
+/*
+** Write data from a buffer into a file. Return SQLITE_OK on success
+** or some other error code on failure.
+*/
+int sqlite3OsWrite(OsFile *id, const void *pBuf, int amt){
+ int rc = 0;
+ DWORD wrote;
+ assert( id->isOpen );
+ SimulateIOError(SQLITE_IOERR);
+ SimulateDiskfullError;
+ TRACE3("WRITE %d lock=%d\n", id->h, id->locktype);
+ assert( amt>0 );
+ 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;
+}
+
+/*
+** Some microsoft compilers lack this definition.
+*/
+#ifndef INVALID_SET_FILE_POINTER
+# define INVALID_SET_FILE_POINTER ((DWORD)-1)
+#endif
+
+/*
+** Move the read/write pointer in a file.
+*/
+int sqlite3OsSeek(OsFile *id, i64 offset){
+ LONG upperBits = offset>>32;
+ LONG lowerBits = offset & 0xffffffff;
+ DWORD rc;
+ assert( id->isOpen );
+#ifdef SQLITE_TEST
+ if( offset ) SimulateDiskfullError
+#endif
+ SEEK(offset/1024 + 1);
+ rc = SetFilePointer(id->h, lowerBits, &upperBits, FILE_BEGIN);
+ TRACE3("SEEK %d %lld\n", id->h, offset);
+ if( rc==INVALID_SET_FILE_POINTER && GetLastError()!=NO_ERROR ){
+ return SQLITE_FULL;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Make sure all writes to a particular file are committed to disk.
+*/
+int sqlite3OsSync(OsFile *id, int dataOnly){
+ assert( id->isOpen );
+ TRACE3("SYNC %d lock=%d\n", id->h, id->locktype);
+ if( FlushFileBuffers(id->h) ){
+ return SQLITE_OK;
+ }else{
+ return SQLITE_IOERR;
+ }
+}
+
+/*
+** Sync the directory zDirname. This is a no-op on operating systems other
+** than UNIX.
+*/
+int sqlite3OsSyncDirectory(const char *zDirname){
+ SimulateIOError(SQLITE_IOERR);
+ return SQLITE_OK;
+}
+
+/*
+** Truncate an open file to a specified size
+*/
+int sqlite3OsTruncate(OsFile *id, i64 nByte){
+ LONG upperBits = nByte>>32;
+ assert( id->isOpen );
+ TRACE3("TRUNCATE %d %lld\n", id->h, nByte);
+ SimulateIOError(SQLITE_IOERR);
+ SetFilePointer(id->h, nByte, &upperBits, FILE_BEGIN);
+ SetEndOfFile(id->h);
+ return SQLITE_OK;
+}
+
+/*
+** Determine the current size of a file in bytes
+*/
+int sqlite3OsFileSize(OsFile *id, i64 *pSize){
+ DWORD upperBits, lowerBits;
+ assert( id->isOpen );
+ SimulateIOError(SQLITE_IOERR);
+ lowerBits = GetFileSize(id->h, &upperBits);
+ *pSize = (((i64)upperBits)<<32) + lowerBits;
+ return SQLITE_OK;
+}
+
+/*
+** Acquire a reader lock.
+** Different API routines are called depending on whether or not this
+** is Win95 or WinNT.
+*/
+static int getReadLock(OsFile *id){
+ int res;
+ if( isNT() ){
+ OVERLAPPED ovlp;
+ ovlp.Offset = SHARED_FIRST;
+ ovlp.OffsetHigh = 0;
+ ovlp.hEvent = 0;
+ res = LockFileEx(id->h, LOCKFILE_FAIL_IMMEDIATELY, 0, SHARED_SIZE,0,&ovlp);
+ }else{
+ int lk;
+ sqlite3Randomness(sizeof(lk), &lk);
+ id->sharedLockByte = (lk & 0x7fffffff)%(SHARED_SIZE - 1);
+ res = LockFile(id->h, SHARED_FIRST+id->sharedLockByte, 0, 1, 0);
+ }
+ return res;
+}
+
+/*
+** Undo a readlock
+*/
+static int unlockReadLock(OsFile *id){
+ int res;
+ if( isNT() ){
+ res = UnlockFile(id->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
+ }else{
+ res = UnlockFile(id->h, SHARED_FIRST + id->sharedLockByte, 0, 1, 0);
+ }
+ return res;
+}
+
+#ifndef SQLITE_OMIT_PAGER_PRAGMAS
+/*
+** Check that a given pathname is a directory and is writable
+**
+*/
+int sqlite3OsIsDirWritable(char *zDirname){
+ int fileAttr;
+ WCHAR *zWide;
+ if( zDirname==0 ) return 0;
+ if( !isNT() && strlen(zDirname)>MAX_PATH ) return 0;
+ zWide = utf8ToUnicode(zDirname);
+ if( zWide ){
+ fileAttr = GetFileAttributesW(zWide);
+ sqliteFree(zWide);
+ }else{
+ fileAttr = GetFileAttributesA(zDirname);
+ }
+ if( fileAttr == 0xffffffff ) return 0;
+ if( (fileAttr & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY ){
+ return 0;
+ }
+ return 1;
+}
+#endif /* SQLITE_OMIT_PAGER_PRAGMAS */
+
+/*
+** 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 sqlite3OsUnlock() 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 sqlite3OsLock(OsFile *id, int locktype){
+ int rc = SQLITE_OK; /* Return code from subroutines */
+ int res = 1; /* Result of a windows lock call */
+ int newLocktype; /* Set id->locktype to this value before exiting */
+ int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */
+
+ assert( id->isOpen );
+ TRACE5("LOCK %d %d was %d(%d)\n",
+ id->h, locktype, id->locktype, id->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( id->locktype>=locktype ){
+ return SQLITE_OK;
+ }
+
+ /* Make sure the locking sequence is correct
+ */
+ assert( id->locktype!=NO_LOCK || locktype==SHARED_LOCK );
+ assert( locktype!=PENDING_LOCK );
+ assert( locktype!=RESERVED_LOCK || id->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 = id->locktype;
+ if( id->locktype==NO_LOCK
+ || (locktype==EXCLUSIVE_LOCK && id->locktype==RESERVED_LOCK)
+ ){
+ int cnt = 3;
+ while( cnt-->0 && (res = LockFile(id->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.
+ */
+ TRACE2("could not get a PENDING lock. cnt=%d\n", cnt);
+ Sleep(1);
+ }
+ gotPendingLock = res;
+ }
+
+ /* Acquire a shared lock
+ */
+ if( locktype==SHARED_LOCK && res ){
+ assert( id->locktype==NO_LOCK );
+ res = getReadLock(id);
+ if( res ){
+ newLocktype = SHARED_LOCK;
+ }
+ }
+
+ /* Acquire a RESERVED lock
+ */
+ if( locktype==RESERVED_LOCK && res ){
+ assert( id->locktype==SHARED_LOCK );
+ res = LockFile(id->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( id->locktype>=SHARED_LOCK );
+ res = unlockReadLock(id);
+ TRACE2("unreadlock = %d\n", res);
+ res = LockFile(id->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
+ if( res ){
+ newLocktype = EXCLUSIVE_LOCK;
+ }else{
+ TRACE2("error-code = %d\n", GetLastError());
+ }
+ }
+
+ /* If we are holding a PENDING lock that ought to be released, then
+ ** release it now.
+ */
+ if( gotPendingLock && locktype==SHARED_LOCK ){
+ UnlockFile(id->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{
+ TRACE4("LOCK FAILED %d trying for %d but got %d\n", id->h,
+ locktype, newLocktype);
+ rc = SQLITE_BUSY;
+ }
+ id->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.
+*/
+int sqlite3OsCheckReservedLock(OsFile *id){
+ int rc;
+ assert( id->isOpen );
+ if( id->locktype>=RESERVED_LOCK ){
+ rc = 1;
+ TRACE3("TEST WR-LOCK %d %d (local)\n", id->h, rc);
+ }else{
+ rc = LockFile(id->h, RESERVED_BYTE, 0, 1, 0);
+ if( rc ){
+ UnlockFile(id->h, RESERVED_BYTE, 0, 1, 0);
+ }
+ rc = !rc;
+ TRACE3("TEST WR-LOCK %d %d (remote)\n", id->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;
+*/
+int sqlite3OsUnlock(OsFile *id, int locktype){
+ int type;
+ int rc = SQLITE_OK;
+ assert( id->isOpen );
+ assert( locktype<=SHARED_LOCK );
+ TRACE5("UNLOCK %d to %d was %d(%d)\n", id->h, locktype,
+ id->locktype, id->sharedLockByte);
+ type = id->locktype;
+ if( type>=EXCLUSIVE_LOCK ){
+ UnlockFile(id->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
+ if( locktype==SHARED_LOCK && !getReadLock(id) ){
+ /* This should never happen. We should always be able to
+ ** reacquire the read lock */
+ rc = SQLITE_IOERR;
+ }
+ }
+ if( type>=RESERVED_LOCK ){
+ UnlockFile(id->h, RESERVED_BYTE, 0, 1, 0);
+ }
+ if( locktype==NO_LOCK && type>=SHARED_LOCK ){
+ unlockReadLock(id);
+ }
+ if( type>=PENDING_LOCK ){
+ UnlockFile(id->h, PENDING_BYTE, 0, 1, 0);
+ }
+ id->locktype = locktype;
+ return rc;
+}
+
+/*
+** 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 *sqlite3OsFullPathname(const char *zRelative){
+ char *zNotUsed;
+ char *zFull;
+ WCHAR *zWide;
+ int nByte;
+#ifdef __CYGWIN__
+ nByte = strlen(zRelative) + MAX_PATH + 1001;
+ zFull = sqliteMalloc( nByte );
+ if( zFull==0 ) return 0;
+ if( cygwin_conv_to_full_win32_path(zRelative, zFull) ) return 0;
+#else
+ zWide = utf8ToUnicode(zRelative);
+ if( zWide ){
+ WCHAR *zTemp, *zNotUsedW;
+ nByte = GetFullPathNameW(zWide, 0, 0, &zNotUsedW) + 1;
+ zTemp = sqliteMalloc( nByte*sizeof(zTemp[0]) );
+ if( zTemp==0 ) return 0;
+ GetFullPathNameW(zWide, nByte, zTemp, &zNotUsedW);
+ sqliteFree(zWide);
+ zFull = unicodeToUtf8(zTemp);
+ sqliteFree(zTemp);
+ }else{
+ nByte = GetFullPathNameA(zRelative, 0, 0, &zNotUsed) + 1;
+ zFull = sqliteMalloc( nByte*sizeof(zFull[0]) );
+ if( zFull==0 ) return 0;
+ GetFullPathNameA(zRelative, nByte, zFull, &zNotUsed);
+ }
+#endif
+ return zFull;
+}
+
+#endif /* SQLITE_OMIT_DISKIO */
+/***************************************************************************
+** Everything above deals with file I/O. Everything that follows deals
+** with other miscellanous aspects of the operating system interface
+****************************************************************************/
+
+/*
+** 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 sqlite3OsRandomSeed(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);
+ GetSystemTime((LPSYSTEMTIME)zBuf);
+ return SQLITE_OK;
+}
+
+/*
+** Sleep for a little while. Return the amount of time slept.
+*/
+int sqlite3OsSleep(int ms){
+ Sleep(ms);
+ return ms;
+}
+
+/*
+** Static variables used for thread synchronization
+*/
+static int inMutex = 0;
+#ifdef SQLITE_W32_THREADS
+ static CRITICAL_SECTION cs;
+#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 sqlite3OsEnterMutex(){
+#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
+ assert( !inMutex );
+ inMutex = 1;
+}
+void sqlite3OsLeaveMutex(){
+ assert( inMutex );
+ inMutex = 0;
+#ifdef SQLITE_W32_THREADS
+ LeaveCriticalSection(&cs);
+#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
+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 sqlite3OsCurrentTime(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;
+ GetSystemTimeAsFileTime( &ft );
+ 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;
+}
+
+#endif /* OS_WIN */
diff --git a/kexi/3rdparty/kexisql3/src/os_win.h b/kexi/3rdparty/kexisql3/src/os_win.h
new file mode 100644
index 000000000..baf937b21
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/os_win.h
@@ -0,0 +1,40 @@
+/*
+** 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 header file defines OS-specific features for Win32
+*/
+#ifndef _SQLITE_OS_WIN_H_
+#define _SQLITE_OS_WIN_H_
+
+#include <windows.h>
+#include <winbase.h>
+
+/*
+** The OsFile structure is a operating-system independing representation
+** of an open file handle. It is defined differently for each architecture.
+**
+** This is the definition for Win32.
+*/
+typedef struct OsFile OsFile;
+struct OsFile {
+ HANDLE h; /* Handle for accessing the file */
+ unsigned char locktype; /* Type of lock currently held on this file */
+ unsigned char isOpen; /* True if needs to be closed */
+ short sharedLockByte; /* Randomly chosen byte used as a shared lock */
+};
+
+
+#define SQLITE_TEMPNAME_SIZE (MAX_PATH+50)
+#define SQLITE_MIN_SLEEP_MS 1
+
+
+#endif /* _SQLITE_OS_WIN_H_ */
diff --git a/kexi/3rdparty/kexisql3/src/pager.c b/kexi/3rdparty/kexisql3/src/pager.c
new file mode 100644
index 000000000..4b981350d
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/pager.c
@@ -0,0 +1,3636 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#ifndef SQLITE_OMIT_DISKIO
+#include "sqliteInt.h"
+#include "os.h"
+#include "pager.h"
+#include <assert.h>
+#include <string.h>
+
+/*
+** Macros for troubleshooting. Normally turned off
+*/
+#if 0
+#define TRACE1(X) sqlite3DebugPrintf(X)
+#define TRACE2(X,Y) sqlite3DebugPrintf(X,Y)
+#define TRACE3(X,Y,Z) sqlite3DebugPrintf(X,Y,Z)
+#define TRACE4(X,Y,Z,W) sqlite3DebugPrintf(X,Y,Z,W)
+#define TRACE5(X,Y,Z,W,V) sqlite3DebugPrintf(X,Y,Z,W,V)
+#else
+#define TRACE1(X)
+#define TRACE2(X,Y)
+#define TRACE3(X,Y,Z)
+#define TRACE4(X,Y,Z,W)
+#define TRACE5(X,Y,Z,W,V)
+#endif
+
+/*
+** The following two macros are used within the TRACEX() macros above
+** to print out file-descriptors. They are required so that tracing
+** can be turned on when using both the regular os_unix.c and os_test.c
+** backends.
+**
+** PAGERID() takes a pointer to a Pager struct as it's argument. The
+** associated file-descriptor is returned. FILEHANDLEID() takes an OsFile
+** struct as it's argument.
+*/
+#ifdef OS_TEST
+#define PAGERID(p) (p->fd->fd.h)
+#define FILEHANDLEID(fd) (fd->fd.h)
+#else
+#define PAGERID(p) (p->fd.h)
+#define FILEHANDLEID(fd) (fd.h)
+#endif
+
+/*
+** 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 the
+** journal file and the transaction will be
+** committed.
+**
+** The page cache comes up in PAGER_UNLOCK. The first time a
+** sqlite3pager_get() 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 sqlite3pager_write() is called, the state transitions to
+** PAGER_RESERVED. (Note that sqlite_page_write() can only be
+** called on an outstanding page which means that the pager must
+** be in PAGER_SHARED before it transitions to PAGER_RESERVED.)
+** The transition to PAGER_EXCLUSIVE occurs when before any changes
+** are made to the database file. After an sqlite3pager_rollback()
+** or sqlite_pager_commit(), the state goes back to PAGER_SHARED.
+*/
+#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)
+
+/*
+** 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 sqlite3pager_write() on a page prior to making
+** any modifications to that page. The first time sqlite3pager_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 sqlite3pager_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 */
+ PgHdr *pNextFree, *pPrevFree; /* Freelist of pages where nRef==0 */
+ PgHdr *pNextAll; /* A list of all pages */
+ PgHdr *pNextStmt, *pPrevStmt; /* List of pages in the statement journal */
+ u8 inJournal; /* TRUE if has been written to journal */
+ u8 inStmt; /* TRUE if in the statement subjournal */
+ 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 */
+ short int nRef; /* Number of users of this page */
+ PgHdr *pDirty; /* Dirty pages sorted by PgHdr.pgno */
+#ifdef SQLITE_CHECK_PAGES
+ u32 pageHash;
+#endif
+ /* pPager->pageSize bytes of page data follow this header */
+ /* Pager.nExtra bytes of local data follow the page data */
+};
+
+/*
+** 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 */
+};
+
+/*
+** 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(G,P) ((void*)&((char*)(&(G)[1]))[(P)->pageSize])
+#define PGHDR_TO_HIST(P,PGR) \
+ ((PgHistory*)&((char*)(&(P)[1]))[(PGR)->pageSize+(PGR)->nExtra])
+
+/*
+** How big to make the hash table used for locating in-memory pages
+** by page number. This macro looks a little silly, but is evaluated
+** at compile-time, not run-time (at least for gcc this is true).
+*/
+#define N_PG_HASH (\
+ (MAX_PAGES>1024)?2048: \
+ (MAX_PAGES>512)?1024: \
+ (MAX_PAGES>256)?512: \
+ (MAX_PAGES>128)?256: \
+ (MAX_PAGES>64)?128:64 \
+)
+
+/*
+** 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 {
+ 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 state; /* PAGER_UNLOCK, _SHARED, _RESERVED, etc. */
+ 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 dirtyCache; /* True if cached pages have changed */
+ u8 alwaysRollback; /* Disable dont_rollback() 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 */
+ 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 nMaxPage; /* High water mark of nPage */
+ int nRef; /* Number of in-memory pages with PgHdr.nRef>0 */
+ int mxPage; /* Maximum number of pages to hold in cache */
+ u8 *aInJournal; /* One bit for each page in the database file */
+ u8 *aInStmt; /* 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 */
+ OsFile fd, jfd; /* File descriptors for database and journal */
+ OsFile stfd; /* File descriptor for the statement subjournal*/
+ BusyHandler *pBusyHandler; /* Pointer to sqlite.busyHandler */
+ PgHdr *pFirst, *pLast; /* List of free pages */
+ PgHdr *pFirstSynced; /* First free page with PgHdr.needSync==0 */
+ PgHdr *pAll; /* List of all pages */
+ PgHdr *pStmt; /* List of pages in the statement subjournal */
+ 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, nOvfl; /* Cache hits, missing, and LRU overflows */
+ int nRead,nWrite; /* Database pages read/written */
+#endif
+ void (*xDestructor)(void*,int); /* Call this routine when freeing pages */
+ void (*xReiniter)(void*,int); /* Call this routine when reloading pages */
+ void (*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */
+ void *pCodecArg; /* First argument to xCodec() */
+ PgHdr *aHash[N_PG_HASH]; /* Hash table to map page number to PgHdr */
+};
+
+/*
+** If SQLITE_TEST is defined then increment the variable given in
+** the argument
+*/
+#ifdef SQLITE_TEST
+# define TEST_INCR(x) x++
+#else
+# define TEST_INCR(x)
+#endif
+
+/*
+** 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? */
+
+/*
+** 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
+
+/*
+** The default size of a disk sector
+*/
+#define PAGER_SECTOR_SIZE 512
+
+/*
+** 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
+
+/*
+** Enable reference count tracking (for debugging) here:
+*/
+#ifdef SQLITE_DEBUG
+ int pager3_refinfo_enable = 0;
+ static void pager_refinfo(PgHdr *p){
+ static int cnt = 0;
+ if( !pager3_refinfo_enable ) return;
+ sqlite3DebugPrintf(
+ "REFCNT: %4d addr=%p nRef=%d\n",
+ p->pgno, 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.
+**
+** All values are stored on disk as big-endian.
+*/
+static int read32bits(OsFile *fd, u32 *pRes){
+ u32 res;
+ int rc;
+ rc = sqlite3OsRead(fd, &res, sizeof(res));
+ if( rc==SQLITE_OK ){
+ 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.
+*/
+static int write32bits(OsFile *fd, u32 val){
+ unsigned char ac[4];
+ ac[0] = (val>>24) & 0xff;
+ ac[1] = (val>>16) & 0xff;
+ ac[2] = (val>>8) & 0xff;
+ ac[3] = val & 0xff;
+ return sqlite3OsWrite(fd, ac, 4);
+}
+
+/*
+** Write the 32-bit integer 'val' into the page identified by page header
+** 'p' at offset 'offset'.
+*/
+static void store32bits(u32 val, PgHdr *p, int offset){
+ unsigned char *ac;
+ ac = &((unsigned char*)PGHDR_TO_DATA(p))[offset];
+ ac[0] = (val>>24) & 0xff;
+ ac[1] = (val>>16) & 0xff;
+ ac[2] = (val>>8) & 0xff;
+ ac[3] = val & 0xff;
+}
+
+/*
+** Read a 32-bit integer at offset 'offset' from the page identified by
+** page header 'p'.
+*/
+static u32 retrieve32bits(PgHdr *p, int offset){
+ unsigned char *ac;
+ ac = &((unsigned char*)PGHDR_TO_DATA(p))[offset];
+ return (ac[0]<<24) | (ac[1]<<16) | (ac[2]<<8) | ac[3];
+}
+
+
+/*
+** 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;
+}
+
+#ifdef SQLITE_CHECK_PAGES
+/*
+** Return a 32-bit hash of the page data for pPage.
+*/
+static u32 pager_pagehash(PgHdr *pPage){
+ u32 hash = 0;
+ int i;
+ unsigned char *pData = (unsigned char *)PGHDR_TO_DATA(pPage);
+ for(i=0; i<pPage->pPager->pageSize; i++){
+ hash = (hash+i)^pData[i];
+ }
+ return hash;
+}
+
+/*
+** 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->errMask || MEMDB || pPg->dirty ||
+ pPg->pageHash==pager_pagehash(pPg) );
+}
+
+#else
+#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 obtained from sqliteMalloc(). *pzMaster is
+** set to point at the memory and SQLITE_OK returned. The caller must
+** sqliteFree() *pzMaster.
+**
+** If no master journal file name is present *pzMaster is set to 0 and
+** SQLITE_OK returned.
+*/
+static int readMasterJournal(OsFile *pJrnl, char **pzMaster){
+ int rc;
+ u32 len;
+ i64 szJ;
+ u32 cksum;
+ int i;
+ unsigned char aMagic[8]; /* A buffer to hold the magic header */
+
+ *pzMaster = 0;
+
+ rc = sqlite3OsFileSize(pJrnl, &szJ);
+ if( rc!=SQLITE_OK || szJ<16 ) return rc;
+
+ rc = sqlite3OsSeek(pJrnl, szJ-16);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = read32bits(pJrnl, &len);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = read32bits(pJrnl, &cksum);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3OsRead(pJrnl, aMagic, 8);
+ if( rc!=SQLITE_OK || memcmp(aMagic, aJournalMagic, 8) ) return rc;
+
+ rc = sqlite3OsSeek(pJrnl, szJ-16-len);
+ if( rc!=SQLITE_OK ) return rc;
+
+ *pzMaster = (char *)sqliteMalloc(len+1);
+ if( !*pzMaster ){
+ return SQLITE_NOMEM;
+ }
+ rc = sqlite3OsRead(pJrnl, *pzMaster, len);
+ if( rc!=SQLITE_OK ){
+ sqliteFree(*pzMaster);
+ *pzMaster = 0;
+ return rc;
+ }
+
+ /* See if the checksum matches the master journal name */
+ for(i=0; i<len; i++){
+ cksum -= (*pzMaster)[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.
+ */
+ sqliteFree(*pzMaster);
+ *pzMaster = 0;
+ }else{
+ (*pzMaster)[len] = '\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 int 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;
+ return sqlite3OsSeek(&pPager->jfd, pPager->journalOff);
+}
+
+/*
+** 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.
+**
+** Followed by (JOURNAL_HDR_SZ - 24) bytes of unused space.
+*/
+static int writeJournalHdr(Pager *pPager){
+
+ int rc = seekJournalHdr(pPager);
+ if( rc ) return rc;
+
+ pPager->journalHdr = pPager->journalOff;
+ if( pPager->stmtHdrOff==0 ){
+ pPager->stmtHdrOff = pPager->journalHdr;
+ }
+ pPager->journalOff += JOURNAL_HDR_SZ(pPager);
+
+ /* FIX ME:
+ **
+ ** Possibly for a pager not in no-sync mode, the journal magic should not
+ ** be written until nRec is filled in as part of next syncJournal().
+ **
+ ** Actually maybe the whole journal header should be delayed until that
+ ** point. Think about this.
+ */
+ rc = sqlite3OsWrite(&pPager->jfd, aJournalMagic, sizeof(aJournalMagic));
+
+ if( rc==SQLITE_OK ){
+ /* The nRec Field. 0xFFFFFFFF for no-sync journals. */
+ rc = write32bits(&pPager->jfd, pPager->noSync ? 0xffffffff : 0);
+ }
+ if( rc==SQLITE_OK ){
+ /* The random check-hash initialiser */
+ sqlite3Randomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
+ rc = write32bits(&pPager->jfd, pPager->cksumInit);
+ }
+ if( rc==SQLITE_OK ){
+ /* The initial database size */
+ rc = write32bits(&pPager->jfd, pPager->dbSize);
+ }
+ if( rc==SQLITE_OK ){
+ /* The assumed sector size for this process */
+ rc = write32bits(&pPager->jfd, pPager->sectorSize);
+ }
+
+ /* The journal header has been written successfully. Seek the journal
+ ** file descriptor to the end of the journal header sector.
+ */
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsSeek(&pPager->jfd, pPager->journalOff-1);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsWrite(&pPager->jfd, "\000", 1);
+ }
+ }
+ 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 */
+
+ rc = seekJournalHdr(pPager);
+ if( rc ) return rc;
+
+ if( pPager->journalOff+JOURNAL_HDR_SZ(pPager) > journalSize ){
+ return SQLITE_DONE;
+ }
+
+ rc = sqlite3OsRead(&pPager->jfd, aMagic, sizeof(aMagic));
+ if( rc ) return rc;
+
+ if( memcmp(aMagic, aJournalMagic, sizeof(aMagic))!=0 ){
+ return SQLITE_DONE;
+ }
+
+ rc = read32bits(&pPager->jfd, pNRec);
+ if( rc ) return rc;
+
+ rc = read32bits(&pPager->jfd, &pPager->cksumInit);
+ if( rc ) return rc;
+
+ rc = read32bits(&pPager->jfd, pDbSize);
+ 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, (u32 *)&pPager->sectorSize);
+ if( rc ) return rc;
+
+ pPager->journalOff += JOURNAL_HDR_SZ(pPager);
+ rc = sqlite3OsSeek(&pPager->jfd, pPager->journalOff);
+ return rc;
+}
+
+
+/*
+** 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.
+*/
+static int writeMasterJournal(Pager *pPager, const char *zMaster){
+ int rc;
+ int len;
+ int i;
+ u32 cksum = 0;
+
+ 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 ){
+ rc = seekJournalHdr(pPager);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ pPager->journalOff += (len+20);
+
+ rc = write32bits(&pPager->jfd, PAGER_MJ_PGNO(pPager));
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3OsWrite(&pPager->jfd, zMaster, len);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = write32bits(&pPager->jfd, len);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = write32bits(&pPager->jfd, cksum);
+ if( rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3OsWrite(&pPager->jfd, aJournalMagic, sizeof(aJournalMagic));
+ pPager->needSync = !pPager->noSync;
+ 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 sqlite3pager_stmt_commit()
+** 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;
+ if( pPg->inStmt ) return;
+ assert( pPg->pPrevStmt==0 && pPg->pNextStmt==0 );
+ pPg->pPrevStmt = 0;
+ if( pPager->pStmt ){
+ pPager->pStmt->pPrevStmt = pPg;
+ }
+ pPg->pNextStmt = pPager->pStmt;
+ pPager->pStmt = pPg;
+ pPg->inStmt = 1;
+}
+static void page_remove_from_stmt_list(PgHdr *pPg){
+ if( !pPg->inStmt ) return;
+ if( pPg->pPrevStmt ){
+ assert( pPg->pPrevStmt->pNextStmt==pPg );
+ pPg->pPrevStmt->pNextStmt = pPg->pNextStmt;
+ }else{
+ assert( pPg->pPager->pStmt==pPg );
+ pPg->pPager->pStmt = pPg->pNextStmt;
+ }
+ if( pPg->pNextStmt ){
+ assert( pPg->pNextStmt->pPrevStmt==pPg );
+ pPg->pNextStmt->pPrevStmt = pPg->pPrevStmt;
+ }
+ pPg->pNextStmt = 0;
+ pPg->pPrevStmt = 0;
+ pPg->inStmt = 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;
+ if( pPager->errMask ) return;
+ 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>=PAGER_RESERVED ){
+ sqlite3pager_rollback(pPager);
+ }
+ sqlite3OsUnlock(&pPager->fd, NO_LOCK);
+ pPager->state = PAGER_UNLOCK;
+ pPager->dbSize = -1;
+ pPager->nRef = 0;
+ assert( pPager->journalOpen==0 );
+}
+
+/*
+** This function is used to reset the pager after a malloc() failure. This
+** doesn't work with in-memory databases. If a malloc() fails when an
+** in-memory database is in use it is not possible to recover.
+**
+** If a transaction or statement transaction is active, it is rolled back.
+**
+** It is an error to call this function if any pages are in use.
+*/
+#ifndef SQLITE_OMIT_GLOBALRECOVER
+int sqlite3pager_reset(Pager *pPager){
+ if( pPager ){
+ if( pPager->nRef || MEMDB ){
+ return SQLITE_ERROR;
+ }
+ pPager->errMask &= ~(PAGER_ERR_MEM);
+ pager_reset(pPager);
+ }
+ return SQLITE_OK;
+}
+#endif
+
+
+/*
+** When this routine is called, the pager has the journal file open and
+** a RESERVED or EXCLUSIVE lock on the database. This routine releases
+** the database lock and acquires a SHARED 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){
+ PgHdr *pPg;
+ int rc;
+ assert( !MEMDB );
+ if( pPager->state<PAGER_RESERVED ){
+ return SQLITE_OK;
+ }
+ sqlite3pager_stmt_commit(pPager);
+ if( pPager->stmtOpen ){
+ sqlite3OsClose(&pPager->stfd);
+ pPager->stmtOpen = 0;
+ }
+ if( pPager->journalOpen ){
+ sqlite3OsClose(&pPager->jfd);
+ pPager->journalOpen = 0;
+ sqlite3OsDelete(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;
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ }
+ pPager->dirtyCache = 0;
+ pPager->nRec = 0;
+ }else{
+ assert( pPager->aInJournal==0 );
+ assert( pPager->dirtyCache==0 || pPager->useJournal==0 );
+ }
+ rc = sqlite3OsUnlock(&pPager->fd, SHARED_LOCK);
+ pPager->state = PAGER_SHARED;
+ pPager->origDbSize = 0;
+ pPager->setMaster = 0;
+ 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 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, Pgno pgno, const char *aData){
+ u32 cksum = pPager->cksumInit;
+ int i = pPager->pageSize-200;
+ while( i>0 ){
+ cksum += aData[i];
+ i -= 200;
+ }
+ return cksum;
+}
+
+/*
+** 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, OsFile *jfd, 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[SQLITE_MAX_PAGE_SIZE]; /* 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) );
+
+
+ rc = read32bits(jfd, &pgno);
+ if( rc!=SQLITE_OK ) return rc;
+ rc = sqlite3OsRead(jfd, &aData, pPager->pageSize);
+ 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, &cksum);
+ if( rc ) return rc;
+ pPager->journalOff += 4;
+ if( pager_cksum(pPager, pgno, 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.
+ **
+ ** 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 in the main rollback
+ ** journal. Otherwise, if a full ROLLBACK occurs after the statement
+ ** rollback the full ROLLBACK will not restore the page to its original
+ ** content. 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 in the main journal either because the page is not in
+ ** cache or else it is marked as needSync==0.
+ */
+ pPg = pager_lookup(pPager, pgno);
+ assert( pPager->state>=PAGER_EXCLUSIVE || pPg!=0 );
+ TRACE3("PLAYBACK %d page %d\n", PAGERID(pPager), pgno);
+ if( pPager->state>=PAGER_EXCLUSIVE && (pPg==0 || pPg->needSync==0) ){
+ rc = sqlite3OsSeek(&pPager->fd, (pgno-1)*(i64)pPager->pageSize);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsWrite(&pPager->fd, aData, pPager->pageSize);
+ }
+ if( pPg ) pPg->dirty = 0;
+ }
+ 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
+ ** sqlite3pager_rollback().
+ */
+ void *pData;
+ /* assert( pPg->nRef==0 || pPg->pgno==1 ); */
+ pData = PGHDR_TO_DATA(pPg);
+ memcpy(pData, aData, pPager->pageSize);
+ if( pPager->xDestructor ){ /*** FIX ME: Should this be xReinit? ***/
+ pPager->xDestructor(pData, pPager->pageSize);
+ }
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ CODEC(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.
+**
+** 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(const char *zMaster){
+ int rc;
+ int master_open = 0;
+ OsFile master;
+ 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.
+ */
+ memset(&master, 0, sizeof(master));
+ rc = sqlite3OsOpenReadOnly(zMaster, &master);
+ if( rc!=SQLITE_OK ) goto delmaster_out;
+ master_open = 1;
+ rc = sqlite3OsFileSize(&master, &nMasterJournal);
+ if( rc!=SQLITE_OK ) goto delmaster_out;
+
+ if( nMasterJournal>0 ){
+ char *zJournal;
+ char *zMasterPtr = 0;
+
+ /* Load the entire master journal file into space obtained from
+ ** sqliteMalloc() and pointed to by zMasterJournal.
+ */
+ zMasterJournal = (char *)sqliteMalloc(nMasterJournal);
+ if( !zMasterJournal ){
+ rc = SQLITE_NOMEM;
+ goto delmaster_out;
+ }
+ rc = sqlite3OsRead(&master, zMasterJournal, nMasterJournal);
+ if( rc!=SQLITE_OK ) goto delmaster_out;
+
+ zJournal = zMasterJournal;
+ while( (zJournal-zMasterJournal)<nMasterJournal ){
+ if( sqlite3OsFileExists(zJournal) ){
+ /* 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.
+ */
+ OsFile journal;
+ int c;
+
+ memset(&journal, 0, sizeof(journal));
+ rc = sqlite3OsOpenReadOnly(zJournal, &journal);
+ if( rc!=SQLITE_OK ){
+ goto delmaster_out;
+ }
+
+ rc = readMasterJournal(&journal, &zMasterPtr);
+ sqlite3OsClose(&journal);
+ if( rc!=SQLITE_OK ){
+ goto delmaster_out;
+ }
+
+ c = zMasterPtr!=0 && strcmp(zMasterPtr, zMaster)==0;
+ sqliteFree(zMasterPtr);
+ if( c ){
+ /* We have a match. Do not delete the master journal file. */
+ goto delmaster_out;
+ }
+ }
+ zJournal += (strlen(zJournal)+1);
+ }
+ }
+
+ sqlite3OsDelete(zMaster);
+
+delmaster_out:
+ if( zMasterJournal ){
+ sqliteFree(zMasterJournal);
+ }
+ if( master_open ){
+ sqlite3OsClose(&master);
+ }
+ return rc;
+}
+
+/*
+** Make every page in the cache agree with what is on disk. In other words,
+** reread the disk to reset the state of the cache.
+**
+** This routine is called after a rollback in which some of the dirty cache
+** pages had never been written out to disk. We need to roll back the
+** cache content and the easiest way to do that is to reread the old content
+** back from the disk.
+*/
+static int pager_reload_cache(Pager *pPager){
+ PgHdr *pPg;
+ int rc = SQLITE_OK;
+ for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
+ char zBuf[SQLITE_MAX_PAGE_SIZE];
+ if( !pPg->dirty ) continue;
+ if( (int)pPg->pgno <= pPager->origDbSize ){
+ rc = sqlite3OsSeek(&pPager->fd, pPager->pageSize*(i64)(pPg->pgno-1));
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsRead(&pPager->fd, zBuf, pPager->pageSize);
+ }
+ TRACE3("REFETCH %d page %d\n", PAGERID(pPager), pPg->pgno);
+ if( rc ) break;
+ CODEC(pPager, zBuf, pPg->pgno, 2);
+ }else{
+ memset(zBuf, 0, pPager->pageSize);
+ }
+ if( pPg->nRef==0 || memcmp(zBuf, PGHDR_TO_DATA(pPg), pPager->pageSize) ){
+ memcpy(PGHDR_TO_DATA(pPg), zBuf, pPager->pageSize);
+ if( pPager->xReiniter ){
+ pPager->xReiniter(PGHDR_TO_DATA(pPg), pPager->pageSize);
+ }else{
+ memset(PGHDR_TO_EXTRA(pPg, pPager), 0, pPager->nExtra);
+ }
+ }
+ pPg->needSync = 0;
+ pPg->dirty = 0;
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ }
+ return rc;
+}
+
+/*
+** Truncate the main file of the given pager to the number of pages
+** indicated.
+*/
+static int pager_truncate(Pager *pPager, int nPage){
+ assert( pPager->state>=PAGER_EXCLUSIVE );
+ return sqlite3OsTruncate(&pPager->fd, pPager->pageSize*(i64)nPage);
+}
+
+/*
+** 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 integer which is the number of bytes in the master journal
+** name. The value may be zero (indicate that there is no master
+** journal.)
+** (6) 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.
+** (7) 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 6 items above.
+** Each entry in the journal is an instance of the 7th 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){
+ 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 */
+ 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 ){
+ 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.
+ */
+ rc = readMasterJournal(&pPager->jfd, &zMaster);
+ assert( rc!=SQLITE_DONE );
+ if( rc!=SQLITE_OK || (zMaster && !sqlite3OsFileExists(zMaster)) ){
+ sqliteFree(zMaster);
+ zMaster = 0;
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ goto end_playback;
+ }
+ sqlite3OsSeek(&pPager->jfd, 0);
+ 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 this is the first header read from the journal, truncate the
+ ** database file back to it's original size.
+ */
+ if( pPager->state>=PAGER_EXCLUSIVE &&
+ pPager->journalOff==JOURNAL_HDR_SZ(pPager) ){
+ assert( pPager->origDbSize==0 || pPager->origDbSize==mxPg );
+ rc = pager_truncate(pPager, mxPg);
+ if( rc!=SQLITE_OK ){
+ goto end_playback;
+ }
+ pPager->dbSize = mxPg;
+ }
+
+ /* rc = sqlite3OsSeek(&pPager->jfd, JOURNAL_HDR_SZ(pPager)); */
+ 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, 1);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ pPager->journalOff = szJ;
+ break;
+ }else{
+ goto end_playback;
+ }
+ }
+ }
+ }
+
+ /* 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.
+ */
+ assert( rc==SQLITE_OK );
+ pager_reload_cache(pPager);
+
+end_playback:
+ if( rc==SQLITE_OK ){
+ rc = pager_unwritelock(pPager);
+ }
+ if( zMaster ){
+ /* If there was a master journal and this routine will return true,
+ ** see if it is possible to delete the master journal.
+ */
+ if( rc==SQLITE_OK ){
+ rc = pager_delmaster(zMaster);
+ }
+ sqliteFree(zMaster);
+ }
+
+ /* The Pager.sectorSize variable may have been updated while rolling
+ ** back a journal created by a process with a different PAGER_SECTOR_SIZE
+ ** value. Reset it to the correct value for this process.
+ */
+ pPager->sectorSize = PAGER_SECTOR_SIZE;
+ 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;
+#ifndef NDEBUG
+ {
+ i64 os_szJ;
+ rc = sqlite3OsFileSize(&pPager->jfd, &os_szJ);
+ if( rc!=SQLITE_OK ) return rc;
+ assert( szJ==os_szJ );
+ }
+#endif
+
+ /* Set hdrOff to be the offset to the first journal header written
+ ** this statement transaction, 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.
+ */
+ if( pPager->state>=PAGER_EXCLUSIVE ){
+ rc = pager_truncate(pPager, pPager->stmtSize);
+ }
+ pPager->dbSize = pPager->stmtSize;
+
+ /* Figure out how many records are in the statement journal.
+ */
+ assert( pPager->stmtInUse && pPager->journalOpen );
+ sqlite3OsSeek(&pPager->stfd, 0);
+ 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=nRec-1; i>=0; i--){
+ rc = pager_playback_one_page(pPager, &pPager->stfd, 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.
+ */
+ rc = sqlite3OsSeek(&pPager->jfd, pPager->stmtJSize);
+ if( rc!=SQLITE_OK ){
+ goto end_stmt_playback;
+ }
+ pPager->journalOff = pPager->stmtJSize;
+ pPager->cksumInit = pPager->stmtCksum;
+ assert( JOURNAL_HDR_SZ(pPager)<(pPager->pageSize+8) );
+ while( pPager->journalOff <= (hdrOff-(pPager->pageSize+8)) ){
+ rc = pager_playback_one_page(pPager, &pPager->jfd, 1);
+ assert( rc!=SQLITE_DONE );
+ if( rc!=SQLITE_OK ) goto end_stmt_playback;
+ }
+
+ while( pPager->journalOff < szJ ){
+ u32 nRec;
+ u32 dummy;
+ rc = readJournalHdr(pPager, szJ, &nRec, &dummy);
+ if( rc!=SQLITE_OK ){
+ assert( rc!=SQLITE_DONE );
+ goto end_stmt_playback;
+ }
+ if( nRec==0 ){
+ nRec = (szJ - pPager->journalOff) / (pPager->pageSize+8);
+ }
+ for(i=nRec-1; i>=0 && pPager->journalOff < szJ; i--){
+ rc = pager_playback_one_page(pPager, &pPager->jfd, 1);
+ assert( rc!=SQLITE_DONE );
+ if( rc!=SQLITE_OK ) goto end_stmt_playback;
+ }
+ }
+
+ pPager->journalOff = szJ;
+
+end_stmt_playback:
+ if( rc!=SQLITE_OK ){
+ pPager->errMask |= PAGER_ERR_CORRUPT;
+ rc = SQLITE_CORRUPT;
+ }else{
+ pPager->journalOff = szJ;
+ /* pager_reload_cache(pPager); */
+ }
+ return rc;
+}
+
+/*
+** Change the maximum number of in-memory pages that are allowed.
+*/
+void sqlite3pager_set_cachesize(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
+void sqlite3pager_set_safety_level(Pager *pPager, int level){
+ pPager->noSync = level==1 || pPager->tempFile;
+ pPager->fullSync = level==3 && !pPager->tempFile;
+ 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.
+*/
+int sqlite3_opentemp_count = 0;
+
+/*
+** Open a temporary file. Write the name of the file into zFile
+** (zFile 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 sqlite3pager_opentemp(char *zFile, OsFile *fd){
+ int cnt = 8;
+ int rc;
+ sqlite3_opentemp_count++; /* Used for testing and analysis only */
+ do{
+ cnt--;
+ sqlite3OsTempFileName(zFile);
+ rc = sqlite3OsOpenExclusive(zFile, fd, 1);
+ }while( cnt>0 && rc!=SQLITE_OK && rc!=SQLITE_NOMEM );
+ 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 sqlite3pager_get() and is only held open until the
+** last page is released using sqlite3pager_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.
+**
+** 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.
+*/
+int sqlite3pager_open(
+ 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 exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
+ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+){
+ Pager *pPager;
+ char *zFullPathname = 0;
+ int nameLen;
+ OsFile fd;
+ 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;
+ char zTemp[SQLITE_TEMPNAME_SIZE];
+
+ *ppPager = 0;
+ memset(&fd, 0, sizeof(fd));
+ if( sqlite3_malloc_failed ){
+ return SQLITE_NOMEM;
+ }
+ if( zFilename && zFilename[0] ){
+#ifndef SQLITE_OMIT_MEMORYDB
+ if( strcmp(zFilename,":memory:")==0 ){
+ memDb = 1;
+ zFullPathname = sqliteStrDup("");
+ rc = SQLITE_OK;
+ }else
+#endif
+ {
+ zFullPathname = sqlite3OsFullPathname(zFilename);
+ if( zFullPathname ){
+ rc = sqlite3OsOpenReadWrite(zFullPathname, &fd, &readOnly, exclusiveFlag, allowReadonly);
+ }
+ }
+ }else{
+ rc = sqlite3pager_opentemp(zTemp, &fd);
+ zFilename = zTemp;
+ zFullPathname = sqlite3OsFullPathname(zFilename);
+ if( rc==SQLITE_OK ){
+ tempFile = 1;
+ }
+ }
+ if( !zFullPathname ){
+ sqlite3OsClose(&fd);
+ return SQLITE_NOMEM;
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3OsClose(&fd);
+ sqliteFree(zFullPathname);
+ return rc;
+ }
+ nameLen = strlen(zFullPathname);
+ pPager = sqliteMalloc( sizeof(*pPager) + nameLen*3 + 30 );
+ if( pPager==0 ){
+ sqlite3OsClose(&fd);
+ sqliteFree(zFullPathname);
+ return SQLITE_NOMEM;
+ }
+ TRACE3("OPEN %d %s\n", FILEHANDLEID(fd), zFullPathname);
+ 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;
+#if OS_UNIX
+ pPager->fd.pPager = pPager;
+#endif
+ 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 = SQLITE_DEFAULT_PAGE_SIZE;
+ pPager->stmtSize = 0;
+ pPager->stmtJSize = 0;
+ pPager->nPage = 0;
+ pPager->nMaxPage = 0;
+ pPager->mxPage = 100;
+ pPager->state = PAGER_UNLOCK;
+ pPager->errMask = 0;
+ pPager->tempFile = tempFile;
+ pPager->memDb = memDb;
+ pPager->readOnly = readOnly;
+ pPager->needSync = 0;
+ pPager->noSync = pPager->tempFile || !useJournal;
+ pPager->fullSync = (pPager->noSync?0:1);
+ pPager->pFirst = 0;
+ pPager->pFirstSynced = 0;
+ pPager->pLast = 0;
+ pPager->nExtra = FORCE_ALIGNMENT(nExtra);
+ pPager->sectorSize = PAGER_SECTOR_SIZE;
+ pPager->pBusyHandler = 0;
+ memset(pPager->aHash, 0, sizeof(pPager->aHash));
+ *ppPager = pPager;
+ return SQLITE_OK;
+}
+
+/*
+** Set the busy handler function.
+*/
+void sqlite3pager_set_busyhandler(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 sqlite3pager_close().
+** Destructors are only called by sqlite3pager_unref().
+*/
+void sqlite3pager_set_destructor(Pager *pPager, void (*xDesc)(void*,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.
+*/
+void sqlite3pager_set_reiniter(Pager *pPager, void (*xReinit)(void*,int)){
+ pPager->xReiniter = xReinit;
+}
+
+/*
+** Set the page size. Return the new size. If the suggest new page
+** size is inappropriate, then an alternative page size is selected
+** and returned.
+*/
+int sqlite3pager_set_pagesize(Pager *pPager, int pageSize){
+ assert( pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE );
+ if( !pPager->memDb ){
+ pPager->pageSize = pageSize;
+ }
+ return pPager->pageSize;
+}
+
+/*
+** Read the first N bytes from the beginning of the file into memory
+** that pDest points to. No error checking is done.
+*/
+void sqlite3pager_read_fileheader(Pager *pPager, int N, unsigned char *pDest){
+ memset(pDest, 0, N);
+ if( MEMDB==0 ){
+ sqlite3OsSeek(&pPager->fd, 0);
+ sqlite3OsRead(&pPager->fd, pDest, N);
+ }
+}
+
+/*
+** 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.
+*/
+int sqlite3pager_pagecount(Pager *pPager){
+ i64 n;
+ assert( pPager!=0 );
+ if( pPager->dbSize>=0 ){
+ n = pPager->dbSize;
+ } else {
+ if( sqlite3OsFileSize(&pPager->fd, &n)!=SQLITE_OK ){
+ pPager->errMask |= PAGER_ERR_DISK;
+ return 0;
+ }
+ 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++;
+ }
+ return n;
+}
+
+/*
+** Forward declaration
+*/
+static int syncJournal(Pager*);
+
+
+/*
+** Unlink pPg from it's 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
+** sqlite3pager_movepage() 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 ){
+ /* If the page number is zero, then this page is not in any hash chain. */
+ return;
+ }
+ if( pPg->pNextHash ){
+ pPg->pNextHash->pPrevHash = pPg->pPrevHash;
+ }
+ if( pPg->pPrevHash ){
+ assert( pPager->aHash[pager_hash(pPg->pgno)]!=pPg );
+ pPg->pPrevHash->pNextHash = pPg->pNextHash;
+ }else{
+ int h = pager_hash(pPg->pgno);
+ assert( pPager->aHash[h]==pPg );
+ pPager->aHash[h] = pPg->pNextHash;
+ }
+
+ 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;
+
+ /* Keep the pFirstSynced pointer pointing at the first synchronized page */
+ if( pPg==pPager->pFirstSynced ){
+ PgHdr *p = pPg->pNextFree;
+ while( p && p->needSync ){ p = p->pNextFree; }
+ pPager->pFirstSynced = p;
+ }
+
+ /* Unlink from the freelist */
+ 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;
+
+ /* Unlink from the pgno hash table */
+ unlinkHashChain(pPager, pPg);
+}
+
+#ifndef SQLITE_OMIT_MEMORYDB
+/*
+** This routine is used to truncate an in-memory database. Delete
+** all pages whose pgno is larger than pPager->dbSize and is unreferenced.
+** Referenced pages larger than pPager->dbSize are zeroed.
+*/
+static void memoryTruncate(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;
+ unlinkPage(pPg);
+ sqliteFree(pPg);
+ pPager->nPage--;
+ }
+ }
+}
+#else
+#define memoryTruncate(p)
+#endif
+
+/*
+** 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;
+ assert( PAGER_SHARED==SHARED_LOCK );
+ assert( PAGER_RESERVED==RESERVED_LOCK );
+ assert( PAGER_EXCLUSIVE==EXCLUSIVE_LOCK );
+ if( pPager->state>=locktype ){
+ rc = SQLITE_OK;
+ }else{
+ do {
+ rc = sqlite3OsLock(&pPager->fd, locktype);
+ }while( rc==SQLITE_BUSY && sqlite3InvokeBusyHandler(pPager->pBusyHandler) );
+ if( rc==SQLITE_OK ){
+ pPager->state = locktype;
+ }
+ }
+ return rc;
+}
+
+/*
+** Truncate the file to the number of pages specified.
+*/
+int sqlite3pager_truncate(Pager *pPager, Pgno nPage){
+ int rc;
+ sqlite3pager_pagecount(pPager);
+ if( pPager->errMask!=0 ){
+ rc = pager_errcode(pPager);
+ return rc;
+ }
+ if( nPage>=(unsigned)pPager->dbSize ){
+ return SQLITE_OK;
+ }
+ if( MEMDB ){
+ pPager->dbSize = nPage;
+ memoryTruncate(pPager);
+ return SQLITE_OK;
+ }
+ rc = syncJournal(pPager);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ /* Get an exclusive lock on the database before truncating. */
+ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ rc = pager_truncate(pPager, 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 sqlite3pager_close(Pager *pPager){
+ PgHdr *pPg, *pNext;
+ switch( pPager->state ){
+ case PAGER_RESERVED:
+ case PAGER_SYNCED:
+ case PAGER_EXCLUSIVE: {
+ /* We ignore any IO errors that occur during the rollback
+ ** operation. So disable IO error simulation so that testing
+ ** works more easily.
+ */
+#if defined(SQLITE_TEST) && (defined(OS_UNIX) || defined(OS_WIN))
+ extern int sqlite3_io_error_pending;
+ int ioerr_cnt = sqlite3_io_error_pending;
+ sqlite3_io_error_pending = -1;
+#endif
+ sqlite3pager_rollback(pPager);
+#if defined(SQLITE_TEST) && (defined(OS_UNIX) || defined(OS_WIN))
+ sqlite3_io_error_pending = ioerr_cnt;
+#endif
+ if( !MEMDB ){
+ sqlite3OsUnlock(&pPager->fd, NO_LOCK);
+ }
+ assert( pPager->errMask || pPager->journalOpen==0 );
+ break;
+ }
+ case PAGER_SHARED: {
+ if( !MEMDB ){
+ sqlite3OsUnlock(&pPager->fd, NO_LOCK);
+ }
+ break;
+ }
+ default: {
+ /* Do nothing */
+ break;
+ }
+ }
+ for(pPg=pPager->pAll; pPg; pPg=pNext){
+#ifndef NDEBUG
+ if( MEMDB ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ assert( !pPg->alwaysRollback );
+ assert( !pHist->pOrig );
+ assert( !pHist->pStmt );
+ }
+#endif
+ pNext = pPg->pNextAll;
+ sqliteFree(pPg);
+ }
+ TRACE2("CLOSE %d\n", PAGERID(pPager));
+ assert( pPager->errMask || (pPager->journalOpen==0 && pPager->stmtOpen==0) );
+ if( pPager->journalOpen ){
+ sqlite3OsClose(&pPager->jfd);
+ }
+ sqliteFree(pPager->aInJournal);
+ if( pPager->stmtOpen ){
+ sqlite3OsClose(&pPager->stfd);
+ }
+ sqlite3OsClose(&pPager->fd);
+ /* Temp files are automatically deleted by the OS
+ ** if( pPager->tempFile ){
+ ** sqlite3OsDelete(pPager->zFilename);
+ ** }
+ */
+
+ sqliteFree(pPager);
+ return SQLITE_OK;
+}
+
+/*
+** Return the page number for the given page data.
+*/
+Pgno sqlite3pager_pagenumber(void *pData){
+ PgHdr *p = DATA_TO_PGHDR(pData);
+ return p->pgno;
+}
+
+/*
+** 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. */
+ 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);
+}
+#ifdef SQLITE_DEBUG
+ static void page_ref(PgHdr *pPg){
+ if( pPg->nRef==0 ){
+ _page_ref(pPg);
+ }else{
+ pPg->nRef++;
+ REFINFO(pPg);
+ }
+ }
+#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.
+*/
+int sqlite3pager_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.
+ */
+ i64 jSz;
+ rc = sqlite3OsFileSize(&pPager->jfd, &jSz);
+ if( rc!=0 ) return rc;
+ assert( pPager->journalOff==jSz );
+ }
+#endif
+ {
+ /* 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.
+ */
+ if( pPager->fullSync ){
+ TRACE2("SYNC journal of %d\n", PAGERID(pPager));
+ rc = sqlite3OsSync(&pPager->jfd, 0);
+ if( rc!=0 ) return rc;
+ }
+ rc = sqlite3OsSeek(&pPager->jfd,
+ pPager->journalHdr + sizeof(aJournalMagic));
+ if( rc ) return rc;
+ rc = write32bits(&pPager->jfd, pPager->nRec);
+ if( rc ) return rc;
+
+ rc = sqlite3OsSeek(&pPager->jfd, pPager->journalOff);
+ if( rc ) return rc;
+ }
+ TRACE2("SYNC journal of %d\n", PAGERID(pPager));
+ rc = sqlite3OsSync(&pPager->jfd, pPager->fullSync);
+ 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;
+
+ /* 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;
+ }
+
+ while( pList ){
+ assert( pList->dirty );
+ rc = sqlite3OsSeek(&pPager->fd, (pList->pgno-1)*(i64)pPager->pageSize);
+ if( rc ) return rc;
+ /* If there are dirty pages in the page cache with page numbers greater
+ ** than Pager.dbSize, this means sqlite3pager_truncate() 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 ){
+ CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 6);
+ TRACE3("STORE %d page %d\n", PAGERID(pPager), pList->pgno);
+ rc = sqlite3OsWrite(&pPager->fd, PGHDR_TO_DATA(pList), pPager->pageSize);
+ CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 0);
+ TEST_INCR(pPager->nWrite);
+ }
+#ifndef NDEBUG
+ else{
+ TRACE3("NOSTORE %d page %d\n", PAGERID(pPager), pList->pgno);
+ }
+#endif
+ if( rc ) return rc;
+ pList->dirty = 0;
+#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){
+ PgHdr *p, *pList;
+ pList = 0;
+ for(p=pPager->pAll; p; p=p->pNextAll){
+ if( p->dirty ){
+ p->pDirty = pList;
+ pList = p;
+ }
+ }
+ return pList;
+}
+
+/*
+** Return TRUE 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.
+*/
+static int hasHotJournal(Pager *pPager){
+ if( !pPager->useJournal ) return 0;
+ if( !sqlite3OsFileExists(pPager->zJournal) ) return 0;
+ if( sqlite3OsCheckReservedLock(&pPager->fd) ) return 0;
+ if( sqlite3pager_pagecount(pPager)==0 ){
+ sqlite3OsDelete(pPager->zJournal);
+ return 0;
+ }else{
+ return 1;
+ }
+}
+
+/*
+** 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 sqlite3pager_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 sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
+ PgHdr *pPg;
+ int rc;
+
+ /* 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( pPager->errMask & ~(PAGER_ERR_FULL) ){
+ return pager_errcode(pPager);
+ }
+
+ /* If this is the first page accessed, then get a SHARED lock
+ ** on the database file.
+ */
+ if( pPager->nRef==0 && !MEMDB ){
+ if( !pPager->noReadlock ){
+ rc = pager_wait_on_lock(pPager, SHARED_LOCK);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+
+ /* 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.
+ */
+ if( hasHotJournal(pPager) ){
+ int rc;
+
+ /* 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 it's own EXCLUSIVE lock on the database file.
+ */
+ rc = sqlite3OsLock(&pPager->fd, EXCLUSIVE_LOCK);
+ if( rc!=SQLITE_OK ){
+ sqlite3OsUnlock(&pPager->fd, NO_LOCK);
+ pPager->state = PAGER_UNLOCK;
+ return rc;
+ }
+ pPager->state = PAGER_EXCLUSIVE;
+
+ /* 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 = sqlite3OsOpenReadOnly(pPager->zJournal, &pPager->jfd);
+ if( rc!=SQLITE_OK ){
+ sqlite3OsUnlock(&pPager->fd, NO_LOCK);
+ pPager->state = PAGER_UNLOCK;
+ return SQLITE_BUSY;
+ }
+ 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);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+ pPg = 0;
+ }else{
+ /* Search for page in cache */
+ pPg = pager_lookup(pPager, pgno);
+ if( MEMDB && pPager->state==PAGER_UNLOCK ){
+ pPager->state = PAGER_SHARED;
+ }
+ }
+ if( pPg==0 ){
+ /* The requested page is not in the page cache. */
+ int h;
+ TEST_INCR(pPager->nMiss);
+ if( pPager->nPage<pPager->mxPage || pPager->pFirst==0 || MEMDB ){
+ /* Create a new page */
+ pPg = sqliteMallocRaw( sizeof(*pPg) + pPager->pageSize
+ + sizeof(u32) + pPager->nExtra
+ + MEMDB*sizeof(PgHistory) );
+ if( pPg==0 ){
+ pPager->errMask |= PAGER_ERR_MEM;
+ return SQLITE_NOMEM;
+ }
+ memset(pPg, 0, sizeof(*pPg));
+ if( MEMDB ){
+ memset(PGHDR_TO_HIST(pPg, pPager), 0, sizeof(PgHistory));
+ }
+ pPg->pPager = pPager;
+ pPg->pNextAll = pPager->pAll;
+ pPager->pAll = pPg;
+ pPager->nPage++;
+ if( pPager->nPage>pPager->nMaxPage ){
+ assert( pPager->nMaxPage==(pPager->nPage-1) );
+ pPager->nMaxPage++;
+ }
+ }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 ){
+ sqlite3pager_rollback(pPager);
+ return SQLITE_IOERR;
+ }
+ if( pPager->fullSync ){
+ /* 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 );
+ rc = writeJournalHdr(pPager);
+ if( rc!=0 ){
+ sqlite3pager_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 ){
+ sqlite3pager_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
+ */
+ unlinkPage(pPg);
+ TEST_INCR(pPager->nOvfl);
+ }
+ pPg->pgno = pgno;
+ if( pPager->aInJournal && (int)pgno<=pPager->origDbSize ){
+ sqlite3CheckMemory(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->aInStmt && (int)pgno<=pPager->stmtSize
+ && (pPager->aInStmt[pgno/8] & (1<<(pgno&7)))!=0 ){
+ page_add_to_stmt_list(pPg);
+ }else{
+ page_remove_from_stmt_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, pPager), 0, pPager->nExtra);
+ }
+ if( pPager->errMask!=0 ){
+ sqlite3pager_unref(PGHDR_TO_DATA(pPg));
+ rc = pager_errcode(pPager);
+ return rc;
+ }
+ if( sqlite3pager_pagecount(pPager)<(int)pgno ){
+ memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize);
+ }else{
+ int rc;
+ assert( MEMDB==0 );
+ rc = sqlite3OsSeek(&pPager->fd, (pgno-1)*(i64)pPager->pageSize);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsRead(&pPager->fd, PGHDR_TO_DATA(pPg), pPager->pageSize);
+ }
+ TRACE3("FETCH %d page %d\n", PAGERID(pPager), pPg->pgno);
+ CODEC(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3);
+ if( rc!=SQLITE_OK ){
+ i64 fileSize;
+ if( sqlite3OsFileSize(&pPager->fd,&fileSize)!=SQLITE_OK
+ || fileSize>=pgno*pPager->pageSize ){
+ sqlite3pager_unref(PGHDR_TO_DATA(pPg));
+ return rc;
+ }else{
+ memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize);
+ }
+ }else{
+ TEST_INCR(pPager->nRead);
+ }
+ }
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ }else{
+ /* The requested page is in the page cache. */
+ TEST_INCR(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 sqlite3pager_get(). The difference between this routine
+** and sqlite3pager_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 *sqlite3pager_lookup(Pager *pPager, Pgno pgno){
+ PgHdr *pPg;
+
+ assert( pPager!=0 );
+ assert( pgno!=0 );
+ if( pPager->errMask & ~(PAGER_ERR_FULL) ){
+ 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 sqlite3pager_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);
+
+ 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 ){
+ 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, 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 && !MEMDB ){
+ pager_reset(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){
+ int rc;
+ assert( !MEMDB );
+ assert( pPager->state>=PAGER_RESERVED );
+ assert( pPager->journalOpen==0 );
+ assert( pPager->useJournal );
+ assert( pPager->aInJournal==0 );
+ sqlite3pager_pagecount(pPager);
+ pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 );
+ if( pPager->aInJournal==0 ){
+ rc = SQLITE_NOMEM;
+ goto failed_to_open_journal;
+ }
+ rc = sqlite3OsOpenExclusive(pPager->zJournal, &pPager->jfd,pPager->tempFile);
+ pPager->journalOff = 0;
+ pPager->setMaster = 0;
+ pPager->journalHdr = 0;
+ if( rc!=SQLITE_OK ){
+ goto failed_to_open_journal;
+ }
+ SET_FULLSYNC(pPager->jfd, pPager->fullSync);
+ SET_FULLSYNC(pPager->fd, pPager->fullSync);
+ sqlite3OsOpenDirectory(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);
+ goto failed_to_open_journal;
+ }
+ pPager->origDbSize = pPager->dbSize;
+
+ rc = writeJournalHdr(pPager);
+
+ if( pPager->stmtAutoopen && rc==SQLITE_OK ){
+ rc = sqlite3pager_stmt_begin(pPager);
+ }
+ if( rc!=SQLITE_OK ){
+ rc = pager_unwritelock(pPager);
+ if( rc==SQLITE_OK ){
+ rc = SQLITE_FULL;
+ }
+ }
+ return rc;
+
+failed_to_open_journal:
+ sqliteFree(pPager->aInJournal);
+ pPager->aInJournal = 0;
+ sqlite3OsUnlock(&pPager->fd, NO_LOCK);
+ pPager->state = PAGER_UNLOCK;
+ return rc;
+}
+
+/*
+** Acquire a write-lock on the database. The lock is removed when
+** the any of the following happen:
+**
+** * sqlite3pager_commit() is called.
+** * sqlite3pager_rollback() is called.
+** * sqlite3pager_close() is called.
+** * sqlite3pager_unref() 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.
+*/
+int sqlite3pager_begin(void *pData, int exFlag){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ Pager *pPager = pPg->pPager;
+ int rc = SQLITE_OK;
+ assert( pPg->nRef>0 );
+ assert( pPager->state!=PAGER_UNLOCK );
+ if( pPager->state==PAGER_SHARED ){
+ assert( pPager->aInJournal==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 ){
+ return rc;
+ }
+ pPager->dirtyCache = 0;
+ TRACE2("TRANSACTION %d\n", PAGERID(pPager));
+ 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 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 sqlite3pager_commit() or sqlite3pager_rollback() to
+** reset.
+*/
+int sqlite3pager_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;
+ }
+
+ assert( !pPager->setMaster );
+
+ CHECK_PAGE(pPg);
+
+ /* 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->inStmt || pPager->stmtInUse==0) ){
+ pPager->dirtyCache = 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 = sqlite3pager_begin(pData, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ assert( pPager->state>=PAGER_RESERVED );
+ if( !pPager->journalOpen && pPager->useJournal ){
+ rc = pager_open_journal(pPager);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ assert( pPager->journalOpen || !pPager->useJournal );
+ pPager->dirtyCache = 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->useJournal || MEMDB) ){
+ if( (int)pPg->pgno <= pPager->origDbSize ){
+ int szPg;
+ u32 saved;
+ if( MEMDB ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ TRACE3("JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno);
+ assert( pHist->pOrig==0 );
+ pHist->pOrig = sqliteMallocRaw( pPager->pageSize );
+ if( pHist->pOrig ){
+ memcpy(pHist->pOrig, PGHDR_TO_DATA(pPg), pPager->pageSize);
+ }
+ }else{
+ u32 cksum;
+ /* 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) );
+ CODEC(pPager, pData, pPg->pgno, 7);
+ cksum = pager_cksum(pPager, pPg->pgno, pData);
+ saved = *(u32*)PGHDR_TO_EXTRA(pPg, pPager);
+ store32bits(cksum, pPg, pPager->pageSize);
+ szPg = pPager->pageSize+8;
+ store32bits(pPg->pgno, pPg, -4);
+ rc = sqlite3OsWrite(&pPager->jfd, &((char*)pData)[-4], szPg);
+ pPager->journalOff += szPg;
+ TRACE4("JOURNAL %d page %d needSync=%d\n",
+ PAGERID(pPager), pPg->pgno, pPg->needSync);
+ CODEC(pPager, pData, pPg->pgno, 0);
+ *(u32*)PGHDR_TO_EXTRA(pPg, pPager) = saved;
+ if( rc!=SQLITE_OK ){
+ sqlite3pager_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;
+ if( pPager->stmtInUse ){
+ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ page_add_to_stmt_list(pPg);
+ }
+ }
+ }else{
+ pPg->needSync = !pPager->journalStarted && !pPager->noSync;
+ TRACE4("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 && !pPg->inStmt && (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 = sqliteMallocRaw( pPager->pageSize );
+ if( pHist->pStmt ){
+ memcpy(pHist->pStmt, PGHDR_TO_DATA(pPg), pPager->pageSize);
+ }
+ TRACE3("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno);
+ }else{
+ store32bits(pPg->pgno, pPg, -4);
+ CODEC(pPager, pData, pPg->pgno, 7);
+ rc = sqlite3OsWrite(&pPager->stfd,((char*)pData)-4, pPager->pageSize+4);
+ TRACE3("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno);
+ CODEC(pPager, pData, pPg->pgno, 0);
+ if( rc!=SQLITE_OK ){
+ sqlite3pager_rollback(pPager);
+ pPager->errMask |= PAGER_ERR_FULL;
+ return rc;
+ }
+ pPager->stmtNRec++;
+ assert( pPager->aInStmt!=0 );
+ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ }
+ page_add_to_stmt_list(pPg);
+ }
+ }
+
+ /* Update the database size and return.
+ */
+ if( pPager->dbSize<(int)pPg->pgno ){
+ pPager->dbSize = pPg->pgno;
+ if( !MEMDB && pPager->dbSize==PENDING_BYTE/pPager->pageSize ){
+ pPager->dbSize++;
+ }
+ }
+ return rc;
+}
+
+/*
+** Return TRUE if the page given in the argument was previously passed
+** to sqlite3pager_write(). In other words, return TRUE if it is ok
+** to change the content of the page.
+*/
+int sqlite3pager_iswriteable(void *pData){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ return pPg->dirty;
+}
+
+#ifndef SQLITE_OMIT_VACUUM
+/*
+** Replace the content of a single page with the information in the third
+** argument.
+*/
+int sqlite3pager_overwrite(Pager *pPager, Pgno pgno, void *pData){
+ void *pPage;
+ int rc;
+
+ rc = sqlite3pager_get(pPager, pgno, &pPage);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3pager_write(pPage);
+ if( rc==SQLITE_OK ){
+ memcpy(pPage, pData, pPager->pageSize);
+ }
+ sqlite3pager_unref(pPage);
+ }
+ return rc;
+}
+#endif
+
+/*
+** 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
+** sqlite3pager_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 sqlite3pager_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 sqlite3pager_dont_write(Pager *pPager, Pgno pgno){
+ PgHdr *pPg;
+
+ if( MEMDB ) return;
+
+ pPg = pager_lookup(pPager, pgno);
+ pPg->alwaysRollback = 1;
+ if( pPg && pPg->dirty && !pPager->stmtInUse ){
+ 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{
+ TRACE3("DONT_WRITE page %d of %d\n", pgno, PAGERID(pPager));
+ pPg->dirty = 0;
+#ifdef SQLITE_CHECK_PAGES
+ pPg->pageHash = pager_pagehash(pPg);
+#endif
+ }
+ }
+}
+
+/*
+** 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 sqlite3pager_dont_rollback(void *pData){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ Pager *pPager = pPg->pPager;
+
+ if( pPager->state!=PAGER_EXCLUSIVE || pPager->journalOpen==0 ) return;
+ if( pPg->alwaysRollback || pPager->alwaysRollback || MEMDB ) 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->stmtInUse ){
+ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ page_add_to_stmt_list(pPg);
+ }
+ TRACE3("DONT_ROLLBACK page %d of %d\n", pPg->pgno, PAGERID(pPager));
+ }
+ if( pPager->stmtInUse && !pPg->inStmt && (int)pPg->pgno<=pPager->stmtSize ){
+ assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize );
+ assert( pPager->aInStmt!=0 );
+ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
+ page_add_to_stmt_list(pPg);
+ }
+}
+
+
+#ifndef SQLITE_OMIT_MEMORYDB
+/*
+** Clear a PgHistory block
+*/
+static void clearHistory(PgHistory *pHist){
+ sqliteFree(pHist->pOrig);
+ sqliteFree(pHist->pStmt);
+ pHist->pOrig = 0;
+ pHist->pStmt = 0;
+}
+#else
+#define clearHistory(x)
+#endif
+
+/*
+** 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 sqlite3pager_commit(Pager *pPager){
+ int rc;
+ PgHdr *pPg;
+
+ if( pPager->errMask==PAGER_ERR_FULL ){
+ rc = sqlite3pager_rollback(pPager);
+ if( rc==SQLITE_OK ){
+ rc = SQLITE_FULL;
+ }
+ return rc;
+ }
+ if( pPager->errMask!=0 ){
+ rc = pager_errcode(pPager);
+ return rc;
+ }
+ if( pPager->state<PAGER_RESERVED ){
+ return SQLITE_ERROR;
+ }
+ TRACE2("COMMIT %d\n", PAGERID(pPager));
+ if( MEMDB ){
+ pPg = pager_get_all_dirty_pages(pPager);
+ while( pPg ){
+ clearHistory(PGHDR_TO_HIST(pPg, pPager));
+ pPg->dirty = 0;
+ pPg->inJournal = 0;
+ pPg->inStmt = 0;
+ pPg->pPrevStmt = pPg->pNextStmt = 0;
+ pPg = pPg->pDirty;
+ }
+#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;
+ return SQLITE_OK;
+ }
+ if( pPager->dirtyCache==0 ){
+ /* Exit early (without doing the time-consuming sqlite3OsSync() 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 = sqlite3pager_sync(pPager, 0, 0);
+ if( rc!=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:
+ sqlite3pager_rollback(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 (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 sqlite3pager_rollback(Pager *pPager){
+ int rc;
+ TRACE2("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);
+ TRACE3("ROLLBACK-PAGE %d of %d\n", p->pgno, PAGERID(pPager));
+ }else{
+ TRACE3("PAGE %d is clean on %d\n", p->pgno, PAGERID(pPager));
+ }
+ clearHistory(pHist);
+ p->dirty = 0;
+ p->inJournal = 0;
+ p->inStmt = 0;
+ p->pPrevStmt = p->pNextStmt = 0;
+
+ if( pPager->xReiniter ){
+ pPager->xReiniter(PGHDR_TO_DATA(p), pPager->pageSize);
+ }
+
+ }
+ pPager->pStmt = 0;
+ pPager->dbSize = pPager->origDbSize;
+ memoryTruncate(pPager);
+ pPager->stmtInUse = 0;
+ pPager->state = PAGER_SHARED;
+ return SQLITE_OK;
+ }
+
+ if( !pPager->dirtyCache || !pPager->journalOpen ){
+ rc = pager_unwritelock(pPager);
+ pPager->dbSize = -1;
+ return rc;
+ }
+
+ if( pPager->errMask!=0 && pPager->errMask!=PAGER_ERR_FULL ){
+ if( pPager->state>=PAGER_EXCLUSIVE ){
+ pager_playback(pPager);
+ }
+ return pager_errcode(pPager);
+ }
+ if( pPager->state==PAGER_RESERVED ){
+ int rc2;
+ rc = pager_reload_cache(pPager);
+ rc2 = pager_unwritelock(pPager);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ }else{
+ rc = pager_playback(pPager);
+ }
+ if( rc!=SQLITE_OK ){
+ rc = SQLITE_CORRUPT_BKPT;
+ 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 sqlite3pager_isreadonly(Pager *pPager){
+ return pPager->readOnly;
+}
+
+/*
+** This routine is used for testing and analysis only.
+*/
+int *sqlite3pager_stats(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->errMask;
+#ifdef SQLITE_TEST
+ a[6] = pPager->nHit;
+ a[7] = pPager->nMiss;
+ a[8] = pPager->nOvfl;
+ a[9] = pPager->nRead;
+ a[10] = pPager->nWrite;
+#endif
+ return a;
+}
+
+/*
+** 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.
+*/
+int sqlite3pager_stmt_begin(Pager *pPager){
+ int rc;
+ char zTemp[SQLITE_TEMPNAME_SIZE];
+ assert( !pPager->stmtInUse );
+ assert( pPager->dbSize>=0 );
+ TRACE2("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 );
+ pPager->aInStmt = sqliteMalloc( pPager->dbSize/8 + 1 );
+ if( pPager->aInStmt==0 ){
+ sqlite3OsLock(&pPager->fd, SHARED_LOCK);
+ return SQLITE_NOMEM;
+ }
+#ifndef NDEBUG
+ rc = sqlite3OsFileSize(&pPager->jfd, &pPager->stmtJSize);
+ if( rc ) goto stmt_begin_failed;
+ assert( pPager->stmtJSize == pPager->journalOff );
+#endif
+ pPager->stmtJSize = pPager->journalOff;
+ pPager->stmtSize = pPager->dbSize;
+ pPager->stmtHdrOff = 0;
+ pPager->stmtCksum = pPager->cksumInit;
+ if( !pPager->stmtOpen ){
+ rc = sqlite3pager_opentemp(zTemp, &pPager->stfd);
+ if( rc ) goto stmt_begin_failed;
+ pPager->stmtOpen = 1;
+ pPager->stmtNRec = 0;
+ }
+ pPager->stmtInUse = 1;
+ return SQLITE_OK;
+
+stmt_begin_failed:
+ if( pPager->aInStmt ){
+ sqliteFree(pPager->aInStmt);
+ pPager->aInStmt = 0;
+ }
+ return rc;
+}
+
+/*
+** Commit a statement.
+*/
+int sqlite3pager_stmt_commit(Pager *pPager){
+ if( pPager->stmtInUse ){
+ PgHdr *pPg, *pNext;
+ TRACE2("STMT-COMMIT %d\n", PAGERID(pPager));
+ if( !MEMDB ){
+ sqlite3OsSeek(&pPager->stfd, 0);
+ /* sqlite3OsTruncate(&pPager->stfd, 0); */
+ sqliteFree( pPager->aInStmt );
+ pPager->aInStmt = 0;
+ }
+ for(pPg=pPager->pStmt; pPg; pPg=pNext){
+ pNext = pPg->pNextStmt;
+ assert( pPg->inStmt );
+ pPg->inStmt = 0;
+ pPg->pPrevStmt = pPg->pNextStmt = 0;
+ if( MEMDB ){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ sqliteFree(pHist->pStmt);
+ pHist->pStmt = 0;
+ }
+ }
+ pPager->stmtNRec = 0;
+ pPager->stmtInUse = 0;
+ pPager->pStmt = 0;
+ }
+ pPager->stmtAutoopen = 0;
+ return SQLITE_OK;
+}
+
+/*
+** Rollback a statement.
+*/
+int sqlite3pager_stmt_rollback(Pager *pPager){
+ int rc;
+ if( pPager->stmtInUse ){
+ TRACE2("STMT-ROLLBACK %d\n", PAGERID(pPager));
+ if( MEMDB ){
+ PgHdr *pPg;
+ for(pPg=pPager->pStmt; pPg; pPg=pPg->pNextStmt){
+ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager);
+ if( pHist->pStmt ){
+ memcpy(PGHDR_TO_DATA(pPg), pHist->pStmt, pPager->pageSize);
+ sqliteFree(pHist->pStmt);
+ pHist->pStmt = 0;
+ }
+ }
+ pPager->dbSize = pPager->stmtSize;
+ memoryTruncate(pPager);
+ rc = SQLITE_OK;
+ }else{
+ rc = pager_stmt_playback(pPager);
+ }
+ sqlite3pager_stmt_commit(pPager);
+ }else{
+ rc = SQLITE_OK;
+ }
+ pPager->stmtAutoopen = 0;
+ return rc;
+}
+
+/*
+** Return the full pathname of the database file.
+*/
+const char *sqlite3pager_filename(Pager *pPager){
+ return pPager->zFilename;
+}
+
+/*
+** Return the directory of the database file.
+*/
+const char *sqlite3pager_dirname(Pager *pPager){
+ return pPager->zDirectory;
+}
+
+/*
+** Return the full pathname of the journal file.
+*/
+const char *sqlite3pager_journalname(Pager *pPager){
+ return pPager->zJournal;
+}
+
+/*
+** Return true if fsync() calls are disabled for this pager. Return FALSE
+** if fsync()s are executed normally.
+*/
+int sqlite3pager_nosync(Pager *pPager){
+ return pPager->noSync;
+}
+
+/*
+** Set the codec for this pager
+*/
+void sqlite3pager_set_codec(
+ Pager *pPager,
+ void (*xCodec)(void*,void*,Pgno,int),
+ void *pCodecArg
+){
+ pPager->xCodec = xCodec;
+ pPager->pCodecArg = pCodecArg;
+}
+
+/*
+** 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){
+ void *pPage;
+ PgHdr *pPgHdr;
+ u32 change_counter;
+ int rc;
+
+ /* Open page 1 of the file for writing. */
+ rc = sqlite3pager_get(pPager, 1, &pPage);
+ if( rc!=SQLITE_OK ) return rc;
+ rc = sqlite3pager_write(pPage);
+ if( rc!=SQLITE_OK ) return rc;
+
+ /* Read the current value at byte 24. */
+ pPgHdr = DATA_TO_PGHDR(pPage);
+ change_counter = retrieve32bits(pPgHdr, 24);
+
+ /* Increment the value just read and write it back to byte 24. */
+ change_counter++;
+ store32bits(change_counter, pPgHdr, 24);
+
+ /* Release the page reference. */
+ sqlite3pager_unref(pPage);
+ return SQLITE_OK;
+}
+
+/*
+** 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 sqlite3pager_sync() call.
+**
+** If parameter nTrunc is non-zero, then the pager file is truncated to
+** nTrunc pages (this is used by auto-vacuum databases).
+*/
+int sqlite3pager_sync(Pager *pPager, const char *zMaster, Pgno nTrunc){
+ int rc = SQLITE_OK;
+
+ TRACE4("DATABASE SYNC: File=%s zMaster=%s nTrunc=%d\n",
+ pPager->zFilename, zMaster, nTrunc);
+
+ /* 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;
+ assert( pPager->journalOpen );
+
+ /* 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);
+ 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;
+ void *pPage;
+ int iSkip = PAGER_MJ_PGNO(pPager);
+ for( i=nTrunc+1; i<=pPager->origDbSize; i++ ){
+ if( !(pPager->aInJournal[i/8] & (1<<(i&7))) && i!=iSkip ){
+ rc = sqlite3pager_get(pPager, i, &pPage);
+ if( rc!=SQLITE_OK ) goto sync_exit;
+ rc = sqlite3pager_write(pPage);
+ sqlite3pager_unref(pPage);
+ 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 = sqlite3pager_truncate(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 ) goto sync_exit;
+
+ /* Sync the database file. */
+ if( !pPager->noSync ){
+ rc = sqlite3OsSync(&pPager->fd, 0);
+ }
+
+ pPager->state = PAGER_SYNCED;
+ }
+
+sync_exit:
+ return rc;
+}
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+/*
+** Move the page identified by pData to location pgno in the file.
+**
+** There must be no references to the current page pgno. If current page
+** pgno is not already in the rollback journal, it is not written there by
+** by this routine. The same applies to the page pData refers to on entry to
+** this routine.
+**
+** References to the page refered to by pData remain valid. Updating any
+** meta-data associated with page pData (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).
+*/
+int sqlite3pager_movepage(Pager *pPager, void *pData, Pgno pgno){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ PgHdr *pPgOld;
+ int h;
+ Pgno needSyncPgno = 0;
+
+ assert( pPg->nRef>0 );
+
+ TRACE5("MOVE %d page %d (needSync=%d) moves to %d\n",
+ PAGERID(pPager), pPg->pgno, pPg->needSync, pgno);
+
+ if( pPg->needSync ){
+ needSyncPgno = pPg->pgno;
+ assert( pPg->inJournal );
+ assert( pPg->dirty );
+ assert( pPager->needSync );
+ }
+
+ /* Unlink pPg from it's hash-chain */
+ unlinkHashChain(pPager, pPg);
+
+ /* If the cache contains a page with page-number pgno, remove it
+ ** from it's 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.
+ */
+ pPgOld = pager_lookup(pPager, pgno);
+ if( pPgOld ){
+ assert( pPgOld->nRef==0 );
+ unlinkHashChain(pPager, pPgOld);
+ pPgOld->dirty = 0;
+ if( pPgOld->needSync ){
+ assert( pPgOld->inJournal );
+ pPg->inJournal = 1;
+ pPg->needSync = 1;
+ assert( pPager->needSync );
+ }
+ }
+
+ /* Change the page number for pPg and insert it into the new hash-chain. */
+ pPg->pgno = pgno;
+ h = pager_hash(pgno);
+ 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;
+
+ pPg->dirty = 1;
+ pPager->dirtyCache = 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.aInJournal bit has been set. This needs to be remedied by loading
+ ** the page into the pager-cache and setting the PgHdr.needSync flag.
+ **
+ ** The sqlite3pager_get() call may cause the journal to sync. So make
+ ** sure the Pager.needSync flag is set too.
+ */
+ int rc;
+ void *pNeedSync;
+ assert( pPager->needSync );
+ rc = sqlite3pager_get(pPager, needSyncPgno, &pNeedSync);
+ if( rc!=SQLITE_OK ) return rc;
+ pPager->needSync = 1;
+ DATA_TO_PGHDR(pNeedSync)->needSync = 1;
+ DATA_TO_PGHDR(pNeedSync)->inJournal = 1;
+ DATA_TO_PGHDR(pNeedSync)->dirty = 1;
+ sqlite3pager_unref(pNeedSync);
+ }
+
+ return SQLITE_OK;
+}
+#endif
+
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
+/*
+** Return the current state of the file lock for the given pager.
+** The return value is one of NO_LOCK, SHARED_LOCK, RESERVED_LOCK,
+** PENDING_LOCK, or EXCLUSIVE_LOCK.
+*/
+int sqlite3pager_lockstate(Pager *pPager){
+#ifdef OS_TEST
+ return pPager->fd->fd.locktype;
+#else
+ return pPager->fd.locktype;
+#endif
+}
+#endif
+
+#ifdef SQLITE_DEBUG
+/*
+** Print a listing of all referenced pages and their ref count.
+*/
+void sqlite3pager_refdump(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 */
diff --git a/kexi/3rdparty/kexisql3/src/pager.h b/kexi/3rdparty/kexisql3/src/pager.h
new file mode 100644
index 000000000..34d84a9eb
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/pager.h
@@ -0,0 +1,116 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+
+/*
+** The default size of a database page.
+*/
+#ifndef SQLITE_DEFAULT_PAGE_SIZE
+# define SQLITE_DEFAULT_PAGE_SIZE 1024
+#endif
+
+/* Maximum page size. The upper bound on this value is 32768. This a limit
+** imposed by necessity of storing the value in a 2-byte unsigned integer
+** and the fact that the page size must be a power of 2.
+**
+** This value is used to initialize certain arrays on the stack at
+** various places in the code. On embedded machines where stack space
+** is limited and the flexibility of having large pages is not needed,
+** it makes good sense to reduce the maximum page size to something more
+** reasonable, like 1024.
+*/
+#ifndef SQLITE_MAX_PAGE_SIZE
+# define SQLITE_MAX_PAGE_SIZE 32768
+#endif
+
+/*
+** Maximum number of pages in one database.
+*/
+#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;
+
+/*
+** Allowed values for the flags parameter to sqlite3pager_open().
+**
+** 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 */
+
+
+/*
+** See source code comments for a detailed description of the following
+** routines:
+*/
+int sqlite3pager_open(Pager **ppPager, const char *zFilename,
+ int nExtra, int flags, int exclusiveFlag, int allowReadonly);
+void sqlite3pager_set_busyhandler(Pager*, BusyHandler *pBusyHandler);
+void sqlite3pager_set_destructor(Pager*, void(*)(void*,int));
+void sqlite3pager_set_reiniter(Pager*, void(*)(void*,int));
+int sqlite3pager_set_pagesize(Pager*, int);
+void sqlite3pager_read_fileheader(Pager*, int, unsigned char*);
+void sqlite3pager_set_cachesize(Pager*, int);
+int sqlite3pager_close(Pager *pPager);
+int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage);
+void *sqlite3pager_lookup(Pager *pPager, Pgno pgno);
+int sqlite3pager_ref(void*);
+int sqlite3pager_unref(void*);
+Pgno sqlite3pager_pagenumber(void*);
+int sqlite3pager_write(void*);
+int sqlite3pager_iswriteable(void*);
+int sqlite3pager_overwrite(Pager *pPager, Pgno pgno, void*);
+int sqlite3pager_pagecount(Pager*);
+int sqlite3pager_truncate(Pager*,Pgno);
+int sqlite3pager_begin(void*, int exFlag);
+int sqlite3pager_commit(Pager*);
+int sqlite3pager_sync(Pager*,const char *zMaster, Pgno);
+int sqlite3pager_rollback(Pager*);
+int sqlite3pager_isreadonly(Pager*);
+int sqlite3pager_stmt_begin(Pager*);
+int sqlite3pager_stmt_commit(Pager*);
+int sqlite3pager_stmt_rollback(Pager*);
+void sqlite3pager_dont_rollback(void*);
+void sqlite3pager_dont_write(Pager*, Pgno);
+int *sqlite3pager_stats(Pager*);
+void sqlite3pager_set_safety_level(Pager*,int);
+const char *sqlite3pager_filename(Pager*);
+const char *sqlite3pager_dirname(Pager*);
+const char *sqlite3pager_journalname(Pager*);
+int sqlite3pager_nosync(Pager*);
+int sqlite3pager_rename(Pager*, const char *zNewName);
+void sqlite3pager_set_codec(Pager*,void(*)(void*,void*,Pgno,int),void*);
+int sqlite3pager_movepage(Pager*,void*,Pgno);
+int sqlite3pager_reset(Pager*);
+
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
+int sqlite3pager_lockstate(Pager*);
+#endif
+
+#ifdef SQLITE_TEST
+void sqlite3pager_refdump(Pager*);
+int pager3_refinfo_enable;
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/parse.c b/kexi/3rdparty/kexisql3/src/parse.c
new file mode 100644
index 000000000..668f00126
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/parse.c
@@ -0,0 +1,3449 @@
+/* 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 51 "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 {
+ 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 operator; /* "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; };
+
+#line 48 "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.
+** 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.
+** 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 247
+#define YYACTIONTYPE unsigned short int
+#define sqlite3ParserTOKENTYPE Token
+typedef union {
+ sqlite3ParserTOKENTYPE yy0;
+ struct TrigEvent yy30;
+ Expr* yy62;
+ SrcList* yy151;
+ Token yy198;
+ struct LimitVal yy220;
+ struct LikeOp yy222;
+ IdList* yy240;
+ int yy280;
+ struct {int value; int mask;} yy359;
+ TriggerStep* yy360;
+ struct AttachKey yy361;
+ Select* yy375;
+ ExprList* yy418;
+ int yy493;
+} YYMINORTYPE;
+#define YYSTACKDEPTH 100
+#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 581
+#define YYNRULE 311
+#define YYERRORSYMBOL 146
+#define YYERRSYMDT yy493
+#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 const YYACTIONTYPE yy_action[] = {
+ /* 0 */ 286, 584, 113, 140, 142, 138, 144, 581, 150, 152,
+ /* 10 */ 154, 156, 158, 160, 162, 164, 166, 168, 3, 577,
+ /* 20 */ 740, 170, 178, 150, 152, 154, 156, 158, 160, 162,
+ /* 30 */ 164, 166, 168, 158, 160, 162, 164, 166, 168, 135,
+ /* 40 */ 97, 171, 181, 186, 191, 180, 185, 146, 148, 140,
+ /* 50 */ 142, 138, 144, 51, 150, 152, 154, 156, 158, 160,
+ /* 60 */ 162, 164, 166, 168, 16, 17, 18, 114, 7, 248,
+ /* 70 */ 150, 152, 154, 156, 158, 160, 162, 164, 166, 168,
+ /* 80 */ 13, 37, 362, 40, 59, 67, 69, 326, 357, 170,
+ /* 90 */ 6, 5, 331, 95, 364, 359, 25, 374, 258, 893,
+ /* 100 */ 1, 580, 514, 13, 4, 575, 33, 135, 97, 171,
+ /* 110 */ 181, 186, 191, 180, 185, 146, 148, 140, 142, 138,
+ /* 120 */ 144, 9, 150, 152, 154, 156, 158, 160, 162, 164,
+ /* 130 */ 166, 168, 374, 136, 592, 80, 112, 99, 269, 34,
+ /* 140 */ 32, 33, 132, 373, 115, 14, 15, 378, 333, 99,
+ /* 150 */ 380, 387, 392, 13, 367, 370, 194, 170, 78, 500,
+ /* 160 */ 525, 315, 395, 369, 375, 408, 10, 98, 14, 15,
+ /* 170 */ 78, 200, 286, 864, 113, 135, 97, 171, 181, 186,
+ /* 180 */ 191, 180, 185, 146, 148, 140, 142, 138, 144, 80,
+ /* 190 */ 150, 152, 154, 156, 158, 160, 162, 164, 166, 168,
+ /* 200 */ 104, 105, 106, 661, 496, 376, 374, 170, 467, 13,
+ /* 210 */ 2, 28, 237, 4, 409, 33, 3, 577, 14, 15,
+ /* 220 */ 51, 132, 133, 115, 241, 135, 97, 171, 181, 186,
+ /* 230 */ 191, 180, 185, 146, 148, 140, 142, 138, 144, 114,
+ /* 240 */ 150, 152, 154, 156, 158, 160, 162, 164, 166, 168,
+ /* 250 */ 40, 59, 67, 69, 326, 357, 136, 44, 45, 501,
+ /* 260 */ 473, 463, 359, 36, 361, 130, 128, 660, 275, 31,
+ /* 270 */ 84, 99, 356, 378, 14, 15, 380, 387, 392, 52,
+ /* 280 */ 170, 117, 122, 123, 113, 541, 369, 643, 395, 348,
+ /* 290 */ 98, 54, 78, 200, 302, 57, 58, 819, 135, 97,
+ /* 300 */ 171, 181, 186, 191, 180, 185, 146, 148, 140, 142,
+ /* 310 */ 138, 144, 861, 150, 152, 154, 156, 158, 160, 162,
+ /* 320 */ 164, 166, 168, 104, 105, 106, 817, 80, 48, 316,
+ /* 330 */ 162, 164, 166, 168, 319, 277, 12, 49, 99, 303,
+ /* 340 */ 283, 818, 99, 124, 304, 99, 241, 172, 593, 114,
+ /* 350 */ 50, 193, 46, 378, 170, 13, 380, 387, 392, 78,
+ /* 360 */ 260, 276, 47, 78, 200, 64, 78, 260, 395, 174,
+ /* 370 */ 175, 221, 135, 97, 171, 181, 186, 191, 180, 185,
+ /* 380 */ 146, 148, 140, 142, 138, 144, 199, 150, 152, 154,
+ /* 390 */ 156, 158, 160, 162, 164, 166, 168, 173, 252, 261,
+ /* 400 */ 120, 122, 123, 212, 170, 268, 254, 130, 128, 288,
+ /* 410 */ 590, 176, 246, 187, 192, 414, 195, 241, 197, 198,
+ /* 420 */ 14, 15, 135, 97, 171, 181, 186, 191, 180, 185,
+ /* 430 */ 146, 148, 140, 142, 138, 144, 433, 150, 152, 154,
+ /* 440 */ 156, 158, 160, 162, 164, 166, 168, 311, 99, 707,
+ /* 450 */ 99, 422, 708, 417, 275, 81, 318, 598, 99, 219,
+ /* 460 */ 13, 231, 124, 13, 176, 48, 187, 192, 20, 78,
+ /* 470 */ 317, 78, 214, 195, 49, 197, 198, 462, 170, 78,
+ /* 480 */ 200, 116, 27, 13, 410, 113, 591, 50, 80, 225,
+ /* 490 */ 195, 11, 197, 198, 506, 235, 135, 97, 171, 181,
+ /* 500 */ 186, 191, 180, 185, 146, 148, 140, 142, 138, 144,
+ /* 510 */ 80, 150, 152, 154, 156, 158, 160, 162, 164, 166,
+ /* 520 */ 168, 277, 215, 324, 606, 14, 15, 301, 14, 15,
+ /* 530 */ 512, 13, 508, 240, 196, 486, 195, 685, 197, 198,
+ /* 540 */ 22, 834, 445, 331, 462, 170, 444, 276, 14, 15,
+ /* 550 */ 114, 468, 278, 394, 599, 280, 470, 288, 446, 680,
+ /* 560 */ 13, 321, 404, 135, 97, 171, 181, 186, 191, 180,
+ /* 570 */ 185, 146, 148, 140, 142, 138, 144, 80, 150, 152,
+ /* 580 */ 154, 156, 158, 160, 162, 164, 166, 168, 74, 99,
+ /* 590 */ 540, 366, 73, 99, 352, 289, 14, 15, 176, 333,
+ /* 600 */ 187, 192, 486, 869, 359, 273, 283, 542, 543, 867,
+ /* 610 */ 78, 500, 510, 170, 78, 323, 682, 176, 472, 187,
+ /* 620 */ 192, 746, 118, 470, 119, 14, 15, 195, 346, 197,
+ /* 630 */ 198, 135, 97, 171, 181, 186, 191, 180, 185, 146,
+ /* 640 */ 148, 140, 142, 138, 144, 99, 150, 152, 154, 156,
+ /* 650 */ 158, 160, 162, 164, 166, 168, 532, 334, 341, 343,
+ /* 660 */ 841, 39, 195, 170, 197, 198, 78, 94, 124, 356,
+ /* 670 */ 271, 353, 439, 441, 440, 544, 883, 428, 72, 862,
+ /* 680 */ 288, 135, 97, 171, 181, 186, 191, 180, 185, 146,
+ /* 690 */ 148, 140, 142, 138, 144, 13, 150, 152, 154, 156,
+ /* 700 */ 158, 160, 162, 164, 166, 168, 195, 99, 197, 198,
+ /* 710 */ 406, 330, 195, 170, 197, 198, 568, 405, 306, 195,
+ /* 720 */ 42, 197, 198, 65, 195, 539, 197, 198, 78, 96,
+ /* 730 */ 66, 135, 97, 171, 181, 186, 191, 180, 185, 146,
+ /* 740 */ 148, 140, 142, 138, 144, 885, 150, 152, 154, 156,
+ /* 750 */ 158, 160, 162, 164, 166, 168, 99, 740, 99, 298,
+ /* 760 */ 14, 15, 272, 170, 13, 74, 572, 86, 600, 73,
+ /* 770 */ 126, 127, 614, 709, 309, 478, 24, 78, 247, 78,
+ /* 780 */ 111, 135, 97, 171, 181, 186, 191, 180, 185, 146,
+ /* 790 */ 148, 140, 142, 138, 144, 99, 150, 152, 154, 156,
+ /* 800 */ 158, 160, 162, 164, 166, 168, 99, 238, 113, 239,
+ /* 810 */ 295, 26, 296, 170, 338, 337, 78, 137, 294, 320,
+ /* 820 */ 347, 239, 348, 390, 211, 348, 30, 78, 139, 14,
+ /* 830 */ 15, 135, 189, 171, 181, 186, 191, 180, 185, 146,
+ /* 840 */ 148, 140, 142, 138, 144, 99, 150, 152, 154, 156,
+ /* 850 */ 158, 160, 162, 164, 166, 168, 99, 80, 99, 372,
+ /* 860 */ 399, 442, 348, 170, 298, 243, 78, 141, 363, 601,
+ /* 870 */ 428, 437, 438, 114, 411, 269, 605, 78, 143, 78,
+ /* 880 */ 145, 448, 97, 171, 181, 186, 191, 180, 185, 146,
+ /* 890 */ 148, 140, 142, 138, 144, 99, 150, 152, 154, 156,
+ /* 900 */ 158, 160, 162, 164, 166, 168, 99, 80, 99, 430,
+ /* 910 */ 99, 296, 555, 170, 413, 856, 78, 147, 672, 457,
+ /* 920 */ 352, 348, 298, 443, 465, 45, 35, 78, 149, 78,
+ /* 930 */ 151, 78, 153, 171, 181, 186, 191, 180, 185, 146,
+ /* 940 */ 148, 140, 142, 138, 144, 99, 150, 152, 154, 156,
+ /* 950 */ 158, 160, 162, 164, 166, 168, 99, 459, 99, 29,
+ /* 960 */ 79, 464, 183, 483, 71, 339, 78, 155, 709, 421,
+ /* 970 */ 428, 79, 109, 99, 491, 71, 296, 78, 157, 78,
+ /* 980 */ 159, 490, 243, 109, 99, 340, 99, 449, 857, 223,
+ /* 990 */ 99, 460, 182, 709, 78, 161, 99, 349, 827, 136,
+ /* 1000 */ 223, 99, 80, 201, 99, 78, 163, 78, 165, 507,
+ /* 1010 */ 136, 78, 167, 42, 201, 38, 493, 78, 169, 569,
+ /* 1020 */ 207, 205, 78, 177, 674, 78, 179, 477, 203, 76,
+ /* 1030 */ 77, 207, 205, 98, 99, 84, 99, 42, 336, 203,
+ /* 1040 */ 76, 77, 99, 43, 98, 41, 428, 79, 494, 80,
+ /* 1050 */ 428, 71, 84, 99, 352, 78, 188, 78, 190, 109,
+ /* 1060 */ 499, 428, 497, 78, 202, 60, 104, 105, 106, 107,
+ /* 1070 */ 108, 209, 213, 99, 78, 204, 223, 104, 105, 106,
+ /* 1080 */ 107, 108, 209, 213, 820, 509, 136, 53, 383, 511,
+ /* 1090 */ 201, 99, 56, 61, 78, 206, 55, 428, 428, 889,
+ /* 1100 */ 513, 99, 243, 99, 352, 99, 79, 207, 205, 312,
+ /* 1110 */ 71, 99, 78, 208, 483, 203, 76, 77, 109, 533,
+ /* 1120 */ 98, 497, 78, 220, 78, 222, 78, 232, 84, 99,
+ /* 1130 */ 428, 353, 78, 234, 352, 223, 517, 521, 389, 99,
+ /* 1140 */ 62, 530, 99, 64, 63, 136, 68, 529, 70, 201,
+ /* 1150 */ 78, 236, 352, 104, 105, 106, 107, 108, 209, 213,
+ /* 1160 */ 78, 249, 99, 78, 265, 877, 207, 205, 398, 527,
+ /* 1170 */ 99, 615, 616, 313, 203, 76, 77, 99, 523, 98,
+ /* 1180 */ 80, 353, 8, 78, 270, 99, 456, 19, 21, 23,
+ /* 1190 */ 412, 78, 300, 75, 78, 310, 82, 84, 78, 365,
+ /* 1200 */ 563, 83, 547, 99, 87, 553, 78, 393, 85, 557,
+ /* 1210 */ 99, 353, 104, 105, 106, 107, 108, 209, 213, 99,
+ /* 1220 */ 269, 536, 99, 467, 78, 434, 88, 266, 534, 353,
+ /* 1230 */ 560, 78, 481, 566, 264, 89, 250, 90, 93, 91,
+ /* 1240 */ 78, 485, 101, 78, 498, 92, 100, 102, 103, 110,
+ /* 1250 */ 131, 121, 134, 125, 129, 168, 184, 242, 686, 687,
+ /* 1260 */ 688, 210, 233, 218, 224, 216, 227, 226, 217, 229,
+ /* 1270 */ 228, 230, 243, 251, 515, 519, 463, 245, 253, 244,
+ /* 1280 */ 505, 257, 255, 256, 258, 84, 259, 262, 263, 239,
+ /* 1290 */ 267, 279, 274, 281, 282, 299, 285, 292, 284, 287,
+ /* 1300 */ 290, 293, 297, 305, 314, 291, 307, 322, 308, 325,
+ /* 1310 */ 327, 345, 329, 328, 332, 350, 354, 330, 358, 335,
+ /* 1320 */ 342, 379, 381, 382, 344, 351, 368, 385, 355, 371,
+ /* 1330 */ 388, 360, 396, 397, 400, 401, 415, 54, 416, 386,
+ /* 1340 */ 384, 391, 418, 402, 407, 419, 377, 420, 423, 424,
+ /* 1350 */ 403, 426, 425, 427, 429, 435, 431, 849, 436, 854,
+ /* 1360 */ 432, 855, 450, 447, 451, 452, 454, 453, 825, 455,
+ /* 1370 */ 458, 826, 469, 461, 466, 747, 748, 848, 471, 464,
+ /* 1380 */ 863, 480, 474, 475, 476, 482, 865, 479, 487, 484,
+ /* 1390 */ 489, 488, 492, 866, 495, 868, 504, 679, 502, 681,
+ /* 1400 */ 833, 875, 518, 503, 516, 739, 520, 524, 522, 742,
+ /* 1410 */ 745, 531, 526, 835, 535, 528, 538, 537, 836, 837,
+ /* 1420 */ 838, 839, 545, 546, 840, 550, 876, 556, 551, 878,
+ /* 1430 */ 548, 549, 554, 879, 559, 882, 884, 562, 886, 561,
+ /* 1440 */ 552, 558, 564, 567, 570, 565, 571, 887, 576, 574,
+ /* 1450 */ 573, 888, 578, 559, 559, 579,
+};
+static const YYCODETYPE yy_lookahead[] = {
+ /* 0 */ 28, 11, 30, 77, 78, 79, 80, 0, 82, 83,
+ /* 10 */ 84, 85, 86, 87, 88, 89, 90, 91, 11, 12,
+ /* 20 */ 11, 49, 81, 82, 83, 84, 85, 86, 87, 88,
+ /* 30 */ 89, 90, 91, 86, 87, 88, 89, 90, 91, 67,
+ /* 40 */ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
+ /* 50 */ 78, 79, 80, 69, 82, 83, 84, 85, 86, 87,
+ /* 60 */ 88, 89, 90, 91, 17, 18, 19, 95, 11, 29,
+ /* 70 */ 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
+ /* 80 */ 30, 97, 98, 99, 100, 101, 102, 103, 104, 49,
+ /* 90 */ 150, 151, 50, 53, 26, 111, 156, 155, 30, 147,
+ /* 100 */ 148, 149, 162, 30, 152, 163, 164, 67, 68, 69,
+ /* 110 */ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ /* 120 */ 80, 153, 82, 83, 84, 85, 86, 87, 88, 89,
+ /* 130 */ 90, 91, 155, 65, 11, 195, 28, 155, 129, 165,
+ /* 140 */ 163, 164, 168, 169, 170, 95, 96, 97, 106, 155,
+ /* 150 */ 100, 101, 102, 30, 86, 87, 162, 49, 176, 177,
+ /* 160 */ 220, 88, 112, 95, 187, 188, 154, 99, 95, 96,
+ /* 170 */ 176, 177, 28, 21, 30, 67, 68, 69, 70, 71,
+ /* 180 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 195,
+ /* 190 */ 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
+ /* 200 */ 132, 133, 134, 27, 222, 29, 155, 49, 56, 30,
+ /* 210 */ 149, 160, 218, 152, 163, 164, 11, 12, 95, 96,
+ /* 220 */ 69, 168, 169, 170, 230, 67, 68, 69, 70, 71,
+ /* 230 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 95,
+ /* 240 */ 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
+ /* 250 */ 99, 100, 101, 102, 103, 104, 65, 192, 193, 107,
+ /* 260 */ 108, 109, 111, 174, 175, 86, 87, 27, 29, 29,
+ /* 270 */ 118, 155, 183, 97, 95, 96, 100, 101, 102, 99,
+ /* 280 */ 49, 171, 172, 173, 30, 106, 95, 27, 112, 29,
+ /* 290 */ 99, 111, 176, 177, 162, 17, 18, 139, 67, 68,
+ /* 300 */ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
+ /* 310 */ 79, 80, 15, 82, 83, 84, 85, 86, 87, 88,
+ /* 320 */ 89, 90, 91, 132, 133, 134, 21, 195, 22, 27,
+ /* 330 */ 88, 89, 90, 91, 218, 96, 155, 31, 155, 207,
+ /* 340 */ 208, 21, 155, 233, 212, 155, 230, 49, 11, 95,
+ /* 350 */ 44, 26, 46, 97, 49, 30, 100, 101, 102, 176,
+ /* 360 */ 177, 122, 56, 176, 177, 105, 176, 177, 112, 71,
+ /* 370 */ 72, 140, 67, 68, 69, 70, 71, 72, 73, 74,
+ /* 380 */ 75, 76, 77, 78, 79, 80, 27, 82, 83, 84,
+ /* 390 */ 85, 86, 87, 88, 89, 90, 91, 99, 215, 216,
+ /* 400 */ 171, 172, 173, 27, 49, 218, 216, 86, 87, 168,
+ /* 410 */ 11, 223, 224, 225, 226, 24, 114, 230, 116, 117,
+ /* 420 */ 95, 96, 67, 68, 69, 70, 71, 72, 73, 74,
+ /* 430 */ 75, 76, 77, 78, 79, 80, 139, 82, 83, 84,
+ /* 440 */ 85, 86, 87, 88, 89, 90, 91, 206, 155, 27,
+ /* 450 */ 155, 60, 27, 62, 29, 162, 27, 11, 155, 139,
+ /* 460 */ 30, 141, 233, 30, 223, 22, 225, 226, 154, 176,
+ /* 470 */ 177, 176, 177, 114, 31, 116, 117, 162, 49, 176,
+ /* 480 */ 177, 26, 26, 30, 28, 30, 11, 44, 195, 46,
+ /* 490 */ 114, 16, 116, 117, 24, 140, 67, 68, 69, 70,
+ /* 500 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ /* 510 */ 195, 82, 83, 84, 85, 86, 87, 88, 89, 90,
+ /* 520 */ 91, 96, 227, 27, 11, 95, 96, 26, 95, 96,
+ /* 530 */ 60, 30, 62, 230, 115, 220, 114, 118, 116, 117,
+ /* 540 */ 154, 11, 32, 50, 162, 49, 36, 122, 95, 96,
+ /* 550 */ 95, 236, 122, 178, 11, 122, 241, 168, 48, 11,
+ /* 560 */ 30, 88, 69, 67, 68, 69, 70, 71, 72, 73,
+ /* 570 */ 74, 75, 76, 77, 78, 79, 80, 195, 82, 83,
+ /* 580 */ 84, 85, 86, 87, 88, 89, 90, 91, 115, 155,
+ /* 590 */ 155, 27, 119, 155, 155, 206, 95, 96, 223, 106,
+ /* 600 */ 225, 226, 220, 11, 111, 207, 208, 172, 173, 11,
+ /* 610 */ 176, 177, 142, 49, 176, 177, 11, 223, 236, 225,
+ /* 620 */ 226, 11, 27, 241, 29, 95, 96, 114, 189, 116,
+ /* 630 */ 117, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 640 */ 76, 77, 78, 79, 80, 155, 82, 83, 84, 85,
+ /* 650 */ 86, 87, 88, 89, 90, 91, 222, 107, 108, 109,
+ /* 660 */ 11, 175, 114, 49, 116, 117, 176, 177, 233, 183,
+ /* 670 */ 29, 232, 107, 108, 109, 26, 11, 155, 26, 15,
+ /* 680 */ 168, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 690 */ 76, 77, 78, 79, 80, 30, 82, 83, 84, 85,
+ /* 700 */ 86, 87, 88, 89, 90, 91, 114, 155, 116, 117,
+ /* 710 */ 183, 184, 114, 49, 116, 117, 194, 190, 206, 114,
+ /* 720 */ 106, 116, 117, 34, 114, 76, 116, 117, 176, 177,
+ /* 730 */ 41, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 740 */ 76, 77, 78, 79, 80, 11, 82, 83, 84, 85,
+ /* 750 */ 86, 87, 88, 89, 90, 91, 155, 11, 155, 155,
+ /* 760 */ 95, 96, 121, 49, 30, 115, 244, 198, 11, 119,
+ /* 770 */ 132, 133, 120, 28, 205, 29, 154, 176, 177, 176,
+ /* 780 */ 177, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 790 */ 76, 77, 78, 79, 80, 155, 82, 83, 84, 85,
+ /* 800 */ 86, 87, 88, 89, 90, 91, 155, 27, 30, 29,
+ /* 810 */ 27, 157, 29, 49, 98, 99, 176, 177, 214, 27,
+ /* 820 */ 27, 29, 29, 27, 162, 29, 27, 176, 177, 95,
+ /* 830 */ 96, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 840 */ 76, 77, 78, 79, 80, 155, 82, 83, 84, 85,
+ /* 850 */ 86, 87, 88, 89, 90, 91, 155, 195, 155, 167,
+ /* 860 */ 27, 52, 29, 49, 155, 120, 176, 177, 176, 11,
+ /* 870 */ 155, 58, 59, 95, 162, 129, 11, 176, 177, 176,
+ /* 880 */ 177, 25, 68, 69, 70, 71, 72, 73, 74, 75,
+ /* 890 */ 76, 77, 78, 79, 80, 155, 82, 83, 84, 85,
+ /* 900 */ 86, 87, 88, 89, 90, 91, 155, 195, 155, 194,
+ /* 910 */ 155, 29, 134, 49, 158, 106, 176, 177, 11, 27,
+ /* 920 */ 155, 29, 155, 214, 192, 193, 166, 176, 177, 176,
+ /* 930 */ 177, 176, 177, 69, 70, 71, 72, 73, 74, 75,
+ /* 940 */ 76, 77, 78, 79, 80, 155, 82, 83, 84, 85,
+ /* 950 */ 86, 87, 88, 89, 90, 91, 155, 101, 155, 161,
+ /* 960 */ 26, 67, 69, 155, 30, 37, 176, 177, 106, 162,
+ /* 970 */ 155, 26, 38, 155, 27, 30, 29, 176, 177, 176,
+ /* 980 */ 177, 214, 120, 38, 155, 57, 155, 231, 106, 55,
+ /* 990 */ 155, 235, 99, 11, 176, 177, 155, 232, 142, 65,
+ /* 1000 */ 55, 155, 195, 69, 155, 176, 177, 176, 177, 194,
+ /* 1010 */ 65, 176, 177, 106, 69, 155, 162, 176, 177, 64,
+ /* 1020 */ 86, 87, 176, 177, 130, 176, 177, 219, 94, 95,
+ /* 1030 */ 96, 86, 87, 99, 155, 118, 155, 106, 110, 94,
+ /* 1040 */ 95, 96, 155, 39, 99, 178, 155, 26, 131, 195,
+ /* 1050 */ 155, 30, 118, 155, 155, 176, 177, 176, 177, 38,
+ /* 1060 */ 27, 155, 29, 176, 177, 51, 132, 133, 134, 135,
+ /* 1070 */ 136, 137, 138, 155, 176, 177, 55, 132, 133, 134,
+ /* 1080 */ 135, 136, 137, 138, 139, 194, 65, 178, 189, 194,
+ /* 1090 */ 69, 155, 47, 179, 176, 177, 186, 155, 155, 144,
+ /* 1100 */ 194, 155, 120, 155, 155, 155, 26, 86, 87, 88,
+ /* 1110 */ 30, 155, 176, 177, 155, 94, 95, 96, 38, 27,
+ /* 1120 */ 99, 29, 176, 177, 176, 177, 176, 177, 118, 155,
+ /* 1130 */ 155, 232, 176, 177, 155, 55, 194, 194, 189, 155,
+ /* 1140 */ 178, 131, 155, 105, 180, 65, 178, 162, 26, 69,
+ /* 1150 */ 176, 177, 155, 132, 133, 134, 135, 136, 137, 138,
+ /* 1160 */ 176, 177, 155, 176, 177, 11, 86, 87, 189, 194,
+ /* 1170 */ 155, 120, 120, 155, 94, 95, 96, 155, 219, 99,
+ /* 1180 */ 195, 232, 15, 176, 177, 155, 189, 20, 21, 22,
+ /* 1190 */ 23, 176, 177, 197, 176, 177, 196, 118, 176, 177,
+ /* 1200 */ 33, 195, 35, 155, 199, 51, 176, 177, 197, 42,
+ /* 1210 */ 155, 232, 132, 133, 134, 135, 136, 137, 138, 155,
+ /* 1220 */ 129, 54, 155, 56, 176, 177, 200, 126, 61, 232,
+ /* 1230 */ 63, 176, 177, 66, 127, 201, 124, 202, 128, 203,
+ /* 1240 */ 176, 177, 155, 176, 177, 204, 120, 120, 155, 26,
+ /* 1250 */ 168, 27, 27, 234, 234, 91, 99, 155, 118, 118,
+ /* 1260 */ 118, 26, 139, 21, 26, 228, 193, 27, 229, 155,
+ /* 1270 */ 29, 27, 120, 125, 107, 108, 109, 159, 29, 155,
+ /* 1280 */ 113, 104, 217, 179, 30, 118, 167, 217, 179, 29,
+ /* 1290 */ 125, 155, 209, 155, 122, 106, 159, 123, 155, 155,
+ /* 1300 */ 210, 26, 155, 27, 120, 211, 210, 27, 211, 178,
+ /* 1310 */ 155, 26, 182, 181, 155, 217, 217, 184, 167, 185,
+ /* 1320 */ 185, 155, 51, 26, 185, 179, 176, 27, 179, 176,
+ /* 1330 */ 26, 186, 51, 26, 103, 155, 155, 111, 159, 178,
+ /* 1340 */ 180, 178, 155, 181, 188, 159, 188, 28, 155, 159,
+ /* 1350 */ 182, 238, 237, 106, 159, 45, 239, 15, 43, 106,
+ /* 1360 */ 240, 106, 142, 52, 155, 159, 155, 106, 11, 26,
+ /* 1370 */ 178, 142, 21, 15, 191, 130, 130, 11, 11, 67,
+ /* 1380 */ 21, 76, 191, 155, 110, 200, 11, 155, 130, 76,
+ /* 1390 */ 26, 155, 221, 11, 26, 11, 200, 11, 121, 11,
+ /* 1400 */ 11, 11, 200, 155, 121, 11, 191, 200, 110, 11,
+ /* 1410 */ 11, 26, 130, 11, 155, 221, 159, 155, 11, 11,
+ /* 1420 */ 11, 11, 155, 27, 11, 28, 11, 40, 155, 11,
+ /* 1430 */ 242, 168, 168, 11, 155, 11, 11, 159, 11, 155,
+ /* 1440 */ 243, 242, 155, 24, 143, 159, 155, 11, 145, 245,
+ /* 1450 */ 144, 11, 13, 246, 246, 14,
+};
+#define YY_SHIFT_USE_DFLT (-75)
+static const short yy_shift_ofst[] = {
+ /* 0 */ 205, 7, -75, -75, 1167, -10, 57, -75, 47, 475,
+ /* 10 */ 399, 123, 337, -75, -75, -75, -75, -75, -75, 475,
+ /* 20 */ 446, 475, 543, 475, 757, 456, 858, 453, 240, 799,
+ /* 30 */ 865, 50, -75, 254, -75, -16, -75, 453, 151, -75,
+ /* 40 */ 931, -75, 1004, 306, -75, -75, -75, -75, -75, -75,
+ /* 50 */ -75, 180, 931, -75, 1045, -75, 278, -75, -75, 1014,
+ /* 60 */ 689, 931, 1038, -75, -75, -75, -75, 931, -75, 1122,
+ /* 70 */ 1080, 652, 473, -75, -75, 1080, 1051, 1052, -75, 934,
+ /* 80 */ -75, 302, 1079, -75, 650, -75, 641, 1091, 1101, 1107,
+ /* 90 */ 1112, 1110, -75, 1080, 40, 1080, 714, 1080, -75, 1126,
+ /* 100 */ 453, 1127, 453, -75, -75, -75, -75, -75, -75, 1223,
+ /* 110 */ 1080, 108, 254, -75, -75, 455, 321, 595, -75, 321,
+ /* 120 */ 1224, -75, -75, -75, 638, -75, -75, -75, 638, -75,
+ /* 130 */ -75, -75, -75, 1225, -75, 1080, -75, 814, 1080, -12,
+ /* 140 */ 1080, -12, 1080, -12, 1080, -12, 1080, -74, 1080, -74,
+ /* 150 */ 1080, -53, 1080, -53, 1080, -53, 1080, -53, 1080, 242,
+ /* 160 */ 1080, 242, 1080, 1164, 1080, 1164, 1080, 1164, 1080, -75,
+ /* 170 */ -75, 298, -75, -75, -75, -75, 1080, -59, 1080, -12,
+ /* 180 */ -75, 893, -75, 1157, -75, -75, -75, 1080, 764, 1080,
+ /* 190 */ -74, -75, 325, 934, 359, 419, 1140, 1141, 1142, -75,
+ /* 200 */ 714, 1080, 864, 1080, -75, 1080, -75, 1080, -75, 1235,
+ /* 210 */ 1079, 376, -75, 945, 158, 1123, 320, 1242, -75, 1080,
+ /* 220 */ 231, 1080, 714, 1238, 443, 1240, -75, 1241, 453, 1244,
+ /* 230 */ -75, 1080, 305, 1080, 355, 1080, 714, 780, -75, 1080,
+ /* 240 */ -75, -75, 1152, 453, -75, -75, -75, 864, 1080, 714,
+ /* 250 */ 1148, 1080, 1249, 1080, 1177, 689, -75, 1254, -75, -75,
+ /* 260 */ 714, 1177, 689, -75, 1080, 714, 1165, 1080, 1260, 1080,
+ /* 270 */ 714, -75, -75, 239, -75, -75, -75, 430, -75, 433,
+ /* 280 */ -75, 1172, -75, 501, 1152, 144, 453, -75, -75, 1189,
+ /* 290 */ 1174, -75, 1275, 453, 783, -75, 453, -75, -75, 1080,
+ /* 300 */ 714, 1079, 422, 425, 1276, 144, 1189, 1174, -75, 1021,
+ /* 310 */ -28, -75, -75, 1184, 73, -75, -75, 429, -75, 792,
+ /* 320 */ -75, 1280, -75, 496, 931, -75, 453, 1285, -75, 42,
+ /* 330 */ -75, 453, -75, 550, 928, -75, 716, -75, -75, -75,
+ /* 340 */ -75, 928, -75, 928, -75, 453, 793, -75, 453, 1177,
+ /* 350 */ 689, -75, -75, 1177, 689, -75, -75, 1254, -75, 1045,
+ /* 360 */ -75, -75, 68, -75, 1080, 564, -75, 191, -75, -75,
+ /* 370 */ 191, -75, -75, -75, -75, 176, 256, -75, 453, -75,
+ /* 380 */ 1271, 1297, 453, 260, 1300, 931, -75, 1304, 453, 796,
+ /* 390 */ 931, -75, 1080, 614, -75, 1281, 1307, 453, 833, 1231,
+ /* 400 */ 453, 1285, -75, 493, 1226, -75, -75, -75, -75, -75,
+ /* 410 */ 1079, 513, 856, 391, 453, 1152, -75, 453, 745, 1319,
+ /* 420 */ 1079, 548, 453, 1152, 510, 565, 1247, 453, 1152, -75,
+ /* 430 */ 1310, 297, 1342, 1080, 664, 1315, 813, -75, -75, 1253,
+ /* 440 */ 1255, 809, 453, 882, -75, -75, 1311, -75, -75, 1220,
+ /* 450 */ 453, 862, 1261, 453, 1343, 453, 892, 907, 1357, 1229,
+ /* 460 */ 1358, 152, 592, 894, 306, -75, 1245, 1246, 1351, 1366,
+ /* 470 */ 1367, 152, 1359, 1312, 453, 1274, 453, 746, 453, 1305,
+ /* 480 */ 1080, 714, 1375, 1313, 1080, 714, 1258, 453, 1364, 453,
+ /* 490 */ 947, -75, 917, 598, 1368, 1080, 1033, 1080, 714, 1382,
+ /* 500 */ 714, 1277, 453, 9, 1384, 470, 453, 1386, 453, 1388,
+ /* 510 */ 453, 1389, 453, 1390, 605, 1283, 453, 9, 1394, 1312,
+ /* 520 */ 453, 1298, 453, 746, 1398, 1282, 453, 1364, 1010, 610,
+ /* 530 */ 1385, 1080, 1092, 1399, 530, 1402, 453, 1152, 649, 179,
+ /* 540 */ 1407, 1408, 1409, 1410, 453, 1396, 1413, 1387, 254, 1397,
+ /* 550 */ 453, 1154, 1415, 778, 1418, 1422, -75, 1387, 453, 1424,
+ /* 560 */ 665, 982, 1425, 734, 982, 1427, 1419, 453, 955, 1301,
+ /* 570 */ 453, 1436, 1306, 1303, 453, 1440, -75, 1439, 1441, -75,
+ /* 580 */ -75,
+};
+#define YY_REDUCE_USE_DFLT (-61)
+static const short yy_reduce_ofst[] = {
+ /* 0 */ -48, 61, -61, -61, -60, -61, -61, -61, -32, 12,
+ /* 10 */ -61, 181, -61, -61, -61, -61, -61, -61, -61, 314,
+ /* 20 */ -61, 386, -61, 622, -61, 654, -61, 51, 798, -61,
+ /* 30 */ -61, -23, -61, -26, 760, 89, -61, 860, 486, -61,
+ /* 40 */ 867, -61, -61, 65, -61, -61, -61, -61, -61, -61,
+ /* 50 */ -61, -61, 909, -61, 910, -61, -61, -61, -61, -61,
+ /* 60 */ 914, 962, 964, -61, -61, -61, -61, 968, -61, -61,
+ /* 70 */ 438, -61, 996, -61, -61, 116, -61, -61, -61, 293,
+ /* 80 */ -61, 1000, 1006, -61, 1011, 569, 1005, 1026, 1034, 1035,
+ /* 90 */ 1036, 1041, -61, 490, 394, 552, 394, 601, -61, -61,
+ /* 100 */ 1087, -61, 1093, -61, -61, -61, -61, -61, -61, -61,
+ /* 110 */ 603, 394, 53, -61, -61, 1082, 110, -61, -61, 229,
+ /* 120 */ -61, -61, -61, -61, 1019, -61, -61, -61, 1020, -61,
+ /* 130 */ -61, -61, -61, -61, -61, 640, -61, 394, 651, 394,
+ /* 140 */ 690, 394, 701, 394, 703, 394, 740, 394, 751, 394,
+ /* 150 */ 753, 394, 755, 394, 790, 394, 801, 394, 803, 394,
+ /* 160 */ 818, 394, 829, 394, 831, 394, 835, 394, 841, 394,
+ /* 170 */ -61, -61, -61, -61, -61, -61, 846, 188, 849, 394,
+ /* 180 */ -61, -61, -61, -61, -61, -61, -61, 879, 394, 881,
+ /* 190 */ 394, -61, 1102, -6, 1000, -61, -61, -61, -61, -61,
+ /* 200 */ 394, 887, 394, 898, 394, 918, 394, 936, 394, -61,
+ /* 210 */ 662, 1000, -61, 295, 394, 1037, 1039, -61, -61, 946,
+ /* 220 */ 394, 948, 394, -61, 1073, -61, -61, -61, 1114, -61,
+ /* 230 */ -61, 950, 394, 956, 394, 974, 394, -61, -61, 303,
+ /* 240 */ -61, -61, 1118, 1124, -61, -61, -61, 394, 984, 394,
+ /* 250 */ -61, 183, -61, 190, 1065, 1104, -61, 1119, -61, -61,
+ /* 260 */ 394, 1070, 1109, -61, 987, 394, -61, 187, -61, 1007,
+ /* 270 */ 394, -61, 398, 1083, -61, -61, -61, 1136, -61, 1138,
+ /* 280 */ -61, -61, -61, 1143, 1137, 389, 1144, -61, -61, 1090,
+ /* 290 */ 1094, -61, -61, 604, -61, -61, 1147, -61, -61, 1015,
+ /* 300 */ 394, 132, 1000, 1083, -61, 512, 1096, 1097, -61, 1018,
+ /* 310 */ 241, -61, -61, -61, 1087, -61, -61, 394, -61, -61,
+ /* 320 */ -61, -61, -61, 394, 1131, -61, 1155, 1132, 1130, 1133,
+ /* 330 */ -61, 1159, -61, -61, 1134, -61, -61, -61, -61, -61,
+ /* 340 */ -61, 1135, -61, 1139, -61, 439, -61, -61, 765, 1098,
+ /* 350 */ 1146, -61, -61, 1099, 1149, -61, -61, 1151, -61, 1145,
+ /* 360 */ -61, -61, 692, -61, 1022, 394, -61, 1150, -61, -61,
+ /* 370 */ 1153, -61, -61, -61, -61, 1156, 1158, -61, 1166, -61,
+ /* 380 */ -61, -61, 899, 1160, -61, 1161, -61, -61, 949, -61,
+ /* 390 */ 1163, -61, 1030, 375, -61, -61, -61, 979, -61, -61,
+ /* 400 */ 1180, 1162, 1168, 527, -61, -61, -61, -61, -61, -61,
+ /* 410 */ 712, 1000, 756, -61, 1181, 1179, -61, 1187, 1186, -61,
+ /* 420 */ 807, 1000, 1193, 1190, 1115, 1113, -61, 715, 1195, -61,
+ /* 430 */ 1117, 1120, -61, 1048, 394, -61, -61, -61, -61, -61,
+ /* 440 */ -61, -61, 709, -61, -61, -61, -61, -61, -61, -61,
+ /* 450 */ 1209, 1206, -61, 1211, -61, 997, -61, 1192, -61, -61,
+ /* 460 */ -61, 315, 1000, 1183, 732, -61, -61, -61, -61, -61,
+ /* 470 */ -61, 382, -61, 1191, 1228, -61, 808, 1185, 1232, -61,
+ /* 480 */ 1055, 394, -61, -61, 1064, 394, -61, 1236, 1171, 767,
+ /* 490 */ -61, -61, 854, 1000, -61, -18, -61, 1067, 394, -61,
+ /* 500 */ 394, -61, 1248, 1196, -61, -61, 815, -61, 891, -61,
+ /* 510 */ 895, -61, 906, -61, 1000, -61, 942, 1202, -61, 1215,
+ /* 520 */ 943, -61, 959, 1207, -61, -61, 975, 1194, 985, 1000,
+ /* 530 */ -61, 434, -61, -61, 1259, -61, 1262, 1257, -61, 435,
+ /* 540 */ -61, -61, -61, -61, 1267, -61, -61, 1188, 1263, -61,
+ /* 550 */ 1273, 1197, -61, 1264, -61, -61, -61, 1199, 1279, -61,
+ /* 560 */ 1284, 1278, -61, 1287, 1286, -61, -61, 522, -61, -61,
+ /* 570 */ 1291, -61, -61, 1204, -58, -61, -61, -61, -61, -61,
+ /* 580 */ -61,
+};
+static const YYACTIONTYPE yy_default[] = {
+ /* 0 */ 587, 587, 582, 585, 892, 892, 892, 586, 594, 892,
+ /* 10 */ 892, 892, 892, 614, 615, 616, 595, 596, 597, 892,
+ /* 20 */ 892, 892, 892, 892, 892, 892, 892, 892, 892, 892,
+ /* 30 */ 892, 892, 607, 617, 627, 609, 626, 892, 892, 628,
+ /* 40 */ 672, 635, 892, 892, 673, 676, 677, 678, 872, 873,
+ /* 50 */ 874, 892, 672, 636, 657, 655, 892, 658, 659, 892,
+ /* 60 */ 728, 672, 643, 637, 644, 726, 727, 672, 638, 892,
+ /* 70 */ 892, 758, 692, 690, 691, 824, 764, 759, 755, 892,
+ /* 80 */ 683, 892, 892, 684, 692, 694, 701, 740, 731, 733,
+ /* 90 */ 721, 735, 689, 892, 736, 892, 737, 892, 757, 892,
+ /* 100 */ 892, 760, 892, 761, 762, 763, 765, 766, 767, 892,
+ /* 110 */ 892, 892, 892, 612, 613, 619, 847, 892, 620, 847,
+ /* 120 */ 892, 621, 624, 625, 892, 842, 844, 845, 892, 843,
+ /* 130 */ 846, 623, 622, 892, 768, 892, 771, 773, 892, 774,
+ /* 140 */ 892, 775, 892, 776, 892, 777, 892, 778, 892, 779,
+ /* 150 */ 892, 780, 892, 781, 892, 782, 892, 783, 892, 784,
+ /* 160 */ 892, 785, 892, 786, 892, 787, 892, 788, 892, 789,
+ /* 170 */ 790, 892, 791, 798, 805, 808, 892, 793, 892, 792,
+ /* 180 */ 795, 892, 796, 892, 799, 797, 804, 892, 892, 892,
+ /* 190 */ 806, 807, 892, 824, 892, 892, 892, 892, 892, 811,
+ /* 200 */ 823, 892, 800, 892, 801, 892, 802, 892, 803, 892,
+ /* 210 */ 892, 892, 813, 892, 892, 892, 892, 892, 814, 892,
+ /* 220 */ 892, 892, 815, 892, 892, 892, 870, 892, 892, 892,
+ /* 230 */ 871, 892, 892, 892, 892, 892, 816, 892, 809, 824,
+ /* 240 */ 821, 822, 709, 892, 710, 812, 794, 772, 892, 738,
+ /* 250 */ 892, 892, 722, 892, 729, 728, 723, 892, 611, 730,
+ /* 260 */ 725, 729, 728, 724, 892, 734, 892, 824, 732, 892,
+ /* 270 */ 741, 693, 704, 702, 703, 712, 713, 892, 714, 892,
+ /* 280 */ 715, 892, 716, 892, 709, 700, 892, 698, 699, 718,
+ /* 290 */ 720, 705, 892, 892, 892, 719, 892, 753, 754, 892,
+ /* 300 */ 717, 704, 892, 892, 892, 700, 718, 720, 706, 892,
+ /* 310 */ 700, 695, 696, 892, 892, 697, 810, 892, 756, 892,
+ /* 320 */ 769, 892, 770, 892, 672, 639, 892, 828, 645, 640,
+ /* 330 */ 646, 892, 647, 892, 892, 648, 892, 651, 652, 653,
+ /* 340 */ 654, 892, 649, 892, 650, 892, 892, 829, 892, 729,
+ /* 350 */ 728, 830, 832, 729, 728, 831, 641, 892, 642, 657,
+ /* 360 */ 656, 629, 892, 630, 892, 892, 631, 892, 632, 764,
+ /* 370 */ 892, 633, 634, 618, 610, 892, 892, 662, 892, 665,
+ /* 380 */ 892, 892, 892, 892, 892, 672, 666, 892, 892, 892,
+ /* 390 */ 672, 667, 892, 672, 668, 892, 892, 892, 892, 892,
+ /* 400 */ 892, 828, 645, 670, 892, 669, 671, 663, 664, 608,
+ /* 410 */ 892, 892, 604, 892, 892, 709, 602, 892, 892, 892,
+ /* 420 */ 892, 892, 892, 709, 853, 892, 892, 892, 709, 711,
+ /* 430 */ 858, 892, 892, 892, 892, 892, 892, 859, 860, 892,
+ /* 440 */ 892, 892, 892, 892, 850, 851, 892, 852, 603, 892,
+ /* 450 */ 892, 892, 892, 892, 892, 892, 892, 892, 892, 892,
+ /* 460 */ 892, 892, 892, 892, 892, 675, 892, 892, 892, 892,
+ /* 470 */ 892, 892, 892, 674, 892, 892, 892, 892, 892, 892,
+ /* 480 */ 892, 743, 892, 892, 892, 744, 892, 892, 751, 892,
+ /* 490 */ 892, 752, 892, 892, 892, 892, 892, 892, 749, 892,
+ /* 500 */ 750, 892, 892, 892, 892, 892, 892, 892, 892, 892,
+ /* 510 */ 892, 892, 892, 892, 892, 892, 892, 892, 892, 674,
+ /* 520 */ 892, 892, 892, 892, 892, 892, 892, 751, 892, 892,
+ /* 530 */ 892, 892, 892, 892, 892, 892, 892, 709, 892, 847,
+ /* 540 */ 892, 892, 892, 892, 892, 892, 892, 881, 892, 892,
+ /* 550 */ 892, 892, 892, 892, 892, 892, 880, 881, 892, 892,
+ /* 560 */ 892, 892, 892, 892, 892, 892, 892, 892, 892, 892,
+ /* 570 */ 892, 892, 892, 890, 892, 892, 891, 588, 892, 589,
+ /* 580 */ 583,
+};
+#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, /* AGG_COLUMN => nothing */
+ 0, /* CONST_FUNC => nothing */
+ 0, /* SEMI => nothing */
+ 30, /* EXPLAIN => ID */
+ 30, /* QUERY => ID */
+ 30, /* PLAN => ID */
+ 30, /* BEGIN => ID */
+ 0, /* TRANSACTION => nothing */
+ 30, /* DEFERRED => ID */
+ 30, /* IMMEDIATE => ID */
+ 30, /* EXCLUSIVE => ID */
+ 0, /* COMMIT => nothing */
+ 30, /* END => ID */
+ 0, /* ROLLBACK => nothing */
+ 0, /* CREATE => nothing */
+ 0, /* TABLE => nothing */
+ 30, /* TEMP => ID */
+ 0, /* LP => nothing */
+ 0, /* RP => nothing */
+ 0, /* AS => nothing */
+ 0, /* COMMA => nothing */
+ 0, /* ID => nothing */
+ 30, /* ABORT => ID */
+ 30, /* AFTER => ID */
+ 30, /* ANALYZE => ID */
+ 30, /* ASC => ID */
+ 30, /* ATTACH => ID */
+ 30, /* BEFORE => ID */
+ 30, /* CASCADE => ID */
+ 30, /* CAST => ID */
+ 30, /* CONFLICT => ID */
+ 30, /* DATABASE => ID */
+ 30, /* DESC => ID */
+ 30, /* DETACH => ID */
+ 30, /* EACH => ID */
+ 30, /* FAIL => ID */
+ 30, /* FOR => ID */
+ 30, /* IGNORE => ID */
+ 30, /* INITIALLY => ID */
+ 30, /* INSTEAD => ID */
+ 30, /* LIKE_KW => ID */
+ 30, /* MATCH => ID */
+ 30, /* KEY => ID */
+ 30, /* OF => ID */
+ 30, /* OFFSET => ID */
+ 30, /* PRAGMA => ID */
+ 30, /* RAISE => ID */
+ 30, /* REPLACE => ID */
+ 30, /* RESTRICT => ID */
+ 30, /* ROW => ID */
+ 30, /* STATEMENT => ID */
+ 30, /* TRIGGER => ID */
+ 30, /* VACUUM => ID */
+ 30, /* VIEW => ID */
+ 30, /* REINDEX => ID */
+ 30, /* RENAME => ID */
+ 30, /* CTIME_KW => ID */
+ 30, /* ALTER => ID */
+ 0, /* OR => nothing */
+ 0, /* AND => nothing */
+ 0, /* NOT => 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, /* 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, /* COLLATE => 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, /* 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, /* INTEGER => nothing */
+ 0, /* FLOAT => nothing */
+ 0, /* BLOB => nothing */
+ 0, /* REGISTER => nothing */
+ 0, /* VARIABLE => nothing */
+ 0, /* EXISTS => nothing */
+ 0, /* CASE => nothing */
+ 0, /* WHEN => nothing */
+ 0, /* THEN => nothing */
+ 0, /* ELSE => nothing */
+ 0, /* INDEX => 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 */
+ 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 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[] = {
+ "$", "END_OF_FILE", "ILLEGAL", "SPACE",
+ "UNCLOSED_STRING", "COMMENT", "FUNCTION", "COLUMN",
+ "AGG_FUNCTION", "AGG_COLUMN", "CONST_FUNC", "SEMI",
+ "EXPLAIN", "QUERY", "PLAN", "BEGIN",
+ "TRANSACTION", "DEFERRED", "IMMEDIATE", "EXCLUSIVE",
+ "COMMIT", "END", "ROLLBACK", "CREATE",
+ "TABLE", "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", "STATEMENT",
+ "TRIGGER", "VACUUM", "VIEW", "REINDEX",
+ "RENAME", "CTIME_KW", "ALTER", "OR",
+ "AND", "NOT", "IS", "BETWEEN",
+ "IN", "ISNULL", "NOTNULL", "NE",
+ "EQ", "GT", "LE", "LT",
+ "GE", "ESCAPE", "BITAND", "BITOR",
+ "LSHIFT", "RSHIFT", "PLUS", "MINUS",
+ "STAR", "SLASH", "REM", "CONCAT",
+ "UMINUS", "UPLUS", "BITNOT", "STRING",
+ "JOIN_KW", "CONSTRAINT", "DEFAULT", "NULL",
+ "PRIMARY", "UNIQUE", "CHECK", "REFERENCES",
+ "COLLATE", "AUTOINCR", "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",
+ "INTEGER", "FLOAT", "BLOB", "REGISTER",
+ "VARIABLE", "EXISTS", "CASE", "WHEN",
+ "THEN", "ELSE", "INDEX", "TO",
+ "ADD", "COLUMNKW", "error", "input",
+ "cmdlist", "ecmd", "cmdx", "cmd",
+ "explain", "transtype", "trans_opt", "nm",
+ "create_table", "create_table_args", "temp", "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", "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", "collate", "exprlist", "setlist",
+ "insert_cmd", "inscollist_opt", "itemlist", "likeop",
+ "escape", "between_op", "in_op", "case_operand",
+ "case_exprlist", "case_else", "expritem", "uniqueflag",
+ "idxitem", "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",
+};
+#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 nm dbnm",
+ /* 22 */ "temp ::= TEMP",
+ /* 23 */ "temp ::=",
+ /* 24 */ "create_table_args ::= LP columnlist conslist_opt RP",
+ /* 25 */ "create_table_args ::= AS select",
+ /* 26 */ "columnlist ::= columnlist COMMA column",
+ /* 27 */ "columnlist ::= column",
+ /* 28 */ "column ::= columnid type carglist",
+ /* 29 */ "columnid ::= nm",
+ /* 30 */ "id ::= ID",
+ /* 31 */ "ids ::= ID",
+ /* 32 */ "ids ::= STRING",
+ /* 33 */ "nm ::= ID",
+ /* 34 */ "nm ::= STRING",
+ /* 35 */ "nm ::= JOIN_KW",
+ /* 36 */ "type ::=",
+ /* 37 */ "type ::= typetoken",
+ /* 38 */ "typetoken ::= typename",
+ /* 39 */ "typetoken ::= typename LP signed RP",
+ /* 40 */ "typetoken ::= typename LP signed COMMA signed RP",
+ /* 41 */ "typename ::= ids",
+ /* 42 */ "typename ::= typename ids",
+ /* 43 */ "signed ::= plus_num",
+ /* 44 */ "signed ::= minus_num",
+ /* 45 */ "carglist ::= carglist carg",
+ /* 46 */ "carglist ::=",
+ /* 47 */ "carg ::= CONSTRAINT nm ccons",
+ /* 48 */ "carg ::= ccons",
+ /* 49 */ "carg ::= DEFAULT term",
+ /* 50 */ "carg ::= DEFAULT LP expr RP",
+ /* 51 */ "carg ::= DEFAULT PLUS term",
+ /* 52 */ "carg ::= DEFAULT MINUS term",
+ /* 53 */ "carg ::= DEFAULT id",
+ /* 54 */ "ccons ::= NULL onconf",
+ /* 55 */ "ccons ::= NOT NULL onconf",
+ /* 56 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc",
+ /* 57 */ "ccons ::= UNIQUE onconf",
+ /* 58 */ "ccons ::= CHECK LP expr RP onconf",
+ /* 59 */ "ccons ::= REFERENCES nm idxlist_opt refargs",
+ /* 60 */ "ccons ::= defer_subclause",
+ /* 61 */ "ccons ::= COLLATE id",
+ /* 62 */ "autoinc ::=",
+ /* 63 */ "autoinc ::= AUTOINCR",
+ /* 64 */ "refargs ::=",
+ /* 65 */ "refargs ::= refargs refarg",
+ /* 66 */ "refarg ::= MATCH nm",
+ /* 67 */ "refarg ::= ON DELETE refact",
+ /* 68 */ "refarg ::= ON UPDATE refact",
+ /* 69 */ "refarg ::= ON INSERT refact",
+ /* 70 */ "refact ::= SET NULL",
+ /* 71 */ "refact ::= SET DEFAULT",
+ /* 72 */ "refact ::= CASCADE",
+ /* 73 */ "refact ::= RESTRICT",
+ /* 74 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt",
+ /* 75 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt",
+ /* 76 */ "init_deferred_pred_opt ::=",
+ /* 77 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED",
+ /* 78 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE",
+ /* 79 */ "conslist_opt ::=",
+ /* 80 */ "conslist_opt ::= COMMA conslist",
+ /* 81 */ "conslist ::= conslist COMMA tcons",
+ /* 82 */ "conslist ::= conslist tcons",
+ /* 83 */ "conslist ::= tcons",
+ /* 84 */ "tcons ::= CONSTRAINT nm",
+ /* 85 */ "tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf",
+ /* 86 */ "tcons ::= UNIQUE LP idxlist RP onconf",
+ /* 87 */ "tcons ::= CHECK expr onconf",
+ /* 88 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt",
+ /* 89 */ "defer_subclause_opt ::=",
+ /* 90 */ "defer_subclause_opt ::= defer_subclause",
+ /* 91 */ "onconf ::=",
+ /* 92 */ "onconf ::= ON CONFLICT resolvetype",
+ /* 93 */ "orconf ::=",
+ /* 94 */ "orconf ::= OR resolvetype",
+ /* 95 */ "resolvetype ::= raisetype",
+ /* 96 */ "resolvetype ::= IGNORE",
+ /* 97 */ "resolvetype ::= REPLACE",
+ /* 98 */ "cmd ::= DROP TABLE fullname",
+ /* 99 */ "cmd ::= CREATE temp VIEW nm dbnm AS select",
+ /* 100 */ "cmd ::= DROP VIEW fullname",
+ /* 101 */ "cmd ::= select",
+ /* 102 */ "select ::= oneselect",
+ /* 103 */ "select ::= select multiselect_op oneselect",
+ /* 104 */ "multiselect_op ::= UNION",
+ /* 105 */ "multiselect_op ::= UNION ALL",
+ /* 106 */ "multiselect_op ::= INTERSECT",
+ /* 107 */ "multiselect_op ::= EXCEPT",
+ /* 108 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt",
+ /* 109 */ "distinct ::= DISTINCT",
+ /* 110 */ "distinct ::= ALL",
+ /* 111 */ "distinct ::=",
+ /* 112 */ "sclp ::= selcollist COMMA",
+ /* 113 */ "sclp ::=",
+ /* 114 */ "selcollist ::= sclp expr as",
+ /* 115 */ "selcollist ::= sclp STAR",
+ /* 116 */ "selcollist ::= sclp nm DOT STAR",
+ /* 117 */ "as ::= AS nm",
+ /* 118 */ "as ::= ids",
+ /* 119 */ "as ::=",
+ /* 120 */ "from ::=",
+ /* 121 */ "from ::= FROM seltablist",
+ /* 122 */ "stl_prefix ::= seltablist joinop",
+ /* 123 */ "stl_prefix ::=",
+ /* 124 */ "seltablist ::= stl_prefix nm dbnm as on_opt using_opt",
+ /* 125 */ "seltablist ::= stl_prefix LP seltablist_paren RP as on_opt using_opt",
+ /* 126 */ "seltablist_paren ::= select",
+ /* 127 */ "seltablist_paren ::= seltablist",
+ /* 128 */ "dbnm ::=",
+ /* 129 */ "dbnm ::= DOT nm",
+ /* 130 */ "fullname ::= nm dbnm",
+ /* 131 */ "joinop ::= COMMA",
+ /* 132 */ "joinop ::= JOIN",
+ /* 133 */ "joinop ::= JOIN_KW JOIN",
+ /* 134 */ "joinop ::= JOIN_KW nm JOIN",
+ /* 135 */ "joinop ::= JOIN_KW nm nm JOIN",
+ /* 136 */ "on_opt ::= ON expr",
+ /* 137 */ "on_opt ::=",
+ /* 138 */ "using_opt ::= USING LP inscollist RP",
+ /* 139 */ "using_opt ::=",
+ /* 140 */ "orderby_opt ::=",
+ /* 141 */ "orderby_opt ::= ORDER BY sortlist",
+ /* 142 */ "sortlist ::= sortlist COMMA sortitem collate sortorder",
+ /* 143 */ "sortlist ::= sortitem collate sortorder",
+ /* 144 */ "sortitem ::= expr",
+ /* 145 */ "sortorder ::= ASC",
+ /* 146 */ "sortorder ::= DESC",
+ /* 147 */ "sortorder ::=",
+ /* 148 */ "collate ::=",
+ /* 149 */ "collate ::= COLLATE id",
+ /* 150 */ "groupby_opt ::=",
+ /* 151 */ "groupby_opt ::= GROUP BY exprlist",
+ /* 152 */ "having_opt ::=",
+ /* 153 */ "having_opt ::= HAVING expr",
+ /* 154 */ "limit_opt ::=",
+ /* 155 */ "limit_opt ::= LIMIT expr",
+ /* 156 */ "limit_opt ::= LIMIT expr OFFSET expr",
+ /* 157 */ "limit_opt ::= LIMIT expr COMMA expr",
+ /* 158 */ "cmd ::= DELETE FROM fullname where_opt",
+ /* 159 */ "where_opt ::=",
+ /* 160 */ "where_opt ::= WHERE expr",
+ /* 161 */ "cmd ::= UPDATE orconf fullname SET setlist where_opt",
+ /* 162 */ "setlist ::= setlist COMMA nm EQ expr",
+ /* 163 */ "setlist ::= nm EQ expr",
+ /* 164 */ "cmd ::= insert_cmd INTO fullname inscollist_opt VALUES LP itemlist RP",
+ /* 165 */ "cmd ::= insert_cmd INTO fullname inscollist_opt select",
+ /* 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",
+ /* 182 */ "term ::= FLOAT",
+ /* 183 */ "term ::= STRING",
+ /* 184 */ "term ::= BLOB",
+ /* 185 */ "expr ::= REGISTER",
+ /* 186 */ "expr ::= VARIABLE",
+ /* 187 */ "expr ::= CAST LP expr AS typetoken RP",
+ /* 188 */ "expr ::= ID LP distinct exprlist RP",
+ /* 189 */ "expr ::= ID LP STAR RP",
+ /* 190 */ "term ::= CTIME_KW",
+ /* 191 */ "expr ::= expr AND expr",
+ /* 192 */ "expr ::= expr OR expr",
+ /* 193 */ "expr ::= expr LT expr",
+ /* 194 */ "expr ::= expr GT expr",
+ /* 195 */ "expr ::= expr LE expr",
+ /* 196 */ "expr ::= expr GE expr",
+ /* 197 */ "expr ::= expr NE expr",
+ /* 198 */ "expr ::= expr EQ expr",
+ /* 199 */ "expr ::= expr BITAND expr",
+ /* 200 */ "expr ::= expr BITOR expr",
+ /* 201 */ "expr ::= expr LSHIFT expr",
+ /* 202 */ "expr ::= expr RSHIFT expr",
+ /* 203 */ "expr ::= expr PLUS expr",
+ /* 204 */ "expr ::= expr MINUS expr",
+ /* 205 */ "expr ::= expr STAR expr",
+ /* 206 */ "expr ::= expr SLASH expr",
+ /* 207 */ "expr ::= expr REM expr",
+ /* 208 */ "expr ::= expr CONCAT expr",
+ /* 209 */ "likeop ::= LIKE_KW",
+ /* 210 */ "likeop ::= NOT LIKE_KW",
+ /* 211 */ "escape ::= ESCAPE expr",
+ /* 212 */ "escape ::=",
+ /* 213 */ "expr ::= expr likeop expr escape",
+ /* 214 */ "expr ::= expr ISNULL",
+ /* 215 */ "expr ::= expr IS NULL",
+ /* 216 */ "expr ::= expr NOTNULL",
+ /* 217 */ "expr ::= expr NOT NULL",
+ /* 218 */ "expr ::= expr IS NOT NULL",
+ /* 219 */ "expr ::= NOT expr",
+ /* 220 */ "expr ::= BITNOT expr",
+ /* 221 */ "expr ::= MINUS expr",
+ /* 222 */ "expr ::= PLUS expr",
+ /* 223 */ "between_op ::= BETWEEN",
+ /* 224 */ "between_op ::= NOT BETWEEN",
+ /* 225 */ "expr ::= expr between_op expr AND expr",
+ /* 226 */ "in_op ::= IN",
+ /* 227 */ "in_op ::= NOT IN",
+ /* 228 */ "expr ::= expr in_op LP exprlist RP",
+ /* 229 */ "expr ::= LP select RP",
+ /* 230 */ "expr ::= expr in_op LP select RP",
+ /* 231 */ "expr ::= expr in_op nm dbnm",
+ /* 232 */ "expr ::= EXISTS LP select RP",
+ /* 233 */ "expr ::= CASE case_operand case_exprlist case_else END",
+ /* 234 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
+ /* 235 */ "case_exprlist ::= WHEN expr THEN expr",
+ /* 236 */ "case_else ::= ELSE expr",
+ /* 237 */ "case_else ::=",
+ /* 238 */ "case_operand ::= expr",
+ /* 239 */ "case_operand ::=",
+ /* 240 */ "exprlist ::= exprlist COMMA expritem",
+ /* 241 */ "exprlist ::= expritem",
+ /* 242 */ "expritem ::= expr",
+ /* 243 */ "expritem ::=",
+ /* 244 */ "cmd ::= CREATE uniqueflag INDEX nm dbnm ON nm LP idxlist RP onconf",
+ /* 245 */ "uniqueflag ::= UNIQUE",
+ /* 246 */ "uniqueflag ::=",
+ /* 247 */ "idxlist_opt ::=",
+ /* 248 */ "idxlist_opt ::= LP idxlist RP",
+ /* 249 */ "idxlist ::= idxlist COMMA idxitem collate sortorder",
+ /* 250 */ "idxlist ::= idxitem collate sortorder",
+ /* 251 */ "idxitem ::= nm",
+ /* 252 */ "cmd ::= DROP INDEX fullname",
+ /* 253 */ "cmd ::= VACUUM",
+ /* 254 */ "cmd ::= VACUUM nm",
+ /* 255 */ "cmd ::= PRAGMA nm dbnm EQ nm",
+ /* 256 */ "cmd ::= PRAGMA nm dbnm EQ ON",
+ /* 257 */ "cmd ::= PRAGMA nm dbnm EQ plus_num",
+ /* 258 */ "cmd ::= PRAGMA nm dbnm EQ minus_num",
+ /* 259 */ "cmd ::= PRAGMA nm dbnm LP nm RP",
+ /* 260 */ "cmd ::= PRAGMA nm dbnm",
+ /* 261 */ "plus_num ::= plus_opt number",
+ /* 262 */ "minus_num ::= MINUS number",
+ /* 263 */ "number ::= INTEGER",
+ /* 264 */ "number ::= FLOAT",
+ /* 265 */ "plus_opt ::= PLUS",
+ /* 266 */ "plus_opt ::=",
+ /* 267 */ "cmd ::= CREATE trigger_decl BEGIN trigger_cmd_list END",
+ /* 268 */ "trigger_decl ::= temp TRIGGER nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause",
+ /* 269 */ "trigger_time ::= BEFORE",
+ /* 270 */ "trigger_time ::= AFTER",
+ /* 271 */ "trigger_time ::= INSTEAD OF",
+ /* 272 */ "trigger_time ::=",
+ /* 273 */ "trigger_event ::= DELETE",
+ /* 274 */ "trigger_event ::= INSERT",
+ /* 275 */ "trigger_event ::= UPDATE",
+ /* 276 */ "trigger_event ::= UPDATE OF inscollist",
+ /* 277 */ "foreach_clause ::=",
+ /* 278 */ "foreach_clause ::= FOR EACH ROW",
+ /* 279 */ "foreach_clause ::= FOR EACH STATEMENT",
+ /* 280 */ "when_clause ::=",
+ /* 281 */ "when_clause ::= WHEN expr",
+ /* 282 */ "trigger_cmd_list ::= trigger_cmd SEMI trigger_cmd_list",
+ /* 283 */ "trigger_cmd_list ::=",
+ /* 284 */ "trigger_cmd ::= UPDATE orconf nm SET setlist where_opt",
+ /* 285 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt VALUES LP itemlist RP",
+ /* 286 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt select",
+ /* 287 */ "trigger_cmd ::= DELETE FROM nm where_opt",
+ /* 288 */ "trigger_cmd ::= select",
+ /* 289 */ "expr ::= RAISE LP IGNORE RP",
+ /* 290 */ "expr ::= RAISE LP raisetype COMMA nm RP",
+ /* 291 */ "raisetype ::= ROLLBACK",
+ /* 292 */ "raisetype ::= ABORT",
+ /* 293 */ "raisetype ::= FAIL",
+ /* 294 */ "cmd ::= DROP TRIGGER fullname",
+ /* 295 */ "cmd ::= ATTACH database_kw_opt ids AS nm key_opt",
+ /* 296 */ "key_opt ::=",
+ /* 297 */ "key_opt ::= KEY ids",
+ /* 298 */ "key_opt ::= KEY BLOB",
+ /* 299 */ "database_kw_opt ::= DATABASE",
+ /* 300 */ "database_kw_opt ::=",
+ /* 301 */ "cmd ::= DETACH database_kw_opt nm",
+ /* 302 */ "cmd ::= REINDEX",
+ /* 303 */ "cmd ::= REINDEX nm dbnm",
+ /* 304 */ "cmd ::= ANALYZE",
+ /* 305 */ "cmd ::= ANALYZE nm dbnm",
+ /* 306 */ "cmd ::= ALTER TABLE fullname RENAME TO nm",
+ /* 307 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt column",
+ /* 308 */ "add_column_fullname ::= fullname",
+ /* 309 */ "kwcolumn_opt ::=",
+ /* 310 */ "kwcolumn_opt ::= COLUMNKW",
+};
+#endif /* NDEBUG */
+
+/*
+** This function returns the symbolic name associated with a token
+** value.
+*/
+const char *sqlite3ParserTokenName(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 sqlite3Parser and sqlite3ParserFree.
+*/
+void *sqlite3ParserAlloc(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 162:
+ case 195:
+ case 212:
+#line 370 "parse.y"
+{sqlite3SelectDelete((yypminor->yy375));}
+#line 1332 "parse.c"
+ break;
+ case 176:
+ case 177:
+ case 200:
+ case 202:
+ case 210:
+ case 216:
+ case 230:
+#line 629 "parse.y"
+{sqlite3ExprDelete((yypminor->yy62));}
+#line 1343 "parse.c"
+ break;
+ case 181:
+ case 189:
+ case 198:
+ case 201:
+ case 203:
+ case 205:
+ case 215:
+ case 218:
+ case 219:
+ case 222:
+ case 228:
+#line 876 "parse.y"
+{sqlite3ExprListDelete((yypminor->yy418));}
+#line 1358 "parse.c"
+ break;
+ case 194:
+ case 199:
+ case 207:
+ case 208:
+#line 499 "parse.y"
+{sqlite3SrcListDelete((yypminor->yy151));}
+#line 1366 "parse.c"
+ break;
+ case 204:
+#line 561 "parse.y"
+{
+ sqlite3ExprDelete((yypminor->yy220).pLimit);
+ sqlite3ExprDelete((yypminor->yy220).pOffset);
+}
+#line 1374 "parse.c"
+ break;
+ case 211:
+ case 214:
+ case 221:
+#line 517 "parse.y"
+{sqlite3IdListDelete((yypminor->yy240));}
+#line 1381 "parse.c"
+ break;
+ case 236:
+ case 241:
+#line 969 "parse.y"
+{sqlite3DeleteTriggerStep((yypminor->yy360));}
+#line 1387 "parse.c"
+ break;
+ case 238:
+#line 953 "parse.y"
+{sqlite3IdListDelete((yypminor->yy30).b);}
+#line 1392 "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 sqlite3ParserAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+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);
+ (*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(
+ int stateno, /* Current state number */
+ 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 ){
+ 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 */
+ sqlite3ParserARG_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 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[] = {
+ { 147, 1 },
+ { 148, 2 },
+ { 148, 1 },
+ { 150, 1 },
+ { 149, 1 },
+ { 149, 3 },
+ { 152, 0 },
+ { 152, 1 },
+ { 152, 3 },
+ { 151, 3 },
+ { 154, 0 },
+ { 154, 1 },
+ { 154, 2 },
+ { 153, 0 },
+ { 153, 1 },
+ { 153, 1 },
+ { 153, 1 },
+ { 151, 2 },
+ { 151, 2 },
+ { 151, 2 },
+ { 151, 2 },
+ { 156, 5 },
+ { 158, 1 },
+ { 158, 0 },
+ { 157, 4 },
+ { 157, 2 },
+ { 160, 3 },
+ { 160, 1 },
+ { 163, 3 },
+ { 164, 1 },
+ { 167, 1 },
+ { 168, 1 },
+ { 168, 1 },
+ { 155, 1 },
+ { 155, 1 },
+ { 155, 1 },
+ { 165, 0 },
+ { 165, 1 },
+ { 169, 1 },
+ { 169, 4 },
+ { 169, 6 },
+ { 170, 1 },
+ { 170, 2 },
+ { 171, 1 },
+ { 171, 1 },
+ { 166, 2 },
+ { 166, 0 },
+ { 174, 3 },
+ { 174, 1 },
+ { 174, 2 },
+ { 174, 4 },
+ { 174, 3 },
+ { 174, 3 },
+ { 174, 2 },
+ { 175, 2 },
+ { 175, 3 },
+ { 175, 5 },
+ { 175, 2 },
+ { 175, 5 },
+ { 175, 4 },
+ { 175, 1 },
+ { 175, 2 },
+ { 180, 0 },
+ { 180, 1 },
+ { 182, 0 },
+ { 182, 2 },
+ { 184, 2 },
+ { 184, 3 },
+ { 184, 3 },
+ { 184, 3 },
+ { 185, 2 },
+ { 185, 2 },
+ { 185, 1 },
+ { 185, 1 },
+ { 183, 3 },
+ { 183, 2 },
+ { 186, 0 },
+ { 186, 2 },
+ { 186, 2 },
+ { 161, 0 },
+ { 161, 2 },
+ { 187, 3 },
+ { 187, 2 },
+ { 187, 1 },
+ { 188, 2 },
+ { 188, 7 },
+ { 188, 5 },
+ { 188, 3 },
+ { 188, 10 },
+ { 190, 0 },
+ { 190, 1 },
+ { 178, 0 },
+ { 178, 3 },
+ { 191, 0 },
+ { 191, 2 },
+ { 192, 1 },
+ { 192, 1 },
+ { 192, 1 },
+ { 151, 3 },
+ { 151, 7 },
+ { 151, 3 },
+ { 151, 1 },
+ { 162, 1 },
+ { 162, 3 },
+ { 196, 1 },
+ { 196, 2 },
+ { 196, 1 },
+ { 196, 1 },
+ { 195, 9 },
+ { 197, 1 },
+ { 197, 1 },
+ { 197, 0 },
+ { 205, 2 },
+ { 205, 0 },
+ { 198, 3 },
+ { 198, 2 },
+ { 198, 4 },
+ { 206, 2 },
+ { 206, 1 },
+ { 206, 0 },
+ { 199, 0 },
+ { 199, 2 },
+ { 208, 2 },
+ { 208, 0 },
+ { 207, 6 },
+ { 207, 7 },
+ { 212, 1 },
+ { 212, 1 },
+ { 159, 0 },
+ { 159, 2 },
+ { 194, 2 },
+ { 209, 1 },
+ { 209, 1 },
+ { 209, 2 },
+ { 209, 3 },
+ { 209, 4 },
+ { 210, 2 },
+ { 210, 0 },
+ { 211, 4 },
+ { 211, 0 },
+ { 203, 0 },
+ { 203, 3 },
+ { 215, 5 },
+ { 215, 3 },
+ { 216, 1 },
+ { 179, 1 },
+ { 179, 1 },
+ { 179, 0 },
+ { 217, 0 },
+ { 217, 2 },
+ { 201, 0 },
+ { 201, 3 },
+ { 202, 0 },
+ { 202, 2 },
+ { 204, 0 },
+ { 204, 2 },
+ { 204, 4 },
+ { 204, 4 },
+ { 151, 4 },
+ { 200, 0 },
+ { 200, 2 },
+ { 151, 6 },
+ { 219, 5 },
+ { 219, 3 },
+ { 151, 8 },
+ { 151, 5 },
+ { 220, 2 },
+ { 220, 1 },
+ { 222, 3 },
+ { 222, 1 },
+ { 221, 0 },
+ { 221, 3 },
+ { 214, 3 },
+ { 214, 1 },
+ { 177, 1 },
+ { 177, 3 },
+ { 176, 1 },
+ { 177, 1 },
+ { 177, 1 },
+ { 177, 3 },
+ { 177, 5 },
+ { 176, 1 },
+ { 176, 1 },
+ { 176, 1 },
+ { 176, 1 },
+ { 177, 1 },
+ { 177, 1 },
+ { 177, 6 },
+ { 177, 5 },
+ { 177, 4 },
+ { 176, 1 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 177, 3 },
+ { 223, 1 },
+ { 223, 2 },
+ { 224, 2 },
+ { 224, 0 },
+ { 177, 4 },
+ { 177, 2 },
+ { 177, 3 },
+ { 177, 2 },
+ { 177, 3 },
+ { 177, 4 },
+ { 177, 2 },
+ { 177, 2 },
+ { 177, 2 },
+ { 177, 2 },
+ { 225, 1 },
+ { 225, 2 },
+ { 177, 5 },
+ { 226, 1 },
+ { 226, 2 },
+ { 177, 5 },
+ { 177, 3 },
+ { 177, 5 },
+ { 177, 4 },
+ { 177, 4 },
+ { 177, 5 },
+ { 228, 5 },
+ { 228, 4 },
+ { 229, 2 },
+ { 229, 0 },
+ { 227, 1 },
+ { 227, 0 },
+ { 218, 3 },
+ { 218, 1 },
+ { 230, 1 },
+ { 230, 0 },
+ { 151, 11 },
+ { 231, 1 },
+ { 231, 0 },
+ { 181, 0 },
+ { 181, 3 },
+ { 189, 5 },
+ { 189, 3 },
+ { 232, 1 },
+ { 151, 3 },
+ { 151, 1 },
+ { 151, 2 },
+ { 151, 5 },
+ { 151, 5 },
+ { 151, 5 },
+ { 151, 5 },
+ { 151, 6 },
+ { 151, 3 },
+ { 172, 2 },
+ { 173, 2 },
+ { 234, 1 },
+ { 234, 1 },
+ { 233, 1 },
+ { 233, 0 },
+ { 151, 5 },
+ { 235, 10 },
+ { 237, 1 },
+ { 237, 1 },
+ { 237, 2 },
+ { 237, 0 },
+ { 238, 1 },
+ { 238, 1 },
+ { 238, 1 },
+ { 238, 3 },
+ { 239, 0 },
+ { 239, 3 },
+ { 239, 3 },
+ { 240, 0 },
+ { 240, 2 },
+ { 236, 3 },
+ { 236, 0 },
+ { 241, 6 },
+ { 241, 8 },
+ { 241, 5 },
+ { 241, 4 },
+ { 241, 1 },
+ { 177, 4 },
+ { 177, 6 },
+ { 193, 1 },
+ { 193, 1 },
+ { 193, 1 },
+ { 151, 3 },
+ { 151, 6 },
+ { 243, 0 },
+ { 243, 2 },
+ { 243, 2 },
+ { 242, 1 },
+ { 242, 0 },
+ { 151, 3 },
+ { 151, 1 },
+ { 151, 3 },
+ { 151, 1 },
+ { 151, 3 },
+ { 151, 6 },
+ { 151, 6 },
+ { 244, 1 },
+ { 245, 0 },
+ { 245, 1 },
+};
+
+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<sizeof(yyRuleName)/sizeof(yyRuleName[0]) ){
+ fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+ yyRuleName[yyruleno]);
+ }
+#endif /* NDEBUG */
+
+#ifndef 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.
+ */
+ memset(&yygotominor, 0, sizeof(yygotominor));
+#endif
+
+ 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 3:
+#line 102 "parse.y"
+{ sqlite3FinishCoding(pParse); }
+#line 1930 "parse.c"
+ break;
+ case 6:
+#line 105 "parse.y"
+{ sqlite3BeginParse(pParse, 0); }
+#line 1935 "parse.c"
+ break;
+ case 7:
+#line 107 "parse.y"
+{ sqlite3BeginParse(pParse, 1); }
+#line 1940 "parse.c"
+ break;
+ case 8:
+#line 108 "parse.y"
+{ sqlite3BeginParse(pParse, 2); }
+#line 1945 "parse.c"
+ break;
+ case 9:
+#line 114 "parse.y"
+{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy280);}
+#line 1950 "parse.c"
+ break;
+ case 13:
+#line 119 "parse.y"
+{yygotominor.yy280 = TK_DEFERRED;}
+#line 1955 "parse.c"
+ break;
+ case 14:
+ case 15:
+ case 16:
+ case 104:
+ case 106:
+ case 107:
+#line 120 "parse.y"
+{yygotominor.yy280 = yymsp[0].major;}
+#line 1965 "parse.c"
+ break;
+ case 17:
+ case 18:
+#line 123 "parse.y"
+{sqlite3CommitTransaction(pParse);}
+#line 1971 "parse.c"
+ break;
+ case 19:
+#line 125 "parse.y"
+{sqlite3RollbackTransaction(pParse);}
+#line 1976 "parse.c"
+ break;
+ case 21:
+#line 130 "parse.y"
+{
+ sqlite3StartTable(pParse,&yymsp[-4].minor.yy0,&yymsp[-1].minor.yy198,&yymsp[0].minor.yy198,yymsp[-3].minor.yy280,0);
+}
+#line 1983 "parse.c"
+ break;
+ case 22:
+ case 63:
+ case 77:
+ case 109:
+ case 224:
+ case 227:
+#line 135 "parse.y"
+{yygotominor.yy280 = 1;}
+#line 1993 "parse.c"
+ break;
+ case 23:
+ case 62:
+ case 76:
+ case 78:
+ case 89:
+ case 110:
+ case 111:
+ case 223:
+ case 226:
+#line 137 "parse.y"
+{yygotominor.yy280 = 0;}
+#line 2006 "parse.c"
+ break;
+ case 24:
+#line 138 "parse.y"
+{
+ sqlite3EndTable(pParse,&yymsp[-1].minor.yy198,&yymsp[0].minor.yy0,0);
+}
+#line 2013 "parse.c"
+ break;
+ case 25:
+#line 141 "parse.y"
+{
+ sqlite3EndTable(pParse,0,0,yymsp[0].minor.yy375);
+ sqlite3SelectDelete(yymsp[0].minor.yy375);
+}
+#line 2021 "parse.c"
+ break;
+ case 28:
+#line 153 "parse.y"
+{
+ yygotominor.yy198.z = yymsp[-2].minor.yy198.z;
+ yygotominor.yy198.n = (pParse->sLastToken.z-yymsp[-2].minor.yy198.z) + pParse->sLastToken.n;
+}
+#line 2029 "parse.c"
+ break;
+ case 29:
+#line 157 "parse.y"
+{
+ sqlite3AddColumn(pParse,&yymsp[0].minor.yy198);
+ yygotominor.yy198 = yymsp[0].minor.yy198;
+}
+#line 2037 "parse.c"
+ break;
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 263:
+ case 264:
+#line 167 "parse.y"
+{yygotominor.yy198 = yymsp[0].minor.yy0;}
+#line 2049 "parse.c"
+ break;
+ case 37:
+#line 227 "parse.y"
+{sqlite3AddColumnType(pParse,&yymsp[0].minor.yy198);}
+#line 2054 "parse.c"
+ break;
+ case 38:
+ case 41:
+ case 117:
+ case 118:
+ case 129:
+ case 149:
+ case 251:
+ case 261:
+ case 262:
+#line 228 "parse.y"
+{yygotominor.yy198 = yymsp[0].minor.yy198;}
+#line 2067 "parse.c"
+ break;
+ case 39:
+#line 229 "parse.y"
+{
+ yygotominor.yy198.z = yymsp[-3].minor.yy198.z;
+ yygotominor.yy198.n = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy198.z;
+}
+#line 2075 "parse.c"
+ break;
+ case 40:
+#line 233 "parse.y"
+{
+ yygotominor.yy198.z = yymsp[-5].minor.yy198.z;
+ yygotominor.yy198.n = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy198.z;
+}
+#line 2083 "parse.c"
+ break;
+ case 42:
+#line 239 "parse.y"
+{yygotominor.yy198.z=yymsp[-1].minor.yy198.z; yygotominor.yy198.n=yymsp[0].minor.yy198.n+(yymsp[0].minor.yy198.z-yymsp[-1].minor.yy198.z);}
+#line 2088 "parse.c"
+ break;
+ case 43:
+#line 241 "parse.y"
+{ yygotominor.yy280 = atoi(yymsp[0].minor.yy198.z); }
+#line 2093 "parse.c"
+ break;
+ case 44:
+#line 242 "parse.y"
+{ yygotominor.yy280 = -atoi(yymsp[0].minor.yy198.z); }
+#line 2098 "parse.c"
+ break;
+ case 49:
+ case 51:
+#line 251 "parse.y"
+{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy62);}
+#line 2104 "parse.c"
+ break;
+ case 50:
+#line 252 "parse.y"
+{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy62);}
+#line 2109 "parse.c"
+ break;
+ case 52:
+#line 254 "parse.y"
+{
+ Expr *p = sqlite3Expr(TK_UMINUS, yymsp[0].minor.yy62, 0, 0);
+ sqlite3AddDefaultValue(pParse,p);
+}
+#line 2117 "parse.c"
+ break;
+ case 53:
+#line 258 "parse.y"
+{
+ Expr *p = sqlite3Expr(TK_STRING, 0, 0, &yymsp[0].minor.yy198);
+ sqlite3AddDefaultValue(pParse,p);
+}
+#line 2125 "parse.c"
+ break;
+ case 55:
+#line 267 "parse.y"
+{sqlite3AddNotNull(pParse, yymsp[0].minor.yy280);}
+#line 2130 "parse.c"
+ break;
+ case 56:
+#line 269 "parse.y"
+{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy280,yymsp[0].minor.yy280);}
+#line 2135 "parse.c"
+ break;
+ case 57:
+#line 270 "parse.y"
+{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy280,0,0);}
+#line 2140 "parse.c"
+ break;
+ case 58:
+#line 271 "parse.y"
+{sqlite3ExprDelete(yymsp[-2].minor.yy62);}
+#line 2145 "parse.c"
+ break;
+ case 59:
+#line 273 "parse.y"
+{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy198,yymsp[-1].minor.yy418,yymsp[0].minor.yy280);}
+#line 2150 "parse.c"
+ break;
+ case 60:
+#line 274 "parse.y"
+{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy280);}
+#line 2155 "parse.c"
+ break;
+ case 61:
+#line 275 "parse.y"
+{sqlite3AddCollateType(pParse, yymsp[0].minor.yy198.z, yymsp[0].minor.yy198.n);}
+#line 2160 "parse.c"
+ break;
+ case 64:
+#line 288 "parse.y"
+{ yygotominor.yy280 = OE_Restrict * 0x010101; }
+#line 2165 "parse.c"
+ break;
+ case 65:
+#line 289 "parse.y"
+{ yygotominor.yy280 = (yymsp[-1].minor.yy280 & yymsp[0].minor.yy359.mask) | yymsp[0].minor.yy359.value; }
+#line 2170 "parse.c"
+ break;
+ case 66:
+#line 291 "parse.y"
+{ yygotominor.yy359.value = 0; yygotominor.yy359.mask = 0x000000; }
+#line 2175 "parse.c"
+ break;
+ case 67:
+#line 292 "parse.y"
+{ yygotominor.yy359.value = yymsp[0].minor.yy280; yygotominor.yy359.mask = 0x0000ff; }
+#line 2180 "parse.c"
+ break;
+ case 68:
+#line 293 "parse.y"
+{ yygotominor.yy359.value = yymsp[0].minor.yy280<<8; yygotominor.yy359.mask = 0x00ff00; }
+#line 2185 "parse.c"
+ break;
+ case 69:
+#line 294 "parse.y"
+{ yygotominor.yy359.value = yymsp[0].minor.yy280<<16; yygotominor.yy359.mask = 0xff0000; }
+#line 2190 "parse.c"
+ break;
+ case 70:
+#line 296 "parse.y"
+{ yygotominor.yy280 = OE_SetNull; }
+#line 2195 "parse.c"
+ break;
+ case 71:
+#line 297 "parse.y"
+{ yygotominor.yy280 = OE_SetDflt; }
+#line 2200 "parse.c"
+ break;
+ case 72:
+#line 298 "parse.y"
+{ yygotominor.yy280 = OE_Cascade; }
+#line 2205 "parse.c"
+ break;
+ case 73:
+#line 299 "parse.y"
+{ yygotominor.yy280 = OE_Restrict; }
+#line 2210 "parse.c"
+ break;
+ case 74:
+ case 75:
+ case 90:
+ case 92:
+ case 94:
+ case 95:
+ case 166:
+#line 301 "parse.y"
+{yygotominor.yy280 = yymsp[0].minor.yy280;}
+#line 2221 "parse.c"
+ break;
+ case 79:
+#line 311 "parse.y"
+{yygotominor.yy198.n = 0; yygotominor.yy198.z = 0;}
+#line 2226 "parse.c"
+ break;
+ case 80:
+#line 312 "parse.y"
+{yygotominor.yy198 = yymsp[-1].minor.yy0;}
+#line 2231 "parse.c"
+ break;
+ case 85:
+#line 318 "parse.y"
+{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy418,yymsp[0].minor.yy280,yymsp[-2].minor.yy280);}
+#line 2236 "parse.c"
+ break;
+ case 86:
+#line 320 "parse.y"
+{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy418,yymsp[0].minor.yy280,0,0);}
+#line 2241 "parse.c"
+ break;
+ case 88:
+#line 323 "parse.y"
+{
+ sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy418, &yymsp[-3].minor.yy198, yymsp[-2].minor.yy418, yymsp[-1].minor.yy280);
+ sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy280);
+}
+#line 2249 "parse.c"
+ break;
+ case 91:
+ case 93:
+#line 337 "parse.y"
+{yygotominor.yy280 = OE_Default;}
+#line 2255 "parse.c"
+ break;
+ case 96:
+#line 342 "parse.y"
+{yygotominor.yy280 = OE_Ignore;}
+#line 2260 "parse.c"
+ break;
+ case 97:
+ case 167:
+#line 343 "parse.y"
+{yygotominor.yy280 = OE_Replace;}
+#line 2266 "parse.c"
+ break;
+ case 98:
+#line 347 "parse.y"
+{
+ sqlite3DropTable(pParse, yymsp[0].minor.yy151, 0);
+}
+#line 2273 "parse.c"
+ break;
+ case 99:
+#line 354 "parse.y"
+{
+ sqlite3CreateView(pParse, &yymsp[-6].minor.yy0, &yymsp[-3].minor.yy198, &yymsp[-2].minor.yy198, yymsp[0].minor.yy375, yymsp[-5].minor.yy280);
+}
+#line 2280 "parse.c"
+ break;
+ case 100:
+#line 357 "parse.y"
+{
+ sqlite3DropTable(pParse, yymsp[0].minor.yy151, 1);
+}
+#line 2287 "parse.c"
+ break;
+ case 101:
+#line 364 "parse.y"
+{
+ sqlite3Select(pParse, yymsp[0].minor.yy375, SRT_Callback, 0, 0, 0, 0, 0);
+ sqlite3SelectDelete(yymsp[0].minor.yy375);
+}
+#line 2295 "parse.c"
+ break;
+ case 102:
+ case 126:
+#line 374 "parse.y"
+{yygotominor.yy375 = yymsp[0].minor.yy375;}
+#line 2301 "parse.c"
+ break;
+ case 103:
+#line 376 "parse.y"
+{
+ if( yymsp[0].minor.yy375 ){
+ yymsp[0].minor.yy375->op = yymsp[-1].minor.yy280;
+ yymsp[0].minor.yy375->pPrior = yymsp[-2].minor.yy375;
+ }
+ yygotominor.yy375 = yymsp[0].minor.yy375;
+}
+#line 2312 "parse.c"
+ break;
+ case 105:
+#line 385 "parse.y"
+{yygotominor.yy280 = TK_ALL;}
+#line 2317 "parse.c"
+ break;
+ case 108:
+#line 390 "parse.y"
+{
+ yygotominor.yy375 = sqlite3SelectNew(yymsp[-6].minor.yy418,yymsp[-5].minor.yy151,yymsp[-4].minor.yy62,yymsp[-3].minor.yy418,yymsp[-2].minor.yy62,yymsp[-1].minor.yy418,yymsp[-7].minor.yy280,yymsp[0].minor.yy220.pLimit,yymsp[0].minor.yy220.pOffset);
+}
+#line 2324 "parse.c"
+ break;
+ case 112:
+ case 248:
+#line 411 "parse.y"
+{yygotominor.yy418 = yymsp[-1].minor.yy418;}
+#line 2330 "parse.c"
+ break;
+ case 113:
+ case 140:
+ case 150:
+ case 247:
+#line 412 "parse.y"
+{yygotominor.yy418 = 0;}
+#line 2338 "parse.c"
+ break;
+ case 114:
+#line 413 "parse.y"
+{
+ yygotominor.yy418 = sqlite3ExprListAppend(yymsp[-2].minor.yy418,yymsp[-1].minor.yy62,yymsp[0].minor.yy198.n?&yymsp[0].minor.yy198:0);
+}
+#line 2345 "parse.c"
+ break;
+ case 115:
+#line 416 "parse.y"
+{
+ yygotominor.yy418 = sqlite3ExprListAppend(yymsp[-1].minor.yy418, sqlite3Expr(TK_ALL, 0, 0, 0), 0);
+}
+#line 2352 "parse.c"
+ break;
+ case 116:
+#line 419 "parse.y"
+{
+ Expr *pRight = sqlite3Expr(TK_ALL, 0, 0, 0);
+ Expr *pLeft = sqlite3Expr(TK_ID, 0, 0, &yymsp[-2].minor.yy198);
+ yygotominor.yy418 = sqlite3ExprListAppend(yymsp[-3].minor.yy418, sqlite3Expr(TK_DOT, pLeft, pRight, 0), 0);
+}
+#line 2361 "parse.c"
+ break;
+ case 119:
+#line 431 "parse.y"
+{yygotominor.yy198.n = 0;}
+#line 2366 "parse.c"
+ break;
+ case 120:
+#line 443 "parse.y"
+{yygotominor.yy151 = sqliteMalloc(sizeof(*yygotominor.yy151));}
+#line 2371 "parse.c"
+ break;
+ case 121:
+#line 444 "parse.y"
+{yygotominor.yy151 = yymsp[0].minor.yy151;}
+#line 2376 "parse.c"
+ break;
+ case 122:
+#line 449 "parse.y"
+{
+ yygotominor.yy151 = yymsp[-1].minor.yy151;
+ if( yygotominor.yy151 && yygotominor.yy151->nSrc>0 ) yygotominor.yy151->a[yygotominor.yy151->nSrc-1].jointype = yymsp[0].minor.yy280;
+}
+#line 2384 "parse.c"
+ break;
+ case 123:
+#line 453 "parse.y"
+{yygotominor.yy151 = 0;}
+#line 2389 "parse.c"
+ break;
+ case 124:
+#line 454 "parse.y"
+{
+ yygotominor.yy151 = sqlite3SrcListAppend(yymsp[-5].minor.yy151,&yymsp[-4].minor.yy198,&yymsp[-3].minor.yy198);
+ if( yymsp[-2].minor.yy198.n ) sqlite3SrcListAddAlias(yygotominor.yy151,&yymsp[-2].minor.yy198);
+ if( yymsp[-1].minor.yy62 ){
+ if( yygotominor.yy151 && yygotominor.yy151->nSrc>1 ){ yygotominor.yy151->a[yygotominor.yy151->nSrc-2].pOn = yymsp[-1].minor.yy62; }
+ else { sqlite3ExprDelete(yymsp[-1].minor.yy62); }
+ }
+ if( yymsp[0].minor.yy240 ){
+ if( yygotominor.yy151 && yygotominor.yy151->nSrc>1 ){ yygotominor.yy151->a[yygotominor.yy151->nSrc-2].pUsing = yymsp[0].minor.yy240; }
+ else { sqlite3IdListDelete(yymsp[0].minor.yy240); }
+ }
+}
+#line 2405 "parse.c"
+ break;
+ case 125:
+#line 468 "parse.y"
+{
+ yygotominor.yy151 = sqlite3SrcListAppend(yymsp[-6].minor.yy151,0,0);
+ yygotominor.yy151->a[yygotominor.yy151->nSrc-1].pSelect = yymsp[-4].minor.yy375;
+ if( yymsp[-2].minor.yy198.n ) sqlite3SrcListAddAlias(yygotominor.yy151,&yymsp[-2].minor.yy198);
+ if( yymsp[-1].minor.yy62 ){
+ if( yygotominor.yy151 && yygotominor.yy151->nSrc>1 ){ yygotominor.yy151->a[yygotominor.yy151->nSrc-2].pOn = yymsp[-1].minor.yy62; }
+ else { sqlite3ExprDelete(yymsp[-1].minor.yy62); }
+ }
+ if( yymsp[0].minor.yy240 ){
+ if( yygotominor.yy151 && yygotominor.yy151->nSrc>1 ){ yygotominor.yy151->a[yygotominor.yy151->nSrc-2].pUsing = yymsp[0].minor.yy240; }
+ else { sqlite3IdListDelete(yymsp[0].minor.yy240); }
+ }
+ }
+#line 2422 "parse.c"
+ break;
+ case 127:
+#line 489 "parse.y"
+{
+ yygotominor.yy375 = sqlite3SelectNew(0,yymsp[0].minor.yy151,0,0,0,0,0,0,0);
+ }
+#line 2429 "parse.c"
+ break;
+ case 128:
+#line 495 "parse.y"
+{yygotominor.yy198.z=0; yygotominor.yy198.n=0;}
+#line 2434 "parse.c"
+ break;
+ case 130:
+#line 500 "parse.y"
+{yygotominor.yy151 = sqlite3SrcListAppend(0,&yymsp[-1].minor.yy198,&yymsp[0].minor.yy198);}
+#line 2439 "parse.c"
+ break;
+ case 131:
+ case 132:
+#line 504 "parse.y"
+{ yygotominor.yy280 = JT_INNER; }
+#line 2445 "parse.c"
+ break;
+ case 133:
+#line 506 "parse.y"
+{ yygotominor.yy280 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); }
+#line 2450 "parse.c"
+ break;
+ case 134:
+#line 507 "parse.y"
+{ yygotominor.yy280 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy198,0); }
+#line 2455 "parse.c"
+ break;
+ case 135:
+#line 509 "parse.y"
+{ yygotominor.yy280 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy198,&yymsp[-1].minor.yy198); }
+#line 2460 "parse.c"
+ break;
+ case 136:
+ case 144:
+ case 153:
+ case 160:
+ case 174:
+ case 211:
+ case 236:
+ case 238:
+ case 242:
+#line 513 "parse.y"
+{yygotominor.yy62 = yymsp[0].minor.yy62;}
+#line 2473 "parse.c"
+ break;
+ case 137:
+ case 152:
+ case 159:
+ case 212:
+ case 237:
+ case 239:
+ case 243:
+#line 514 "parse.y"
+{yygotominor.yy62 = 0;}
+#line 2484 "parse.c"
+ break;
+ case 138:
+ case 171:
+#line 518 "parse.y"
+{yygotominor.yy240 = yymsp[-1].minor.yy240;}
+#line 2490 "parse.c"
+ break;
+ case 139:
+ case 170:
+#line 519 "parse.y"
+{yygotominor.yy240 = 0;}
+#line 2496 "parse.c"
+ break;
+ case 141:
+ case 151:
+#line 530 "parse.y"
+{yygotominor.yy418 = yymsp[0].minor.yy418;}
+#line 2502 "parse.c"
+ break;
+ case 142:
+#line 531 "parse.y"
+{
+ yygotominor.yy418 = sqlite3ExprListAppend(yymsp[-4].minor.yy418,yymsp[-2].minor.yy62,yymsp[-1].minor.yy198.n>0?&yymsp[-1].minor.yy198:0);
+ if( yygotominor.yy418 ) yygotominor.yy418->a[yygotominor.yy418->nExpr-1].sortOrder = yymsp[0].minor.yy280;
+}
+#line 2510 "parse.c"
+ break;
+ case 143:
+#line 535 "parse.y"
+{
+ yygotominor.yy418 = sqlite3ExprListAppend(0,yymsp[-2].minor.yy62,yymsp[-1].minor.yy198.n>0?&yymsp[-1].minor.yy198:0);
+ if( yygotominor.yy418 && yygotominor.yy418->a ) yygotominor.yy418->a[0].sortOrder = yymsp[0].minor.yy280;
+}
+#line 2518 "parse.c"
+ break;
+ case 145:
+ case 147:
+#line 544 "parse.y"
+{yygotominor.yy280 = SQLITE_SO_ASC;}
+#line 2524 "parse.c"
+ break;
+ case 146:
+#line 545 "parse.y"
+{yygotominor.yy280 = SQLITE_SO_DESC;}
+#line 2529 "parse.c"
+ break;
+ case 148:
+#line 547 "parse.y"
+{yygotominor.yy198.z = 0; yygotominor.yy198.n = 0;}
+#line 2534 "parse.c"
+ break;
+ case 154:
+#line 565 "parse.y"
+{yygotominor.yy220.pLimit = 0; yygotominor.yy220.pOffset = 0;}
+#line 2539 "parse.c"
+ break;
+ case 155:
+#line 566 "parse.y"
+{yygotominor.yy220.pLimit = yymsp[0].minor.yy62; yygotominor.yy220.pOffset = 0;}
+#line 2544 "parse.c"
+ break;
+ case 156:
+#line 568 "parse.y"
+{yygotominor.yy220.pLimit = yymsp[-2].minor.yy62; yygotominor.yy220.pOffset = yymsp[0].minor.yy62;}
+#line 2549 "parse.c"
+ break;
+ case 157:
+#line 570 "parse.y"
+{yygotominor.yy220.pOffset = yymsp[-2].minor.yy62; yygotominor.yy220.pLimit = yymsp[0].minor.yy62;}
+#line 2554 "parse.c"
+ break;
+ case 158:
+#line 574 "parse.y"
+{sqlite3DeleteFrom(pParse,yymsp[-1].minor.yy151,yymsp[0].minor.yy62);}
+#line 2559 "parse.c"
+ break;
+ case 161:
+#line 585 "parse.y"
+{sqlite3Update(pParse,yymsp[-3].minor.yy151,yymsp[-1].minor.yy418,yymsp[0].minor.yy62,yymsp[-4].minor.yy280);}
+#line 2564 "parse.c"
+ break;
+ case 162:
+#line 591 "parse.y"
+{yygotominor.yy418 = sqlite3ExprListAppend(yymsp[-4].minor.yy418,yymsp[0].minor.yy62,&yymsp[-2].minor.yy198);}
+#line 2569 "parse.c"
+ break;
+ case 163:
+#line 592 "parse.y"
+{yygotominor.yy418 = sqlite3ExprListAppend(0,yymsp[0].minor.yy62,&yymsp[-2].minor.yy198);}
+#line 2574 "parse.c"
+ break;
+ case 164:
+#line 598 "parse.y"
+{sqlite3Insert(pParse, yymsp[-5].minor.yy151, yymsp[-1].minor.yy418, 0, yymsp[-4].minor.yy240, yymsp[-7].minor.yy280);}
+#line 2579 "parse.c"
+ break;
+ case 165:
+#line 600 "parse.y"
+{sqlite3Insert(pParse, yymsp[-2].minor.yy151, 0, yymsp[0].minor.yy375, yymsp[-1].minor.yy240, yymsp[-4].minor.yy280);}
+#line 2584 "parse.c"
+ break;
+ case 168:
+ case 240:
+#line 610 "parse.y"
+{yygotominor.yy418 = sqlite3ExprListAppend(yymsp[-2].minor.yy418,yymsp[0].minor.yy62,0);}
+#line 2590 "parse.c"
+ break;
+ case 169:
+ case 241:
+#line 611 "parse.y"
+{yygotominor.yy418 = sqlite3ExprListAppend(0,yymsp[0].minor.yy62,0);}
+#line 2596 "parse.c"
+ break;
+ case 172:
+#line 620 "parse.y"
+{yygotominor.yy240 = sqlite3IdListAppend(yymsp[-2].minor.yy240,&yymsp[0].minor.yy198);}
+#line 2601 "parse.c"
+ break;
+ case 173:
+#line 621 "parse.y"
+{yygotominor.yy240 = sqlite3IdListAppend(0,&yymsp[0].minor.yy198);}
+#line 2606 "parse.c"
+ break;
+ case 175:
+#line 632 "parse.y"
+{yygotominor.yy62 = yymsp[-1].minor.yy62; sqlite3ExprSpan(yygotominor.yy62,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); }
+#line 2611 "parse.c"
+ break;
+ case 176:
+ case 181:
+ case 182:
+ case 183:
+ case 184:
+#line 633 "parse.y"
+{yygotominor.yy62 = sqlite3Expr(yymsp[0].major, 0, 0, &yymsp[0].minor.yy0);}
+#line 2620 "parse.c"
+ break;
+ case 177:
+ case 178:
+#line 634 "parse.y"
+{yygotominor.yy62 = sqlite3Expr(TK_ID, 0, 0, &yymsp[0].minor.yy0);}
+#line 2626 "parse.c"
+ break;
+ case 179:
+#line 636 "parse.y"
+{
+ Expr *temp1 = sqlite3Expr(TK_ID, 0, 0, &yymsp[-2].minor.yy198);
+ Expr *temp2 = sqlite3Expr(TK_ID, 0, 0, &yymsp[0].minor.yy198);
+ yygotominor.yy62 = sqlite3Expr(TK_DOT, temp1, temp2, 0);
+}
+#line 2635 "parse.c"
+ break;
+ case 180:
+#line 641 "parse.y"
+{
+ Expr *temp1 = sqlite3Expr(TK_ID, 0, 0, &yymsp[-4].minor.yy198);
+ Expr *temp2 = sqlite3Expr(TK_ID, 0, 0, &yymsp[-2].minor.yy198);
+ Expr *temp3 = sqlite3Expr(TK_ID, 0, 0, &yymsp[0].minor.yy198);
+ Expr *temp4 = sqlite3Expr(TK_DOT, temp2, temp3, 0);
+ yygotominor.yy62 = sqlite3Expr(TK_DOT, temp1, temp4, 0);
+}
+#line 2646 "parse.c"
+ break;
+ case 185:
+#line 652 "parse.y"
+{yygotominor.yy62 = sqlite3RegisterExpr(pParse, &yymsp[0].minor.yy0);}
+#line 2651 "parse.c"
+ break;
+ case 186:
+#line 653 "parse.y"
+{
+ Token *pToken = &yymsp[0].minor.yy0;
+ Expr *pExpr = yygotominor.yy62 = sqlite3Expr(TK_VARIABLE, 0, 0, pToken);
+ sqlite3ExprAssignVarNumber(pParse, pExpr);
+}
+#line 2660 "parse.c"
+ break;
+ case 187:
+#line 659 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_CAST, yymsp[-3].minor.yy62, 0, &yymsp[-1].minor.yy198);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0);
+}
+#line 2668 "parse.c"
+ break;
+ case 188:
+#line 664 "parse.y"
+{
+ yygotominor.yy62 = sqlite3ExprFunction(yymsp[-1].minor.yy418, &yymsp[-4].minor.yy0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0);
+ if( yymsp[-2].minor.yy280 ){
+ yygotominor.yy62->flags |= EP_Distinct;
+ }
+}
+#line 2679 "parse.c"
+ break;
+ case 189:
+#line 671 "parse.y"
+{
+ yygotominor.yy62 = sqlite3ExprFunction(0, &yymsp[-3].minor.yy0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0);
+}
+#line 2687 "parse.c"
+ break;
+ case 190:
+#line 675 "parse.y"
+{
+ /* The CURRENT_TIME, CURRENT_DATE, and CURRENT_TIMESTAMP values are
+ ** treated as functions that return constants */
+ yygotominor.yy62 = sqlite3ExprFunction(0,&yymsp[0].minor.yy0);
+ if( yygotominor.yy62 ) yygotominor.yy62->op = TK_CONST_FUNC;
+}
+#line 2697 "parse.c"
+ break;
+ case 191:
+ case 192:
+ case 193:
+ case 194:
+ case 195:
+ case 196:
+ case 197:
+ case 198:
+ case 199:
+ case 200:
+ case 201:
+ case 202:
+ case 203:
+ case 204:
+ case 205:
+ case 206:
+ case 207:
+ case 208:
+#line 681 "parse.y"
+{yygotominor.yy62 = sqlite3Expr(yymsp[-1].major, yymsp[-2].minor.yy62, yymsp[0].minor.yy62, 0);}
+#line 2719 "parse.c"
+ break;
+ case 209:
+#line 700 "parse.y"
+{yygotominor.yy222.operator = yymsp[0].minor.yy0; yygotominor.yy222.not = 0;}
+#line 2724 "parse.c"
+ break;
+ case 210:
+#line 701 "parse.y"
+{yygotominor.yy222.operator = yymsp[0].minor.yy0; yygotominor.yy222.not = 1;}
+#line 2729 "parse.c"
+ break;
+ case 213:
+#line 705 "parse.y"
+{
+ ExprList *pList = sqlite3ExprListAppend(0, yymsp[-1].minor.yy62, 0);
+ pList = sqlite3ExprListAppend(pList, yymsp[-3].minor.yy62, 0);
+ if( yymsp[0].minor.yy62 ){
+ pList = sqlite3ExprListAppend(pList, yymsp[0].minor.yy62, 0);
+ }
+ yygotominor.yy62 = sqlite3ExprFunction(pList, &yymsp[-2].minor.yy222.operator);
+ if( yymsp[-2].minor.yy222.not ) yygotominor.yy62 = sqlite3Expr(TK_NOT, yygotominor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62, &yymsp[-3].minor.yy62->span, &yymsp[-1].minor.yy62->span);
+}
+#line 2743 "parse.c"
+ break;
+ case 214:
+#line 716 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_ISNULL, yymsp[-1].minor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-1].minor.yy62->span,&yymsp[0].minor.yy0);
+}
+#line 2751 "parse.c"
+ break;
+ case 215:
+#line 720 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_ISNULL, yymsp[-2].minor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-2].minor.yy62->span,&yymsp[0].minor.yy0);
+}
+#line 2759 "parse.c"
+ break;
+ case 216:
+#line 724 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_NOTNULL, yymsp[-1].minor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-1].minor.yy62->span,&yymsp[0].minor.yy0);
+}
+#line 2767 "parse.c"
+ break;
+ case 217:
+#line 728 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_NOTNULL, yymsp[-2].minor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-2].minor.yy62->span,&yymsp[0].minor.yy0);
+}
+#line 2775 "parse.c"
+ break;
+ case 218:
+#line 732 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_NOTNULL, yymsp[-3].minor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-3].minor.yy62->span,&yymsp[0].minor.yy0);
+}
+#line 2783 "parse.c"
+ break;
+ case 219:
+ case 220:
+#line 736 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(yymsp[-1].major, yymsp[0].minor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy62->span);
+}
+#line 2792 "parse.c"
+ break;
+ case 221:
+#line 744 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_UMINUS, yymsp[0].minor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy62->span);
+}
+#line 2800 "parse.c"
+ break;
+ case 222:
+#line 748 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_UPLUS, yymsp[0].minor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy62->span);
+}
+#line 2808 "parse.c"
+ break;
+ case 225:
+#line 755 "parse.y"
+{
+ ExprList *pList = sqlite3ExprListAppend(0, yymsp[-2].minor.yy62, 0);
+ pList = sqlite3ExprListAppend(pList, yymsp[0].minor.yy62, 0);
+ yygotominor.yy62 = sqlite3Expr(TK_BETWEEN, yymsp[-4].minor.yy62, 0, 0);
+ if( yygotominor.yy62 ){
+ yygotominor.yy62->pList = pList;
+ }else{
+ sqlite3ExprListDelete(pList);
+ }
+ if( yymsp[-3].minor.yy280 ) yygotominor.yy62 = sqlite3Expr(TK_NOT, yygotominor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-4].minor.yy62->span,&yymsp[0].minor.yy62->span);
+}
+#line 2824 "parse.c"
+ break;
+ case 228:
+#line 771 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_IN, yymsp[-4].minor.yy62, 0, 0);
+ if( yygotominor.yy62 ){
+ yygotominor.yy62->pList = yymsp[-1].minor.yy418;
+ }else{
+ sqlite3ExprListDelete(yymsp[-1].minor.yy418);
+ }
+ if( yymsp[-3].minor.yy280 ) yygotominor.yy62 = sqlite3Expr(TK_NOT, yygotominor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-4].minor.yy62->span,&yymsp[0].minor.yy0);
+ }
+#line 2838 "parse.c"
+ break;
+ case 229:
+#line 781 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_SELECT, 0, 0, 0);
+ if( yygotominor.yy62 ){
+ yygotominor.yy62->pSelect = yymsp[-1].minor.yy375;
+ }else{
+ sqlite3SelectDelete(yymsp[-1].minor.yy375);
+ }
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);
+ }
+#line 2851 "parse.c"
+ break;
+ case 230:
+#line 790 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_IN, yymsp[-4].minor.yy62, 0, 0);
+ if( yygotominor.yy62 ){
+ yygotominor.yy62->pSelect = yymsp[-1].minor.yy375;
+ }else{
+ sqlite3SelectDelete(yymsp[-1].minor.yy375);
+ }
+ if( yymsp[-3].minor.yy280 ) yygotominor.yy62 = sqlite3Expr(TK_NOT, yygotominor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-4].minor.yy62->span,&yymsp[0].minor.yy0);
+ }
+#line 2865 "parse.c"
+ break;
+ case 231:
+#line 800 "parse.y"
+{
+ SrcList *pSrc = sqlite3SrcListAppend(0,&yymsp[-1].minor.yy198,&yymsp[0].minor.yy198);
+ yygotominor.yy62 = sqlite3Expr(TK_IN, yymsp[-3].minor.yy62, 0, 0);
+ if( yygotominor.yy62 ){
+ yygotominor.yy62->pSelect = sqlite3SelectNew(0,pSrc,0,0,0,0,0,0,0);
+ }else{
+ sqlite3SrcListDelete(pSrc);
+ }
+ if( yymsp[-2].minor.yy280 ) yygotominor.yy62 = sqlite3Expr(TK_NOT, yygotominor.yy62, 0, 0);
+ sqlite3ExprSpan(yygotominor.yy62,&yymsp[-3].minor.yy62->span,yymsp[0].minor.yy198.z?&yymsp[0].minor.yy198:&yymsp[-1].minor.yy198);
+ }
+#line 2880 "parse.c"
+ break;
+ case 232:
+#line 811 "parse.y"
+{
+ Expr *p = yygotominor.yy62 = sqlite3Expr(TK_EXISTS, 0, 0, 0);
+ if( p ){
+ p->pSelect = yymsp[-1].minor.yy375;
+ sqlite3ExprSpan(p,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0);
+ }else{
+ sqlite3SelectDelete(yymsp[-1].minor.yy375);
+ }
+ }
+#line 2893 "parse.c"
+ break;
+ case 233:
+#line 823 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_CASE, yymsp[-3].minor.yy62, yymsp[-1].minor.yy62, 0);
+ if( yygotominor.yy62 ){
+ yygotominor.yy62->pList = yymsp[-2].minor.yy418;
+ }else{
+ sqlite3ExprListDelete(yymsp[-2].minor.yy418);
+ }
+ sqlite3ExprSpan(yygotominor.yy62, &yymsp[-4].minor.yy0, &yymsp[0].minor.yy0);
+}
+#line 2906 "parse.c"
+ break;
+ case 234:
+#line 834 "parse.y"
+{
+ yygotominor.yy418 = sqlite3ExprListAppend(yymsp[-4].minor.yy418, yymsp[-2].minor.yy62, 0);
+ yygotominor.yy418 = sqlite3ExprListAppend(yygotominor.yy418, yymsp[0].minor.yy62, 0);
+}
+#line 2914 "parse.c"
+ break;
+ case 235:
+#line 838 "parse.y"
+{
+ yygotominor.yy418 = sqlite3ExprListAppend(0, yymsp[-2].minor.yy62, 0);
+ yygotominor.yy418 = sqlite3ExprListAppend(yygotominor.yy418, yymsp[0].minor.yy62, 0);
+}
+#line 2922 "parse.c"
+ break;
+ case 244:
+#line 863 "parse.y"
+{
+ if( yymsp[-9].minor.yy280!=OE_None ) yymsp[-9].minor.yy280 = yymsp[0].minor.yy280;
+ if( yymsp[-9].minor.yy280==OE_Default) yymsp[-9].minor.yy280 = OE_Abort;
+ sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy198, &yymsp[-6].minor.yy198, sqlite3SrcListAppend(0,&yymsp[-4].minor.yy198,0),yymsp[-2].minor.yy418,yymsp[-9].minor.yy280, &yymsp[-10].minor.yy0, &yymsp[-1].minor.yy0);
+}
+#line 2931 "parse.c"
+ break;
+ case 245:
+ case 292:
+#line 870 "parse.y"
+{yygotominor.yy280 = OE_Abort;}
+#line 2937 "parse.c"
+ break;
+ case 246:
+#line 871 "parse.y"
+{yygotominor.yy280 = OE_None;}
+#line 2942 "parse.c"
+ break;
+ case 249:
+#line 881 "parse.y"
+{
+ Expr *p = 0;
+ if( yymsp[-1].minor.yy198.n>0 ){
+ p = sqlite3Expr(TK_COLUMN, 0, 0, 0);
+ if( p ) p->pColl = sqlite3LocateCollSeq(pParse, yymsp[-1].minor.yy198.z, yymsp[-1].minor.yy198.n);
+ }
+ yygotominor.yy418 = sqlite3ExprListAppend(yymsp[-4].minor.yy418, p, &yymsp[-2].minor.yy198);
+}
+#line 2954 "parse.c"
+ break;
+ case 250:
+#line 889 "parse.y"
+{
+ Expr *p = 0;
+ if( yymsp[-1].minor.yy198.n>0 ){
+ p = sqlite3Expr(TK_COLUMN, 0, 0, 0);
+ if( p ) p->pColl = sqlite3LocateCollSeq(pParse, yymsp[-1].minor.yy198.z, yymsp[-1].minor.yy198.n);
+ }
+ yygotominor.yy418 = sqlite3ExprListAppend(0, p, &yymsp[-2].minor.yy198);
+}
+#line 2966 "parse.c"
+ break;
+ case 252:
+#line 902 "parse.y"
+{sqlite3DropIndex(pParse, yymsp[0].minor.yy151);}
+#line 2971 "parse.c"
+ break;
+ case 253:
+ case 254:
+#line 906 "parse.y"
+{sqlite3Vacuum(pParse,0);}
+#line 2977 "parse.c"
+ break;
+ case 255:
+ case 257:
+#line 912 "parse.y"
+{sqlite3Pragma(pParse,&yymsp[-3].minor.yy198,&yymsp[-2].minor.yy198,&yymsp[0].minor.yy198,0);}
+#line 2983 "parse.c"
+ break;
+ case 256:
+#line 913 "parse.y"
+{sqlite3Pragma(pParse,&yymsp[-3].minor.yy198,&yymsp[-2].minor.yy198,&yymsp[0].minor.yy0,0);}
+#line 2988 "parse.c"
+ break;
+ case 258:
+#line 915 "parse.y"
+{
+ sqlite3Pragma(pParse,&yymsp[-3].minor.yy198,&yymsp[-2].minor.yy198,&yymsp[0].minor.yy198,1);
+}
+#line 2995 "parse.c"
+ break;
+ case 259:
+#line 918 "parse.y"
+{sqlite3Pragma(pParse,&yymsp[-4].minor.yy198,&yymsp[-3].minor.yy198,&yymsp[-1].minor.yy198,0);}
+#line 3000 "parse.c"
+ break;
+ case 260:
+#line 919 "parse.y"
+{sqlite3Pragma(pParse,&yymsp[-1].minor.yy198,&yymsp[0].minor.yy198,0,0);}
+#line 3005 "parse.c"
+ break;
+ case 267:
+#line 932 "parse.y"
+{
+ Token all;
+ all.z = yymsp[-3].minor.yy198.z;
+ all.n = (yymsp[0].minor.yy0.z - yymsp[-3].minor.yy198.z) + yymsp[0].minor.yy0.n;
+ sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy360, &all);
+}
+#line 3015 "parse.c"
+ break;
+ case 268:
+#line 941 "parse.y"
+{
+ sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy198, &yymsp[-6].minor.yy198, yymsp[-5].minor.yy280, yymsp[-4].minor.yy30.a, yymsp[-4].minor.yy30.b, yymsp[-2].minor.yy151, yymsp[-1].minor.yy280, yymsp[0].minor.yy62, yymsp[-9].minor.yy280);
+ yygotominor.yy198 = (yymsp[-6].minor.yy198.n==0?yymsp[-7].minor.yy198:yymsp[-6].minor.yy198);
+}
+#line 3023 "parse.c"
+ break;
+ case 269:
+ case 272:
+#line 947 "parse.y"
+{ yygotominor.yy280 = TK_BEFORE; }
+#line 3029 "parse.c"
+ break;
+ case 270:
+#line 948 "parse.y"
+{ yygotominor.yy280 = TK_AFTER; }
+#line 3034 "parse.c"
+ break;
+ case 271:
+#line 949 "parse.y"
+{ yygotominor.yy280 = TK_INSTEAD;}
+#line 3039 "parse.c"
+ break;
+ case 273:
+ case 274:
+ case 275:
+#line 954 "parse.y"
+{yygotominor.yy30.a = yymsp[0].major; yygotominor.yy30.b = 0;}
+#line 3046 "parse.c"
+ break;
+ case 276:
+#line 957 "parse.y"
+{yygotominor.yy30.a = TK_UPDATE; yygotominor.yy30.b = yymsp[0].minor.yy240;}
+#line 3051 "parse.c"
+ break;
+ case 277:
+ case 278:
+#line 960 "parse.y"
+{ yygotominor.yy280 = TK_ROW; }
+#line 3057 "parse.c"
+ break;
+ case 279:
+#line 962 "parse.y"
+{ yygotominor.yy280 = TK_STATEMENT; }
+#line 3062 "parse.c"
+ break;
+ case 280:
+#line 965 "parse.y"
+{ yygotominor.yy62 = 0; }
+#line 3067 "parse.c"
+ break;
+ case 281:
+#line 966 "parse.y"
+{ yygotominor.yy62 = yymsp[0].minor.yy62; }
+#line 3072 "parse.c"
+ break;
+ case 282:
+#line 970 "parse.y"
+{
+ yymsp[-2].minor.yy360->pNext = yymsp[0].minor.yy360;
+ yygotominor.yy360 = yymsp[-2].minor.yy360;
+}
+#line 3080 "parse.c"
+ break;
+ case 283:
+#line 974 "parse.y"
+{ yygotominor.yy360 = 0; }
+#line 3085 "parse.c"
+ break;
+ case 284:
+#line 980 "parse.y"
+{ yygotominor.yy360 = sqlite3TriggerUpdateStep(&yymsp[-3].minor.yy198, yymsp[-1].minor.yy418, yymsp[0].minor.yy62, yymsp[-4].minor.yy280); }
+#line 3090 "parse.c"
+ break;
+ case 285:
+#line 985 "parse.y"
+{yygotominor.yy360 = sqlite3TriggerInsertStep(&yymsp[-5].minor.yy198, yymsp[-4].minor.yy240, yymsp[-1].minor.yy418, 0, yymsp[-7].minor.yy280);}
+#line 3095 "parse.c"
+ break;
+ case 286:
+#line 988 "parse.y"
+{yygotominor.yy360 = sqlite3TriggerInsertStep(&yymsp[-2].minor.yy198, yymsp[-1].minor.yy240, 0, yymsp[0].minor.yy375, yymsp[-4].minor.yy280);}
+#line 3100 "parse.c"
+ break;
+ case 287:
+#line 992 "parse.y"
+{yygotominor.yy360 = sqlite3TriggerDeleteStep(&yymsp[-1].minor.yy198, yymsp[0].minor.yy62);}
+#line 3105 "parse.c"
+ break;
+ case 288:
+#line 995 "parse.y"
+{yygotominor.yy360 = sqlite3TriggerSelectStep(yymsp[0].minor.yy375); }
+#line 3110 "parse.c"
+ break;
+ case 289:
+#line 998 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_RAISE, 0, 0, 0);
+ yygotominor.yy62->iColumn = OE_Ignore;
+ sqlite3ExprSpan(yygotominor.yy62, &yymsp[-3].minor.yy0, &yymsp[0].minor.yy0);
+}
+#line 3119 "parse.c"
+ break;
+ case 290:
+#line 1003 "parse.y"
+{
+ yygotominor.yy62 = sqlite3Expr(TK_RAISE, 0, 0, &yymsp[-1].minor.yy198);
+ yygotominor.yy62->iColumn = yymsp[-3].minor.yy280;
+ sqlite3ExprSpan(yygotominor.yy62, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0);
+}
+#line 3128 "parse.c"
+ break;
+ case 291:
+#line 1011 "parse.y"
+{yygotominor.yy280 = OE_Rollback;}
+#line 3133 "parse.c"
+ break;
+ case 293:
+#line 1013 "parse.y"
+{yygotominor.yy280 = OE_Fail;}
+#line 3138 "parse.c"
+ break;
+ case 294:
+#line 1018 "parse.y"
+{
+ sqlite3DropTrigger(pParse,yymsp[0].minor.yy151);
+}
+#line 3145 "parse.c"
+ break;
+ case 295:
+#line 1024 "parse.y"
+{
+ sqlite3Attach(pParse, &yymsp[-3].minor.yy198, &yymsp[-1].minor.yy198, yymsp[0].minor.yy361.type, &yymsp[0].minor.yy361.key);
+}
+#line 3152 "parse.c"
+ break;
+ case 296:
+#line 1028 "parse.y"
+{ yygotominor.yy361.type = 0; }
+#line 3157 "parse.c"
+ break;
+ case 297:
+#line 1029 "parse.y"
+{ yygotominor.yy361.type=1; yygotominor.yy361.key = yymsp[0].minor.yy198; }
+#line 3162 "parse.c"
+ break;
+ case 298:
+#line 1030 "parse.y"
+{ yygotominor.yy361.type=2; yygotominor.yy361.key = yymsp[0].minor.yy0; }
+#line 3167 "parse.c"
+ break;
+ case 301:
+#line 1036 "parse.y"
+{
+ sqlite3Detach(pParse, &yymsp[0].minor.yy198);
+}
+#line 3174 "parse.c"
+ break;
+ case 302:
+#line 1042 "parse.y"
+{sqlite3Reindex(pParse, 0, 0);}
+#line 3179 "parse.c"
+ break;
+ case 303:
+#line 1043 "parse.y"
+{sqlite3Reindex(pParse, &yymsp[-1].minor.yy198, &yymsp[0].minor.yy198);}
+#line 3184 "parse.c"
+ break;
+ case 304:
+#line 1048 "parse.y"
+{sqlite3Analyze(pParse, 0, 0);}
+#line 3189 "parse.c"
+ break;
+ case 305:
+#line 1049 "parse.y"
+{sqlite3Analyze(pParse, &yymsp[-1].minor.yy198, &yymsp[0].minor.yy198);}
+#line 3194 "parse.c"
+ break;
+ case 306:
+#line 1054 "parse.y"
+{
+ sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy151,&yymsp[0].minor.yy198);
+}
+#line 3201 "parse.c"
+ break;
+ case 307:
+#line 1057 "parse.y"
+{
+ sqlite3AlterFinishAddColumn(pParse, &yymsp[0].minor.yy198);
+}
+#line 3208 "parse.c"
+ break;
+ case 308:
+#line 1060 "parse.y"
+{
+ sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy151);
+}
+#line 3215 "parse.c"
+ 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 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 */
+){
+ 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)
+#line 34 "parse.y"
+
+ if( pParse->zErrMsg==0 ){
+ if( TOKEN.z[0] ){
+ sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN);
+ }else{
+ sqlite3ErrorMsg(pParse, "incomplete SQL statement");
+ }
+ }
+#line 3282 "parse.c"
+ 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.
+*/
+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 */
+ 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; // not sure why this was here... */
+ 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 ){
+ 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/kexi/3rdparty/kexisql3/src/parse.h b/kexi/3rdparty/kexisql3/src/parse.h
new file mode 100644
index 000000000..fcedda589
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/parse.h
@@ -0,0 +1,145 @@
+#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_AGG_COLUMN 9
+#define TK_CONST_FUNC 10
+#define TK_SEMI 11
+#define TK_EXPLAIN 12
+#define TK_QUERY 13
+#define TK_PLAN 14
+#define TK_BEGIN 15
+#define TK_TRANSACTION 16
+#define TK_DEFERRED 17
+#define TK_IMMEDIATE 18
+#define TK_EXCLUSIVE 19
+#define TK_COMMIT 20
+#define TK_END 21
+#define TK_ROLLBACK 22
+#define TK_CREATE 23
+#define TK_TABLE 24
+#define TK_TEMP 25
+#define TK_LP 26
+#define TK_RP 27
+#define TK_AS 28
+#define TK_COMMA 29
+#define TK_ID 30
+#define TK_ABORT 31
+#define TK_AFTER 32
+#define TK_ANALYZE 33
+#define TK_ASC 34
+#define TK_ATTACH 35
+#define TK_BEFORE 36
+#define TK_CASCADE 37
+#define TK_CAST 38
+#define TK_CONFLICT 39
+#define TK_DATABASE 40
+#define TK_DESC 41
+#define TK_DETACH 42
+#define TK_EACH 43
+#define TK_FAIL 44
+#define TK_FOR 45
+#define TK_IGNORE 46
+#define TK_INITIALLY 47
+#define TK_INSTEAD 48
+#define TK_LIKE_KW 49
+#define TK_MATCH 50
+#define TK_KEY 51
+#define TK_OF 52
+#define TK_OFFSET 53
+#define TK_PRAGMA 54
+#define TK_RAISE 55
+#define TK_REPLACE 56
+#define TK_RESTRICT 57
+#define TK_ROW 58
+#define TK_STATEMENT 59
+#define TK_TRIGGER 60
+#define TK_VACUUM 61
+#define TK_VIEW 62
+#define TK_REINDEX 63
+#define TK_RENAME 64
+#define TK_CTIME_KW 65
+#define TK_ALTER 66
+#define TK_OR 67
+#define TK_AND 68
+#define TK_NOT 69
+#define TK_IS 70
+#define TK_BETWEEN 71
+#define TK_IN 72
+#define TK_ISNULL 73
+#define TK_NOTNULL 74
+#define TK_NE 75
+#define TK_EQ 76
+#define TK_GT 77
+#define TK_LE 78
+#define TK_LT 79
+#define TK_GE 80
+#define TK_ESCAPE 81
+#define TK_BITAND 82
+#define TK_BITOR 83
+#define TK_LSHIFT 84
+#define TK_RSHIFT 85
+#define TK_PLUS 86
+#define TK_MINUS 87
+#define TK_STAR 88
+#define TK_SLASH 89
+#define TK_REM 90
+#define TK_CONCAT 91
+#define TK_UMINUS 92
+#define TK_UPLUS 93
+#define TK_BITNOT 94
+#define TK_STRING 95
+#define TK_JOIN_KW 96
+#define TK_CONSTRAINT 97
+#define TK_DEFAULT 98
+#define TK_NULL 99
+#define TK_PRIMARY 100
+#define TK_UNIQUE 101
+#define TK_CHECK 102
+#define TK_REFERENCES 103
+#define TK_COLLATE 104
+#define TK_AUTOINCR 105
+#define TK_ON 106
+#define TK_DELETE 107
+#define TK_UPDATE 108
+#define TK_INSERT 109
+#define TK_SET 110
+#define TK_DEFERRABLE 111
+#define TK_FOREIGN 112
+#define TK_DROP 113
+#define TK_UNION 114
+#define TK_ALL 115
+#define TK_INTERSECT 116
+#define TK_EXCEPT 117
+#define TK_SELECT 118
+#define TK_DISTINCT 119
+#define TK_DOT 120
+#define TK_FROM 121
+#define TK_JOIN 122
+#define TK_USING 123
+#define TK_ORDER 124
+#define TK_BY 125
+#define TK_GROUP 126
+#define TK_HAVING 127
+#define TK_LIMIT 128
+#define TK_WHERE 129
+#define TK_INTO 130
+#define TK_VALUES 131
+#define TK_INTEGER 132
+#define TK_FLOAT 133
+#define TK_BLOB 134
+#define TK_REGISTER 135
+#define TK_VARIABLE 136
+#define TK_EXISTS 137
+#define TK_CASE 138
+#define TK_WHEN 139
+#define TK_THEN 140
+#define TK_ELSE 141
+#define TK_INDEX 142
+#define TK_TO 143
+#define TK_ADD 144
+#define TK_COLUMNKW 145
diff --git a/kexi/3rdparty/kexisql3/src/parse.y b/kexi/3rdparty/kexisql3/src/parse.y
new file mode 100644
index 000000000..b92d22767
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/parse.y
@@ -0,0 +1,915 @@
+/*
+** 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 SQLite's grammar for SQL. Process this file
+** using the lemon parser generator to generate C code that runs
+** the parser. Lemon will also generate a header file containing
+** numeric codes for all of the tokens.
+**
+** @(#) $Id: parse.y 410099 2005-05-06 17:52:07Z staniek $
+*/
+%token_prefix TK_
+%token_type {Token}
+%default_type {Token}
+%extra_argument {Parse *pParse}
+%syntax_error {
+ if( pParse->zErrMsg==0 ){
+ if( TOKEN.z[0] ){
+ sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN);
+ }else{
+ sqlite3ErrorMsg(pParse, "incomplete SQL statement");
+ }
+ }
+}
+%name sqlite3Parser
+%include {
+#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; };
+
+/*
+** An instance of this structure holds the ATTACH key and the key type.
+*/
+struct AttachKey { int type; Token key; };
+
+} // end %include
+
+// These are extra tokens used by the lexer but never seen by the
+// parser. We put them in a rule so that the parser generator will
+// add them to the parse.h output file.
+//
+%nonassoc END_OF_FILE ILLEGAL SPACE UNCLOSED_STRING COMMENT FUNCTION
+ COLUMN AGG_FUNCTION.
+
+// Input is a single SQL command
+input ::= cmdlist.
+cmdlist ::= cmdlist ecmd.
+cmdlist ::= ecmd.
+ecmd ::= explain cmdx SEMI.
+ecmd ::= SEMI.
+cmdx ::= cmd. { sqlite3FinishCoding(pParse); }
+explain ::= EXPLAIN. { sqlite3BeginParse(pParse, 1); }
+explain ::= . { sqlite3BeginParse(pParse, 0); }
+
+///////////////////// Begin and end transactions. ////////////////////////////
+//
+
+cmd ::= BEGIN trans_opt. {sqlite3BeginTransaction(pParse);}
+trans_opt ::= .
+trans_opt ::= TRANSACTION.
+trans_opt ::= TRANSACTION nm.
+cmd ::= COMMIT trans_opt. {sqlite3CommitTransaction(pParse);}
+cmd ::= END trans_opt. {sqlite3CommitTransaction(pParse);}
+cmd ::= ROLLBACK trans_opt. {sqlite3RollbackTransaction(pParse);}
+
+///////////////////// The CREATE TABLE statement ////////////////////////////
+//
+cmd ::= create_table create_table_args.
+create_table ::= CREATE(X) temp(T) TABLE nm(Y) dbnm(Z). {
+ sqlite3StartTable(pParse,&X,&Y,&Z,T,0);
+}
+%type temp {int}
+temp(A) ::= TEMP. {A = 1;}
+temp(A) ::= . {A = 0;}
+create_table_args ::= LP columnlist conslist_opt RP(X). {
+ sqlite3EndTable(pParse,&X,0);
+}
+create_table_args ::= AS select(S). {
+ sqlite3EndTable(pParse,0,S);
+ sqlite3SelectDelete(S);
+}
+columnlist ::= columnlist COMMA column.
+columnlist ::= column.
+
+// About the only information used for a column is the name of the
+// column. The type is always just "text". But the code will accept
+// an elaborate typename. Perhaps someday we'll do something with it.
+//
+column ::= columnid type carglist.
+columnid ::= nm(X). {sqlite3AddColumn(pParse,&X);}
+
+// An IDENTIFIER can be a generic identifier, or one of several
+// keywords. Any non-standard keyword can also be an identifier.
+//
+%type id {Token}
+id(A) ::= ID(X). {A = X;}
+
+// The following directive causes tokens ABORT, AFTER, ASC, etc. to
+// fallback to ID if they will not parse as their original value.
+// This obviates the need for the "id" nonterminal.
+//
+%fallback ID
+ ABORT AFTER ASC ATTACH BEFORE BEGIN CASCADE CLUSTER CONFLICT
+ DATABASE DEFERRED DESC DETACH EACH END EXPLAIN FAIL FOR
+ GLOB IGNORE IMMEDIATE INITIALLY INSTEAD LIKE MATCH KEY
+ OF OFFSET PRAGMA RAISE REPLACE RESTRICT ROW STATEMENT
+ TEMP TRIGGER VACUUM VIEW.
+
+// Define operator precedence early so that this is the first occurance
+// of the operator tokens in the grammer. Keeping the operators together
+// causes them to be assigned integer values that are close together,
+// which keeps parser tables smaller.
+//
+%left OR.
+%left AND.
+%right NOT.
+%left EQ NE ISNULL NOTNULL IS LIKE GLOB BETWEEN IN.
+%left GT GE LT LE.
+%left BITAND BITOR LSHIFT RSHIFT.
+%left PLUS MINUS.
+%left STAR SLASH REM.
+%left CONCAT.
+%right UMINUS UPLUS BITNOT.
+
+// And "ids" is an identifer-or-string.
+//
+%type ids {Token}
+ids(A) ::= ID(X). {A = X;}
+ids(A) ::= STRING(X). {A = X;}
+
+// The name of a column or table can be any of the following:
+//
+%type nm {Token}
+nm(A) ::= ID(X). {A = X;}
+nm(A) ::= STRING(X). {A = X;}
+nm(A) ::= JOIN_KW(X). {A = X;}
+
+type ::= .
+type ::= typename(X). {sqlite3AddColumnType(pParse,&X,&X);}
+type ::= typename(X) LP signed RP(Y). {sqlite3AddColumnType(pParse,&X,&Y);}
+type ::= typename(X) LP signed COMMA signed RP(Y).
+ {sqlite3AddColumnType(pParse,&X,&Y);}
+%type typename {Token}
+typename(A) ::= ids(X). {A = X;}
+typename(A) ::= typename(X) ids(Y). {A.z=X.z; A.n=X.n+Addr(Y.z)-Addr(X.z);}
+%type signed {int}
+signed(A) ::= INTEGER(X). { A = atoi(X.z); }
+signed(A) ::= PLUS INTEGER(X). { A = atoi(X.z); }
+signed(A) ::= MINUS INTEGER(X). { A = -atoi(X.z); }
+carglist ::= carglist carg.
+carglist ::= .
+carg ::= CONSTRAINT nm ccons.
+carg ::= ccons.
+carg ::= DEFAULT STRING(X). {sqlite3AddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT ID(X). {sqlite3AddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT INTEGER(X). {sqlite3AddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT PLUS INTEGER(X). {sqlite3AddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT MINUS INTEGER(X). {sqlite3AddDefaultValue(pParse,&X,1);}
+carg ::= DEFAULT FLOAT(X). {sqlite3AddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT PLUS FLOAT(X). {sqlite3AddDefaultValue(pParse,&X,0);}
+carg ::= DEFAULT MINUS FLOAT(X). {sqlite3AddDefaultValue(pParse,&X,1);}
+carg ::= DEFAULT NULL.
+
+// In addition to the type name, we also care about the primary key and
+// UNIQUE constraints.
+//
+ccons ::= NULL onconf.
+ccons ::= NOT NULL onconf(R). {sqlite3AddNotNull(pParse, R);}
+ccons ::= PRIMARY KEY sortorder onconf(R). {sqlite3AddPrimaryKey(pParse,0,R);}
+ccons ::= UNIQUE onconf(R). {sqlite3CreateIndex(pParse,0,0,0,0,R,0,0);}
+ccons ::= CHECK LP expr RP onconf.
+ccons ::= REFERENCES nm(T) idxlist_opt(TA) refargs(R).
+ {sqlite3CreateForeignKey(pParse,0,&T,TA,R);}
+ccons ::= defer_subclause(D). {sqlite3DeferForeignKey(pParse,D);}
+ccons ::= COLLATE id(C). {sqlite3AddCollateType(pParse, C.z, C.n);}
+
+// The next group of rules parses the arguments to a REFERENCES clause
+// that determine if the referential integrity checking is deferred or
+// or immediate and which determine what action to take if a ref-integ
+// check fails.
+//
+%type refargs {int}
+refargs(A) ::= . { A = OE_Restrict * 0x010101; }
+refargs(A) ::= refargs(X) refarg(Y). { A = (X & Y.mask) | Y.value; }
+%type refarg {struct {int value; int mask;}}
+refarg(A) ::= MATCH nm. { A.value = 0; A.mask = 0x000000; }
+refarg(A) ::= ON DELETE refact(X). { A.value = X; A.mask = 0x0000ff; }
+refarg(A) ::= ON UPDATE refact(X). { A.value = X<<8; A.mask = 0x00ff00; }
+refarg(A) ::= ON INSERT refact(X). { A.value = X<<16; A.mask = 0xff0000; }
+%type refact {int}
+refact(A) ::= SET NULL. { A = OE_SetNull; }
+refact(A) ::= SET DEFAULT. { A = OE_SetDflt; }
+refact(A) ::= CASCADE. { A = OE_Cascade; }
+refact(A) ::= RESTRICT. { A = OE_Restrict; }
+%type defer_subclause {int}
+defer_subclause(A) ::= NOT DEFERRABLE init_deferred_pred_opt(X). {A = X;}
+defer_subclause(A) ::= DEFERRABLE init_deferred_pred_opt(X). {A = X;}
+%type init_deferred_pred_opt {int}
+init_deferred_pred_opt(A) ::= . {A = 0;}
+init_deferred_pred_opt(A) ::= INITIALLY DEFERRED. {A = 1;}
+init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE. {A = 0;}
+
+// For the time being, the only constraint we care about is the primary
+// key and UNIQUE. Both create indices.
+//
+conslist_opt ::= .
+conslist_opt ::= COMMA conslist.
+conslist ::= conslist COMMA tcons.
+conslist ::= conslist tcons.
+conslist ::= tcons.
+tcons ::= CONSTRAINT nm.
+tcons ::= PRIMARY KEY LP idxlist(X) RP onconf(R).
+ {sqlite3AddPrimaryKey(pParse,X,R);}
+tcons ::= UNIQUE LP idxlist(X) RP onconf(R).
+ {sqlite3CreateIndex(pParse,0,0,0,X,R,0,0);}
+tcons ::= CHECK expr onconf.
+tcons ::= FOREIGN KEY LP idxlist(FA) RP
+ REFERENCES nm(T) idxlist_opt(TA) refargs(R) defer_subclause_opt(D). {
+ sqlite3CreateForeignKey(pParse, FA, &T, TA, R);
+ sqlite3DeferForeignKey(pParse, D);
+}
+%type defer_subclause_opt {int}
+defer_subclause_opt(A) ::= . {A = 0;}
+defer_subclause_opt(A) ::= defer_subclause(X). {A = X;}
+
+// The following is a non-standard extension that allows us to declare the
+// default behavior when there is a constraint conflict.
+//
+%type onconf {int}
+%type orconf {int}
+%type resolvetype {int}
+onconf(A) ::= . { A = OE_Default; }
+onconf(A) ::= ON CONFLICT resolvetype(X). { A = X; }
+orconf(A) ::= . { A = OE_Default; }
+orconf(A) ::= OR resolvetype(X). { A = X; }
+resolvetype(A) ::= ROLLBACK. { A = OE_Rollback; }
+resolvetype(A) ::= ABORT. { A = OE_Abort; }
+resolvetype(A) ::= FAIL. { A = OE_Fail; }
+resolvetype(A) ::= IGNORE. { A = OE_Ignore; }
+resolvetype(A) ::= REPLACE. { A = OE_Replace; }
+
+////////////////////////// The DROP TABLE /////////////////////////////////////
+//
+cmd ::= DROP TABLE nm(X) dbnm(Y). {
+ sqlite3DropTable(pParse, sqlite3SrcListAppend(0,&X,&Y), 0);
+}
+
+///////////////////// The CREATE VIEW statement /////////////////////////////
+//
+cmd ::= CREATE(X) temp(T) VIEW nm(Y) dbnm(Z) AS select(S). {
+ sqlite3CreateView(pParse, &X, &Y, &Z, S, T);
+}
+cmd ::= DROP VIEW nm(X) dbnm(Y). {
+ sqlite3DropTable(pParse, sqlite3SrcListAppend(0,&X,&Y), 1);
+}
+
+//////////////////////// The SELECT statement /////////////////////////////////
+//
+cmd ::= select(X). {
+ sqlite3Select(pParse, X, SRT_Callback, 0, 0, 0, 0, 0);
+ sqlite3SelectDelete(X);
+}
+
+%type select {Select*}
+%destructor select {sqlite3SelectDelete($$);}
+%type oneselect {Select*}
+%destructor oneselect {sqlite3SelectDelete($$);}
+
+select(A) ::= oneselect(X). {A = X;}
+select(A) ::= select(X) multiselect_op(Y) oneselect(Z). {
+ if( Z ){
+ Z->op = Y;
+ Z->pPrior = X;
+ }
+ A = Z;
+}
+%type multiselect_op {int}
+multiselect_op(A) ::= UNION. {A = TK_UNION;}
+multiselect_op(A) ::= UNION ALL. {A = TK_ALL;}
+multiselect_op(A) ::= INTERSECT. {A = TK_INTERSECT;}
+multiselect_op(A) ::= EXCEPT. {A = TK_EXCEPT;}
+oneselect(A) ::= SELECT distinct(D) selcollist(W) from(X) where_opt(Y)
+ groupby_opt(P) having_opt(Q) orderby_opt(Z) limit_opt(L). {
+ A = sqlite3SelectNew(W,X,Y,P,Q,Z,D,L.limit,L.offset);
+}
+
+// The "distinct" nonterminal is true (1) if the DISTINCT keyword is
+// present and false (0) if it is not.
+//
+%type distinct {int}
+distinct(A) ::= DISTINCT. {A = 1;}
+distinct(A) ::= ALL. {A = 0;}
+distinct(A) ::= . {A = 0;}
+
+// selcollist is a list of expressions that are to become the return
+// values of the SELECT statement. The "*" in statements like
+// "SELECT * FROM ..." is encoded as a special expression with an
+// opcode of TK_ALL.
+//
+%type selcollist {ExprList*}
+%destructor selcollist {sqlite3ExprListDelete($$);}
+%type sclp {ExprList*}
+%destructor sclp {sqlite3ExprListDelete($$);}
+sclp(A) ::= selcollist(X) COMMA. {A = X;}
+sclp(A) ::= . {A = 0;}
+selcollist(A) ::= sclp(P) expr(X) as(Y). {
+ A = sqlite3ExprListAppend(P,X,Y.n?&Y:0);
+}
+selcollist(A) ::= sclp(P) STAR. {
+ A = sqlite3ExprListAppend(P, sqlite3Expr(TK_ALL, 0, 0, 0), 0);
+}
+selcollist(A) ::= sclp(P) nm(X) DOT STAR. {
+ Expr *pRight = sqlite3Expr(TK_ALL, 0, 0, 0);
+ Expr *pLeft = sqlite3Expr(TK_ID, 0, 0, &X);
+ A = sqlite3ExprListAppend(P, sqlite3Expr(TK_DOT, pLeft, pRight, 0), 0);
+}
+
+// An option "AS <id>" phrase that can follow one of the expressions that
+// define the result set, or one of the tables in the FROM clause.
+//
+%type as {Token}
+as(X) ::= AS nm(Y). { X = Y; }
+as(X) ::= ids(Y). { X = Y; }
+as(X) ::= . { X.n = 0; }
+
+
+%type seltablist {SrcList*}
+%destructor seltablist {sqlite3SrcListDelete($$);}
+%type stl_prefix {SrcList*}
+%destructor stl_prefix {sqlite3SrcListDelete($$);}
+%type from {SrcList*}
+%destructor from {sqlite3SrcListDelete($$);}
+
+// A complete FROM clause.
+//
+from(A) ::= . {A = sqliteMalloc(sizeof(*A));}
+from(A) ::= FROM seltablist(X). {A = X;}
+
+// "seltablist" is a "Select Table List" - the content of the FROM clause
+// in a SELECT statement. "stl_prefix" is a prefix of this list.
+//
+stl_prefix(A) ::= seltablist(X) joinop(Y). {
+ A = X;
+ if( A && A->nSrc>0 ) A->a[A->nSrc-1].jointype = Y;
+}
+stl_prefix(A) ::= . {A = 0;}
+seltablist(A) ::= stl_prefix(X) nm(Y) dbnm(D) as(Z) on_opt(N) using_opt(U). {
+ A = sqlite3SrcListAppend(X,&Y,&D);
+ if( Z.n ) sqlite3SrcListAddAlias(A,&Z);
+ if( N ){
+ if( A && A->nSrc>1 ){ A->a[A->nSrc-2].pOn = N; }
+ else { sqlite3ExprDelete(N); }
+ }
+ if( U ){
+ if( A && A->nSrc>1 ){ A->a[A->nSrc-2].pUsing = U; }
+ else { sqlite3IdListDelete(U); }
+ }
+}
+seltablist(A) ::= stl_prefix(X) LP seltablist_paren(S) RP
+ as(Z) on_opt(N) using_opt(U). {
+ A = sqlite3SrcListAppend(X,0,0);
+ A->a[A->nSrc-1].pSelect = S;
+ if( Z.n ) sqlite3SrcListAddAlias(A,&Z);
+ if( N ){
+ if( A && A->nSrc>1 ){ A->a[A->nSrc-2].pOn = N; }
+ else { sqlite3ExprDelete(N); }
+ }
+ if( U ){
+ if( A && A->nSrc>1 ){ A->a[A->nSrc-2].pUsing = U; }
+ else { sqlite3IdListDelete(U); }
+ }
+}
+
+// A seltablist_paren nonterminal represents anything in a FROM that
+// is contained inside parentheses. This can be either a subquery or
+// a grouping of table and subqueries.
+//
+%type seltablist_paren {Select*}
+%destructor seltablist_paren {sqlite3SelectDelete($$);}
+seltablist_paren(A) ::= select(S). {A = S;}
+seltablist_paren(A) ::= seltablist(F). {
+ A = sqlite3SelectNew(0,F,0,0,0,0,0,-1,0);
+}
+
+%type dbnm {Token}
+dbnm(A) ::= . {A.z=0; A.n=0;}
+dbnm(A) ::= DOT nm(X). {A = X;}
+
+%type joinop {int}
+%type joinop2 {int}
+joinop(X) ::= COMMA. { X = JT_INNER; }
+joinop(X) ::= JOIN. { X = JT_INNER; }
+joinop(X) ::= JOIN_KW(A) JOIN. { X = sqlite3JoinType(pParse,&A,0,0); }
+joinop(X) ::= JOIN_KW(A) nm(B) JOIN. { X = sqlite3JoinType(pParse,&A,&B,0); }
+joinop(X) ::= JOIN_KW(A) nm(B) nm(C) JOIN.
+ { X = sqlite3JoinType(pParse,&A,&B,&C); }
+
+%type on_opt {Expr*}
+%destructor on_opt {sqlite3ExprDelete($$);}
+on_opt(N) ::= ON expr(E). {N = E;}
+on_opt(N) ::= . {N = 0;}
+
+%type using_opt {IdList*}
+%destructor using_opt {sqlite3IdListDelete($$);}
+using_opt(U) ::= USING LP inscollist(L) RP. {U = L;}
+using_opt(U) ::= . {U = 0;}
+
+
+%type orderby_opt {ExprList*}
+%destructor orderby_opt {sqlite3ExprListDelete($$);}
+%type sortlist {ExprList*}
+%destructor sortlist {sqlite3ExprListDelete($$);}
+%type sortitem {Expr*}
+%destructor sortitem {sqlite3ExprDelete($$);}
+
+orderby_opt(A) ::= . {A = 0;}
+orderby_opt(A) ::= ORDER BY sortlist(X). {A = X;}
+sortlist(A) ::= sortlist(X) COMMA sortitem(Y) collate(C) sortorder(Z). {
+ A = sqlite3ExprListAppend(X,Y,C.n>0?&C:0);
+ if( A ) A->a[A->nExpr-1].sortOrder = Z;
+}
+sortlist(A) ::= sortitem(Y) collate(C) sortorder(Z). {
+ A = sqlite3ExprListAppend(0,Y,C.n>0?&C:0);
+ if( A && A->a ) A->a[0].sortOrder = Z;
+}
+sortitem(A) ::= expr(X). {A = X;}
+
+%type sortorder {int}
+%type collate {Token}
+
+sortorder(A) ::= ASC. {A = SQLITE_SO_ASC;}
+sortorder(A) ::= DESC. {A = SQLITE_SO_DESC;}
+sortorder(A) ::= . {A = SQLITE_SO_ASC;}
+collate(C) ::= . {C.z = 0; C.n = 0;}
+collate(C) ::= COLLATE id(X). {C = X;}
+
+%type groupby_opt {ExprList*}
+%destructor groupby_opt {sqlite3ExprListDelete($$);}
+groupby_opt(A) ::= . {A = 0;}
+groupby_opt(A) ::= GROUP BY exprlist(X). {A = X;}
+
+%type having_opt {Expr*}
+%destructor having_opt {sqlite3ExprDelete($$);}
+having_opt(A) ::= . {A = 0;}
+having_opt(A) ::= HAVING expr(X). {A = X;}
+
+%type limit_opt {struct LimitVal}
+limit_opt(A) ::= . {A.limit = -1; A.offset = 0;}
+limit_opt(A) ::= LIMIT signed(X). {A.limit = X; A.offset = 0;}
+limit_opt(A) ::= LIMIT signed(X) OFFSET signed(Y).
+ {A.limit = X; A.offset = Y;}
+limit_opt(A) ::= LIMIT signed(X) COMMA signed(Y).
+ {A.limit = Y; A.offset = X;}
+
+/////////////////////////// The DELETE statement /////////////////////////////
+//
+cmd ::= DELETE FROM nm(X) dbnm(D) where_opt(Y). {
+ sqlite3DeleteFrom(pParse, sqlite3SrcListAppend(0,&X,&D), Y);
+}
+
+%type where_opt {Expr*}
+%destructor where_opt {sqlite3ExprDelete($$);}
+
+where_opt(A) ::= . {A = 0;}
+where_opt(A) ::= WHERE expr(X). {A = X;}
+
+%type setlist {ExprList*}
+%destructor setlist {sqlite3ExprListDelete($$);}
+
+////////////////////////// The UPDATE command ////////////////////////////////
+//
+cmd ::= UPDATE orconf(R) nm(X) dbnm(D) SET setlist(Y) where_opt(Z).
+ {sqlite3Update(pParse,sqlite3SrcListAppend(0,&X,&D),Y,Z,R);}
+
+setlist(A) ::= setlist(Z) COMMA nm(X) EQ expr(Y).
+ {A = sqlite3ExprListAppend(Z,Y,&X);}
+setlist(A) ::= nm(X) EQ expr(Y). {A = sqlite3ExprListAppend(0,Y,&X);}
+
+////////////////////////// The INSERT command /////////////////////////////////
+//
+cmd ::= insert_cmd(R) INTO nm(X) dbnm(D) inscollist_opt(F)
+ VALUES LP itemlist(Y) RP.
+ {sqlite3Insert(pParse, sqlite3SrcListAppend(0,&X,&D), Y, 0, F, R);}
+cmd ::= insert_cmd(R) INTO nm(X) dbnm(D) inscollist_opt(F) select(S).
+ {sqlite3Insert(pParse, sqlite3SrcListAppend(0,&X,&D), 0, S, F, R);}
+
+%type insert_cmd {int}
+insert_cmd(A) ::= INSERT orconf(R). {A = R;}
+insert_cmd(A) ::= REPLACE. {A = OE_Replace;}
+
+
+%type itemlist {ExprList*}
+%destructor itemlist {sqlite3ExprListDelete($$);}
+
+itemlist(A) ::= itemlist(X) COMMA expr(Y). {A = sqlite3ExprListAppend(X,Y,0);}
+itemlist(A) ::= expr(X). {A = sqlite3ExprListAppend(0,X,0);}
+
+%type inscollist_opt {IdList*}
+%destructor inscollist_opt {sqlite3IdListDelete($$);}
+%type inscollist {IdList*}
+%destructor inscollist {sqlite3IdListDelete($$);}
+
+inscollist_opt(A) ::= . {A = 0;}
+inscollist_opt(A) ::= LP inscollist(X) RP. {A = X;}
+inscollist(A) ::= inscollist(X) COMMA nm(Y). {A = sqlite3IdListAppend(X,&Y);}
+inscollist(A) ::= nm(Y). {A = sqlite3IdListAppend(0,&Y);}
+
+/////////////////////////// Expression Processing /////////////////////////////
+//
+
+%type expr {Expr*}
+%destructor expr {sqlite3ExprDelete($$);}
+
+expr(A) ::= LP(B) expr(X) RP(E). {A = X; sqlite3ExprSpan(A,&B,&E); }
+expr(A) ::= NULL(X). {A = sqlite3Expr(@X, 0, 0, &X);}
+expr(A) ::= ID(X). {A = sqlite3Expr(TK_ID, 0, 0, &X);}
+expr(A) ::= JOIN_KW(X). {A = sqlite3Expr(TK_ID, 0, 0, &X);}
+expr(A) ::= nm(X) DOT nm(Y). {
+ Expr *temp1 = sqlite3Expr(TK_ID, 0, 0, &X);
+ Expr *temp2 = sqlite3Expr(TK_ID, 0, 0, &Y);
+ A = sqlite3Expr(TK_DOT, temp1, temp2, 0);
+}
+expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). {
+ Expr *temp1 = sqlite3Expr(TK_ID, 0, 0, &X);
+ Expr *temp2 = sqlite3Expr(TK_ID, 0, 0, &Y);
+ Expr *temp3 = sqlite3Expr(TK_ID, 0, 0, &Z);
+ Expr *temp4 = sqlite3Expr(TK_DOT, temp2, temp3, 0);
+ A = sqlite3Expr(TK_DOT, temp1, temp4, 0);
+}
+expr(A) ::= INTEGER(X). {A = sqlite3Expr(@X, 0, 0, &X);}
+expr(A) ::= FLOAT(X). {A = sqlite3Expr(@X, 0, 0, &X);}
+expr(A) ::= STRING(X). {A = sqlite3Expr(@X, 0, 0, &X);}
+expr(A) ::= BLOB(X). {A = sqlite3Expr(@X, 0, 0, &X);}
+expr(A) ::= VARIABLE(X). {
+ Token *pToken = &X;
+ Expr *pExpr = A = sqlite3Expr(TK_VARIABLE, 0, 0, pToken);
+ sqlite3ExprAssignVarNumber(pParse, pExpr);
+}
+expr(A) ::= ID(X) LP exprlist(Y) RP(E). {
+ A = sqlite3ExprFunction(Y, &X);
+ sqlite3ExprSpan(A,&X,&E);
+}
+expr(A) ::= ID(X) LP STAR RP(E). {
+ A = sqlite3ExprFunction(0, &X);
+ sqlite3ExprSpan(A,&X,&E);
+}
+expr(A) ::= expr(X) AND(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) OR(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) LT(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) GT(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) LE(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) GE(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) NE(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) EQ(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) BITAND(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) BITOR(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) LSHIFT(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) RSHIFT(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) likeop(OP) expr(Y). [LIKE] {
+ ExprList *pList = sqlite3ExprListAppend(0, Y, 0);
+ pList = sqlite3ExprListAppend(pList, X, 0);
+ A = sqlite3ExprFunction(pList, 0);
+ if( A ) A->op = OP;
+ sqlite3ExprSpan(A, &X->span, &Y->span);
+}
+expr(A) ::= expr(X) NOT likeop(OP) expr(Y). [LIKE] {
+ ExprList *pList = sqlite3ExprListAppend(0, Y, 0);
+ pList = sqlite3ExprListAppend(pList, X, 0);
+ A = sqlite3ExprFunction(pList, 0);
+ if( A ) A->op = OP;
+ A = sqlite3Expr(TK_NOT, A, 0, 0);
+ sqlite3ExprSpan(A,&X->span,&Y->span);
+}
+%type likeop {int}
+likeop(A) ::= LIKE. {A = TK_LIKE;}
+likeop(A) ::= GLOB. {A = TK_GLOB;}
+expr(A) ::= expr(X) PLUS(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) MINUS(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) STAR(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) SLASH(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) REM(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) CONCAT(OP) expr(Y). {A = sqlite3Expr(@OP, X, Y, 0);}
+expr(A) ::= expr(X) ISNULL(E). {
+ A = sqlite3Expr(TK_ISNULL, X, 0, 0);
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) IS NULL(E). {
+ A = sqlite3Expr(TK_ISNULL, X, 0, 0);
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) NOTNULL(E). {
+ A = sqlite3Expr(TK_NOTNULL, X, 0, 0);
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) NOT NULL(E). {
+ A = sqlite3Expr(TK_NOTNULL, X, 0, 0);
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) IS NOT NULL(E). {
+ A = sqlite3Expr(TK_NOTNULL, X, 0, 0);
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= NOT(B) expr(X). {
+ A = sqlite3Expr(@B, X, 0, 0);
+ sqlite3ExprSpan(A,&B,&X->span);
+}
+expr(A) ::= BITNOT(B) expr(X). {
+ A = sqlite3Expr(@B, X, 0, 0);
+ sqlite3ExprSpan(A,&B,&X->span);
+}
+expr(A) ::= MINUS(B) expr(X). [UMINUS] {
+ A = sqlite3Expr(TK_UMINUS, X, 0, 0);
+ sqlite3ExprSpan(A,&B,&X->span);
+}
+expr(A) ::= PLUS(B) expr(X). [UPLUS] {
+ A = sqlite3Expr(TK_UPLUS, X, 0, 0);
+ sqlite3ExprSpan(A,&B,&X->span);
+}
+expr(A) ::= LP(B) select(X) RP(E). {
+ A = sqlite3Expr(TK_SELECT, 0, 0, 0);
+ if( A ) A->pSelect = X;
+ sqlite3ExprSpan(A,&B,&E);
+}
+expr(A) ::= expr(W) BETWEEN expr(X) AND expr(Y). {
+ ExprList *pList = sqlite3ExprListAppend(0, X, 0);
+ pList = sqlite3ExprListAppend(pList, Y, 0);
+ A = sqlite3Expr(TK_BETWEEN, W, 0, 0);
+ if( A ) A->pList = pList;
+ sqlite3ExprSpan(A,&W->span,&Y->span);
+}
+expr(A) ::= expr(W) NOT BETWEEN expr(X) AND expr(Y). {
+ ExprList *pList = sqlite3ExprListAppend(0, X, 0);
+ pList = sqlite3ExprListAppend(pList, Y, 0);
+ A = sqlite3Expr(TK_BETWEEN, W, 0, 0);
+ if( A ) A->pList = pList;
+ A = sqlite3Expr(TK_NOT, A, 0, 0);
+ sqlite3ExprSpan(A,&W->span,&Y->span);
+}
+expr(A) ::= expr(X) IN LP exprlist(Y) RP(E). {
+ A = sqlite3Expr(TK_IN, X, 0, 0);
+ if( A ) A->pList = Y;
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) IN LP select(Y) RP(E). {
+ A = sqlite3Expr(TK_IN, X, 0, 0);
+ if( A ) A->pSelect = Y;
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) NOT IN LP exprlist(Y) RP(E). {
+ A = sqlite3Expr(TK_IN, X, 0, 0);
+ if( A ) A->pList = Y;
+ A = sqlite3Expr(TK_NOT, A, 0, 0);
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) NOT IN LP select(Y) RP(E). {
+ A = sqlite3Expr(TK_IN, X, 0, 0);
+ if( A ) A->pSelect = Y;
+ A = sqlite3Expr(TK_NOT, A, 0, 0);
+ sqlite3ExprSpan(A,&X->span,&E);
+}
+expr(A) ::= expr(X) IN nm(Y) dbnm(D). {
+ SrcList *pSrc = sqlite3SrcListAppend(0, &Y, &D);
+ A = sqlite3Expr(TK_IN, X, 0, 0);
+ if( A ) A->pSelect = sqlite3SelectNew(0,pSrc,0,0,0,0,0,-1,0);
+ sqlite3ExprSpan(A,&X->span,D.z?&D:&Y);
+}
+expr(A) ::= expr(X) NOT IN nm(Y) dbnm(D). {
+ SrcList *pSrc = sqlite3SrcListAppend(0, &Y, &D);
+ A = sqlite3Expr(TK_IN, X, 0, 0);
+ if( A ) A->pSelect = sqlite3SelectNew(0,pSrc,0,0,0,0,0,-1,0);
+ A = sqlite3Expr(TK_NOT, A, 0, 0);
+ sqlite3ExprSpan(A,&X->span,D.z?&D:&Y);
+}
+
+
+/* CASE expressions */
+expr(A) ::= CASE(C) case_operand(X) case_exprlist(Y) case_else(Z) END(E). {
+ A = sqlite3Expr(TK_CASE, X, Z, 0);
+ if( A ) A->pList = Y;
+ sqlite3ExprSpan(A, &C, &E);
+}
+%type case_exprlist {ExprList*}
+%destructor case_exprlist {sqlite3ExprListDelete($$);}
+case_exprlist(A) ::= case_exprlist(X) WHEN expr(Y) THEN expr(Z). {
+ A = sqlite3ExprListAppend(X, Y, 0);
+ A = sqlite3ExprListAppend(A, Z, 0);
+}
+case_exprlist(A) ::= WHEN expr(Y) THEN expr(Z). {
+ A = sqlite3ExprListAppend(0, Y, 0);
+ A = sqlite3ExprListAppend(A, Z, 0);
+}
+%type case_else {Expr*}
+case_else(A) ::= ELSE expr(X). {A = X;}
+case_else(A) ::= . {A = 0;}
+%type case_operand {Expr*}
+case_operand(A) ::= expr(X). {A = X;}
+case_operand(A) ::= . {A = 0;}
+
+%type exprlist {ExprList*}
+%destructor exprlist {sqlite3ExprListDelete($$);}
+%type expritem {Expr*}
+%destructor expritem {sqlite3ExprDelete($$);}
+
+exprlist(A) ::= exprlist(X) COMMA expritem(Y).
+ {A = sqlite3ExprListAppend(X,Y,0);}
+exprlist(A) ::= expritem(X). {A = sqlite3ExprListAppend(0,X,0);}
+expritem(A) ::= expr(X). {A = X;}
+expritem(A) ::= . {A = 0;}
+
+///////////////////////////// The CREATE INDEX command ///////////////////////
+//
+cmd ::= CREATE(S) uniqueflag(U) INDEX nm(X) dbnm(D)
+ ON nm(Y) dbnm(C) LP idxlist(Z) RP(E) onconf(R). {
+ if( U!=OE_None ) U = R;
+ if( U==OE_Default) U = OE_Abort;
+ sqlite3CreateIndex(pParse, &X, &D, sqlite3SrcListAppend(0,&Y,&C),
+ Z, U, &S, &E);
+}
+
+%type uniqueflag {int}
+uniqueflag(A) ::= UNIQUE. { A = OE_Abort; }
+uniqueflag(A) ::= . { A = OE_None; }
+
+%type idxlist {ExprList*}
+%destructor idxlist {sqlite3ExprListDelete($$);}
+%type idxlist_opt {ExprList*}
+%destructor idxlist_opt {sqlite3ExprListDelete($$);}
+%type idxitem {Token}
+
+idxlist_opt(A) ::= . {A = 0;}
+idxlist_opt(A) ::= LP idxlist(X) RP. {A = X;}
+idxlist(A) ::= idxlist(X) COMMA idxitem(Y) collate(C) sortorder. {
+ Expr *p = 0;
+ if( C.n>0 ){
+ p = sqlite3Expr(TK_COLUMN, 0, 0, 0);
+ if( p ) p->pColl = sqlite3LocateCollSeq(pParse, C.z, C.n);
+ }
+ A = sqlite3ExprListAppend(X, p, &Y);
+}
+idxlist(A) ::= idxitem(Y) collate(C) sortorder. {
+ Expr *p = 0;
+ if( C.n>0 ){
+ p = sqlite3Expr(TK_COLUMN, 0, 0, 0);
+ if( p ) p->pColl = sqlite3LocateCollSeq(pParse, C.z, C.n);
+ }
+ A = sqlite3ExprListAppend(0, p, &Y);
+}
+idxitem(A) ::= nm(X). {A = X;}
+
+
+///////////////////////////// The DROP INDEX command /////////////////////////
+//
+
+cmd ::= DROP INDEX nm(X) dbnm(Y). {
+ sqlite3DropIndex(pParse, sqlite3SrcListAppend(0,&X,&Y));
+}
+
+///////////////////////////// The VACUUM command /////////////////////////////
+//
+cmd ::= VACUUM. {sqlite3Vacuum(pParse,0);}
+cmd ::= VACUUM nm(X). {sqlite3Vacuum(pParse,&X);}
+
+///////////////////////////// The PRAGMA command /////////////////////////////
+//
+cmd ::= PRAGMA nm(X) dbnm(Z) EQ nm(Y). {sqlite3Pragma(pParse,&X,&Z,&Y,0);}
+cmd ::= PRAGMA nm(X) dbnm(Z) EQ ON(Y). {sqlite3Pragma(pParse,&X,&Z,&Y,0);}
+cmd ::= PRAGMA nm(X) dbnm(Z) EQ plus_num(Y). {sqlite3Pragma(pParse,&X,&Z,&Y,0);}
+cmd ::= PRAGMA nm(X) dbnm(Z) EQ minus_num(Y). {
+ sqlite3Pragma(pParse,&X,&Z,&Y,1);
+}
+cmd ::= PRAGMA nm(X) dbnm(Z) LP nm(Y) RP. {sqlite3Pragma(pParse,&X,&Z,&Y,0);}
+cmd ::= PRAGMA nm(X) dbnm(Z). {sqlite3Pragma(pParse,&X,&Z,0,0);}
+plus_num(A) ::= plus_opt number(X). {A = X;}
+minus_num(A) ::= MINUS number(X). {A = X;}
+number(A) ::= INTEGER(X). {A = X;}
+number(A) ::= FLOAT(X). {A = X;}
+plus_opt ::= PLUS.
+plus_opt ::= .
+
+//////////////////////////// The CREATE TRIGGER command /////////////////////
+
+cmd ::= CREATE trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). {
+ Token all;
+ all.z = A.z;
+ all.n = (Z.z - A.z) + Z.n;
+ sqlite3FinishTrigger(pParse, S, &all);
+}
+
+trigger_decl(A) ::= temp(T) TRIGGER nm(B) dbnm(Z) trigger_time(C) trigger_event(D)
+ ON nm(E) dbnm(DB) foreach_clause(F) when_clause(G). {
+ SrcList *pTab = sqlite3SrcListAppend(0, &E, &DB);
+ sqlite3BeginTrigger(pParse, &B, &Z, C, D.a, D.b, pTab, F, G, T);
+ A = (Z.n==0?B:Z);
+}
+
+%type trigger_time {int}
+trigger_time(A) ::= BEFORE. { A = TK_BEFORE; }
+trigger_time(A) ::= AFTER. { A = TK_AFTER; }
+trigger_time(A) ::= INSTEAD OF. { A = TK_INSTEAD;}
+trigger_time(A) ::= . { A = TK_BEFORE; }
+
+%type trigger_event {struct TrigEvent}
+%destructor trigger_event {sqlite3IdListDelete($$.b);}
+trigger_event(A) ::= DELETE. { A.a = TK_DELETE; A.b = 0; }
+trigger_event(A) ::= INSERT. { A.a = TK_INSERT; A.b = 0; }
+trigger_event(A) ::= UPDATE. { A.a = TK_UPDATE; A.b = 0;}
+trigger_event(A) ::= UPDATE OF inscollist(X). {A.a = TK_UPDATE; A.b = X; }
+
+%type foreach_clause {int}
+foreach_clause(A) ::= . { A = TK_ROW; }
+foreach_clause(A) ::= FOR EACH ROW. { A = TK_ROW; }
+foreach_clause(A) ::= FOR EACH STATEMENT. { A = TK_STATEMENT; }
+
+%type when_clause {Expr*}
+when_clause(A) ::= . { A = 0; }
+when_clause(A) ::= WHEN expr(X). { A = X; }
+
+%type trigger_cmd_list {TriggerStep*}
+%destructor trigger_cmd_list {sqlite3DeleteTriggerStep($$);}
+trigger_cmd_list(A) ::= trigger_cmd(X) SEMI trigger_cmd_list(Y). {
+ X->pNext = Y;
+ A = X;
+}
+trigger_cmd_list(A) ::= . { A = 0; }
+
+%type trigger_cmd {TriggerStep*}
+%destructor trigger_cmd {sqlite3DeleteTriggerStep($$);}
+// UPDATE
+trigger_cmd(A) ::= UPDATE orconf(R) nm(X) SET setlist(Y) where_opt(Z).
+ { A = sqlite3TriggerUpdateStep(&X, Y, Z, R); }
+
+// INSERT
+trigger_cmd(A) ::= insert_cmd(R) INTO nm(X) inscollist_opt(F)
+ VALUES LP itemlist(Y) RP.
+{A = sqlite3TriggerInsertStep(&X, F, Y, 0, R);}
+
+trigger_cmd(A) ::= insert_cmd(R) INTO nm(X) inscollist_opt(F) select(S).
+ {A = sqlite3TriggerInsertStep(&X, F, 0, S, R);}
+
+// DELETE
+trigger_cmd(A) ::= DELETE FROM nm(X) where_opt(Y).
+ {A = sqlite3TriggerDeleteStep(&X, Y);}
+
+// SELECT
+trigger_cmd(A) ::= select(X). {A = sqlite3TriggerSelectStep(X); }
+
+// The special RAISE expression that may occur in trigger programs
+expr(A) ::= RAISE(X) LP IGNORE RP(Y). {
+ A = sqlite3Expr(TK_RAISE, 0, 0, 0);
+ A->iColumn = OE_Ignore;
+ sqlite3ExprSpan(A, &X, &Y);
+}
+expr(A) ::= RAISE(X) LP ROLLBACK COMMA nm(Z) RP(Y). {
+ A = sqlite3Expr(TK_RAISE, 0, 0, &Z);
+ A->iColumn = OE_Rollback;
+ sqlite3ExprSpan(A, &X, &Y);
+}
+expr(A) ::= RAISE(X) LP ABORT COMMA nm(Z) RP(Y). {
+ A = sqlite3Expr(TK_RAISE, 0, 0, &Z);
+ A->iColumn = OE_Abort;
+ sqlite3ExprSpan(A, &X, &Y);
+}
+expr(A) ::= RAISE(X) LP FAIL COMMA nm(Z) RP(Y). {
+ A = sqlite3Expr(TK_RAISE, 0, 0, &Z);
+ A->iColumn = OE_Fail;
+ sqlite3ExprSpan(A, &X, &Y);
+}
+
+//////////////////////// DROP TRIGGER statement //////////////////////////////
+cmd ::= DROP TRIGGER nm(X) dbnm(D). {
+ sqlite3DropTrigger(pParse,sqlite3SrcListAppend(0,&X,&D));
+}
+
+//////////////////////// ATTACH DATABASE file AS name /////////////////////////
+cmd ::= ATTACH database_kw_opt ids(F) AS nm(D) key_opt(K). {
+ sqlite3Attach(pParse, &F, &D, K.type, &K.key);
+}
+%type key_opt {struct AttachKey}
+key_opt(A) ::= . { A.type = 0; }
+key_opt(A) ::= KEY ids(X). { A.type=1; A.key = X; }
+key_opt(A) ::= KEY BLOB(X). { A.type=2; A.key = X; }
+
+database_kw_opt ::= DATABASE.
+database_kw_opt ::= .
+
+//////////////////////// DETACH DATABASE name /////////////////////////////////
+cmd ::= DETACH database_kw_opt nm(D). {
+ sqlite3Detach(pParse, &D);
+}
diff --git a/kexi/3rdparty/kexisql3/src/patches/README b/kexi/3rdparty/kexisql3/src/patches/README
new file mode 100644
index 000000000..c098fd7ca
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/patches/README
@@ -0,0 +1,18 @@
+CHANGES in this version compared to official SQLite 3.2.8
+
+(c) 2005-2006, Jaroslaw Staniek, js at iidea.pl
+
+- to simplify patching, removed lines with CVS variable "$Id" (see remove_id.sh script)
+- test*.c and os_test.* files removed
+- config.h updated for compatibility with KDE build tools and renamed to sqliteconfig.h
+- for convenience in the code that optionally uses SQLite2, sqlite.h added as an alias for sqlite3.h
+- in sqlite3_open(), sqlite3_open16() and related internal functions following args added:
+ int exclusive, /* If 1, exclusive read/write access is required,
+ else only shared writing is locked. */
+ int allowReadonly /* If 1 and read/write opening fails,
+ try opening in read-only mode */
+ Above functions in addition to SQLITE_CANTOPEN can now return following values
+ (useful for fine grained error-checking):
+ - SQLITE_CANTOPEN_WITH_LOCKED_READWRITE - unable to open with locked read/write access
+ - SQLITE_CANTOPEN_WITH_LOCKED_WRITE - unable to open with locked write access"; break;
+
diff --git a/kexi/3rdparty/kexisql3/src/patches/kexisql-3.2.8.patch b/kexi/3rdparty/kexisql3/src/patches/kexisql-3.2.8.patch
new file mode 100644
index 000000000..42e481225
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/patches/kexisql-3.2.8.patch
@@ -0,0 +1,664 @@
+diff -u ../../sqlite-3.2.8.orig/attach.c ./attach.c
+--- ../../sqlite-3.2.8.orig/attach.c 2005-12-19 10:37:50.000000000 +0100
++++ ./attach.c 2006-06-05 11:10:38.734375000 +0200
+@@ -101,7 +101,7 @@
+ aNew->zName = zName;
+ zName = 0;
+ aNew->safety_level = 3;
+- rc = sqlite3BtreeFactory(db, zFile, 0, MAX_PAGES, &aNew->pBt);
++ rc = sqlite3BtreeFactory(db, zFile, 0, MAX_PAGES, &aNew->pBt, 0 /*!exclusive*/, 1/*allowReadonly*/);
+ if( rc ){
+ sqlite3ErrorMsg(pParse, "unable to open database: %s", zFile);
+ }
+diff -u ../../sqlite-3.2.8.orig/btree.c ./btree.c
+--- ../../sqlite-3.2.8.orig/btree.c 2005-12-19 10:37:50.000000000 +0100
++++ ./btree.c 2006-06-05 11:20:46.015625000 +0200
+@@ -1213,7 +1213,9 @@
+ int sqlite3BtreeOpen(
+ const char *zFilename, /* Name of the file containing the BTree database */
+ Btree **ppBtree, /* Pointer to new Btree object written here */
+- int flags /* Options */
++ int flags, /* Options */
++ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
++ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+ ){
+ Btree *pBt;
+ int rc;
+@@ -1236,7 +1238,8 @@
+ *ppBtree = 0;
+ return SQLITE_NOMEM;
+ }
+- rc = sqlite3pager_open(&pBt->pPager, zFilename, EXTRA_SIZE, flags);
++ rc = sqlite3pager_open(&pBt->pPager, zFilename, EXTRA_SIZE, flags,
++ exclusiveFlag, allowReadonly);
+ if( rc!=SQLITE_OK ){
+ if( pBt->pPager ) sqlite3pager_close(pBt->pPager);
+ sqliteFree(pBt);
+@@ -5831,3 +5834,8 @@
+ return sqlite3pager_reset(pBt->pPager);
+ }
+ #endif
++
++/* js */
++int sqlite3_is_readonly(sqlite3* db) {
++ return db->aDb->pBt->readOnly;
++}
+diff -u ../../sqlite-3.2.8.orig/btree.h ./btree.h
+--- ../../sqlite-3.2.8.orig/btree.h 2005-12-19 10:37:50.000000000 +0100
++++ ./btree.h 2006-06-05 11:10:38.765625000 +0200
+@@ -41,7 +41,9 @@
+ int sqlite3BtreeOpen(
+ const char *zFilename, /* Name of database file to open */
+ Btree **, /* Return open Btree* here */
+- int flags /* Flags */
++ int flags, /* Flags */
++ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
++ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+ );
+
+ /* The flags parameter to sqlite3BtreeOpen can be the bitwise or of the
+diff -u ../../sqlite-3.2.8.orig/build.c ./build.c
+--- ../../sqlite-3.2.8.orig/build.c 2005-12-19 10:37:50.000000000 +0100
++++ ./build.c 2006-06-05 11:10:38.781250000 +0200
+@@ -2721,7 +2721,7 @@
+ static int sqlite3OpenTempDatabase(Parse *pParse){
+ sqlite3 *db = pParse->db;
+ if( db->aDb[1].pBt==0 && !pParse->explain ){
+- int rc = sqlite3BtreeFactory(db, 0, 0, MAX_PAGES, &db->aDb[1].pBt);
++ int rc = sqlite3BtreeFactory(db, 0, 0, MAX_PAGES, &db->aDb[1].pBt, 0 /*!exclusive*/, 1/*allowReadonly*/);
+ if( rc!=SQLITE_OK ){
+ sqlite3ErrorMsg(pParse, "unable to open a temporary database "
+ "file for storing temporary tables");
+diff -u ../../sqlite-3.2.8.orig/main.c ./main.c
+--- ../../sqlite-3.2.8.orig/main.c 2005-12-19 10:37:50.000000000 +0100
++++ ./main.c 2006-06-05 11:10:38.828125000 +0200
+@@ -20,6 +20,12 @@
+ #include "os.h"
+ #include <ctype.h>
+
++/* (jstaniek) used by sqlite_set_verbose_vacuum and in vacuum.c */
++int g_verbose_vacuum = 0;
++void sqlite_set_verbose_vacuum(int set){
++ g_verbose_vacuum=set;
++}
++
+ /*
+ ** The following constant value is used by the SQLITE_BIGENDIAN and
+ ** SQLITE_LITTLEENDIAN macros.
+@@ -256,6 +262,11 @@
+ 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;
++ /* js */
++ case SQLITE_CANTOPEN_WITH_LOCKED_READWRITE:
++ z = "unable to open with locked read/write access"; break;
++ case SQLITE_CANTOPEN_WITH_LOCKED_WRITE:
++ z = "unable to open with locked write access"; break;
+ default: z = "unknown error"; break;
+ }
+ return z;
+@@ -592,7 +603,9 @@
+ 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 **ppBtree, /* Pointer to new Btree object written here */
++ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
++ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+ ){
+ int btree_flags = 0;
+ int rc;
+@@ -621,7 +634,7 @@
+ #endif /* SQLITE_OMIT_MEMORYDB */
+ }
+
+- rc = sqlite3BtreeOpen(zFilename, ppBtree, btree_flags);
++ rc = sqlite3BtreeOpen(zFilename, ppBtree, btree_flags, exclusiveFlag, allowReadonly);
+ if( rc==SQLITE_OK ){
+ sqlite3BtreeSetBusyHandler(*ppBtree, (void*)&db->busyHandler);
+ sqlite3BtreeSetCacheSize(*ppBtree, nCache);
+@@ -710,7 +723,9 @@
+ */
+ static int openDatabase(
+ const char *zFilename, /* Database filename UTF-8 encoded */
+- sqlite3 **ppDb /* OUT: Returned database handle */
++ sqlite3 **ppDb, /* OUT: Returned database handle */
++ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
++ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+ ){
+ sqlite3 *db;
+ int rc, i;
+@@ -759,7 +774,7 @@
+ }
+
+ /* Open the backend database driver */
+- rc = sqlite3BtreeFactory(db, zFilename, 0, MAX_PAGES, &db->aDb[0].pBt);
++ rc = sqlite3BtreeFactory(db, zFilename, 0, MAX_PAGES, &db->aDb[0].pBt, exclusiveFlag, allowReadonly);
+ if( rc!=SQLITE_OK ){
+ sqlite3Error(db, rc, 0);
+ db->magic = SQLITE_MAGIC_CLOSED;
+@@ -806,9 +821,11 @@
+ */
+ int sqlite3_open(
+ const char *zFilename,
+- sqlite3 **ppDb
++ sqlite3 **ppDb,
++ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
++ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+ ){
+- return openDatabase(zFilename, ppDb);
++ return openDatabase(zFilename, ppDb, exclusiveFlag, allowReadonly);
+ }
+
+ #ifndef SQLITE_OMIT_UTF16
+@@ -817,7 +834,9 @@
+ */
+ int sqlite3_open16(
+ const void *zFilename,
+- sqlite3 **ppDb
++ sqlite3 **ppDb,
++ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
++ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+ ){
+ char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */
+ int rc = SQLITE_NOMEM;
+@@ -829,7 +848,7 @@
+ sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8);
+ if( zFilename8 ){
+- rc = openDatabase(zFilename8, ppDb);
++ rc = openDatabase(zFilename8, ppDb, exclusiveFlag, allowReadonly);
+ if( rc==SQLITE_OK && *ppDb ){
+ sqlite3_exec(*ppDb, "PRAGMA encoding = 'UTF-16'", 0, 0, 0);
+ }
+diff -u ../../sqlite-3.2.8.orig/os.h ./os.h
+--- ../../sqlite-3.2.8.orig/os.h 2005-12-19 10:37:50.000000000 +0100
++++ ./os.h 2006-06-05 11:10:38.843750000 +0200
+@@ -175,7 +175,8 @@
+
+ int sqlite3OsDelete(const char*);
+ int sqlite3OsFileExists(const char*);
+-int sqlite3OsOpenReadWrite(const char*, OsFile*, int*);
++/* js: extended */
++int sqlite3OsOpenReadWrite(const char*, OsFile*, int*, int, int);
+ int sqlite3OsOpenExclusive(const char*, OsFile*, int);
+ int sqlite3OsOpenReadOnly(const char*, OsFile*);
+ int sqlite3OsOpenDirectory(const char*, OsFile*);
+diff -u ../../sqlite-3.2.8.orig/os_unix.c ./os_unix.c
+--- ../../sqlite-3.2.8.orig/os_unix.c 2005-12-19 10:37:50.000000000 +0100
++++ ./os_unix.c 2006-06-05 11:10:38.859375000 +0200
+@@ -509,7 +509,9 @@
+ int sqlite3OsOpenReadWrite(
+ const char *zFilename,
+ OsFile *id,
+- int *pReadonly
++ int *pReadonly,
++ int exclusiveFlag,
++ int allowReadonly
+ ){
+ int rc;
+ assert( !id->isOpen );
+diff -u ../../sqlite-3.2.8.orig/os_win.c ./os_win.c
+--- ../../sqlite-3.2.8.orig/os_win.c 2005-12-19 10:37:50.000000000 +0100
++++ ./os_win.c 2006-06-05 11:55:14.062500000 +0200
+@@ -155,26 +155,37 @@
+
+ /*
+ ** 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.
++** fails, and allowReadonly is 1, try opening it read-only.
++** If the file does not exist, try to create it.
++**
++** Exclusive write access is required if exclusiveFlag is 1.
++** Exclusive read/write access is required if exclusiveFlag is 2.
++** In this case, if allowReadonly is also 1, only shared writing is locked.
+ **
+ ** 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.
++** On failure the function leaves *id and *pReadonly unchanged, and returns:
++** - SQLITE_CANTOPEN_WITH_LOCKED_READWRITE if it was unable to lock the file
++** for read/write access.
++** - SQLITE_CANTOPEN_WITH_LOCKED_WRITE if it was unable to lock the file
++** for write access.
++** - SQLITE_CANTOPEN in any other case.
+ */
+ int sqlite3OsOpenReadWrite(
+ const char *zFilename,
+ OsFile *id,
+- int *pReadonly
++ int *pReadonly,
++ int exclusiveFlag,
++ int allowReadonly
+ ){
+ HANDLE h;
++ int access;
+ WCHAR *zWide = utf8ToUnicode(zFilename);
+ assert( !id->isOpen );
+- if( zWide ){
++/* if( zWide ){
+ h = CreateFileW(zWide,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+@@ -201,31 +212,84 @@
+ *pReadonly = 0;
+ }
+ sqliteFree(zWide);
+- }else{
+- h = CreateFileA(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 = CreateFileA(zFilename,
+- GENERIC_READ,
+- FILE_SHARE_READ,
+- NULL,
+- OPEN_ALWAYS,
+- FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+- NULL
+- );
+- if( h==INVALID_HANDLE_VALUE ){
++ }else*/{
++ /* js */
++ if (exclusiveFlag!=SQLITE_OPEN_READONLY) {
++ if (exclusiveFlag==SQLITE_OPEN_NO_LOCKED)
++ access = FILE_SHARE_READ | FILE_SHARE_WRITE;
++ else if (exclusiveFlag==SQLITE_OPEN_WRITE_LOCKED)
++ access = FILE_SHARE_READ;
++ else /* SQLITE_OPEN_READ_WRITE_LOCKED */
++ access = 0;
++ if( zWide ){
++ h = CreateFileW(zWide,
++ GENERIC_READ | GENERIC_WRITE,
++ access,
++ NULL,
++ OPEN_ALWAYS,
++ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
++ NULL
++ );
++ }
++ else {
++ h = CreateFileA(zFilename,
++ GENERIC_READ | GENERIC_WRITE,
++ access,
++ NULL,
++ OPEN_ALWAYS,
++ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
++ NULL
++ );
++ }
++ if (!allowReadonly && GetLastError()==ERROR_SHARING_VIOLATION) {
++ sqliteFree(zWide);
++ if (exclusiveFlag==SQLITE_OPEN_NO_LOCKED)
++ return SQLITE_CANTOPEN;
++ return exclusiveFlag==SQLITE_OPEN_READ_WRITE_LOCKED
++ ? SQLITE_CANTOPEN_WITH_LOCKED_READWRITE
++ : SQLITE_CANTOPEN_WITH_LOCKED_WRITE;
++ }
++ }
++ if( exclusiveFlag==SQLITE_OPEN_READONLY || (allowReadonly && h==INVALID_HANDLE_VALUE) ){
++ /* open read only */
++ /* if (exclusiveFlag==0) */
++ access = FILE_SHARE_READ | FILE_SHARE_WRITE;
++ /* else
++ access = FILE_SHARE_READ;*/
++ if( zWide ){
++ h = CreateFileW(zWide,
++ GENERIC_READ,
++ access,
++ NULL,
++ OPEN_ALWAYS,
++ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
++ NULL
++ );
++ }
++ else {
++ h = CreateFileA(zFilename,
++ GENERIC_READ,
++ access,
++ NULL,
++ OPEN_ALWAYS,
++ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
++ NULL
++ );
++ }
++ if (GetLastError()==ERROR_SHARING_VIOLATION) {
++ sqliteFree(zWide);
+ return SQLITE_CANTOPEN;
+ }
+- *pReadonly = 1;
++ if( h!=INVALID_HANDLE_VALUE ){
++ *pReadonly = 1;
++ }
+ }else{
+ *pReadonly = 0;
+ }
++ sqliteFree(zWide);
++ if( h==INVALID_HANDLE_VALUE ){
++ return SQLITE_CANTOPEN;
++ }
+ }
+ id->h = h;
+ id->locktype = NO_LOCK;
+diff -u ../../sqlite-3.2.8.orig/pager.c ./pager.c
+--- ../../sqlite-3.2.8.orig/pager.c 2005-12-19 10:37:50.000000000 +0100
++++ ./pager.c 2006-06-05 11:54:53.484375000 +0200
+@@ -1590,7 +1590,9 @@
+ 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 flags, /* flags controlling this file */
++ int exclusiveFlag, /* as in sqlite3OsOpenReadWrite() */
++ int allowReadonly /* as in sqlite3OsOpenReadWrite() */
+ ){
+ Pager *pPager;
+ char *zFullPathname = 0;
+@@ -1621,7 +1623,7 @@
+ {
+ zFullPathname = sqlite3OsFullPathname(zFilename);
+ if( zFullPathname ){
+- rc = sqlite3OsOpenReadWrite(zFullPathname, &fd, &readOnly);
++ rc = sqlite3OsOpenReadWrite(zFullPathname, &fd, &readOnly, exclusiveFlag, allowReadonly);
+ }
+ }
+ }else{
+diff -u ../../sqlite-3.2.8.orig/pager.h ./pager.h
+--- ../../sqlite-3.2.8.orig/pager.h 2005-12-19 10:37:50.000000000 +0100
++++ ./pager.h 2006-06-05 11:43:24.921875000 +0200
+@@ -67,7 +67,7 @@
+ ** routines:
+ */
+ int sqlite3pager_open(Pager **ppPager, const char *zFilename,
+- int nExtra, int flags);
++ int nExtra, int flags, int exclusiveFlag, int allowReadonly);
+ void sqlite3pager_set_busyhandler(Pager*, BusyHandler *pBusyHandler);
+ void sqlite3pager_set_destructor(Pager*, void(*)(void*,int));
+ void sqlite3pager_set_reiniter(Pager*, void(*)(void*,int));
+diff -u ../../sqlite-3.2.8.orig/shell.c ./shell.c
+--- ../../sqlite-3.2.8.orig/shell.c 2006-06-05 11:12:30.875000000 +0200
++++ ./shell.c 2006-06-05 11:12:18.781250000 +0200
+@@ -28,6 +28,10 @@
+ # include <sys/types.h>
+ #endif
+
++#ifdef _WIN32
++# include <io.h>
++#endif
++
+ #ifdef __MACOS__
+ # include <console.h>
+ # include <signal.h>
+@@ -77,7 +81,6 @@
+ 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.
+ */
+@@ -793,7 +796,7 @@
+ */
+ static void open_db(struct callback_data *p){
+ if( p->db==0 ){
+- sqlite3_open(p->zDbFilename, &p->db);
++ sqlite3_open(p->zDbFilename, &p->db, 0/*!exclusive*/, 1/*allowReadonly*/);
+ db = p->db;
+ #ifdef SQLITE_HAS_CODEC
+ sqlite3_key(p->db, p->zKey, p->zKey ? strlen(p->zKey) : 0);
+@@ -1626,6 +1629,9 @@
+ " -list set output mode to 'list'\n"
+ " -separator 'x' set output field separator (|)\n"
+ " -nullvalue 'text' set text string for NULL values\n"
++ " -verbose-vacuum set VACUUM to verbose, interactive mode\n"
++ " so it is possible to get progress info\n"
++ " and abort the process\n"
+ " -version show SQLite version\n"
+ " -help show this text, also show dot-commands\n"
+ ;
+@@ -1753,6 +1759,8 @@
+ data.showHeader = 0;
+ }else if( strcmp(z,"-echo")==0 ){
+ data.echoOn = 1;
++ }else if( strcmp(z,"-verbose-vacuum")==0 ){ /*(jstaniek)*/
++ sqlite_set_verbose_vacuum(1);
+ }else if( strcmp(z,"-version")==0 ){
+ printf("%s\n", sqlite3_libversion());
+ return 1;
+@@ -1787,7 +1795,7 @@
+ char *zHome;
+ char *zHistory = 0;
+ printf(
+- "SQLite version %s\n"
++ "SQLite version %s (customized, bundled with Kexi)\n"
+ "Enter \".help\" for instructions\n",
+ sqlite3_libversion()
+ );
+diff -u ../../sqlite-3.2.8.orig/sqlite3.h ./sqlite3.h
+--- ../../sqlite-3.2.8.orig/sqlite3.h 2005-12-19 10:37:50.000000000 +0100
++++ ./sqlite3.h 2006-06-05 11:10:53.343750000 +0200
+@@ -186,6 +186,10 @@
+ #define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
+ #define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
+
++/* js: Used in sqlite3OsOpenReadWrite() */
++#define SQLITE_CANTOPEN_WITH_LOCKED_READWRITE 0x1001 /* Cannot open with locked read/write access */
++#define SQLITE_CANTOPEN_WITH_LOCKED_WRITE 0x1002 /* Cannot open with locked write access */
++
+ /*
+ ** 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,
+@@ -538,13 +542,30 @@
+ ** with the sqlite3* handle should be released by passing it to
+ ** sqlite3_close() when it is no longer required.
+ */
++#define SQLITE_OPEN_READONLY 3
++#define SQLITE_OPEN_READ_WRITE_LOCKED 2
++#define SQLITE_OPEN_WRITE_LOCKED 1
++#define SQLITE_OPEN_NO_LOCKED 0
+ int sqlite3_open(
+ const char *filename, /* Database filename (UTF-8) */
+- sqlite3 **ppDb /* OUT: SQLite db handle */
++ sqlite3 **ppDb, /* OUT: SQLite db handle */
++ int exclusiveFlag, /* If SQLITE_OPEN_READONLY, readonly mode is used, */
++ /* If SQLITE_OPEN_READ_WRITE_LOCKED, exclusive read/write access is required, */
++ /* If SQLITE_OPEN_WRITE_LOCKED, exclusive write access is required,
++ If SQLITE_OPEN_NO_LOCKED, no locking is performed. */
++ int allowReadonly /* If 1 and read/write opening fails,
++ try opening in read-only mode */
+ );
++
+ int sqlite3_open16(
+ const void *filename, /* Database filename (UTF-16) */
+- sqlite3 **ppDb /* OUT: SQLite db handle */
++ sqlite3 **ppDb, /* OUT: SQLite db handle */
++ int exclusiveFlag, /* If SQLITE_OPEN_READONLY, readonly mode is used, */
++ /* If SQLITE_OPEN_READ_WRITE_LOCKED, exclusive read/write access is required, */
++ /* If SQLITE_OPEN_WRITE_LOCKED, exclusive write access is required,
++ If SQLITE_OPEN_NO_LOCKED, no locking is performed. */
++ int allowReadonly /* If 1 and read/write opening fails,
++ try opening in read-only mode */
+ );
+
+ /*
+@@ -1269,6 +1290,12 @@
+ */
+ sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
+
++/* (jstaniek) used in vacuum.c, set==1 sets VACUUM to verbose,
++** interactive mode, so it is possible to get progress info and abort
++** the process. Usable for GUI apps.
++*/
++void sqlite_set_verbose_vacuum(int set);
++
+ #ifdef __cplusplus
+ } /* End of the 'extern "C"' block */
+ #endif
+diff -u ../../sqlite-3.2.8.orig/sqliteInt.h ./sqliteInt.h
+--- ../../sqlite-3.2.8.orig/sqliteInt.h 2005-12-19 10:37:50.000000000 +0100
++++ ./sqliteInt.h 2006-06-05 11:45:44.859375000 +0200
+@@ -50,6 +50,7 @@
+ # define _LARGEFILE_SOURCE 1
+ #endif
+
++#include "sqliteconfig.h"
+ #include "sqlite3.h"
+ #include "hash.h"
+ #include "parse.h"
+@@ -1586,7 +1587,8 @@
+ void sqlite3Attach(Parse*, Token*, Token*, int, Token*);
+ void sqlite3Detach(Parse*, Token*);
+ int sqlite3BtreeFactory(const sqlite3 *db, const char *zFilename,
+- int omitJournal, int nCache, Btree **ppBtree);
++ int omitJournal, int nCache, Btree **ppBtree,
++ int exclusiveFlag, int allowReadonly);
+ int sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
+ int sqlite3FixSrcList(DbFixer*, SrcList*);
+ int sqlite3FixSelect(DbFixer*, Select*);
+diff -u ../../sqlite-3.2.8.orig/vacuum.c ./vacuum.c
+--- ../../sqlite-3.2.8.orig/vacuum.c 2005-12-19 10:37:50.000000000 +0100
++++ ./vacuum.c 2006-06-05 11:50:09.671875000 +0200
+@@ -46,18 +46,37 @@
+ return sqlite3_finalize(pStmt);
+ }
+
++/* (jstaniek) */
++extern int g_verbose_vacuum;
++
+ /*
+ ** Execute zSql on database db. The statement returns exactly
+ ** one column. Execute this as SQL on the same database.
++**
++** (js: extension): if count > 0, "VACUUM: X%" string will
++** be printed to stdout, so user (a human or calling application)
++** can know the overall progress of the operation.
++** and the program will wait for a key press (followed by RETURN);
++** 'q' key aborts the execution and any other key allows to proceed.
+ */
+-static int execExecSql(sqlite3 *db, const char *zSql){
++static int execExecSql(sqlite3 *db, const char *zSql, int count){
+ sqlite3_stmt *pStmt;
+- int rc;
++ int rc, i;
++ char ch;
+
+ rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+
+- while( SQLITE_ROW==sqlite3_step(pStmt) ){
++ for( i=0; SQLITE_ROW==sqlite3_step(pStmt); i++ ){
++ if (g_verbose_vacuum!=0 && count>0) {
++ fprintf(stdout, "VACUUM: %d%%\n", 100*(i+1)/count);
++ fflush(stdout);
++ fscanf(stdin, "%c", &ch);
++ if ('q'==ch) { /* quit */
++ sqlite3_finalize(pStmt);
++ return SQLITE_ABORT;
++ }
++ }
+ rc = execSql(db, sqlite3_column_text(pStmt, 0));
+ if( rc!=SQLITE_OK ){
+ sqlite3_finalize(pStmt);
+@@ -93,6 +112,8 @@
+ */
+ int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){
+ int rc = SQLITE_OK; /* Return code from service routines */
++ sqlite3_stmt *pStmt = 0;
++ int tables_count = 0;
+ #ifndef SQLITE_OMIT_VACUUM
+ const char *zFilename; /* full pathname of the database file */
+ int nFilename; /* number of characters in zFilename[] */
+@@ -184,22 +205,29 @@
+ */
+ rc = execExecSql(db,
+ "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14,100000000) "
+- " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'");
++ " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'", 0);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14,100000000)"
+- " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' ");
++ " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' ", 0);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21,100000000) "
+- " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %'");
++ " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %'", 0);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'CREATE VIEW vacuum_db.' || substr(sql,13,100000000) "
+- " FROM sqlite_master WHERE type='view'"
++ " FROM sqlite_master WHERE type='view'", 0
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
++ /* (js) get # of tables so we can output progress.
++ */
++ rc = sqlite3_prepare(db, "SELECT count(name) FROM sqlite_master "
++ "WHERE type = 'table';", -1, &pStmt, 0);
++ if( rc!=SQLITE_OK || SQLITE_ROW!=sqlite3_step(pStmt)) goto end_of_vacuum;
++ tables_count = atoi( sqlite3_column_text(pStmt, 0) );
++
+ /* 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.
+@@ -208,7 +236,7 @@
+ "SELECT 'INSERT INTO vacuum_db.' || quote(name) "
+ "|| ' SELECT * FROM ' || quote(name) || ';'"
+ "FROM sqlite_master "
+- "WHERE type = 'table' AND name!='sqlite_sequence';"
++ "WHERE type = 'table' AND name!='sqlite_sequence';", tables_count
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+@@ -216,13 +244,13 @@
+ */
+ rc = execExecSql(db,
+ "SELECT 'DELETE FROM vacuum_db.' || quote(name) || ';' "
+- "FROM vacuum_db.sqlite_master WHERE name='sqlite_sequence' "
++ "FROM vacuum_db.sqlite_master WHERE name='sqlite_sequence' ", 0
+ );
+ 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';"
++ "FROM vacuum_db.sqlite_master WHERE name=='sqlite_sequence';", 0
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+@@ -234,11 +262,10 @@
+ */
+ rc = execExecSql(db,
+ "SELECT 'CREATE TRIGGER vacuum_db.' || substr(sql, 16, 1000000) "
+- "FROM sqlite_master WHERE type='trigger'"
++ "FROM sqlite_master WHERE type='trigger'", 0
+ );
+ if( rc!=SQLITE_OK ) 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
+diff -u ../../sqlite-3.2.8.orig/vdbe.c ./vdbe.c
+--- ../../sqlite-3.2.8.orig/vdbe.c 2005-12-19 10:37:50.000000000 +0100
++++ ./vdbe.c 2006-06-05 11:10:53.421875000 +0200
+@@ -2593,7 +2593,7 @@
+ pCx = allocateCursor(p, i);
+ if( pCx==0 ) goto no_mem;
+ pCx->nullRow = 1;
+- rc = sqlite3BtreeFactory(db, 0, 1, TEMP_PAGES, &pCx->pBt);
++ rc = sqlite3BtreeFactory(db, 0, 1, TEMP_PAGES, &pCx->pBt, 0 /*!exclusive*/, 1/*allowReadonly*/);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3BtreeBeginTrans(pCx->pBt, 1);
+ }
diff --git a/kexi/3rdparty/kexisql3/src/patches/mk_patch.sh b/kexi/3rdparty/kexisql3/src/patches/mk_patch.sh
new file mode 100755
index 000000000..e50f8c18f
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/patches/mk_patch.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+cd ..
+
+diff -u ../../sqlite-3.2.8.orig/ . | grep -v "^Only in"
diff --git a/kexi/3rdparty/kexisql3/src/patches/remove_id.sh b/kexi/3rdparty/kexisql3/src/patches/remove_id.sh
new file mode 100755
index 000000000..a5e09df7a
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/patches/remove_id.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+for i in *.h *.c ; do cat $i | grep -v "\$Id" > $i.tmp && mv $i.tmp $i ; done
diff --git a/kexi/3rdparty/kexisql3/src/pragma.c b/kexi/3rdparty/kexisql3/src/pragma.c
new file mode 100644
index 000000000..c85c19d5c
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/pragma.c
@@ -0,0 +1,935 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+
+/* Ignore this whole file if pragmas are disabled
+*/
+#if !defined(SQLITE_OMIT_PRAGMA) && !defined(SQLITE_OMIT_PARSER)
+
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
+# include "pager.h"
+# include "btree.h"
+#endif
+
+/*
+** 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 u8 *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 u8 *z){
+ return getSafetyLevel(z)&1;
+}
+
+#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->flags & SQLITE_InTrans ){
+ 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);
+ sqlite3VdbeAddOp(v, OP_Integer, value, 0);
+ if( pParse->explain==0 ){
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, zLabel, P3_STATIC);
+ }
+ sqlite3VdbeAddOp(v, OP_Callback, 1, 0);
+}
+
+#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[] = {
+ { "vdbe_trace", SQLITE_VdbeTrace },
+ { "sql_trace", SQLITE_SqlTrace },
+ { "vdbe_listing", SQLITE_VdbeListing },
+ { "full_column_names", SQLITE_FullColNames },
+ { "short_column_names", SQLITE_ShortColNames },
+ { "count_changes", SQLITE_CountRows },
+ { "empty_result_callbacks", SQLITE_NullCallback },
+ /* The following is VERY experimental */
+ { "writable_schema", SQLITE_WriteSchema },
+ { "omit_readlock", SQLITE_NoReadlock },
+ };
+ 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;
+ }
+ }
+ /* If one of these pragmas is executed, any prepared statements
+ ** need to be recompiled.
+ */
+ sqlite3VdbeAddOp(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.
+*/
+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 = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+
+ /* 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];
+
+ zLeft = sqlite3NameFromToken(pId);
+ if( !zLeft ) return;
+ if( minusFlag ){
+ zRight = sqlite3MPrintf("-%T", pValue);
+ }else{
+ zRight = sqlite3NameFromToken(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, 2, 0}, /* 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_Callback, 1, 0, 0},
+ };
+ int addr;
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ if( !zRight ){
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, "cache_size", P3_STATIC);
+ addr = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize);
+ sqlite3VdbeChangeP1(v, addr, iDb);
+ sqlite3VdbeChangeP1(v, addr+5, MAX_PAGES);
+ }else{
+ int size = atoi(zRight);
+ if( size<0 ) size = -size;
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3VdbeAddOp(v, OP_Integer, size, 0);
+ sqlite3VdbeAddOp(v, OP_ReadCookie, iDb, 2);
+ addr = sqlite3VdbeAddOp(v, OP_Integer, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Ge, 0, addr+3);
+ sqlite3VdbeAddOp(v, OP_Negative, 0, 0);
+ sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 2);
+ pDb->cache_size = size;
+ sqlite3BtreeSetCacheSize(pDb->pBt, pDb->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{
+ sqlite3BtreeSetPageSize(pBt, atoi(zRight), -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( !zRight ){
+ int auto_vacuum =
+ pBt ? sqlite3BtreeGetAutoVacuum(pBt) : SQLITE_DEFAULT_AUTOVACUUM;
+ returnSingleInt(pParse, "auto_vacuum", auto_vacuum);
+ }else{
+ sqlite3BtreeSetAutoVacuum(pBt, getBoolean(zRight));
+ }
+ }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->cache_size);
+ }else{
+ int size = atoi(zRight);
+ if( size<0 ) size = -size;
+ pDb->cache_size = size;
+ sqlite3BtreeSetCacheSize(pDb->pBt, pDb->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, "temp_store_directory", P3_STATIC);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, sqlite3_temp_directory, 0);
+ sqlite3VdbeAddOp(v, OP_Callback, 1, 0);
+ }
+ }else{
+ if( zRight[0] && !sqlite3OsIsDirWritable(zRight) ){
+ 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);
+ }
+ sqliteFree(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;
+ sqlite3BtreeSetSafetyLevel(pDb->pBt, pDb->safety_level);
+ }
+ }
+ }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;
+ sqlite3VdbeSetNumCols(v, 6);
+ sqlite3VdbeSetColName(v, 0, "cid", P3_STATIC);
+ sqlite3VdbeSetColName(v, 1, "name", P3_STATIC);
+ sqlite3VdbeSetColName(v, 2, "type", P3_STATIC);
+ sqlite3VdbeSetColName(v, 3, "notnull", P3_STATIC);
+ sqlite3VdbeSetColName(v, 4, "dflt_value", P3_STATIC);
+ sqlite3VdbeSetColName(v, 5, "pk", P3_STATIC);
+ sqlite3ViewGetColumnNames(pParse, pTab);
+ for(i=0; i<pTab->nCol; i++){
+ sqlite3VdbeAddOp(v, OP_Integer, i, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->aCol[i].zName, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0,
+ pTab->aCol[i].zType ? pTab->aCol[i].zType : "numeric", 0);
+ sqlite3VdbeAddOp(v, OP_Integer, pTab->aCol[i].notNull, 0);
+ sqlite3ExprCode(pParse, pTab->aCol[i].pDflt);
+ sqlite3VdbeAddOp(v, OP_Integer, pTab->aCol[i].isPrimKey, 0);
+ sqlite3VdbeAddOp(v, OP_Callback, 6, 0);
+ }
+ }
+ }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);
+ sqlite3VdbeSetColName(v, 0, "seqno", P3_STATIC);
+ sqlite3VdbeSetColName(v, 1, "cid", P3_STATIC);
+ sqlite3VdbeSetColName(v, 2, "name", P3_STATIC);
+ for(i=0; i<pIdx->nColumn; i++){
+ int cnum = pIdx->aiColumn[i];
+ sqlite3VdbeAddOp(v, OP_Integer, i, 0);
+ sqlite3VdbeAddOp(v, OP_Integer, cnum, 0);
+ assert( pTab->nCol>cnum );
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->aCol[cnum].zName, 0);
+ sqlite3VdbeAddOp(v, OP_Callback, 3, 0);
+ }
+ }
+ }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);
+ sqlite3VdbeSetColName(v, 0, "seq", P3_STATIC);
+ sqlite3VdbeSetColName(v, 1, "name", P3_STATIC);
+ sqlite3VdbeSetColName(v, 2, "unique", P3_STATIC);
+ while(pIdx){
+ sqlite3VdbeAddOp(v, OP_Integer, i, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pIdx->zName, 0);
+ sqlite3VdbeAddOp(v, OP_Integer, pIdx->onError!=OE_None, 0);
+ sqlite3VdbeAddOp(v, OP_Callback, 3, 0);
+ ++i;
+ pIdx = pIdx->pNext;
+ }
+ }
+ }
+ }else
+
+ if( sqlite3StrICmp(zLeft, "database_list")==0 ){
+ int i;
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ sqlite3VdbeSetNumCols(v, 3);
+ sqlite3VdbeSetColName(v, 0, "seq", P3_STATIC);
+ sqlite3VdbeSetColName(v, 1, "name", P3_STATIC);
+ sqlite3VdbeSetColName(v, 2, "file", P3_STATIC);
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt==0 ) continue;
+ assert( db->aDb[i].zName!=0 );
+ sqlite3VdbeAddOp(v, OP_Integer, i, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, db->aDb[i].zName, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0,
+ sqlite3BtreeGetFilename(db->aDb[i].pBt), 0);
+ sqlite3VdbeAddOp(v, OP_Callback, 3, 0);
+ }
+ }else
+
+ if( sqlite3StrICmp(zLeft, "collation_list")==0 ){
+ int i = 0;
+ HashElem *p;
+ sqlite3VdbeSetNumCols(v, 2);
+ sqlite3VdbeSetColName(v, 0, "seq", P3_STATIC);
+ sqlite3VdbeSetColName(v, 1, "name", P3_STATIC);
+ for(p=sqliteHashFirst(&db->aCollSeq); p; p=sqliteHashNext(p)){
+ CollSeq *pColl = (CollSeq *)sqliteHashData(p);
+ sqlite3VdbeAddOp(v, OP_Integer, i++, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pColl->zName, 0);
+ sqlite3VdbeAddOp(v, OP_Callback, 2, 0);
+ }
+ }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);
+ sqlite3VdbeSetColName(v, 0, "id", P3_STATIC);
+ sqlite3VdbeSetColName(v, 1, "seq", P3_STATIC);
+ sqlite3VdbeSetColName(v, 2, "table", P3_STATIC);
+ sqlite3VdbeSetColName(v, 3, "from", P3_STATIC);
+ sqlite3VdbeSetColName(v, 4, "to", P3_STATIC);
+ while(pFK){
+ int j;
+ for(j=0; j<pFK->nCol; j++){
+ char *zCol = pFK->aCol[j].zCol;
+ sqlite3VdbeAddOp(v, OP_Integer, i, 0);
+ sqlite3VdbeAddOp(v, OP_Integer, j, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, pFK->zTo, 0);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0,
+ pTab->aCol[pFK->aCol[j].iFrom].zName, 0);
+ sqlite3VdbeOp3(v, zCol ? OP_String8 : OP_Null, 0, 0, zCol, 0);
+ sqlite3VdbeAddOp(v, OP_Callback, 5, 0);
+ }
+ ++i;
+ pFK = pFK->pNextFrom;
+ }
+ }
+ }
+ }else
+#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */
+
+#ifndef NDEBUG
+ if( sqlite3StrICmp(zLeft, "parser_trace")==0 ){
+ extern void sqlite3ParserTrace(FILE*, char *);
+ 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_OMIT_INTEGRITY_CHECK
+ if( sqlite3StrICmp(zLeft, "integrity_check")==0 ){
+ int i, j, addr;
+
+ /* 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_MemLoad, 0, 0, 0},
+ { OP_Integer, 0, 0, 0},
+ { OP_Ne, 0, 0, 0}, /* 2 */
+ { OP_String8, 0, 0, "ok"},
+ { OP_Callback, 1, 0, 0},
+ };
+
+ /* Initialize the VDBE program */
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, "integrity_check", P3_STATIC);
+ sqlite3VdbeAddOp(v, OP_MemInt, 0, 0); /* Initialize error count to 0 */
+
+ /* Do an integrity check on each database file */
+ for(i=0; i<db->nDb; i++){
+ HashElem *x;
+ int cnt = 0;
+
+ if( OMIT_TEMPDB && i==1 ) continue;
+
+ sqlite3CodeVerifySchema(pParse, i);
+
+ /* Do an integrity check of the B-Tree
+ */
+ for(x=sqliteHashFirst(&db->aDb[i].tblHash); x; x=sqliteHashNext(x)){
+ Table *pTab = sqliteHashData(x);
+ Index *pIdx;
+ sqlite3VdbeAddOp(v, OP_Integer, pTab->tnum, 0);
+ cnt++;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ if( sqlite3CheckIndexCollSeq(pParse, pIdx) ) goto pragma_out;
+ sqlite3VdbeAddOp(v, OP_Integer, pIdx->tnum, 0);
+ cnt++;
+ }
+ }
+ assert( cnt>0 );
+ sqlite3VdbeAddOp(v, OP_IntegrityCk, cnt, i);
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 1);
+ addr = sqlite3VdbeOp3(v, OP_String8, 0, 0, "ok", P3_STATIC);
+ sqlite3VdbeAddOp(v, OP_Eq, 0, addr+6);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0,
+ sqlite3MPrintf("*** in database %s ***\n", db->aDb[i].zName),
+ P3_DYNAMIC);
+ sqlite3VdbeAddOp(v, OP_Pull, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Concat, 0, 1);
+ sqlite3VdbeAddOp(v, OP_Callback, 1, 0);
+
+ /* Make sure all the indices are constructed correctly.
+ */
+ sqlite3CodeVerifySchema(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;
+ sqlite3OpenTableAndIndices(pParse, pTab, 1, OP_OpenRead);
+ sqlite3VdbeAddOp(v, OP_MemInt, 0, 1);
+ loopTop = sqlite3VdbeAddOp(v, OP_Rewind, 1, 0);
+ sqlite3VdbeAddOp(v, OP_MemIncr, 1, 0);
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ int jmp2;
+ static const VdbeOpList idxErr[] = {
+ { OP_MemIncr, 0, 0, 0},
+ { OP_String8, 0, 0, "rowid "},
+ { OP_Rowid, 1, 0, 0},
+ { OP_String8, 0, 0, " missing from index "},
+ { OP_String8, 0, 0, 0}, /* 4 */
+ { OP_Concat, 2, 0, 0},
+ { OP_Callback, 1, 0, 0},
+ };
+ sqlite3GenerateIndexKey(v, pIdx, 1);
+ jmp2 = sqlite3VdbeAddOp(v, OP_Found, j+2, 0);
+ addr = sqlite3VdbeAddOpList(v, ArraySize(idxErr), idxErr);
+ sqlite3VdbeChangeP3(v, addr+4, pIdx->zName, P3_STATIC);
+ sqlite3VdbeJumpHere(v, jmp2);
+ }
+ sqlite3VdbeAddOp(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_MemInt, 0, 2, 0},
+ { OP_Rewind, 0, 0, 0}, /* 1 */
+ { OP_MemIncr, 2, 0, 0},
+ { OP_Next, 0, 0, 0}, /* 3 */
+ { OP_MemLoad, 1, 0, 0},
+ { OP_MemLoad, 2, 0, 0},
+ { OP_Eq, 0, 0, 0}, /* 6 */
+ { OP_MemIncr, 0, 0, 0},
+ { OP_String8, 0, 0, "wrong # of entries in index "},
+ { OP_String8, 0, 0, 0}, /* 9 */
+ { OP_Concat, 0, 0, 0},
+ { OP_Callback, 1, 0, 0},
+ };
+ if( pIdx->tnum==0 ) continue;
+ 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+6);
+ sqlite3VdbeChangeP3(v, addr+9, pIdx->zName, P3_STATIC);
+ }
+ }
+ }
+ addr = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode);
+ sqlite3VdbeJumpHere(v, addr+2);
+ }else
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+#ifndef SQLITE_OMIT_UTF16
+ /*
+ ** PRAGMA encoding
+ ** PRAGMA encoding = "utf-8"|"utf-16"|"utf-16le"|"utf-16be"
+ **
+ ** In it's 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 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 /* Filled in at run-time */ },
+ { "UTF16", 0 /* Filled in at run-time */ },
+ { 0, 0 }
+ };
+ struct EncName *pEnc;
+ encnames[6].enc = encnames[7].enc = SQLITE_UTF16NATIVE;
+ if( !zRight ){ /* "PRAGMA encoding" */
+ if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, "encoding", P3_STATIC);
+ sqlite3VdbeAddOp(v, OP_String8, 0, 0);
+ for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
+ if( pEnc->enc==pParse->db->enc ){
+ sqlite3VdbeChangeP3(v, -1, pEnc->zName, P3_STATIC);
+ break;
+ }
+ }
+ sqlite3VdbeAddOp(v, OP_Callback, 1, 0);
+ }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( !(pParse->db->flags&SQLITE_Initialized) ){
+ for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
+ if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){
+ pParse->db->enc = pEnc->enc;
+ 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 ){
+
+ int iCookie; /* Cookie index. 0 for schema-cookie, 6 for user-cookie. */
+ if( zLeft[0]=='s' || zLeft[0]=='S' ){
+ iCookie = 0;
+ }else{
+ iCookie = 5;
+ }
+
+ if( zRight ){
+ /* Write the specified cookie value */
+ static const VdbeOpList setCookie[] = {
+ { OP_Transaction, 0, 1, 0}, /* 0 */
+ { OP_Integer, 0, 0, 0}, /* 1 */
+ { OP_SetCookie, 0, 0, 0}, /* 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, 0, 0}, /* 0 */
+ { OP_Callback, 1, 0, 0}
+ };
+ int addr = sqlite3VdbeAddOpList(v, ArraySize(readCookie), readCookie);
+ sqlite3VdbeChangeP1(v, addr, iDb);
+ sqlite3VdbeChangeP2(v, addr, iCookie);
+ sqlite3VdbeSetNumCols(v, 1);
+ }
+ }
+#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);
+ sqlite3VdbeSetColName(v, 0, "database", P3_STATIC);
+ sqlite3VdbeSetColName(v, 1, "status", P3_STATIC);
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt;
+ Pager *pPager;
+ if( db->aDb[i].zName==0 ) continue;
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, db->aDb[i].zName, P3_STATIC);
+ pBt = db->aDb[i].pBt;
+ if( pBt==0 || (pPager = sqlite3BtreePager(pBt))==0 ){
+ sqlite3VdbeOp3(v, OP_String8, 0, 0, "closed", P3_STATIC);
+ }else{
+ int j = sqlite3pager_lockstate(pPager);
+ sqlite3VdbeOp3(v, OP_String8, 0, 0,
+ (j>=0 && j<=4) ? azLockName[j] : "unknown", P3_STATIC);
+ }
+ sqlite3VdbeAddOp(v, OP_Callback, 2, 0);
+ }
+ }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( 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.
+ */
+ sqlite3VdbeAddOp(v, OP_Expire, 1, 0);
+ }
+pragma_out:
+ sqliteFree(zLeft);
+ sqliteFree(zRight);
+}
+
+#endif /* SQLITE_OMIT_PRAGMA || SQLITE_OMIT_PARSER */
diff --git a/kexi/3rdparty/kexisql3/src/prepare.c b/kexi/3rdparty/kexisql3/src/prepare.c
new file mode 100644
index 000000000..b3222a82f
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/prepare.c
@@ -0,0 +1,540 @@
+/*
+** 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.4 2005/09/10 16:46:13 drh Exp $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+
+/*
+** Fill the InitData structure with an error message that indicates
+** that the database is corrupt.
+*/
+static void corruptSchema(InitData *pData, const char *zExtra){
+ if( !sqlite3_malloc_failed ){
+ sqlite3SetString(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 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. NULL for trigger or view.
+** argv[2] = SQL text for the CREATE statement.
+** argv[3] = "1" for temporary files, "0" for main database, "2" or more
+** for auxiliary database files.
+**
+*/
+int sqlite3InitCallback(void *pInit, int argc, char **argv, char **azColName){
+ InitData *pData = (InitData*)pInit;
+ sqlite3 *db = pData->db;
+ int iDb;
+
+ assert( argc==4 );
+ if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */
+ if( argv[1]==0 || argv[3]==0 ){
+ corruptSchema(pData, 0);
+ return 1;
+ }
+ iDb = atoi(argv[3]);
+ 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;
+ if( SQLITE_OK!=rc ){
+ corruptSchema(pData, zErr);
+ sqlite3_free(zErr);
+ return rc;
+ }
+ }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;
+ char const *azArg[5];
+ char zDbNum[30];
+ 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 );
+
+ /* 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. */
+ sqlite3SafetyOff(db);
+ azArg[0] = zMasterName;
+ azArg[1] = "1";
+ azArg[2] = zMasterSchema;
+ sprintf(zDbNum, "%d", iDb);
+ azArg[3] = zDbNum;
+ azArg[4] = 0;
+ initData.db = db;
+ initData.pzErrMsg = pzErrMsg;
+ rc = sqlite3InitCallback(&initData, 4, (char **)azArg, 0);
+ if( rc!=SQLITE_OK ){
+ sqlite3SafetyOn(db);
+ return rc;
+ }
+ pTab = sqlite3FindTable(db, zMasterName, db->aDb[iDb].zName);
+ if( pTab ){
+ pTab->readOnly = 1;
+ }
+ sqlite3SafetyOn(db);
+
+ /* Create a cursor to hold the database open
+ */
+ if( db->aDb[iDb].pBt==0 ){
+ if( !OMIT_TEMPDB && iDb==1 ) DbSetProperty(db, 1, DB_SchemaLoaded);
+ return SQLITE_OK;
+ }
+ rc = sqlite3BtreeCursor(db->aDb[iDb].pBt, MASTER_ROOT, 0, 0, 0, &curMain);
+ if( rc!=SQLITE_OK && rc!=SQLITE_EMPTY ){
+ sqlite3SetString(pzErrMsg, sqlite3ErrStr(rc), (char*)0);
+ return rc;
+ }
+
+ /* 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 3:UTF-16 LE 4:UTF-16 BE
+ ** meta[5] The user cookie. Used by the application.
+ ** meta[6]
+ ** meta[7]
+ ** meta[8]
+ ** meta[9]
+ **
+ ** Note: The hash 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(db->aDb[iDb].pBt, i+1, (u32 *)&meta[i]);
+ }
+ if( rc ){
+ sqlite3SetString(pzErrMsg, sqlite3ErrStr(rc), (char*)0);
+ sqlite3BtreeCloseCursor(curMain);
+ return rc;
+ }
+ }else{
+ memset(meta, 0, sizeof(meta));
+ }
+ db->aDb[iDb].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 db->enc. */
+ db->enc = (u8)meta[4];
+ db->pDfltColl = sqlite3FindCollSeq(db, db->enc, "BINARY", 6, 0);
+ }else{
+ /* If opening an attached database, the encoding much match db->enc */
+ if( meta[4]!=db->enc ){
+ sqlite3BtreeCloseCursor(curMain);
+ sqlite3SetString(pzErrMsg, "attached databases must use the same"
+ " text encoding as main database", (char*)0);
+ return SQLITE_ERROR;
+ }
+ }
+ }
+
+ size = meta[2];
+ if( size==0 ){ size = MAX_PAGES; }
+ db->aDb[iDb].cache_size = size;
+
+ if( iDb==0 ){
+ db->file_format = meta[1];
+ if( db->file_format==0 ){
+ /* This happens if the database was initially empty */
+ db->file_format = 1;
+ }
+
+ if( db->file_format==2 || db->file_format==3 ){
+ /* File format 2 is treated exactly as file format 1. New
+ ** databases are created with file format 1.
+ */
+ db->file_format = 1;
+ }
+ }
+
+ /*
+ ** file_format==1 Version 3.0.0.
+ ** file_format==2 Version 3.1.3.
+ ** file_format==3 Version 3.1.4.
+ **
+ ** Version 3.0 can only use files with file_format==1. Version 3.1.3
+ ** can read and write files with file_format==1 or file_format==2.
+ ** Version 3.1.4 can read and write file formats 1, 2 and 3.
+ */
+ if( meta[1]>3 ){
+ sqlite3BtreeCloseCursor(curMain);
+ sqlite3SetString(pzErrMsg, "unsupported file format", (char*)0);
+ return SQLITE_ERROR;
+ }
+
+ sqlite3BtreeSetCacheSize(db->aDb[iDb].pBt, db->aDb[iDb].cache_size);
+
+ /* 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(
+ "SELECT name, rootpage, sql, '%s' FROM '%q'.%s",
+ zDbNum, db->aDb[iDb].zName, zMasterName);
+ sqlite3SafetyOff(db);
+ rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
+ sqlite3SafetyOn(db);
+ sqliteFree(zSql);
+#ifndef SQLITE_OMIT_ANALYZE
+ if( rc==SQLITE_OK ){
+ sqlite3AnalysisLoad(db, iDb);
+ }
+#endif
+ sqlite3BtreeCloseCursor(curMain);
+ }
+ if( sqlite3_malloc_failed ){
+ sqlite3SetString(pzErrMsg, "out of memory", (char*)0);
+ rc = SQLITE_NOMEM;
+ sqlite3ResetInternalSchema(db, 0);
+ }
+ if( rc==SQLITE_OK ){
+ DbSetProperty(db, iDb, DB_SchemaLoaded);
+ }else{
+ sqlite3ResetInternalSchema(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.
+*/
+int sqlite3Init(sqlite3 *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 = 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 ){
+ db->flags |= SQLITE_Initialized;
+ sqlite3CommitInternalChanges(db);
+ }
+
+ if( rc!=SQLITE_OK ){
+ db->flags &= ~SQLITE_Initialized;
+ }
+ 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.
+*/
+int sqlite3ReadSchema(Parse *pParse){
+ int rc = SQLITE_OK;
+ sqlite3 *db = pParse->db;
+ if( !db->init.busy ){
+ if( (db->flags & SQLITE_Initialized)==0 ){
+ rc = sqlite3Init(db, &pParse->zErrMsg);
+ }
+ }
+ assert( rc!=SQLITE_OK || (db->flags & SQLITE_Initialized) || db->init.busy );
+ 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;
+
+ for(iDb=0; allOk && iDb<db->nDb; iDb++){
+ Btree *pBt;
+ pBt = db->aDb[iDb].pBt;
+ if( pBt==0 ) continue;
+ rc = sqlite3BtreeCursor(pBt, MASTER_ROOT, 0, 0, 0, &curTemp);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3BtreeGetMeta(pBt, 1, (u32 *)&cookie);
+ if( rc==SQLITE_OK && cookie!=db->aDb[iDb].schema_cookie ){
+ allOk = 0;
+ }
+ sqlite3BtreeCloseCursor(curTemp);
+ }
+ }
+ return allOk;
+}
+
+/*
+** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
+*/
+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 */
+){
+ Parse sParse;
+ char *zErrMsg = 0;
+ int rc = SQLITE_OK;
+
+ if( sqlite3_malloc_failed ){
+ return SQLITE_NOMEM;
+ }
+
+ assert( ppStmt );
+ *ppStmt = 0;
+ if( sqlite3SafetyOn(db) ){
+ return SQLITE_MISUSE;
+ }
+
+ memset(&sParse, 0, sizeof(sParse));
+ sParse.db = db;
+ sqlite3RunParser(&sParse, zSql, &zErrMsg);
+
+ if( sqlite3_malloc_failed ){
+ rc = SQLITE_NOMEM;
+ sqlite3RollbackAll(db);
+ sqlite3ResetInternalSchema(db, 0);
+ db->flags &= ~SQLITE_InTrans;
+ goto prepare_out;
+ }
+ if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;
+ if( sParse.rc!=SQLITE_OK && sParse.checkSchema && !schemaIsValid(db) ){
+ sParse.rc = SQLITE_SCHEMA;
+ }
+ if( sParse.rc==SQLITE_SCHEMA ){
+ sqlite3ResetInternalSchema(db, 0);
+ }
+ 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, "order", P3_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 1, "from", P3_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 2, "detail", P3_STATIC);
+ }else{
+ sqlite3VdbeSetNumCols(sParse.pVdbe, 5);
+ sqlite3VdbeSetColName(sParse.pVdbe, 0, "addr", P3_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 1, "opcode", P3_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 2, "p1", P3_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 3, "p2", P3_STATIC);
+ sqlite3VdbeSetColName(sParse.pVdbe, 4, "p3", P3_STATIC);
+ }
+ }
+#endif
+
+prepare_out:
+ if( sqlite3SafetyOff(db) ){
+ rc = SQLITE_MISUSE;
+ }
+ if( rc==SQLITE_OK ){
+ *ppStmt = (sqlite3_stmt*)sParse.pVdbe;
+ }else if( sParse.pVdbe ){
+ sqlite3_finalize((sqlite3_stmt*)sParse.pVdbe);
+ }
+
+ if( zErrMsg ){
+ sqlite3Error(db, rc, "%s", zErrMsg);
+ sqliteFree(zErrMsg);
+ }else{
+ sqlite3Error(db, rc, 0);
+ }
+ return rc;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Compile the UTF-16 encoded SQL statement zSql into a statement handle.
+*/
+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 */
+){
+ /* 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 const *zSql8 = 0;
+ char const *zTail8 = 0;
+ int rc;
+ sqlite3_value *pTmp;
+
+ if( sqlite3SafetyCheck(db) ){
+ return SQLITE_MISUSE;
+ }
+ pTmp = sqlite3GetTransientValue(db);
+ sqlite3ValueSetStr(pTmp, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zSql8 = sqlite3ValueText(pTmp, SQLITE_UTF8);
+ if( !zSql8 ){
+ sqlite3Error(db, SQLITE_NOMEM, 0);
+ return SQLITE_NOMEM;
+ }
+ rc = sqlite3_prepare(db, zSql8, -1, 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);
+ }
+
+ return rc;
+}
+#endif /* SQLITE_OMIT_UTF16 */
diff --git a/kexi/3rdparty/kexisql3/src/printf.c b/kexi/3rdparty/kexisql3/src/printf.c
new file mode 100644
index 000000000..a669eb8d4
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/printf.c
@@ -0,0 +1,866 @@
+/*
+** 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 */
+#define etPOINTER 16 /* The %p conversion */
+
+
+/*
+** 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, 6, etDYNSTRING, 0, 0 },
+ { 'q', 0, 4, etSQLESCAPE, 0, 0 },
+ { 'Q', 0, 4, etSQLESCAPE2, 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 },
+ { 'f', 0, 1, etFLOAT, 0, 0 },
+ { 'e', 0, 1, etEXP, 30, 0 },
+ { 'E', 0, 1, etEXP, 14, 0 },
+ { 'G', 0, 1, etGENERIC, 14, 0 },
+ { '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 },
+};
+#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
+
+/*
+** 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 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_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 */
+ UINT64_TYPE 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 */
+ static const char spaces[] =
+ " ";
+#define etSPACESIZE (sizeof(spaces)-1)
+#ifndef etNOFLOATINGPOINT
+ 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
+
+ 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_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;
+ 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;
+
+ /* 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 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];
+ {
+ register const char *cset; /* Use registers for speed */
+ register 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 etNOFLOATINGPOINT
+ 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( 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 ){
+ 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;
+ }
+ 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*)) = 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;
+ int needQuote;
+ 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++;
+ }
+ needQuote = !isnull && xtype==etSQLESCAPE2;
+ n += i + 1 + needQuote*2;
+ if( n>etBUFSIZE ){
+ bufpt = zExtra = sqliteMalloc( n );
+ if( bufpt==0 ) return -1;
+ }else{
+ bufpt = buf;
+ }
+ j = 0;
+ if( needQuote ) bufpt[j++] = '\'';
+ for(i=0; (c=arg[i])!=0; i++){
+ bufpt[j++] = c;
+ if( c=='\'' ) bufpt[j++] = c;
+ }
+ if( needQuote ) bufpt[j++] = '\'';
+ bufpt[j] = 0;
+ length = j;
+ if( precision>=0 && precision<length ) length = precision;
+ break;
+ }
+ case etTOKEN: {
+ Token *pToken = va_arg(ap, Token*);
+ if( pToken && pToken->z ){
+ (*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 ){
+ register 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 ){
+ register 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{
+ char *zNew;
+ zNew = pM->xRealloc(pM->zText, pM->nAlloc);
+ if( zNew ){
+ pM->zText = zNew;
+ }
+ }
+ }
+ }
+ 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);
+ if( sM.zText ){
+ memcpy(sM.zText, sM.zBase, sM.nChar+1);
+ }
+ }else if( sM.nAlloc>sM.nChar+10 ){
+ char *zNew = xRealloc(sM.zText, sM.nChar+1);
+ if( zNew ){
+ sM.zText = zNew;
+ }
+ }
+ }
+ 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 *sqlite3VMPrintf(const char *zFormat, va_list ap){
+ char zBase[SQLITE_PRINT_BUF_SIZE];
+ return base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap);
+}
+
+/*
+** Print into memory obtained from sqliteMalloc(). Use the internal
+** %-conversion extensions.
+*/
+char *sqlite3MPrintf(const char *zFormat, ...){
+ va_list ap;
+ char *z;
+ char zBase[SQLITE_PRINT_BUF_SIZE];
+ 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 *sqlite3_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 sqlite3_mprintf.
+*/
+char *sqlite3_vmprintf(const char *zFormat, va_list ap){
+ char zBuf[200];
+ return base_vprintf((void*(*)(void*,int))realloc, 0,
+ zBuf, sizeof(zBuf), zFormat, ap);
+}
+
+/*
+** 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.
+*/
+char *sqlite3_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;
+}
+
+#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
+/*
+** 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.
+*/
+void sqlite3DebugPrintf(const char *zFormat, ...){
+ extern int getpid(void);
+ va_list ap;
+ char zBuf[500];
+ va_start(ap, zFormat);
+ base_vprintf(0, 0, zBuf, sizeof(zBuf), zFormat, ap);
+ va_end(ap);
+ fprintf(stdout,"%d: %s", getpid(), zBuf);
+ fflush(stdout);
+}
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/random.c b/kexi/3rdparty/kexisql3/src/random.c
new file mode 100644
index 000000000..12bbe4930
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/random.c
@@ -0,0 +1,100 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#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_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(){
+ 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;
+ sqlite3OsRandomSeed(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 sqlite3Randomness(int N, void *pBuf){
+ unsigned char *zBuf = pBuf;
+ sqlite3OsEnterMutex();
+ while( N-- ){
+ *(zBuf++) = randomByte();
+ }
+ sqlite3OsLeaveMutex();
+}
diff --git a/kexi/3rdparty/kexisql3/src/select.c b/kexi/3rdparty/kexisql3/src/select.c
new file mode 100644
index 000000000..86b22c386
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/select.c
@@ -0,0 +1,3127 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include "sqliteInt.h"
+
+
+/*
+** Allocate a new Select structure and return a pointer to that
+** structure.
+*/
+Select *sqlite3SelectNew(
+ 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;
+ pNew = sqliteMalloc( sizeof(*pNew) );
+ assert( !pOffset || pLimit ); /* Can't have OFFSET without LIMIT. */
+ if( pNew==0 ){
+ sqlite3ExprListDelete(pEList);
+ sqlite3SrcListDelete(pSrc);
+ sqlite3ExprDelete(pWhere);
+ sqlite3ExprListDelete(pGroupBy);
+ sqlite3ExprDelete(pHaving);
+ sqlite3ExprListDelete(pOrderBy);
+ sqlite3ExprDelete(pLimit);
+ sqlite3ExprDelete(pOffset);
+ }else{
+ if( pEList==0 ){
+ pEList = sqlite3ExprListAppend(0, sqlite3Expr(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->pLimit = pLimit;
+ pNew->pOffset = pOffset;
+ pNew->iLimit = -1;
+ pNew->iOffset = -1;
+ pNew->addrOpenVirt[0] = -1;
+ pNew->addrOpenVirt[1] = -1;
+ pNew->addrOpenVirt[2] = -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_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.
+*/
+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(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 = z;
+ p->n = strlen(z);
+ p->dyn = 0;
+}
+
+/*
+** Create an expression node for an identifier with the name of zName
+*/
+static Expr *createIdExpr(const char *zName){
+ Token dummy;
+ setToken(&dummy, zName);
+ return sqlite3Expr(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(
+ 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 */
+){
+ Expr *pE1a, *pE1b, *pE1c;
+ Expr *pE2a, *pE2b, *pE2c;
+ Expr *pE;
+
+ pE1a = createIdExpr(zCol);
+ pE2a = createIdExpr(zCol);
+ if( zAlias1==0 ){
+ zAlias1 = pTab1->zName;
+ }
+ pE1b = createIdExpr(zAlias1);
+ if( zAlias2==0 ){
+ zAlias2 = pTab2->zName;
+ }
+ pE2b = createIdExpr(zAlias2);
+ pE1c = sqlite3Expr(TK_DOT, pE1b, pE1a, 0);
+ pE2c = sqlite3Expr(TK_DOT, pE2b, pE2a, 0);
+ pE = sqlite3Expr(TK_EQ, pE1c, pE2c, 0);
+ ExprSetProperty(pE, EP_FromJoin);
+ pE->iRightJoinTable = iRightJoinTable;
+ *ppExpr = sqlite3ExprAnd(*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;
+
+ if( pLeftTab==0 || pRightTab==0 ) continue;
+
+ /* When the NATURAL keyword is present, add WHERE clause terms for
+ ** every column that the two tables have in common.
+ */
+ if( pLeft->jointype & JT_NATURAL ){
+ if( pLeft->pOn || pLeft->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(zName, pLeftTab, pLeft->zAlias,
+ pRightTab, pRight->zAlias,
+ pRight->iCursor, &p->pWhere);
+
+ }
+ }
+ }
+
+ /* Disallow both ON and USING clauses in the same join
+ */
+ if( pLeft->pOn && pLeft->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( pLeft->pOn ){
+ setJoinExpr(pLeft->pOn, pRight->iCursor);
+ p->pWhere = sqlite3ExprAnd(p->pWhere, pLeft->pOn);
+ pLeft->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( pLeft->pUsing ){
+ IdList *pList = pLeft->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(zName, pLeftTab, pLeft->zAlias,
+ pRightTab, pRight->zAlias,
+ pRight->iCursor, &p->pWhere);
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Delete the given Select structure and all of its substructures.
+*/
+void sqlite3SelectDelete(Select *p){
+ if( p==0 ) return;
+ 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);
+ sqliteFree(p);
+}
+
+/*
+** 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){
+ sqlite3ExprCodeExprList(pParse, pOrderBy);
+ sqlite3VdbeAddOp(v, OP_Sequence, pOrderBy->iECursor, 0);
+ sqlite3VdbeAddOp(v, OP_Pull, pOrderBy->nExpr + 1, 0);
+ sqlite3VdbeAddOp(v, OP_MakeRecord, pOrderBy->nExpr + 2, 0);
+ sqlite3VdbeAddOp(v, OP_IdxInsert, pOrderBy->iECursor, 0);
+}
+
+/*
+** 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 && iContinue!=0 ){
+ int addr = sqlite3VdbeCurrentAddr(v) + 3;
+ if( nPop>0 ) addr++;
+ sqlite3VdbeAddOp(v, OP_MemIncr, p->iOffset, 0);
+ sqlite3VdbeAddOp(v, OP_IfMemPos, p->iOffset, addr);
+ if( nPop>0 ){
+ sqlite3VdbeAddOp(v, OP_Pop, nPop, 0);
+ }
+ sqlite3VdbeAddOp(v, OP_Goto, 0, iContinue);
+ VdbeComment((v, "# skip OFFSET records"));
+ }
+ if( p->iLimit>=0 && iBreak!=0 ){
+ sqlite3VdbeAddOp(v, OP_MemIncr, p->iLimit, iBreak);
+ VdbeComment((v, "# exit when LIMIT reached"));
+ }
+}
+
+/*
+** Add code that will check to make sure the top N elements of the
+** stack are distinct. 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 K values are popped from the
+** stack if the top N elements are not distinct.
+*/
+static void codeDistinct(
+ Vdbe *v, /* Generate code into this VM */
+ int iTab, /* A sorting index used to test for distinctness */
+ int addrRepeat, /* Jump to here if not distinct */
+ int N, /* The top N elements of the stack must be distinct */
+ int K /* Pop K elements from the stack if indistinct */
+){
+#if NULL_ALWAYS_DISTINCT
+ sqlite3VdbeAddOp(v, OP_IsNull, -N, sqlite3VdbeCurrentAddr(v)+6);
+#endif
+ sqlite3VdbeAddOp(v, OP_MakeRecord, -N, 0);
+ sqlite3VdbeAddOp(v, OP_Distinct, iTab, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeAddOp(v, OP_Pop, K, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, addrRepeat);
+ VdbeComment((v, "# skip indistinct records"));
+ sqlite3VdbeAddOp(v, OP_IdxInsert, iTab, 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 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 */
+ char *aff /* affinity string if eDest is SRT_Union */
+){
+ 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++){
+ sqlite3VdbeAddOp(v, OP_Column, srcTab, i);
+ }
+ }else{
+ nColumn = pEList->nExpr;
+ sqlite3ExprCodeExprList(pParse, pEList);
+ }
+
+ /* 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 ){
+ int n = pEList->nExpr;
+ codeDistinct(v, distinct, iContinue, n, n+1);
+ 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.
+ */
+#ifndef SQLITE_OMIT_COMPOUND_SELECT
+ case SRT_Union: {
+ sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, NULL_ALWAYS_DISTINCT);
+ if( aff ){
+ sqlite3VdbeChangeP3(v, -1, aff, P3_STATIC);
+ }
+ sqlite3VdbeAddOp(v, OP_IdxInsert, 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 = sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, NULL_ALWAYS_DISTINCT);
+ sqlite3VdbeChangeP3(v, -1, aff, P3_STATIC);
+ sqlite3VdbeAddOp(v, OP_NotFound, iParm, addr+3);
+ sqlite3VdbeAddOp(v, OP_Delete, iParm, 0);
+ break;
+ }
+#endif
+
+ /* Store the result as data using a unique key.
+ */
+ case SRT_Table:
+ case SRT_VirtualTab: {
+ sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, 0);
+ if( pOrderBy ){
+ pushOntoSorter(pParse, v, pOrderBy);
+ }else{
+ sqlite3VdbeAddOp(v, OP_NewRowid, iParm, 0);
+ sqlite3VdbeAddOp(v, OP_Pull, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Insert, iParm, 0);
+ }
+ 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 addr1 = sqlite3VdbeCurrentAddr(v);
+ int addr2;
+
+ assert( nColumn==1 );
+ sqlite3VdbeAddOp(v, OP_NotNull, -1, addr1+3);
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ addr2 = sqlite3VdbeAddOp(v, OP_Goto, 0, 0);
+ 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, v, pOrderBy);
+ }else{
+ char aff = (iParm>>16)&0xFF;
+ aff = sqlite3CompareAffinity(pEList->a[0].pExpr, aff);
+ sqlite3VdbeOp3(v, OP_MakeRecord, 1, 0, &aff, 1);
+ sqlite3VdbeAddOp(v, OP_IdxInsert, (iParm&0x0000FFFF), 0);
+ }
+ sqlite3VdbeJumpHere(v, addr2);
+ 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_Exists:
+ case SRT_Mem: {
+ assert( nColumn==1 );
+ if( pOrderBy ){
+ pushOntoSorter(pParse, v, pOrderBy);
+ }else{
+ sqlite3VdbeAddOp(v, OP_MemStore, iParm, 1);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, iBreak);
+ }
+ 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 ){
+ sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, 0);
+ pushOntoSorter(pParse, v, pOrderBy);
+ }else if( eDest==SRT_Subroutine ){
+ sqlite3VdbeAddOp(v, OP_Gosub, 0, iParm);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Callback, nColumn, 0);
+ }
+ 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 );
+ sqlite3VdbeAddOp(v, OP_Pop, nColumn, 0);
+ break;
+ }
+#endif
+ }
+ return 0;
+}
+
+/*
+** 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 P3 field of an opcode using
+** P3_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 = sqliteMalloc( sizeof(*pInfo) + nExpr*(sizeof(CollSeq*)+1) );
+ if( pInfo ){
+ pInfo->aSortOrder = (char*)&pInfo->aColl[nExpr];
+ pInfo->nField = nExpr;
+ pInfo->enc = db->enc;
+ 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, /* The parsing context */
+ 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 brk = sqlite3VdbeMakeLabel(v);
+ int cont = sqlite3VdbeMakeLabel(v);
+ int addr;
+ int iTab;
+ ExprList *pOrderBy = p->pOrderBy;
+
+ iTab = pOrderBy->iECursor;
+ addr = 1 + sqlite3VdbeAddOp(v, OP_Sort, iTab, brk);
+ codeLimiter(v, p, cont, brk, 0);
+ sqlite3VdbeAddOp(v, OP_Column, iTab, pOrderBy->nExpr + 1);
+ switch( eDest ){
+ case SRT_Table:
+ case SRT_VirtualTab: {
+ sqlite3VdbeAddOp(v, OP_NewRowid, iParm, 0);
+ sqlite3VdbeAddOp(v, OP_Pull, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Insert, iParm, 0);
+ break;
+ }
+#ifndef SQLITE_OMIT_SUBQUERY
+ case SRT_Set: {
+ assert( nColumn==1 );
+ sqlite3VdbeAddOp(v, OP_NotNull, -1, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeOp3(v, OP_MakeRecord, 1, 0, "n", P3_STATIC);
+ sqlite3VdbeAddOp(v, OP_IdxInsert, (iParm&0x0000FFFF), 0);
+ break;
+ }
+ case SRT_Exists:
+ case SRT_Mem: {
+ assert( nColumn==1 );
+ sqlite3VdbeAddOp(v, OP_MemStore, iParm, 1);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, brk);
+ break;
+ }
+#endif
+ case SRT_Callback:
+ case SRT_Subroutine: {
+ int i;
+ sqlite3VdbeAddOp(v, OP_Integer, p->pEList->nExpr, 0);
+ sqlite3VdbeAddOp(v, OP_Pull, 1, 0);
+ for(i=0; i<nColumn; i++){
+ sqlite3VdbeAddOp(v, OP_Column, -1-i, i);
+ }
+ if( eDest==SRT_Callback ){
+ sqlite3VdbeAddOp(v, OP_Callback, nColumn, 0);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Gosub, 0, iParm);
+ }
+ sqlite3VdbeAddOp(v, OP_Pop, 2, 0);
+ break;
+ }
+ default: {
+ /* Do nothing */
+ break;
+ }
+ }
+ sqlite3VdbeResolveLabel(v, cont);
+ sqlite3VdbeAddOp(v, OP_Next, iTab, addr);
+ sqlite3VdbeResolveLabel(v, brk);
+}
+
+/*
+** Return a pointer to a string containing the 'declaration type' of the
+** expression pExpr. The string may be treated as static by the caller.
+**
+** If 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 an expression is either TEXT, NUMERIC or ANY.
+** The declaration type for a ROWID field is INTEGER.
+*/
+static const char *columnType(NameContext *pNC, Expr *pExpr){
+ char const *zType;
+ int j;
+ if( pExpr==0 || pNC->pSrcList==0 ) return 0;
+
+ /* The TK_AS operator can only occur in ORDER BY, GROUP BY, HAVING,
+ ** and LIMIT clauses. But pExpr originates in the result set of a
+ ** SELECT. So pExpr can never contain an AS operator.
+ */
+ assert( pExpr->op!=TK_AS );
+
+ switch( pExpr->op ){
+ case TK_COLUMN: {
+ Table *pTab = 0;
+ int iCol = pExpr->iColumn;
+ 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;
+ }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( iCol<0 ) iCol = pTab->iPKey;
+ assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) );
+ if( iCol<0 ){
+ zType = "INTEGER";
+ }else{
+ zType = pTab->aCol[iCol].zType;
+ }
+ break;
+ }
+#ifndef SQLITE_OMIT_SUBQUERY
+ case TK_SELECT: {
+ NameContext sNC;
+ Select *pS = pExpr->pSelect;
+ sNC.pSrcList = pExpr->pSelect->pSrc;
+ sNC.pNext = pNC;
+ zType = columnType(&sNC, pS->pEList->a[0].pExpr);
+ break;
+ }
+#endif
+ default:
+ zType = 0;
+ }
+
+ 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 */
+){
+ Vdbe *v = pParse->pVdbe;
+ int i;
+ NameContext sNC;
+ sNC.pSrcList = pTabList;
+ for(i=0; i<pEList->nExpr; i++){
+ Expr *p = pEList->a[i].pExpr;
+ const char *zType = columnType(&sNC, p);
+ if( zType==0 ) continue;
+ /* The vdbe must make it's own copy of the column-type, in case the
+ ** schema is reset before this virtual machine is deleted.
+ */
+ sqlite3VdbeSetColName(v, i+pEList->nExpr, zType, strlen(zType));
+ }
+}
+
+/*
+** 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 || sqlite3_malloc_failed ) 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, 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, 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, 0);
+ sqlite3VdbeSetColName(v, i, zName, P3_DYNAMIC);
+ }else{
+ sqlite3VdbeSetColName(v, i, zCol, strlen(zCol));
+ }
+ }else if( p->span.z && p->span.z[0] ){
+ sqlite3VdbeSetColName(v, i, p->span.z, p->span.n);
+ /* sqlite3VdbeCompressSpace(v, addr); */
+ }else{
+ char zName[30];
+ assert( p->op!=TK_COLUMN || pTabList==0 );
+ sprintf(zName, "column%d", i+1);
+ sqlite3VdbeSetColName(v, i, 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.
+*/
+Table *sqlite3ResultSetOfSelect(Parse *pParse, char *zTabName, Select *pSelect){
+ Table *pTab;
+ int i, j;
+ ExprList *pEList;
+ Column *aCol, *pCol;
+
+ if( prepSelectStmt(pParse, pSelect) ){
+ return 0;
+ }
+ if( sqlite3SelectResolve(pParse, pSelect, 0) ){
+ return 0;
+ }
+ pTab = sqliteMalloc( sizeof(Table) );
+ if( pTab==0 ){
+ return 0;
+ }
+ pTab->nRef = 1;
+ 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, pCol=aCol; i<pTab->nCol; i++, pCol++){
+ Expr *p, *pR;
+ char *zType;
+ char *zName;
+ char *zBasename;
+ 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 = sqliteStrDup(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("%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("%T", &p->span);
+ }else{
+ /* If all else fails, make up a name */
+ zName = sqlite3MPrintf("column%d", i+1);
+ }
+ sqlite3Dequote(zName);
+ if( sqlite3_malloc_failed ){
+ sqliteFree(zName);
+ sqlite3DeleteTable(0, pTab);
+ return 0;
+ }
+
+ /* Make sure the column name is unique. If the name is not unique,
+ ** append a integer to the name so that it becomes unique.
+ */
+ zBasename = zName;
+ for(j=cnt=0; j<i; j++){
+ if( sqlite3StrICmp(aCol[j].zName, zName)==0 ){
+ zName = sqlite3MPrintf("%s:%d", zBasename, ++cnt);
+ j = -1;
+ if( zName==0 ) break;
+ }
+ }
+ if( zBasename!=zName ){
+ sqliteFree(zBasename);
+ }
+ pCol->zName = zName;
+
+ /* Get the typename, type affinity, and collating sequence for the
+ ** column.
+ */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pSrcList = pSelect->pSrc;
+ zType = sqliteStrDup(columnType(&sNC, p));
+ pCol->zType = zType;
+ pCol->affinity = sqlite3ExprAffinity(p);
+ pCol->pColl = sqlite3ExprCollSeq(pParse, p);
+ if( !pCol->pColl ){
+ pCol->pColl = pParse->db->pDfltColl;
+ }
+ }
+ 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;
+ Table *pTab;
+ struct SrcList_item *pFrom;
+
+ if( p==0 || p->pSrc==0 || sqlite3_malloc_failed ) 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++){
+ 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("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 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;
+#endif
+ }else{
+ /* An ordinary table or view name in the FROM clause */
+ assert( pFrom->pTab==0 );
+ pFrom->pTab = pTab =
+ sqlite3LocateTable(pParse,pFrom->zName,pFrom->zDatabase);
+ if( pTab==0 ){
+ return 1;
+ }
+ pTab->nRef++;
+#ifndef SQLITE_OMIT_VIEW
+ if( pTab->pSelect ){
+ /* 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(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(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 = sqlite3NameFromToken(&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, *pLeft, *pRight;
+ char *zName = pTab->aCol[j].zName;
+
+ if( i>0 ){
+ struct SrcList_item *pLeft = &pTabList->a[i-1];
+ if( (pLeft->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->pUsing, zName)>=0 ){
+ /* In a join with a USING clause, omit columns in the
+ ** using clause from the table on the right. */
+ continue;
+ }
+ }
+ pRight = sqlite3Expr(TK_ID, 0, 0, 0);
+ if( pRight==0 ) break;
+ setToken(&pRight->token, zName);
+ if( zTabName && (longNames || pTabList->nSrc>1) ){
+ pLeft = sqlite3Expr(TK_ID, 0, 0, 0);
+ pExpr = sqlite3Expr(TK_DOT, pLeft, pRight, 0);
+ if( pExpr==0 ) break;
+ setToken(&pLeft->token, zTabName);
+ setToken(&pExpr->span, sqlite3MPrintf("%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;
+ }
+ if( longNames ){
+ pNew = sqlite3ExprListAppend(pNew, pExpr, &pExpr->span);
+ }else{
+ pNew = sqlite3ExprListAppend(pNew, pExpr, &pRight->token);
+ }
+ }
+ }
+ if( !tableSeen ){
+ if( zTName ){
+ sqlite3ErrorMsg(pParse, "no such table: %s", zTName);
+ }else{
+ sqlite3ErrorMsg(pParse, "no tables specified");
+ }
+ rc = 1;
+ }
+ sqliteFree(zTName);
+ }
+ }
+ sqlite3ExprListDelete(pEList);
+ p->pEList = pNew;
+ }
+ return rc;
+}
+
+#ifndef SQLITE_OMIT_COMPOUND_SELECT
+/*
+** 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.
+*/
+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( prepSelectStmt(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( sqlite3ExprIsInteger(pE, &iCol) ){
+ if( iCol<=0 || iCol>pEList->nExpr ){
+ sqlite3ErrorMsg(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;
+ zLabel = sqlite3NameFromToken(&pE->token);
+ assert( zLabel!=0 );
+ if( sqlite3StrICmp(zName, zLabel)==0 ){
+ iCol = j;
+ }
+ sqliteFree(zLabel);
+ }
+ if( iCol<0 && sqlite3ExprCompare(pE, pEList->a[j].pExpr) ){
+ iCol = j;
+ }
+ }
+ if( iCol>=0 ){
+ pE->op = TK_COLUMN;
+ pE->iColumn = iCol;
+ pE->iTable = iTable;
+ pE->iAgg = -1;
+ pOrderBy->a[i].done = 1;
+ }
+ if( iCol<0 && mustComplete ){
+ sqlite3ErrorMsg(pParse,
+ "ORDER BY term number %d does not match any result column", i+1);
+ nErr++;
+ break;
+ }
+ }
+ return nErr;
+}
+#endif /* #ifndef SQLITE_OMIT_COMPOUND_SELECT */
+
+/*
+** 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 *sqlite3GetVdbe(Parse *pParse){
+ Vdbe *v = pParse->pVdbe;
+ if( v==0 ){
+ v = pParse->pVdbe = sqlite3VdbeCreate(pParse->db);
+ }
+ return v;
+}
+
+/*
+** Compute the iLimit and iOffset fields of the SELECT based on the
+** pLimit and pOffset expressions. nLimit and nOffset 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 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){
+ /*
+ ** "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 ){
+ int iMem = pParse->nMem++;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+ sqlite3ExprCode(pParse, p->pLimit);
+ sqlite3VdbeAddOp(v, OP_MustBeInt, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Negative, 0, 0);
+ sqlite3VdbeAddOp(v, OP_MemStore, iMem, 1);
+ VdbeComment((v, "# LIMIT counter"));
+ p->iLimit = iMem;
+ }
+ if( p->pOffset ){
+ int iMem = pParse->nMem++;
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v==0 ) return;
+ sqlite3ExprCode(pParse, p->pOffset);
+ sqlite3VdbeAddOp(v, OP_MustBeInt, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Negative, 0, 0);
+ sqlite3VdbeAddOp(v, OP_MemStore, iMem, 1);
+ VdbeComment((v, "# OFFSET counter"));
+ p->iOffset = iMem;
+ }
+}
+
+/*
+** 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 = sqlite3VdbeAddOp(pParse->pVdbe, OP_OpenVirtual,
+ pOrderBy->iECursor, pOrderBy->nExpr+1);
+ assert( p->addrOpenVirt[2] == -1 );
+ p->addrOpenVirt[2] = addr;
+ }
+}
+
+/*
+** The opcode at addr is an OP_OpenVirtual that created a sorting
+** index tha we ended up not needing. This routine changes that
+** opcode to OP_Noop.
+*/
+static void uncreateSortingIndex(Parse *pParse, int addr){
+ Vdbe *v = pParse->pVdbe;
+ VdbeOp *pOp = sqlite3VdbeGetOp(v, addr);
+ sqlite3VdbeChangeP3(v, addr, 0, 0);
+ pOp->opcode = OP_Noop;
+ pOp->p1 = 0;
+ pOp->p2 = 0;
+}
+
+#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 */
+ int eDest, /* \___ Store query results as specified */
+ int iParm, /* / by these two parameters. */
+ 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 */
+
+ /* 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( eDest==SRT_VirtualTab ){
+ assert( p->pEList );
+ assert( nSetP2<sizeof(aSetP2)/sizeof(aSetP2[0]) );
+ aSetP2[nSetP2++] = sqlite3VdbeAddOp(v, OP_OpenVirtual, iParm, 0);
+ eDest = SRT_Table;
+ }
+
+ /* Generate code for the left and right SELECT statements.
+ */
+ pOrderBy = p->pOrderBy;
+ switch( p->op ){
+ case TK_ALL: {
+ if( pOrderBy==0 ){
+ assert( !pPrior->pLimit );
+ pPrior->pLimit = p->pLimit;
+ pPrior->pOffset = p->pOffset;
+ rc = sqlite3Select(pParse, pPrior, eDest, iParm, 0, 0, 0, aff);
+ if( rc ){
+ goto multi_select_end;
+ }
+ p->pPrior = 0;
+ p->iLimit = pPrior->iLimit;
+ p->iOffset = pPrior->iOffset;
+ p->pLimit = 0;
+ p->pOffset = 0;
+ rc = sqlite3Select(pParse, p, eDest, iParm, 0, 0, 0, aff);
+ p->pPrior = pPrior;
+ if( rc ){
+ goto multi_select_end;
+ }
+ 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;
+
+ priorOp = p->op==TK_ALL ? SRT_Table : SRT_Union;
+ if( eDest==priorOp && pOrderBy==0 && !p->pLimit && !p->pOffset ){
+ /* 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( pOrderBy && matchOrderbyToColumn(pParse, p, pOrderBy, unionTab,1) ){
+ rc = 1;
+ goto multi_select_end;
+ }
+ addr = sqlite3VdbeAddOp(v, OP_OpenVirtual, unionTab, 0);
+ if( priorOp==SRT_Table ){
+ assert( nSetP2<sizeof(aSetP2)/sizeof(aSetP2[0]) );
+ aSetP2[nSetP2++] = addr;
+ }else{
+ assert( p->addrOpenVirt[0] == -1 );
+ p->addrOpenVirt[0] = addr;
+ p->pRightmost->usesVirt = 1;
+ }
+ createSortingIndex(pParse, p, pOrderBy);
+ assert( p->pEList );
+ }
+
+ /* Code the SELECT statements to our left
+ */
+ assert( !pPrior->pOrderBy );
+ rc = sqlite3Select(pParse, pPrior, priorOp, unionTab, 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;
+ rc = sqlite3Select(pParse, p, op, unionTab, 0, 0, 0, aff);
+ 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( eDest!=priorOp || unionTab!=iParm ){
+ int iCont, iBreak, iStart;
+ assert( p->pEList );
+ if( eDest==SRT_Callback ){
+ generateColumnNames(pParse, 0, p->pEList);
+ }
+ iBreak = sqlite3VdbeMakeLabel(v);
+ iCont = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp(v, OP_Rewind, unionTab, iBreak);
+ computeLimitRegisters(pParse, p);
+ iStart = sqlite3VdbeCurrentAddr(v);
+ rc = selectInnerLoop(pParse, p, p->pEList, unionTab, p->pEList->nExpr,
+ pOrderBy, -1, eDest, iParm,
+ iCont, iBreak, 0);
+ if( rc ){
+ rc = 1;
+ goto multi_select_end;
+ }
+ sqlite3VdbeResolveLabel(v, iCont);
+ sqlite3VdbeAddOp(v, OP_Next, unionTab, iStart);
+ sqlite3VdbeResolveLabel(v, iBreak);
+ sqlite3VdbeAddOp(v, OP_Close, unionTab, 0);
+ }
+ break;
+ }
+ case TK_INTERSECT: {
+ int tab1, tab2;
+ int iCont, iBreak, iStart;
+ Expr *pLimit, *pOffset;
+ int addr;
+
+ /* 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( pOrderBy && matchOrderbyToColumn(pParse,p,pOrderBy,tab1,1) ){
+ rc = 1;
+ goto multi_select_end;
+ }
+ createSortingIndex(pParse, p, pOrderBy);
+
+ addr = sqlite3VdbeAddOp(v, OP_OpenVirtual, tab1, 0);
+ assert( p->addrOpenVirt[0] == -1 );
+ p->addrOpenVirt[0] = addr;
+ p->pRightmost->usesVirt = 1;
+ assert( p->pEList );
+
+ /* Code the SELECTs to our left into temporary table "tab1".
+ */
+ rc = sqlite3Select(pParse, pPrior, SRT_Union, tab1, 0, 0, 0, aff);
+ if( rc ){
+ goto multi_select_end;
+ }
+
+ /* Code the current SELECT into temporary table "tab2"
+ */
+ addr = sqlite3VdbeAddOp(v, OP_OpenVirtual, tab2, 0);
+ assert( p->addrOpenVirt[1] == -1 );
+ p->addrOpenVirt[1] = addr;
+ p->pPrior = 0;
+ pLimit = p->pLimit;
+ p->pLimit = 0;
+ pOffset = p->pOffset;
+ p->pOffset = 0;
+ rc = sqlite3Select(pParse, p, SRT_Union, tab2, 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( eDest==SRT_Callback ){
+ generateColumnNames(pParse, 0, p->pEList);
+ }
+ iBreak = sqlite3VdbeMakeLabel(v);
+ iCont = sqlite3VdbeMakeLabel(v);
+ sqlite3VdbeAddOp(v, OP_Rewind, tab1, iBreak);
+ computeLimitRegisters(pParse, p);
+ iStart = sqlite3VdbeAddOp(v, OP_RowKey, tab1, 0);
+ sqlite3VdbeAddOp(v, OP_NotFound, tab2, iCont);
+ rc = selectInnerLoop(pParse, p, p->pEList, tab1, p->pEList->nExpr,
+ pOrderBy, -1, eDest, iParm,
+ iCont, iBreak, 0);
+ if( rc ){
+ rc = 1;
+ goto multi_select_end;
+ }
+ sqlite3VdbeResolveLabel(v, iCont);
+ sqlite3VdbeAddOp(v, OP_Next, tab1, iStart);
+ sqlite3VdbeResolveLabel(v, iBreak);
+ sqlite3VdbeAddOp(v, OP_Close, tab2, 0);
+ sqlite3VdbeAddOp(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->usesVirt ){
+ int i; /* Loop counter */
+ KeyInfo *pKeyInfo; /* Collating sequence for the result set */
+ Select *pLoop; /* For looping through SELECT statements */
+ CollSeq **apColl;
+ CollSeq **aCopy;
+
+ assert( p->pRightmost==p );
+ pKeyInfo = sqliteMalloc(sizeof(*pKeyInfo)+nCol*2*sizeof(CollSeq*) + nCol);
+ if( !pKeyInfo ){
+ rc = SQLITE_NOMEM;
+ goto multi_select_end;
+ }
+
+ pKeyInfo->enc = pParse->db->enc;
+ 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->addrOpenVirt[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->addrOpenVirt[1]<0 );
+ break;
+ }
+ sqlite3VdbeChangeP2(v, addr, nCol);
+ sqlite3VdbeChangeP3(v, addr, (char*)pKeyInfo, P3_KEYINFO);
+ }
+ }
+
+ if( pOrderBy ){
+ struct ExprList_item *pOTerm = pOrderBy->a;
+ int nExpr = pOrderBy->nExpr;
+ int addr;
+ u8 *pSortOrder;
+
+ aCopy = (CollSeq**)&pKeyInfo[1];
+ pSortOrder = pKeyInfo->aSortOrder = (u8*)&aCopy[nExpr];
+ memcpy(aCopy, pKeyInfo->aColl, nCol*sizeof(CollSeq*));
+ apColl = pKeyInfo->aColl;
+ for(i=0; i<pOrderBy->nExpr; i++, pOTerm++, apColl++, pSortOrder++){
+ Expr *pExpr = pOTerm->pExpr;
+ char *zName = pOTerm->zName;
+ assert( pExpr->op==TK_COLUMN && pExpr->iColumn<nCol );
+ if( zName ){
+ *apColl = sqlite3LocateCollSeq(pParse, zName, -1);
+ }else{
+ *apColl = aCopy[pExpr->iColumn];
+ }
+ *pSortOrder = pOTerm->sortOrder;
+ }
+ assert( p->pRightmost==p );
+ assert( p->addrOpenVirt[2]>=0 );
+ addr = p->addrOpenVirt[2];
+ sqlite3VdbeChangeP2(v, addr, p->pEList->nExpr+2);
+ pKeyInfo->nField = pOrderBy->nExpr;
+ sqlite3VdbeChangeP3(v, addr, (char*)pKeyInfo, P3_KEYINFO_HANDOFF);
+ pKeyInfo = 0;
+ generateSortTail(pParse, p, v, p->pEList->nExpr, eDest, iParm);
+ }
+
+ sqliteFree(pKeyInfo);
+ }
+
+multi_select_end:
+ return rc;
+}
+#endif /* SQLITE_OMIT_COMPOUND_SELECT */
+
+#ifndef SQLITE_OMIT_VIEW
+/*
+** 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 substSelect(Select *, 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;
+ assert( pExpr->pLeft==0 );
+ pExpr->pLeft = sqlite3ExprDup(pNew->pLeft);
+ assert( pExpr->pRight==0 );
+ pExpr->pRight = sqlite3ExprDup(pNew->pRight);
+ assert( pExpr->pList==0 );
+ pExpr->pList = sqlite3ExprListDup(pNew->pList);
+ pExpr->iTable = pNew->iTable;
+ pExpr->iColumn = pNew->iColumn;
+ pExpr->iAgg = pNew->iAgg;
+ sqlite3TokenCopy(&pExpr->token, &pNew->token);
+ sqlite3TokenCopy(&pExpr->span, &pNew->span);
+ pExpr->pSelect = sqlite3SelectDup(pNew->pSelect);
+ pExpr->flags = pNew->flags;
+ }
+ }else{
+ substExpr(pExpr->pLeft, iTable, pEList);
+ substExpr(pExpr->pRight, iTable, pEList);
+ substSelect(pExpr->pSelect, 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);
+ }
+}
+static void substSelect(Select *p, int iTable, ExprList *pEList){
+ if( !p ) return;
+ substExprList(p->pEList, iTable, pEList);
+ substExprList(p->pGroupBy, iTable, pEList);
+ substExprList(p->pOrderBy, iTable, pEList);
+ substExpr(p->pHaving, iTable, pEList);
+ substExpr(p->pWhere, 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)
+**
+** 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; /* 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;
+ if( subqueryIsAgg && pSrc->nSrc>1 ) return 0;
+ pSubSrc = pSub->pSrc;
+ assert( pSubSrc );
+ if( (pSub->pLimit && p->pLimit) || pSub->pOffset ||
+ (pSub->pLimit && isAgg) ) return 0;
+ if( pSubSrc->nSrc==0 ) return 0;
+ if( pSub->isDistinct && (pSrc->nSrc>1 || isAgg) ){
+ return 0;
+ }
+ if( p->isDistinct && subqueryIsAgg ) return 0;
+ if( (p->disallowOrderBy || 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 = pSubitem->iCursor;
+ {
+ int nSubSrc = pSubSrc->nSrc;
+ int jointype = pSubitem->jointype;
+
+ sqlite3DeleteTable(0, pSubitem->pTab);
+ sqliteFree(pSubitem->zDatabase);
+ sqliteFree(pSubitem->zName);
+ sqliteFree(pSubitem->zAlias);
+ if( nSubSrc>1 ){
+ int extra = nSubSrc - 1;
+ for(i=1; i<nSubSrc; i++){
+ pSrc = sqlite3SrcListAppend(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 = sqlite3ExprDup(pSub->pWhere);
+ }else{
+ pWhere = 0;
+ }
+ if( subqueryIsAgg ){
+ assert( p->pHaving==0 );
+ p->pHaving = p->pWhere;
+ p->pWhere = pWhere;
+ substExpr(p->pHaving, iParent, pSub->pEList);
+ p->pHaving = sqlite3ExprAnd(p->pHaving, sqlite3ExprDup(pSub->pHaving));
+ assert( p->pGroupBy==0 );
+ p->pGroupBy = sqlite3ExprListDup(pSub->pGroupBy);
+ }else{
+ substExpr(p->pWhere, iParent, pSub->pEList);
+ p->pWhere = sqlite3ExprAnd(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;
+ */
+ 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 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 sqlite3Select().
+** 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( sqlite3StrNICmp(pExpr->token.z,"min",3)==0 ){
+ seekOp = OP_Rewind;
+ }else if( sqlite3StrNICmp(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{
+ CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr);
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ assert( pIdx->nColumn>=1 );
+ if( pIdx->aiColumn[0]==iCol && pIdx->keyInfo.aColl[0]==pColl ) 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 = sqlite3GetVdbe(pParse);
+ if( v==0 ) return 0;
+
+ /* If the output is destined for a temporary table, open that table.
+ */
+ if( eDest==SRT_VirtualTab ){
+ sqlite3VdbeAddOp(v, OP_OpenVirtual, iParm, 1);
+ }
+
+ /* 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.
+ */
+ sqlite3CodeVerifySchema(pParse, pTab->iDb);
+ base = pSrc->a[0].iCursor;
+ computeLimitRegisters(pParse, p);
+ if( pSrc->a[0].pSelect==0 ){
+ sqlite3OpenTableForReading(v, base, pTab);
+ }
+ cont = sqlite3VdbeMakeLabel(v);
+ if( pIdx==0 ){
+ sqlite3VdbeAddOp(v, seekOp, base, 0);
+ }else{
+ /* Even though the cursor used to open the index here is closed
+ ** as soon as a single value has been read from it, allocate it
+ ** using (pParse->nTab++) to prevent the cursor id from being
+ ** reused. This is important for statements of the form
+ ** "INSERT INTO x SELECT max() FROM x".
+ */
+ int iIdx;
+ iIdx = pParse->nTab++;
+ sqlite3VdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ sqlite3VdbeOp3(v, OP_OpenRead, iIdx, pIdx->tnum,
+ (char*)&pIdx->keyInfo, P3_KEYINFO);
+ if( seekOp==OP_Rewind ){
+ sqlite3VdbeAddOp(v, OP_Null, 0, 0);
+ sqlite3VdbeAddOp(v, OP_MakeRecord, 1, 0);
+ seekOp = OP_MoveGt;
+ }
+ sqlite3VdbeAddOp(v, seekOp, iIdx, 0);
+ sqlite3VdbeAddOp(v, OP_IdxRowid, iIdx, 0);
+ sqlite3VdbeAddOp(v, OP_Close, iIdx, 0);
+ sqlite3VdbeAddOp(v, OP_MoveGe, 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, 0);
+ sqlite3VdbeResolveLabel(v, cont);
+ sqlite3VdbeAddOp(v, OP_Close, base, 0);
+
+ return 1;
+}
+
+/*
+** Analyze and ORDER BY or GROUP BY clause in a SELECT statement. Return
+** the number of errors seen.
+**
+** An ORDER BY or GROUP BY is a list of expressions. If any expression
+** is an integer constant, then that expression is replaced by the
+** corresponding entry in the result set.
+*/
+static int processOrderGroupBy(
+ NameContext *pNC, /* Name context of the SELECT statement. */
+ ExprList *pOrderBy, /* The ORDER BY or GROUP BY clause to be processed */
+ const char *zType /* Either "ORDER" or "GROUP", as appropriate */
+){
+ int i;
+ ExprList *pEList = pNC->pEList; /* The result set of the SELECT */
+ Parse *pParse = pNC->pParse; /* The result set of the SELECT */
+ assert( pEList );
+
+ if( pOrderBy==0 ) return 0;
+ for(i=0; i<pOrderBy->nExpr; i++){
+ int iCol;
+ Expr *pE = pOrderBy->a[i].pExpr;
+ if( sqlite3ExprIsInteger(pE, &iCol) ){
+ if( iCol>0 && iCol<=pEList->nExpr ){
+ sqlite3ExprDelete(pE);
+ pE = pOrderBy->a[i].pExpr = sqlite3ExprDup(pEList->a[iCol-1].pExpr);
+ }else{
+ sqlite3ErrorMsg(pParse,
+ "%s BY column number %d out of range - should be "
+ "between 1 and %d", zType, iCol, pEList->nExpr);
+ return 1;
+ }
+ }
+ if( sqlite3ExprResolveNames(pNC, pE) ){
+ return 1;
+ }
+ if( sqlite3ExprIsConstant(pE) ){
+ sqlite3ErrorMsg(pParse,
+ "%s BY terms must not be non-integer constants", zType);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** 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.
+*/
+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.
+ */
+ sNC.pParse = pParse;
+ sNC.hasAgg = 0;
+ sNC.nErr = 0;
+ sNC.nRef = 0;
+ sNC.pEList = 0;
+ sNC.allowAgg = 0;
+ sNC.pSrcList = 0;
+ sNC.pNext = 0;
+ 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) ||
+ processOrderGroupBy(&sNC, p->pOrderBy, "ORDER") ||
+ processOrderGroupBy(&sNC, pGroupBy, "GROUP")
+ ){
+ return SQLITE_ERROR;
+ }
+
+ /* 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;
+ }
+ }
+ }
+
+ 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++){
+ sqlite3VdbeAddOp(v, OP_MemNull, pAggInfo->aCol[i].iMem, 0);
+ }
+ for(pFunc=pAggInfo->aFunc, i=0; i<pAggInfo->nFunc; i++, pFunc++){
+ sqlite3VdbeAddOp(v, OP_MemNull, pFunc->iMem, 0);
+ 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);
+ sqlite3VdbeOp3(v, OP_OpenVirtual, pFunc->iDistinct, 0,
+ (char*)pKeyInfo, P3_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;
+ sqlite3VdbeOp3(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0,
+ (void*)pF->pFunc, P3_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;
+ ExprList *pList = pF->pExpr->pList;
+ if( pList ){
+ nArg = pList->nExpr;
+ sqlite3ExprCodeExprList(pParse, pList);
+ }else{
+ nArg = 0;
+ }
+ if( pF->iDistinct>=0 ){
+ addrNext = sqlite3VdbeMakeLabel(v);
+ assert( nArg==1 );
+ codeDistinct(v, pF->iDistinct, addrNext, 1, 2);
+ }
+ if( pF->pFunc->needCollSeq ){
+ CollSeq *pColl = 0;
+ struct ExprList_item *pItem;
+ int j;
+ for(j=0, pItem=pList->a; !pColl && j<pList->nExpr; j++, pItem++){
+ pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
+ }
+ if( !pColl ){
+ pColl = pParse->db->pDfltColl;
+ }
+ sqlite3VdbeOp3(v, OP_CollSeq, 0, 0, (char *)pColl, P3_COLLSEQ);
+ }
+ sqlite3VdbeOp3(v, OP_AggStep, pF->iMem, nArg, (void*)pF->pFunc, P3_FUNCDEF);
+ if( addrNext ){
+ sqlite3VdbeResolveLabel(v, addrNext);
+ }
+ }
+ for(i=0, pC=pAggInfo->aCol; i<pAggInfo->nAccumulator; i++, pC++){
+ sqlite3ExprCode(pParse, pC->pExpr);
+ sqlite3VdbeAddOp(v, OP_MemStore, pC->iMem, 1);
+ }
+ pAggInfo->directMode = 0;
+}
+
+
+/*
+** 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 table 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 sqlite3Select(
+ 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 */
+ 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_OpenVirtual instruction */
+ AggInfo sAggInfo; /* Information used by aggregate queries */
+
+ if( sqlite3_malloc_failed || pParse->nErr || p==0 ) return 1;
+ if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
+ memset(&sAggInfo, 0, sizeof(sAggInfo));
+
+#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;
+ for(pLoop=p; pLoop; pLoop=pLoop->pPrior){
+ pLoop->pRightmost = p;
+ }
+ }
+ return multiSelect(pParse, p, eDest, iParm, aff);
+ }
+#endif
+
+ pOrderBy = p->pOrderBy;
+ if( IgnorableOrderby(eDest) ){
+ p->pOrderBy = 0;
+ }
+ if( sqlite3SelectResolve(pParse, p, 0) ){
+ goto select_end;
+ }
+ p->pOrderBy = pOrderBy;
+
+ /* 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.
+ */
+ assert( eDest!=SRT_Exists || pEList->nExpr==1 );
+#ifndef SQLITE_OMIT_SUBQUERY
+ if( (eDest==SRT_Mem || eDest==SRT_Set) && pEList->nExpr>1 ){
+ sqlite3ErrorMsg(pParse, "only a single result allowed for "
+ "a SELECT that is part of an expression");
+ goto select_end;
+ }
+#endif
+
+ /* ORDER BY is ignored for some destinations.
+ */
+ if( IgnorableOrderby(eDest) ){
+ pOrderBy = 0;
+ }
+
+ /* Begin generating code.
+ */
+ v = sqlite3GetVdbe(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
+ */
+#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];
+
+ if( pItem->pSelect==0 ) continue;
+ if( pItem->zName!=0 ){
+ zSavedAuthContext = pParse->zAuthContext;
+ pParse->zAuthContext = pItem->zName;
+ needRestoreContext = 1;
+ }else{
+ needRestoreContext = 0;
+ }
+ sqlite3Select(pParse, pItem->pSelect, SRT_VirtualTab,
+ pItem->iCursor, p, i, &isAgg, 0);
+ if( needRestoreContext ){
+ pParse->zAuthContext = zSavedAuthContext;
+ }
+ pTabList = p->pSrc;
+ pWhere = p->pWhere;
+ if( !IgnorableOrderby(eDest) ){
+ pOrderBy = p->pOrderBy;
+ }
+ pGroupBy = p->pGroupBy;
+ pHaving = p->pHaving;
+ isDistinct = p->isDistinct;
+ }
+#endif
+
+ /* 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.
+ */
+#ifndef SQLITE_OMIT_VIEW
+ if( pParent && pParentAgg &&
+ flattenSubquery(pParse, pParent, parentTab, *pParentAgg, isAgg) ){
+ if( isAgg ) *pParentAgg = 1;
+ goto select_end;
+ }
+#endif
+
+ /* If there is an ORDER BY clause, resolve any collation sequences
+ ** names that have been explicitly specified and create a sorting index.
+ **
+ ** 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_OpenVirtual 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 ){
+ struct ExprList_item *pTerm;
+ KeyInfo *pKeyInfo;
+ for(i=0, pTerm=pOrderBy->a; i<pOrderBy->nExpr; i++, pTerm++){
+ if( pTerm->zName ){
+ pTerm->pExpr->pColl = sqlite3LocateCollSeq(pParse, pTerm->zName, -1);
+ }
+ }
+ if( pParse->nErr ){
+ goto select_end;
+ }
+ pKeyInfo = keyInfoFromExprList(pParse, pOrderBy);
+ pOrderBy->iECursor = pParse->nTab++;
+ p->addrOpenVirt[2] = addrSortIndex =
+ sqlite3VdbeOp3(v, OP_OpenVirtual, pOrderBy->iECursor, pOrderBy->nExpr+2,
+ (char*)pKeyInfo, P3_KEYINFO_HANDOFF);
+ }else{
+ addrSortIndex = -1;
+ }
+
+ /* Set the limiter.
+ */
+ computeLimitRegisters(pParse, p);
+
+ /* If the output is destined for a temporary table, open that table.
+ */
+ if( eDest==SRT_VirtualTab ){
+ sqlite3VdbeAddOp(v, OP_OpenVirtual, iParm, pEList->nExpr);
+ }
+
+
+ /* Initialize the memory cell to NULL for SRT_Mem or 0 for SRT_Exists
+ */
+ if( eDest==SRT_Mem ){
+ sqlite3VdbeAddOp(v, OP_MemNull, iParm, 0);
+ }else if( eDest==SRT_Exists ){
+ sqlite3VdbeAddOp(v, OP_MemInt, 0, iParm);
+ }
+
+ /* Open a virtual index to use for the distinct set.
+ */
+ if( isDistinct ){
+ KeyInfo *pKeyInfo;
+ distinct = pParse->nTab++;
+ pKeyInfo = keyInfoFromExprList(pParse, p->pEList);
+ sqlite3VdbeOp3(v, OP_OpenVirtual, distinct, 0,
+ (char*)pKeyInfo, P3_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);
+ if( pWInfo==0 ) goto select_end;
+
+ /* If sorting index that was created by a prior OP_OpenVirtual
+ ** instruction ended up not being needed, then change the OP_OpenVirtual
+ ** into an OP_Noop.
+ */
+ if( addrSortIndex>=0 && pOrderBy==0 ){
+ uncreateSortingIndex(pParse, addrSortIndex);
+ p->addrOpenVirt[2] = -1;
+ }
+
+ /* Use the standard inner loop
+ */
+ if( selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, distinct, eDest,
+ iParm, pWInfo->iContinue, pWInfo->iBreak, aff) ){
+ goto select_end;
+ }
+
+ /* 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_OpenVirtual 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;
+ if( sqlite3ExprAnalyzeAggList(&sNC, pEList) ){
+ goto select_end;
+ }
+ if( sqlite3ExprAnalyzeAggList(&sNC, pOrderBy) ){
+ goto select_end;
+ }
+ if( pHaving && sqlite3ExprAnalyzeAggregates(&sNC, pHaving) ){
+ goto select_end;
+ }
+ sAggInfo.nAccumulator = sAggInfo.nColumn;
+ for(i=0; i<sAggInfo.nFunc; i++){
+ if( sqlite3ExprAnalyzeAggList(&sNC, sAggInfo.aFunc[i].pExpr->pList) ){
+ goto select_end;
+ }
+ }
+ if( sqlite3_malloc_failed ) goto select_end;
+
+ /* Processing for aggregates with GROUP BY is very different and
+ ** much more complex tha 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 OpenVirtual instruction
+ ** will be converted into a Noop.
+ */
+ sAggInfo.sortingIdx = pParse->nTab++;
+ pKeyInfo = keyInfoFromExprList(pParse, pGroupBy);
+ addrSortingIdx =
+ sqlite3VdbeOp3(v, OP_OpenVirtual, sAggInfo.sortingIdx,
+ sAggInfo.nSortingColumn,
+ (char*)pKeyInfo, P3_KEYINFO_HANDOFF);
+
+ /* Initialize memory locations used by GROUP BY aggregate processing
+ */
+ iUseFlag = pParse->nMem++;
+ iAbortFlag = pParse->nMem++;
+ iAMem = pParse->nMem;
+ pParse->nMem += pGroupBy->nExpr;
+ iBMem = pParse->nMem;
+ pParse->nMem += pGroupBy->nExpr;
+ sqlite3VdbeAddOp(v, OP_MemInt, 0, iAbortFlag);
+ VdbeComment((v, "# clear abort flag"));
+ sqlite3VdbeAddOp(v, OP_MemInt, 0, iUseFlag);
+ VdbeComment((v, "# indicate accumulator empty"));
+ sqlite3VdbeAddOp(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);
+ sqlite3VdbeAddOp(v, OP_MemInt, 1, iAbortFlag);
+ VdbeComment((v, "# set abort flag"));
+ sqlite3VdbeAddOp(v, OP_Return, 0, 0);
+ addrOutputRow = sqlite3VdbeCurrentAddr(v);
+ sqlite3VdbeAddOp(v, OP_IfMemPos, iUseFlag, addrOutputRow+2);
+ VdbeComment((v, "# Groupby result generator entry point"));
+ sqlite3VdbeAddOp(v, OP_Return, 0, 0);
+ finalizeAggFunctions(pParse, &sAggInfo);
+ if( pHaving ){
+ sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, 1);
+ }
+ rc = selectInnerLoop(pParse, p, p->pEList, 0, 0, pOrderBy,
+ distinct, eDest, iParm,
+ addrOutputRow+1, addrSetAbort, aff);
+ if( rc ){
+ goto select_end;
+ }
+ sqlite3VdbeAddOp(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);
+ sqlite3VdbeAddOp(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);
+ sqlite3VdbeAddOp(v, OP_Gosub, 0, addrReset);
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, &pGroupBy);
+ 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_OpenVirtual 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
+ */
+ groupBySort = 1;
+ sqlite3ExprCodeExprList(pParse, pGroupBy);
+ sqlite3VdbeAddOp(v, OP_Sequence, sAggInfo.sortingIdx, 0);
+ j = pGroupBy->nExpr+1;
+ for(i=0; i<sAggInfo.nColumn; i++){
+ struct AggInfo_col *pCol = &sAggInfo.aCol[i];
+ if( pCol->iSorterColumn<j ) continue;
+ if( pCol->iColumn<0 ){
+ sqlite3VdbeAddOp(v, OP_Rowid, pCol->iTable, 0);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Column, pCol->iTable, pCol->iColumn);
+ }
+ j++;
+ }
+ sqlite3VdbeAddOp(v, OP_MakeRecord, j, 0);
+ sqlite3VdbeAddOp(v, OP_IdxInsert, sAggInfo.sortingIdx, 0);
+ sqlite3WhereEnd(pWInfo);
+ sqlite3VdbeAddOp(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 ){
+ sqlite3VdbeAddOp(v, OP_Column, sAggInfo.sortingIdx, j);
+ }else{
+ sAggInfo.directMode = 1;
+ sqlite3ExprCode(pParse, pGroupBy->a[j].pExpr);
+ }
+ sqlite3VdbeAddOp(v, OP_MemStore, iBMem+j, j<pGroupBy->nExpr-1);
+ }
+ for(j=pGroupBy->nExpr-1; j>=0; j--){
+ if( j<pGroupBy->nExpr-1 ){
+ sqlite3VdbeAddOp(v, OP_MemLoad, iBMem+j, 0);
+ }
+ sqlite3VdbeAddOp(v, OP_MemLoad, iAMem+j, 0);
+ if( j==0 ){
+ sqlite3VdbeAddOp(v, OP_Eq, 0x200, addrProcessRow);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Ne, 0x200, addrGroupByChange);
+ }
+ sqlite3VdbeChangeP3(v, -1, (void*)pKeyInfo->aColl[j], P3_COLLSEQ);
+ }
+
+ /* 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++){
+ sqlite3VdbeAddOp(v, OP_MemMove, iAMem+j, iBMem+j);
+ }
+ sqlite3VdbeAddOp(v, OP_Gosub, 0, addrOutputRow);
+ VdbeComment((v, "# output one row"));
+ sqlite3VdbeAddOp(v, OP_IfMemPos, iAbortFlag, addrEnd);
+ VdbeComment((v, "# check abort flag"));
+ sqlite3VdbeAddOp(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);
+ sqlite3VdbeAddOp(v, OP_MemInt, 1, iUseFlag);
+ VdbeComment((v, "# indicate data in accumulator"));
+
+ /* End of the loop
+ */
+ if( groupBySort ){
+ sqlite3VdbeAddOp(v, OP_Next, sAggInfo.sortingIdx, addrTopOfLoop);
+ }else{
+ sqlite3WhereEnd(pWInfo);
+ uncreateSortingIndex(pParse, addrSortingIdx);
+ }
+
+ /* Output the final row of result
+ */
+ sqlite3VdbeAddOp(v, OP_Gosub, 0, addrOutputRow);
+ VdbeComment((v, "# output final row"));
+
+ } /* endif pGroupBy */
+ else {
+ /* 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, 0);
+ if( pWInfo==0 ) goto select_end;
+ updateAccumulator(pParse, &sAggInfo);
+ sqlite3WhereEnd(pWInfo);
+ finalizeAggFunctions(pParse, &sAggInfo);
+ pOrderBy = 0;
+ if( pHaving ){
+ sqlite3ExprIfFalse(pParse, pHaving, addrEnd, 1);
+ }
+ selectInnerLoop(pParse, p, p->pEList, 0, 0, 0, -1,
+ eDest, iParm, addrEnd, addrEnd, aff);
+ }
+ 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, eDest, iParm);
+ }
+
+#ifndef SQLITE_OMIT_SUBQUERY
+ /* 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 );
+ sqlite3SelectDelete(p);
+ pParent->pSrc->a[parentTab].pSelect = 0;
+ }
+#endif
+
+ /* 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:
+ sqliteFree(sAggInfo.aCol);
+ sqliteFree(sAggInfo.aFunc);
+ return rc;
+}
diff --git a/kexi/3rdparty/kexisql3/src/shell.c b/kexi/3rdparty/kexisql3/src/shell.c
new file mode 100644
index 000000000..a69f96be4
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/shell.c
@@ -0,0 +1,1821 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include "sqlite3.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 _WIN32
+# include <io.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 sqlite3 *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.
+*/
+static int isNumber(const unsigned char *z, int *realnum){
+ if( *z=='-' || *z=='+' ) z++;
+ if( !isdigit(*z) ){
+ return 0;
+ }
+ z++;
+ if( realnum ) *realnum = 0;
+ while( isdigit(*z) ){ z++; }
+ if( *z=='.' ){
+ z++;
+ if( !isdigit(*z) ) return 0;
+ while( isdigit(*z) ){ z++; }
+ if( realnum ) *realnum = 1;
+ }
+ if( *z=='e' || *z=='E' ){
+ z++;
+ if( *z=='+' || *z=='-' ) z++;
+ if( !isdigit(*z) ) return 0;
+ while( isdigit(*z) ){ z++; }
+ if( realnum ) *realnum = 1;
+ }
+ return *z==0;
+}
+
+/*
+** A global char* and an SQL function to access its current value
+** from within an SQL statement. This program used to use the
+** sqlite_exec_printf() API to substitue a string into an SQL statement.
+** The correct way to do this with sqlite3 is to use the bind API, but
+** since the shell is built around the callback paradigm it would be a lot
+** of work. Instead just use this hack, which is quite harmless.
+*/
+static const char *zShellStatic = 0;
+static void shellstaticFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ assert( 0==argc );
+ assert( zShellStatic );
+ sqlite3_result_text(context, zShellStatic, -1, SQLITE_STATIC);
+}
+
+
+/*
+** This routine reads a line of text from FILE in, 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 defined(HAVE_READLINE) && HAVE_READLINE==1
+ if( zResult ) add_history(zResult);
+#endif
+ 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 {
+ sqlite3 *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_Tcl 6 /* Generate ANSI-C or TCL quoted elements */
+#define MODE_Csv 7 /* Quote strings, numbers are plain */
+#define MODE_NUM_OF 8 /* The number of modes (not a mode itself) */
+
+char *modeDescr[MODE_NUM_OF] = {
+ "line",
+ "column",
+ "list",
+ "semi",
+ "html",
+ "insert",
+ "tcl",
+ "csv",
+};
+
+/*
+** 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 as a quoted according to C or TCL quoting rules.
+*/
+static void output_c_string(FILE *out, const char *z){
+ unsigned int c;
+ fputc('"', out);
+ while( (c = *(z++))!=0 ){
+ if( c=='\\' ){
+ fputc(c, out);
+ fputc(c, out);
+ }else if( c=='\t' ){
+ fputc('\\', out);
+ fputc('t', out);
+ }else if( c=='\n' ){
+ fputc('\\', out);
+ fputc('n', out);
+ }else if( c=='\r' ){
+ fputc('\\', out);
+ fputc('r', out);
+ }else if( !isprint(c) ){
+ fprintf(out, "\\%03o", c&0xff);
+ }else{
+ fputc(c, out);
+ }
+ }
+ fputc('"', 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;
+ }
+}
+
+/*
+** Output a single term of CSV. Actually, p->separator is used for
+** the separator, which may or may not be a comma. p->nullvalue is
+** the null value. Strings are quoted using ANSI-C rules. Numbers
+** appear outside of quotes.
+*/
+static void output_csv(struct callback_data *p, const char *z, int bSep){
+ if( z==0 ){
+ fprintf(p->out,"%s",p->nullvalue);
+ }else if( isNumber(z, 0) ){
+ fprintf(p->out,"%s",z);
+ }else{
+ output_c_string(p->out, z);
+ }
+ if( bSep ){
+ fprintf(p->out, p->separator);
+ }
+}
+
+#ifdef SIGINT
+/*
+** This routine runs when the user presses Ctrl-C
+*/
+static void interrupt_handler(int NotUsed){
+ seenInterrupt = 1;
+ if( db ) sqlite3_interrupt(db);
+}
+#endif
+
+/*
+** 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_Tcl: {
+ if( p->cnt++==0 && p->showHeader ){
+ for(i=0; i<nArg; i++){
+ output_c_string(p->out,azCol[i]);
+ fprintf(p->out, "%s", p->separator);
+ }
+ fprintf(p->out,"\n");
+ }
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ output_c_string(p->out, azArg[i] ? azArg[i] : p->nullvalue);
+ fprintf(p->out, "%s", p->separator);
+ }
+ fprintf(p->out,"\n");
+ break;
+ }
+ case MODE_Csv: {
+ if( p->cnt++==0 && p->showHeader ){
+ for(i=0; i<nArg; i++){
+ output_csv(p, azCol[i], i<nArg-1);
+ }
+ fprintf(p->out,"\n");
+ }
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ output_csv(p, azArg[i], i<nArg-1);
+ }
+ fprintf(p->out,"\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( isNumber(azArg[i], 0) ){
+ 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((unsigned char)*zName) && *zName!='_';
+ for(i=n=0; zName[i]; i++, n++){
+ if( !isalnum((unsigned char)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;
+}
+
+/* zIn is either a pointer to a NULL-terminated string in memory obtained
+** from malloc(), or a NULL pointer. The string pointed to by zAppend is
+** added to zIn, and the result returned in memory obtained from malloc().
+** zIn, if it was not NULL, is freed.
+**
+** If the third argument, quote, is not '\0', then it is used as a
+** quote character for zAppend.
+*/
+static char * appendText(char *zIn, char const *zAppend, char quote){
+ int len;
+ int i;
+ int nAppend = strlen(zAppend);
+ int nIn = (zIn?strlen(zIn):0);
+
+ len = nAppend+nIn+1;
+ if( quote ){
+ len += 2;
+ for(i=0; i<nAppend; i++){
+ if( zAppend[i]==quote ) len++;
+ }
+ }
+
+ zIn = (char *)realloc(zIn, len);
+ if( !zIn ){
+ return 0;
+ }
+
+ if( quote ){
+ char *zCsr = &zIn[nIn];
+ *zCsr++ = quote;
+ for(i=0; i<nAppend; i++){
+ *zCsr++ = zAppend[i];
+ if( zAppend[i]==quote ) *zCsr++ = quote;
+ }
+ *zCsr++ = quote;
+ *zCsr++ = '\0';
+ assert( (zCsr-zIn)==len );
+ }else{
+ memcpy(&zIn[nIn], zAppend, nAppend);
+ zIn[len-1] = '\0';
+ }
+
+ return zIn;
+}
+
+
+/*
+** Execute a query statement that has a single result column. Print
+** that result column on a line by itself with a semicolon terminator.
+*/
+static int run_table_dump_query(FILE *out, sqlite3 *db, const char *zSelect){
+ sqlite3_stmt *pSelect;
+ int rc;
+ rc = sqlite3_prepare(db, zSelect, -1, &pSelect, 0);
+ if( rc!=SQLITE_OK || !pSelect ){
+ return rc;
+ }
+ rc = sqlite3_step(pSelect);
+ while( rc==SQLITE_ROW ){
+ fprintf(out, "%s;\n", sqlite3_column_text(pSelect, 0));
+ rc = sqlite3_step(pSelect);
+ }
+ return sqlite3_finalize(pSelect);
+}
+
+
+/*
+** 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){
+ int rc;
+ const char *zTable;
+ const char *zType;
+ const char *zSql;
+ struct callback_data *p = (struct callback_data *)pArg;
+
+ if( nArg!=3 ) return 1;
+ zTable = azArg[0];
+ zType = azArg[1];
+ zSql = azArg[2];
+
+ if( strcmp(zTable, "sqlite_sequence")==0 ){
+ fprintf(p->out, "DELETE FROM sqlite_sequence;\n");
+ }else if( strcmp(zTable, "sqlite_stat1")==0 ){
+ fprintf(p->out, "ANALYZE sqlite_master;\n");
+ }else if( strncmp(zTable, "sqlite_", 7)==0 ){
+ return 0;
+ }else{
+ fprintf(p->out, "%s;\n", zSql);
+ }
+
+ if( strcmp(zType, "table")==0 ){
+ sqlite3_stmt *pTableInfo = 0;
+ char *zSelect = 0;
+ char *zTableInfo = 0;
+ char *zTmp = 0;
+
+ zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0);
+ zTableInfo = appendText(zTableInfo, zTable, '"');
+ zTableInfo = appendText(zTableInfo, ");", 0);
+
+ rc = sqlite3_prepare(p->db, zTableInfo, -1, &pTableInfo, 0);
+ if( zTableInfo ) free(zTableInfo);
+ if( rc!=SQLITE_OK || !pTableInfo ){
+ return 1;
+ }
+
+ zSelect = appendText(zSelect, "SELECT 'INSERT INTO ' || ", 0);
+ zTmp = appendText(zTmp, zTable, '"');
+ if( zTmp ){
+ zSelect = appendText(zSelect, zTmp, '\'');
+ }
+ zSelect = appendText(zSelect, " || ' VALUES(' || ", 0);
+ rc = sqlite3_step(pTableInfo);
+ while( rc==SQLITE_ROW ){
+ zSelect = appendText(zSelect, "quote(", 0);
+ zSelect = appendText(zSelect, sqlite3_column_text(pTableInfo, 1), '"');
+ rc = sqlite3_step(pTableInfo);
+ if( rc==SQLITE_ROW ){
+ zSelect = appendText(zSelect, ") || ', ' || ", 0);
+ }else{
+ zSelect = appendText(zSelect, ") ", 0);
+ }
+ }
+ rc = sqlite3_finalize(pTableInfo);
+ if( rc!=SQLITE_OK ){
+ if( zSelect ) free(zSelect);
+ return 1;
+ }
+ zSelect = appendText(zSelect, "|| ')' FROM ", 0);
+ zSelect = appendText(zSelect, zTable, '"');
+
+ rc = run_table_dump_query(p->out, p->db, zSelect);
+ if( rc==SQLITE_CORRUPT ){
+ zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0);
+ rc = run_table_dump_query(p->out, p->db, zSelect);
+ }
+ if( zSelect ) free(zSelect);
+ if( rc!=SQLITE_OK ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** Run zQuery. Update dump_callback() as the callback routine.
+** If we get a SQLITE_CORRUPT error, rerun the query after appending
+** "ORDER BY rowid DESC" to the end.
+*/
+static int run_schema_dump_query(
+ struct callback_data *p,
+ const char *zQuery,
+ char **pzErrMsg
+){
+ int rc;
+ rc = sqlite3_exec(p->db, zQuery, dump_callback, p, pzErrMsg);
+ if( rc==SQLITE_CORRUPT ){
+ char *zQ2;
+ int len = strlen(zQuery);
+ if( pzErrMsg ) sqlite3_free(*pzErrMsg);
+ zQ2 = malloc( len+100 );
+ if( zQ2==0 ) return rc;
+ sprintf(zQ2, "%s ORDER BY rowid DESC", zQuery);
+ rc = sqlite3_exec(p->db, zQ2, dump_callback, p, pzErrMsg);
+ free(zQ2);
+ }
+ return rc;
+}
+
+/*
+** Text of a help message
+*/
+static char zHelp[] =
+ ".databases List names and files of attached databases\n"
+ ".dump ?TABLE? ... Dump the database in an SQL 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"
+ ".import FILE TABLE Import data from FILE into TABLE\n"
+ ".indices TABLE Show names of all indices on TABLE\n"
+ ".mode MODE ?TABLE? Set output mode where MODE is one of:\n"
+ " csv Comma-separated values\n"
+ " column Left-aligned columns. (See .width)\n"
+ " html HTML <table> code\n"
+ " insert SQL insert statements for TABLE\n"
+ " line One value per line\n"
+ " list Values delimited by .separator string\n"
+ " tabs Tab-separated values\n"
+ " tcl TCL list elements\n"
+ ".nullvalue STRING Print STRING in place of NULL values\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 used by output mode and .import\n"
+ ".show Show the current values for various settings\n"
+ ".tables ?PATTERN? List names of tables matching a LIKE 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 ){
+ sqlite3_open(p->zDbFilename, &p->db, 0/*!exclusive*/, 1/*allowReadonly*/);
+ db = p->db;
+#ifdef SQLITE_HAS_CODEC
+ sqlite3_key(p->db, p->zKey, p->zKey ? strlen(p->zKey) : 0);
+#endif
+ sqlite3_create_function(db, "shellstatic", 0, SQLITE_UTF8, 0,
+ shellstaticFunc, 0, 0);
+ if( SQLITE_OK!=sqlite3_errcode(db) ){
+ fprintf(stderr,"Unable to open database \"%s\": %s\n",
+ p->zDbFilename, sqlite3_errmsg(db));
+ exit(1);
+ }
+ }
+}
+
+/*
+** Do C-language style dequoting.
+**
+** \t -> tab
+** \n -> newline
+** \r -> carriage return
+** \NNN -> ascii character NNN in octal
+** \\ -> backslash
+*/
+static void resolve_backslashes(char *z){
+ int i, j, c;
+ for(i=j=0; (c = z[i])!=0; i++, j++){
+ if( c=='\\' ){
+ c = z[++i];
+ if( c=='n' ){
+ c = '\n';
+ }else if( c=='t' ){
+ c = '\t';
+ }else if( c=='r' ){
+ c = '\r';
+ }else if( c>='0' && c<='7' ){
+ c =- '0';
+ if( z[i+1]>='0' && z[i+1]<='7' ){
+ i++;
+ c = (c<<3) + z[i] - '0';
+ if( z[i+1]>='0' && z[i+1]<='7' ){
+ i++;
+ c = (c<<3) + z[i] - '0';
+ }
+ }
+ }
+ }
+ z[j] = c;
+ }
+ z[j] = 0;
+}
+
+/*
+** 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((unsigned char)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;
+ }
+ if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
+ }else{
+ azArg[nArg++] = &zLine[i];
+ while( zLine[i] && !isspace((unsigned char)zLine[i]) ){ i++; }
+ if( zLine[i] ) zLine[i++] = 0;
+ resolve_backslashes(azArg[nArg-1]);
+ }
+ }
+
+ /* 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;
+ data.cnt = 0;
+ sqlite3_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg);
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite3_free(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 ){
+ run_schema_dump_query(p,
+ "SELECT name, type, sql FROM sqlite_master "
+ "WHERE sql NOT NULL AND type=='table'", 0
+ );
+ run_schema_dump_query(p,
+ "SELECT name, type, sql FROM sqlite_master "
+ "WHERE sql NOT NULL AND type!='table' AND type!='meta'", 0
+ );
+ }else{
+ int i;
+ for(i=1; i<nArg; i++){
+ zShellStatic = azArg[i];
+ run_schema_dump_query(p,
+ "SELECT name, type, sql FROM sqlite_master "
+ "WHERE tbl_name LIKE shellstatic() AND type=='table'"
+ " AND sql NOT NULL", 0);
+ run_schema_dump_query(p,
+ "SELECT name, type, sql FROM sqlite_master "
+ "WHERE tbl_name LIKE shellstatic() AND type!='table'"
+ " AND type!='meta' AND sql NOT NULL", 0);
+ zShellStatic = 0;
+ }
+ }
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite3_free(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++){
+ z[j] = tolower((unsigned char)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;
+ static char zOne[] = "1";
+ char *z = nArg>=2 ? azArg[1] : zOne;
+ int val = atoi(z);
+ for(j=0; z[j]; j++){
+ z[j] = tolower((unsigned char)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] = 14;
+ p->colWidth[2] = 10;
+ p->colWidth[3] = 10;
+ p->colWidth[4] = 33;
+ }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++){
+ z[j] = tolower((unsigned char)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,zHelp);
+ }else
+
+ if( c=='i' && strncmp(azArg[0], "import", n)==0 && nArg>=3 ){
+ char *zTable = azArg[2]; /* Insert data into this table */
+ char *zFile = azArg[1]; /* The file from which to extract data */
+ sqlite3_stmt *pStmt; /* A statement */
+ int rc; /* Result code */
+ int nCol; /* Number of columns in the table */
+ int nByte; /* Number of bytes in an SQL string */
+ int i, j; /* Loop counters */
+ int nSep; /* Number of bytes in p->separator[] */
+ char *zSql; /* An SQL statement */
+ char *zLine; /* A single line of input from the file */
+ char **azCol; /* zLine[] broken up into columns */
+ char *zCommit; /* How to commit changes */
+ FILE *in; /* The input file */
+ int lineno = 0; /* Line number of input file */
+
+ nSep = strlen(p->separator);
+ if( nSep==0 ){
+ fprintf(stderr, "non-null separator required for import\n");
+ return 0;
+ }
+ zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable);
+ if( zSql==0 ) return 0;
+ nByte = strlen(zSql);
+ rc = sqlite3_prepare(p->db, zSql, 0, &pStmt, 0);
+ sqlite3_free(zSql);
+ if( rc ){
+ fprintf(stderr,"Error: %s\n", sqlite3_errmsg(db));
+ nCol = 0;
+ }else{
+ nCol = sqlite3_column_count(pStmt);
+ }
+ sqlite3_finalize(pStmt);
+ if( nCol==0 ) return 0;
+ zSql = malloc( nByte + 20 + nCol*2 );
+ if( zSql==0 ) return 0;
+ sqlite3_snprintf(nByte+20, zSql, "INSERT INTO '%q' VALUES(?", zTable);
+ j = strlen(zSql);
+ for(i=1; i<nCol; i++){
+ zSql[j++] = ',';
+ zSql[j++] = '?';
+ }
+ zSql[j++] = ')';
+ zSql[j] = 0;
+ rc = sqlite3_prepare(p->db, zSql, 0, &pStmt, 0);
+ free(zSql);
+ if( rc ){
+ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
+ sqlite3_finalize(pStmt);
+ return 0;
+ }
+ in = fopen(zFile, "rb");
+ if( in==0 ){
+ fprintf(stderr, "cannot open file: %s\n", zFile);
+ sqlite3_finalize(pStmt);
+ return 0;
+ }
+ azCol = malloc( sizeof(azCol[0])*(nCol+1) );
+ if( azCol==0 ) return 0;
+ sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
+ zCommit = "COMMIT";
+ while( (zLine = local_getline(0, in))!=0 ){
+ char *z;
+ i = 0;
+ lineno++;
+ azCol[0] = zLine;
+ for(i=0, z=zLine; *z && *z!='\n' && *z!='\r'; z++){
+ if( *z==p->separator[0] && strncmp(z, p->separator, nSep)==0 ){
+ *z = 0;
+ i++;
+ if( i<nCol ){
+ azCol[i] = &z[nSep];
+ z += nSep-1;
+ }
+ }
+ }
+ *z = 0;
+ if( i+1!=nCol ){
+ fprintf(stderr,"%s line %d: expected %d columns of data but found %d\n",
+ zFile, lineno, nCol, i+1);
+ zCommit = "ROLLBACK";
+ break;
+ }
+ for(i=0; i<nCol; i++){
+ sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC);
+ }
+ sqlite3_step(pStmt);
+ rc = sqlite3_reset(pStmt);
+ free(zLine);
+ if( rc!=SQLITE_OK ){
+ fprintf(stderr,"Error: %s\n", sqlite3_errmsg(db));
+ zCommit = "ROLLBACK";
+ break;
+ }
+ }
+ free(azCol);
+ fclose(in);
+ sqlite3_finalize(pStmt);
+ sqlite3_exec(p->db, zCommit, 0, 0, 0);
+ }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;
+ zShellStatic = azArg[1];
+ sqlite3_exec(p->db,
+ "SELECT name FROM sqlite_master "
+ "WHERE type='index' AND tbl_name LIKE shellstatic() "
+ "UNION ALL "
+ "SELECT name FROM sqlite_temp_master "
+ "WHERE type='index' AND tbl_name LIKE shellstatic() "
+ "ORDER BY 1",
+ callback, &data, &zErrMsg
+ );
+ zShellStatic = 0;
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite3_free(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],"tcl",n2)==0 ){
+ p->mode = MODE_Tcl;
+ }else if( strncmp(azArg[1],"csv",n2)==0 ){
+ p->mode = MODE_Csv;
+ strcpy(p->separator, ",");
+ }else if( strncmp(azArg[1],"tabs",n2)==0 ){
+ p->mode = MODE_List;
+ strcpy(p->separator, "\t");
+ }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 csv html insert line list tabs tcl\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{
+ sqlite3_free(p->zKey);
+ p->zKey = sqlite3_mprintf("%s", azArg[2]);
+ sqlite3_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 ){
+ int i;
+ for(i=0; azArg[1][i]; i++) azArg[1][i] = tolower(azArg[1][i]);
+ if( strcmp(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( strcmp(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{
+ zShellStatic = azArg[1];
+ sqlite3_exec(p->db,
+ "SELECT sql FROM "
+ " (SELECT * FROM sqlite_master UNION ALL"
+ " SELECT * FROM sqlite_temp_master) "
+ "WHERE tbl_name LIKE shellstatic() AND type!='meta' AND sql NOTNULL "
+ "ORDER BY substr(type,2,1), name",
+ callback, &data, &zErrMsg);
+ zShellStatic = 0;
+ }
+ }else{
+ sqlite3_exec(p->db,
+ "SELECT sql FROM "
+ " (SELECT * FROM sqlite_master UNION ALL"
+ " SELECT * FROM sqlite_temp_master) "
+ "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%'"
+ "ORDER BY substr(type,2,1), name",
+ callback, &data, &zErrMsg
+ );
+ }
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite3_free(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: ", "nullvalue");
+ output_c_string(p->out, p->nullvalue);
+ fprintf(p->out, "\n");
+ fprintf(p->out,"%9.9s: %s\n","output",
+ strlen(p->outfile) ? p->outfile : "stdout");
+ fprintf(p->out,"%9.9s: ", "separator");
+ output_c_string(p->out, p->separator);
+ fprintf(p->out, "\n");
+ 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");
+ }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 = sqlite3_get_table(p->db,
+ "SELECT name FROM sqlite_master "
+ "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%'"
+ "UNION ALL "
+ "SELECT name FROM sqlite_temp_master "
+ "WHERE type IN ('table','view') "
+ "ORDER BY 1",
+ &azResult, &nRow, 0, &zErrMsg
+ );
+ }else{
+ zShellStatic = azArg[1];
+ rc = sqlite3_get_table(p->db,
+ "SELECT name FROM sqlite_master "
+ "WHERE type IN ('table','view') AND name LIKE '%'||shellstatic()||'%' "
+ "UNION ALL "
+ "SELECT name FROM sqlite_temp_master "
+ "WHERE type IN ('table','view') AND name LIKE '%'||shellstatic()||'%' "
+ "ORDER BY 1",
+ &azResult, &nRow, 0, &zErrMsg
+ );
+ zShellStatic = 0;
+ }
+ if( zErrMsg ){
+ fprintf(stderr,"Error: %s\n", zErrMsg);
+ sqlite3_free(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");
+ }
+ }
+ sqlite3_free_table(azResult);
+ }else
+
+ if( c=='t' && n>1 && strncmp(azArg[0], "timeout", n)==0 && nArg>=2 ){
+ open_db(p);
+ sqlite3_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((unsigned char)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(*(unsigned char*)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){
+ while( isspace(*(unsigned char*)zLine) ){ zLine++; };
+ if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ) return 1; /* Oracle */
+ if( tolower(zLine[0])=='g' && tolower(zLine[1])=='o'
+ && _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((unsigned char)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) && sqlite3_complete(zSql) ){
+ p->cnt = 0;
+ open_db(p);
+ rc = sqlite3_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);
+ sqlite3_free(zErrMsg);
+ zErrMsg = 0;
+ }else{
+ printf("SQL error: %s\n", sqlite3_errmsg(p->db));
+ }
+ }
+ 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"
+ " -verbose-vacuum set VACUUM to verbose, interactive mode\n"
+ " so it is possible to get progress info\n"
+ " and abort the process\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;
+
+#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 = sqlite3_mprintf("%s",argv[i]);
+ }
+ }
+ if( i<argc ){
+ data.zDbFilename = argv[i++];
+ }else{
+#ifndef SQLITE_OMIT_MEMORYDB
+ data.zDbFilename = ":memory:";
+#else
+ data.zDbFilename = 0;
+#endif
+ }
+ if( i<argc ){
+ zFirstCmd = argv[i++];
+ }
+ data.out = stdout;
+
+#ifdef SQLITE_OMIT_MEMORYDB
+ if( data.zDbFilename==0 ){
+ fprintf(stderr,"%s: no database filename specified\n", argv[0]);
+ exit(1);
+ }
+#endif
+
+ /* 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( access(data.zDbFilename, 0)==0 ){
+ 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,"-verbose-vacuum")==0 ){ /*(jstaniek)*/
+ sqlite_set_verbose_vacuum(1);
+ }else if( strcmp(z,"-version")==0 ){
+ printf("%s\n", sqlite3_libversion());
+ 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 = sqlite3_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 (customized, bundled with Kexi)\n"
+ "Enter \".help\" for instructions\n",
+ sqlite3_libversion()
+ );
+ zHome = find_home_dir();
+ if( zHome && (zHistory = malloc(strlen(zHome)+20))!=0 ){
+ sprintf(zHistory,"%s/.sqlite_history", zHome);
+ }
+#if defined(HAVE_READLINE) && HAVE_READLINE==1
+ if( zHistory ) read_history(zHistory);
+#endif
+ process_input(&data, 0);
+ if( zHistory ){
+ stifle_history(100);
+ write_history(zHistory);
+ }
+ }else{
+ process_input(&data, stdin);
+ }
+ }
+ set_table_name(&data, 0);
+ if( db ) sqlite3_close(db);
+ return 0;
+}
diff --git a/kexi/3rdparty/kexisql3/src/sqlite.h b/kexi/3rdparty/kexisql3/src/sqlite.h
new file mode 100644
index 000000000..b5e2c60e9
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/sqlite.h
@@ -0,0 +1 @@
+#include "sqlite3.h"
diff --git a/kexi/3rdparty/kexisql3/src/sqlite3.h b/kexi/3rdparty/kexisql3/src/sqlite3.h
new file mode 100644
index 000000000..414c6cac6
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/sqlite3.h
@@ -0,0 +1,1302 @@
+/*
+** 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: sqlite3.h 548347 2006-06-05 10:53:00Z staniek $
+*/
+#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
+
+/*
+** The version of the SQLite library.
+*/
+#ifdef SQLITE_VERSION
+# undef SQLITE_VERSION
+#endif
+#define SQLITE_VERSION "3.2.8"
+
+/*
+** The format of the version string is "X.Y.Z<trailing string>", where
+** X is the major version number, Y is the minor version number and Z
+** is the release number. The trailing string is often "alpha" or "beta".
+** For example "3.1.1beta".
+**
+** The SQLITE_VERSION_NUMBER is an integer with the value
+** (X*100000 + Y*1000 + Z). For example, for version "3.1.1beta",
+** SQLITE_VERSION_NUMBER is set to 3001001. To detect if they are using
+** version 3.1.1 or greater at compile time, programs may use the test
+** (SQLITE_VERSION_NUMBER>=3001001).
+*/
+#ifdef SQLITE_VERSION_NUMBER
+# undef SQLITE_VERSION_NUMBER
+#endif
+#define SQLITE_VERSION_NUMBER 3002008
+
+/*
+** 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. The sqlite3_libversion() function returns a pointer
+** to the sqlite3_version variable - useful in DLLs which cannot access
+** global variables.
+*/
+extern const char sqlite3_version[];
+const char *sqlite3_libversion(void);
+
+/*
+** Return the value of the SQLITE_VERSION_NUMBER macro when the
+** library was compiled.
+*/
+int sqlite3_libversion_number(void);
+
+/*
+** Each open sqlite database is represented by an instance of the
+** following opaque structure.
+*/
+typedef struct sqlite3 sqlite3;
+
+
+/*
+** Some compilers do not support the "long long" datatype. So we have
+** to do a typedef that for 64-bit integers that depends on what compiler
+** is being used.
+*/
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+ typedef __int64 sqlite_int64;
+ typedef unsigned __int64 sqlite_uint64;
+#else
+ typedef long long int sqlite_int64;
+ typedef unsigned long long int sqlite_uint64;
+#endif
+
+
+/*
+** A function to close the database.
+**
+** Call this function with a pointer to a structure that was previously
+** returned from sqlite3_open() and the corresponding database will by closed.
+**
+** All SQL statements prepared using sqlite3_prepare() or
+** sqlite3_prepare16() must be deallocated using sqlite3_finalize() before
+** this routine is called. Otherwise, SQLITE_BUSY is returned and the
+** database connection remains open.
+*/
+int sqlite3_close(sqlite3 *);
+
+/*
+** The type for a callback function.
+*/
+typedef int (*sqlite3_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 sqlite3_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 sqlite3_free() 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 sqlite3_busy_handler()
+** and sqlite3_busy_timeout() functions below.)
+*/
+int sqlite3_exec(
+ sqlite3*, /* An open database */
+ const char *sql, /* SQL to be executed */
+ sqlite3_callback, /* Callback function */
+ void *, /* 1st argument to callback function */
+ char **errmsg /* Error msg written here */
+);
+
+/*
+** Return values for sqlite3_exec() and sqlite3_step()
+*/
+#define SQLITE_OK 0 /* Successful result */
+#define SQLITE_ERROR 1 /* SQL error or missing database */
+#define SQLITE_INTERNAL 2 /* NOT USED. 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 /* Database lock protocol error */
+#define SQLITE_EMPTY 16 /* Database is empty */
+#define SQLITE_SCHEMA 17 /* The database schema changed */
+#define SQLITE_TOOBIG 18 /* NOT USED. Too much data for one row */
+#define SQLITE_CONSTRAINT 19 /* Abort due to contraint 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 */
+
+/* js: Used in sqlite3OsOpenReadWrite() */
+#define SQLITE_CANTOPEN_WITH_LOCKED_READWRITE 0x1001 /* Cannot open with locked read/write access */
+#define SQLITE_CANTOPEN_WITH_LOCKED_WRITE 0x1002 /* Cannot open with locked write access */
+
+/*
+** 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.
+*/
+sqlite_int64 sqlite3_last_insert_rowid(sqlite3*);
+
+/*
+** This function returns the number of database rows that were changed
+** (or inserted or deleted) by the most recent called sqlite3_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 sqlite3_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 sqlite3_changes(sqlite3*);
+
+/*
+** This function returns the number of database rows that have been
+** modified by INSERT, UPDATE or DELETE statements since the database handle
+** was opened. This includes UPDATE, INSERT and DELETE statements executed
+** as part of trigger programs. All changes are counted as soon as the
+** statement that makes them is completed (when the statement handle is
+** passed to sqlite3_reset() or sqlite_finalise()).
+**
+** 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 sqlite3_total_changes(sqlite3*);
+
+/* 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 sqlite3_interrupt(sqlite3*);
+
+
+/* These functions return true if the given input string comprises
+** one or more complete SQL statements. For the sqlite3_complete() call,
+** the parameter must be a nul-terminated UTF-8 string. For
+** sqlite3_complete16(), a nul-terminated machine byte order UTF-16 string
+** is required.
+**
+** The algorithm is simple. If the last token other than spaces
+** and comments is a semicolon, then return true. otherwise return
+** false.
+*/
+int sqlite3_complete(const char *sql);
+int sqlite3_complete16(const void *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 sqlite3_exec() returns SQLITE_BUSY immediately if
+** it finds a locked table. If the busy callback is not NULL, then
+** sqlite3_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 sqlite3_exec() immediately returns
+** SQLITE_BUSY. If the callback returns non-zero, then sqlite3_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.
+*/
+int sqlite3_busy_handler(sqlite3*, int(*)(void*,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" milleseconds of sleeping have been done. After
+** "ms" milleseconds of sleeping, the handler returns 0 which
+** causes sqlite3_exec() to return SQLITE_BUSY.
+**
+** Calling this routine with an argument less than or equal to zero
+** turns off all busy handlers.
+*/
+int sqlite3_busy_timeout(sqlite3*, int ms);
+
+/*
+** This next routine is really just a wrapper around sqlite3_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 sqlite3_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
+** free() directly. Only sqlite3_free_table() is able to release
+** the memory properly and safely.
+**
+** The return value of this routine is the same as from sqlite3_exec().
+*/
+int sqlite3_get_table(
+ sqlite3*, /* 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 sqlite3_get_table() allocated.
+*/
+void sqlite3_free_table(char **result);
+
+/*
+** The following routines are variants of the "sprintf()" from the
+** standard C library. The resulting string is written into memory
+** obtained from malloc() so that there is never a possiblity of buffer
+** overflow. These routines also implement some additional formatting
+** options that are useful for constructing SQL statements.
+**
+** The strings returned by these routines should be freed by calling
+** sqlite3_free().
+**
+** 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:
+**
+** char *z = sqlite3_mprintf("INSERT INTO TABLES('%q')", zText);
+** sqlite3_exec(db, z, callback1, 0, 0);
+** sqlite3_free(z);
+**
+** 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.
+*/
+char *sqlite3_mprintf(const char*,...);
+char *sqlite3_vmprintf(const char*, va_list);
+void sqlite3_free(char *z);
+char *sqlite3_snprintf(int,char*,const char*, ...);
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+/*
+** 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 sqlite3_set_authorizer(
+ sqlite3*,
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+ void *pUserData
+);
+#endif
+
+/*
+** 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 */
+#define SQLITE_ALTER_TABLE 26 /* Database Name Table Name */
+#define SQLITE_REINDEX 27 /* Index Name NULL */
+#define SQLITE_ANALYZE 28 /* Table 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 for tracing SQL command evaluation. The function
+** registered by sqlite3_trace() is invoked at the first sqlite3_step()
+** for the evaluation of an SQL statement. The function registered by
+** sqlite3_profile() runs at the end of each SQL statement and includes
+** information on how long that statement ran.
+**
+** The sqlite3_profile() API is currently considered experimental and
+** is subject to change.
+*/
+void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*);
+void *sqlite3_profile(sqlite3*,
+ void(*xProfile)(void*,const char*,sqlite_uint64), void*);
+
+/*
+** 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 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 sqlite3_exec(), sqlite3_step() or sqlite3_get_table() results
+** in less than N opcodes being executed, then the progress callback is not
+** invoked.
+**
+** 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 sqlite3_exec() call returns SQLITE_ABORT.
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
+*/
+void sqlite3_progress_handler(sqlite3*, 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 *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*);
+
+/*
+** Open the sqlite database file "filename". The "filename" is UTF-8
+** encoded for sqlite3_open() and UTF-16 encoded in the native byte order
+** for sqlite3_open16(). An sqlite3* handle is returned in *ppDb, even
+** if an error occurs. If the database is opened (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.
+**
+** If the database file does not exist, then a new database is created.
+** The encoding for the database is UTF-8 if sqlite3_open() is called and
+** UTF-16 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.
+*/
+#define SQLITE_OPEN_READONLY 3
+#define SQLITE_OPEN_READ_WRITE_LOCKED 2
+#define SQLITE_OPEN_WRITE_LOCKED 1
+#define SQLITE_OPEN_NO_LOCKED 0
+int sqlite3_open(
+ const char *filename, /* Database filename (UTF-8) */
+ sqlite3 **ppDb, /* OUT: SQLite db handle */
+ int exclusiveFlag, /* If SQLITE_OPEN_READONLY, readonly mode is used, */
+ /* If SQLITE_OPEN_READ_WRITE_LOCKED, exclusive read/write access is required, */
+ /* If SQLITE_OPEN_WRITE_LOCKED, exclusive write access is required,
+ If SQLITE_OPEN_NO_LOCKED, no locking is performed. */
+ int allowReadonly /* If 1 and read/write opening fails,
+ try opening in read-only mode */
+);
+
+int sqlite3_open16(
+ const void *filename, /* Database filename (UTF-16) */
+ sqlite3 **ppDb, /* OUT: SQLite db handle */
+ int exclusiveFlag, /* If SQLITE_OPEN_READONLY, readonly mode is used, */
+ /* If SQLITE_OPEN_READ_WRITE_LOCKED, exclusive read/write access is required, */
+ /* If SQLITE_OPEN_WRITE_LOCKED, exclusive write access is required,
+ If SQLITE_OPEN_NO_LOCKED, no locking is performed. */
+ int allowReadonly /* If 1 and read/write opening fails,
+ try opening in read-only mode */
+);
+
+/*
+** Return the error code for the most recent sqlite3_* API call associated
+** with sqlite3 handle 'db'. SQLITE_OK is returned if the most recent
+** API call was successful.
+**
+** Calls to many sqlite3_* functions set the error code and string returned
+** by sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16()
+** (overwriting the previous values). Note that calls to sqlite3_errcode(),
+** sqlite3_errmsg() and sqlite3_errmsg16() themselves do not affect the
+** results of future invocations.
+**
+** Assuming no other intervening sqlite3_* API calls are made, the error
+** code returned by this function is associated with the same error as
+** the strings returned by sqlite3_errmsg() and sqlite3_errmsg16().
+*/
+int sqlite3_errcode(sqlite3 *db);
+
+/*
+** Return a pointer to a UTF-8 encoded string describing in english the
+** error condition for the most recent sqlite3_* API call. The returned
+** string is always terminated by an 0x00 byte.
+**
+** The string "not an error" is returned when the most recent API call was
+** successful.
+*/
+const char *sqlite3_errmsg(sqlite3*);
+
+/*
+** Return a pointer to a UTF-16 native byte order encoded string describing
+** in english the error condition for the most recent sqlite3_* API call.
+** The returned string is always terminated by a pair of 0x00 bytes.
+**
+** The string "not an error" is returned when the most recent API call was
+** successful.
+*/
+const void *sqlite3_errmsg16(sqlite3*);
+
+/*
+** An instance of the following opaque structure is used to represent
+** a compiled SQL statment.
+*/
+typedef struct sqlite3_stmt sqlite3_stmt;
+
+/*
+** To execute an SQL query, it must first be compiled into a byte-code
+** program using one of the following routines. The only difference between
+** them is that the second argument, specifying the SQL statement to
+** compile, is assumed to be encoded in UTF-8 for the sqlite3_prepare()
+** function and UTF-16 for sqlite3_prepare16().
+**
+** The first parameter "db" is an SQLite database handle. The second
+** parameter "zSql" is the statement to be compiled, encoded as either
+** UTF-8 or UTF-16 (see above). If the next parameter, "nBytes", is less
+** than zero, then zSql is read up to the first nul terminator. If
+** "nBytes" is not less than zero, then it is the length of the string zSql
+** in bytes (not characters).
+**
+** *pzTail is made to point to the first byte 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.
+**
+** *ppStmt is left pointing to a compiled SQL statement that can be
+** executed using sqlite3_step(). Or if there is an error, *ppStmt may be
+** set to NULL. If the input text contained no SQL (if the input is and
+** empty string or a comment) then *ppStmt is set to NULL.
+**
+** On success, SQLITE_OK is returned. Otherwise an error code is returned.
+*/
+int sqlite3_prepare(
+ sqlite3 *db, /* Database handle */
+ const char *zSql, /* SQL statement, UTF-8 encoded */
+ int nBytes, /* 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 nBytes, /* Length of zSql in bytes. */
+ sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const void **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+
+/*
+** Pointers to the following two opaque structures are used to communicate
+** with the implementations of user-defined functions.
+*/
+typedef struct sqlite3_context sqlite3_context;
+typedef struct Mem sqlite3_value;
+
+/*
+** In the SQL strings input to sqlite3_prepare() and sqlite3_prepare16(),
+** one or more literals can be replace by parameters "?" or ":AAA" or
+** "$VVV" where AAA is an identifer and VVV is a variable name according
+** to the syntax rules of the TCL programming language.
+** The value of these parameters (also called "host parameter names") can
+** be set using the routines listed below.
+**
+** In every case, the first parameter is a pointer to the sqlite3_stmt
+** structure returned from sqlite3_prepare(). The second parameter is the
+** index of the parameter. The first parameter as an index of 1. For
+** named parameters (":AAA" or "$VVV") you can use
+** sqlite3_bind_parameter_index() to get the correct index value given
+** the parameters name. If the same named parameter occurs more than
+** once, it is assigned the same index each time.
+**
+** The fifth parameter to sqlite3_bind_blob(), sqlite3_bind_text(), and
+** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or
+** text after SQLite has finished with it. If the fifth argument is the
+** special value SQLITE_STATIC, then the library 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.
+**
+** The sqlite3_bind_* routine must be called before sqlite3_step() after
+** an sqlite3_prepare() or sqlite3_reset(). Unbound parameterss are
+** interpreted as NULL.
+*/
+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, sqlite_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*);
+
+/*
+** Return the number of parameters in a compiled SQL statement. This
+** routine was added to support DBD::SQLite.
+*/
+int sqlite3_bind_parameter_count(sqlite3_stmt*);
+
+/*
+** Return the name of the i-th parameter. Ordinary parameters "?" are
+** nameless and a NULL is returned. For parameters of the form :AAA or
+** $VVV the complete text of the parameter name is returned, including
+** the initial ":" or "$". NULL is returned if the index is out of range.
+*/
+const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int);
+
+/*
+** Return the index of a parameter with the given name. The name
+** must match exactly. If no parameter with the given name is found,
+** return 0.
+*/
+int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
+
+/*
+** Set all the parameters in the compiled SQL statement to NULL.
+*/
+int sqlite3_clear_bindings(sqlite3_stmt*);
+
+/*
+** Return the number of columns in the result set returned by the compiled
+** SQL statement. This routine returns 0 if pStmt is an SQL statement
+** that does not return data (for example an UPDATE).
+*/
+int sqlite3_column_count(sqlite3_stmt *pStmt);
+
+/*
+** The first parameter is a compiled SQL statement. This function returns
+** the column heading for the Nth column of that statement, where N is the
+** second function parameter. The string returned is UTF-8 for
+** sqlite3_column_name() and UTF-16 for sqlite3_column_name16().
+*/
+const char *sqlite3_column_name(sqlite3_stmt*,int);
+const void *sqlite3_column_name16(sqlite3_stmt*,int);
+
+/*
+** The first parameter is a compiled SQL statement. If this statement
+** is a SELECT statement, the Nth column of the returned result set
+** of the SELECT is a table column then the declared type of the table
+** column is returned. If the Nth column of the result set is not at table
+** column, then a NULL pointer is returned. The returned string is always
+** UTF-8 encoded. For example, in the database schema:
+**
+** CREATE TABLE t1(c1 VARIANT);
+**
+** And the following statement compiled:
+**
+** SELECT c1 + 1, 0 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).
+*/
+const char *sqlite3_column_decltype(sqlite3_stmt *, int i);
+
+/*
+** The first parameter is a compiled SQL statement. If this statement
+** is a SELECT statement, the Nth column of the returned result set
+** of the SELECT is a table column then the declared type of the table
+** column is returned. If the Nth column of the result set is not at table
+** column, then a NULL pointer is returned. The returned string is always
+** UTF-16 encoded. For example, in the database schema:
+**
+** CREATE TABLE t1(c1 INTEGER);
+**
+** And the following statement compiled:
+**
+** SELECT c1 + 1, 0 FROM t1;
+**
+** Then this routine would return the string "INTEGER" for the second
+** result column (i==1), and a NULL pointer for the first result column
+** (i==0).
+*/
+const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
+
+/*
+** After an SQL query has been compiled with a call to either
+** sqlite3_prepare() or sqlite3_prepare16(), then this function must be
+** called one or more times to execute the statement.
+**
+** The return value will be either SQLITE_BUSY, SQLITE_DONE,
+** SQLITE_ROW, SQLITE_ERROR, or SQLITE_MISUSE.
+**
+** SQLITE_BUSY means that the database engine attempted to open
+** a locked database and there is no busy callback registered.
+** Call sqlite3_step() again to retry the open.
+**
+** SQLITE_DONE means that the statement has finished executing
+** successfully. sqlite3_step() should not be called again on this virtual
+** machine.
+**
+** 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_*() functions described below. 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().
+**
+** 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 sqlite3_step(sqlite3_stmt*);
+
+/*
+** Return the number of values in the current row of the result set.
+**
+** After a call to sqlite3_step() that returns SQLITE_ROW, this routine
+** will return the same value as the sqlite3_column_count() function.
+** After sqlite3_step() has returned an SQLITE_DONE, SQLITE_BUSY or
+** error code, or before sqlite3_step() has been called on a
+** compiled SQL statement, this routine returns zero.
+*/
+int sqlite3_data_count(sqlite3_stmt *pStmt);
+
+/*
+** Values are stored in the database in one of the following fundamental
+** types.
+*/
+#define SQLITE_INTEGER 1
+#define SQLITE_FLOAT 2
+/* #define SQLITE_TEXT 3 // See below */
+#define SQLITE_BLOB 4
+#define SQLITE_NULL 5
+
+/*
+** SQLite version 2 defines SQLITE_TEXT differently. To allow both
+** version 2 and version 3 to be included, undefine them both if a
+** conflict is seen. Define SQLITE3_TEXT to be the version 3 value.
+*/
+#ifdef SQLITE_TEXT
+# undef SQLITE_TEXT
+#else
+# define SQLITE_TEXT 3
+#endif
+#define SQLITE3_TEXT 3
+
+/*
+** The next group of routines returns information about the information
+** in a single column of the current result row of a query. In every
+** case the first parameter is a pointer to the SQL statement that is being
+** executed (the sqlite_stmt* that was returned from sqlite3_prepare()) and
+** the second argument is the index of the column for which information
+** should be returned. iCol is zero-indexed. The left-most column as an
+** index of 0.
+**
+** If the SQL statement is not currently point to a valid row, or if the
+** the colulmn index is out of range, the result 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, sprintf() is used internally to do the conversion
+** automatically. The following table details the conversions that
+** are applied:
+**
+** Internal Type Requested Type Conversion
+** ------------- -------------- --------------------------
+** NULL INTEGER Result is 0
+** NULL FLOAT Result is 0.0
+** NULL TEXT Result is an empty string
+** NULL BLOB Result is a zero-length BLOB
+** INTEGER FLOAT Convert from integer to float
+** INTEGER TEXT ASCII rendering of the integer
+** INTEGER BLOB Same as for INTEGER->TEXT
+** FLOAT INTEGER Convert from float to integer
+** FLOAT TEXT ASCII rendering of the float
+** FLOAT BLOB Same as FLOAT->TEXT
+** TEXT INTEGER Use atoi()
+** TEXT FLOAT Use atof()
+** TEXT BLOB No change
+** BLOB INTEGER Convert to TEXT then use atoi()
+** BLOB FLOAT Convert to TEXT then use atof()
+** BLOB TEXT Add a \000 terminator if needed
+**
+** The following access routines are provided:
+**
+** _type() Return the datatype of the result. This is one of
+** SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB,
+** or SQLITE_NULL.
+** _blob() Return the value of a BLOB.
+** _bytes() Return the number of bytes in a BLOB value or the number
+** of bytes in a TEXT value represented as UTF-8. The \000
+** terminator is included in the byte count for TEXT values.
+** _bytes16() Return the number of bytes in a BLOB value or the number
+** of bytes in a TEXT value represented as UTF-16. The \u0000
+** terminator is included in the byte count for TEXT values.
+** _double() Return a FLOAT value.
+** _int() Return an INTEGER value in the host computer's native
+** integer representation. This might be either a 32- or 64-bit
+** integer depending on the host.
+** _int64() Return an INTEGER value as a 64-bit signed integer.
+** _text() Return the value as UTF-8 text.
+** _text16() Return the value as UTF-16 text.
+*/
+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);
+sqlite_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);
+
+/*
+** The sqlite3_finalize() function is called to delete a compiled
+** SQL statement obtained by a previous call to sqlite3_prepare()
+** or sqlite3_prepare16(). 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 is returned.
+**
+** 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 sqlite3_interrupt().) Incomplete updates may be
+** rolled back and transactions cancelled, depending on the circumstances,
+** and the result code returned will be SQLITE_ABORT.
+*/
+int sqlite3_finalize(sqlite3_stmt *pStmt);
+
+/*
+** The sqlite3_reset() function is called to reset a compiled SQL
+** statement obtained by a previous call to sqlite3_prepare() or
+** sqlite3_prepare16() back to it's initial state, ready to be re-executed.
+** Any SQL statement variables that had values bound to them using
+** the sqlite3_bind_*() API retain their values.
+*/
+int sqlite3_reset(sqlite3_stmt *pStmt);
+
+/*
+** The following two functions are used to add user functions or aggregates
+** implemented in C to the SQL langauge interpreted by SQLite. 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 argument is the database handle that the new function or
+** aggregate is to be added to. If a single program uses more than one
+** database handle internally, then user functions or aggregates must
+** be added individually to each database handle with which they will be
+** used.
+**
+** The third parameter is the number of arguments that the function or
+** aggregate takes. If this parameter is negative, then the function or
+** aggregate may take any number of arguments.
+**
+** The fourth parameter is one of SQLITE_UTF* values defined below,
+** indicating the encoding that the function is most likely to handle
+** values in. This does not change the behaviour of the programming
+** interface. However, if two versions of the same function are registered
+** with different encoding values, SQLite invokes the version likely to
+** minimize conversions between text encodings.
+**
+** The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are
+** pointers to user implemented C functions that implement the user
+** function or aggregate. A scalar function requires an implementation of
+** the xFunc callback only, NULL pointers should be passed as the xStep
+** and xFinal parameters. An aggregate function requires an implementation
+** of xStep and xFinal, but NULL should be passed for xFunc. To delete an
+** existing user function or aggregate, pass NULL for all three function
+** callback. Specifying an inconstent set of callback values, such as an
+** xFunc and an xFinal, or an xStep but no xFinal, SQLITE_ERROR is
+** returned.
+*/
+int sqlite3_create_function(
+ sqlite3 *,
+ const char *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void*,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*)
+);
+int sqlite3_create_function16(
+ sqlite3*,
+ const void *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void*,
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**),
+ void (*xFinal)(sqlite3_context*)
+);
+
+/*
+** 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 sqlite3_aggregate_count(sqlite3_context*);
+
+/*
+** The next group of routines returns information about parameters to
+** a user-defined function. Function implementations use these routines
+** to access their parameters. These routines are the same as the
+** sqlite3_column_* routines except that these routines take a single
+** sqlite3_value* pointer instead of an sqlite3_stmt* and an integer
+** column number.
+*/
+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*);
+sqlite_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*);
+
+/*
+** 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 by SQLite.
+*/
+void *sqlite3_aggregate_context(sqlite3_context*, int nBytes);
+
+/*
+** The pUserData parameter to the sqlite3_create_function()
+** routine used to register user functions is available to
+** the implementation of the function using this call.
+*/
+void *sqlite3_user_data(sqlite3_context*);
+
+/*
+** The following two functions may be used by scalar user functions to
+** associate meta-data with argument values. If the same value is passed to
+** multiple invocations of the user-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.
+**
+** Calling sqlite3_get_auxdata() returns a pointer to the meta data
+** associated with the Nth argument value to the current user function
+** call, where N is the second parameter. If no meta-data has been set for
+** that value, then a NULL pointer is returned.
+**
+** The sqlite3_set_auxdata() is used to associate meta data with a user
+** function argument. The third parameter is a pointer to the meta data
+** to be associated with the Nth user function argument value. The fourth
+** parameter specifies a 'delete function' that will be called on the meta
+** data pointer to release it when it is no longer required. If the delete
+** function pointer is NULL, it is not invoked.
+**
+** In practice, meta-data is preserved between function calls for
+** expressions that are constant at compile time. This includes literal
+** values and SQL variables.
+*/
+void *sqlite3_get_auxdata(sqlite3_context*, int);
+void sqlite3_set_auxdata(sqlite3_context*, int, void*, void (*)(void*));
+
+
+/*
+** 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.
+*/
+#define SQLITE_STATIC ((void(*)(void *))0)
+#define SQLITE_TRANSIENT ((void(*)(void *))-1)
+
+/*
+** User-defined functions invoke the following routines in order to
+** set their return 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_int(sqlite3_context*, int);
+void sqlite3_result_int64(sqlite3_context*, sqlite_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*);
+
+/*
+** These are the allowed values for the eTextRep argument to
+** sqlite3_create_collation and sqlite3_create_function.
+*/
+#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 */
+
+/*
+** These two 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 a UTF-16 string for
+** sqlite3_create_collation16(). In both cases the name is passed as the
+** second function argument.
+**
+** The third argument must 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.
+**
+** 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 user
+** 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 user-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. The user 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).
+*/
+int sqlite3_create_collation(
+ sqlite3*,
+ const char *zName,
+ int eTextRep,
+ void*,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+);
+int sqlite3_create_collation16(
+ sqlite3*,
+ const char *zName,
+ int eTextRep,
+ void*,
+ int(*xCompare)(void*,int,const void*,int,const void*)
+);
+
+/*
+** 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. 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 user-function 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 collation sequence is returned to SQLite by a collation-needed
+** callback using the sqlite3_create_collation() or
+** sqlite3_create_collation16() APIs, described above.
+*/
+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 */
+);
+
+/*
+** Sleep for a little while. The second parameter is the number of
+** miliseconds to sleep for.
+**
+** If the operating system does not support sleep requests with
+** milisecond time resolution, then the time will be rounded up to
+** the nearest second. The number of miliseconds of sleep actually
+** requested from the operating system is returned.
+*/
+int sqlite3_sleep(int);
+
+/*
+** Return TRUE (non-zero) if 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.
+**
+*/
+int sqlite3_expired(sqlite3_stmt*);
+
+/*
+** Move all bindings from the first prepared statement over to the second.
+** This routine is useful, for example, if the first prepared statement
+** fails with an SQLITE_SCHEMA error. The same SQL can be prepared into
+** the second prepared statement then all of the bindings transfered over
+** to the second statement before the first statement is finalized.
+*/
+int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*);
+
+/*
+** If the following global variable is made to point to a
+** string which is the name of a 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.
+**
+** Once sqlite3_open() has been called, changing this variable will invalidate
+** the current temporary database, if any.
+*/
+extern char *sqlite3_temp_directory;
+
+/*
+** This function is called to recover from a malloc() failure that occured
+** within the SQLite library. Normally, after a single malloc() fails the
+** library refuses to function (all major calls return SQLITE_NOMEM).
+** This function restores the library state so that it can be used again.
+**
+** All existing statements (sqlite3_stmt pointers) must be finalized or
+** reset before this call is made. Otherwise, SQLITE_BUSY is returned.
+** If any in-memory databases are in use, either as a main or TEMP
+** database, SQLITE_ERROR is returned. In either of these cases, the
+** library is not reset and remains unusable.
+**
+** This function is *not* threadsafe. Calling this from within a threaded
+** application when threads other than the caller have used SQLite is
+** dangerous and will almost certainly result in malfunctions.
+**
+** This functionality can be omitted from a build by defining the
+** SQLITE_OMIT_GLOBALRECOVER at compile time.
+*/
+int sqlite3_global_recover();
+
+/*
+** 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.
+*/
+int sqlite3_get_autocommit(sqlite3*);
+
+/*
+** 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.
+*/
+sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
+
+/* (jstaniek) used in vacuum.c, set==1 sets VACUUM to verbose,
+** interactive mode, so it is possible to get progress info and abort
+** the process. Usable for GUI apps.
+*/
+void sqlite_set_verbose_vacuum(int set);
+
+#ifdef __cplusplus
+} /* End of the 'extern "C"' block */
+#endif
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/sqliteInt.h b/kexi/3rdparty/kexisql3/src/sqliteInt.h
new file mode 100644
index 000000000..796653e7c
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/sqliteInt.h
@@ -0,0 +1,1666 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#ifndef _SQLITEINT_H_
+#define _SQLITEINT_H_
+
+/*
+** 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
+
+/*
+** 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
+
+#include "sqliteconfig.h"
+#include "sqlite3.h"
+#include "hash.h"
+#include "parse.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stddef.h>
+
+/*
+** The maximum number of in-memory pages to use for the main database
+** table and for temporary tables. Internally, the MAX_PAGES and
+** TEMP_PAGES macros are used. To override the default values at
+** compilation time, the SQLITE_DEFAULT_CACHE_SIZE and
+** SQLITE_DEFAULT_TEMP_CACHE_SIZE macros should be set.
+*/
+#ifdef SQLITE_DEFAULT_CACHE_SIZE
+# define MAX_PAGES SQLITE_DEFAULT_CACHE_SIZE
+#else
+# define MAX_PAGES 2000
+#endif
+#ifdef SQLITE_DEFAULT_TEMP_CACHE_SIZE
+# define TEMP_PAGES SQLITE_DEFAULT_TEMP_CACHE_SIZE
+#else
+# define TEMP_PAGES 500
+#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 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 32 because
+** we use a bitmask of databases with a u32 in places (for example
+** the Parse.cookieMask field).
+*/
+#define MAX_ATTACHED 10
+
+/*
+** The maximum value of a ?nnn wildcard that the parser will accept.
+*/
+#define SQLITE_MAX_VARIABLE_NUMBER 999
+
+/*
+** 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_MEMORYDB 1 */
+/* #define SQLITE_OMIT_VACUUM 1 */
+/* #define SQLITE_OMIT_DATETIME_FUNCS 1 */
+/* #define SQLITE_OMIT_PROGRESS_CALLBACK 1 */
+/* #define SQLITE_OMIT_AUTOVACUUM */
+/* #define SQLITE_OMIT_ALTERTABLE */
+
+/*
+** 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
+
+/*
+** 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 UINT64_TYPE
+# if defined(_MSC_VER) || defined(__BORLANDC__)
+# define UINT64_TYPE unsigned __int64
+# else
+# define UINT64_TYPE unsigned long long int
+# endif
+#endif
+#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 LONGDOUBLE_TYPE
+# define LONGDOUBLE_TYPE long double
+#endif
+typedef sqlite_int64 i64; /* 8-byte signed integer */
+typedef UINT64_TYPE 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.
+*/
+extern const int sqlite3one;
+#define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0)
+#define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1)
+
+/*
+** 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 */
+};
+
+/*
+** Defer sourcing vdbe.h and btree.h until after the "u8" and
+** "BusyHandler typedefs.
+*/
+#include "vdbe.h"
+#include "btree.h"
+
+/*
+** This macro casts a pointer to an integer. Useful for doing
+** pointer arithmetic.
+*/
+#define Addr(X) ((uptr)X)
+
+/*
+** If memory allocation problems are found, recompile with
+**
+** -DSQLITE_DEBUG=1
+**
+** to enable some sanity checking on malloc() and free(). To
+** check for memory leaks, recompile with
+**
+** -DSQLITE_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 SQLITE_MEMDEBUG
+# define sqliteMalloc(X) sqlite3Malloc_(X,1,__FILE__,__LINE__)
+# define sqliteMallocRaw(X) sqlite3Malloc_(X,0,__FILE__,__LINE__)
+# define sqliteFree(X) sqlite3Free_(X,__FILE__,__LINE__)
+# define sqliteRealloc(X,Y) sqlite3Realloc_(X,Y,__FILE__,__LINE__)
+# define sqliteStrDup(X) sqlite3StrDup_(X,__FILE__,__LINE__)
+# define sqliteStrNDup(X,Y) sqlite3StrNDup_(X,Y,__FILE__,__LINE__)
+#else
+# define sqliteFree sqlite3FreeX
+# define sqliteMalloc sqlite3Malloc
+# define sqliteMallocRaw sqlite3MallocRaw
+# define sqliteRealloc sqlite3Realloc
+# define sqliteStrDup sqlite3StrDup
+# define sqliteStrNDup sqlite3StrNDup
+#endif
+
+/*
+** This variable gets set if malloc() ever fails. After it gets set,
+** the SQLite library shuts down permanently.
+*/
+extern int sqlite3_malloc_failed;
+
+/*
+** The following global variables are used for testing and debugging
+** only. They only work if SQLITE_DEBUG is defined.
+*/
+#ifdef SQLITE_MEMDEBUG
+extern int sqlite3_nMalloc; /* Number of sqliteMalloc() calls */
+extern int sqlite3_nFree; /* Number of sqliteFree() calls */
+extern int sqlite3_iMallocFail; /* Fail sqliteMalloc() after this many calls */
+extern int sqlite3_iMallocReset; /* Set iMallocFail to this when it reaches 0 */
+#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 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 CollSeq CollSeq;
+typedef struct Column Column;
+typedef struct Db Db;
+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 NameContext NameContext;
+typedef struct Parse Parse;
+typedef struct Select Select;
+typedef struct SrcList SrcList;
+typedef struct Table Table;
+typedef struct Token Token;
+typedef struct TriggerStack TriggerStack;
+typedef struct TriggerStep TriggerStep;
+typedef struct Trigger Trigger;
+typedef struct WhereInfo WhereInfo;
+typedef struct WhereLevel WhereLevel;
+
+/*
+** 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 */
+ u16 flags; /* Flags associated with this database */
+ u8 inTrans; /* 0: not writable. 1: Transaction. 2: Checkpoint */
+ u8 safety_level; /* How aggressive at synching data to disk */
+ int cache_size; /* Number of pages to use in the cache */
+ Table *pSeqTab; /* The sqlite_sequence table used by AUTOINCREMENT */
+ 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_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 SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE)
+
+/*
+** 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 {
+ int nDb; /* Number of backends currently in use */
+ Db *aDb; /* All backends */
+ int flags; /* Miscellanous flags. See below */
+ int errCode; /* Most recent error code (SQLITE_*) */
+ u8 enc; /* Text encoding for this database. */
+ u8 autoCommit; /* The auto-commit flag. */
+ u8 file_format; /* What file format version is this database? */
+ u8 temp_store; /* 1: file 2: memory 0: default */
+ 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() */
+ 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;
+ 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(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
+ void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
+ void *pCollNeededArg;
+ sqlite3_value *pValue; /* Value used for transient conversions */
+ 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) */
+#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_GLOBALRECOVER
+ sqlite3 *pNext; /* Linked list of open db handles. */
+#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
+};
+
+/*
+** 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_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 */
+
+/*
+** 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 {
+ 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 */
+};
+
+/*
+** 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 */
+
+/*
+** 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 */
+ CollSeq *pColl; /* 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 */
+};
+
+/*
+** 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
+** transalations, 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*);
+};
+
+/*
+** 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.
+*/
+#define SQLITE_AFF_INTEGER 'i'
+#define SQLITE_AFF_NUMERIC 'n'
+#define SQLITE_AFF_TEXT 't'
+#define SQLITE_AFF_NONE 'o'
+
+
+/*
+** 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 */
+ u8 autoInc; /* True if the integer primary key is autoincrement */
+ 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_ALTERTABLE
+ int addColOffset; /* Offset in CREATE TABLE statement to add a new column */
+#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 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 */
+
+
+/*
+** 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 {
+ u8 enc; /* Text encoding - one of the TEXT_Utf* values */
+ u8 incrKey; /* Increase 2nd key by epsilon before comparison */
+ 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) */
+ u8 iDb; /* Index in sqlite.aDb[] of where this index is stored */
+ char *zColAff; /* String defining the affinity of each column */
+ Index *pNext; /* The next index associated with the same table */
+ KeyInfo keyInfo; /* Info on how to order keys. MUST BE LAST */
+};
+
+/*
+** 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 */
+ 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; /* Virtual 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 */
+ u8 iDb; /* Database referenced by this expression */
+ u8 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. */
+};
+
+/*
+** The following are the meanings of bits in the Expr.flags field.
+*/
+#define EP_FromJoin 0x01 /* Originated in ON or USING clause of a join */
+#define EP_Agg 0x02 /* Contains one or more aggregate functions */
+#define EP_Resolved 0x04 /* IDs have been resolved to COLUMNs */
+#define EP_Error 0x08 /* Expression contains one or more errors */
+#define EP_Distinct 0x10 /* Aggregate function with DISTINCT keyword */
+#define EP_VarSelect 0x20 /* pSelect is correlated, not constant */
+#define EP_Dequoted 0x40 /* True if the string has been dequoted */
+
+/*
+** 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.
+*/
+typedef unsigned int 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.
+*/
+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 jointype; /* Type of join between this table and the next */
+ i16 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.
+*/
+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 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 */
+ int *aInLoop; /* Loop terminators for IN operators */
+};
+
+/*
+** 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 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 */
+ 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 */
+ 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.
+**
+** addrOpenVirt[] entries contain the address of OP_OpenVirtual opcodes.
+** These addresses must be stored so that we can go back and fill in
+** the P3_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_OpenVirtual instruction is coded because not
+** enough information about the compound query is known at that point.
+** The KeyInfo for addrOpenVirt[0] and [1] contains collating sequences
+** for the result set. The KeyInfo for addrOpenVirt[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 usesVirt; /* True if uses an OpenVirtual opcode */
+ u8 disallowOrderBy; /* Do not allow an ORDER BY to be attached if TRUE */
+ 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 *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 addrOpenVirt[3]; /* OP_OpenVirtual 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_Discard 3 /* Do not save the results anywhere */
+
+/* The ORDER BY clause is ignored for all of the above */
+#define IgnorableOrderby(X) (X<=SRT_Discard)
+
+#define SRT_Callback 4 /* Invoke a callback with each row of result */
+#define SRT_Mem 5 /* Store result in a memory cell */
+#define SRT_Set 6 /* Store non-null results as keys in an index */
+#define SRT_Table 7 /* Store result as data with an automatic rowid */
+#define SRT_VirtualTab 8 /* Create virtual table and store like SRT_Table */
+#define SRT_Subroutine 9 /* Call a subroutine to handle results */
+#define SRT_Exists 10 /* Put 0 or 1 in a memory cell */
+
+/*
+** 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.
+*/
+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 */
+ 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 */
+ 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[MAX_ATTACHED+2]; /* Values of cookies to verify */
+
+ /* 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 */
+};
+
+/*
+** 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 */
+
+/*
+ * 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 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 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 */
+ 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 */
+};
+
+/*
+** 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 */
+};
+
+/*
+ * 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 */
+};
+
+/*
+** 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 */
+ char **pzErrMsg; /* Error message stored here */
+} InitData;
+
+/*
+ * 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 sqlite3_always_code_trigger_setup;
+
+/*
+** 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
+ extern int sqlite3Corrupt(void);
+# define SQLITE_CORRUPT_BKPT sqlite3Corrupt()
+#else
+# define SQLITE_CORRUPT_BKPT SQLITE_CORRUPT
+#endif
+
+/*
+** Internal function prototypes
+*/
+int sqlite3StrICmp(const char *, const char *);
+int sqlite3StrNICmp(const char *, const char *, int);
+int sqlite3HashNoCase(const char *, int);
+int sqlite3IsNumber(const char*, int*, u8);
+int sqlite3Compare(const char *, const char *);
+int sqlite3SortCompare(const char *, const char *);
+void sqlite3RealToSortable(double r, char *);
+#ifdef SQLITE_MEMDEBUG
+ void *sqlite3Malloc_(int,int,char*,int);
+ void sqlite3Free_(void*,char*,int);
+ void *sqlite3Realloc_(void*,int,char*,int);
+ char *sqlite3StrDup_(const char*,char*,int);
+ char *sqlite3StrNDup_(const char*, int,char*,int);
+ void sqlite3CheckMemory(void*,int);
+#else
+ void *sqlite3Malloc(int);
+ void *sqlite3MallocRaw(int);
+ void sqlite3Free(void*);
+ void *sqlite3Realloc(void*,int);
+ char *sqlite3StrDup(const char*);
+ char *sqlite3StrNDup(const char*, int);
+# define sqlite3CheckMemory(a,b)
+# define sqlite3MallocX sqlite3Malloc
+#endif
+void sqlite3ReallocOrFree(void**,int);
+void sqlite3FreeX(void*);
+void *sqlite3MallocX(int);
+char *sqlite3MPrintf(const char*, ...);
+char *sqlite3VMPrintf(const char*, va_list);
+void sqlite3DebugPrintf(const char*, ...);
+void *sqlite3TextToPtr(const char*);
+void sqlite3SetString(char **, ...);
+void sqlite3ErrorMsg(Parse*, const char*, ...);
+void sqlite3Dequote(char*);
+void sqlite3DequoteExpr(Expr*);
+int sqlite3KeywordCode(const char*, int);
+int sqlite3RunParser(Parse*, const char*, char **);
+void sqlite3FinishCoding(Parse*);
+Expr *sqlite3Expr(int, Expr*, Expr*, const Token*);
+Expr *sqlite3RegisterExpr(Parse*,Token*);
+Expr *sqlite3ExprAnd(Expr*, Expr*);
+void sqlite3ExprSpan(Expr*,Token*,Token*);
+Expr *sqlite3ExprFunction(ExprList*, Token*);
+void sqlite3ExprAssignVarNumber(Parse*, Expr*);
+void sqlite3ExprDelete(Expr*);
+ExprList *sqlite3ExprListAppend(ExprList*,Expr*,Token*);
+void sqlite3ExprListDelete(ExprList*);
+int sqlite3Init(sqlite3*, char**);
+int sqlite3InitCallback(void*, int, char**, char**);
+void sqlite3Pragma(Parse*,Token*,Token*,Token*,int);
+void sqlite3ResetInternalSchema(sqlite3*, int);
+void sqlite3BeginParse(Parse*,int);
+void sqlite3RollbackInternalChanges(sqlite3*);
+void sqlite3CommitInternalChanges(sqlite3*);
+Table *sqlite3ResultSetOfSelect(Parse*,char*,Select*);
+void sqlite3OpenMasterTable(Vdbe *v, int);
+void sqlite3StartTable(Parse*,Token*,Token*,Token*,int,int);
+void sqlite3AddColumn(Parse*,Token*);
+void sqlite3AddNotNull(Parse*, int);
+void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int);
+void sqlite3AddColumnType(Parse*,Token*);
+void sqlite3AddDefaultValue(Parse*,Expr*);
+void sqlite3AddCollateType(Parse*, const char*, int);
+void sqlite3EndTable(Parse*,Token*,Token*,Select*);
+
+#ifndef SQLITE_OMIT_VIEW
+ void sqlite3CreateView(Parse*,Token*,Token*,Token*,Select*,int);
+ int sqlite3ViewGetColumnNames(Parse*,Table*);
+#else
+# define sqlite3ViewGetColumnNames(A,B) 0
+#endif
+
+void sqlite3DropTable(Parse*, SrcList*, int);
+void sqlite3DeleteTable(sqlite3*, Table*);
+void sqlite3Insert(Parse*, SrcList*, ExprList*, Select*, IdList*, int);
+int sqlite3ArrayAllocate(void**,int,int);
+IdList *sqlite3IdListAppend(IdList*, Token*);
+int sqlite3IdListIndex(IdList*,const char*);
+SrcList *sqlite3SrcListAppend(SrcList*, Token*, Token*);
+void sqlite3SrcListAddAlias(SrcList*, Token*);
+void sqlite3SrcListAssignCursors(Parse*, SrcList*);
+void sqlite3IdListDelete(IdList*);
+void sqlite3SrcListDelete(SrcList*);
+void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
+ Token*);
+void sqlite3DropIndex(Parse*, SrcList*);
+void sqlite3AddKeyType(Vdbe*, ExprList*);
+void sqlite3AddIdxKeyType(Vdbe*, Index*);
+int sqlite3Select(Parse*, Select*, int, int, Select*, int, int*, char *aff);
+Select *sqlite3SelectNew(ExprList*,SrcList*,Expr*,ExprList*,Expr*,ExprList*,
+ int,Expr*,Expr*);
+void sqlite3SelectDelete(Select*);
+void sqlite3SelectUnbind(Select*);
+Table *sqlite3SrcListLookup(Parse*, SrcList*);
+int sqlite3IsReadOnly(Parse*, Table*, int);
+void sqlite3OpenTableForReading(Vdbe*, int iCur, Table*);
+void sqlite3OpenTable(Vdbe*, int iCur, Table*, int);
+void sqlite3DeleteFrom(Parse*, SrcList*, Expr*);
+void sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
+WhereInfo *sqlite3WhereBegin(Parse*, SrcList*, Expr*, ExprList**);
+void sqlite3WhereEnd(WhereInfo*);
+void sqlite3ExprCode(Parse*, Expr*);
+void sqlite3ExprCodeAndCache(Parse*, Expr*);
+int sqlite3ExprCodeExprList(Parse*, ExprList*);
+void sqlite3ExprIfTrue(Parse*, Expr*, int, int);
+void sqlite3ExprIfFalse(Parse*, Expr*, int, int);
+void sqlite3NextedParse(Parse*, const char*, ...);
+Table *sqlite3FindTable(sqlite3*,const char*, const char*);
+Table *sqlite3LocateTable(Parse*,const char*, const char*);
+Index *sqlite3FindIndex(sqlite3*,const char*, const char*);
+void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*);
+void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*);
+void sqlite3Vacuum(Parse*, Token*);
+int sqlite3RunVacuum(char**, sqlite3*);
+char *sqlite3NameFromToken(Token*);
+int sqlite3ExprCheck(Parse*, Expr*, int, int*);
+int sqlite3ExprCompare(Expr*, Expr*);
+int sqliteFuncId(Token*);
+int sqlite3ExprResolveNames(NameContext *, Expr *);
+int sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
+int sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
+Vdbe *sqlite3GetVdbe(Parse*);
+void sqlite3Randomness(int, void*);
+void sqlite3RollbackAll(sqlite3*);
+void sqlite3CodeVerifySchema(Parse*, int);
+void sqlite3BeginTransaction(Parse*, int);
+void sqlite3CommitTransaction(Parse*);
+void sqlite3RollbackTransaction(Parse*);
+int sqlite3ExprIsConstant(Expr*);
+int sqlite3ExprIsConstantOrFunction(Expr*);
+int sqlite3ExprIsInteger(Expr*, int*);
+int sqlite3IsRowid(const char*);
+void sqlite3GenerateRowDelete(sqlite3*, Vdbe*, Table*, int, int);
+void sqlite3GenerateRowIndexDelete(sqlite3*, Vdbe*, Table*, int, char*);
+void sqlite3GenerateIndexKey(Vdbe*, Index*, int);
+void sqlite3GenerateConstraintChecks(Parse*,Table*,int,char*,int,int,int,int);
+void sqlite3CompleteInsertion(Parse*, Table*, int, char*, int, int, int);
+void sqlite3OpenTableAndIndices(Parse*, Table*, int, int);
+void sqlite3BeginWriteOperation(Parse*, int, int);
+Expr *sqlite3ExprDup(Expr*);
+void sqlite3TokenCopy(Token*, Token*);
+ExprList *sqlite3ExprListDup(ExprList*);
+SrcList *sqlite3SrcListDup(SrcList*);
+IdList *sqlite3IdListDup(IdList*);
+Select *sqlite3SelectDup(Select*);
+FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,int,u8,int);
+void sqlite3RegisterBuiltinFunctions(sqlite3*);
+void sqlite3RegisterDateTimeFunctions(sqlite3*);
+int sqlite3SafetyOn(sqlite3*);
+int sqlite3SafetyOff(sqlite3*);
+int sqlite3SafetyCheck(sqlite3*);
+void sqlite3ChangeCookie(sqlite3*, Vdbe*, int);
+
+#ifndef SQLITE_OMIT_TRIGGER
+ void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*,
+ int,Expr*,int);
+ void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*);
+ void sqlite3DropTrigger(Parse*, SrcList*);
+ void sqlite3DropTriggerPtr(Parse*, Trigger*, int);
+ int sqlite3TriggersExist(Parse*, Table*, int, ExprList*);
+ int sqlite3CodeRowTrigger(Parse*, int, ExprList*, int, Table *, int, int,
+ int, int);
+ void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*);
+ void sqlite3DeleteTriggerStep(TriggerStep*);
+ TriggerStep *sqlite3TriggerSelectStep(Select*);
+ TriggerStep *sqlite3TriggerInsertStep(Token*, IdList*, ExprList*,Select*,int);
+ TriggerStep *sqlite3TriggerUpdateStep(Token*, ExprList*, Expr*, int);
+ TriggerStep *sqlite3TriggerDeleteStep(Token*, Expr*);
+ void sqlite3DeleteTrigger(Trigger*);
+ void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*);
+#else
+# define sqlite3TriggersExist(A,B,C,D,E,F) 0
+# define sqlite3DeleteTrigger(A)
+# define sqlite3DropTriggerPtr(A,B,C)
+# define sqlite3UnlinkAndDeleteTrigger(A,B,C)
+# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I) 0
+#endif
+
+int sqlite3JoinType(Parse*, Token*, Token*, Token*);
+void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int);
+void sqlite3DeferForeignKey(Parse*, int);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ void sqlite3AuthRead(Parse*,Expr*,SrcList*);
+ int sqlite3AuthCheck(Parse*,int, const char*, const char*, const char*);
+ void sqlite3AuthContextPush(Parse*, AuthContext*, const char*);
+ void sqlite3AuthContextPop(AuthContext*);
+#else
+# define sqlite3AuthRead(a,b,c)
+# define sqlite3AuthCheck(a,b,c,d,e) SQLITE_OK
+# define sqlite3AuthContextPush(a,b,c)
+# define sqlite3AuthContextPop(a) ((void)(a))
+#endif
+void sqlite3Attach(Parse*, Token*, Token*, int, Token*);
+void sqlite3Detach(Parse*, Token*);
+int sqlite3BtreeFactory(const sqlite3 *db, const char *zFilename,
+ int omitJournal, int nCache, Btree **ppBtree,
+ int exclusiveFlag, int allowReadonly);
+int sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
+int sqlite3FixSrcList(DbFixer*, SrcList*);
+int sqlite3FixSelect(DbFixer*, Select*);
+int sqlite3FixExpr(DbFixer*, Expr*);
+int sqlite3FixExprList(DbFixer*, ExprList*);
+int sqlite3FixTriggerStep(DbFixer*, TriggerStep*);
+int sqlite3AtoF(const char *z, double*);
+char *sqlite3_snprintf(int,char*,const char*,...);
+int sqlite3GetInt32(const char *, int*);
+int sqlite3FitsIn64Bits(const char *);
+int sqlite3utf16ByteLen(const void *pData, int nChar);
+int sqlite3utf8CharLen(const char *pData, int nByte);
+int sqlite3ReadUtf8(const unsigned char *);
+int sqlite3PutVarint(unsigned char *, u64);
+int sqlite3GetVarint(const unsigned char *, u64 *);
+int sqlite3GetVarint32(const unsigned char *, u32 *);
+int sqlite3VarintLen(u64 v);
+void sqlite3IndexAffinityStr(Vdbe *, Index *);
+void sqlite3TableAffinityStr(Vdbe *, Table *);
+char sqlite3CompareAffinity(Expr *pExpr, char aff2);
+int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity);
+char sqlite3ExprAffinity(Expr *pExpr);
+int sqlite3atoi64(const char*, i64*);
+void sqlite3Error(sqlite3*, int, const char*,...);
+void *sqlite3HexToBlob(const char *z);
+int sqlite3TwoPartName(Parse *, Token *, Token *, Token **);
+const char *sqlite3ErrStr(int);
+int sqlite3ReadUniChar(const char *zStr, int *pOffset, u8 *pEnc, int fold);
+int sqlite3ReadSchema(Parse *pParse);
+CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char *,int,int);
+CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName, int nName);
+CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
+int sqlite3CheckCollSeq(Parse *, CollSeq *);
+int sqlite3CheckIndexCollSeq(Parse *, Index *);
+int sqlite3CheckObjectName(Parse *, const char *);
+void sqlite3VdbeSetChanges(sqlite3 *, int);
+void sqlite3utf16Substr(sqlite3_context *,int,sqlite3_value **);
+
+const void *sqlite3ValueText(sqlite3_value*, u8);
+int sqlite3ValueBytes(sqlite3_value*, u8);
+void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*));
+void sqlite3ValueFree(sqlite3_value*);
+sqlite3_value *sqlite3ValueNew(void);
+sqlite3_value *sqlite3GetTransientValue(sqlite3*db);
+int sqlite3ValueFromExpr(Expr *, u8, u8, sqlite3_value **);
+void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8);
+extern const unsigned char sqlite3UpperToLower[];
+void sqlite3RootPageMoved(Db*, int, int);
+void sqlite3Reindex(Parse*, Token*, Token*);
+void sqlite3AlterFunctions(sqlite3*);
+void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
+int sqlite3GetToken(const unsigned char *, int *);
+void sqlite3NestedParse(Parse*, const char*, ...);
+void sqlite3ExpirePreparedStatements(sqlite3*);
+void sqlite3CodeSubselect(Parse *, Expr *);
+int sqlite3SelectResolve(Parse *, Select *, NameContext *);
+void sqlite3ColumnDefault(Vdbe *, Table *, int);
+void sqlite3AlterFinishAddColumn(Parse *, Token *);
+void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
+const char *sqlite3TestErrorName(int);
+CollSeq *sqlite3GetCollSeq(sqlite3*, CollSeq *, const char *, int);
+char sqlite3AffinityType(const Token*);
+void sqlite3Analyze(Parse*, Token*, Token*);
+int sqlite3InvokeBusyHandler(BusyHandler*);
+int sqlite3FindDb(sqlite3*, Token*);
+void sqlite3AnalysisLoad(sqlite3*,int iDB);
+void sqlite3DefaultRowEst(Index*);
+void sqlite3RegisterLikeFunctions(sqlite3*, int);
+int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*);
+
+#ifdef SQLITE_SSE
+#include "sseInt.h"
+#endif
+
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/sqliteconfig.h b/kexi/3rdparty/kexisql3/src/sqliteconfig.h
new file mode 100644
index 000000000..c64047b3e
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/sqliteconfig.h
@@ -0,0 +1,8 @@
+#ifdef _WIN32
+# define SQLITE_PTR_SZ 4
+#else
+#ifndef _BKSYS
+# include "../../../../config.h"
+# define SQLITE_PTR_SZ SIZEOF_CHAR_P
+#endif
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/table.c b/kexi/3rdparty/kexisql3/src/table.c
new file mode 100644
index 000000000..d4ef2c8a7
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/table.c
@@ -0,0 +1,195 @@
+/*
+** 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.
+*/
+#include <stdlib.h>
+#include <string.h>
+#include "sqliteInt.h"
+
+/*
+** 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 = 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++){
+ if( colv[i]==0 ){
+ z = 0;
+ }else{
+ z = malloc( strlen(colv[i])+1 );
+ if( z==0 ) goto malloc_failed;
+ strcpy(z, colv[i]);
+ }
+ p->azResult[p->nData++] = z;
+ }
+ }else if( p->nColumn!=nCol ){
+ sqlite3SetString(&p->zErrMsg,
+ "sqlite3_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 ) goto malloc_failed;
+ strcpy(z, argv[i]);
+ }
+ 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.
+*/
+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;
+ 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 = sqlite3_exec(db, zSql, sqlite3_get_table_cb, &res, pzErrMsg);
+ if( res.azResult ){
+ res.azResult[0] = (char*)res.nData;
+ }
+ if( rc==SQLITE_ABORT ){
+ sqlite3_free_table(&res.azResult[1]);
+ if( res.zErrMsg ){
+ if( pzErrMsg ){
+ free(*pzErrMsg);
+ *pzErrMsg = sqlite3_mprintf("%s",res.zErrMsg);
+ }
+ sqliteFree(res.zErrMsg);
+ }
+ db->errCode = res.rc;
+ return res.rc;
+ }
+ sqliteFree(res.zErrMsg);
+ if( rc!=SQLITE_OK ){
+ sqlite3_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 ){
+ sqlite3_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 sqlite3_get_table() malloced.
+*/
+void sqlite3_free_table(
+ char **azResult /* Result returned from from sqlite3_get_table() */
+){
+ if( azResult ){
+ int i, n;
+ azResult--;
+ if( azResult==0 ) return;
+ n = (int)azResult[0];
+ for(i=1; i<n; i++){ if( azResult[i] ) free(azResult[i]); }
+ free(azResult);
+ }
+}
diff --git a/kexi/3rdparty/kexisql3/src/tclsqlite.c b/kexi/3rdparty/kexisql3/src/tclsqlite.c
new file mode 100644
index 000000000..51bde4eec
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/tclsqlite.c
@@ -0,0 +1,2079 @@
+/*
+** 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.
+**
+*************************************************************************
+** A TCL Interface to SQLite
+**
+** $Id: tclsqlite.c 548347 2006-06-05 10:53:00Z staniek $
+*/
+#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
+
+#include "sqliteInt.h"
+#include "hash.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#define NUM_PREPARED_STMTS 10
+#define MAX_PREPARED_STMTS 100
+
+/*
+** If TCL uses UTF-8 and SQLite is configured to use iso8859, then we
+** have to do a translation when going between the two. Set the
+** UTF_TRANSLATION_NEEDED macro to indicate that we need to do
+** this translation.
+*/
+#if defined(TCL_UTF_MAX) && !defined(SQLITE_UTF8)
+# define UTF_TRANSLATION_NEEDED 1
+#endif
+
+/*
+** New SQL functions can be created as TCL scripts. Each such function
+** is described by an instance of the following structure.
+*/
+typedef struct SqlFunc SqlFunc;
+struct SqlFunc {
+ Tcl_Interp *interp; /* The TCL interpret to execute the function */
+ Tcl_Obj *pScript; /* The Tcl_Obj representation of the script */
+ int useEvalObjv; /* True if it is safe to use Tcl_EvalObjv */
+ char *zName; /* Name of this function */
+ SqlFunc *pNext; /* Next function on the list of them all */
+};
+
+/*
+** New collation sequences function can be created as TCL scripts. Each such
+** function is described by an instance of the following structure.
+*/
+typedef struct SqlCollate SqlCollate;
+struct SqlCollate {
+ Tcl_Interp *interp; /* The TCL interpret to execute the function */
+ char *zScript; /* The script to be run */
+ SqlCollate *pNext; /* Next function on the list of them all */
+};
+
+/*
+** Prepared statements are cached for faster execution. Each prepared
+** statement is described by an instance of the following structure.
+*/
+typedef struct SqlPreparedStmt SqlPreparedStmt;
+struct SqlPreparedStmt {
+ SqlPreparedStmt *pNext; /* Next in linked list */
+ SqlPreparedStmt *pPrev; /* Previous on the list */
+ sqlite3_stmt *pStmt; /* The prepared statement */
+ int nSql; /* chars in zSql[] */
+ char zSql[1]; /* Text of the SQL statement */
+};
+
+/*
+** There is one instance of this structure for each SQLite database
+** that has been opened by the SQLite TCL interface.
+*/
+typedef struct SqliteDb SqliteDb;
+struct SqliteDb {
+ sqlite3 *db; /* The "real" database structure */
+ Tcl_Interp *interp; /* The interpreter used for this database */
+ char *zBusy; /* The busy callback routine */
+ char *zCommit; /* The commit hook callback routine */
+ char *zTrace; /* The trace callback routine */
+ char *zProfile; /* The profile callback routine */
+ char *zProgress; /* The progress callback routine */
+ char *zAuth; /* The authorization callback routine */
+ char *zNull; /* Text to substitute for an SQL NULL value */
+ SqlFunc *pFunc; /* List of SQL functions */
+ SqlCollate *pCollate; /* List of SQL collation functions */
+ int rc; /* Return code of most recent sqlite3_exec() */
+ Tcl_Obj *pCollateNeeded; /* Collation needed script */
+ SqlPreparedStmt *stmtList; /* List of prepared statements*/
+ SqlPreparedStmt *stmtLast; /* Last statement in the list */
+ int maxStmt; /* The next maximum number of stmtList */
+ int nStmt; /* Number of statements in stmtList */
+};
+
+/*
+** Look at the script prefix in pCmd. We will be executing this script
+** after first appending one or more arguments. This routine analyzes
+** the script to see if it is safe to use Tcl_EvalObjv() on the script
+** rather than the more general Tcl_EvalEx(). Tcl_EvalObjv() is much
+** faster.
+**
+** Scripts that are safe to use with Tcl_EvalObjv() consists of a
+** command name followed by zero or more arguments with no [...] or $
+** or {...} or ; to be seen anywhere. Most callback scripts consist
+** of just a single procedure name and they meet this requirement.
+*/
+static int safeToUseEvalObjv(Tcl_Interp *interp, Tcl_Obj *pCmd){
+ /* We could try to do something with Tcl_Parse(). But we will instead
+ ** just do a search for forbidden characters. If any of the forbidden
+ ** characters appear in pCmd, we will report the string as unsafe.
+ */
+ const char *z;
+ int n;
+ z = Tcl_GetStringFromObj(pCmd, &n);
+ while( n-- > 0 ){
+ int c = *(z++);
+ if( c=='$' || c=='[' || c==';' ) return 0;
+ }
+ return 1;
+}
+
+/*
+** Find an SqlFunc structure with the given name. Or create a new
+** one if an existing one cannot be found. Return a pointer to the
+** structure.
+*/
+static SqlFunc *findSqlFunc(SqliteDb *pDb, const char *zName){
+ SqlFunc *p, *pNew;
+ int i;
+ pNew = (SqlFunc*)Tcl_Alloc( sizeof(*pNew) + strlen(zName) + 1 );
+ pNew->zName = (char*)&pNew[1];
+ for(i=0; zName[i]; i++){ pNew->zName[i] = tolower(zName[i]); }
+ pNew->zName[i] = 0;
+ for(p=pDb->pFunc; p; p=p->pNext){
+ if( strcmp(p->zName, pNew->zName)==0 ){
+ Tcl_Free((char*)pNew);
+ return p;
+ }
+ }
+ pNew->interp = pDb->interp;
+ pNew->pScript = 0;
+ pNew->pNext = pDb->pFunc;
+ pDb->pFunc = pNew;
+ return pNew;
+}
+
+/*
+** Finalize and free a list of prepared statements
+*/
+static void flushStmtCache( SqliteDb *pDb ){
+ SqlPreparedStmt *pPreStmt;
+
+ while( pDb->stmtList ){
+ sqlite3_finalize( pDb->stmtList->pStmt );
+ pPreStmt = pDb->stmtList;
+ pDb->stmtList = pDb->stmtList->pNext;
+ Tcl_Free( (char*)pPreStmt );
+ }
+ pDb->nStmt = 0;
+ pDb->stmtLast = 0;
+}
+
+/*
+** TCL calls this procedure when an sqlite3 database command is
+** deleted.
+*/
+static void DbDeleteCmd(void *db){
+ SqliteDb *pDb = (SqliteDb*)db;
+ flushStmtCache(pDb);
+ sqlite3_close(pDb->db);
+ while( pDb->pFunc ){
+ SqlFunc *pFunc = pDb->pFunc;
+ pDb->pFunc = pFunc->pNext;
+ Tcl_DecrRefCount(pFunc->pScript);
+ Tcl_Free((char*)pFunc);
+ }
+ while( pDb->pCollate ){
+ SqlCollate *pCollate = pDb->pCollate;
+ pDb->pCollate = pCollate->pNext;
+ Tcl_Free((char*)pCollate);
+ }
+ if( pDb->zBusy ){
+ Tcl_Free(pDb->zBusy);
+ }
+ if( pDb->zTrace ){
+ Tcl_Free(pDb->zTrace);
+ }
+ if( pDb->zProfile ){
+ Tcl_Free(pDb->zProfile);
+ }
+ if( pDb->zAuth ){
+ Tcl_Free(pDb->zAuth);
+ }
+ if( pDb->zNull ){
+ Tcl_Free(pDb->zNull);
+ }
+ Tcl_Free((char*)pDb);
+}
+
+/*
+** This routine is called when a database file is locked while trying
+** to execute SQL.
+*/
+static int DbBusyHandler(void *cd, int nTries){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ int rc;
+ char zVal[30];
+
+ sprintf(zVal, "%d", nTries);
+ rc = Tcl_VarEval(pDb->interp, pDb->zBusy, " ", zVal, (char*)0);
+ if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){
+ return 0;
+ }
+ return 1;
+}
+
+/*
+** This routine is invoked as the 'progress callback' for the database.
+*/
+static int DbProgressHandler(void *cd){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ int rc;
+
+ assert( pDb->zProgress );
+ rc = Tcl_Eval(pDb->interp, pDb->zProgress);
+ if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** This routine is called by the SQLite trace handler whenever a new
+** block of SQL is executed. The TCL script in pDb->zTrace is executed.
+*/
+static void DbTraceHandler(void *cd, const char *zSql){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ Tcl_DString str;
+
+ Tcl_DStringInit(&str);
+ Tcl_DStringAppend(&str, pDb->zTrace, -1);
+ Tcl_DStringAppendElement(&str, zSql);
+ Tcl_Eval(pDb->interp, Tcl_DStringValue(&str));
+ Tcl_DStringFree(&str);
+ Tcl_ResetResult(pDb->interp);
+}
+
+/*
+** This routine is called by the SQLite profile handler after a statement
+** SQL has executed. The TCL script in pDb->zProfile is evaluated.
+*/
+static void DbProfileHandler(void *cd, const char *zSql, sqlite_uint64 tm){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ Tcl_DString str;
+ char zTm[100];
+
+ sqlite3_snprintf(sizeof(zTm)-1, zTm, "%lld", tm);
+ Tcl_DStringInit(&str);
+ Tcl_DStringAppend(&str, pDb->zProfile, -1);
+ Tcl_DStringAppendElement(&str, zSql);
+ Tcl_DStringAppendElement(&str, zTm);
+ Tcl_Eval(pDb->interp, Tcl_DStringValue(&str));
+ Tcl_DStringFree(&str);
+ Tcl_ResetResult(pDb->interp);
+}
+
+/*
+** This routine is called when a transaction is committed. The
+** TCL script in pDb->zCommit is executed. If it returns non-zero or
+** if it throws an exception, the transaction is rolled back instead
+** of being committed.
+*/
+static int DbCommitHandler(void *cd){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ int rc;
+
+ rc = Tcl_Eval(pDb->interp, pDb->zCommit);
+ if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){
+ return 1;
+ }
+ return 0;
+}
+
+static void tclCollateNeeded(
+ void *pCtx,
+ sqlite3 *db,
+ int enc,
+ const char *zName
+){
+ SqliteDb *pDb = (SqliteDb *)pCtx;
+ Tcl_Obj *pScript = Tcl_DuplicateObj(pDb->pCollateNeeded);
+ Tcl_IncrRefCount(pScript);
+ Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj(zName, -1));
+ Tcl_EvalObjEx(pDb->interp, pScript, 0);
+ Tcl_DecrRefCount(pScript);
+}
+
+/*
+** This routine is called to evaluate an SQL collation function implemented
+** using TCL script.
+*/
+static int tclSqlCollate(
+ void *pCtx,
+ int nA,
+ const void *zA,
+ int nB,
+ const void *zB
+){
+ SqlCollate *p = (SqlCollate *)pCtx;
+ Tcl_Obj *pCmd;
+
+ pCmd = Tcl_NewStringObj(p->zScript, -1);
+ Tcl_IncrRefCount(pCmd);
+ Tcl_ListObjAppendElement(p->interp, pCmd, Tcl_NewStringObj(zA, nA));
+ Tcl_ListObjAppendElement(p->interp, pCmd, Tcl_NewStringObj(zB, nB));
+ Tcl_EvalObjEx(p->interp, pCmd, TCL_EVAL_DIRECT);
+ Tcl_DecrRefCount(pCmd);
+ return (atoi(Tcl_GetStringResult(p->interp)));
+}
+
+/*
+** This routine is called to evaluate an SQL function implemented
+** using TCL script.
+*/
+static void tclSqlFunc(sqlite3_context *context, int argc, sqlite3_value**argv){
+ SqlFunc *p = sqlite3_user_data(context);
+ Tcl_Obj *pCmd;
+ int i;
+ int rc;
+
+ if( argc==0 ){
+ /* If there are no arguments to the function, call Tcl_EvalObjEx on the
+ ** script object directly. This allows the TCL compiler to generate
+ ** bytecode for the command on the first invocation and thus make
+ ** subsequent invocations much faster. */
+ pCmd = p->pScript;
+ Tcl_IncrRefCount(pCmd);
+ rc = Tcl_EvalObjEx(p->interp, pCmd, 0);
+ Tcl_DecrRefCount(pCmd);
+ }else{
+ /* If there are arguments to the function, make a shallow copy of the
+ ** script object, lappend the arguments, then evaluate the copy.
+ **
+ ** By "shallow" copy, we mean a only the outer list Tcl_Obj is duplicated.
+ ** The new Tcl_Obj contains pointers to the original list elements.
+ ** That way, when Tcl_EvalObjv() is run and shimmers the first element
+ ** of the list to tclCmdNameType, that alternate representation will
+ ** be preserved and reused on the next invocation.
+ */
+ Tcl_Obj **aArg;
+ int nArg;
+ if( Tcl_ListObjGetElements(p->interp, p->pScript, &nArg, &aArg) ){
+ sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
+ return;
+ }
+ pCmd = Tcl_NewListObj(nArg, aArg);
+ Tcl_IncrRefCount(pCmd);
+ for(i=0; i<argc; i++){
+ sqlite3_value *pIn = argv[i];
+ Tcl_Obj *pVal;
+
+ /* Set pVal to contain the i'th column of this row. */
+ switch( sqlite3_value_type(pIn) ){
+ case SQLITE_BLOB: {
+ int bytes = sqlite3_value_bytes(pIn);
+ pVal = Tcl_NewByteArrayObj(sqlite3_value_blob(pIn), bytes);
+ break;
+ }
+ case SQLITE_INTEGER: {
+ sqlite_int64 v = sqlite3_value_int64(pIn);
+ if( v>=-2147483647 && v<=2147483647 ){
+ pVal = Tcl_NewIntObj(v);
+ }else{
+ pVal = Tcl_NewWideIntObj(v);
+ }
+ break;
+ }
+ case SQLITE_FLOAT: {
+ double r = sqlite3_value_double(pIn);
+ pVal = Tcl_NewDoubleObj(r);
+ break;
+ }
+ case SQLITE_NULL: {
+ pVal = Tcl_NewStringObj("", 0);
+ break;
+ }
+ default: {
+ int bytes = sqlite3_value_bytes(pIn);
+ pVal = Tcl_NewStringObj(sqlite3_value_text(pIn), bytes);
+ break;
+ }
+ }
+ rc = Tcl_ListObjAppendElement(p->interp, pCmd, pVal);
+ if( rc ){
+ Tcl_DecrRefCount(pCmd);
+ sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
+ return;
+ }
+ }
+ if( !p->useEvalObjv ){
+ /* Tcl_EvalObjEx() will automatically call Tcl_EvalObjv() if pCmd
+ ** is a list without a string representation. To prevent this from
+ ** happening, make sure pCmd has a valid string representation */
+ Tcl_GetString(pCmd);
+ }
+ rc = Tcl_EvalObjEx(p->interp, pCmd, TCL_EVAL_DIRECT);
+ Tcl_DecrRefCount(pCmd);
+ }
+
+ if( rc && rc!=TCL_RETURN ){
+ sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
+ }else{
+ Tcl_Obj *pVar = Tcl_GetObjResult(p->interp);
+ int n;
+ u8 *data;
+ char *zType = pVar->typePtr ? pVar->typePtr->name : "";
+ char c = zType[0];
+ if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){
+ /* Only return a BLOB type if the Tcl variable is a bytearray and
+ ** has no string representation. */
+ data = Tcl_GetByteArrayFromObj(pVar, &n);
+ sqlite3_result_blob(context, data, n, SQLITE_TRANSIENT);
+ }else if( (c=='b' && strcmp(zType,"boolean")==0) ||
+ (c=='i' && strcmp(zType,"int")==0) ){
+ Tcl_GetIntFromObj(0, pVar, &n);
+ sqlite3_result_int(context, n);
+ }else if( c=='d' && strcmp(zType,"double")==0 ){
+ double r;
+ Tcl_GetDoubleFromObj(0, pVar, &r);
+ sqlite3_result_double(context, r);
+ }else if( c=='w' && strcmp(zType,"wideInt")==0 ){
+ Tcl_WideInt v;
+ Tcl_GetWideIntFromObj(0, pVar, &v);
+ sqlite3_result_int64(context, v);
+ }else{
+ data = Tcl_GetStringFromObj(pVar, &n);
+ sqlite3_result_text(context, data, n, SQLITE_TRANSIENT);
+ }
+ }
+}
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+/*
+** This is the authentication function. It appends the authentication
+** type code and the two arguments to zCmd[] then invokes the result
+** on the interpreter. The reply is examined to determine if the
+** authentication fails or succeeds.
+*/
+static int auth_callback(
+ void *pArg,
+ int code,
+ const char *zArg1,
+ const char *zArg2,
+ const char *zArg3,
+ const char *zArg4
+){
+ char *zCode;
+ Tcl_DString str;
+ int rc;
+ const char *zReply;
+ SqliteDb *pDb = (SqliteDb*)pArg;
+
+ switch( code ){
+ case SQLITE_COPY : zCode="SQLITE_COPY"; break;
+ case SQLITE_CREATE_INDEX : zCode="SQLITE_CREATE_INDEX"; break;
+ case SQLITE_CREATE_TABLE : zCode="SQLITE_CREATE_TABLE"; break;
+ case SQLITE_CREATE_TEMP_INDEX : zCode="SQLITE_CREATE_TEMP_INDEX"; break;
+ case SQLITE_CREATE_TEMP_TABLE : zCode="SQLITE_CREATE_TEMP_TABLE"; break;
+ case SQLITE_CREATE_TEMP_TRIGGER: zCode="SQLITE_CREATE_TEMP_TRIGGER"; break;
+ case SQLITE_CREATE_TEMP_VIEW : zCode="SQLITE_CREATE_TEMP_VIEW"; break;
+ case SQLITE_CREATE_TRIGGER : zCode="SQLITE_CREATE_TRIGGER"; break;
+ case SQLITE_CREATE_VIEW : zCode="SQLITE_CREATE_VIEW"; break;
+ case SQLITE_DELETE : zCode="SQLITE_DELETE"; break;
+ case SQLITE_DROP_INDEX : zCode="SQLITE_DROP_INDEX"; break;
+ case SQLITE_DROP_TABLE : zCode="SQLITE_DROP_TABLE"; break;
+ case SQLITE_DROP_TEMP_INDEX : zCode="SQLITE_DROP_TEMP_INDEX"; break;
+ case SQLITE_DROP_TEMP_TABLE : zCode="SQLITE_DROP_TEMP_TABLE"; break;
+ case SQLITE_DROP_TEMP_TRIGGER : zCode="SQLITE_DROP_TEMP_TRIGGER"; break;
+ case SQLITE_DROP_TEMP_VIEW : zCode="SQLITE_DROP_TEMP_VIEW"; break;
+ case SQLITE_DROP_TRIGGER : zCode="SQLITE_DROP_TRIGGER"; break;
+ case SQLITE_DROP_VIEW : zCode="SQLITE_DROP_VIEW"; break;
+ case SQLITE_INSERT : zCode="SQLITE_INSERT"; break;
+ case SQLITE_PRAGMA : zCode="SQLITE_PRAGMA"; break;
+ case SQLITE_READ : zCode="SQLITE_READ"; break;
+ case SQLITE_SELECT : zCode="SQLITE_SELECT"; break;
+ case SQLITE_TRANSACTION : zCode="SQLITE_TRANSACTION"; break;
+ case SQLITE_UPDATE : zCode="SQLITE_UPDATE"; break;
+ case SQLITE_ATTACH : zCode="SQLITE_ATTACH"; break;
+ case SQLITE_DETACH : zCode="SQLITE_DETACH"; break;
+ case SQLITE_ALTER_TABLE : zCode="SQLITE_ALTER_TABLE"; break;
+ case SQLITE_REINDEX : zCode="SQLITE_REINDEX"; break;
+ case SQLITE_ANALYZE : zCode="SQLITE_ANALYZE"; break;
+ default : zCode="????"; break;
+ }
+ Tcl_DStringInit(&str);
+ Tcl_DStringAppend(&str, pDb->zAuth, -1);
+ Tcl_DStringAppendElement(&str, zCode);
+ Tcl_DStringAppendElement(&str, zArg1 ? zArg1 : "");
+ Tcl_DStringAppendElement(&str, zArg2 ? zArg2 : "");
+ Tcl_DStringAppendElement(&str, zArg3 ? zArg3 : "");
+ Tcl_DStringAppendElement(&str, zArg4 ? zArg4 : "");
+ rc = Tcl_GlobalEval(pDb->interp, Tcl_DStringValue(&str));
+ Tcl_DStringFree(&str);
+ zReply = Tcl_GetStringResult(pDb->interp);
+ if( strcmp(zReply,"SQLITE_OK")==0 ){
+ rc = SQLITE_OK;
+ }else if( strcmp(zReply,"SQLITE_DENY")==0 ){
+ rc = SQLITE_DENY;
+ }else if( strcmp(zReply,"SQLITE_IGNORE")==0 ){
+ rc = SQLITE_IGNORE;
+ }else{
+ rc = 999;
+ }
+ return rc;
+}
+#endif /* SQLITE_OMIT_AUTHORIZATION */
+
+/*
+** zText is a pointer to text obtained via an sqlite3_result_text()
+** or similar interface. This routine returns a Tcl string object,
+** reference count set to 0, containing the text. If a translation
+** between iso8859 and UTF-8 is required, it is preformed.
+*/
+static Tcl_Obj *dbTextToObj(char const *zText){
+ Tcl_Obj *pVal;
+#ifdef UTF_TRANSLATION_NEEDED
+ Tcl_DString dCol;
+ Tcl_DStringInit(&dCol);
+ Tcl_ExternalToUtfDString(NULL, zText, -1, &dCol);
+ pVal = Tcl_NewStringObj(Tcl_DStringValue(&dCol), -1);
+ Tcl_DStringFree(&dCol);
+#else
+ pVal = Tcl_NewStringObj(zText, -1);
+#endif
+ return pVal;
+}
+
+/*
+** This routine reads a line of text from FILE in, 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.
+**
+** copied from shell.c from '.import' command
+*/
+static char *local_getline(char *zPrompt, FILE *in){
+ char *zLine;
+ int nLine;
+ int n;
+ int eol;
+
+ 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;
+}
+
+/*
+** The "sqlite" command below creates a new Tcl command for each
+** connection it opens to an SQLite database. This routine is invoked
+** whenever one of those connection-specific commands is executed
+** in Tcl. For example, if you run Tcl code like this:
+**
+** sqlite3 db1 "my_database"
+** db1 close
+**
+** The first command opens a connection to the "my_database" database
+** and calls that connection "db1". The second command causes this
+** subroutine to be invoked.
+*/
+static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ int choice;
+ int rc = TCL_OK;
+ static const char *DB_strs[] = {
+ "authorizer", "busy", "cache",
+ "changes", "close", "collate",
+ "collation_needed", "commit_hook", "complete",
+ "copy", "errorcode", "eval",
+ "function", "last_insert_rowid", "nullvalue",
+ "onecolumn", "profile", "progress",
+ "rekey", "timeout", "total_changes",
+ "trace", "transaction", "version",
+ 0
+ };
+ enum DB_enum {
+ DB_AUTHORIZER, DB_BUSY, DB_CACHE,
+ DB_CHANGES, DB_CLOSE, DB_COLLATE,
+ DB_COLLATION_NEEDED, DB_COMMIT_HOOK, DB_COMPLETE,
+ DB_COPY, DB_ERRORCODE, DB_EVAL,
+ DB_FUNCTION, DB_LAST_INSERT_ROWID,DB_NULLVALUE,
+ DB_ONECOLUMN, DB_PROFILE, DB_PROGRESS,
+ DB_REKEY, DB_TIMEOUT, DB_TOTAL_CHANGES,
+ DB_TRACE, DB_TRANSACTION, DB_VERSION
+ };
+ /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
+ return TCL_ERROR;
+ }
+ if( Tcl_GetIndexFromObj(interp, objv[1], DB_strs, "option", 0, &choice) ){
+ return TCL_ERROR;
+ }
+
+ switch( (enum DB_enum)choice ){
+
+ /* $db authorizer ?CALLBACK?
+ **
+ ** Invoke the given callback to authorize each SQL operation as it is
+ ** compiled. 5 arguments are appended to the callback before it is
+ ** invoked:
+ **
+ ** (1) The authorization type (ex: SQLITE_CREATE_TABLE, SQLITE_INSERT, ...)
+ ** (2) First descriptive name (depends on authorization type)
+ ** (3) Second descriptive name
+ ** (4) Name of the database (ex: "main", "temp")
+ ** (5) Name of trigger that is doing the access
+ **
+ ** The callback should return on of the following strings: SQLITE_OK,
+ ** SQLITE_IGNORE, or SQLITE_DENY. Any other return value is an error.
+ **
+ ** If this method is invoked with no arguments, the current authorization
+ ** callback string is returned.
+ */
+ case DB_AUTHORIZER: {
+#ifdef SQLITE_OMIT_AUTHORIZATION
+ Tcl_AppendResult(interp, "authorization not available in this build", 0);
+ return TCL_ERROR;
+#else
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+ return TCL_ERROR;
+ }else if( objc==2 ){
+ if( pDb->zAuth ){
+ Tcl_AppendResult(interp, pDb->zAuth, 0);
+ }
+ }else{
+ char *zAuth;
+ int len;
+ if( pDb->zAuth ){
+ Tcl_Free(pDb->zAuth);
+ }
+ zAuth = Tcl_GetStringFromObj(objv[2], &len);
+ if( zAuth && len>0 ){
+ pDb->zAuth = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zAuth, zAuth);
+ }else{
+ pDb->zAuth = 0;
+ }
+ if( pDb->zAuth ){
+ pDb->interp = interp;
+ sqlite3_set_authorizer(pDb->db, auth_callback, pDb);
+ }else{
+ sqlite3_set_authorizer(pDb->db, 0, 0);
+ }
+ }
+#endif
+ break;
+ }
+
+ /* $db busy ?CALLBACK?
+ **
+ ** Invoke the given callback if an SQL statement attempts to open
+ ** a locked database file.
+ */
+ case DB_BUSY: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "CALLBACK");
+ return TCL_ERROR;
+ }else if( objc==2 ){
+ if( pDb->zBusy ){
+ Tcl_AppendResult(interp, pDb->zBusy, 0);
+ }
+ }else{
+ char *zBusy;
+ int len;
+ if( pDb->zBusy ){
+ Tcl_Free(pDb->zBusy);
+ }
+ zBusy = Tcl_GetStringFromObj(objv[2], &len);
+ if( zBusy && len>0 ){
+ pDb->zBusy = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zBusy, zBusy);
+ }else{
+ pDb->zBusy = 0;
+ }
+ if( pDb->zBusy ){
+ pDb->interp = interp;
+ sqlite3_busy_handler(pDb->db, DbBusyHandler, pDb);
+ }else{
+ sqlite3_busy_handler(pDb->db, 0, 0);
+ }
+ }
+ break;
+ }
+
+ /* $db cache flush
+ ** $db cache size n
+ **
+ ** Flush the prepared statement cache, or set the maximum number of
+ ** cached statements.
+ */
+ case DB_CACHE: {
+ char *subCmd;
+ int n;
+
+ if( objc<=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "cache option ?arg?");
+ return TCL_ERROR;
+ }
+ subCmd = Tcl_GetStringFromObj( objv[2], 0 );
+ if( *subCmd=='f' && strcmp(subCmd,"flush")==0 ){
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "flush");
+ return TCL_ERROR;
+ }else{
+ flushStmtCache( pDb );
+ }
+ }else if( *subCmd=='s' && strcmp(subCmd,"size")==0 ){
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "size n");
+ return TCL_ERROR;
+ }else{
+ if( TCL_ERROR==Tcl_GetIntFromObj(interp, objv[3], &n) ){
+ Tcl_AppendResult( interp, "cannot convert \"",
+ Tcl_GetStringFromObj(objv[3],0), "\" to integer", 0);
+ return TCL_ERROR;
+ }else{
+ if( n<0 ){
+ flushStmtCache( pDb );
+ n = 0;
+ }else if( n>MAX_PREPARED_STMTS ){
+ n = MAX_PREPARED_STMTS;
+ }
+ pDb->maxStmt = n;
+ }
+ }
+ }else{
+ Tcl_AppendResult( interp, "bad option \"",
+ Tcl_GetStringFromObj(objv[0],0), "\": must be flush or size", 0);
+ return TCL_ERROR;
+ }
+ break;
+ }
+
+ /* $db changes
+ **
+ ** Return the number of rows that were modified, inserted, or deleted by
+ ** the most recent INSERT, UPDATE or DELETE statement, not including
+ ** any changes made by trigger programs.
+ */
+ case DB_CHANGES: {
+ Tcl_Obj *pResult;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetIntObj(pResult, sqlite3_changes(pDb->db));
+ break;
+ }
+
+ /* $db close
+ **
+ ** Shutdown the database
+ */
+ case DB_CLOSE: {
+ Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0));
+ break;
+ }
+
+ /*
+ ** $db collate NAME SCRIPT
+ **
+ ** Create a new SQL collation function called NAME. Whenever
+ ** that function is called, invoke SCRIPT to evaluate the function.
+ */
+ case DB_COLLATE: {
+ SqlCollate *pCollate;
+ char *zName;
+ char *zScript;
+ int nScript;
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "NAME SCRIPT");
+ return TCL_ERROR;
+ }
+ zName = Tcl_GetStringFromObj(objv[2], 0);
+ zScript = Tcl_GetStringFromObj(objv[3], &nScript);
+ pCollate = (SqlCollate*)Tcl_Alloc( sizeof(*pCollate) + nScript + 1 );
+ if( pCollate==0 ) return TCL_ERROR;
+ pCollate->interp = interp;
+ pCollate->pNext = pDb->pCollate;
+ pCollate->zScript = (char*)&pCollate[1];
+ pDb->pCollate = pCollate;
+ strcpy(pCollate->zScript, zScript);
+ if( sqlite3_create_collation(pDb->db, zName, SQLITE_UTF8,
+ pCollate, tclSqlCollate) ){
+ Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE);
+ return TCL_ERROR;
+ }
+ break;
+ }
+
+ /*
+ ** $db collation_needed SCRIPT
+ **
+ ** Create a new SQL collation function called NAME. Whenever
+ ** that function is called, invoke SCRIPT to evaluate the function.
+ */
+ case DB_COLLATION_NEEDED: {
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "SCRIPT");
+ return TCL_ERROR;
+ }
+ if( pDb->pCollateNeeded ){
+ Tcl_DecrRefCount(pDb->pCollateNeeded);
+ }
+ pDb->pCollateNeeded = Tcl_DuplicateObj(objv[2]);
+ Tcl_IncrRefCount(pDb->pCollateNeeded);
+ sqlite3_collation_needed(pDb->db, pDb, tclCollateNeeded);
+ break;
+ }
+
+ /* $db commit_hook ?CALLBACK?
+ **
+ ** Invoke the given callback just before committing every SQL transaction.
+ ** If the callback throws an exception or returns non-zero, then the
+ ** transaction is aborted. If CALLBACK is an empty string, the callback
+ ** is disabled.
+ */
+ case DB_COMMIT_HOOK: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+ return TCL_ERROR;
+ }else if( objc==2 ){
+ if( pDb->zCommit ){
+ Tcl_AppendResult(interp, pDb->zCommit, 0);
+ }
+ }else{
+ char *zCommit;
+ int len;
+ if( pDb->zCommit ){
+ Tcl_Free(pDb->zCommit);
+ }
+ zCommit = Tcl_GetStringFromObj(objv[2], &len);
+ if( zCommit && len>0 ){
+ pDb->zCommit = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zCommit, zCommit);
+ }else{
+ pDb->zCommit = 0;
+ }
+ if( pDb->zCommit ){
+ pDb->interp = interp;
+ sqlite3_commit_hook(pDb->db, DbCommitHandler, pDb);
+ }else{
+ sqlite3_commit_hook(pDb->db, 0, 0);
+ }
+ }
+ break;
+ }
+
+ /* $db complete SQL
+ **
+ ** Return TRUE if SQL is a complete SQL statement. Return FALSE if
+ ** additional lines of input are needed. This is similar to the
+ ** built-in "info complete" command of Tcl.
+ */
+ case DB_COMPLETE: {
+#ifndef SQLITE_OMIT_COMPLETE
+ Tcl_Obj *pResult;
+ int isComplete;
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "SQL");
+ return TCL_ERROR;
+ }
+ isComplete = sqlite3_complete( Tcl_GetStringFromObj(objv[2], 0) );
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetBooleanObj(pResult, isComplete);
+#endif
+ break;
+ }
+
+ /* $db copy conflict-algorithm table filename ?SEPARATOR? ?NULLINDICATOR?
+ **
+ ** Copy data into table from filename, optionally using SEPARATOR
+ ** as column separators. If a column contains a null string, or the
+ ** value of NULLINDICATOR, a NULL is inserted for the column.
+ ** conflict-algorithm is one of the sqlite conflict algorithms:
+ ** rollback, abort, fail, ignore, replace
+ ** On success, return the number of lines processed, not necessarily same
+ ** as 'db changes' due to conflict-algorithm selected.
+ **
+ ** This code is basically an implementation/enhancement of
+ ** the sqlite3 shell.c ".import" command.
+ **
+ ** This command usage is equivalent to the sqlite2.x COPY statement,
+ ** which imports file data into a table using the PostgreSQL COPY file format:
+ ** $db copy $conflit_algo $table_name $filename \t \\N
+ */
+ case DB_COPY: {
+ char *zTable; /* Insert data into this table */
+ char *zFile; /* The file from which to extract data */
+ char *zConflict; /* The conflict algorithm to use */
+ sqlite3_stmt *pStmt; /* A statement */
+ int rc; /* Result code */
+ int nCol; /* Number of columns in the table */
+ int nByte; /* Number of bytes in an SQL string */
+ int i, j; /* Loop counters */
+ int nSep; /* Number of bytes in zSep[] */
+ int nNull; /* Number of bytes in zNull[] */
+ char *zSql; /* An SQL statement */
+ char *zLine; /* A single line of input from the file */
+ char **azCol; /* zLine[] broken up into columns */
+ char *zCommit; /* How to commit changes */
+ FILE *in; /* The input file */
+ int lineno = 0; /* Line number of input file */
+ char zLineNum[80]; /* Line number print buffer */
+ Tcl_Obj *pResult; /* interp result */
+
+ char *zSep;
+ char *zNull;
+ if( objc<5 || objc>7 ){
+ Tcl_WrongNumArgs(interp, 2, objv,
+ "CONFLICT-ALGORITHM TABLE FILENAME ?SEPARATOR? ?NULLINDICATOR?");
+ return TCL_ERROR;
+ }
+ if( objc>=6 ){
+ zSep = Tcl_GetStringFromObj(objv[5], 0);
+ }else{
+ zSep = "\t";
+ }
+ if( objc>=7 ){
+ zNull = Tcl_GetStringFromObj(objv[6], 0);
+ }else{
+ zNull = "";
+ }
+ zConflict = Tcl_GetStringFromObj(objv[2], 0);
+ zTable = Tcl_GetStringFromObj(objv[3], 0);
+ zFile = Tcl_GetStringFromObj(objv[4], 0);
+ nSep = strlen(zSep);
+ nNull = strlen(zNull);
+ if( nSep==0 ){
+ Tcl_AppendResult(interp, "Error: non-null separator required for copy", 0);
+ return TCL_ERROR;
+ }
+ if(sqlite3StrICmp(zConflict, "rollback") != 0 &&
+ sqlite3StrICmp(zConflict, "abort" ) != 0 &&
+ sqlite3StrICmp(zConflict, "fail" ) != 0 &&
+ sqlite3StrICmp(zConflict, "ignore" ) != 0 &&
+ sqlite3StrICmp(zConflict, "replace" ) != 0 ) {
+ Tcl_AppendResult(interp, "Error: \"", zConflict,
+ "\", conflict-algorithm must be one of: rollback, "
+ "abort, fail, ignore, or replace", 0);
+ return TCL_ERROR;
+ }
+ zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable);
+ if( zSql==0 ){
+ Tcl_AppendResult(interp, "Error: no such table: ", zTable, 0);
+ return TCL_ERROR;
+ }
+ nByte = strlen(zSql);
+ rc = sqlite3_prepare(pDb->db, zSql, 0, &pStmt, 0);
+ sqlite3_free(zSql);
+ if( rc ){
+ Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0);
+ nCol = 0;
+ }else{
+ nCol = sqlite3_column_count(pStmt);
+ }
+ sqlite3_finalize(pStmt);
+ if( nCol==0 ) {
+ return TCL_ERROR;
+ }
+ zSql = malloc( nByte + 50 + nCol*2 );
+ if( zSql==0 ) {
+ Tcl_AppendResult(interp, "Error: can't malloc()", 0);
+ return TCL_ERROR;
+ }
+ sqlite3_snprintf(nByte+50, zSql, "INSERT OR %q INTO '%q' VALUES(?",
+ zConflict, zTable);
+ j = strlen(zSql);
+ for(i=1; i<nCol; i++){
+ zSql[j++] = ',';
+ zSql[j++] = '?';
+ }
+ zSql[j++] = ')';
+ zSql[j] = 0;
+ rc = sqlite3_prepare(pDb->db, zSql, 0, &pStmt, 0);
+ free(zSql);
+ if( rc ){
+ Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0);
+ sqlite3_finalize(pStmt);
+ return TCL_ERROR;
+ }
+ in = fopen(zFile, "rb");
+ if( in==0 ){
+ Tcl_AppendResult(interp, "Error: cannot open file: ", zFile, NULL);
+ sqlite3_finalize(pStmt);
+ return TCL_ERROR;
+ }
+ azCol = malloc( sizeof(azCol[0])*(nCol+1) );
+ if( azCol==0 ) {
+ Tcl_AppendResult(interp, "Error: can't malloc()", 0);
+ return TCL_ERROR;
+ }
+ sqlite3_exec(pDb->db, "BEGIN", 0, 0, 0);
+ zCommit = "COMMIT";
+ while( (zLine = local_getline(0, in))!=0 ){
+ char *z;
+ i = 0;
+ lineno++;
+ azCol[0] = zLine;
+ for(i=0, z=zLine; *z; z++){
+ if( *z==zSep[0] && strncmp(z, zSep, nSep)==0 ){
+ *z = 0;
+ i++;
+ if( i<nCol ){
+ azCol[i] = &z[nSep];
+ z += nSep-1;
+ }
+ }
+ }
+ if( i+1!=nCol ){
+ char *zErr;
+ zErr = malloc(200 + strlen(zFile));
+ sprintf(zErr,"Error: %s line %d: expected %d columns of data but found %d",
+ zFile, lineno, nCol, i+1);
+ Tcl_AppendResult(interp, zErr, 0);
+ free(zErr);
+ zCommit = "ROLLBACK";
+ break;
+ }
+ for(i=0; i<nCol; i++){
+ /* check for null data, if so, bind as null */
+ if ((nNull>0 && strcmp(azCol[i], zNull)==0) || strlen(azCol[i])==0) {
+ sqlite3_bind_null(pStmt, i+1);
+ }else{
+ sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC);
+ }
+ }
+ sqlite3_step(pStmt);
+ rc = sqlite3_reset(pStmt);
+ free(zLine);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp,"Error: ", sqlite3_errmsg(pDb->db), 0);
+ zCommit = "ROLLBACK";
+ break;
+ }
+ }
+ free(azCol);
+ fclose(in);
+ sqlite3_finalize(pStmt);
+ sqlite3_exec(pDb->db, zCommit, 0, 0, 0);
+
+ if( zCommit[0] == 'C' ){
+ /* success, set result as number of lines processed */
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetIntObj(pResult, lineno);
+ rc = TCL_OK;
+ }else{
+ /* failure, append lineno where failed */
+ sprintf(zLineNum,"%d",lineno);
+ Tcl_AppendResult(interp,", failed while processing line: ",zLineNum,0);
+ rc = TCL_ERROR;
+ }
+ break;
+ }
+
+ /*
+ ** $db errorcode
+ **
+ ** Return the numeric error code that was returned by the most recent
+ ** call to sqlite3_exec().
+ */
+ case DB_ERRORCODE: {
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_errcode(pDb->db)));
+ break;
+ }
+
+ /*
+ ** $db eval $sql ?array? ?{ ...code... }?
+ ** $db onecolumn $sql
+ **
+ ** The SQL statement in $sql is evaluated. For each row, the values are
+ ** placed in elements of the array named "array" and ...code... is executed.
+ ** If "array" and "code" are omitted, then no callback is every invoked.
+ ** If "array" is an empty string, then the values are placed in variables
+ ** that have the same name as the fields extracted by the query.
+ **
+ ** The onecolumn method is the equivalent of:
+ ** lindex [$db eval $sql] 0
+ */
+ case DB_ONECOLUMN:
+ case DB_EVAL: {
+ char const *zSql; /* Next SQL statement to execute */
+ char const *zLeft; /* What is left after first stmt in zSql */
+ sqlite3_stmt *pStmt; /* Compiled SQL statment */
+ Tcl_Obj *pArray; /* Name of array into which results are written */
+ Tcl_Obj *pScript; /* Script to run for each result set */
+ Tcl_Obj **apParm; /* Parameters that need a Tcl_DecrRefCount() */
+ int nParm; /* Number of entries used in apParm[] */
+ Tcl_Obj *aParm[10]; /* Static space for apParm[] in the common case */
+ Tcl_Obj *pRet; /* Value to be returned */
+ SqlPreparedStmt *pPreStmt; /* Pointer to a prepared statement */
+ int rc2;
+
+ if( choice==DB_ONECOLUMN ){
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "SQL");
+ return TCL_ERROR;
+ }
+ pRet = 0;
+ }else{
+ if( objc<3 || objc>5 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME? ?SCRIPT?");
+ return TCL_ERROR;
+ }
+ pRet = Tcl_NewObj();
+ Tcl_IncrRefCount(pRet);
+ }
+ if( objc==3 ){
+ pArray = pScript = 0;
+ }else if( objc==4 ){
+ pArray = 0;
+ pScript = objv[3];
+ }else{
+ pArray = objv[3];
+ if( Tcl_GetString(pArray)[0]==0 ) pArray = 0;
+ pScript = objv[4];
+ }
+
+ Tcl_IncrRefCount(objv[2]);
+ zSql = Tcl_GetStringFromObj(objv[2], 0);
+ while( rc==TCL_OK && zSql[0] ){
+ int i; /* Loop counter */
+ int nVar; /* Number of bind parameters in the pStmt */
+ int nCol; /* Number of columns in the result set */
+ Tcl_Obj **apColName = 0; /* Array of column names */
+ int len; /* String length of zSql */
+
+ /* Try to find a SQL statement that has already been compiled and
+ ** which matches the next sequence of SQL.
+ */
+ pStmt = 0;
+ pPreStmt = pDb->stmtList;
+ len = strlen(zSql);
+ if( pPreStmt && sqlite3_expired(pPreStmt->pStmt) ){
+ flushStmtCache(pDb);
+ pPreStmt = 0;
+ }
+ for(; pPreStmt; pPreStmt=pPreStmt->pNext){
+ int n = pPreStmt->nSql;
+ if( len>=n
+ && memcmp(pPreStmt->zSql, zSql, n)==0
+ && (zSql[n]==0 || zSql[n-1]==';')
+ ){
+ pStmt = pPreStmt->pStmt;
+ zLeft = &zSql[pPreStmt->nSql];
+
+ /* When a prepared statement is found, unlink it from the
+ ** cache list. It will later be added back to the beginning
+ ** of the cache list in order to implement LRU replacement.
+ */
+ if( pPreStmt->pPrev ){
+ pPreStmt->pPrev->pNext = pPreStmt->pNext;
+ }else{
+ pDb->stmtList = pPreStmt->pNext;
+ }
+ if( pPreStmt->pNext ){
+ pPreStmt->pNext->pPrev = pPreStmt->pPrev;
+ }else{
+ pDb->stmtLast = pPreStmt->pPrev;
+ }
+ pDb->nStmt--;
+ break;
+ }
+ }
+
+ /* If no prepared statement was found. Compile the SQL text
+ */
+ if( pStmt==0 ){
+ if( SQLITE_OK!=sqlite3_prepare(pDb->db, zSql, -1, &pStmt, &zLeft) ){
+ Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db)));
+ rc = TCL_ERROR;
+ break;
+ }
+ if( pStmt==0 ){
+ if( SQLITE_OK!=sqlite3_errcode(pDb->db) ){
+ /* A compile-time error in the statement
+ */
+ Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db)));
+ rc = TCL_ERROR;
+ break;
+ }else{
+ /* The statement was a no-op. Continue to the next statement
+ ** in the SQL string.
+ */
+ zSql = zLeft;
+ continue;
+ }
+ }
+ assert( pPreStmt==0 );
+ }
+
+ /* Bind values to parameters that begin with $ or :
+ */
+ nVar = sqlite3_bind_parameter_count(pStmt);
+ nParm = 0;
+ if( nVar>sizeof(aParm)/sizeof(aParm[0]) ){
+ apParm = (Tcl_Obj**)Tcl_Alloc(nVar*sizeof(apParm[0]));
+ }else{
+ apParm = aParm;
+ }
+ for(i=1; i<=nVar; i++){
+ const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
+ if( zVar!=0 && (zVar[0]=='$' || zVar[0]==':') ){
+ Tcl_Obj *pVar = Tcl_GetVar2Ex(interp, &zVar[1], 0, 0);
+ if( pVar ){
+ int n;
+ u8 *data;
+ char *zType = pVar->typePtr ? pVar->typePtr->name : "";
+ char c = zType[0];
+ if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){
+ /* Only load a BLOB type if the Tcl variable is a bytearray and
+ ** has no string representation. */
+ data = Tcl_GetByteArrayFromObj(pVar, &n);
+ sqlite3_bind_blob(pStmt, i, data, n, SQLITE_STATIC);
+ Tcl_IncrRefCount(pVar);
+ apParm[nParm++] = pVar;
+ }else if( (c=='b' && strcmp(zType,"boolean")==0) ||
+ (c=='i' && strcmp(zType,"int")==0) ){
+ Tcl_GetIntFromObj(interp, pVar, &n);
+ sqlite3_bind_int(pStmt, i, n);
+ }else if( c=='d' && strcmp(zType,"double")==0 ){
+ double r;
+ Tcl_GetDoubleFromObj(interp, pVar, &r);
+ sqlite3_bind_double(pStmt, i, r);
+ }else if( c=='w' && strcmp(zType,"wideInt")==0 ){
+ Tcl_WideInt v;
+ Tcl_GetWideIntFromObj(interp, pVar, &v);
+ sqlite3_bind_int64(pStmt, i, v);
+ }else{
+ data = Tcl_GetStringFromObj(pVar, &n);
+ sqlite3_bind_text(pStmt, i, data, n, SQLITE_STATIC);
+ Tcl_IncrRefCount(pVar);
+ apParm[nParm++] = pVar;
+ }
+ }else{
+ sqlite3_bind_null( pStmt, i );
+ }
+ }
+ }
+
+ /* Compute column names */
+ nCol = sqlite3_column_count(pStmt);
+ if( pScript ){
+ apColName = (Tcl_Obj**)Tcl_Alloc( sizeof(Tcl_Obj*)*nCol );
+ if( apColName==0 ) break;
+ for(i=0; i<nCol; i++){
+ apColName[i] = dbTextToObj(sqlite3_column_name(pStmt,i));
+ Tcl_IncrRefCount(apColName[i]);
+ }
+ }
+
+ /* If results are being stored in an array variable, then create
+ ** the array(*) entry for that array
+ */
+ if( pArray ){
+ Tcl_Obj *pColList = Tcl_NewObj();
+ Tcl_Obj *pStar = Tcl_NewStringObj("*", -1);
+ Tcl_IncrRefCount(pColList);
+ for(i=0; i<nCol; i++){
+ Tcl_ListObjAppendElement(interp, pColList, apColName[i]);
+ }
+ Tcl_ObjSetVar2(interp, pArray, pStar, pColList,0);
+ Tcl_DecrRefCount(pColList);
+ Tcl_DecrRefCount(pStar);
+ }
+
+ /* Execute the SQL
+ */
+ while( rc==TCL_OK && pStmt && SQLITE_ROW==sqlite3_step(pStmt) ){
+ for(i=0; i<nCol; i++){
+ Tcl_Obj *pVal;
+
+ /* Set pVal to contain the i'th column of this row. */
+ switch( sqlite3_column_type(pStmt, i) ){
+ case SQLITE_BLOB: {
+ int bytes = sqlite3_column_bytes(pStmt, i);
+ pVal = Tcl_NewByteArrayObj(sqlite3_column_blob(pStmt, i), bytes);
+ break;
+ }
+ case SQLITE_INTEGER: {
+ sqlite_int64 v = sqlite3_column_int64(pStmt, i);
+ if( v>=-2147483647 && v<=2147483647 ){
+ pVal = Tcl_NewIntObj(v);
+ }else{
+ pVal = Tcl_NewWideIntObj(v);
+ }
+ break;
+ }
+ case SQLITE_FLOAT: {
+ double r = sqlite3_column_double(pStmt, i);
+ pVal = Tcl_NewDoubleObj(r);
+ break;
+ }
+ case SQLITE_NULL: {
+ pVal = dbTextToObj(pDb->zNull);
+ break;
+ }
+ default: {
+ pVal = dbTextToObj(sqlite3_column_text(pStmt, i));
+ break;
+ }
+ }
+
+ if( pScript ){
+ if( pArray==0 ){
+ Tcl_ObjSetVar2(interp, apColName[i], 0, pVal, 0);
+ }else{
+ Tcl_ObjSetVar2(interp, pArray, apColName[i], pVal, 0);
+ }
+ }else if( choice==DB_ONECOLUMN ){
+ if( pRet==0 ){
+ pRet = pVal;
+ Tcl_IncrRefCount(pRet);
+ }
+ rc = TCL_BREAK;
+ }else{
+ Tcl_ListObjAppendElement(interp, pRet, pVal);
+ }
+ }
+
+ if( pScript ){
+ rc = Tcl_EvalObjEx(interp, pScript, 0);
+ if( rc==TCL_CONTINUE ){
+ rc = TCL_OK;
+ }
+ }
+ }
+ if( rc==TCL_BREAK ){
+ rc = TCL_OK;
+ }
+
+ /* Free the column name objects */
+ if( pScript ){
+ for(i=0; i<nCol; i++){
+ Tcl_DecrRefCount(apColName[i]);
+ }
+ Tcl_Free((char*)apColName);
+ }
+
+ /* Free the bound string and blob parameters */
+ for(i=0; i<nParm; i++){
+ Tcl_DecrRefCount(apParm[i]);
+ }
+ if( apParm!=aParm ){
+ Tcl_Free((char*)apParm);
+ }
+
+ /* Reset the statement. If the result code is SQLITE_SCHEMA, then
+ ** flush the statement cache and try the statement again.
+ */
+ rc2 = sqlite3_reset(pStmt);
+ if( SQLITE_SCHEMA==rc2 ){
+ /* After a schema change, flush the cache and try to run the
+ ** statement again
+ */
+ flushStmtCache( pDb );
+ sqlite3_finalize(pStmt);
+ if( pPreStmt ) Tcl_Free((char*)pPreStmt);
+ continue;
+ }else if( SQLITE_OK!=rc2 ){
+ /* If a run-time error occurs, report the error and stop reading
+ ** the SQL
+ */
+ Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db)));
+ sqlite3_finalize(pStmt);
+ rc = TCL_ERROR;
+ if( pPreStmt ) Tcl_Free((char*)pPreStmt);
+ break;
+ }else if( pDb->maxStmt<=0 ){
+ /* If the cache is turned off, deallocated the statement */
+ if( pPreStmt ) Tcl_Free((char*)pPreStmt);
+ sqlite3_finalize(pStmt);
+ }else{
+ /* Everything worked and the cache is operational.
+ ** Create a new SqlPreparedStmt structure if we need one.
+ ** (If we already have one we can just reuse it.)
+ */
+ if( pPreStmt==0 ){
+ len = zLeft - zSql;
+ pPreStmt = (SqlPreparedStmt*)Tcl_Alloc( sizeof(*pPreStmt) + len );
+ if( pPreStmt==0 ) return TCL_ERROR;
+ pPreStmt->pStmt = pStmt;
+ pPreStmt->nSql = len;
+ memcpy(pPreStmt->zSql, zSql, len);
+ pPreStmt->zSql[len] = 0;
+ }
+
+ /* Add the prepared statement to the beginning of the cache list
+ */
+ pPreStmt->pNext = pDb->stmtList;
+ pPreStmt->pPrev = 0;
+ if( pDb->stmtList ){
+ pDb->stmtList->pPrev = pPreStmt;
+ }
+ pDb->stmtList = pPreStmt;
+ if( pDb->stmtLast==0 ){
+ assert( pDb->nStmt==0 );
+ pDb->stmtLast = pPreStmt;
+ }else{
+ assert( pDb->nStmt>0 );
+ }
+ pDb->nStmt++;
+
+ /* If we have too many statement in cache, remove the surplus from the
+ ** end of the cache list.
+ */
+ while( pDb->nStmt>pDb->maxStmt ){
+ sqlite3_finalize(pDb->stmtLast->pStmt);
+ pDb->stmtLast = pDb->stmtLast->pPrev;
+ Tcl_Free((char*)pDb->stmtLast->pNext);
+ pDb->stmtLast->pNext = 0;
+ pDb->nStmt--;
+ }
+ }
+
+ /* Proceed to the next statement */
+ zSql = zLeft;
+ }
+ Tcl_DecrRefCount(objv[2]);
+
+ if( pRet ){
+ if( rc==TCL_OK ){
+ Tcl_SetObjResult(interp, pRet);
+ }
+ Tcl_DecrRefCount(pRet);
+ }
+ break;
+ }
+
+ /*
+ ** $db function NAME SCRIPT
+ **
+ ** Create a new SQL function called NAME. Whenever that function is
+ ** called, invoke SCRIPT to evaluate the function.
+ */
+ case DB_FUNCTION: {
+ SqlFunc *pFunc;
+ Tcl_Obj *pScript;
+ char *zName;
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "NAME SCRIPT");
+ return TCL_ERROR;
+ }
+ zName = Tcl_GetStringFromObj(objv[2], 0);
+ pScript = objv[3];
+ pFunc = findSqlFunc(pDb, zName);
+ if( pFunc==0 ) return TCL_ERROR;
+ if( pFunc->pScript ){
+ Tcl_DecrRefCount(pFunc->pScript);
+ }
+ pFunc->pScript = pScript;
+ Tcl_IncrRefCount(pScript);
+ pFunc->useEvalObjv = safeToUseEvalObjv(interp, pScript);
+ rc = sqlite3_create_function(pDb->db, zName, -1, SQLITE_UTF8,
+ pFunc, tclSqlFunc, 0, 0);
+ if( rc!=SQLITE_OK ){
+ rc = TCL_ERROR;
+ Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE);
+ }else{
+ /* Must flush any cached statements */
+ flushStmtCache( pDb );
+ }
+ break;
+ }
+
+ /*
+ ** $db nullvalue ?STRING?
+ **
+ ** Change text used when a NULL comes back from the database. If ?STRING?
+ ** is not present, then the current string used for NULL is returned.
+ ** If STRING is present, then STRING is returned.
+ **
+ */
+ case DB_NULLVALUE: {
+ if( objc!=2 && objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "NULLVALUE");
+ return TCL_ERROR;
+ }
+ if( objc==3 ){
+ int len;
+ char *zNull = Tcl_GetStringFromObj(objv[2], &len);
+ if( pDb->zNull ){
+ Tcl_Free(pDb->zNull);
+ }
+ if( zNull && len>0 ){
+ pDb->zNull = Tcl_Alloc( len + 1 );
+ strncpy(pDb->zNull, zNull, len);
+ pDb->zNull[len] = '\0';
+ }else{
+ pDb->zNull = 0;
+ }
+ }
+ Tcl_SetObjResult(interp, dbTextToObj(pDb->zNull));
+ break;
+ }
+
+ /*
+ ** $db last_insert_rowid
+ **
+ ** Return an integer which is the ROWID for the most recent insert.
+ */
+ case DB_LAST_INSERT_ROWID: {
+ Tcl_Obj *pResult;
+ int rowid;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ rowid = sqlite3_last_insert_rowid(pDb->db);
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetIntObj(pResult, rowid);
+ break;
+ }
+
+ /*
+ ** The DB_ONECOLUMN method is implemented together with DB_EVAL.
+ */
+
+ /* $db progress ?N CALLBACK?
+ **
+ ** Invoke the given callback every N virtual machine opcodes while executing
+ ** queries.
+ */
+ case DB_PROGRESS: {
+ if( objc==2 ){
+ if( pDb->zProgress ){
+ Tcl_AppendResult(interp, pDb->zProgress, 0);
+ }
+ }else if( objc==4 ){
+ char *zProgress;
+ int len;
+ int N;
+ if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &N) ){
+ return TCL_ERROR;
+ };
+ if( pDb->zProgress ){
+ Tcl_Free(pDb->zProgress);
+ }
+ zProgress = Tcl_GetStringFromObj(objv[3], &len);
+ if( zProgress && len>0 ){
+ pDb->zProgress = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zProgress, zProgress);
+ }else{
+ pDb->zProgress = 0;
+ }
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ if( pDb->zProgress ){
+ pDb->interp = interp;
+ sqlite3_progress_handler(pDb->db, N, DbProgressHandler, pDb);
+ }else{
+ sqlite3_progress_handler(pDb->db, 0, 0, 0);
+ }
+#endif
+ }else{
+ Tcl_WrongNumArgs(interp, 2, objv, "N CALLBACK");
+ return TCL_ERROR;
+ }
+ break;
+ }
+
+ /* $db profile ?CALLBACK?
+ **
+ ** Make arrangements to invoke the CALLBACK routine after each SQL statement
+ ** that has run. The text of the SQL and the amount of elapse time are
+ ** appended to CALLBACK before the script is run.
+ */
+ case DB_PROFILE: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+ return TCL_ERROR;
+ }else if( objc==2 ){
+ if( pDb->zProfile ){
+ Tcl_AppendResult(interp, pDb->zProfile, 0);
+ }
+ }else{
+ char *zProfile;
+ int len;
+ if( pDb->zProfile ){
+ Tcl_Free(pDb->zProfile);
+ }
+ zProfile = Tcl_GetStringFromObj(objv[2], &len);
+ if( zProfile && len>0 ){
+ pDb->zProfile = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zProfile, zProfile);
+ }else{
+ pDb->zProfile = 0;
+ }
+#ifndef SQLITE_OMIT_TRACE
+ if( pDb->zProfile ){
+ pDb->interp = interp;
+ sqlite3_profile(pDb->db, DbProfileHandler, pDb);
+ }else{
+ sqlite3_profile(pDb->db, 0, 0);
+ }
+#endif
+ }
+ break;
+ }
+
+ /*
+ ** $db rekey KEY
+ **
+ ** Change the encryption key on the currently open database.
+ */
+ case DB_REKEY: {
+ int nKey;
+ void *pKey;
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "KEY");
+ return TCL_ERROR;
+ }
+ pKey = Tcl_GetByteArrayFromObj(objv[2], &nKey);
+#ifdef SQLITE_HAS_CODEC
+ rc = sqlite3_rekey(pDb->db, pKey, nKey);
+ if( rc ){
+ Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
+ rc = TCL_ERROR;
+ }
+#endif
+ break;
+ }
+
+ /*
+ ** $db timeout MILLESECONDS
+ **
+ ** Delay for the number of milliseconds specified when a file is locked.
+ */
+ case DB_TIMEOUT: {
+ int ms;
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "MILLISECONDS");
+ return TCL_ERROR;
+ }
+ if( Tcl_GetIntFromObj(interp, objv[2], &ms) ) return TCL_ERROR;
+ sqlite3_busy_timeout(pDb->db, ms);
+ break;
+ }
+
+ /*
+ ** $db total_changes
+ **
+ ** Return the number of rows that were modified, inserted, or deleted
+ ** since the database handle was created.
+ */
+ case DB_TOTAL_CHANGES: {
+ Tcl_Obj *pResult;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ pResult = Tcl_GetObjResult(interp);
+ Tcl_SetIntObj(pResult, sqlite3_total_changes(pDb->db));
+ break;
+ }
+
+ /* $db trace ?CALLBACK?
+ **
+ ** Make arrangements to invoke the CALLBACK routine for each SQL statement
+ ** that is executed. The text of the SQL is appended to CALLBACK before
+ ** it is executed.
+ */
+ case DB_TRACE: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+ return TCL_ERROR;
+ }else if( objc==2 ){
+ if( pDb->zTrace ){
+ Tcl_AppendResult(interp, pDb->zTrace, 0);
+ }
+ }else{
+ char *zTrace;
+ int len;
+ if( pDb->zTrace ){
+ Tcl_Free(pDb->zTrace);
+ }
+ zTrace = Tcl_GetStringFromObj(objv[2], &len);
+ if( zTrace && len>0 ){
+ pDb->zTrace = Tcl_Alloc( len + 1 );
+ strcpy(pDb->zTrace, zTrace);
+ }else{
+ pDb->zTrace = 0;
+ }
+#ifndef SQLITE_OMIT_TRACE
+ if( pDb->zTrace ){
+ pDb->interp = interp;
+ sqlite3_trace(pDb->db, DbTraceHandler, pDb);
+ }else{
+ sqlite3_trace(pDb->db, 0, 0);
+ }
+#endif
+ }
+ break;
+ }
+
+ /* $db transaction [-deferred|-immediate|-exclusive] SCRIPT
+ **
+ ** Start a new transaction (if we are not already in the midst of a
+ ** transaction) and execute the TCL script SCRIPT. After SCRIPT
+ ** completes, either commit the transaction or roll it back if SCRIPT
+ ** throws an exception. Or if no new transation was started, do nothing.
+ ** pass the exception on up the stack.
+ **
+ ** This command was inspired by Dave Thomas's talk on Ruby at the
+ ** 2005 O'Reilly Open Source Convention (OSCON).
+ */
+ case DB_TRANSACTION: {
+ int inTrans;
+ Tcl_Obj *pScript;
+ const char *zBegin = "BEGIN";
+ if( objc!=3 && objc!=4 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "[TYPE] SCRIPT");
+ return TCL_ERROR;
+ }
+ if( objc==3 ){
+ pScript = objv[2];
+ } else {
+ static const char *TTYPE_strs[] = {
+ "deferred", "exclusive", "immediate", 0
+ };
+ enum TTYPE_enum {
+ TTYPE_DEFERRED, TTYPE_EXCLUSIVE, TTYPE_IMMEDIATE
+ };
+ int ttype;
+ if( Tcl_GetIndexFromObj(interp, objv[2], TTYPE_strs, "transaction type",
+ 0, &ttype) ){
+ return TCL_ERROR;
+ }
+ switch( (enum TTYPE_enum)ttype ){
+ case TTYPE_DEFERRED: /* no-op */; break;
+ case TTYPE_EXCLUSIVE: zBegin = "BEGIN EXCLUSIVE"; break;
+ case TTYPE_IMMEDIATE: zBegin = "BEGIN IMMEDIATE"; break;
+ }
+ pScript = objv[3];
+ }
+ inTrans = !sqlite3_get_autocommit(pDb->db);
+ if( !inTrans ){
+ sqlite3_exec(pDb->db, zBegin, 0, 0, 0);
+ }
+ rc = Tcl_EvalObjEx(interp, pScript, 0);
+ if( !inTrans ){
+ const char *zEnd;
+ if( rc==TCL_ERROR ){
+ zEnd = "ROLLBACK";
+ } else {
+ zEnd = "COMMIT";
+ }
+ sqlite3_exec(pDb->db, zEnd, 0, 0, 0);
+ }
+ break;
+ }
+
+ /* $db version
+ **
+ ** Return the version string for this database.
+ */
+ case DB_VERSION: {
+ Tcl_SetResult(interp, (char *)sqlite3_libversion(), TCL_STATIC);
+ break;
+ }
+
+
+ } /* End of the SWITCH statement */
+ return rc;
+}
+
+/*
+** sqlite3 DBNAME FILENAME ?MODE? ?-key KEY?
+**
+** This is the main Tcl command. When the "sqlite" Tcl command is
+** invoked, this routine runs to process that command.
+**
+** The first argument, DBNAME, is an arbitrary name for a new
+** database connection. This command creates a new command named
+** DBNAME that is used to control that connection. The database
+** connection is deleted when the DBNAME command is deleted.
+**
+** The second argument is the name of the directory that contains
+** the sqlite database that is to be accessed.
+**
+** For testing purposes, we also support the following:
+**
+** sqlite3 -encoding
+**
+** Return the encoding used by LIKE and GLOB operators. Choices
+** are UTF-8 and iso8859.
+**
+** sqlite3 -version
+**
+** Return the version number of the SQLite library.
+**
+** sqlite3 -tcl-uses-utf
+**
+** Return "1" if compiled with a Tcl uses UTF-8. Return "0" if
+** not. Used by tests to make sure the library was compiled
+** correctly.
+*/
+static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
+ SqliteDb *p;
+ void *pKey = 0;
+ int nKey = 0;
+ const char *zArg;
+ char *zErrMsg;
+ const char *zFile;
+ char zBuf[80];
+ if( objc==2 ){
+ zArg = Tcl_GetStringFromObj(objv[1], 0);
+ if( strcmp(zArg,"-version")==0 ){
+ Tcl_AppendResult(interp,sqlite3_version,0);
+ return TCL_OK;
+ }
+ if( strcmp(zArg,"-has-codec")==0 ){
+#ifdef SQLITE_HAS_CODEC
+ Tcl_AppendResult(interp,"1",0);
+#else
+ Tcl_AppendResult(interp,"0",0);
+#endif
+ return TCL_OK;
+ }
+ if( strcmp(zArg,"-tcl-uses-utf")==0 ){
+#ifdef TCL_UTF_MAX
+ Tcl_AppendResult(interp,"1",0);
+#else
+ Tcl_AppendResult(interp,"0",0);
+#endif
+ return TCL_OK;
+ }
+ }
+ if( objc==5 || objc==6 ){
+ zArg = Tcl_GetStringFromObj(objv[objc-2], 0);
+ if( strcmp(zArg,"-key")==0 ){
+ pKey = Tcl_GetByteArrayFromObj(objv[objc-1], &nKey);
+ objc -= 2;
+ }
+ }
+ if( objc!=3 && objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv,
+#ifdef SQLITE_HAS_CODEC
+ "HANDLE FILENAME ?-key CODEC-KEY?"
+#else
+ "HANDLE FILENAME ?MODE?"
+#endif
+ );
+ return TCL_ERROR;
+ }
+ zErrMsg = 0;
+ p = (SqliteDb*)Tcl_Alloc( sizeof(*p) );
+ if( p==0 ){
+ Tcl_SetResult(interp, "malloc failed", TCL_STATIC);
+ return TCL_ERROR;
+ }
+ memset(p, 0, sizeof(*p));
+ zFile = Tcl_GetStringFromObj(objv[2], 0);
+ sqlite3_open(zFile, &p->db);
+ if( SQLITE_OK!=sqlite3_errcode(p->db) ){
+ zErrMsg = strdup(sqlite3_errmsg(p->db));
+ sqlite3_close(p->db);
+ p->db = 0;
+ }
+#ifdef SQLITE_HAS_CODEC
+ sqlite3_key(p->db, pKey, nKey);
+#endif
+ if( p->db==0 ){
+ Tcl_SetResult(interp, zErrMsg, TCL_VOLATILE);
+ Tcl_Free((char*)p);
+ free(zErrMsg);
+ return TCL_ERROR;
+ }
+ p->maxStmt = NUM_PREPARED_STMTS;
+ zArg = Tcl_GetStringFromObj(objv[1], 0);
+ Tcl_CreateObjCommand(interp, zArg, DbObjCmd, (char*)p, DbDeleteCmd);
+
+ /* The return value is the value of the sqlite* pointer
+ */
+ sprintf(zBuf, "%p", p->db);
+ if( strncmp(zBuf,"0x",2) ){
+ sprintf(zBuf, "0x%p", p->db);
+ }
+ Tcl_AppendResult(interp, zBuf, 0);
+
+ /* If compiled with SQLITE_TEST turned on, then register the "md5sum"
+ ** SQL function.
+ */
+#ifdef SQLITE_TEST
+ {
+ extern void Md5_Register(sqlite3*);
+#ifdef SQLITE_MEMDEBUG
+ int mallocfail = sqlite3_iMallocFail;
+ sqlite3_iMallocFail = 0;
+#endif
+ Md5_Register(p->db);
+#ifdef SQLITE_MEMDEBUG
+ sqlite3_iMallocFail = mallocfail;
+#endif
+ }
+#endif
+ p->interp = interp;
+ return TCL_OK;
+}
+
+/*
+** Provide a dummy Tcl_InitStubs if we are using this as a static
+** library.
+*/
+#ifndef USE_TCL_STUBS
+# undef Tcl_InitStubs
+# define Tcl_InitStubs(a,b,c)
+#endif
+
+/*
+** Initialize this module.
+**
+** This Tcl module contains only a single new Tcl command named "sqlite".
+** (Hence there is no namespace. There is no point in using a namespace
+** if the extension only supplies one new name!) The "sqlite" command is
+** used to open a new SQLite database. See the DbMain() routine above
+** for additional information.
+*/
+int Sqlite3_Init(Tcl_Interp *interp){
+ Tcl_InitStubs(interp, "8.4", 0);
+ Tcl_CreateObjCommand(interp, "sqlite3", (Tcl_ObjCmdProc*)DbMain, 0, 0);
+ Tcl_PkgProvide(interp, "sqlite3", "3.0");
+ Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0);
+ Tcl_PkgProvide(interp, "sqlite", "3.0");
+ return TCL_OK;
+}
+int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
+int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
+int Tclsqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
+
+#ifndef SQLITE_3_SUFFIX_ONLY
+int Sqlite_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
+int Tclsqlite_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
+int Sqlite_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
+int Tclsqlite_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
+#endif
+
+#ifdef TCLSH
+/*****************************************************************************
+** The code that follows is used to build standalone TCL interpreters
+*/
+
+/*
+** If the macro TCLSH is one, then put in code this for the
+** "main" routine that will initialize Tcl and take input from
+** standard input.
+*/
+#if TCLSH==1
+static char zMainloop[] =
+ "set line {}\n"
+ "while {![eof stdin]} {\n"
+ "if {$line!=\"\"} {\n"
+ "puts -nonewline \"> \"\n"
+ "} else {\n"
+ "puts -nonewline \"% \"\n"
+ "}\n"
+ "flush stdout\n"
+ "append line [gets stdin]\n"
+ "if {[info complete $line]} {\n"
+ "if {[catch {uplevel #0 $line} result]} {\n"
+ "puts stderr \"Error: $result\"\n"
+ "} elseif {$result!=\"\"} {\n"
+ "puts $result\n"
+ "}\n"
+ "set line {}\n"
+ "} else {\n"
+ "append line \\n\n"
+ "}\n"
+ "}\n"
+;
+#endif
+
+/*
+** If the macro TCLSH is two, then get the main loop code out of
+** the separate file "spaceanal_tcl.h".
+*/
+#if TCLSH==2
+static char zMainloop[] =
+#include "spaceanal_tcl.h"
+;
+#endif
+
+#define TCLSH_MAIN main /* Needed to fake out mktclapp */
+int TCLSH_MAIN(int argc, char **argv){
+ Tcl_Interp *interp;
+ Tcl_FindExecutable(argv[0]);
+ interp = Tcl_CreateInterp();
+ Sqlite3_Init(interp);
+#ifdef SQLITE_TEST
+ {
+ extern int Sqlitetest1_Init(Tcl_Interp*);
+ extern int Sqlitetest2_Init(Tcl_Interp*);
+ extern int Sqlitetest3_Init(Tcl_Interp*);
+ extern int Sqlitetest4_Init(Tcl_Interp*);
+ extern int Sqlitetest5_Init(Tcl_Interp*);
+ extern int Md5_Init(Tcl_Interp*);
+ extern int Sqlitetestsse_Init(Tcl_Interp*);
+
+ Sqlitetest1_Init(interp);
+ Sqlitetest2_Init(interp);
+ Sqlitetest3_Init(interp);
+ Sqlitetest4_Init(interp);
+ Sqlitetest5_Init(interp);
+ Md5_Init(interp);
+#ifdef SQLITE_SSE
+ Sqlitetestsse_Init(interp);
+#endif
+ }
+#endif
+ if( argc>=2 || TCLSH==2 ){
+ int i;
+ Tcl_SetVar(interp,"argv0",argv[1],TCL_GLOBAL_ONLY);
+ Tcl_SetVar(interp,"argv", "", TCL_GLOBAL_ONLY);
+ for(i=3-TCLSH; i<argc; i++){
+ Tcl_SetVar(interp, "argv", argv[i],
+ TCL_GLOBAL_ONLY | TCL_LIST_ELEMENT | TCL_APPEND_VALUE);
+ }
+ if( TCLSH==1 && Tcl_EvalFile(interp, argv[1])!=TCL_OK ){
+ const char *zInfo = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
+ if( zInfo==0 ) zInfo = interp->result;
+ fprintf(stderr,"%s: %s\n", *argv, zInfo);
+ return 1;
+ }
+ }
+ if( argc<=1 || TCLSH==2 ){
+ Tcl_GlobalEval(interp, zMainloop);
+ }
+ return 0;
+}
+#endif /* TCLSH */
+
+#endif /* !defined(NO_TCL) */
diff --git a/kexi/3rdparty/kexisql3/src/tokenize.c b/kexi/3rdparty/kexisql3/src/tokenize.c
new file mode 100644
index 000000000..229f1bdea
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/tokenize.c
@@ -0,0 +1,433 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+#include <stdlib.h>
+
+/*
+** 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"
+
+
+/*
+** If X is a character that can be used in an identifier and
+** X&0x80==0 then sqlite3IsIdChar[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). sqlite3IsIdChar[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.
+**
+** 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.
+*/
+const char sqlite3IsIdChar[] = {
+/* 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 && sqlite3IsIdChar[c-0x20]))
+
+/*
+** Return the length of the token that begins at z[0].
+** Store the token type in *tokenType before returning.
+*/
+static int getToken(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 ) i++;
+ *tokenType = TK_STRING;
+ 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
+ return i;
+ }
+ case '[': {
+ for(i=1, c=z[0]; c!=']' && (c=z[i])!=0; i++){}
+ *tokenType = TK_ID;
+ 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 ':': {
+ 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( (c=z[1])=='\'' || c=='"' ){
+ int delim = c;
+ *tokenType = TK_BLOB;
+ for(i=2; (c=z[i])!=0; i++){
+ if( c==delim ){
+ if( i%2 ) *tokenType = TK_ILLEGAL;
+ break;
+ }
+ if( !isxdigit(c) ){
+ *tokenType = TK_ILLEGAL;
+ return i;
+ }
+ }
+ 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;
+}
+int sqlite3GetToken(const unsigned char *z, int *tokenType){
+ return getToken(z, tokenType);
+}
+
+/*
+** 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 sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
+ int nErr = 0;
+ int i;
+ void *pEngine;
+ int tokenType;
+ int lastTokenParsed = -1;
+ sqlite3 *db = pParse->db;
+ extern void *sqlite3ParserAlloc(void*(*)(int));
+ extern void sqlite3ParserFree(void*, void(*)(void*));
+ extern int sqlite3Parser(void*, int, Token, Parse*);
+
+ db->flags &= ~SQLITE_Interrupt;
+ pParse->rc = SQLITE_OK;
+ i = 0;
+ pEngine = sqlite3ParserAlloc((void*(*)(int))sqlite3MallocX);
+ if( pEngine==0 ){
+ sqlite3SetString(pzErrMsg, "out of memory", (char*)0);
+ 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 );
+ pParse->zTail = pParse->zSql = zSql;
+ while( sqlite3_malloc_failed==0 && zSql[i]!=0 ){
+ assert( i>=0 );
+ pParse->sLastToken.z = &zSql[i];
+ assert( pParse->sLastToken.dyn==0 );
+ pParse->sLastToken.n = getToken((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;
+ sqlite3SetString(pzErrMsg, "interrupt", (char*)0);
+ goto abort_parse;
+ }
+ break;
+ }
+ case TK_ILLEGAL: {
+ if( pzErrMsg ){
+ sqliteFree(*pzErrMsg);
+ *pzErrMsg = sqlite3MPrintf("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, sqlite3FreeX);
+ if( sqlite3_malloc_failed ){
+ 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{
+ sqliteFree(pParse->zErrMsg);
+ }
+ pParse->zErrMsg = 0;
+ if( !nErr ) nErr++;
+ }
+ if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){
+ sqlite3VdbeDelete(pParse->pVdbe);
+ pParse->pVdbe = 0;
+ }
+ sqlite3DeleteTable(pParse->db, pParse->pNewTable);
+ sqlite3DeleteTrigger(pParse->pNewTrigger);
+ sqliteFree(pParse->apVarExpr);
+ if( nErr>0 && (pParse->rc==SQLITE_OK || pParse->rc==SQLITE_DONE) ){
+ pParse->rc = SQLITE_ERROR;
+ }
+ return nErr;
+}
diff --git a/kexi/3rdparty/kexisql3/src/trigger.c b/kexi/3rdparty/kexisql3/src/trigger.c
new file mode 100644
index 000000000..f39d2bd83
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/trigger.c
@@ -0,0 +1,802 @@
+/*
+**
+** 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"
+
+#ifndef SQLITE_OMIT_TRIGGER
+/*
+** Delete a linked list of TriggerStep structures.
+*/
+void sqlite3DeleteTriggerStep(TriggerStep *pTriggerStep){
+ while( pTriggerStep ){
+ TriggerStep * pTmp = pTriggerStep;
+ pTriggerStep = pTriggerStep->pNext;
+
+ if( pTmp->target.dyn ) sqliteFree((char*)pTmp->target.z);
+ sqlite3ExprDelete(pTmp->pWhere);
+ sqlite3ExprListDelete(pTmp->pExprList);
+ sqlite3SelectDelete(pTmp->pSelect);
+ sqlite3IdListDelete(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
+** sqlite3FinishTrigger() function is called to complete the trigger
+** construction process.
+*/
+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 */
+ int foreach, /* One of TK_ROW or TK_STATEMENT */
+ Expr *pWhen, /* WHEN clause */
+ int isTemp /* True if the TEMPORARY keyword is present */
+){
+ 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;
+
+ if( isTemp ){
+ /* If TEMP was specified, then the trigger name may not be qualified. */
+ if( pName2 && 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 || sqlite3_malloc_failed ) goto trigger_cleanup;
+ pTab = sqlite3SrcListLookup(pParse, pTableName);
+ if( pName2->n==0 && pTab && pTab->iDb==1 ){
+ iDb = 1;
+ }
+
+ /* Ensure the table name matches database name and that the table exists */
+ if( sqlite3_malloc_failed ) 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;
+ }
+
+ /* Check that the trigger name is not reserved and that no trigger of the
+ ** specified name exists */
+ zName = sqlite3NameFromToken(pName);
+ if( !zName || SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
+ goto trigger_cleanup;
+ }
+ if( sqlite3HashFind(&(db->aDb[iDb].trigHash), zName,pName->n+1) ){
+ 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;
+ }
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ {
+ int code = SQLITE_CREATE_TRIGGER;
+ const char *zDb = db->aDb[pTab->iDb].zName;
+ const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb;
+ if( pTab->iDb==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(pTab->iDb),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*)sqliteMalloc(sizeof(Trigger));
+ if( pTrigger==0 ) goto trigger_cleanup;
+ pTrigger->name = zName;
+ zName = 0;
+ pTrigger->table = sqliteStrDup(pTableName->a[0].zName);
+ pTrigger->iDb = iDb;
+ pTrigger->iTabDb = pTab->iDb;
+ pTrigger->op = op;
+ pTrigger->tr_tm = tr_tm==TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER;
+ pTrigger->pWhen = sqlite3ExprDup(pWhen);
+ pTrigger->pColumns = sqlite3IdListDup(pColumns);
+ pTrigger->foreach = foreach;
+ sqlite3TokenCopy(&pTrigger->nameToken,pName);
+ assert( pParse->pNewTrigger==0 );
+ pParse->pNewTrigger = pTrigger;
+
+trigger_cleanup:
+ sqliteFree(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.
+*/
+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;
+
+ pTrig = pParse->pNewTrigger;
+ pParse->pNewTrigger = 0;
+ if( pParse->nErr || pTrig==0 ) goto triggerfinish_cleanup;
+ pTrig->step_list = pStepList;
+ while( pStepList ){
+ pStepList->pTrig = pTrig;
+ pStepList = pStepList->pNext;
+ }
+ if( sqlite3FixInit(&sFix, pParse, pTrig->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 ){
+ static const VdbeOpList insertTrig[] = {
+ { OP_NewRowid, 0, 0, 0 },
+ { OP_String8, 0, 0, "trigger" },
+ { OP_String8, 0, 0, 0 }, /* 2: trigger name */
+ { OP_String8, 0, 0, 0 }, /* 3: table name */
+ { OP_Integer, 0, 0, 0 },
+ { OP_String8, 0, 0, "CREATE TRIGGER "},
+ { OP_String8, 0, 0, 0 }, /* 6: SQL */
+ { OP_Concat, 0, 0, 0 },
+ { OP_MakeRecord, 5, 0, "tttit" },
+ { OP_Insert, 0, 0, 0 },
+ };
+ int addr;
+ Vdbe *v;
+
+ /* Make an entry in the sqlite_master table */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto triggerfinish_cleanup;
+ sqlite3BeginWriteOperation(pParse, 0, pTrig->iDb);
+ sqlite3OpenMasterTable(v, pTrig->iDb);
+ addr = sqlite3VdbeAddOpList(v, ArraySize(insertTrig), insertTrig);
+ sqlite3VdbeChangeP3(v, addr+2, pTrig->name, 0);
+ sqlite3VdbeChangeP3(v, addr+3, pTrig->table, 0);
+ sqlite3VdbeChangeP3(v, addr+6, pAll->z, pAll->n);
+ sqlite3ChangeCookie(db, v, pTrig->iDb);
+ sqlite3VdbeAddOp(v, OP_Close, 0, 0);
+ sqlite3VdbeOp3(v, OP_ParseSchema, pTrig->iDb, 0,
+ sqlite3MPrintf("type='trigger' AND name='%q'", pTrig->name), P3_DYNAMIC);
+ }
+
+ if( db->init.busy ){
+ Table *pTab;
+ Trigger *pDel;
+ pDel = sqlite3HashInsert(&db->aDb[pTrig->iDb].trigHash,
+ pTrig->name, strlen(pTrig->name)+1, pTrig);
+ if( pDel ){
+ assert( sqlite3_malloc_failed && pDel==pTrig );
+ goto triggerfinish_cleanup;
+ }
+ pTab = sqlite3LocateTable(pParse,pTrig->table,db->aDb[pTrig->iTabDb].zName);
+ 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 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 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(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 = sqlite3SelectDup(p->pSelect);
+ sqlite3SelectDelete(p->pSelect);
+ p->pSelect = pNew;
+ }
+ if( p->pWhere ){
+ Expr *pNew = sqlite3ExprDup(p->pWhere);
+ sqlite3ExprDelete(p->pWhere);
+ p->pWhere = pNew;
+ }
+ if( p->pExprList ){
+ ExprList *pNew = sqlite3ExprListDup(p->pExprList);
+ sqlite3ExprListDelete(p->pExprList);
+ p->pExprList = pNew;
+ }
+ if( p->pIdList ){
+ IdList *pNew = sqlite3IdListDup(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.
+*/
+TriggerStep *sqlite3TriggerSelectStep(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 *sqlite3TriggerInsertStep(
+ 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));
+
+ assert(pEList == 0 || pSelect == 0);
+ assert(pEList != 0 || pSelect != 0);
+
+ if( pTriggerStep ){
+ pTriggerStep->op = TK_INSERT;
+ pTriggerStep->pSelect = pSelect;
+ pTriggerStep->target = *pTableName;
+ pTriggerStep->pIdList = pColumn;
+ pTriggerStep->pExprList = pEList;
+ pTriggerStep->orconf = orconf;
+ sqlitePersistTriggerStep(pTriggerStep);
+ }else{
+ sqlite3IdListDelete(pColumn);
+ sqlite3ExprListDelete(pEList);
+ sqlite3SelectDup(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.
+*/
+TriggerStep *sqlite3TriggerUpdateStep(
+ 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 *sqlite3TriggerDeleteStep(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 sqlite3DeleteTrigger(Trigger *pTrigger){
+ if( pTrigger==0 ) return;
+ sqlite3DeleteTriggerStep(pTrigger->step_list);
+ sqliteFree(pTrigger->name);
+ sqliteFree(pTrigger->table);
+ sqlite3ExprDelete(pTrigger->pWhen);
+ sqlite3IdListDelete(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 sqlite3DropTriggerPtr() routine does the
+** same job as this routine except it takes a pointer to the trigger
+** instead of the trigger name.
+**/
+void sqlite3DropTrigger(Parse *pParse, SrcList *pName){
+ Trigger *pTrigger = 0;
+ int i;
+ const char *zDb;
+ const char *zName;
+ int nName;
+ sqlite3 *db = pParse->db;
+
+ if( sqlite3_malloc_failed ) 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].trigHash), zName, nName+1);
+ if( pTrigger ) break;
+ }
+ if( !pTrigger ){
+ sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0);
+ goto drop_trigger_cleanup;
+ }
+ sqlite3DropTriggerPtr(pParse, pTrigger, 0);
+
+drop_trigger_cleanup:
+ sqlite3SrcListDelete(pName);
+}
+
+/*
+** Return a pointer to the Table structure for the table that a trigger
+** is set on.
+*/
+static Table *tableOfTrigger(sqlite3 *db, Trigger *pTrigger){
+ return sqlite3FindTable(db,pTrigger->table,db->aDb[pTrigger->iTabDb].zName);
+}
+
+
+/*
+** 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 sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger, int nested){
+ Table *pTable;
+ Vdbe *v;
+ sqlite3 *db = pParse->db;
+ int iDb;
+
+ iDb = pTrigger->iDb;
+ assert( iDb>=0 && iDb<db->nDb );
+ pTable = tableOfTrigger(db, pTrigger);
+ assert(pTable);
+ assert( pTable->iDb==iDb || 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.
+ */
+ if( pTable!=0 && (v = sqlite3GetVdbe(pParse))!=0 ){
+ int base;
+ static const VdbeOpList dropTrigger[] = {
+ { OP_Rewind, 0, ADDR(9), 0},
+ { OP_String8, 0, 0, 0}, /* 1 */
+ { OP_Column, 0, 1, 0},
+ { OP_Ne, 0, ADDR(8), 0},
+ { OP_String8, 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 */
+ };
+
+ sqlite3BeginWriteOperation(pParse, 0, iDb);
+ sqlite3OpenMasterTable(v, iDb);
+ base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger);
+ sqlite3VdbeChangeP3(v, base+1, pTrigger->name, 0);
+ sqlite3ChangeCookie(db, v, iDb);
+ sqlite3VdbeAddOp(v, OP_Close, 0, 0);
+ sqlite3VdbeOp3(v, OP_DropTrigger, iDb, 0, pTrigger->name, 0);
+ }
+}
+
+/*
+** Remove a trigger from the hash tables of the sqlite* pointer.
+*/
+void sqlite3UnlinkAndDeleteTrigger(sqlite3 *db, int iDb, const char *zName){
+ Trigger *pTrigger;
+ int nName = strlen(zName);
+ pTrigger = sqlite3HashInsert(&(db->aDb[iDb].trigHash), zName, nName+1, 0);
+ if( pTrigger ){
+ Table *pTable = tableOfTrigger(db, 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.
+*/
+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 = pTab->pTrigger;
+ int mask = 0;
+
+ while( pTrigger ){
+ if( pTrigger->op==op && checkColumnOverLap(pTrigger->pColumns, pChanges) ){
+ TriggerStack *ss;
+ ss = pParse->trigStack;
+ while( ss && ss->pTrigger!=pTab->pTrigger ){
+ ss = ss->pNext;
+ }
+ if( ss==0 ){
+ 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 = 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 = sqlite3SrcListAppend(0, &sDb, &pStep->target);
+ } else {
+ pSrc = sqlite3SrcListAppend(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;
+
+ assert( pTriggerStep!=0 );
+ assert( v!=0 );
+ sqlite3VdbeAddOp(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(pTriggerStep->pSelect);
+ assert(ss);
+ assert(ss->pSrc);
+ sqlite3SelectResolve(pParse, ss, 0);
+ sqlite3Select(pParse, ss, SRT_Discard, 0, 0, 0, 0, 0);
+ sqlite3SelectDelete(ss);
+ break;
+ }
+ case TK_UPDATE: {
+ SrcList *pSrc;
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqlite3VdbeAddOp(v, OP_ResetCount, 0, 0);
+ sqlite3Update(pParse, pSrc,
+ sqlite3ExprListDup(pTriggerStep->pExprList),
+ sqlite3ExprDup(pTriggerStep->pWhere), orconf);
+ sqlite3VdbeAddOp(v, OP_ResetCount, 1, 0);
+ break;
+ }
+ case TK_INSERT: {
+ SrcList *pSrc;
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqlite3VdbeAddOp(v, OP_ResetCount, 0, 0);
+ sqlite3Insert(pParse, pSrc,
+ sqlite3ExprListDup(pTriggerStep->pExprList),
+ sqlite3SelectDup(pTriggerStep->pSelect),
+ sqlite3IdListDup(pTriggerStep->pIdList), orconf);
+ sqlite3VdbeAddOp(v, OP_ResetCount, 1, 0);
+ break;
+ }
+ case TK_DELETE: {
+ SrcList *pSrc;
+ sqlite3VdbeAddOp(v, OP_ResetCount, 0, 0);
+ pSrc = targetSrcList(pParse, pTriggerStep);
+ sqlite3DeleteFrom(pParse, pSrc, sqlite3ExprDup(pTriggerStep->pWhere));
+ sqlite3VdbeAddOp(v, OP_ResetCount, 1, 0);
+ break;
+ }
+ default:
+ assert(0);
+ }
+ pTriggerStep = pTriggerStep->pNext;
+ }
+ sqlite3VdbeAddOp(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).
+**
+*/
+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) */
+){
+ Trigger *pTrigger;
+ TriggerStack *pStack;
+ TriggerStack trigStackEntry;
+
+ assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE);
+ assert(tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_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 ){
+ fire_this = 1;
+ for(pStack=pParse->trigStack; pStack; pStack=pStack->pNext){
+ if( pStack->pTrigger==pTrigger ){
+ fire_this = 0;
+ }
+ }
+ if( op == TK_UPDATE && pTrigger->pColumns &&
+ !checkColumnOverLap(pTrigger->pColumns, pChanges) ){
+ fire_this = 0;
+ }
+ }
+
+ if( fire_this ){
+ int endTrigger;
+ Expr * whenExpr;
+ AuthContext sContext;
+ NameContext sNC;
+
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+
+ /* Push an entry on to the trigger stack */
+ trigStackEntry.pTrigger = pTrigger;
+ trigStackEntry.newIdx = newIdx;
+ trigStackEntry.oldIdx = oldIdx;
+ trigStackEntry.pTab = pTab;
+ trigStackEntry.pNext = pParse->trigStack;
+ trigStackEntry.ignoreJump = ignoreJump;
+ pParse->trigStack = &trigStackEntry;
+ sqlite3AuthContextPush(pParse, &sContext, pTrigger->name);
+
+ /* code the WHEN clause */
+ endTrigger = sqlite3VdbeMakeLabel(pParse->pVdbe);
+ whenExpr = sqlite3ExprDup(pTrigger->pWhen);
+ if( sqlite3ExprResolveNames(&sNC, whenExpr) ){
+ pParse->trigStack = trigStackEntry.pNext;
+ sqlite3ExprDelete(whenExpr);
+ return 1;
+ }
+ sqlite3ExprIfFalse(pParse, whenExpr, endTrigger, 1);
+ sqlite3ExprDelete(whenExpr);
+
+ codeTriggerProgram(pParse, pTrigger->step_list, orconf);
+
+ /* Pop the entry off the trigger stack */
+ pParse->trigStack = trigStackEntry.pNext;
+ sqlite3AuthContextPop(&sContext);
+
+ sqlite3VdbeResolveLabel(pParse->pVdbe, endTrigger);
+ }
+ pTrigger = pTrigger->pNext;
+ }
+ return 0;
+}
+#endif /* !defined(SQLITE_OMIT_TRIGGER) */
diff --git a/kexi/3rdparty/kexisql3/src/update.c b/kexi/3rdparty/kexisql3/src/update.c
new file mode 100644
index 000000000..644691fd7
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/update.c
@@ -0,0 +1,506 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include "sqliteInt.h"
+
+/*
+** The most recently coded instruction was an OP_Column to retrieve column
+** 'i' of table pTab. This routine sets the P3 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 P3 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 P3 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 P3 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.
+*/
+void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i){
+ if( pTab && !pTab->pSelect ){
+ sqlite3_value *pValue;
+ u8 enc = sqlite3VdbeDb(v)->enc;
+ Column *pCol = &pTab->aCol[i];
+ sqlite3ValueFromExpr(pCol->pDflt, enc, pCol->affinity, &pValue);
+ if( pValue ){
+ sqlite3VdbeChangeP3(v, -1, (const char *)pValue, P3_MEM);
+ }else{
+ VdbeComment((v, "# %s.%s", pTab->zName, pCol->zName));
+ }
+ }
+}
+
+/*
+** 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 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 nIdxTotal; /* Total number of indices */
+ int iCur; /* VDBE Cursor number of pTab */
+ sqlite3 *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 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 */
+
+#ifndef SQLITE_OMIT_TRIGGER
+ int isView; /* Trying to update a view */
+ int triggers_exist = 0; /* True if any row triggers exist */
+#endif
+
+ int newIdx = -1; /* index of trigger "new" temp table */
+ int oldIdx = -1; /* index of trigger "old" temp table */
+
+ sContext.pParse = 0;
+ if( pParse->nErr || sqlite3_malloc_failed ) goto update_cleanup;
+ db = pParse->db;
+ assert( pTabList->nSrc==1 );
+
+ /* Locate the table which we want to update.
+ */
+ pTab = sqlite3SrcListLookup(pParse, pTabList);
+ if( pTab==0 ) goto update_cleanup;
+
+ /* 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( isView ){
+ if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ goto update_cleanup;
+ }
+ }
+ aXRef = sqliteMallocRaw( 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[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( chngRowid ){
+ 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 = sqliteMallocRaw( 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( chngRowid ){
+ i = 0;
+ }else{
+ for(i=0; i<pIdx->nColumn; i++){
+ if( aXRef[pIdx->aiColumn[i]]>=0 ) break;
+ }
+ }
+ if( i<pIdx->nColumn ){
+ if( sqlite3CheckIndexCollSeq(pParse, pIdx) ) goto update_cleanup;
+ apIdx[nIdx++] = pIdx;
+ aIdxUsed[j] = 1;
+ }else{
+ aIdxUsed[j] = 0;
+ }
+ }
+
+ /* Resolve the column names in all the expressions in the
+ ** WHERE clause.
+ */
+ if( sqlite3ExprResolveNames(&sNC, pWhere) ){
+ goto update_cleanup;
+ }
+
+ /* Start the view context
+ */
+ if( isView ){
+ sqlite3AuthContextPush(pParse, &sContext, pTab->zName);
+ }
+
+ /* Begin generating code.
+ */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto update_cleanup;
+ if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
+ sqlite3BeginWriteOperation(pParse, 1, pTab->iDb);
+
+ /* If we are trying to update a view, realize that view into
+ ** a ephemeral table.
+ */
+ if( isView ){
+ Select *pView;
+ pView = sqlite3SelectDup(pTab->pSelect);
+ sqlite3Select(pParse, pView, SRT_VirtualTab, iCur, 0, 0, 0, 0);
+ sqlite3SelectDelete(pView);
+ }
+
+ /* Begin the database scan
+ */
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0);
+ if( pWInfo==0 ) goto update_cleanup;
+
+ /* Remember the index of every item to be updated.
+ */
+ sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_FifoWrite, 0, 0);
+
+ /* End the database scan loop.
+ */
+ sqlite3WhereEnd(pWInfo);
+
+ /* Initialize the count of updated rows
+ */
+ if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
+ sqlite3VdbeAddOp(v, OP_Integer, 0, 0);
+ }
+
+ if( triggers_exist ){
+ /* Create pseudo-tables for NEW and OLD
+ */
+ sqlite3VdbeAddOp(v, OP_OpenPseudo, oldIdx, 0);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, oldIdx, pTab->nCol);
+ sqlite3VdbeAddOp(v, OP_OpenPseudo, newIdx, 0);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, newIdx, pTab->nCol);
+
+ /* The top of the update loop for when there are triggers.
+ */
+ addr = sqlite3VdbeAddOp(v, OP_FifoRead, 0, 0);
+
+ if( !isView ){
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ /* Open a cursor and make it point to the record that is
+ ** being updated.
+ */
+ sqlite3OpenTableForReading(v, iCur, pTab);
+ }
+ sqlite3VdbeAddOp(v, OP_MoveGe, iCur, 0);
+
+ /* Generate the OLD table
+ */
+ sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_RowData, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_Insert, oldIdx, 0);
+
+ /* Generate the NEW table
+ */
+ if( chngRowid ){
+ sqlite3ExprCodeAndCache(pParse, pRowidExpr);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
+ }
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ sqlite3VdbeAddOp(v, OP_Null, 0, 0);
+ continue;
+ }
+ j = aXRef[i];
+ if( j<0 ){
+ sqlite3VdbeAddOp(v, OP_Column, iCur, i);
+ sqlite3ColumnDefault(v, pTab, i);
+ }else{
+ sqlite3ExprCodeAndCache(pParse, pChanges->a[j].pExpr);
+ }
+ }
+ sqlite3VdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
+ if( !isView ){
+ sqlite3TableAffinityStr(v, pTab);
+ }
+ if( pParse->nErr ) goto update_cleanup;
+ sqlite3VdbeAddOp(v, OP_Insert, newIdx, 0);
+ if( !isView ){
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }
+
+ /* Fire the BEFORE and INSTEAD OF triggers
+ */
+ if( sqlite3CodeRowTrigger(pParse, TK_UPDATE, pChanges, TRIGGER_BEFORE, pTab,
+ newIdx, oldIdx, onError, addr) ){
+ 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.
+ */
+ sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqlite3VdbeAddOp(v, OP_OpenWrite, iCur, pTab->tnum);
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, pTab->nCol);
+ 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] ){
+ sqlite3VdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ sqlite3VdbeOp3(v, OP_OpenWrite, iCur+i+1, pIdx->tnum,
+ (char*)&pIdx->keyInfo, P3_KEYINFO);
+ 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( !triggers_exist ){
+ addr = sqlite3VdbeAddOp(v, OP_FifoRead, 0, 0);
+ sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
+ }
+ sqlite3VdbeAddOp(v, OP_NotExists, iCur, addr);
+
+ /* 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);
+ sqlite3VdbeAddOp(v, OP_MustBeInt, 0, 0);
+ }
+
+ /* Compute new data for this record.
+ */
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ sqlite3VdbeAddOp(v, OP_Null, 0, 0);
+ continue;
+ }
+ j = aXRef[i];
+ if( j<0 ){
+ sqlite3VdbeAddOp(v, OP_Column, iCur, i);
+ sqlite3ColumnDefault(v, pTab, i);
+ }else{
+ sqlite3ExprCode(pParse, pChanges->a[j].pExpr);
+ }
+ }
+
+ /* Do constraint checks
+ */
+ sqlite3GenerateConstraintChecks(pParse, pTab, iCur, aIdxUsed, chngRowid, 1,
+ onError, addr);
+
+ /* Delete the old indices for the current record.
+ */
+ sqlite3GenerateRowIndexDelete(db, v, pTab, iCur, aIdxUsed);
+
+ /* If changing the record number, delete the old record.
+ */
+ if( chngRowid ){
+ sqlite3VdbeAddOp(v, OP_Delete, iCur, 0);
+ }
+
+ /* Create the new index entries and the new record.
+ */
+ sqlite3CompleteInsertion(pParse, pTab, iCur, aIdxUsed, chngRowid, 1, -1);
+ }
+
+ /* Increment the row counter
+ */
+ if( db->flags & SQLITE_CountRows && !pParse->trigStack){
+ sqlite3VdbeAddOp(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( triggers_exist ){
+ if( !isView ){
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aIdxUsed[i] )
+ sqlite3VdbeAddOp(v, OP_Close, iCur+i+1, 0);
+ }
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }
+ if( sqlite3CodeRowTrigger(pParse, TK_UPDATE, pChanges, TRIGGER_AFTER, pTab,
+ newIdx, oldIdx, onError, addr) ){
+ goto update_cleanup;
+ }
+ }
+
+ /* Repeat the above with the next record to be updated, until
+ ** all record selected by the WHERE clause have been updated.
+ */
+ sqlite3VdbeAddOp(v, OP_Goto, 0, addr);
+ sqlite3VdbeJumpHere(v, addr);
+
+ /* Close all tables if there were no FOR EACH ROW triggers */
+ if( !triggers_exist ){
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aIdxUsed[i] ){
+ sqlite3VdbeAddOp(v, OP_Close, iCur+i+1, 0);
+ }
+ }
+ sqlite3VdbeAddOp(v, OP_Close, iCur, 0);
+ }else{
+ sqlite3VdbeAddOp(v, OP_Close, newIdx, 0);
+ sqlite3VdbeAddOp(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 ){
+ sqlite3VdbeAddOp(v, OP_Callback, 1, 0);
+ sqlite3VdbeSetNumCols(v, 1);
+ sqlite3VdbeSetColName(v, 0, "rows updated", P3_STATIC);
+ }
+
+update_cleanup:
+ sqlite3AuthContextPop(&sContext);
+ sqliteFree(apIdx);
+ sqliteFree(aXRef);
+ sqlite3SrcListDelete(pTabList);
+ sqlite3ExprListDelete(pChanges);
+ sqlite3ExprDelete(pWhere);
+ return;
+}
diff --git a/kexi/3rdparty/kexisql3/src/utf.c b/kexi/3rdparty/kexisql3/src/utf.c
new file mode 100644
index 000000000..d42ab759e
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/utf.c
@@ -0,0 +1,570 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+**
+** 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
+**
+**
+** Handling of malformed strings:
+**
+** SQLite accepts and processes malformed strings without an error wherever
+** possible. However this is not possible when converting between UTF-8 and
+** UTF-16.
+**
+** When converting malformed UTF-8 strings to UTF-16, one instance of the
+** replacement character U+FFFD for each byte that cannot be interpeted as
+** part of a valid unicode character.
+**
+** When converting malformed UTF-16 strings to UTF-8, one instance of the
+** replacement character U+FFFD for each pair of bytes that cannot be
+** interpeted as part of a valid unicode character.
+**
+** This file contains the following public routines:
+**
+** sqlite3VdbeMemTranslate() - Translate the encoding used by a Mem* string.
+** sqlite3VdbeMemHandleBom() - Handle byte-order-marks in UTF16 Mem* strings.
+** sqlite3utf16ByteLen() - Calculate byte-length of a void* UTF16 string.
+** sqlite3utf8CharLen() - Calculate char-length of a char* UTF8 string.
+** sqlite3utf8LikeCompare() - Do a LIKE match given two UTF8 char* strings.
+**
+*/
+#include "sqliteInt.h"
+#include <assert.h>
+#include "vdbeInt.h"
+
+/*
+** This table maps from the first byte of a UTF-8 character to the number
+** of trailing bytes expected. A value '255' indicates that the table key
+** is not a legal first byte for a UTF-8 character.
+*/
+static const u8 xtra_utf8_bytes[256] = {
+/* 0xxxxxxx */
+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, 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,
+
+/* 10wwwwww */
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+
+/* 110yyyyy */
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+/* 1110zzzz */
+2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+/* 11110yyy */
+3, 3, 3, 3, 3, 3, 3, 3, 255, 255, 255, 255, 255, 255, 255, 255,
+};
+
+/*
+** This table maps from the number of trailing bytes in a UTF-8 character
+** to an integer constant that is effectively calculated for each character
+** read by a naive implementation of a UTF-8 character reader. The code
+** in the READ_UTF8 macro explains things best.
+*/
+static const int xtra_utf8_bits[4] = {
+0,
+12416, /* (0xC0 << 6) + (0x80) */
+925824, /* (0xE0 << 12) + (0x80 << 6) + (0x80) */
+63447168 /* (0xF0 << 18) + (0x80 << 12) + (0x80 << 6) + 0x80 */
+};
+
+#define READ_UTF8(zIn, c) { \
+ int xtra; \
+ c = *(zIn)++; \
+ xtra = xtra_utf8_bytes[c]; \
+ switch( xtra ){ \
+ case 255: c = (int)0xFFFD; break; \
+ case 3: c = (c<<6) + *(zIn)++; \
+ case 2: c = (c<<6) + *(zIn)++; \
+ case 1: c = (c<<6) + *(zIn)++; \
+ c -= xtra_utf8_bits[xtra]; \
+ } \
+}
+int sqlite3ReadUtf8(const unsigned char *z){
+ int c;
+ READ_UTF8(z, c);
+ return c;
+}
+
+#define SKIP_UTF8(zIn) { \
+ zIn += (xtra_utf8_bytes[*(u8 *)zIn] + 1); \
+}
+
+#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); \
+ } \
+}
+
+#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); \
+ } \
+}
+
+#define SKIP_UTF16BE(zIn){ \
+ if( *zIn>=0xD8 && (*zIn<0xE0 || (*zIn==0xE0 && *(zIn+1)==0x00)) ){ \
+ zIn += 4; \
+ }else{ \
+ zIn += 2; \
+ } \
+}
+#define SKIP_UTF16LE(zIn){ \
+ zIn++; \
+ if( *zIn>=0xD8 && (*zIn<0xE0 || (*zIn==0xE0 && *(zIn-1)==0x00)) ){ \
+ zIn += 3; \
+ }else{ \
+ zIn += 1; \
+ } \
+}
+
+#define RSKIP_UTF16LE(zIn){ \
+ if( *zIn>=0xD8 && (*zIn<0xE0 || (*zIn==0xE0 && *(zIn-1)==0x00)) ){ \
+ zIn -= 4; \
+ }else{ \
+ zIn -= 2; \
+ } \
+}
+#define RSKIP_UTF16BE(zIn){ \
+ zIn--; \
+ if( *zIn>=0xD8 && (*zIn<0xE0 || (*zIn==0xE0 && *(zIn+1)==0x00)) ){ \
+ zIn -= 3; \
+ }else{ \
+ zIn -= 1; \
+ } \
+}
+
+/*
+** 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.
+*/
+int sqlite3VdbeMemTranslate(Mem *pMem, u8 desiredEnc){
+ unsigned char zShort[NBFS]; /* Temporary short output buffer */
+ 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 */
+ int c;
+
+ 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, 100);
+ 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 = 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 3-byte UTF-8 character (i.e.
+ ** code-point 0xFFFC). A single byte is required for the output string
+ ** nul-terminator.
+ */
+ len = (pMem->n/2) * 3 + 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. This may be space
+ ** obtained from malloc(), or Mem.zShort, if it large enough and not in
+ ** use, or the zShort array on the stack (see above).
+ */
+ zIn = pMem->z;
+ zTerm = &zIn[pMem->n];
+ if( len>NBFS ){
+ zOut = sqliteMallocRaw(len);
+ if( !zOut ) return SQLITE_NOMEM;
+ }else{
+ zOut = zShort;
+ }
+ z = zOut;
+
+ if( pMem->enc==SQLITE_UTF8 ){
+ if( desiredEnc==SQLITE_UTF16LE ){
+ /* UTF-8 -> UTF-16 Little-endian */
+ while( zIn<zTerm ){
+ READ_UTF8(zIn, c);
+ WRITE_UTF16LE(z, c);
+ }
+ }else{
+ assert( desiredEnc==SQLITE_UTF16BE );
+ /* UTF-8 -> UTF-16 Big-endian */
+ while( zIn<zTerm ){
+ READ_UTF8(zIn, c);
+ 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|MEM_Short);
+ pMem->enc = desiredEnc;
+ if( zOut==zShort ){
+ memcpy(pMem->zShort, zOut, len);
+ zOut = pMem->zShort;
+ pMem->flags |= (MEM_Term|MEM_Short);
+ }else{
+ pMem->flags |= (MEM_Term|MEM_Dyn);
+ }
+ pMem->z = zOut;
+
+translate_out:
+#if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG)
+ {
+ char zBuf[100];
+ sqlite3VdbeMemPrettyPrint(pMem, zBuf, 100);
+ 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.
+*/
+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 ){
+ /* This function is called as soon as a string is stored in a Mem*,
+ ** from within sqlite3VdbeMemSetStr(). At that point it is not possible
+ ** for the string to be stored in Mem.zShort, or for it to be stored
+ ** in dynamic memory with no destructor.
+ */
+ assert( !(pMem->flags&MEM_Short) );
+ assert( !(pMem->flags&MEM_Dyn) || pMem->xDel );
+ if( pMem->flags & MEM_Dyn ){
+ void (*xDel)(void*) = pMem->xDel;
+ char *z = pMem->z;
+ pMem->z = 0;
+ pMem->xDel = 0;
+ rc = sqlite3VdbeMemSetStr(pMem, &z[2], pMem->n-2, bom, SQLITE_TRANSIENT);
+ xDel(z);
+ }else{
+ rc = sqlite3VdbeMemSetStr(pMem, &pMem->z[2], pMem->n-2, bom,
+ SQLITE_TRANSIENT);
+ }
+ }
+ 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).
+*/
+int sqlite3utf8CharLen(const char *z, int nByte){
+ int r = 0;
+ const char *zTerm;
+ if( nByte>=0 ){
+ zTerm = &z[nByte];
+ }else{
+ zTerm = (const char *)(-1);
+ }
+ assert( z<=zTerm );
+ while( *z!=0 && z<zTerm ){
+ SKIP_UTF8(z);
+ r++;
+ }
+ return r;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** 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).
+*/
+int sqlite3utf16ByteLen(const void *zIn, int nChar){
+ int c = 1;
+ char const *z = zIn;
+ int n = 0;
+ if( SQLITE_UTF16NATIVE==SQLITE_UTF16BE ){
+ 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);
+}
+
+/*
+** UTF-16 implementation of the substr()
+*/
+void sqlite3utf16Substr(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int y, z;
+ unsigned char const *zStr;
+ unsigned char const *zStrEnd;
+ unsigned char const *zStart;
+ unsigned char const *zEnd;
+ int i;
+
+ zStr = (unsigned char const *)sqlite3_value_text16(argv[0]);
+ zStrEnd = &zStr[sqlite3_value_bytes16(argv[0])];
+ y = sqlite3_value_int(argv[1]);
+ z = sqlite3_value_int(argv[2]);
+
+ if( y>0 ){
+ y = y-1;
+ zStart = zStr;
+ if( SQLITE_UTF16BE==SQLITE_UTF16NATIVE ){
+ for(i=0; i<y && zStart<zStrEnd; i++) SKIP_UTF16BE(zStart);
+ }else{
+ for(i=0; i<y && zStart<zStrEnd; i++) SKIP_UTF16LE(zStart);
+ }
+ }else{
+ zStart = zStrEnd;
+ if( SQLITE_UTF16BE==SQLITE_UTF16NATIVE ){
+ for(i=y; i<0 && zStart>zStr; i++) RSKIP_UTF16BE(zStart);
+ }else{
+ for(i=y; i<0 && zStart>zStr; i++) RSKIP_UTF16LE(zStart);
+ }
+ for(; i<0; i++) z -= 1;
+ }
+
+ zEnd = zStart;
+ if( SQLITE_UTF16BE==SQLITE_UTF16NATIVE ){
+ for(i=0; i<z && zEnd<zStrEnd; i++) SKIP_UTF16BE(zEnd);
+ }else{
+ for(i=0; i<z && zEnd<zStrEnd; i++) SKIP_UTF16LE(zEnd);
+ }
+
+ sqlite3_result_text16(context, zStart, zEnd-zStart, SQLITE_TRANSIENT);
+}
+
+#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.
+*/
+void sqlite3utfSelfTest(){
+ int i;
+ unsigned char zBuf[20];
+ unsigned char *z;
+ int n;
+ int c;
+
+ for(i=0; i<0x00110000; i++){
+ z = zBuf;
+ WRITE_UTF8(z, i);
+ n = z-zBuf;
+ z = zBuf;
+ READ_UTF8(z, c);
+ assert( c==i );
+ 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 = 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 = zBuf;
+ READ_UTF16BE(z, c);
+ assert( c==i );
+ assert( (z-zBuf)==n );
+ }
+}
+#endif /* SQLITE_TEST */
+#endif /* SQLITE_OMIT_UTF16 */
diff --git a/kexi/3rdparty/kexisql3/src/util.c b/kexi/3rdparty/kexisql3/src/util.c
new file mode 100644
index 000000000..0bbcb0193
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/util.c
@@ -0,0 +1,1005 @@
+/*
+** 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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#include "sqliteInt.h"
+#include <stdarg.h>
+#include <ctype.h>
+
+#if SQLITE_MEMDEBUG>2 && defined(__GLIBC__)
+#include <execinfo.h>
+void print_stack_trace(){
+ void *bt[30];
+ int i;
+ int n = backtrace(bt, 30);
+
+ fprintf(stderr, "STACK: ");
+ for(i=0; i<n;i++){
+ fprintf(stderr, "%p ", bt[i]);
+ }
+ fprintf(stderr, "\n");
+}
+#else
+#define print_stack_trace()
+#endif
+
+/*
+** If malloc() ever fails, this global variable gets set to 1.
+** This causes the library to abort and never again function.
+*/
+int sqlite3_malloc_failed = 0;
+
+/*
+** If SQLITE_MEMDEBUG is defined, then use versions of malloc() and
+** free() that track memory usage and check for buffer overruns.
+*/
+#ifdef SQLITE_MEMDEBUG
+
+/*
+** For keeping track of the number of mallocs and frees. This
+** is used to check for memory leaks. The iMallocFail and iMallocReset
+** values are used to simulate malloc() failures during testing in
+** order to verify that the library correctly handles an out-of-memory
+** condition.
+*/
+int sqlite3_nMalloc; /* Number of sqliteMalloc() calls */
+int sqlite3_nFree; /* Number of sqliteFree() calls */
+int sqlite3_memUsed; /* Total memory obtained from malloc */
+int sqlite3_memMax; /* Mem usage high-water mark */
+int sqlite3_iMallocFail; /* Fail sqliteMalloc() after this many calls */
+int sqlite3_iMallocReset = -1; /* When iMallocFail reaches 0, set to this */
+#if SQLITE_MEMDEBUG>1
+static int memcnt = 0;
+#endif
+
+/*
+** Number of 32-bit guard words. This should probably be a multiple of
+** 2 since on 64-bit machines we want the value returned by sqliteMalloc()
+** to be 8-byte aligned.
+*/
+#define N_GUARD 2
+
+/*
+** Check for a simulated memory allocation failure. Return true if
+** the failure should be simulated. Return false to proceed as normal.
+*/
+static int simulatedMallocFailure(int n, char *zFile, int line){
+ if( sqlite3_iMallocFail>=0 ){
+ sqlite3_iMallocFail--;
+ if( sqlite3_iMallocFail==0 ){
+ sqlite3_malloc_failed++;
+#if SQLITE_MEMDEBUG>1
+ fprintf(stderr,"**** failed to allocate %d bytes at %s:%d\n",
+ n, zFile,line);
+#endif
+ sqlite3_iMallocFail = sqlite3_iMallocReset;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** Allocate new memory and set it to zero. Return NULL if
+** no memory is available.
+*/
+void *sqlite3Malloc_(int n, int bZero, char *zFile, int line){
+ void *p;
+ int *pi;
+ int i, k;
+ if( n==0 ){
+ return 0;
+ }
+ if( simulatedMallocFailure(n, zFile, line) ){
+ return 0;
+ }
+ sqlite3_memUsed += n;
+ if( sqlite3_memMax<sqlite3_memUsed ) sqlite3_memMax = sqlite3_memUsed;
+ k = (n+sizeof(int)-1)/sizeof(int);
+ pi = malloc( (N_GUARD*2+1+k)*sizeof(int));
+ if( pi==0 ){
+ if( n>0 ) sqlite3_malloc_failed++;
+ return 0;
+ }
+ sqlite3_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 SQLITE_MEMDEBUG>1
+ print_stack_trace();
+ fprintf(stderr,"%06d malloc %d bytes at 0x%x from %s:%d\n",
+ ++memcnt, n, (int)p, zFile,line);
+#endif
+ return p;
+}
+
+/*
+** This version of malloc is always a real function, never a macro
+*/
+void *sqlite3MallocX(int n){
+ return sqlite3Malloc_(n, 0, __FILE__, __LINE__);
+}
+
+/*
+** 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 sqlite3CheckMemory(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 sqlite3Free_(void *p, char *zFile, int line){
+ if( p ){
+ int *pi, i, k, n;
+ pi = p;
+ pi -= N_GUARD+1;
+ sqlite3_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];
+ sqlite3_memUsed -= n;
+ 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 SQLITE_MEMDEBUG>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 *sqlite3Realloc_(void *oldP, int n, char *zFile, int line){
+ int *oldPi, *pi, i, k, oldN, oldK;
+ void *p;
+ if( oldP==0 ){
+ return sqlite3Malloc_(n,1,zFile,line);
+ }
+ if( n==0 ){
+ sqlite3Free_(oldP,zFile,line);
+ return 0;
+ }
+ if( simulatedMallocFailure(n, 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];
+ sqlite3_memUsed -= oldN;
+ 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 ){
+ if( n>0 ) sqlite3_malloc_failed++;
+ return 0;
+ }
+ for(i=0; i<N_GUARD; i++) pi[i] = 0xdead1122;
+ pi[N_GUARD] = n;
+ sqlite3_memUsed += n;
+ if( sqlite3_memMax<sqlite3_memUsed ) sqlite3_memMax = sqlite3_memUsed;
+ 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], 0x55, n-oldN);
+ }
+ memset(oldPi, 0xab, (oldK+N_GUARD+2)*sizeof(int));
+ free(oldPi);
+#if SQLITE_MEMDEBUG>1
+ print_stack_trace();
+ 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 copy of a string in memory obtained from sqliteMalloc()
+*/
+char *sqlite3StrDup_(const char *z, char *zFile, int line){
+ char *zNew;
+ if( z==0 ) return 0;
+ zNew = sqlite3Malloc_(strlen(z)+1, 0, zFile, line);
+ if( zNew ) strcpy(zNew, z);
+ return zNew;
+}
+char *sqlite3StrNDup_(const char *z, int n, char *zFile, int line){
+ char *zNew;
+ if( z==0 ) return 0;
+ zNew = sqlite3Malloc_(n+1, 0, zFile, line);
+ if( zNew ){
+ memcpy(zNew, z, n);
+ zNew[n] = 0;
+ }
+ return zNew;
+}
+
+/*
+** A version of sqliteFree that is always a function, not a macro.
+*/
+void sqlite3FreeX(void *p){
+ sqliteFree(p);
+}
+#endif /* SQLITE_MEMDEBUG */
+
+/*
+** The following versions of malloc() and free() are for use in a
+** normal build.
+*/
+#if !defined(SQLITE_MEMDEBUG)
+
+/*
+** Allocate new memory and set it to zero. Return NULL if
+** no memory is available. See also sqliteMallocRaw().
+*/
+void *sqlite3Malloc(int n){
+ void *p;
+ if( n==0 ) return 0;
+ if( (p = malloc(n))==0 ){
+ if( n>0 ) sqlite3_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 *sqlite3MallocRaw(int n){
+ void *p;
+ if( n==0 ) return 0;
+ if( (p = malloc(n))==0 ){
+ if( n>0 ) sqlite3_malloc_failed++;
+ }
+ return p;
+}
+
+/*
+** Free memory previously obtained from sqliteMalloc()
+*/
+void sqlite3FreeX(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 *sqlite3Realloc(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 ){
+ if( n>0 ) sqlite3_malloc_failed++;
+ }
+ return p2;
+}
+
+/*
+** Make a copy of a string in memory obtained from sqliteMalloc()
+*/
+char *sqlite3StrDup(const char *z){
+ char *zNew;
+ if( z==0 ) return 0;
+ zNew = sqliteMallocRaw(strlen(z)+1);
+ if( zNew ) strcpy(zNew, z);
+ return zNew;
+}
+char *sqlite3StrNDup(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(SQLITE_MEMDEBUG) */
+
+/*
+** Reallocate a buffer to a different size. This is similar to
+** sqliteRealloc() except that if the allocation fails the buffer
+** is freed.
+*/
+void sqlite3ReallocOrFree(void **ppBuf, int newSize){
+ void *pNew = sqliteRealloc(*ppBuf, newSize);
+ if( pNew==0 ){
+ sqliteFree(*ppBuf);
+ }
+ *ppBuf = pNew;
+}
+
+/*
+** 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 sqlite3SetString(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 SQLITE_MEMDEBUG
+#if SQLITE_MEMDEBUG>1
+ fprintf(stderr,"string at 0x%x is %s\n", (int)*pz, *pz);
+#endif
+#endif
+}
+
+/*
+** 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.
+*/
+void sqlite3Error(sqlite3 *db, int err_code, const char *zFormat, ...){
+ if( db && (db->pErr || (db->pErr = sqlite3ValueNew())) ){
+ db->errCode = err_code;
+ if( zFormat ){
+ char *z;
+ va_list ap;
+ va_start(ap, zFormat);
+ z = sqlite3VMPrintf(zFormat, ap);
+ va_end(ap);
+ sqlite3ValueSetStr(db->pErr, -1, z, SQLITE_UTF8, sqlite3FreeX);
+ }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.).
+*/
+void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){
+ va_list ap;
+ pParse->nErr++;
+ sqliteFree(pParse->zErrMsg);
+ va_start(ap, zFormat);
+ pParse->zErrMsg = sqlite3VMPrintf(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 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.
+*/
+const unsigned char sqlite3UpperToLower[] = {
+ 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
+};
+#define UpperToLower sqlite3UpperToLower
+
+/*
+** Some systems have stricmp(). Others have strcasecmp(). Because
+** there is no consistency, we will define our own.
+*/
+int sqlite3StrICmp(const char *zLeft, const char *zRight){
+ register 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 sqlite3StrNICmp(const char *zLeft, const char *zRight, int N){
+ register 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.
+*/
+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.
+*/
+int sqlite3AtoF(const char *z, double *pResult){
+ int sign = 1;
+ const char *zBegin = z;
+ LONGDOUBLE_TYPE v1 = 0.0;
+ if( *z=='-' ){
+ sign = -1;
+ z++;
+ }else if( *z=='+' ){
+ z++;
+ }
+ while( isdigit(*(u8*)z) ){
+ v1 = v1*10.0 + (*z - '0');
+ z++;
+ }
+ if( *z=='.' ){
+ LONGDOUBLE_TYPE divisor = 1.0;
+ z++;
+ while( isdigit(*(u8*)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(*(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;
+}
+
+/*
+** 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. If n>0 and the integer is string is not
+** exactly n bytes long, 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.
+*/
+int sqlite3atoi64(const char *zNum, i64 *pNum){
+ i64 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<19 || (i==19 && memcmp(zNum,"9223372036854775807",19)<=0));
+}
+
+/*
+** 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 fact fit in a 32-bit integer. But positive
+** 2147483648 will not fit in 32 bits. So it seems safer to return
+** false.
+*/
+static int sqlite3FitsIn32Bits(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);
+}
+
+/*
+** If zNum represents an integer that will fit in 32-bits, then set
+** *pValue to that integer and return true. Otherwise return false.
+*/
+int sqlite3GetInt32(const char *zNum, int *pValue){
+ if( sqlite3FitsIn32Bits(zNum) ){
+ *pValue = atoi(zNum);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** 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.
+*/
+int sqlite3FitsIn64Bits(const char *zNum){
+ int i, c;
+ if( *zNum=='-' || *zNum=='+' ) zNum++;
+ for(i=0; (c=zNum[i])>='0' && c<='9'; i++){}
+ return i<19 || (i==19 && memcmp(zNum,"9223372036854775807",19)<=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 sqlite3_close(db) and db has been deallocated. And we do
+** not want to write into deallocated memory.
+*/
+int sqlite3SafetyOn(sqlite3 *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->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 sqlite3SafetyOff(sqlite3 *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->flags |= SQLITE_Interrupt;
+ }
+ return 1;
+}
+
+/*
+** 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
+** TRUE it means that the db pointer is invalid and should not be
+** dereferenced for any reason. The calling function should invoke
+** SQLITE_MISUSE immediately.
+*/
+int sqlite3SafetyCheck(sqlite3 *db){
+ int magic;
+ if( db==0 ) return 1;
+ magic = db->magic;
+ if( magic!=SQLITE_MAGIC_CLOSED &&
+ magic!=SQLITE_MAGIC_OPEN &&
+ magic!=SQLITE_MAGIC_BUSY ) return 1;
+ return 0;
+}
+
+/*
+** 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.
+*/
+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;
+}
+
+/*
+** 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.
+*/
+int sqlite3GetVarint(const unsigned char *p, u64 *v){
+ u32 x;
+ u64 x64;
+ int n;
+ unsigned char c;
+ if( ((c = p[0]) & 0x80)==0 ){
+ *v = c;
+ return 1;
+ }
+ x = c & 0x7f;
+ if( ((c = p[1]) & 0x80)==0 ){
+ *v = (x<<7) | c;
+ return 2;
+ }
+ x = (x<<7) | (c&0x7f);
+ if( ((c = p[2]) & 0x80)==0 ){
+ *v = (x<<7) | c;
+ return 3;
+ }
+ x = (x<<7) | (c&0x7f);
+ if( ((c = p[3]) & 0x80)==0 ){
+ *v = (x<<7) | c;
+ return 4;
+ }
+ x64 = (x<<7) | (c&0x7f);
+ n = 4;
+ do{
+ c = p[n++];
+ if( n==9 ){
+ x64 = (x64<<8) | c;
+ break;
+ }
+ x64 = (x64<<7) | (c&0x7f);
+ }while( (c & 0x80)!=0 );
+ *v = x64;
+ return n;
+}
+
+/*
+** 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.
+*/
+int sqlite3GetVarint32(const unsigned char *p, u32 *v){
+ u32 x;
+ int n;
+ unsigned char c;
+ if( ((signed char*)p)[0]>=0 ){
+ *v = p[0];
+ return 1;
+ }
+ x = p[0] & 0x7f;
+ if( ((signed char*)p)[1]>=0 ){
+ *v = (x<<7) | p[1];
+ return 2;
+ }
+ x = (x<<7) | (p[1] & 0x7f);
+ n = 2;
+ do{
+ x = (x<<7) | ((c = p[n++])&0x7f);
+ }while( (c & 0x80)!=0 && n<9 );
+ *v = x;
+ return n;
+}
+
+/*
+** Return the number of bytes that will be needed to store the given
+** 64-bit integer.
+*/
+int sqlite3VarintLen(u64 v){
+ int i = 0;
+ do{
+ i++;
+ v >>= 7;
+ }while( v!=0 && i<9 );
+ return i;
+}
+
+#if !defined(SQLITE_OMIT_BLOB_LITERAL) || defined(SQLITE_HAS_CODEC) \
+ || defined(SQLITE_TEST)
+/*
+** Translate a single byte of Hex into an integer.
+*/
+static int hexToInt(int h){
+ if( h>='0' && h<='9' ){
+ return h - '0';
+ }else if( h>='a' && h<='f' ){
+ return h - 'a' + 10;
+ }else{
+ assert( h>='A' && h<='F' );
+ return h - 'A' + 10;
+ }
+}
+#endif /* !SQLITE_OMIT_BLOB_LITERAL || SQLITE_HAS_CODEC || SQLITE_TEST */
+
+#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.
+*/
+void *sqlite3HexToBlob(const char *z){
+ char *zBlob;
+ int i;
+ int n = strlen(z);
+ if( n%2 ) return 0;
+
+ zBlob = (char *)sqliteMalloc(n/2);
+ for(i=0; i<n; i+=2){
+ zBlob[i/2] = (hexToInt(z[i])<<4) | hexToInt(z[i+1]);
+ }
+ return zBlob;
+}
+#endif /* !SQLITE_OMIT_BLOB_LITERAL || SQLITE_HAS_CODEC */
+
+#if defined(SQLITE_TEST)
+/*
+** Convert text generated by the "%p" conversion format back into
+** a pointer.
+*/
+void *sqlite3TextToPtr(const char *z){
+ void *p;
+ u64 v;
+ u32 v2;
+ if( z[0]=='0' && z[1]=='x' ){
+ z += 2;
+ }
+ v = 0;
+ while( *z ){
+ v = (v<<4) + hexToInt(*z);
+ z++;
+ }
+ if( sizeof(p)==sizeof(v) ){
+ p = *(void**)&v;
+ }else{
+ assert( sizeof(p)==sizeof(v2) );
+ v2 = (u32)v;
+ p = *(void**)&v2;
+ }
+ return p;
+}
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/vacuum.c b/kexi/3rdparty/kexisql3/src/vacuum.c
new file mode 100644
index 000000000..6f544a6bc
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/vacuum.c
@@ -0,0 +1,337 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include "sqliteInt.h"
+#include "os.h"
+
+#ifndef SQLITE_OMIT_VACUUM
+/*
+** Generate a random name of 20 character in length.
+*/
+static void randomName(unsigned char *zBuf){
+ static const unsigned char zChars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789";
+ int i;
+ sqlite3Randomness(20, zBuf);
+ for(i=0; i<20; i++){
+ zBuf[i] = zChars[ zBuf[i]%(sizeof(zChars)-1) ];
+ }
+}
+
+/*
+** Execute zSql on database db. Return an error code.
+*/
+static int execSql(sqlite3 *db, const char *zSql){
+ sqlite3_stmt *pStmt;
+ if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){
+ return sqlite3_errcode(db);
+ }
+ while( SQLITE_ROW==sqlite3_step(pStmt) );
+ return sqlite3_finalize(pStmt);
+}
+
+/* (jstaniek) */
+extern int g_verbose_vacuum;
+
+/*
+** Execute zSql on database db. The statement returns exactly
+** one column. Execute this as SQL on the same database.
+**
+** (js: extension): if count > 0, "VACUUM: X%" string will
+** be printed to stdout, so user (a human or calling application)
+** can know the overall progress of the operation.
+** and the program will wait for a key press (followed by RETURN);
+** 'q' key aborts the execution and any other key allows to proceed.
+*/
+static int execExecSql(sqlite3 *db, const char *zSql, int count){
+ sqlite3_stmt *pStmt;
+ int rc, i;
+ char ch;
+
+ rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+
+ for( i=0; SQLITE_ROW==sqlite3_step(pStmt); i++ ){
+ if (g_verbose_vacuum!=0 && count>0) {
+ fprintf(stdout, "VACUUM: %d%%\n", 100*(i+1)/count);
+ fflush(stdout);
+ fscanf(stdin, "%c", &ch);
+ if ('q'==ch) { /* quit */
+ sqlite3_finalize(pStmt);
+ return SQLITE_ABORT;
+ }
+ }
+ rc = execSql(db, sqlite3_column_text(pStmt, 0));
+ if( rc!=SQLITE_OK ){
+ sqlite3_finalize(pStmt);
+ return rc;
+ }
+ }
+
+ return sqlite3_finalize(pStmt);
+}
+
+#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 sqlite3Vacuum(Parse *pParse, Token *pTableName){
+ Vdbe *v = sqlite3GetVdbe(pParse);
+ if( v ){
+ sqlite3VdbeAddOp(v, OP_Vacuum, 0, 0);
+ }
+ return;
+}
+
+/*
+** This routine implements the OP_Vacuum opcode of the VDBE.
+*/
+int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){
+ int rc = SQLITE_OK; /* Return code from service routines */
+ sqlite3_stmt *pStmt = 0;
+ int tables_count = 0;
+#ifndef 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 */
+ Btree *pMain; /* The database being vacuumed */
+ Btree *pTemp;
+ char *zSql = 0;
+ int writeschema_flag; /* Saved value of the write-schema flag */
+
+ /* Save the current value of the write-schema flag before setting it. */
+ writeschema_flag = db->flags&SQLITE_WriteSchema;
+ db->flags |= SQLITE_WriteSchema;
+
+ if( !db->autoCommit ){
+ sqlite3SetString(pzErrMsg, "cannot VACUUM from within a transaction",
+ (char*)0);
+ rc = SQLITE_ERROR;
+ goto end_of_vacuum;
+ }
+
+ /* Get the full pathname of the database file and create a
+ ** temporary filename in the same directory as the original file.
+ */
+ pMain = db->aDb[0].pBt;
+ zFilename = sqlite3BtreeGetFilename(pMain);
+ assert( zFilename );
+ if( zFilename[0]=='\0' ){
+ /* The in-memory database. Do nothing. Return directly to avoid causing
+ ** an error trying to DETACH the vacuum_db (which never got attached)
+ ** in the exit-handler.
+ */
+ return SQLITE_OK;
+ }
+ nFilename = strlen(zFilename);
+ zTemp = sqliteMalloc( nFilename+100 );
+ if( zTemp==0 ){
+ rc = SQLITE_NOMEM;
+ goto end_of_vacuum;
+ }
+ strcpy(zTemp, zFilename);
+
+ /* The randomName() procedure in the following loop uses an excellent
+ ** source of randomness to generate a name from a space of 1.3e+31
+ ** possibilities. So unless the directory already contains on the order
+ ** of 1.3e+31 files, the probability that the following loop will
+ ** run more than once or twice is vanishingly small. We are certain
+ ** enough that this loop will always terminate (and terminate quickly)
+ ** that we don't even bother to set a maximum loop count.
+ */
+ do {
+ zTemp[nFilename] = '-';
+ randomName((unsigned char*)&zTemp[nFilename+1]);
+ } while( sqlite3OsFileExists(zTemp) );
+
+ /* 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.
+ */
+ zSql = sqlite3MPrintf("ATTACH '%q' AS vacuum_db;", zTemp);
+ if( !zSql ){
+ rc = SQLITE_NOMEM;
+ goto end_of_vacuum;
+ }
+ rc = execSql(db, zSql);
+ sqliteFree(zSql);
+ zSql = 0;
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ assert( strcmp(db->aDb[db->nDb-1].zName,"vacuum_db")==0 );
+ pTemp = db->aDb[db->nDb-1].pBt;
+ sqlite3BtreeSetPageSize(pTemp, sqlite3BtreeGetPageSize(pMain),
+ sqlite3BtreeGetReserve(pMain));
+ assert( sqlite3BtreeGetPageSize(pTemp)==sqlite3BtreeGetPageSize(pMain) );
+ execSql(db, "PRAGMA vacuum_db.synchronous=OFF");
+
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ sqlite3BtreeSetAutoVacuum(pTemp, sqlite3BtreeGetAutoVacuum(pMain));
+#endif
+
+ /* Begin a transaction */
+ rc = execSql(db, "BEGIN;");
+ 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,100000000) "
+ " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'", 0);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14,100000000)"
+ " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' ", 0);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21,100000000) "
+ " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %'", 0);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = execExecSql(db,
+ "SELECT 'CREATE VIEW vacuum_db.' || substr(sql,13,100000000) "
+ " FROM sqlite_master WHERE type='view'", 0
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+ /* (js) get # of tables so we can output progress.
+ */
+ rc = sqlite3_prepare(db, "SELECT count(name) FROM sqlite_master "
+ "WHERE type = 'table';", -1, &pStmt, 0);
+ if( rc!=SQLITE_OK || SQLITE_ROW!=sqlite3_step(pStmt)) goto end_of_vacuum;
+ tables_count = atoi( sqlite3_column_text(pStmt, 0) );
+
+ /* 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';", tables_count
+ );
+ 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' ", 0
+ );
+ 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';", 0
+ );
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+
+ /* Copy the triggers from the main database to the temporary database.
+ ** This was deferred before in case the triggers interfered with copying
+ ** the data. It's possible the indices should be deferred until this
+ ** point also.
+ */
+ rc = execExecSql(db,
+ "SELECT 'CREATE TRIGGER vacuum_db.' || substr(sql, 16, 1000000) "
+ "FROM sqlite_master WHERE type='trigger'", 0
+ );
+ if( rc!=SQLITE_OK ) 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( sqlite3BtreeIsInTrans(pTemp) ){
+ 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( 0==sqlite3BtreeIsInTrans(pMain) );
+ rc = sqlite3BtreeBeginTrans(pMain, 1);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+
+ /* 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]);
+ }
+
+ rc = sqlite3BtreeCopyFile(pMain, pTemp);
+ if( rc!=SQLITE_OK ) goto end_of_vacuum;
+ rc = sqlite3BtreeCommit(pMain);
+ }
+
+end_of_vacuum:
+ /* Restore the original value of the write-schema flag. */
+ db->flags &= ~SQLITE_WriteSchema;
+ db->flags |= writeschema_flag;
+
+ /* 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( rc==SQLITE_OK ){
+ rc = execSql(db, "DETACH vacuum_db;");
+ }else{
+ execSql(db, "DETACH vacuum_db;");
+ }
+ if( zTemp ){
+ sqlite3OsDelete(zTemp);
+ sqliteFree(zTemp);
+ }
+ if( zSql ) sqliteFree( zSql );
+ sqlite3ResetInternalSchema(db, 0);
+#endif
+
+ return rc;
+}
diff --git a/kexi/3rdparty/kexisql3/src/vdbe.c b/kexi/3rdparty/kexisql3/src/vdbe.c
new file mode 100644
index 000000000..abb68aec0
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/vdbe.c
@@ -0,0 +1,4432 @@
+/*
+** 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 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 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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#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_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.
+*/
+int sqlite3_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 and interrupt.
+**
+** This facility is used for testing purposes only. It does not function
+** in an ordinary build.
+*/
+int sqlite3_interrupt_count = 0;
+
+/*
+** 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.
+*/
+int sqlite3_sort_count = 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){ sqlite3VdbeMemRelease(P); }
+
+/*
+** Convert the given stack entity 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; }
+
+/*
+** 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,enc) sqlite3VdbeMemDynamicify(P)
+
+
+/*
+** 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 \
+ && sqlite3VdbeMemMakeWriteable(P) ){ goto no_mem;}
+
+/*
+** 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) sqlite3VdbeMemIntegerify(P)
+
+/*
+** Convert P so that it has type MEM_Real.
+**
+** Any prior string or integer representation is invalidated.
+** NULLs are converted into 0.0.
+*/
+#define Realify(P) sqlite3VdbeMemRealify(P)
+
+/*
+** Argument pMem points at a memory cell 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
+** stack 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;
+ }
+}
+
+/*
+** Pop the stack N times.
+*/
+static void popStack(Mem **ppTos, int N){
+ Mem *pTos = *ppTos;
+ while( N>0 ){
+ N--;
+ Release(pTos);
+ pTos--;
+ }
+ *ppTos = pTos;
+}
+
+/*
+** Allocate cursor number iCur. Return a pointer to it. Return NULL
+** if we run out of memory.
+*/
+static Cursor *allocateCursor(Vdbe *p, int iCur){
+ Cursor *pCx;
+ assert( iCur<p->nCursor );
+ if( p->apCsr[iCur] ){
+ sqlite3VdbeFreeCursor(p->apCsr[iCur]);
+ }
+ p->apCsr[iCur] = pCx = sqliteMalloc( sizeof(Cursor) );
+ return pCx;
+}
+
+/*
+** Apply any conversion required by the supplied column affinity to
+** memory cell pRec. affinity may be one of:
+**
+** SQLITE_AFF_NUMERIC
+** SQLITE_AFF_TEXT
+** SQLITE_AFF_NONE
+** SQLITE_AFF_INTEGER
+**
+*/
+static void applyAffinity(Mem *pRec, char affinity, u8 enc){
+ if( affinity==SQLITE_AFF_NONE ){
+ /* do nothing */
+ }else 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( 0==(pRec->flags&(MEM_Real|MEM_Int)) ){
+ /* pRec does not have a valid integer or real representation.
+ ** Attempt a conversion if pRec has a string representation and
+ ** it looks like a number.
+ */
+ int realnum;
+ sqlite3VdbeMemNulTerminate(pRec);
+ if( pRec->flags&MEM_Str && sqlite3IsNumber(pRec->z, &realnum, enc) ){
+ if( realnum ){
+ Realify(pRec);
+ }else{
+ Integerify(pRec);
+ }
+ }
+ }
+
+ if( affinity==SQLITE_AFF_INTEGER ){
+ /* For INTEGER affinity, try to convert a real value to an int */
+ if( (pRec->flags&MEM_Real) && !(pRec->flags&MEM_Int) ){
+ pRec->i = pRec->r;
+ if( ((double)pRec->i)==pRec->r ){
+ pRec->flags |= MEM_Int;
+ }
+ }
+ }
+ }
+}
+
+/*
+** Exported version of applyAffinity(). This one works on sqlite3_value*,
+** not the internal Mem* type.
+*/
+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.
+*/
+void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf, int nBuf){
+ 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';
+ }
+
+ zCsr += sprintf(zCsr, "%c", c);
+ zCsr += sprintf(zCsr, "%d[", pMem->n);
+ for(i=0; i<16 && i<pMem->n; i++){
+ zCsr += sprintf(zCsr, "%02X ", ((int)pMem->z[i] & 0xFF));
+ }
+ for(i=0; i<16 && i<pMem->n; i++){
+ char z = pMem->z[i];
+ if( z<32 || z>126 ) *zCsr++ = '.';
+ else *zCsr++ = z;
+ }
+
+ zCsr += sprintf(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;
+ k += sprintf(&zBuf[k], "%d", pMem->n);
+ 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++] = ']';
+ k += sprintf(&zBuf[k], encnames[pMem->enc]);
+ zBuf[k++] = 0;
+ }
+}
+#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 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
+** 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->flags & SQLITE_Interrupt ) 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 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, sqlite3VdbeFinalize() should be
+** used to clean up the mess that was left behind.
+*/
+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 */
+ Mem *pTos; /* Top entry in the operand stack */
+#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
+#ifndef NDEBUG
+ Mem *pStackLimit;
+#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 );
+ pTos = p->pTos;
+ if( sqlite3_malloc_failed ) goto no_mem;
+ if( p->popStack ){
+ popStack(&pTos, p->popStack);
+ p->popStack = 0;
+ }
+ p->resOnStack = 0;
+ db->busyHandler.nBusy = 0;
+ CHECK_FOR_INTERRUPT;
+ for(pc=p->pc; rc==SQLITE_OK; pc++){
+ assert( pc>=0 && pc<p->nOp );
+ assert( pTos<=&p->aStack[pc] );
+ if( sqlite3_malloc_failed ) 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 && sqlite3OsFileExists("vdbe_sqltrace") ){
+ sqlite3VdbePrintSql(p);
+ }
+#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 ){
+ if( db->xProgress(db->pProgressArg)!=0 ){
+ rc = SQLITE_ABORT;
+ continue; /* skip to the next iteration of the for loop */
+ }
+ nProgressOps = 0;
+ }
+ nProgressOps++;
+ }
+#endif
+
+#ifndef NDEBUG
+ /* This is to check that the return value of static function
+ ** opcodeNoPush() (see vdbeaux.c) returns values that match the
+ ** implementation of the virtual machine in this file. If
+ ** opcodeNoPush() returns non-zero, then the stack is guarenteed
+ ** not to grow when the opcode is executed. If it returns zero, then
+ ** the stack may grow by at most 1.
+ **
+ ** The global wrapper function sqlite3VdbeOpcodeUsesStack() is not
+ ** available if NDEBUG is defined at build time.
+ */
+ pStackLimit = pTos;
+ if( !sqlite3VdbeOpcodeNoPush(pOp->opcode) ){
+ pStackLimit++;
+ }
+#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. 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.
+**
+** If a comment on the same line as the "case OP_" construction contains
+** the word "no-push", then the opcode is guarenteed not to grow the
+** vdbe stack when it is executed. See function opcode() in
+** vdbeaux.c for details.
+**
+** 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: { /* no-push */
+ 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: { /* no-push */
+ 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: { /* no-push */
+ assert( p->returnDepth>0 );
+ p->returnDepth--;
+ pc = p->returnStack[p->returnDepth] - 1;
+ break;
+}
+
+/* Opcode: Halt P1 P2 P3
+**
+** 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 P3 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: { /* no-push */
+ p->pTos = pTos;
+ p->rc = pOp->p1;
+ p->pc = pc;
+ p->errorAction = pOp->p2;
+ if( pOp->p3 ){
+ sqlite3SetString(&p->zErrMsg, pOp->p3, (char*)0);
+ }
+ rc = sqlite3VdbeHalt(p);
+ assert( rc==SQLITE_BUSY || rc==SQLITE_OK );
+ if( rc==SQLITE_BUSY ){
+ p->rc = SQLITE_BUSY;
+ return SQLITE_BUSY;
+ }
+ return p->rc ? SQLITE_ERROR : SQLITE_DONE;
+}
+
+/* Opcode: Integer P1 * *
+**
+** The 32-bit integer value P1 is pushed onto the stack.
+*/
+case OP_Integer: {
+ pTos++;
+ pTos->flags = MEM_Int;
+ pTos->i = pOp->p1;
+ break;
+}
+
+/* Opcode: Int64 * * P3
+**
+** P3 is a string representation of an integer. Convert that integer
+** to a 64-bit value and push it onto the stack.
+*/
+case OP_Int64: {
+ pTos++;
+ assert( pOp->p3!=0 );
+ pTos->flags = MEM_Str|MEM_Static|MEM_Term;
+ pTos->z = pOp->p3;
+ pTos->n = strlen(pTos->z);
+ pTos->enc = SQLITE_UTF8;
+ pTos->i = sqlite3VdbeIntValue(pTos);
+ pTos->flags |= MEM_Int;
+ break;
+}
+
+/* Opcode: Real * * P3
+**
+** The string value P3 is converted to a real and pushed on to the stack.
+*/
+case OP_Real: { /* same as TK_FLOAT, */
+ pTos++;
+ pTos->flags = MEM_Str|MEM_Static|MEM_Term;
+ pTos->z = pOp->p3;
+ pTos->n = strlen(pTos->z);
+ pTos->enc = SQLITE_UTF8;
+ pTos->r = sqlite3VdbeRealValue(pTos);
+ pTos->flags |= MEM_Real;
+ sqlite3VdbeChangeEncoding(pTos, db->enc);
+ break;
+}
+
+/* Opcode: String8 * * P3
+**
+** P3 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 */
+#ifndef SQLITE_OMIT_UTF16
+ pOp->opcode = OP_String;
+
+ assert( pOp->p3!=0 );
+ if( db->enc!=SQLITE_UTF8 ){
+ pTos++;
+ sqlite3VdbeMemSetStr(pTos, pOp->p3, -1, SQLITE_UTF8, SQLITE_STATIC);
+ if( SQLITE_OK!=sqlite3VdbeChangeEncoding(pTos, db->enc) ) goto no_mem;
+ if( SQLITE_OK!=sqlite3VdbeMemDynamicify(pTos) ) goto no_mem;
+ pTos->flags &= ~(MEM_Dyn);
+ pTos->flags |= MEM_Static;
+ if( pOp->p3type==P3_DYNAMIC ){
+ sqliteFree(pOp->p3);
+ }
+ pOp->p3type = P3_DYNAMIC;
+ pOp->p3 = pTos->z;
+ break;
+ }
+#endif
+ /* Otherwise fall through to the next case, OP_String */
+}
+
+/* Opcode: String * * P3
+**
+** The string value P3 is pushed onto the stack. If P3==0 then a
+** NULL is pushed onto the stack. P3 is assumed to be a nul terminated
+** string encoded with the database native encoding.
+*/
+case OP_String: {
+ pTos++;
+ assert( pOp->p3!=0 );
+ pTos->flags = MEM_Str|MEM_Static|MEM_Term;
+ pTos->z = pOp->p3;
+#ifndef SQLITE_OMIT_UTF16
+ if( db->enc==SQLITE_UTF8 ){
+ pTos->n = strlen(pTos->z);
+ }else{
+ pTos->n = sqlite3utf16ByteLen(pTos->z, -1);
+ }
+#else
+ assert( db->enc==SQLITE_UTF8 );
+ pTos->n = strlen(pTos->z);
+#endif
+ pTos->enc = db->enc;
+ break;
+}
+
+/* Opcode: Null * * *
+**
+** Push a NULL onto the stack.
+*/
+case OP_Null: {
+ pTos++;
+ pTos->flags = MEM_Null;
+ pTos->n = 0;
+ break;
+}
+
+
+#ifndef SQLITE_OMIT_BLOB_LITERAL
+/* Opcode: HexBlob * * P3
+**
+** P3 is an UTF-8 SQL hex encoding of a blob. The blob is pushed onto the
+** vdbe stack.
+**
+** The first time this instruction executes, in transforms itself into a
+** 'Blob' opcode with a binary blob as P3.
+*/
+case OP_HexBlob: { /* same as TK_BLOB */
+ pOp->opcode = OP_Blob;
+ pOp->p1 = strlen(pOp->p3)/2;
+ if( pOp->p1 ){
+ char *zBlob = sqlite3HexToBlob(pOp->p3);
+ if( !zBlob ) goto no_mem;
+ if( pOp->p3type==P3_DYNAMIC ){
+ sqliteFree(pOp->p3);
+ }
+ pOp->p3 = zBlob;
+ pOp->p3type = P3_DYNAMIC;
+ }else{
+ if( pOp->p3type==P3_DYNAMIC ){
+ sqliteFree(pOp->p3);
+ }
+ pOp->p3type = P3_STATIC;
+ pOp->p3 = "";
+ }
+
+ /* Fall through to the next case, OP_Blob. */
+}
+
+/* Opcode: Blob P1 * P3
+**
+** P3 points to a blob of data P1 bytes long. Push this
+** value onto the stack. 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 P3. This opcode is transformed to an OP_Blob
+** the first time it is executed.
+*/
+case OP_Blob: {
+ pTos++;
+ sqlite3VdbeMemSetStr(pTos, pOp->p3, pOp->p1, 0, 0);
+ break;
+}
+#endif /* SQLITE_OMIT_BLOB_LITERAL */
+
+/* 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 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: {
+ int j = pOp->p1 - 1;
+ assert( j>=0 && j<p->nVar );
+
+ pTos++;
+ sqlite3VdbeMemShallowCopy(pTos, &p->aVar[j], MEM_Static);
+ break;
+}
+
+/* Opcode: Pop P1 * *
+**
+** P1 elements are popped off of the top of stack and discarded.
+*/
+case OP_Pop: { /* no-push */
+ 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++;
+ sqlite3VdbeMemShallowCopy(pTos, pFrom, MEM_Ephem);
+ if( pOp->p2 ){
+ Deephemeralize(pTos);
+ }
+ 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: { /* no-push */
+ Mem *pFrom = &pTos[-pOp->p1];
+ int i;
+ Mem ts;
+
+ ts = *pFrom;
+ Deephemeralize(pTos);
+ for(i=0; i<pOp->p1; i++, pFrom++){
+ Deephemeralize(&pFrom[1]);
+ assert( (pFrom->flags & MEM_Ephem)==0 );
+ *pFrom = pFrom[1];
+ if( pFrom->flags & MEM_Short ){
+ assert( pFrom->flags & (MEM_Str|MEM_Blob) );
+ assert( pFrom->z==pFrom[1].zShort );
+ pFrom->z = pFrom->zShort;
+ }
+ }
+ *pTos = ts;
+ if( pTos->flags & MEM_Short ){
+ assert( pTos->flags & (MEM_Str|MEM_Blob) );
+ 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: { /* no-push */
+ Mem *pTo = &pTos[-pOp->p1];
+
+ assert( pTo>=p->aStack );
+ sqlite3VdbeMemMove(pTo, pTos);
+ pTos--;
+ 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: { /* no-push */
+ int i;
+ assert( p->nResColumn==pOp->p1 );
+
+ for(i=0; i<pOp->p1; i++){
+ Mem *pVal = &pTos[0-i];
+ sqlite3VdbeMemNulTerminate(pVal);
+ storeTypeInfo(pVal, db->enc);
+ }
+
+ p->resOnStack = 1;
+ p->nCallback++;
+ p->popStack = pOp->p1;
+ p->pc = pc + 1;
+ p->pTos = pTos;
+ return SQLITE_ROW;
+}
+
+/* Opcode: Concat P1 P2 *
+**
+** Look at the first P1+2 elements of the stack. Append them all
+** together with the lowest element first. The original P1+2 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.
+**
+** When P1==1, this routine makes a copy of the top stack element
+** into memory obtained from sqliteMalloc().
+*/
+case OP_Concat: { /* same as TK_CONCAT */
+ char *zNew;
+ int nByte;
+ int nField;
+ int i, j;
+ Mem *pTerm;
+
+ /* Loop through the stack elements to see how long the result will be. */
+ nField = pOp->p1 + 2;
+ pTerm = &pTos[1-nField];
+ nByte = 0;
+ for(i=0; i<nField; i++, pTerm++){
+ assert( pOp->p2==0 || (pTerm->flags&MEM_Str) );
+ if( pTerm->flags&MEM_Null ){
+ nByte = -1;
+ break;
+ }
+ Stringify(pTerm, db->enc);
+ nByte += pTerm->n;
+ }
+
+ if( nByte<0 ){
+ /* If nByte is less than zero, then there is a NULL value on the stack.
+ ** In this case just pop the values off the stack (if required) and
+ ** push on a NULL.
+ */
+ if( pOp->p2==0 ){
+ popStack(&pTos, nField);
+ }
+ pTos++;
+ pTos->flags = MEM_Null;
+ }else{
+ /* Otherwise malloc() space for the result and concatenate all the
+ ** stack values.
+ */
+ zNew = sqliteMallocRaw( nByte+2 );
+ if( zNew==0 ) goto no_mem;
+ j = 0;
+ pTerm = &pTos[1-nField];
+ for(i=j=0; i<nField; i++, pTerm++){
+ int n = pTerm->n;
+ assert( pTerm->flags & (MEM_Str|MEM_Blob) );
+ memcpy(&zNew[j], pTerm->z, n);
+ j += n;
+ }
+ zNew[j] = 0;
+ zNew[j+1] = 0;
+ assert( j==nByte );
+
+ if( pOp->p2==0 ){
+ popStack(&pTos, nField);
+ }
+ pTos++;
+ pTos->n = j;
+ pTos->flags = MEM_Str|MEM_Dyn|MEM_Term;
+ pTos->xDel = 0;
+ pTos->enc = db->enc;
+ 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: /* same as TK_PLUS, no-push */
+case OP_Subtract: /* same as TK_MINUS, no-push */
+case OP_Multiply: /* same as TK_STAR, no-push */
+case OP_Divide: /* same as TK_SLASH, no-push */
+case OP_Remainder: { /* same as TK_REM, no-push */
+ 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 ){
+ i64 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;
+ a = sqlite3VdbeRealValue(pTos);
+ b = sqlite3VdbeRealValue(pNos);
+ 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: CollSeq * * P3
+**
+** P3 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: { /* no-push */
+ assert( pOp->p3type==P3_COLLSEQ );
+ break;
+}
+
+/* Opcode: Function P1 P2 P3
+**
+** Invoke a user function (P3 is a pointer to a Function structure that
+** defines the function) with P2 arguments taken from the stack. Pop all
+** arguments from the stack and push back the result.
+**
+** 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->p2;
+
+ apVal = p->apArg;
+ assert( apVal || n==0 );
+
+ pArg = &pTos[1-n];
+ for(i=0; i<n; i++, pArg++){
+ apVal[i] = pArg;
+ storeTypeInfo(pArg, db->enc);
+ }
+
+ assert( pOp->p3type==P3_FUNCDEF || pOp->p3type==P3_VDBEFUNC );
+ if( pOp->p3type==P3_FUNCDEF ){
+ ctx.pFunc = (FuncDef*)pOp->p3;
+ ctx.pVdbeFunc = 0;
+ }else{
+ ctx.pVdbeFunc = (VdbeFunc*)pOp->p3;
+ ctx.pFunc = ctx.pVdbeFunc->pFunc;
+ }
+
+ ctx.s.flags = MEM_Null;
+ ctx.s.z = 0;
+ ctx.s.xDel = 0;
+ ctx.isError = 0;
+ if( ctx.pFunc->needCollSeq ){
+ assert( pOp>p->aOp );
+ assert( pOp[-1].p3type==P3_COLLSEQ );
+ assert( pOp[-1].opcode==OP_CollSeq );
+ ctx.pColl = (CollSeq *)pOp[-1].p3;
+ }
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ (*ctx.pFunc->xFunc)(&ctx, n, apVal);
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+ if( sqlite3_malloc_failed ) goto no_mem;
+ popStack(&pTos, n);
+
+ /* 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->p3 = (char *)ctx.pVdbeFunc;
+ pOp->p3type = P3_VDBEFUNC;
+ }
+
+ /* Copy the result of the function to the top of the stack */
+ sqlite3VdbeChangeEncoding(&ctx.s, db->enc);
+ pTos++;
+ pTos->flags = 0;
+ sqlite3VdbeMemMove(pTos, &ctx.s);
+
+ /* If the function returned an error, throw an exception */
+ if( ctx.isError ){
+ if( !(pTos->flags&MEM_Str) ){
+ sqlite3SetString(&p->zErrMsg, "user function error", (char*)0);
+ }else{
+ sqlite3SetString(&p->zErrMsg, sqlite3_value_text(pTos), (char*)0);
+ sqlite3VdbeChangeEncoding(pTos, db->enc);
+ }
+ 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 second element shifted
+** left by N bits where N is the top 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 second element shifted
+** right by N bits where N is the top element on the stack.
+** If either operand is NULL, the result is NULL.
+*/
+case OP_BitAnd: /* same as TK_BITAND, no-push */
+case OP_BitOr: /* same as TK_BITOR, no-push */
+case OP_ShiftLeft: /* same as TK_LSHIFT, no-push */
+case OP_ShiftRight: { /* same as TK_RSHIFT, no-push */
+ 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;
+ }
+ a = sqlite3VdbeIntValue(pNos);
+ b = sqlite3VdbeIntValue(pTos);
+ 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;
+ }
+ Release(pTos);
+ 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: { /* no-push */
+ 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: { /* no-push */
+ i64 v;
+ assert( pTos>=p->aStack );
+ applyAffinity(pTos, SQLITE_AFF_INTEGER, db->enc);
+ if( (pTos->flags & (MEM_Int|MEM_Real))==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: { /* no-push */
+ assert( pTos>=p->aStack );
+ applyAffinity(pTos, SQLITE_AFF_INTEGER, db->enc);
+ if( (pTos->flags & MEM_Int)==0 ){
+ if( pOp->p2==0 ){
+ rc = SQLITE_MISMATCH;
+ goto abort_due_to_error;
+ }else{
+ if( pOp->p1 ) popStack(&pTos, 1);
+ pc = pOp->p2 - 1;
+ }
+ }else{
+ Release(pTos);
+ pTos->flags = MEM_Int;
+ }
+ break;
+}
+
+#ifndef SQLITE_OMIT_CAST
+/* Opcode: ToInt * * *
+**
+** Force the value on the top of the stack to 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: { /* no-push */
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ) break;
+ assert( MEM_Str==(MEM_Blob>>3) );
+ pTos->flags |= (pTos->flags&MEM_Blob)>>3;
+ applyAffinity(pTos, SQLITE_AFF_INTEGER, db->enc);
+ sqlite3VdbeMemIntegerify(pTos);
+ break;
+}
+
+/* Opcode: ToNumeric * * *
+**
+** Force the value on the top of the stack 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: { /* no-push */
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ) break;
+ assert( MEM_Str==(MEM_Blob>>3) );
+ pTos->flags |= (pTos->flags&MEM_Blob)>>3;
+ applyAffinity(pTos, SQLITE_AFF_NUMERIC, db->enc);
+ if( (pTos->flags & (MEM_Int|MEM_Real))==0 ){
+ sqlite3VdbeMemRealify(pTos);
+ }else{
+ sqlite3VdbeMemRelease(pTos);
+ }
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ pTos->flags &= (MEM_Int|MEM_Real);
+ break;
+}
+
+/* Opcode: ToText * * *
+**
+** Force the value on the top of the stack to be text.
+** If the value is numeric, convert it to an 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: { /* no-push */
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ) break;
+ assert( MEM_Str==(MEM_Blob>>3) );
+ pTos->flags |= (pTos->flags&MEM_Blob)>>3;
+ applyAffinity(pTos, SQLITE_AFF_TEXT, db->enc);
+ assert( pTos->flags & MEM_Str );
+ pTos->flags &= ~(MEM_Int|MEM_Real|MEM_Blob);
+ break;
+}
+
+/* Opcode: ToBlob * * *
+**
+** Force the value on the top of the stack 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: { /* no-push */
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ) break;
+ if( (pTos->flags & MEM_Blob)==0 ){
+ applyAffinity(pTos, SQLITE_AFF_TEXT, db->enc);
+ assert( pTos->flags & MEM_Str );
+ pTos->flags |= MEM_Blob;
+ }
+ pTos->flags &= ~(MEM_Int|MEM_Real|MEM_Str);
+ break;
+}
+#endif /* SQLITE_OMIT_CAST */
+
+/* Opcode: Eq P1 P2 P3
+**
+** Pop the top two elements from the stack. If they are equal, then
+** jump to instruction P2. Otherwise, continue to the next instruction.
+**
+** If the 0x100 bit of P1 is true and either operand is NULL then take the
+** jump. If the 0x100 bit of P1 is clear then fall thru if either operand
+** is NULL.
+**
+** If the 0x200 bit of P1 is set and either operand is NULL then
+** both operands are converted to integers prior to comparison.
+** NULL operands are converted to zero and non-NULL operands are
+** converted to 1. Thus, for example, with 0x200 set, NULL==NULL is true
+** whereas it would normally be NULL. Similarly, NULL==123 is false when
+** 0x200 is set but is NULL when the 0x200 bit of P1 is clear.
+**
+** The least significant byte of P1 (mask 0xff) must be an affinity character -
+** 'n', 't', 'i' or 'o' - or 0x00. An attempt is made to coerce both values
+** according to the affinity before the comparison is made. If the byte is
+** 0x00, then numeric affinity is used.
+**
+** Once any conversions have taken place, and neither value is NULL,
+** the values are compared. If both values are blobs, or both are text,
+** then memcmp() is used to determine the results of the comparison. If
+** both values are numeric, then a numeric comparison is used. If the
+** two values are of different types, then they are inequal.
+**
+** 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.
+**
+** If P3 is not NULL it is a pointer to a collating sequence (a CollSeq
+** structure) that defines how to compare text.
+*/
+/* Opcode: Ne P1 P2 P3
+**
+** This works just like the Eq opcode except that the jump is taken if
+** the operands from the stack are not equal. See the Eq opcode for
+** additional information.
+*/
+/* Opcode: Lt P1 P2 P3
+**
+** This works just like the Eq opcode except that the jump is taken if
+** the 2nd element down on the stack is less than the top of the stack.
+** See the Eq opcode for additional information.
+*/
+/* Opcode: Le P1 P2 P3
+**
+** This works just like the Eq opcode except that the jump is taken if
+** the 2nd element down on the stack is less than or equal to the
+** top of the stack. See the Eq opcode for additional information.
+*/
+/* Opcode: Gt P1 P2 P3
+**
+** This works just like the Eq opcode except that the jump is taken if
+** the 2nd element down on the stack is greater than the top of the stack.
+** See the Eq opcode for additional information.
+*/
+/* Opcode: Ge P1 P2 P3
+**
+** This works just like the Eq opcode except that the jump is taken if
+** the 2nd element down on the stack is greater than or equal to the
+** top of the stack. See the Eq opcode for additional information.
+*/
+case OP_Eq: /* same as TK_EQ, no-push */
+case OP_Ne: /* same as TK_NE, no-push */
+case OP_Lt: /* same as TK_LT, no-push */
+case OP_Le: /* same as TK_LE, no-push */
+case OP_Gt: /* same as TK_GT, no-push */
+case OP_Ge: { /* same as TK_GE, no-push */
+ Mem *pNos;
+ int flags;
+ int res;
+ char affinity;
+
+ pNos = &pTos[-1];
+ flags = pTos->flags|pNos->flags;
+
+ /* If either value is a NULL P2 is not zero, take the jump if the least
+ ** significant byte of P1 is true. If P2 is zero, then push a NULL onto
+ ** the stack.
+ */
+ if( flags&MEM_Null ){
+ if( (pOp->p1 & 0x200)!=0 ){
+ /* The 0x200 bit of P1 means, roughly "do not treat NULL as the
+ ** magic SQL value it normally is - treat it as if it were another
+ ** integer".
+ **
+ ** With 0x200 set, if 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 0x200 set, NULL==NULL is true whereas it would normally
+ ** be NULL. Similarly, NULL!=123 is true.
+ */
+ sqlite3VdbeMemSetInt64(pTos, (pTos->flags & MEM_Null)==0);
+ sqlite3VdbeMemSetInt64(pNos, (pNos->flags & MEM_Null)==0);
+ }else{
+ /* If the 0x200 bit of P1 is clear and either operand is NULL then
+ ** the result is always NULL. The jump is taken if the 0x100 bit
+ ** of P1 is set.
+ */
+ popStack(&pTos, 2);
+ if( pOp->p2 ){
+ if( pOp->p1 & 0x100 ){
+ pc = pOp->p2-1;
+ }
+ }else{
+ pTos++;
+ pTos->flags = MEM_Null;
+ }
+ break;
+ }
+ }
+
+ affinity = pOp->p1 & 0xFF;
+ if( affinity ){
+ applyAffinity(pNos, affinity, db->enc);
+ applyAffinity(pTos, affinity, db->enc);
+ }
+
+ assert( pOp->p3type==P3_COLLSEQ || pOp->p3==0 );
+ res = sqlite3MemCompare(pNos, pTos, (CollSeq*)pOp->p3);
+ 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;
+ }
+
+ popStack(&pTos, 2);
+ if( pOp->p2 ){
+ if( res ){
+ pc = pOp->p2-1;
+ }
+ }else{
+ pTos++;
+ pTos->flags = MEM_Int;
+ pTos->i = res;
+ }
+ 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: /* same as TK_AND, no-push */
+case OP_Or: { /* same as TK_OR, no-push */
+ 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: /* same as TK_UMINUS, no-push */
+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);
+ 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: { /* same as TK_NOT, no-push */
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ) break; /* Do nothing to NULLs */
+ Integerify(pTos);
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ 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: { /* same as TK_BITNOT, no-push */
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ) break; /* Do nothing to NULLs */
+ Integerify(pTos);
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ pTos->i = ~pTos->i;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* 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.
+*/
+case OP_Explain:
+case OP_Noop: { /* no-push */
+ 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: /* no-push */
+case OP_IfNot: { /* no-push */
+ int c;
+ assert( pTos>=p->aStack );
+ if( pTos->flags & MEM_Null ){
+ c = pOp->p1;
+ }else{
+#ifdef SQLITE_OMIT_FLOATING_POINT
+ c = sqlite3VdbeIntValue(pTos);
+#else
+ c = sqlite3VdbeRealValue(pTos)!=0.0;
+#endif
+ if( pOp->opcode==OP_IfNot ) c = !c;
+ }
+ Release(pTos);
+ 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: { /* same as TK_ISNULL, no-push */
+ 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: { /* same as TK_NOTNULL, no-push */
+ 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: SetNumColumns P1 P2 *
+**
+** Before the OP_Column opcode can be executed on a cursor, this
+** opcode must be called to set the number of fields in the table.
+**
+** This opcode sets the number of columns for cursor P1 to P2.
+**
+** If OP_KeyAsData is to be applied to cursor P1, it must be executed
+** before this op-code.
+*/
+case OP_SetNumColumns: { /* no-push */
+ Cursor *pC;
+ assert( (pOp->p1)<p->nCursor );
+ assert( p->apCsr[pOp->p1]!=0 );
+ pC = p->apCsr[pOp->p1];
+ pC->nField = pOp->p2;
+ break;
+}
+
+/* Opcode: Column P1 P2 P3
+**
+** 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 there are less that (P2+1)
+** values in the record, push a NULL onto the stack.
+**
+** 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. The number of columns in the
+** record is stored on the stack just above the record itself.
+**
+** If the column contains fewer than P2 fields, then push a NULL. Or
+** if P3 is of type P3_MEM, then push the P3 value. The P3 value will
+** be default value for a column that has been added using the ALTER TABLE
+** ADD COLUMN command. If P3 is an ordinary string, just push a NULL.
+** When P3 is a string it is really just a comment describing the value
+** to be pushed, not a default value.
+*/
+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 */
+ u32 szHdr; /* Number of bytes in the record header */
+ int len; /* The length of the serialized data for the column */
+ int offset = 0; /* Offset into the data */
+ int idx; /* Index into the header */
+ int i; /* Loop counter */
+ char *zData; /* Part of the record being decoded */
+ Mem sMem; /* For storing the record being decoded */
+
+ sMem.flags = 0;
+ assert( p1<p->nCursor );
+ pTos++;
+ pTos->flags = 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 and
+ ** when we are decoded a record from the stack. 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. For
+ ** records on the stack, the next entry down on the stack is an integer
+ ** which is the number of records.
+ */
+ assert( p1<0 || p->apCsr[p1]!=0 );
+ if( p1<0 ){
+ /* Take the record off of the stack */
+ Mem *pRec = &pTos[p1];
+ Mem *pCnt = &pRec[-1];
+ assert( pRec>=p->aStack );
+ assert( pRec->flags & MEM_Blob );
+ payloadSize = pRec->n;
+ zRec = pRec->z;
+ assert( pCnt>=p->aStack );
+ assert( pCnt->flags & MEM_Int );
+ nField = pCnt->i;
+ pCrsr = 0;
+ }else if( (pC = p->apCsr[p1])->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->cacheValid ){
+ payloadSize = pC->payloadSize;
+ zRec = pC->aRow;
+ }else if( pC->isIndex ){
+ i64 payloadSize64;
+ sqlite3BtreeKeySize(pCrsr, &payloadSize64);
+ payloadSize = payloadSize64;
+ }else{
+ sqlite3BtreeDataSize(pCrsr, &payloadSize);
+ }
+ nField = pC->nField;
+#ifndef SQLITE_OMIT_TRIGGER
+ }else if( pC->pseudoTable ){
+ /* The record is the sole entry of a pseudo-table */
+ payloadSize = pC->nData;
+ zRec = pC->pData;
+ pC->cacheValid = 0;
+ assert( payloadSize==0 || zRec!=0 );
+ nField = pC->nField;
+ pCrsr = 0;
+#endif
+ }else{
+ zRec = 0;
+ payloadSize = 0;
+ pCrsr = 0;
+ nField = 0;
+ }
+
+ /* If payloadSize is 0, then just push a NULL onto the stack. */
+ if( payloadSize==0 ){
+ pTos->flags = MEM_Null;
+ break;
+ }
+
+ assert( p2<nField );
+
+ /* Read and parse the table header. Store the results of the parse
+ ** into the record header cache fields of the cursor.
+ */
+ if( pC && pC->cacheValid ){
+ aType = pC->aType;
+ aOffset = pC->aOffset;
+ }else{
+ int avail; /* Number of bytes of available data */
+ if( pC && pC->aType ){
+ aType = pC->aType;
+ }else{
+ aType = sqliteMallocRaw( 2*nField*sizeof(aType) );
+ }
+ aOffset = &aType[nField];
+ if( aType==0 ){
+ goto no_mem;
+ }
+
+ /* 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 = pC->aRow = zData;
+ }else{
+ pC->aRow = 0;
+ }
+ }
+ idx = sqlite3GetVarint32(zData, &szHdr);
+
+
+ /* 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<szHdr ){
+ rc = sqlite3VdbeMemFromBtree(pCrsr, 0, szHdr, pC->isIndex, &sMem);
+ if( rc!=SQLITE_OK ){
+ goto op_column_out;
+ }
+ zData = sMem.z;
+ }
+
+ /* 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
+ */
+ offset = szHdr;
+ assert( offset>0 );
+ i = 0;
+ while( idx<szHdr && i<nField && offset<=payloadSize ){
+ aOffset[i] = offset;
+ idx += sqlite3GetVarint32(&zData[idx], &aType[i]);
+ offset += sqlite3VdbeSerialTypeLen(aType[i]);
+ i++;
+ }
+ Release(&sMem);
+ sMem.flags = MEM_Null;
+
+ /* 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 push a NULL onto the
+ ** stack instead of deserializing a value from the record.
+ */
+ while( i<nField ){
+ aOffset[i++] = 0;
+ }
+
+ /* The header should end at the start of data and the data should
+ ** end at last byte of the record. If this is not the case then
+ ** we are dealing with a malformed record.
+ */
+ if( idx!=szHdr || offset!=payloadSize ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto op_column_out;
+ }
+
+ /* Remember all aType and aColumn information if we have a cursor
+ ** to remember it in. */
+ if( pC ){
+ pC->payloadSize = payloadSize;
+ pC->aType = aType;
+ pC->aOffset = aOffset;
+ pC->cacheValid = 1;
+ }
+ }
+
+ /* 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 P3 if P3 is
+ ** a pointer to a Mem object.
+ */
+ if( aOffset[p2] ){
+ assert( rc==SQLITE_OK );
+ if( zRec ){
+ zData = &zRec[aOffset[p2]];
+ }else{
+ len = sqlite3VdbeSerialTypeLen(aType[p2]);
+ rc = sqlite3VdbeMemFromBtree(pCrsr, aOffset[p2], len, pC->isIndex,&sMem);
+ if( rc!=SQLITE_OK ){
+ goto op_column_out;
+ }
+ zData = sMem.z;
+ }
+ sqlite3VdbeSerialGet(zData, aType[p2], pTos);
+ pTos->enc = db->enc;
+ }else{
+ if( pOp->p3type==P3_MEM ){
+ sqlite3VdbeMemShallowCopy(pTos, (Mem *)(pOp->p3), MEM_Static);
+ }else{
+ pTos->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 pTos structure rather.
+ ** This prevents a memory copy.
+ */
+ if( (sMem.flags & MEM_Dyn)!=0 ){
+ assert( pTos->flags & MEM_Ephem );
+ assert( pTos->flags & (MEM_Str|MEM_Blob) );
+ assert( pTos->z==sMem.z );
+ assert( sMem.flags & MEM_Term );
+ pTos->flags &= ~MEM_Ephem;
+ pTos->flags |= MEM_Dyn|MEM_Term;
+ }
+
+ /* pTos->z might be pointing to sMem.zShort[]. Fix that so that we
+ ** can abandon sMem */
+ rc = sqlite3VdbeMemMakeWriteable(pTos);
+
+op_column_out:
+ /* Release the aType[] memory if we are not dealing with cursor */
+ if( !pC || !pC->aType ){
+ sqliteFree(aType);
+ }
+ break;
+}
+
+/* Opcode: MakeRecord P1 P2 P3
+**
+** Convert the top abs(P1) entries of the stack 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 and as long as the
+** sqlite3VdbeRecordCompare function will correctly compare two encoded
+** records. Refer to source code comments for the details of the record
+** format.
+**
+** The original stack entries are popped from the stack if P1>0 but
+** remain on the stack if P1<0.
+**
+** If P2 is not zero and one or more of the entries are NULL, then jump
+** to the address given by P2. This feature can be used to skip a
+** uniqueness test on indices.
+**
+** P3 may be a string that is P1 characters long. The nth character of the
+** string indicates the column affinity that should be used for the nth
+** field of the index key (i.e. the first character of P3 corresponds to the
+** lowest element on the stack).
+**
+** The mapping from character to affinity is as follows:
+** 'n' = NUMERIC.
+** 'i' = INTEGER.
+** 't' = TEXT.
+** 'o' = NONE.
+**
+** If P3 is NULL then all index fields have the affinity NONE.
+**
+** See also OP_MakeIdxRec
+*/
+/* Opcode: MakeRecordI P1 P2 P3
+**
+** This opcode works just OP_MakeRecord except that it reads an extra
+** integer from the stack (thus reading a total of abs(P1+1) entries)
+** and appends that extra integer to the end of the record as a varint.
+** This results in an index key.
+*/
+case OP_MakeIdxRec:
+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 the lowest element of the stack and data(N-1) is
+ ** the top of the stack.
+ **
+ ** 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.
+ */
+ unsigned char *zNewRecord;
+ unsigned char *zCsr;
+ Mem *pRec;
+ Mem *pRowid = 0;
+ int nData = 0; /* Number of bytes of data space */
+ int nHdr = 0; /* Number of bytes of header space */
+ int nByte = 0; /* Space required for this record */
+ int nVarint; /* Number of bytes in a varint */
+ u32 serial_type; /* Type field */
+ int containsNull = 0; /* True if any of the data fields are NULL */
+ char zTemp[NBFS]; /* Space to hold small records */
+ Mem *pData0;
+
+ int leaveOnStack; /* If true, leave the entries on the stack */
+ int nField; /* Number of fields in the record */
+ int jumpIfNull; /* Jump here if non-zero and any entries are NULL. */
+ int addRowid; /* True to append a rowid column at the end */
+ char *zAffinity; /* The affinity string for the record */
+
+ leaveOnStack = ((pOp->p1<0)?1:0);
+ nField = pOp->p1 * (leaveOnStack?-1:1);
+ jumpIfNull = pOp->p2;
+ addRowid = pOp->opcode==OP_MakeIdxRec;
+ zAffinity = pOp->p3;
+
+ pData0 = &pTos[1-nField];
+ assert( pData0>=p->aStack );
+ containsNull = 0;
+
+ /* 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<=pTos; pRec++){
+ if( zAffinity ){
+ applyAffinity(pRec, zAffinity[pRec-pData0], db->enc);
+ }
+ if( pRec->flags&MEM_Null ){
+ containsNull = 1;
+ }
+ serial_type = sqlite3VdbeSerialType(pRec);
+ nData += sqlite3VdbeSerialTypeLen(serial_type);
+ nHdr += sqlite3VarintLen(serial_type);
+ }
+
+ /* If we have to append a varint rowid to this record, set 'rowid'
+ ** to the value of the rowid and increase nByte by the amount of space
+ ** required to store it and the 0x00 seperator byte.
+ */
+ if( addRowid ){
+ pRowid = &pTos[0-nField];
+ assert( pRowid>=p->aStack );
+ Integerify(pRowid);
+ serial_type = sqlite3VdbeSerialType(pRowid);
+ nData += sqlite3VdbeSerialTypeLen(serial_type);
+ nHdr += sqlite3VarintLen(serial_type);
+ }
+
+ /* Add the initial header varint and total the size */
+ nHdr += nVarint = sqlite3VarintLen(nHdr);
+ if( nVarint<sqlite3VarintLen(nHdr) ){
+ nHdr++;
+ }
+ nByte = nHdr+nData;
+
+ /* Allocate space for the new record. */
+ if( nByte>sizeof(zTemp) ){
+ zNewRecord = sqliteMallocRaw(nByte);
+ if( !zNewRecord ){
+ goto no_mem;
+ }
+ }else{
+ zNewRecord = zTemp;
+ }
+
+ /* Write the record */
+ zCsr = zNewRecord;
+ zCsr += sqlite3PutVarint(zCsr, nHdr);
+ for(pRec=pData0; pRec<=pTos; pRec++){
+ serial_type = sqlite3VdbeSerialType(pRec);
+ zCsr += sqlite3PutVarint(zCsr, serial_type); /* serial type */
+ }
+ if( addRowid ){
+ zCsr += sqlite3PutVarint(zCsr, sqlite3VdbeSerialType(pRowid));
+ }
+ for(pRec=pData0; pRec<=pTos; pRec++){
+ zCsr += sqlite3VdbeSerialPut(zCsr, pRec); /* serial data */
+ }
+ if( addRowid ){
+ zCsr += sqlite3VdbeSerialPut(zCsr, pRowid);
+ }
+ assert( zCsr==(zNewRecord+nByte) );
+
+ /* Pop entries off the stack if required. Push the new record on. */
+ if( !leaveOnStack ){
+ popStack(&pTos, nField+addRowid);
+ }
+ pTos++;
+ pTos->n = nByte;
+ if( nByte<=sizeof(zTemp) ){
+ assert( zNewRecord==(unsigned char *)zTemp );
+ pTos->z = pTos->zShort;
+ memcpy(pTos->zShort, zTemp, nByte);
+ pTos->flags = MEM_Blob | MEM_Short;
+ }else{
+ assert( zNewRecord!=(unsigned char *)zTemp );
+ pTos->z = zNewRecord;
+ pTos->flags = MEM_Blob | MEM_Dyn;
+ pTos->xDel = 0;
+ }
+ pTos->enc = SQLITE_UTF8; /* In case the blob is ever converted to text */
+
+ /* If a NULL was encountered and jumpIfNull is non-zero, take the jump. */
+ if( jumpIfNull && containsNull ){
+ pc = jumpIfNull - 1;
+ }
+ break;
+}
+
+/* Opcode: Statement P1 * *
+**
+** Begin an individual statement transaction which is part of a larger
+** BEGIN..COMMIT 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.
+**
+** 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: { /* no-push */
+ int i = pOp->p1;
+ Btree *pBt;
+ if( i>=0 && i<db->nDb && (pBt = db->aDb[i].pBt) && !(db->autoCommit) ){
+ assert( sqlite3BtreeIsInTrans(pBt) );
+ if( !sqlite3BtreeIsInStmt(pBt) ){
+ rc = sqlite3BtreeBeginStmt(pBt);
+ }
+ }
+ 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: { /* no-push */
+ 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", 0);
+ rc = SQLITE_ERROR;
+ }else if( i!=db->autoCommit ){
+ db->autoCommit = i;
+ if( pOp->p2 ){
+ assert( i==1 );
+ sqlite3RollbackAll(db);
+ }else if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
+ p->pTos = pTos;
+ p->pc = pc;
+ db->autoCommit = 1-i;
+ p->rc = SQLITE_BUSY;
+ return SQLITE_BUSY;
+ }
+ return SQLITE_DONE;
+ }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"), 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.
+**
+** 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: { /* no-push */
+ int i = pOp->p1;
+ Btree *pBt;
+
+ assert( i>=0 && i<db->nDb );
+ pBt = db->aDb[i].pBt;
+
+ if( pBt ){
+ rc = sqlite3BtreeBeginTrans(pBt, pOp->p2);
+ if( rc==SQLITE_BUSY ){
+ p->pc = pc;
+ p->rc = SQLITE_BUSY;
+ p->pTos = pTos;
+ return SQLITE_BUSY;
+ }
+ if( rc!=SQLITE_OK && rc!=SQLITE_READONLY /* && rc!=SQLITE_BUSY */ ){
+ goto abort_due_to_error;
+ }
+ }
+ 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 iMeta;
+ assert( pOp->p2<SQLITE_N_BTREE_META );
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ assert( db->aDb[pOp->p1].pBt!=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[pOp->p1].pBt, 1 + pOp->p2, (u32 *)&iMeta);
+ pTos++;
+ pTos->i = iMeta;
+ 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: { /* no-push */
+ Db *pDb;
+ assert( pOp->p2<SQLITE_N_BTREE_META );
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ pDb = &db->aDb[pOp->p1];
+ assert( pDb->pBt!=0 );
+ assert( pTos>=p->aStack );
+ Integerify(pTos);
+ /* See note about index shifting on OP_ReadCookie */
+ rc = sqlite3BtreeUpdateMeta(pDb->pBt, 1+pOp->p2, (int)pTos->i);
+ if( pOp->p2==0 ){
+ /* When the schema cookie changes, record the new cookie internally */
+ pDb->schema_cookie = pTos->i;
+ db->flags |= SQLITE_InternChanges;
+ }
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ 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: { /* no-push */
+ int iMeta;
+ Btree *pBt;
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ 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 ){
+ sqlite3SetString(&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 a pointer to a KeyInfo structure that defines the
+** content and collating sequence of indices. P3 is NULL for cursors
+** that are not pointing to indices.
+**
+** 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 a pointer to a KeyInfo structure that defines the
+** content and collating sequence of indices. P3 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: /* no-push */
+case OP_OpenWrite: { /* no-push */
+ int i = pOp->p1;
+ int p2 = pOp->p2;
+ int wrFlag;
+ Btree *pX;
+ int iDb;
+ Cursor *pCur;
+
+ assert( pTos>=p->aStack );
+ Integerify(pTos);
+ iDb = pTos->i;
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ 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;
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ pTos--;
+ assert( p2>=2 );
+ }
+ assert( i>=0 );
+ pCur = allocateCursor(p, i);
+ if( pCur==0 ) goto no_mem;
+ pCur->nullRow = 1;
+ if( pX==0 ) break;
+ /* We always provide a key comparison function. If the table being
+ ** opened is of type INTKEY, the comparision function will be ignored. */
+ rc = sqlite3BtreeCursor(pX, p2, wrFlag,
+ sqlite3VdbeRecordCompare, pOp->p3,
+ &pCur->pCursor);
+ if( pOp->p3type==P3_KEYINFO ){
+ pCur->pKeyInfo = (KeyInfo*)pOp->p3;
+ pCur->pIncrKey = &pCur->pKeyInfo->incrKey;
+ pCur->pKeyInfo->enc = p->db->enc;
+ }else{
+ pCur->pKeyInfo = 0;
+ pCur->pIncrKey = &pCur->bogusIncrKey;
+ }
+ switch( rc ){
+ case SQLITE_BUSY: {
+ p->pc = pc;
+ p->rc = SQLITE_BUSY;
+ p->pTos = &pTos[1 + (pOp->p2<=0)]; /* Operands must remain on stack */
+ return SQLITE_BUSY;
+ }
+ 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 P3==0 it means we are expected to open a table. If P3!=0 then
+ ** we expect to be opening an index. If this is not what happened,
+ ** then the database is corrupt
+ */
+ if( (pCur->isTable && pOp->p3type==P3_KEYINFO)
+ || (pCur->isIndex && pOp->p3type!=P3_KEYINFO) ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto abort_due_to_error;
+ }
+ break;
+ }
+ case SQLITE_EMPTY: {
+ pCur->isTable = pOp->p3type!=P3_KEYINFO;
+ pCur->isIndex = !pCur->isTable;
+ rc = SQLITE_OK;
+ break;
+ }
+ default: {
+ goto abort_due_to_error;
+ }
+ }
+ break;
+}
+
+/* Opcode: OpenVirtual P1 P2 P3
+**
+** Open a new cursor P1 to a transient or virtual 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 P3==0 and to a BTree index
+** if P3 is not 0. If P3 is not NULL, it points to a KeyInfo structure
+** that defines the format of keys in the index.
+*/
+case OP_OpenVirtual: { /* no-push */
+ int i = pOp->p1;
+ Cursor *pCx;
+ assert( i>=0 );
+ pCx = allocateCursor(p, i);
+ if( pCx==0 ) goto no_mem;
+ pCx->nullRow = 1;
+ rc = sqlite3BtreeFactory(db, 0, 1, TEMP_PAGES, &pCx->pBt, 0 /*!exclusive*/, 1/*allowReadonly*/);
+ 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->p3 ){
+ int pgno;
+ assert( pOp->p3type==P3_KEYINFO );
+ rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_ZERODATA);
+ if( rc==SQLITE_OK ){
+ assert( pgno==MASTER_ROOT+1 );
+ rc = sqlite3BtreeCursor(pCx->pBt, pgno, 1, sqlite3VdbeRecordCompare,
+ pOp->p3, &pCx->pCursor);
+ pCx->pKeyInfo = (KeyInfo*)pOp->p3;
+ pCx->pKeyInfo->enc = p->db->enc;
+ pCx->pIncrKey = &pCx->pKeyInfo->incrKey;
+ }
+ pCx->isTable = 0;
+ }else{
+ rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, 1, 0, 0, &pCx->pCursor);
+ pCx->isTable = 1;
+ pCx->pIncrKey = &pCx->bogusIncrKey;
+ }
+ }
+ pCx->nField = pOp->p2;
+ pCx->isIndex = !pCx->isTable;
+ break;
+}
+
+#ifndef SQLITE_OMIT_TRIGGER
+/* 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: { /* no-push */
+ int i = pOp->p1;
+ Cursor *pCx;
+ assert( i>=0 );
+ pCx = allocateCursor(p, i);
+ if( pCx==0 ) goto no_mem;
+ pCx->nullRow = 1;
+ pCx->pseudoTable = 1;
+ pCx->pIncrKey = &pCx->bogusIncrKey;
+ pCx->isTable = 1;
+ pCx->isIndex = 0;
+ break;
+}
+#endif
+
+/* 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: { /* no-push */
+ int i = pOp->p1;
+ if( i>=0 && i<p->nCursor ){
+ sqlite3VdbeFreeCursor(p->apCsr[i]);
+ p->apCsr[i] = 0;
+ }
+ break;
+}
+
+/* Opcode: MoveGe P1 P2 *
+**
+** Pop the top of the stack and use its value as a key. Reposition
+** cursor P1 so that it points to the smallest entry that is greater
+** than or equal to the key that was popped ffrom the stack.
+** If there are no records greater than or equal to the key and P2
+** is not zero, then jump to P2.
+**
+** See also: Found, NotFound, Distinct, MoveLt, MoveGt, MoveLe
+*/
+/* Opcode: MoveGt P1 P2 *
+**
+** Pop the top of the stack and use its value as a key. Reposition
+** cursor P1 so that it points to the smallest entry that is greater
+** than the key from the stack.
+** 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 *
+**
+** Pop the top of the stack and use its value as a key. Reposition
+** cursor P1 so that it points to the largest entry that is less
+** than the key from the stack.
+** 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 *
+**
+** Pop the top of the stack and use its value as a key. Reposition
+** cursor P1 so that it points to the largest entry that is less than
+** or equal to the key that was popped from the stack.
+** If there are no records less than or eqal to the key and P2 is not zero,
+** then jump to P2.
+**
+** See also: Found, NotFound, Distinct, MoveGt, MoveGe, MoveLt
+*/
+case OP_MoveLt: /* no-push */
+case OP_MoveLe: /* no-push */
+case OP_MoveGe: /* no-push */
+case OP_MoveGt: { /* no-push */
+ int i = pOp->p1;
+ Cursor *pC;
+
+ assert( pTos>=p->aStack );
+ 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;
+ Integerify(pTos);
+ iKey = intToKey(pTos->i);
+ if( pOp->p2==0 && pOp->opcode==OP_MoveGe ){
+ pC->movetoTarget = iKey;
+ pC->deferredMoveto = 1;
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ pTos--;
+ break;
+ }
+ rc = sqlite3BtreeMoveto(pC->pCursor, 0, (u64)iKey, &res);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ pC->lastRowid = pTos->i;
+ pC->rowidIsValid = res==0;
+ }else{
+ Stringify(pTos, db->enc);
+ rc = sqlite3BtreeMoveto(pC->pCursor, pTos->z, pTos->n, &res);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ pC->rowidIsValid = 0;
+ }
+ pC->deferredMoveto = 0;
+ pC->cacheValid = 0;
+ *pC->pIncrKey = 0;
+ sqlite3_search_count++;
+ 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);
+ }
+ }
+ if( res ){
+ if( pOp->p2>0 ){
+ pc = pOp->p2 - 1;
+ }else{
+ pC->nullRow = 1;
+ }
+ }
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: Distinct P1 P2 *
+**
+** Use the top of the stack as a record created using MakeRecord. P1 is a
+** cursor on a table that declared as an index. If that table contains an
+** entry that matches the top of the stack fall thru. If the top of the stack
+** matches no entry in P1 then jump to P2.
+**
+** The cursor is left pointing at the matching entry if it exists. The
+** record on the top of the stack is not popped.
+**
+** This instruction is similar to NotFound except that this operation
+** does not pop the key from the stack.
+**
+** The instruction is used to implement the DISTINCT operator on SELECT
+** statements. The P1 table is not a true index but rather a record of
+** all results that have produced so far.
+**
+** See also: Found, NotFound, MoveTo, IsUnique, NotExists
+*/
+/* Opcode: Found P1 P2 *
+**
+** Top of the stack holds a blob constructed by MakeRecord. P1 is an index.
+** If an entry that matches the top of the stack exists in P1 then
+** jump to P2. If the top of the stack does not match any entry in P1
+** then fall thru. The P1 cursor is left pointing at the matching entry
+** if it exists. The blob is popped off the top of the stack.
+**
+** This instruction is used to implement the IN operator where the
+** left-hand side is a SELECT statement. P1 is not a true index but
+** is instead a temporary index that holds the results of the SELECT
+** statement. This instruction just checks to see if the left-hand side
+** of the IN operator (stored on the top of the stack) exists in the
+** result of the SELECT statement.
+**
+** See also: Distinct, NotFound, MoveTo, IsUnique, NotExists
+*/
+/* Opcode: NotFound P1 P2 *
+**
+** The top of the stack holds a blob constructed by MakeRecord. P1 is
+** an index. If no entry exists in P1 that matches the blob then jump
+** to P1. If an entry does existing, fall through. The cursor is left
+** pointing to the entry that matches. The blob 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: /* no-push */
+case OP_NotFound: /* no-push */
+case OP_Found: { /* no-push */
+ int i = pOp->p1;
+ int alreadyExists = 0;
+ Cursor *pC;
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pC = p->apCsr[i])->pCursor!=0 ){
+ int res, rx;
+ assert( pC->isTable==0 );
+ Stringify(pTos, db->enc);
+ rx = sqlite3BtreeMoveto(pC->pCursor, pTos->z, pTos->n, &res);
+ alreadyExists = rx==SQLITE_OK && res==0;
+ pC->deferredMoveto = 0;
+ pC->cacheValid = 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 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 pushed onto the stack and control
+** falls through to the next instruction.
+**
+** See also: Distinct, NotFound, NotExists, Found
+*/
+case OP_IsUnique: { /* no-push */
+ int i = pOp->p1;
+ Mem *pNos = &pTos[-1];
+ Cursor *pCx;
+ BtCursor *pCrsr;
+ i64 R;
+
+ /* Pop the value R off the top of the stack
+ */
+ assert( pNos>=p->aStack );
+ Integerify(pTos);
+ R = pTos->i;
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ pTos--;
+ assert( i>=0 && i<=p->nCursor );
+ pCx = p->apCsr[i];
+ assert( pCx!=0 );
+ pCrsr = pCx->pCursor;
+ if( pCrsr!=0 ){
+ int res, rc;
+ 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
+ */
+ Stringify(pNos, db->enc);
+ zKey = pNos->z;
+ nKey = pNos->n;
+
+ szRowid = sqlite3VdbeIdxRowidLen(nKey, 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->cacheValid = 0;
+ rc = sqlite3BtreeMoveto(pCrsr, zKey, len, &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, len, 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. Push it onto
+ ** the stack. (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 that P1 is a table whereas
+** NotFound assumes key is a blob constructed from MakeRecord and
+** P1 is an index.
+**
+** See also: Distinct, Found, MoveTo, NotFound, IsUnique
+*/
+case OP_NotExists: { /* no-push */
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
+ int res;
+ u64 iKey;
+ assert( pTos->flags & MEM_Int );
+ assert( p->apCsr[i]->isTable );
+ iKey = intToKey(pTos->i);
+ rc = sqlite3BtreeMoveto(pCrsr, 0, iKey, &res);
+ pC->lastRowid = pTos->i;
+ pC->rowidIsValid = res==0;
+ pC->nullRow = 0;
+ pC->cacheValid = 0;
+ if( res!=0 ){
+ pc = pOp->p2 - 1;
+ pC->rowidIsValid = 0;
+ }
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: Sequence P1 * *
+**
+** Push an integer onto the stack which is the next available
+** sequence number for cursor P1. The sequence number on the
+** cursor is incremented after the push.
+*/
+case OP_Sequence: {
+ int i = pOp->p1;
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ pTos++;
+ pTos->i = p->apCsr[i]->seqCount++;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+
+/* Opcode: NewRowid P1 P2 *
+**
+** 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 pushed
+** onto the stack.
+**
+** If P2>0 then P2 is a memory cell 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 P2 memory cell is updated with the generated
+** record number. This P2 mechanism is used to help implement the
+** AUTOINCREMENT feature.
+*/
+case OP_NewRowid: {
+ 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{
+ rx = sqlite3BtreeLast(pC->pCursor, &res);
+ 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->p2 ){
+ Mem *pMem;
+ assert( pOp->p2>0 && pOp->p2<p->nMem ); /* P2 is a valid memory cell */
+ pMem = &p->aMem[pOp->p2];
+ Integerify(pMem);
+ assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P2) holds an integer */
+ if( pMem->i==MAX_ROWID || pC->useRandomRowid ){
+ rc = SQLITE_FULL;
+ goto abort_due_to_error;
+ }
+ if( v<pMem->i+1 ){
+ v = pMem->i + 1;
+ }
+ pMem->i = v;
+ }
+#endif
+
+ if( v<MAX_ROWID ){
+ pC->nextRowidValid = 1;
+ pC->nextRowid = v+1;
+ }else{
+ pC->nextRowidValid = 0;
+ }
+ }
+ if( pC->useRandomRowid ){
+ assert( pOp->p2==0 ); /* SQLITE_FULL must have occurred prior to this */
+ v = db->priorNewRowid;
+ cnt = 0;
+ do{
+ if( v==0 || cnt>2 ){
+ sqlite3Randomness(sizeof(v), &v);
+ if( cnt<5 ) v &= 0xffffff;
+ }else{
+ unsigned char r;
+ sqlite3Randomness(1, &r);
+ v += r + 1;
+ }
+ if( v==0 ) continue;
+ x = intToKey(v);
+ rx = sqlite3BtreeMoveto(pC->pCursor, 0, (u64)x, &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->rowidIsValid = 0;
+ pC->deferredMoveto = 0;
+ pC->cacheValid = 0;
+ }
+ pTos++;
+ pTos->i = v;
+ pTos->flags = MEM_Int;
+ break;
+}
+
+/* Opcode: Insert 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_LASTROWID flag of P2 is set,
+** then rowid is stored for subsequent return by the
+** sqlite3_last_insert_rowid() function (otherwise it's unmodified).
+**
+** This instruction only works on tables. The equivalent instruction
+** for indices is OP_IdxInsert.
+*/
+case OP_Insert: { /* no-push */
+ Mem *pNos = &pTos[-1];
+ int i = pOp->p1;
+ Cursor *pC;
+ assert( pNos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( ((pC = p->apCsr[i])->pCursor!=0 || pC->pseudoTable) ){
+ i64 iKey; /* The integer ROWID or key for the record to be inserted */
+
+ assert( pNos->flags & MEM_Int );
+ assert( pC->isTable );
+ iKey = intToKey(pNos->i);
+
+ if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++;
+ if( pOp->p2 & OPFLAG_LASTROWID ) db->lastRowid = pNos->i;
+ 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_Blob|MEM_Str) );
+ }
+#ifndef SQLITE_OMIT_TRIGGER
+ if( pC->pseudoTable ){
+ 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+2 );
+ if( !pC->pData ) goto no_mem;
+ memcpy(pC->pData, pTos->z, pC->nData);
+ pC->pData[pC->nData] = 0;
+ pC->pData[pC->nData+1] = 0;
+ }
+ pC->nullRow = 0;
+ }else{
+#endif
+ rc = sqlite3BtreeInsert(pC->pCursor, 0, iKey, pTos->z, pTos->n);
+#ifndef SQLITE_OMIT_TRIGGER
+ }
+#endif
+
+ pC->rowidIsValid = 0;
+ pC->deferredMoveto = 0;
+ pC->cacheValid = 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 P1 is a pseudo-table, then this instruction is a no-op.
+*/
+case OP_Delete: { /* no-push */
+ int i = pOp->p1;
+ Cursor *pC;
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ if( pC->pCursor!=0 ){
+ rc = sqlite3VdbeCursorMoveto(pC);
+ if( rc ) goto abort_due_to_error;
+ rc = sqlite3BtreeDelete(pC->pCursor);
+ pC->nextRowidValid = 0;
+ pC->cacheValid = 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: { /* no-push */
+ if( pOp->p1 ){
+ sqlite3VdbeSetChanges(db, p->nChange);
+ }
+ p->nChange = 0;
+ 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;
+ u32 n;
+
+ /* Note that RowKey and RowData are really exactly the same instruction */
+ pTos++;
+ 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 );
+ if( pC->nullRow ){
+ pTos->flags = MEM_Null;
+ }else if( pC->pCursor!=0 ){
+ BtCursor *pCrsr = pC->pCursor;
+ rc = sqlite3VdbeCursorMoveto(pC);
+ if( rc ) goto abort_due_to_error;
+ if( pC->nullRow ){
+ pTos->flags = MEM_Null;
+ break;
+ }else if( pC->isIndex ){
+ i64 n64;
+ assert( !pC->isTable );
+ sqlite3BtreeKeySize(pCrsr, &n64);
+ n = n64;
+ }else{
+ sqlite3BtreeDataSize(pCrsr, &n);
+ }
+ pTos->n = n;
+ if( n<=NBFS ){
+ pTos->flags = MEM_Blob | MEM_Short;
+ pTos->z = pTos->zShort;
+ }else{
+ char *z = sqliteMallocRaw( n );
+ if( z==0 ) goto no_mem;
+ pTos->flags = MEM_Blob | MEM_Dyn;
+ pTos->xDel = 0;
+ pTos->z = z;
+ }
+ if( pC->isIndex ){
+ sqlite3BtreeKey(pCrsr, 0, n, pTos->z);
+ }else{
+ sqlite3BtreeData(pCrsr, 0, n, pTos->z);
+ }
+#ifndef SQLITE_OMIT_TRIGGER
+ }else if( pC->pseudoTable ){
+ pTos->n = pC->nData;
+ pTos->z = pC->pData;
+ pTos->flags = MEM_Blob|MEM_Ephem;
+#endif
+ }else{
+ pTos->flags = MEM_Null;
+ }
+ pTos->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */
+ break;
+}
+
+/* Opcode: Rowid P1 * *
+**
+** Push onto the stack an integer which is the key of the table entry that
+** P1 is currently point to.
+*/
+case OP_Rowid: {
+ 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;
+ pTos++;
+ if( pC->rowidIsValid ){
+ v = pC->lastRowid;
+ }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 );
+ sqlite3BtreeKeySize(pC->pCursor, &v);
+ v = keyToInt(v);
+ }
+ pTos->i = v;
+ pTos->flags = 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 push
+** a NULL onto the stack.
+*/
+case OP_NullRow: { /* no-push */
+ 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: { /* no-push */
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+
+ assert( i>=0 && i<p->nCursor );
+ pC = p->apCsr[i];
+ assert( pC!=0 );
+ if( (pCrsr = pC->pCursor)!=0 ){
+ int res;
+ rc = sqlite3BtreeLast(pCrsr, &res);
+ pC->nullRow = res;
+ pC->deferredMoveto = 0;
+ pC->cacheValid = 0;
+ if( res && pOp->p2>0 ){
+ pc = pOp->p2 - 1;
+ }
+ }else{
+ pC->nullRow = 0;
+ }
+ 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: { /* no-push */
+ sqlite3_sort_count++;
+ sqlite3_search_count--;
+ /* 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: { /* no-push */
+ 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->cacheValid = 0;
+ }else{
+ res = 1;
+ }
+ pC->nullRow = res;
+ if( res && pOp->p2>0 ){
+ 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.
+**
+** 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: /* no-push */
+case OP_Next: { /* no-push */
+ Cursor *pC;
+ BtCursor *pCrsr;
+
+ CHECK_FOR_INTERRUPT;
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ if( (pCrsr = pC->pCursor)!=0 ){
+ int res;
+ if( pC->nullRow ){
+ res = 1;
+ }else{
+ assert( pC->deferredMoveto==0 );
+ rc = pOp->opcode==OP_Next ? sqlite3BtreeNext(pCrsr, &res) :
+ sqlite3BtreePrevious(pCrsr, &res);
+ pC->nullRow = res;
+ pC->cacheValid = 0;
+ }
+ if( res==0 ){
+ pc = pOp->p2 - 1;
+ sqlite3_search_count++;
+ }
+ }else{
+ pC->nullRow = 1;
+ }
+ pC->rowidIsValid = 0;
+ break;
+}
+
+/* Opcode: IdxInsert P1 * *
+**
+** 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.
+**
+** This instruction only works for indices. The equivalent instruction
+** for tables is OP_Insert.
+*/
+case OP_IdxInsert: { /* no-push */
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ assert( pTos->flags & MEM_Blob );
+ assert( pOp->p2==0 );
+ if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
+ int nKey = pTos->n;
+ const char *zKey = pTos->z;
+ assert( pC->isTable==0 );
+ rc = sqlite3BtreeInsert(pCrsr, zKey, nKey, "", 0);
+ assert( pC->deferredMoveto==0 );
+ pC->cacheValid = 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: { /* no-push */
+ int i = pOp->p1;
+ Cursor *pC;
+ BtCursor *pCrsr;
+ assert( pTos>=p->aStack );
+ assert( pTos->flags & MEM_Blob );
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
+ int rx, res;
+ rx = sqlite3BtreeMoveto(pCrsr, pTos->z, pTos->n, &res);
+ if( rx==SQLITE_OK && res==0 ){
+ rc = sqlite3BtreeDelete(pCrsr);
+ }
+ assert( pC->deferredMoveto==0 );
+ pC->cacheValid = 0;
+ }
+ Release(pTos);
+ pTos--;
+ break;
+}
+
+/* Opcode: IdxRowid P1 * *
+**
+** Push onto the stack 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, MakeIdxKey.
+*/
+case OP_IdxRowid: {
+ int i = pOp->p1;
+ BtCursor *pCrsr;
+ Cursor *pC;
+
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ pTos++;
+ pTos->flags = MEM_Null;
+ if( (pCrsr = (pC = p->apCsr[i])->pCursor)!=0 ){
+ i64 rowid;
+
+ assert( pC->deferredMoveto==0 );
+ assert( pC->isTable==0 );
+ if( pC->nullRow ){
+ pTos->flags = MEM_Null;
+ }else{
+ rc = sqlite3VdbeIdxRowid(pCrsr, &rowid);
+ if( rc!=SQLITE_OK ){
+ goto abort_due_to_error;
+ }
+ pTos->flags = MEM_Int;
+ pTos->i = rowid;
+ }
+ }
+ break;
+}
+
+/* Opcode: IdxGT P1 P2 *
+**
+** The top of the stack is an index entry that omits the ROWID. Compare
+** the top of stack against the index that P1 is currently pointing to.
+** Ignore the ROWID on the P1 index.
+**
+** The top of the stack might have fewer columns that P1.
+**
+** If the P1 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 P3
+**
+** The top of the stack is an index entry that omits the ROWID. Compare
+** the top of stack against the index that P1 is currently pointing to.
+** Ignore the ROWID on the P1 index.
+**
+** If the P1 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.
+**
+** If P3 is the "+" string (or any other non-NULL string) then the
+** index taken from the top of the stack is temporarily increased by
+** an epsilon prior to the comparison. This make the opcode work
+** like IdxGT except that if the key from the stack 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
+**
+** The top of the stack is an index entry that omits the ROWID. Compare
+** the top of stack against the index that P1 is currently pointing to.
+** Ignore the ROWID on the P1 index.
+**
+** If the P1 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.
+**
+** If P3 is the "+" string (or any other non-NULL string) then the
+** index taken from the top of the stack is temporarily increased by
+** an epsilon prior to the comparison. This makes the opcode work
+** like IdxLE.
+*/
+case OP_IdxLT: /* no-push */
+case OP_IdxGT: /* no-push */
+case OP_IdxGE: { /* no-push */
+ int i= pOp->p1;
+ Cursor *pC;
+
+ assert( i>=0 && i<p->nCursor );
+ assert( p->apCsr[i]!=0 );
+ assert( pTos>=p->aStack );
+ if( (pC = p->apCsr[i])->pCursor!=0 ){
+ int res, rc;
+
+ assert( pTos->flags & MEM_Blob ); /* Created using OP_Make*Key */
+ Stringify(pTos, db->enc);
+ assert( pC->deferredMoveto==0 );
+ *pC->pIncrKey = pOp->p3!=0;
+ assert( pOp->p3==0 || pOp->opcode!=OP_IdxGT );
+ rc = sqlite3VdbeIdxKeyCompare(pC, pTos->n, pTos->z, &res);
+ *pC->pIncrKey = 0;
+ 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: { /* no-push */
+ int i = pOp->p1;
+ int k, n;
+ const char *z;
+ u32 serial_type;
+
+ assert( pTos>=p->aStack );
+ assert( pTos->flags & MEM_Blob );
+ z = pTos->z;
+ n = pTos->n;
+ k = sqlite3GetVarint32(z, &serial_type);
+ for(; k<n && i>0; i--){
+ k += sqlite3GetVarint32(&z[k], &serial_type);
+ if( serial_type==0 ){ /* Serial type 0 is a NULL */
+ pc = pOp->p2-1;
+ break;
+ }
+ }
+ 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.
+**
+** 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 pushed onto the stack. If no page movement was required (because
+** the table being dropped was already the last one in the database) then
+** a zero is pushed onto the stack. If AUTOVACUUM is disabled
+** then a zero is pushed onto the stack.
+**
+** See also: Clear
+*/
+case OP_Destroy: {
+ int iMoved;
+ if( db->activeVdbeCnt>1 ){
+ rc = SQLITE_LOCKED;
+ }else{
+ assert( db->activeVdbeCnt==1 );
+ rc = sqlite3BtreeDropTable(db->aDb[pOp->p2].pBt, pOp->p1, &iMoved);
+ pTos++;
+ pTos->flags = MEM_Int;
+ pTos->i = iMoved;
+ #ifndef SQLITE_OMIT_AUTOVACUUM
+ if( rc==SQLITE_OK && iMoved!=0 ){
+ sqlite3RootPageMoved(&db->aDb[pOp->p2], 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: { /* no-push */
+ rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, pOp->p1);
+ break;
+}
+
+/* Opcode: CreateTable P1 * *
+**
+** 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 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 * *
+**
+** 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;
+ int flags;
+ Db *pDb;
+ assert( pOp->p1>=0 && pOp->p1<db->nDb );
+ 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);
+ pTos++;
+ if( rc==SQLITE_OK ){
+ pTos->i = pgno;
+ pTos->flags = MEM_Int;
+ }else{
+ pTos->flags = MEM_Null;
+ }
+ break;
+}
+
+/* Opcode: ParseSchema P1 * P3
+**
+** Read and parse all entries from the SQLITE_MASTER table of database P1
+** that match the WHERE clause P3.
+**
+** 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: { /* no-push */
+ char *zSql;
+ int iDb = pOp->p1;
+ const char *zMaster;
+ InitData initData;
+
+ assert( iDb>=0 && iDb<db->nDb );
+ if( !DbHasProperty(db, iDb, DB_SchemaLoaded) ) break;
+ zMaster = SCHEMA_TABLE(iDb);
+ initData.db = db;
+ initData.pzErrMsg = &p->zErrMsg;
+ zSql = sqlite3MPrintf(
+ "SELECT name, rootpage, sql, %d FROM '%q'.%s WHERE %s",
+ pOp->p1, db->aDb[iDb].zName, zMaster, pOp->p3);
+ if( zSql==0 ) goto no_mem;
+ sqlite3SafetyOff(db);
+ assert( db->init.busy==0 );
+ db->init.busy = 1;
+ rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
+ db->init.busy = 0;
+ sqlite3SafetyOn(db);
+ sqliteFree(zSql);
+ break;
+}
+
+#ifndef SQLITE_OMIT_ANALYZE
+/* 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: { /* no-push */
+ int iDb = pOp->p1;
+ assert( iDb>=0 && iDb<db->nDb );
+ sqlite3AnalysisLoad(db, iDb);
+ break;
+}
+#endif /* SQLITE_OMIT_ANALYZE */
+
+/* Opcode: DropTable P1 * P3
+**
+** Remove the internal (in-memory) data structures that describe
+** the table named P3 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: { /* no-push */
+ sqlite3UnlinkAndDeleteTable(db, pOp->p1, pOp->p3);
+ break;
+}
+
+/* Opcode: DropIndex P1 * P3
+**
+** Remove the internal (in-memory) data structures that describe
+** the index named P3 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: { /* no-push */
+ sqlite3UnlinkAndDeleteIndex(db, pOp->p1, pOp->p3);
+ break;
+}
+
+/* Opcode: DropTrigger P1 * P3
+**
+** Remove the internal (in-memory) data structures that describe
+** the trigger named P3 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: { /* no-push */
+ sqlite3UnlinkAndDeleteTrigger(db, pOp->p1, pOp->p3);
+ break;
+}
+
+
+#ifndef SQLITE_OMIT_INTEGRITY_CHECK
+/* Opcode: IntegrityCk * 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.
+**
+** The root page numbers of all tables in the database are integer
+** values on the stack. This opcode pulls as many integers as it
+** can off of the stack and uses those numbers as the root pages.
+**
+** 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 j;
+ char *z;
+
+ for(nRoot=0; &pTos[-nRoot]>=p->aStack; nRoot++){
+ if( (pTos[-nRoot].flags & MEM_Int)==0 ) break;
+ }
+ assert( nRoot>0 );
+ aRoot = sqliteMallocRaw( sizeof(int*)*(nRoot+1) );
+ if( aRoot==0 ) goto no_mem;
+ for(j=0; j<nRoot; j++){
+ Mem *pMem = &pTos[-j];
+ aRoot[j] = pMem->i;
+ }
+ aRoot[j] = 0;
+ popStack(&pTos, nRoot);
+ pTos++;
+ z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p2].pBt, aRoot, nRoot);
+ if( z==0 || z[0]==0 ){
+ if( z ) sqliteFree(z);
+ pTos->z = "ok";
+ pTos->n = 2;
+ pTos->flags = MEM_Str | MEM_Static | MEM_Term;
+ }else{
+ pTos->z = z;
+ pTos->n = strlen(z);
+ pTos->flags = MEM_Str | MEM_Dyn | MEM_Term;
+ pTos->xDel = 0;
+ }
+ pTos->enc = SQLITE_UTF8;
+ sqlite3VdbeChangeEncoding(pTos, db->enc);
+ sqliteFree(aRoot);
+ break;
+}
+#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
+
+/* Opcode: FifoWrite * * *
+**
+** Write the integer on the top of the stack
+** into the Fifo.
+*/
+case OP_FifoWrite: { /* no-push */
+ assert( pTos>=p->aStack );
+ Integerify(pTos);
+ sqlite3VdbeFifoPush(&p->sFifo, pTos->i);
+ assert( (pTos->flags & MEM_Dyn)==0 );
+ pTos--;
+ break;
+}
+
+/* Opcode: FifoRead * P2 *
+**
+** Attempt to read a single integer from the Fifo
+** and push it onto the stack. If the Fifo is empty
+** push nothing but instead jump to P2.
+*/
+case OP_FifoRead: {
+ i64 v;
+ CHECK_FOR_INTERRUPT;
+ if( sqlite3VdbeFifoPop(&p->sFifo, &v)==SQLITE_DONE ){
+ pc = pOp->p2 - 1;
+ }else{
+ pTos++;
+ pTos->i = v;
+ pTos->flags = MEM_Int;
+ }
+ 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: { /* no-push */
+ 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;
+ sqlite3ReallocOrFree((void**)&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: { /* no-push */
+ 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 */
+
+/* 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: { /* no-push */
+ assert( pTos>=p->aStack );
+ assert( pOp->p1>=0 && pOp->p1<p->nMem );
+ rc = sqlite3VdbeMemMove(&p->aMem[pOp->p1], pTos);
+ pTos--;
+
+ /* If P2 is 0 then fall thru to the next opcode, OP_MemLoad, that will
+ ** restore the top of the stack to its original value.
+ */
+ if( pOp->p2 ){
+ 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++;
+ sqlite3VdbeMemShallowCopy(pTos, &p->aMem[i], MEM_Ephem);
+ break;
+}
+
+#ifndef SQLITE_OMIT_AUTOINCREMENT
+/* Opcode: MemMax P1 * *
+**
+** Set the value of memory cell P1 to the maximum of its current value
+** and the value on the top of the stack. The stack is unchanged.
+**
+** This instruction throws an error if the memory cell is not initially
+** an integer.
+*/
+case OP_MemMax: { /* no-push */
+ int i = pOp->p1;
+ Mem *pMem;
+ assert( pTos>=p->aStack );
+ assert( i>=0 && i<p->nMem );
+ pMem = &p->aMem[i];
+ Integerify(pMem);
+ Integerify(pTos);
+ if( pMem->i<pTos->i){
+ pMem->i = pTos->i;
+ }
+ break;
+}
+#endif /* SQLITE_OMIT_AUTOINCREMENT */
+
+/* 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 exactly 1, then jump
+** to P2.
+**
+** This instruction throws an error if the memory cell is not initially
+** an integer.
+*/
+case OP_MemIncr: { /* no-push */
+ 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==1 ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: IfMemPos P1 P2 *
+**
+** If the value of memory cell P1 is 1 or greater, jump to P2. This
+** opcode assumes that memory cell P1 holds an integer value.
+*/
+case OP_IfMemPos: { /* no-push */
+ int i = pOp->p1;
+ Mem *pMem;
+ assert( i>=0 && i<p->nMem );
+ pMem = &p->aMem[i];
+ assert( pMem->flags==MEM_Int );
+ if( pMem->i>0 ){
+ pc = pOp->p2 - 1;
+ }
+ break;
+}
+
+/* Opcode: MemNull P1 * *
+**
+** Store a NULL in memory cell P1
+*/
+case OP_MemNull: {
+ assert( pOp->p1>=0 && pOp->p1<p->nMem );
+ sqlite3VdbeMemSetNull(&p->aMem[pOp->p1]);
+ break;
+}
+
+/* Opcode: MemInt P1 P2 *
+**
+** Store the integer value P1 in memory cell P2.
+*/
+case OP_MemInt: {
+ assert( pOp->p2>=0 && pOp->p2<p->nMem );
+ sqlite3VdbeMemSetInt64(&p->aMem[pOp->p2], pOp->p1);
+ break;
+}
+
+/* Opcode: MemMove P1 P2 *
+**
+** Move the content of memory cell P2 over to memory cell P1.
+** Any prior content of P1 is erased. Memory cell P2 is left
+** containing a NULL.
+*/
+case OP_MemMove: {
+ assert( pOp->p1>=0 && pOp->p1<p->nMem );
+ assert( pOp->p2>=0 && pOp->p2<p->nMem );
+ rc = sqlite3VdbeMemMove(&p->aMem[pOp->p1], &p->aMem[pOp->p2]);
+ break;
+}
+
+/* Opcode: AggStep P1 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. Use memory location
+** P1 as the accumulator.
+**
+** The P2 arguments are popped from the stack.
+*/
+case OP_AggStep: { /* no-push */
+ int n = pOp->p2;
+ int i;
+ Mem *pMem, *pRec;
+ sqlite3_context ctx;
+ sqlite3_value **apVal;
+
+ assert( n>=0 );
+ pRec = &pTos[1-n];
+ assert( pRec>=p->aStack );
+ apVal = p->apArg;
+ assert( apVal || n==0 );
+ for(i=0; i<n; i++, pRec++){
+ apVal[i] = pRec;
+ storeTypeInfo(pRec, db->enc);
+ }
+ ctx.pFunc = (FuncDef*)pOp->p3;
+ assert( pOp->p1>=0 && pOp->p1<p->nMem );
+ ctx.pMem = pMem = &p->aMem[pOp->p1];
+ pMem->n++;
+ ctx.isError = 0;
+ ctx.pColl = 0;
+ if( ctx.pFunc->needCollSeq ){
+ assert( pOp>p->aOp );
+ assert( pOp[-1].p3type==P3_COLLSEQ );
+ assert( pOp[-1].opcode==OP_CollSeq );
+ ctx.pColl = (CollSeq *)pOp[-1].p3;
+ }
+ (ctx.pFunc->xStep)(&ctx, n, apVal);
+ popStack(&pTos, n);
+ if( ctx.isError ){
+ rc = SQLITE_ERROR;
+ }
+ break;
+}
+
+/* Opcode: AggFinal P1 P2 P3
+**
+** 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
+** P3 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
+** P3 argument is only needed for the degenerate case where
+** the step function was not previously called.
+*/
+case OP_AggFinal: { /* no-push */
+ Mem *pMem;
+ assert( pOp->p1>=0 && pOp->p1<p->nMem );
+ pMem = &p->aMem[pOp->p1];
+ assert( (pMem->flags & ~(MEM_Null|MEM_Agg))==0 );
+ sqlite3VdbeMemFinalize(pMem, (FuncDef*)pOp->p3);
+ 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: { /* no-push */
+ if( sqlite3SafetyOff(db) ) goto abort_due_to_misuse;
+ rc = sqlite3RunVacuum(&p->zErrMsg, db);
+ if( sqlite3SafetyOn(db) ) goto abort_due_to_misuse;
+ break;
+}
+
+/* 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: { /* no-push */
+ if( !pOp->p1 ){
+ sqlite3ExpirePreparedStatements(db);
+ }else{
+ p->expired = 1;
+ }
+ break;
+}
+
+
+/* An other opcode is illegal...
+*/
+default: {
+ assert( 0 );
+ 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.
+*****************************************************************************/
+ }
+
+ /* Make sure the stack limit was not exceeded */
+ assert( pTos<=pStackLimit );
+
+#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
+ /* Sanity checking on the top element of the stack */
+ if( pTos>=p->aStack ){
+ sqlite3VdbeMemSanity(pTos, db->enc);
+ }
+ assert( pc>=-1 && pc<p->nOp );
+#ifdef SQLITE_DEBUG
+ /* Code for tracing the vdbe stack. */
+ 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:%lld", pTos[i].i);
+ }else if( pTos[i].flags & MEM_Int ){
+ fprintf(p->trace, " i:%lld", pTos[i].i);
+ }else if( pTos[i].flags & MEM_Real ){
+ fprintf(p->trace, " r:%g", pTos[i].r);
+ }else{
+ char zBuf[100];
+ sqlite3VdbeMemPrettyPrint(&pTos[i], zBuf, 100);
+ fprintf(p->trace, " ");
+ fprintf(p->trace, "%s", zBuf);
+ }
+ }
+ if( rc!=0 ) fprintf(p->trace," rc=%d",rc);
+ fprintf(p->trace,"\n");
+ }
+#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.
+ */
+vdbe_halt:
+ if( rc ){
+ p->rc = rc;
+ rc = SQLITE_ERROR;
+ }else{
+ rc = SQLITE_DONE;
+ }
+ sqlite3VdbeHalt(p);
+ 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:
+ sqlite3SetString(&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( sqlite3_malloc_failed ) rc = SQLITE_NOMEM;
+ sqlite3SetString(&p->zErrMsg, sqlite3ErrStr(rc), (char*)0);
+ }
+ goto vdbe_halt;
+
+ /* Jump to here if the sqlite3_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;
+ }
+ p->rc = rc;
+ sqlite3SetString(&p->zErrMsg, sqlite3ErrStr(rc), (char*)0);
+ goto vdbe_halt;
+}
diff --git a/kexi/3rdparty/kexisql3/src/vdbe.h b/kexi/3rdparty/kexisql3/src/vdbe.h
new file mode 100644
index 000000000..332a1eb87
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/vdbe.h
@@ -0,0 +1,131 @@
+/*
+** 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 653457 2007-04-13 11:18:02Z scripty $
+*/
+#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; /* One of the P3_xxx constants defined below */
+#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_COLLSEQ (-4) /* P3 is a pointer to a CollSeq structure */
+#define P3_FUNCDEF (-5) /* P3 is a pointer to a FuncDef structure */
+#define P3_KEYINFO (-6) /* P3 is a pointer to a KeyInfo structure */
+#define P3_VDBEFUNC (-7) /* P3 is a pointer to a VdbeFunc structure */
+#define P3_MEM (-8) /* P3 is a pointer to a Mem* structure */
+
+/* When adding a P3 argument using P3_KEYINFO, a copy of the KeyInfo structure
+** is made. That copy is freed when the Vdbe is finalized. But if the
+** argument is P3_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 P3_KEYINFO_HANDOFF (-9)
+
+/*
+** 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"
+
+/*
+** Prototypes for the VDBE interface. See comments on the implementation
+** for a description of what each of these routines does.
+*/
+Vdbe *sqlite3VdbeCreate(sqlite3*);
+void sqlite3VdbeCreateCallback(Vdbe*, int*);
+int sqlite3VdbeAddOp(Vdbe*,int,int,int);
+int sqlite3VdbeOp3(Vdbe*,int,int,int,const char *zP3,int);
+int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp);
+void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1);
+void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2);
+void sqlite3VdbeJumpHere(Vdbe*, int addr);
+void sqlite3VdbeChangeP3(Vdbe*, int addr, const char *zP1, int N);
+VdbeOp *sqlite3VdbeGetOp(Vdbe*, int);
+int sqlite3VdbeMakeLabel(Vdbe*);
+void sqlite3VdbeDelete(Vdbe*);
+void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int);
+int sqlite3VdbeFinalize(Vdbe*);
+void sqlite3VdbeResolveLabel(Vdbe*, int);
+int sqlite3VdbeCurrentAddr(Vdbe*);
+void sqlite3VdbeTrace(Vdbe*,FILE*);
+int sqlite3VdbeReset(Vdbe*);
+int sqliteVdbeSetVariables(Vdbe*,int,const char**);
+void sqlite3VdbeSetNumCols(Vdbe*,int);
+int sqlite3VdbeSetColName(Vdbe*, int, const char *, int);
+void sqlite3VdbeCountChanges(Vdbe*);
+sqlite3 *sqlite3VdbeDb(Vdbe*);
+
+#ifndef NDEBUG
+ void sqlite3VdbeComment(Vdbe*, const char*, ...);
+# define VdbeComment(X) sqlite3VdbeComment X
+#else
+# define VdbeComment(X)
+#endif
+
+#endif
diff --git a/kexi/3rdparty/kexisql3/src/vdbeInt.h b/kexi/3rdparty/kexisql3/src/vdbeInt.h
new file mode 100644
index 000000000..1746ee561
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/vdbeInt.h
@@ -0,0 +1,377 @@
+/*
+** 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.
+*/
+
+/*
+** 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)
+
+/*
+** The makefile scans the vdbe.c 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 *sqlite3OpcodeNames[];
+
+/*
+** 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 */
+ 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 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 */
+
+ /* Cached information about the header for the data record that the
+ ** cursor is currently pointing to. Only valid if cacheValid is true.
+ ** zRow might point to (ephemeral) data for the current row, or it might
+ ** be NULL. */
+ Bool cacheValid; /* True if the cache is valid */
+ 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;
+
+/*
+** 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
+
+/*
+** 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 {
+ i64 i; /* Integer value. Or FuncDef* when flags==MEM_Agg */
+ double r; /* Real value */
+ char *z; /* String or BLOB value */
+ int n; /* Number of characters in string value, including '\0' */
+ u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
+ u8 type; /* One of MEM_Null, MEM_Str, etc. */
+ u8 enc; /* TEXT_Utf8, TEXT_Utf16le, or TEXT_Utf16be */
+ void (*xDel)(void *); /* If not null, call this function to delete Mem.z */
+ char zShort[NBFS]; /* Space for short strings */
+};
+typedef struct Mem Mem;
+
+/* 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 */
+
+/* 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_Short 0x0200 /* Mem.z points to Mem.zShort */
+#define MEM_Agg 0x0400 /* Mem.z points to an agg function context */
+
+
+/* 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 */
+};
+typedef struct VdbeFunc VdbeFunc;
+
+/*
+** 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 */
+ u8 isError; /* Set to true for an error */
+ 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 {
+ int 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.
+*/
+struct Vdbe {
+ sqlite3 *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 */
+ 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 */
+ 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[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 resOnStack; /* True if there are result values on the stack */
+ 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 */
+ int nChange; /* Number of db changes made since last reset */
+ i64 startTime; /* Time when query started - used for profiling */
+};
+
+/*
+** 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 sqlite3VdbeFreeCursor(Cursor*);
+void sqliteVdbePopStack(Vdbe*,int);
+int sqlite3VdbeCursorMoveto(Cursor*);
+#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE)
+void sqlite3VdbePrintOp(FILE*, int, Op*);
+#endif
+#ifdef SQLITE_DEBUG
+void sqlite3VdbePrintSql(Vdbe*);
+#endif
+int sqlite3VdbeSerialTypeLen(u32);
+u32 sqlite3VdbeSerialType(Mem*);
+int sqlite3VdbeSerialPut(unsigned char*, Mem*);
+int sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*);
+void sqlite3VdbeDeleteAuxData(VdbeFunc*, int);
+
+int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *);
+int sqlite3VdbeIdxKeyCompare(Cursor*, int , const unsigned char*, int*);
+int sqlite3VdbeIdxRowid(BtCursor *, i64 *);
+int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
+int sqlite3VdbeRecordCompare(void*,int,const void*,int, const void*);
+int sqlite3VdbeIdxRowidLen(int,const u8*);
+int sqlite3VdbeExec(Vdbe*);
+int sqlite3VdbeList(Vdbe*);
+int sqlite3VdbeHalt(Vdbe*);
+int sqlite3VdbeChangeEncoding(Mem *, int);
+int sqlite3VdbeMemCopy(Mem*, const Mem*);
+void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int);
+int sqlite3VdbeMemMove(Mem*, Mem*);
+int sqlite3VdbeMemNulTerminate(Mem*);
+int sqlite3VdbeMemSetStr(Mem*, const char*, int, u8, void(*)(void*));
+void sqlite3VdbeMemSetInt64(Mem*, i64);
+void sqlite3VdbeMemSetDouble(Mem*, double);
+void sqlite3VdbeMemSetNull(Mem*);
+int sqlite3VdbeMemMakeWriteable(Mem*);
+int sqlite3VdbeMemDynamicify(Mem*);
+int sqlite3VdbeMemStringify(Mem*, int);
+i64 sqlite3VdbeIntValue(Mem*);
+int sqlite3VdbeMemIntegerify(Mem*);
+double sqlite3VdbeRealValue(Mem*);
+int sqlite3VdbeMemRealify(Mem*);
+int sqlite3VdbeMemFromBtree(BtCursor*,int,int,int,Mem*);
+void sqlite3VdbeMemRelease(Mem *p);
+void sqlite3VdbeMemFinalize(Mem*, FuncDef*);
+#ifndef NDEBUG
+void sqlite3VdbeMemSanity(Mem*, u8);
+int sqlite3VdbeOpcodeNoPush(u8);
+#endif
+int sqlite3VdbeMemTranslate(Mem*, u8);
+void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf, int nBuf);
+int sqlite3VdbeMemHandleBom(Mem *pMem);
+void sqlite3VdbeFifoInit(Fifo*);
+int sqlite3VdbeFifoPush(Fifo*, i64);
+int sqlite3VdbeFifoPop(Fifo*, i64*);
+void sqlite3VdbeFifoClear(Fifo*);
diff --git a/kexi/3rdparty/kexisql3/src/vdbeapi.c b/kexi/3rdparty/kexisql3/src/vdbeapi.c
new file mode 100644
index 000000000..2fd54f38e
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/vdbeapi.c
@@ -0,0 +1,737 @@
+/*
+** 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.
+*/
+#include "sqliteInt.h"
+#include "vdbeInt.h"
+#include "os.h"
+
+/*
+** 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.
+*/
+int sqlite3_expired(sqlite3_stmt *pStmt){
+ Vdbe *p = (Vdbe*)pStmt;
+ return p==0 || p->expired;
+}
+
+/**************************** sqlite3_value_ *******************************
+** The following routines extract information from a Mem or sqlite3_value
+** structure.
+*/
+const void *sqlite3_value_blob(sqlite3_value *pVal){
+ Mem *p = (Mem*)pVal;
+ if( p->flags & (MEM_Blob|MEM_Str) ){
+ return p->z;
+ }else{
+ return sqlite3_value_text(pVal);
+ }
+}
+int sqlite3_value_bytes(sqlite3_value *pVal){
+ return sqlite3ValueBytes(pVal, SQLITE_UTF8);
+}
+int sqlite3_value_bytes16(sqlite3_value *pVal){
+ return sqlite3ValueBytes(pVal, SQLITE_UTF16NATIVE);
+}
+double sqlite3_value_double(sqlite3_value *pVal){
+ return sqlite3VdbeRealValue((Mem*)pVal);
+}
+int sqlite3_value_int(sqlite3_value *pVal){
+ return sqlite3VdbeIntValue((Mem*)pVal);
+}
+sqlite_int64 sqlite3_value_int64(sqlite3_value *pVal){
+ return sqlite3VdbeIntValue((Mem*)pVal);
+}
+const unsigned char *sqlite3_value_text(sqlite3_value *pVal){
+ return (const char *)sqlite3ValueText(pVal, SQLITE_UTF8);
+}
+#ifndef SQLITE_OMIT_UTF16
+const void *sqlite3_value_text16(sqlite3_value* pVal){
+ return sqlite3ValueText(pVal, SQLITE_UTF16NATIVE);
+}
+const void *sqlite3_value_text16be(sqlite3_value *pVal){
+ return sqlite3ValueText(pVal, SQLITE_UTF16BE);
+}
+const void *sqlite3_value_text16le(sqlite3_value *pVal){
+ return sqlite3ValueText(pVal, SQLITE_UTF16LE);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+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.
+*/
+void sqlite3_result_blob(
+ sqlite3_context *pCtx,
+ const void *z,
+ int n,
+ void (*xDel)(void *)
+){
+ assert( n>=0 );
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, 0, xDel);
+}
+void sqlite3_result_double(sqlite3_context *pCtx, double rVal){
+ sqlite3VdbeMemSetDouble(&pCtx->s, rVal);
+}
+void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){
+ pCtx->isError = 1;
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF8, SQLITE_TRANSIENT);
+}
+void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){
+ pCtx->isError = 1;
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT);
+}
+void sqlite3_result_int(sqlite3_context *pCtx, int iVal){
+ sqlite3VdbeMemSetInt64(&pCtx->s, (i64)iVal);
+}
+void sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){
+ sqlite3VdbeMemSetInt64(&pCtx->s, iVal);
+}
+void sqlite3_result_null(sqlite3_context *pCtx){
+ sqlite3VdbeMemSetNull(&pCtx->s);
+}
+void sqlite3_result_text(
+ sqlite3_context *pCtx,
+ const char *z,
+ int n,
+ void (*xDel)(void *)
+){
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF8, xDel);
+}
+#ifndef SQLITE_OMIT_UTF16
+void sqlite3_result_text16(
+ sqlite3_context *pCtx,
+ const void *z,
+ int n,
+ void (*xDel)(void *)
+){
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16NATIVE, xDel);
+}
+void sqlite3_result_text16be(
+ sqlite3_context *pCtx,
+ const void *z,
+ int n,
+ void (*xDel)(void *)
+){
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16BE, xDel);
+}
+void sqlite3_result_text16le(
+ sqlite3_context *pCtx,
+ const void *z,
+ int n,
+ void (*xDel)(void *)
+){
+ sqlite3VdbeMemSetStr(&pCtx->s, z, n, SQLITE_UTF16LE, xDel);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){
+ sqlite3VdbeMemCopy(&pCtx->s, pValue);
+}
+
+
+/*
+** Execute the statement pStmt, either until a row of data is ready, the
+** statement is completely executed or an error occurs.
+*/
+int sqlite3_step(sqlite3_stmt *pStmt){
+ Vdbe *p = (Vdbe*)pStmt;
+ sqlite3 *db;
+ int rc;
+
+ if( p==0 || p->magic!=VDBE_MAGIC_RUN ){
+ return SQLITE_MISUSE;
+ }
+ if( p->aborted ){
+ return SQLITE_ABORT;
+ }
+ if( p->pc<=0 && p->expired ){
+ if( p->rc==SQLITE_OK ){
+ p->rc = SQLITE_SCHEMA;
+ }
+ return SQLITE_ERROR;
+ }
+ db = p->db;
+ if( sqlite3SafetyOn(db) ){
+ p->rc = SQLITE_MISUSE;
+ return SQLITE_MISUSE;
+ }
+ if( p->pc<0 ){
+#ifndef SQLITE_OMIT_TRACE
+ /* Invoke the trace callback if there is one
+ */
+ if( db->xTrace && !db->init.busy ){
+ assert( p->nOp>0 );
+ assert( p->aOp[p->nOp-1].opcode==OP_Noop );
+ assert( p->aOp[p->nOp-1].p3!=0 );
+ assert( p->aOp[p->nOp-1].p3type==P3_DYNAMIC );
+ sqlite3SafetyOff(db);
+ db->xTrace(db->pTraceArg, p->aOp[p->nOp-1].p3);
+ if( sqlite3SafetyOn(db) ){
+ p->rc = SQLITE_MISUSE;
+ return SQLITE_MISUSE;
+ }
+ }
+ if( db->xProfile && !db->init.busy ){
+ double rNow;
+ sqlite3OsCurrentTime(&rNow);
+ p->startTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0;
+ }
+#endif
+
+ /* Print a copy of SQL as it is executed if the SQL_TRACE pragma is turned
+ ** on in debugging mode.
+ */
+#ifdef SQLITE_DEBUG
+ if( (db->flags & SQLITE_SqlTrace)!=0 ){
+ sqlite3DebugPrintf("SQL-trace: %s\n", p->aOp[p->nOp-1].p3);
+ }
+#endif /* SQLITE_DEBUG */
+
+ db->activeVdbeCnt++;
+ p->pc = 0;
+ }
+#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 ){
+ double rNow;
+ u64 elapseTime;
+
+ sqlite3OsCurrentTime(&rNow);
+ elapseTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0 - p->startTime;
+ assert( p->nOp>0 );
+ assert( p->aOp[p->nOp-1].opcode==OP_Noop );
+ assert( p->aOp[p->nOp-1].p3!=0 );
+ assert( p->aOp[p->nOp-1].p3type==P3_DYNAMIC );
+ db->xProfile(db->pProfileArg, p->aOp[p->nOp-1].p3, elapseTime);
+ }
+#endif
+
+ sqlite3Error(p->db, rc, p->zErrMsg ? "%s" : 0, p->zErrMsg);
+ return rc;
+}
+
+/*
+** Extract the user data from a sqlite3_context structure and return a
+** pointer to it.
+*/
+void *sqlite3_user_data(sqlite3_context *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.
+*/
+void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){
+ Mem *pMem = p->pMem;
+ assert( p && p->pFunc && p->pFunc->xStep );
+ if( (pMem->flags & MEM_Agg)==0 ){
+ if( nByte==0 ){
+ assert( pMem->flags==MEM_Null );
+ pMem->z = 0;
+ }else{
+ pMem->flags = MEM_Agg;
+ pMem->xDel = sqlite3FreeX;
+ *(FuncDef**)&pMem->i = p->pFunc;
+ if( nByte<=NBFS ){
+ pMem->z = pMem->zShort;
+ memset(pMem->z, 0, nByte);
+ }else{
+ pMem->z = sqliteMalloc( nByte );
+ }
+ }
+ }
+ return (void*)pMem->z;
+}
+
+/*
+** Return the auxilary data pointer, if any, for the iArg'th argument to
+** the user-function defined by pCtx.
+*/
+void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){
+ VdbeFunc *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.
+*/
+void sqlite3_set_auxdata(
+ sqlite3_context *pCtx,
+ int iArg,
+ void *pAux,
+ void (*xDelete)(void*)
+){
+ struct AuxData *pAuxData;
+ VdbeFunc *pVdbeFunc;
+ if( iArg<0 ) return;
+
+ pVdbeFunc = pCtx->pVdbeFunc;
+ if( !pVdbeFunc || pVdbeFunc->nAux<=iArg ){
+ int nMalloc = sizeof(VdbeFunc) + sizeof(struct AuxData)*iArg;
+ pVdbeFunc = sqliteRealloc(pVdbeFunc, nMalloc);
+ if( !pVdbeFunc ) return;
+ pCtx->pVdbeFunc = pVdbeFunc;
+ memset(&pVdbeFunc->apAux[pVdbeFunc->nAux], 0,
+ sizeof(struct AuxData)*(iArg+1-pVdbeFunc->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 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 sqlite3_context structure which is only defined in
+** this source file.
+*/
+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.
+*/
+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.
+*/
+int sqlite3_data_count(sqlite3_stmt *pStmt){
+ Vdbe *pVm = (Vdbe *)pStmt;
+ if( pVm==0 || !pVm->resOnStack ) 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 = (Vdbe *)pStmt;
+ int vals = sqlite3_data_count(pStmt);
+ if( i>=vals || i<0 ){
+ static Mem nullMem;
+ if( nullMem.flags==0 ){ nullMem.flags = MEM_Null; }
+ sqlite3Error(pVm->db, SQLITE_RANGE, 0);
+ return &nullMem;
+ }
+ return &pVm->pTos[(1-vals)+i];
+}
+
+/**************************** sqlite3_column_ *******************************
+** The following routines are used to access elements of the current row
+** in the result set.
+*/
+const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_blob( columnMem(pStmt,i) );
+}
+int sqlite3_column_bytes(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_bytes( columnMem(pStmt,i) );
+}
+int sqlite3_column_bytes16(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_bytes16( columnMem(pStmt,i) );
+}
+double sqlite3_column_double(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_double( columnMem(pStmt,i) );
+}
+int sqlite3_column_int(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_int( columnMem(pStmt,i) );
+}
+sqlite_int64 sqlite3_column_int64(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_int64( columnMem(pStmt,i) );
+}
+const unsigned char *sqlite3_column_text(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_text( columnMem(pStmt,i) );
+}
+#if 0
+sqlite3_value *sqlite3_column_value(sqlite3_stmt *pStmt, int i){
+ return columnMem(pStmt, i);
+}
+#endif
+#ifndef SQLITE_OMIT_UTF16
+const void *sqlite3_column_text16(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_text16( columnMem(pStmt,i) );
+}
+#endif /* SQLITE_OMIT_UTF16 */
+int sqlite3_column_type(sqlite3_stmt *pStmt, int i){
+ return sqlite3_value_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
+){
+ Vdbe *p = (Vdbe *)pStmt;
+ int n = sqlite3_column_count(pStmt);
+
+ if( p==0 || N>=n || N<0 ){
+ return 0;
+ }
+ N += useType*n;
+ return xFunc(&p->aColName[N]);
+}
+
+
+/*
+** Return the name of the Nth column of the result set returned by SQL
+** statement pStmt.
+*/
+const char *sqlite3_column_name(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, 0);
+}
+#ifndef SQLITE_OMIT_UTF16
+const void *sqlite3_column_name16(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, 0);
+}
+#endif
+
+/*
+** Return the column declaration type (if applicable) of the 'i'th column
+** of the result set of SQL statement pStmt.
+*/
+const char *sqlite3_column_decltype(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, 1);
+}
+#ifndef SQLITE_OMIT_UTF16
+const void *sqlite3_column_decltype16(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, 1);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+
+#if !defined(SQLITE_OMIT_ORIGIN_NAMES) && 0
+/*
+** 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.
+*/
+const char *sqlite3_column_database_name(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, 2);
+}
+#ifndef SQLITE_OMIT_UTF16
+const void *sqlite3_column_database_name16(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, 2);
+}
+#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.
+*/
+const char *sqlite3_column_table_name(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, 3);
+}
+#ifndef SQLITE_OMIT_UTF16
+const void *sqlite3_column_table_name16(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, 3);
+}
+#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.
+*/
+const char *sqlite3_column_origin_name(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, 4);
+}
+#ifndef SQLITE_OMIT_UTF16
+const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, 4);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+#endif /* SQLITE_OMIT_ORIGIN_NAMES */
+
+
+
+
+/******************************* 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,
+ int i,
+ const void *zData,
+ int nData,
+ void (*xDel)(void*),
+ int encoding
+){
+ Vdbe *p = (Vdbe *)pStmt;
+ Mem *pVar;
+ int rc;
+
+ rc = vdbeUnbind(p, i);
+ if( rc || zData==0 ){
+ return rc;
+ }
+ pVar = &p->aVar[i-1];
+ rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel);
+ if( rc ){
+ return rc;
+ }
+ if( rc==SQLITE_OK && encoding!=0 ){
+ rc = sqlite3VdbeChangeEncoding(pVar, p->db->enc);
+ }
+ return rc;
+}
+
+
+/*
+** Bind a blob value to an SQL statement variable.
+*/
+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);
+}
+int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){
+ int rc;
+ Vdbe *p = (Vdbe *)pStmt;
+ rc = vdbeUnbind(p, i);
+ if( rc==SQLITE_OK ){
+ sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue);
+ }
+ return rc;
+}
+int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){
+ return sqlite3_bind_int64(p, i, (i64)iValue);
+}
+int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
+ int rc;
+ Vdbe *p = (Vdbe *)pStmt;
+ rc = vdbeUnbind(p, i);
+ if( rc==SQLITE_OK ){
+ sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue);
+ }
+ return rc;
+}
+int sqlite3_bind_null(sqlite3_stmt* p, int i){
+ return vdbeUnbind((Vdbe *)p, i);
+}
+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
+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 */
+
+/*
+** Return the number of wildcards that can be potentially bound to.
+** This routine is added to support DBD::SQLite.
+*/
+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 ){
+ 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->p3;
+ }
+ }
+ p->okVar = 1;
+ }
+}
+
+/*
+** 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.
+*/
+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.
+*/
+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.
+*/
+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) ){
+ return SQLITE_MISUSE;
+ }
+ if( pFrom->nVar!=pTo->nVar ){
+ return SQLITE_ERROR;
+ }
+ for(i=0; rc==SQLITE_OK && i<pFrom->nVar; i++){
+ rc = sqlite3VdbeMemMove(&pTo->aVar[i], &pFrom->aVar[i]);
+ }
+ 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.
+*/
+sqlite3 *sqlite3_db_handle(sqlite3_stmt *pStmt){
+ return pStmt ? ((Vdbe*)pStmt)->db : 0;
+}
diff --git a/kexi/3rdparty/kexisql3/src/vdbeaux.c b/kexi/3rdparty/kexisql3/src/vdbeaux.c
new file mode 100644
index 000000000..cba9c096d
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/vdbeaux.c
@@ -0,0 +1,1819 @@
+/*
+** 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.
+*/
+#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 sqlite3_vdbe_addop_trace to 1 and all opcodes will be printed
+** as they are added to the instruction stream.
+*/
+#ifdef SQLITE_DEBUG
+int sqlite3_vdbe_addop_trace = 0;
+#endif
+
+
+/*
+** Create a new virtual database engine.
+*/
+Vdbe *sqlite3VdbeCreate(sqlite3 *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 sqlite3VdbeTrace(Vdbe *p, FILE *trace){
+ p->trace = trace;
+}
+
+/*
+** Resize the Vdbe.aOp array so that it contains at least N
+** elements. If the Vdbe is in VDBE_MAGIC_RUN state, then
+** the Vdbe.aOp array will be sized to contain exactly N
+** elements.
+*/
+static void resizeOpArray(Vdbe *p, int N){
+ int runMode = p->magic==VDBE_MAGIC_RUN;
+ if( runMode || p->nOpAlloc<N ){
+ VdbeOp *pNew;
+ int nNew = N + 100*(!runMode);
+ int oldSize = p->nOpAlloc;
+ pNew = sqliteRealloc(p->aOp, nNew*sizeof(Op));
+ if( pNew ){
+ p->nOpAlloc = nNew;
+ p->aOp = pNew;
+ if( nNew>oldSize ){
+ memset(&p->aOp[oldSize], 0, (nNew-oldSize)*sizeof(Op));
+ }
+ }
+ }
+}
+
+/*
+** 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 sqlite3VdbeResolveLabel() function to fix an address and
+** the sqlite3VdbeChangeP3() function to change the value of the P3
+** operand.
+*/
+int sqlite3VdbeAddOp(Vdbe *p, int op, int p1, int p2){
+ int i;
+ VdbeOp *pOp;
+
+ i = p->nOp;
+ p->nOp++;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ resizeOpArray(p, i+1);
+ if( sqlite3_malloc_failed ){
+ return 0;
+ }
+ pOp = &p->aOp[i];
+ pOp->opcode = op;
+ pOp->p1 = p1;
+ pOp->p2 = p2;
+ pOp->p3 = 0;
+ pOp->p3type = P3_NOTUSED;
+ p->expired = 0;
+#ifdef SQLITE_DEBUG
+ if( sqlite3_vdbe_addop_trace ) sqlite3VdbePrintOp(0, i, &p->aOp[i]);
+#endif
+ return i;
+}
+
+/*
+** Add an opcode that includes the p3 value.
+*/
+int sqlite3VdbeOp3(Vdbe *p, int op, int p1, int p2, const char *zP3,int p3type){
+ int addr = sqlite3VdbeAddOp(p, op, p1, p2);
+ sqlite3VdbeChangeP3(p, addr, zP3, p3type);
+ 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.
+*/
+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;
+ sqlite3ReallocOrFree((void**)&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().
+*/
+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;
+ }
+}
+
+/*
+** Return non-zero if opcode 'op' is guarenteed not to push more values
+** onto the VDBE stack than it pops off.
+*/
+static int opcodeNoPush(u8 op){
+ /* The 10 NOPUSH_MASK_n constants are defined in the automatically
+ ** generated header file opcodes.h. Each is a 16-bit bitmask, one
+ ** bit corresponding to each opcode implemented by the virtual
+ ** machine in vdbe.c. The bit is true if the word "no-push" appears
+ ** in a comment on the same line as the "case OP_XXX:" in
+ ** sqlite3VdbeExec() in vdbe.c.
+ **
+ ** If the bit is true, then the corresponding opcode is guarenteed not
+ ** to grow the stack when it is executed. Otherwise, it may grow the
+ ** stack by at most one entry.
+ **
+ ** NOPUSH_MASK_0 corresponds to opcodes 0 to 15. NOPUSH_MASK_1 contains
+ ** one bit for opcodes 16 to 31, and so on.
+ **
+ ** 16-bit bitmasks (rather than 32-bit) are specified in opcodes.h
+ ** because the file is generated by an awk program. Awk manipulates
+ ** all numbers as floating-point and we don't want to risk a rounding
+ ** error if someone builds with an awk that uses (for example) 32-bit
+ ** IEEE floats.
+ */
+ static const u32 masks[5] = {
+ NOPUSH_MASK_0 + (NOPUSH_MASK_1<<16),
+ NOPUSH_MASK_2 + (NOPUSH_MASK_3<<16),
+ NOPUSH_MASK_4 + (NOPUSH_MASK_5<<16),
+ NOPUSH_MASK_6 + (NOPUSH_MASK_7<<16),
+ NOPUSH_MASK_8 + (NOPUSH_MASK_9<<16)
+ };
+ return (masks[op>>5] & (1<<(op&0x1F)));
+}
+
+#ifndef NDEBUG
+int sqlite3VdbeOpcodeNoPush(u8 op){
+ return opcodeNoPush(op);
+}
+#endif
+
+/*
+** Loop through the program looking for P2 values that are negative.
+** 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 or OP_AggStep opcode. This is used by
+** sqlite3VdbeMakeReady() to size the Vdbe.apArg[] array.
+**
+** The integer *pMaxStack is set to the maximum number of vdbe stack
+** entries that static analysis reveals this program might need.
+**
+** This routine also does the following optimization: It scans for
+** Halt instructions where P1==SQLITE_CONSTRAINT or P2==OE_Abort or for
+** IdxInsert instructions where P2!=0. 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 *pMaxStack){
+ int i;
+ int nMaxArgs = 0;
+ int nMaxStack = p->nOp;
+ 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 || opcode==OP_AggStep ){
+ if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2;
+ }else if( opcode==OP_Halt ){
+ if( pOp->p1==SQLITE_CONSTRAINT && pOp->p2==OE_Abort ){
+ doesStatementRollback = 1;
+ }
+ }else if( opcode==OP_IdxInsert ){
+ if( pOp->p2 ){
+ doesStatementRollback = 1;
+ }
+ }else if( opcode==OP_Statement ){
+ hasStatementBegin = 1;
+ }
+
+ if( opcodeNoPush(opcode) ){
+ nMaxStack--;
+ }
+
+ if( pOp->p2>=0 ) continue;
+ assert( -1-pOp->p2<p->nLabel );
+ pOp->p2 = aLabel[-1-pOp->p2];
+ }
+ sqliteFree(p->aLabel);
+ p->aLabel = 0;
+
+ *pMaxFuncArgs = nMaxArgs;
+ *pMaxStack = nMaxStack;
+
+ /* 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.
+*/
+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.
+*/
+int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp){
+ int addr;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ resizeOpArray(p, p->nOp + nOp);
+ if( sqlite3_malloc_failed ){
+ 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;
+ pOut->p2 = p2<0 ? addr + ADDR(p2) : p2;
+ pOut->p3 = pIn->p3;
+ pOut->p3type = pIn->p3 ? P3_STATIC : P3_NOTUSED;
+#ifdef SQLITE_DEBUG
+ if( sqlite3_vdbe_addop_trace ){
+ 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.
+*/
+void sqlite3VdbeChangeP1(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 sqlite3VdbeChangeP2(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 teh P2 operand of instruction addr so that it points to
+** the address of the next instruction to be coded.
+*/
+void sqlite3VdbeJumpHere(Vdbe *p, int addr){
+ sqlite3VdbeChangeP2(p, addr, p->nOp);
+}
+
+/*
+** Delete a P3 value if necessary.
+*/
+static void freeP3(int p3type, void *p3){
+ if( p3 ){
+ switch( p3type ){
+ case P3_DYNAMIC:
+ case P3_KEYINFO:
+ case P3_KEYINFO_HANDOFF: {
+ sqliteFree(p3);
+ break;
+ }
+ case P3_VDBEFUNC: {
+ VdbeFunc *pVdbeFunc = (VdbeFunc *)p3;
+ sqlite3VdbeDeleteAuxData(pVdbeFunc, 0);
+ sqliteFree(pVdbeFunc);
+ break;
+ }
+ case P3_MEM: {
+ sqlite3ValueFree((sqlite3_value*)p3);
+ break;
+ }
+ }
+ }
+}
+
+
+/*
+** 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 sqlite3VdbeAddOpList 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_KEYINFO it means that zP3 is a pointer to a KeyInfo structure.
+** A copy is made of the KeyInfo structure into memory obtained from
+** sqliteMalloc, to be freed when the Vdbe is finalized.
+** n==P3_KEYINFO_HANDOFF indicates that zP3 points to a KeyInfo structure
+** stored in memory that the caller has obtained from sqliteMalloc. The
+** caller should not free the allocation, it will be freed when the Vdbe is
+** finalized.
+**
+** Other values of n (P3_STATIC, P3_COLLSEQ etc.) indicate that zP3 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 P3 on the most recently inserted instruction.
+*/
+void sqlite3VdbeChangeP3(Vdbe *p, int addr, const char *zP3, int n){
+ Op *pOp;
+ assert( p->magic==VDBE_MAGIC_INIT );
+ if( p==0 || p->aOp==0 ){
+ freeP3(n, (void*)*(char**)&zP3);
+ return;
+ }
+ if( addr<0 || addr>=p->nOp ){
+ addr = p->nOp - 1;
+ if( addr<0 ) return;
+ }
+ pOp = &p->aOp[addr];
+ freeP3(pOp->p3type, pOp->p3);
+ pOp->p3 = 0;
+ if( zP3==0 ){
+ pOp->p3 = 0;
+ pOp->p3type = P3_NOTUSED;
+ }else if( n==P3_KEYINFO ){
+ KeyInfo *pKeyInfo;
+ int nField, nByte;
+
+ /* KeyInfo structures that include an KeyInfo.aSortOrder are always
+ ** sent in using P3_KEYINFO_HANDOFF. The KeyInfo.aSortOrder array
+ ** is not duplicated when P3_KEYINFO is used. */
+ /* assert( pKeyInfo->aSortOrder==0 ); */
+ nField = ((KeyInfo*)zP3)->nField;
+ nByte = sizeof(*pKeyInfo) + (nField-1)*sizeof(pKeyInfo->aColl[0]);
+ pKeyInfo = sqliteMallocRaw( nByte );
+ pOp->p3 = (char*)pKeyInfo;
+ if( pKeyInfo ){
+ memcpy(pKeyInfo, zP3, nByte);
+ pOp->p3type = P3_KEYINFO;
+ }else{
+ pOp->p3type = P3_NOTUSED;
+ }
+ }else if( n==P3_KEYINFO_HANDOFF ){
+ pOp->p3 = (char*)zP3;
+ pOp->p3type = P3_KEYINFO;
+ }else if( n<0 ){
+ pOp->p3 = (char*)zP3;
+ pOp->p3type = n;
+ }else{
+ if( n==0 ) n = strlen(zP3);
+ pOp->p3 = sqliteStrNDup(zP3, n);
+ pOp->p3type = P3_DYNAMIC;
+ }
+}
+
+#ifndef NDEBUG
+/*
+** Replace the P3 field of the most recently coded instruction with
+** comment text.
+*/
+void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){
+ va_list ap;
+ assert( p->nOp>0 );
+ assert( p->aOp==0 || p->aOp[p->nOp-1].p3==0 );
+ va_start(ap, zFormat);
+ sqlite3VdbeChangeP3(p, -1, sqlite3VMPrintf(zFormat, ap), P3_DYNAMIC);
+ va_end(ap);
+}
+#endif
+
+/*
+** Return the opcode for a given address.
+*/
+VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){
+ assert( p->magic==VDBE_MAGIC_INIT );
+ assert( addr>=0 && addr<p->nOp );
+ return &p->aOp[addr];
+}
+
+#if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) \
+ || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG)
+/*
+** Compute a string that describes the P3 parameter for an opcode.
+** Use zTemp for any required temporary buffer space.
+*/
+static char *displayP3(Op *pOp, char *zTemp, int nTemp){
+ char *zP3;
+ assert( nTemp>=20 );
+ switch( pOp->p3type ){
+ case P3_KEYINFO: {
+ int i, j;
+ KeyInfo *pKeyInfo = (KeyInfo*)pOp->p3;
+ sprintf(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 ){
+ strcpy(&zTemp[i],",...");
+ break;
+ }
+ zTemp[i++] = ',';
+ if( pKeyInfo->aSortOrder && pKeyInfo->aSortOrder[j] ){
+ zTemp[i++] = '-';
+ }
+ strcpy(&zTemp[i], pColl->zName);
+ i += n;
+ }else if( i+4<nTemp-6 ){
+ strcpy(&zTemp[i],",nil");
+ i += 4;
+ }
+ }
+ zTemp[i++] = ')';
+ zTemp[i] = 0;
+ assert( i<nTemp );
+ zP3 = zTemp;
+ break;
+ }
+ case P3_COLLSEQ: {
+ CollSeq *pColl = (CollSeq*)pOp->p3;
+ sprintf(zTemp, "collseq(%.20s)", pColl->zName);
+ zP3 = zTemp;
+ break;
+ }
+ case P3_FUNCDEF: {
+ FuncDef *pDef = (FuncDef*)pOp->p3;
+ char zNum[30];
+ sprintf(zTemp, "%.*s", nTemp, pDef->zName);
+ sprintf(zNum,"(%d)", pDef->nArg);
+ if( strlen(zTemp)+strlen(zNum)+1<=nTemp ){
+ strcat(zTemp, zNum);
+ }
+ zP3 = zTemp;
+ break;
+ }
+ default: {
+ zP3 = pOp->p3;
+ if( zP3==0 || pOp->opcode==OP_Noop ){
+ zP3 = "";
+ }
+ }
+ }
+ return zP3;
+}
+#endif
+
+
+#if defined(VDBE_PROFILE) || defined(SQLITE_DEBUG)
+/*
+** Print a single opcode. This routine is used for debugging only.
+*/
+void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){
+ char *zP3;
+ char zPtr[50];
+ static const char *zFormat1 = "%4d %-13s %4d %4d %s\n";
+ if( pOut==0 ) pOut = stdout;
+ zP3 = displayP3(pOp, zPtr, sizeof(zPtr));
+ fprintf(pOut, zFormat1,
+ pc, sqlite3OpcodeNames[pOp->opcode], pOp->p1, pOp->p2, zP3);
+ fflush(pOut);
+}
+#endif
+
+/*
+** Release an array of N Mem elements
+*/
+static void releaseMemArray(Mem *p, int N){
+ if( p ){
+ while( N-->0 ){
+ sqlite3VdbeMemRelease(p++);
+ }
+ }
+}
+
+#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".
+*/
+int sqlite3VdbeList(
+ Vdbe *p /* The VDBE */
+){
+ sqlite3 *db = p->db;
+ int i;
+ int rc = SQLITE_OK;
+
+ 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 put dynamic strings onto the
+ ** the stack, they may become dynamic if the user calls
+ ** sqlite3_column_text16(), causing a translation to UTF-16 encoding.
+ */
+ if( p->pTos==&p->aStack[4] ){
+ releaseMemArray(p->aStack, 5);
+ }
+ p->resOnStack = 0;
+
+ 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->flags & SQLITE_Interrupt ){
+ db->flags &= ~SQLITE_Interrupt;
+ p->rc = SQLITE_INTERRUPT;
+ rc = SQLITE_ERROR;
+ sqlite3SetString(&p->zErrMsg, sqlite3ErrStr(p->rc), (char*)0);
+ }else{
+ Op *pOp = &p->aOp[i];
+ Mem *pMem = p->aStack;
+ pMem->flags = MEM_Int;
+ pMem->type = SQLITE_INTEGER;
+ pMem->i = i; /* Program counter */
+ pMem++;
+
+ pMem->flags = MEM_Static|MEM_Str|MEM_Term;
+ pMem->z = sqlite3OpcodeNames[pOp->opcode]; /* Opcode */
+ pMem->n = strlen(pMem->z);
+ pMem->type = SQLITE_TEXT;
+ pMem->enc = SQLITE_UTF8;
+ pMem++;
+
+ pMem->flags = MEM_Int;
+ pMem->i = pOp->p1; /* P1 */
+ pMem->type = SQLITE_INTEGER;
+ pMem++;
+
+ pMem->flags = MEM_Int;
+ pMem->i = pOp->p2; /* P2 */
+ pMem->type = SQLITE_INTEGER;
+ pMem++;
+
+ pMem->flags = MEM_Short|MEM_Str|MEM_Term; /* P3 */
+ pMem->z = displayP3(pOp, pMem->zShort, sizeof(pMem->zShort));
+ pMem->type = SQLITE_TEXT;
+ pMem->enc = SQLITE_UTF8;
+
+ p->nResColumn = 5 - 2*(p->explain-1);
+ p->pTos = pMem;
+ p->rc = SQLITE_OK;
+ p->resOnStack = 1;
+ rc = SQLITE_ROW;
+ }
+ return rc;
+}
+#endif /* SQLITE_OMIT_EXPLAIN */
+
+/*
+** Print the SQL that was used to generate a VDBE program.
+*/
+void sqlite3VdbePrintSql(Vdbe *p){
+#ifdef SQLITE_DEBUG
+ int nOp = p->nOp;
+ VdbeOp *pOp;
+ if( nOp<1 ) return;
+ pOp = &p->aOp[nOp-1];
+ if( pOp->opcode==OP_Noop && pOp->p3!=0 ){
+ const char *z = pOp->p3;
+ while( isspace(*(u8*)z) ) z++;
+ printf("SQL: [%s]\n", z);
+ }
+#endif
+}
+
+/*
+** 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.
+*/
+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;
+
+ 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;
+
+ /* 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. (Added later:) The
+ ** resolveP2Values() call computes a tighter upper bound on the
+ ** stack size.
+ **
+ ** Allocation all the stack space we will ever need.
+ */
+ if( p->aStack==0 ){
+ int nArg; /* Maximum number of args passed to a user function. */
+ int nStack; /* Maximum number of stack entries required */
+ resolveP2Values(p, &nArg, &nStack);
+ resizeOpArray(p, p->nOp);
+ assert( nVar>=0 );
+ assert( nStack<p->nOp );
+ nStack = isExplain ? 10 : nStack;
+ p->aStack = sqliteMalloc(
+ nStack*sizeof(p->aStack[0]) /* aStack */
+ + nArg*sizeof(Mem*) /* apArg */
+ + nVar*sizeof(Mem) /* aVar */
+ + nVar*sizeof(char*) /* azVar */
+ + nMem*sizeof(Mem) /* aMem */
+ + nCursor*sizeof(Cursor*) /* apCsr */
+ );
+ if( !sqlite3_malloc_failed ){
+ p->aMem = &p->aStack[nStack];
+ p->nMem = nMem;
+ p->aVar = &p->aMem[nMem];
+ 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;
+ }
+ }
+ }
+ for(n=0; n<p->nMem; n++){
+ p->aMem[n].flags = MEM_Null;
+ }
+
+#ifdef SQLITE_DEBUG
+ if( (p->db->flags & SQLITE_VdbeListing)!=0
+ || sqlite3OsFileExists("vdbe_explain")
+ ){
+ int i;
+ printf("VDBE Program Listing:\n");
+ sqlite3VdbePrintSql(p);
+ for(i=0; i<p->nOp; i++){
+ sqlite3VdbePrintOp(stdout, i, &p->aOp[i]);
+ }
+ }
+ if( sqlite3OsFileExists("vdbe_trace") ){
+ p->trace = stdout;
+ }
+#endif
+ p->pTos = &p->aStack[-1];
+ p->pc = -1;
+ p->rc = SQLITE_OK;
+ p->uniqueCnt = 0;
+ p->returnDepth = 0;
+ p->errorAction = OE_Abort;
+ p->popStack = 0;
+ p->explain |= isExplain;
+ p->magic = VDBE_MAGIC_RUN;
+ p->nChange = 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 cursor and release all the resources that cursor happens
+** to hold.
+*/
+void sqlite3VdbeFreeCursor(Cursor *pCx){
+ if( pCx==0 ){
+ return;
+ }
+ if( pCx->pCursor ){
+ sqlite3BtreeCloseCursor(pCx->pCursor);
+ }
+ if( pCx->pBt ){
+ sqlite3BtreeClose(pCx->pBt);
+ }
+ sqliteFree(pCx->pData);
+ sqliteFree(pCx->aType);
+ sqliteFree(pCx);
+}
+
+/*
+** Close all cursors
+*/
+static void closeAllCursors(Vdbe *p){
+ int i;
+ if( p->apCsr==0 ) return;
+ for(i=0; i<p->nCursor; i++){
+ sqlite3VdbeFreeCursor(p->apCsr[i]);
+ 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 i;
+ if( p->aStack ){
+ releaseMemArray(p->aStack, 1 + (p->pTos - p->aStack));
+ p->pTos = &p->aStack[-1];
+ }
+ closeAllCursors(p);
+ releaseMemArray(p->aMem, p->nMem);
+ sqlite3VdbeFifoClear(&p->sFifo);
+ if( p->contextStack ){
+ for(i=0; i<p->contextStackTop; i++){
+ sqlite3VdbeFifoClear(&p->contextStack[i].sFifo);
+ }
+ sqliteFree(p->contextStack);
+ }
+ p->contextStack = 0;
+ p->contextStackDepth = 0;
+ p->contextStackTop = 0;
+ sqliteFree(p->zErrMsg);
+ p->zErrMsg = 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().
+*/
+void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){
+ Mem *pColName;
+ int n;
+ assert( 0==p->nResColumn );
+ p->nResColumn = nResColumn;
+ n = nResColumn*2;
+ p->aColName = pColName = (Mem*)sqliteMalloc( sizeof(Mem)*n );
+ if( p->aColName==0 ) return;
+ while( n-- > 0 ){
+ (pColName++)->flags = MEM_Null;
+ }
+}
+
+/*
+** 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==P3_STATIC it means that zName is a pointer to a constant static
+** string and we can just copy the pointer. If it is P3_DYNAMIC, then
+** the string is freed using sqliteFree() when the vdbe is finished with
+** it. Otherwise, N bytes of zName are copied.
+*/
+int sqlite3VdbeSetColName(Vdbe *p, int idx, const char *zName, int N){
+ int rc;
+ Mem *pColName;
+ assert( idx<(2*p->nResColumn) );
+ if( sqlite3_malloc_failed ) return SQLITE_NOMEM;
+ assert( p->aColName!=0 );
+ pColName = &(p->aColName[idx]);
+ if( N==P3_DYNAMIC || N==P3_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==P3_DYNAMIC ){
+ pColName->flags = (pColName->flags&(~MEM_Static))|MEM_Dyn;
+ pColName->xDel = 0;
+ }
+ 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;
+
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt && sqlite3BtreeIsInTrans(pBt) ){
+ needXcommit = 1;
+ if( i!=1 ) nTrans++;
+ }
+ }
+
+ /* If there are any write-transactions at all, invoke the commit hook */
+ if( needXcommit && db->xCommitCallback ){
+ int rc;
+ sqlite3SafetyOff(db);
+ rc = db->xCommitCallback(db->pCommitArg);
+ 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 = sqlite3BtreeSync(pBt, 0);
+ }
+ }
+
+ /* Do the commit only if all databases successfully synced */
+ if( rc==SQLITE_OK ){
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ sqlite3BtreeCommit(pBt);
+ }
+ }
+ }
+ }
+
+ /* 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{
+ int needSync = 0;
+ char *zMaster = 0; /* File-name for the master journal */
+ char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt);
+ OsFile master;
+
+ /* Select a master journal file name */
+ do {
+ u32 random;
+ sqliteFree(zMaster);
+ sqlite3Randomness(sizeof(random), &random);
+ zMaster = sqlite3MPrintf("%s-mj%08X", zMainFile, random&0x7fffffff);
+ if( !zMaster ){
+ return SQLITE_NOMEM;
+ }
+ }while( sqlite3OsFileExists(zMaster) );
+
+ /* Open the master journal. */
+ memset(&master, 0, sizeof(master));
+ rc = sqlite3OsOpenExclusive(zMaster, &master, 0);
+ if( rc!=SQLITE_OK ){
+ sqliteFree(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( pBt && sqlite3BtreeIsInTrans(pBt) ){
+ char const *zFile = sqlite3BtreeGetJournalname(pBt);
+ if( zFile[0]==0 ) continue; /* Ignore :memory: databases */
+ if( !needSync && !sqlite3BtreeSyncDisabled(pBt) ){
+ needSync = 1;
+ }
+ rc = sqlite3OsWrite(&master, zFile, strlen(zFile)+1);
+ if( rc!=SQLITE_OK ){
+ sqlite3OsClose(&master);
+ sqlite3OsDelete(zMaster);
+ sqliteFree(zMaster);
+ return rc;
+ }
+ }
+ }
+
+
+ /* Sync the master journal file. Before doing this, open the directory
+ ** the master journal file is store in so that it gets synced too.
+ */
+ zMainFile = sqlite3BtreeGetDirname(db->aDb[0].pBt);
+ rc = sqlite3OsOpenDirectory(zMainFile, &master);
+ if( rc!=SQLITE_OK ||
+ (needSync && (rc=sqlite3OsSync(&master,0))!=SQLITE_OK) ){
+ sqlite3OsClose(&master);
+ sqlite3OsDelete(zMaster);
+ sqliteFree(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 sqlite3BtreeSync(),
+ ** 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; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt && sqlite3BtreeIsInTrans(pBt) ){
+ rc = sqlite3BtreeSync(pBt, zMaster);
+ if( rc!=SQLITE_OK ){
+ sqlite3OsClose(&master);
+ sqliteFree(zMaster);
+ return rc;
+ }
+ }
+ }
+ sqlite3OsClose(&master);
+
+ /* 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(zMaster);
+ assert( rc==SQLITE_OK );
+ sqliteFree(zMaster);
+ zMaster = 0;
+ rc = sqlite3OsSyncDirectory(zMainFile);
+ if( rc!=SQLITE_OK ){
+ /* This is not good. The master journal file has been deleted, but
+ ** the directory sync failed. There is no completely safe course of
+ ** action from here. The individual journals contain the name of the
+ ** master journal file, but there is no way of knowing if that
+ ** master journal exists now or if it will exist after the operating
+ ** system crash that may follow the fsync() failure.
+ */
+ return rc;
+ }
+
+ /* All files and directories have already been synced, so the following
+ ** calls to sqlite3BtreeCommit() are only closing files and deleting
+ ** 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.
+ */
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ sqlite3BtreeCommit(pBt);
+ }
+ }
+ }
+#endif
+
+ return rc;
+}
+
+/*
+** Find every active VM other than pVdbe and change its status to
+** aborted. This happens when one VM causes a rollback due to an
+** ON CONFLICT ROLLBACK clause (for example). The other VMs must be
+** aborted so that they do not have data rolled out from underneath
+** them leading to a segfault.
+*/
+static void abortOtherActiveVdbes(Vdbe *pVdbe){
+ Vdbe *pOther;
+ for(pOther=pVdbe->db->pVdbe; pOther; pOther=pOther->pNext){
+ if( pOther==pVdbe ) continue;
+ if( pOther->magic!=VDBE_MAGIC_RUN || pOther->pc<0 ) continue;
+ closeAllCursors(pOther);
+ pOther->aborted = 1;
+ }
+}
+
+/*
+** 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
+
+/*
+** 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.
+**
+** 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.
+*/
+int sqlite3VdbeHalt(Vdbe *p){
+ sqlite3 *db = p->db;
+ int i;
+ int (*xFunc)(Btree *pBt) = 0; /* Function to call on each btree backend */
+
+ if( p->magic!=VDBE_MAGIC_RUN ){
+ /* Already halted. Nothing to do. */
+ assert( p->magic==VDBE_MAGIC_HALT );
+ return SQLITE_OK;
+ }
+ closeAllCursors(p);
+ checkActiveVdbeCnt(db);
+ if( p->pc<0 ){
+ /* No commit or rollback needed if the program never started */
+ }else if( db->autoCommit && db->activeVdbeCnt==1 ){
+ if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){
+ /* The auto-commit flag is true, there are no other active queries
+ ** using this handle 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 ){
+ return SQLITE_BUSY;
+ }else if( rc!=SQLITE_OK ){
+ p->rc = rc;
+ xFunc = sqlite3BtreeRollback;
+ }
+ }else{
+ xFunc = sqlite3BtreeRollback;
+ }
+ }else{
+ if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){
+ xFunc = sqlite3BtreeCommitStmt;
+ }else if( p->errorAction==OE_Abort ){
+ xFunc = sqlite3BtreeRollbackStmt;
+ }else{
+ xFunc = sqlite3BtreeRollback;
+ db->autoCommit = 1;
+ abortOtherActiveVdbes(p);
+ }
+ }
+
+ /* If xFunc is not NULL, then it is one of sqlite3BtreeRollback,
+ ** 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.
+ */
+ for(i=0; xFunc && i<db->nDb; i++){
+ int rc;
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ rc = xFunc(pBt);
+ if( p->rc==SQLITE_OK ) p->rc = rc;
+ }
+ }
+
+ /* If this was an INSERT, UPDATE or DELETE, 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 ){
+ sqlite3RollbackInternalChanges(db);
+ }else if( db->flags & SQLITE_InternChanges ){
+ sqlite3CommitInternalChanges(db);
+ }
+
+ /* We have successfully halted and closed the VM. Record this fact. */
+ if( p->pc>=0 ){
+ db->activeVdbeCnt--;
+ }
+ p->magic = VDBE_MAGIC_HALT;
+ checkActiveVdbeCnt(db);
+
+ return 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.
+*/
+int sqlite3VdbeReset(Vdbe *p){
+ if( p->magic!=VDBE_MAGIC_RUN && p->magic!=VDBE_MAGIC_HALT ){
+ sqlite3Error(p->db, SQLITE_MISUSE, 0);
+ return SQLITE_MISUSE;
+ }
+
+ /* 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.
+ */
+ sqlite3VdbeHalt(p);
+
+ /* 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 ){
+ sqlite3Error(p->db, p->rc, "%s", p->zErrMsg);
+ sqliteFree(p->zErrMsg);
+ p->zErrMsg = 0;
+ }else if( p->rc ){
+ sqlite3Error(p->db, p->rc, 0);
+ }else{
+ sqlite3Error(p->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(p->db, p->rc, 0);
+ }
+
+ /* Reclaim all memory used by the VDBE
+ */
+ Cleanup(p);
+
+ /* Save profiling information from this VDBE run.
+ */
+ assert( p->pTos<&p->aStack[p->pc<0?0:p->pc] || sqlite3_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
+ );
+ sqlite3VdbePrintOp(out, i, &p->aOp[i]);
+ }
+ fclose(out);
+ }
+ }
+#endif
+ p->magic = VDBE_MAGIC_INIT;
+ p->aborted = 0;
+ if( p->rc==SQLITE_SCHEMA ){
+ sqlite3ResetInternalSchema(p->db, 0);
+ }
+ 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 sqlite3VdbeFinalize(Vdbe *p){
+ int rc = SQLITE_OK;
+
+ if( p->magic==VDBE_MAGIC_RUN || p->magic==VDBE_MAGIC_HALT ){
+ rc = sqlite3VdbeReset(p);
+ }else if( p->magic!=VDBE_MAGIC_INIT ){
+ return SQLITE_MISUSE;
+ }
+ 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.
+*/
+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.
+*/
+void sqlite3VdbeDelete(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;
+ }
+ if( p->aOp ){
+ for(i=0; i<p->nOp; i++){
+ Op *pOp = &p->aOp[i];
+ freeP3(pOp->p3type, pOp->p3);
+ }
+ sqliteFree(p->aOp);
+ }
+ releaseMemArray(p->aVar, p->nVar);
+ sqliteFree(p->aLabel);
+ sqliteFree(p->aStack);
+ releaseMemArray(p->aColName, p->nResColumn*2);
+ sqliteFree(p->aColName);
+ p->magic = VDBE_MAGIC_DEAD;
+ sqliteFree(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.
+*/
+int sqlite3VdbeCursorMoveto(Cursor *p){
+ if( p->deferredMoveto ){
+ int res, rc;
+ extern int sqlite3_search_count;
+ assert( p->isTable );
+ if( p->isTable ){
+ rc = sqlite3BtreeMoveto(p->pCursor, 0, p->movetoTarget, &res);
+ }else{
+ rc = sqlite3BtreeMoveto(p->pCursor,(char*)&p->movetoTarget,
+ sizeof(i64),&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;
+ }
+ sqlite3_search_count++;
+ p->deferredMoveto = 0;
+ p->cacheValid = 0;
+ }
+ 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-11 reserved for expansion
+** N>=12 and even (N-12)/2 BLOB
+** N>=13 and odd (N-13)/2 text
+**
+*/
+
+/*
+** Return the serial-type for the value stored in pMem.
+*/
+u32 sqlite3VdbeSerialType(Mem *pMem){
+ int flags = pMem->flags;
+
+ 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)0x00001000)<<32)-1)
+ i64 i = pMem->i;
+ u64 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;
+ }
+ if( flags&MEM_Str ){
+ int n = pMem->n;
+ assert( n>=0 );
+ return ((n*2) + 13);
+ }
+ if( flags&MEM_Blob ){
+ return (pMem->n*2 + 12);
+ }
+ return 0;
+}
+
+/*
+** Return the length of the data corresponding to the supplied serial-type.
+*/
+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];
+ }
+}
+
+/*
+** 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.
+*/
+int sqlite3VdbeSerialPut(unsigned char *buf, Mem *pMem){
+ u32 serial_type = sqlite3VdbeSerialType(pMem);
+ int len;
+
+ /* NULL */
+ if( serial_type==0 ){
+ return 0;
+ }
+
+ /* Integer and Real */
+ if( serial_type<=7 ){
+ u64 v;
+ int i;
+ if( serial_type==7 ){
+ v = *(u64*)&pMem->r;
+ }else{
+ v = *(u64*)&pMem->i;
+ }
+ len = i = sqlite3VdbeSerialTypeLen(serial_type);
+ while( i-- ){
+ buf[i] = (v&0xFF);
+ v >>= 8;
+ }
+ return len;
+ }
+
+ /* String or blob */
+ assert( serial_type>=12 );
+ len = sqlite3VdbeSerialTypeLen(serial_type);
+ memcpy(buf, pMem->z, len);
+ return len;
+}
+
+/*
+** 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.
+*/
+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 8: /* Reserved for future use */
+ case 9: /* Reserved for future use */
+ 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->i = (signed char)buf[0];
+ pMem->flags = MEM_Int;
+ return 1;
+ }
+ case 2: { /* 2-byte signed integer */
+ pMem->i = (((signed char)buf[0])<<8) | buf[1];
+ pMem->flags = MEM_Int;
+ return 2;
+ }
+ case 3: { /* 3-byte signed integer */
+ pMem->i = (((signed char)buf[0])<<16) | (buf[1]<<8) | buf[2];
+ pMem->flags = MEM_Int;
+ return 3;
+ }
+ case 4: { /* 4-byte signed integer */
+ pMem->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->i = *(i64*)&x;
+ pMem->flags = MEM_Int;
+ return 6;
+ }
+ case 6: /* 8-byte signed integer */
+ case 7: { /* IEEE floating point */
+ u64 x;
+ u32 y;
+#ifndef NDEBUG
+ /* Verify that integers and floating point values use the same
+ ** byte order. The byte order differs on some (broken) architectures.
+ */
+ static const u64 t1 = ((u64)0x3ff00000)<<32;
+ assert( 1.0==*(double*)&t1 );
+#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->i = *(i64*)&x;
+ pMem->flags = MEM_Int;
+ }else{
+ pMem->r = *(double*)&x;
+ pMem->flags = MEM_Real;
+ }
+ return 8;
+ }
+ 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;
+}
+
+/*
+** This function compares the two table rows or index records specified by
+** {nKey1, pKey1} and {nKey2, pKey2}, returning a negative, zero
+** or positive integer if {nKey1, pKey1} is less than, equal to or
+** greater than {nKey2, pKey2}. Both Key1 and Key2 must be byte strings
+** composed by the OP_MakeRecord opcode of the VDBE.
+*/
+int sqlite3VdbeRecordCompare(
+ void *userData,
+ int nKey1, const void *pKey1,
+ int nKey2, const void *pKey2
+){
+ KeyInfo *pKeyInfo = (KeyInfo*)userData;
+ u32 d1, d2; /* Offset into aKey[] of next data element */
+ u32 idx1, idx2; /* Offset into aKey[] of next header element */
+ u32 szHdr1, szHdr2; /* Number of bytes in header */
+ int i = 0;
+ int nField;
+ int rc = 0;
+ const unsigned char *aKey1 = (const unsigned char *)pKey1;
+ const unsigned char *aKey2 = (const unsigned char *)pKey2;
+
+ Mem mem1;
+ Mem mem2;
+ mem1.enc = pKeyInfo->enc;
+ mem2.enc = pKeyInfo->enc;
+
+ idx1 = sqlite3GetVarint32(pKey1, &szHdr1);
+ d1 = szHdr1;
+ idx2 = sqlite3GetVarint32(pKey2, &szHdr2);
+ d2 = szHdr2;
+ nField = pKeyInfo->nField;
+ while( idx1<szHdr1 && idx2<szHdr2 ){
+ u32 serial_type1;
+ u32 serial_type2;
+
+ /* Read the serial types for the next element in each key. */
+ idx1 += sqlite3GetVarint32(&aKey1[idx1], &serial_type1);
+ if( d1>=nKey1 && sqlite3VdbeSerialTypeLen(serial_type1)>0 ) break;
+ idx2 += sqlite3GetVarint32(&aKey2[idx2], &serial_type2);
+ if( d2>=nKey2 && sqlite3VdbeSerialTypeLen(serial_type2)>0 ) break;
+
+ /* Assert that there is enough space left in each key for the blob of
+ ** data to go with the serial type just read. This assert may fail if
+ ** the file is corrupted. Then read the value from each key into mem1
+ ** and mem2 respectively.
+ */
+ d1 += sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1);
+ d2 += sqlite3VdbeSerialGet(&aKey2[d2], serial_type2, &mem2);
+
+ rc = sqlite3MemCompare(&mem1, &mem2, i<nField ? pKeyInfo->aColl[i] : 0);
+ if( mem1.flags & MEM_Dyn ) sqlite3VdbeMemRelease(&mem1);
+ if( mem2.flags & MEM_Dyn ) sqlite3VdbeMemRelease(&mem2);
+ if( rc!=0 ){
+ break;
+ }
+ i++;
+ }
+
+ /* 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( d1<nKey1 ){
+ rc = 1;
+ }else if( d2<nKey2 ){
+ rc = -1;
+ }
+ }
+
+ 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.
+*/
+int sqlite3VdbeIdxRowidLen(int nKey, const u8 *aKey){
+ u32 szHdr; /* Size of the header */
+ u32 typeRowid; /* Serial type of the rowid */
+
+ sqlite3GetVarint32(aKey, &szHdr);
+ sqlite3GetVarint32(&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.
+*/
+int sqlite3VdbeIdxRowid(BtCursor *pCur, i64 *rowid){
+ i64 nCellKey;
+ 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;
+ }
+ rc = sqlite3VdbeMemFromBtree(pCur, 0, nCellKey, 1, &m);
+ if( rc ){
+ return rc;
+ }
+ sqlite3GetVarint32(m.z, &szHdr);
+ sqlite3GetVarint32(&m.z[szHdr-1], &typeRowid);
+ lenRowid = sqlite3VdbeSerialTypeLen(typeRowid);
+ sqlite3VdbeSerialGet(&m.z[m.n-lenRowid], typeRowid, &v);
+ *rowid = v.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.
+*/
+int sqlite3VdbeIdxKeyCompare(
+ Cursor *pC, /* The cursor to compare against */
+ int nKey, const u8 *pKey, /* The key to compare */
+ int *res /* Write the comparison result here */
+){
+ i64 nCellKey;
+ int rc;
+ BtCursor *pCur = pC->pCursor;
+ int lenRowid;
+ Mem m;
+
+ sqlite3BtreeKeySize(pCur, &nCellKey);
+ if( nCellKey<=0 ){
+ *res = 0;
+ return SQLITE_OK;
+ }
+ rc = sqlite3VdbeMemFromBtree(pC->pCursor, 0, nCellKey, 1, &m);
+ if( rc ){
+ return rc;
+ }
+ lenRowid = sqlite3VdbeIdxRowidLen(m.n, m.z);
+ *res = sqlite3VdbeRecordCompare(pC->pKeyInfo, m.n-lenRowid, m.z, nKey, pKey);
+ sqlite3VdbeMemRelease(&m);
+ return SQLITE_OK;
+}
+
+/*
+** This routine sets the value to be returned by subsequent calls to
+** sqlite3_changes() on the database handle 'db'.
+*/
+void sqlite3VdbeSetChanges(sqlite3 *db, int nChange){
+ db->nChange = nChange;
+ db->nTotalChange += nChange;
+}
+
+/*
+** Set a flag in the vdbe to update the change counter when it is finalised
+** or reset.
+*/
+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.
+*/
+void sqlite3ExpirePreparedStatements(sqlite3 *db){
+ Vdbe *p;
+ for(p = db->pVdbe; p; p=p->pNext){
+ p->expired = 1;
+ }
+}
+
+/*
+** Return the database associated with the Vdbe.
+*/
+sqlite3 *sqlite3VdbeDb(Vdbe *v){
+ return v->db;
+}
diff --git a/kexi/3rdparty/kexisql3/src/vdbefifo.c b/kexi/3rdparty/kexisql3/src/vdbefifo.c
new file mode 100644
index 000000000..7ea6c050f
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/vdbefifo.c
@@ -0,0 +1,114 @@
+/*
+** 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.
+*/
+#include "sqliteInt.h"
+#include "vdbeInt.h"
+
+/*
+** 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 *allocatePage(int nEntry){
+ FifoPage *pPage;
+ if( nEntry>32767 ){
+ nEntry = 32767;
+ }
+ pPage = sqliteMallocRaw( 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.
+*/
+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.
+*/
+int sqlite3VdbeFifoPush(Fifo *pFifo, i64 val){
+ FifoPage *pPage;
+ pPage = pFifo->pLast;
+ if( pPage==0 ){
+ pPage = pFifo->pLast = pFifo->pFirst = allocatePage(20);
+ if( pPage==0 ){
+ return SQLITE_NOMEM;
+ }
+ }else if( pPage->iWrite>=pPage->nSlot ){
+ pPage->pNext = allocatePage(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.
+*/
+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;
+ sqliteFree(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.
+*/
+void sqlite3VdbeFifoClear(Fifo *pFifo){
+ FifoPage *pPage, *pNextPage;
+ for(pPage=pFifo->pFirst; pPage; pPage=pNextPage){
+ pNextPage = pPage->pNext;
+ sqliteFree(pPage);
+ }
+ sqlite3VdbeFifoInit(pFifo);
+}
diff --git a/kexi/3rdparty/kexisql3/src/vdbemem.c b/kexi/3rdparty/kexisql3/src/vdbemem.c
new file mode 100644
index 000000000..0b7e193be
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/vdbemem.c
@@ -0,0 +1,840 @@
+/*
+** 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
+*/
+#include "sqliteInt.h"
+#include "os.h"
+#include <ctype.h>
+#include "vdbeInt.h"
+
+/*
+** 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.
+*/
+int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){
+ int rc;
+ if( !(pMem->flags&MEM_Str) || pMem->enc==desiredEnc ){
+ return SQLITE_OK;
+ }
+#ifdef SQLITE_OMIT_UTF16
+ return SQLITE_ERROR;
+#else
+ rc = sqlite3VdbeMemTranslate(pMem, desiredEnc);
+ if( rc==SQLITE_NOMEM ){
+ sqlite3VdbeMemRelease(pMem);
+ pMem->flags = MEM_Null;
+ pMem->z = 0;
+ }
+ return rc;
+#endif
+}
+
+/*
+** Make the given Mem object MEM_Dyn.
+**
+** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails.
+*/
+int sqlite3VdbeMemDynamicify(Mem *pMem){
+ int n = pMem->n;
+ u8 *z;
+ if( (pMem->flags & (MEM_Ephem|MEM_Static|MEM_Short))==0 ){
+ return SQLITE_OK;
+ }
+ assert( (pMem->flags & MEM_Dyn)==0 );
+ assert( pMem->flags & (MEM_Str|MEM_Blob) );
+ z = sqliteMallocRaw( n+2 );
+ if( z==0 ){
+ return SQLITE_NOMEM;
+ }
+ pMem->flags |= MEM_Dyn|MEM_Term;
+ pMem->xDel = 0;
+ memcpy(z, pMem->z, n );
+ z[n] = 0;
+ z[n+1] = 0;
+ pMem->z = z;
+ pMem->flags &= ~(MEM_Ephem|MEM_Static|MEM_Short);
+ return SQLITE_OK;
+}
+
+/*
+** 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.
+*/
+int sqlite3VdbeMemMakeWriteable(Mem *pMem){
+ int n;
+ u8 *z;
+ if( (pMem->flags & (MEM_Ephem|MEM_Static))==0 ){
+ return SQLITE_OK;
+ }
+ assert( (pMem->flags & MEM_Dyn)==0 );
+ assert( pMem->flags & (MEM_Str|MEM_Blob) );
+ if( (n = pMem->n)+2<sizeof(pMem->zShort) ){
+ z = pMem->zShort;
+ pMem->flags |= MEM_Short|MEM_Term;
+ }else{
+ z = sqliteMallocRaw( n+2 );
+ if( z==0 ){
+ return SQLITE_NOMEM;
+ }
+ pMem->flags |= MEM_Dyn|MEM_Term;
+ pMem->xDel = 0;
+ }
+ memcpy(z, pMem->z, n );
+ z[n] = 0;
+ z[n+1] = 0;
+ pMem->z = z;
+ pMem->flags &= ~(MEM_Ephem|MEM_Static);
+ return SQLITE_OK;
+}
+
+/*
+** Make sure the given Mem is \u0000 terminated.
+*/
+int sqlite3VdbeMemNulTerminate(Mem *pMem){
+ /* In SQLite, a string without a nul terminator occurs when a string
+ ** is loaded from disk (in this case the memory management is ephemeral),
+ ** or when it is supplied by the user as a bound variable or function
+ ** return value. Therefore, the memory management of the string must be
+ ** either ephemeral, static or controlled by a user-supplied destructor.
+ */
+ assert(
+ !(pMem->flags&MEM_Str) || /* it's not a string, or */
+ (pMem->flags&MEM_Term) || /* it's nul term. already, or */
+ (pMem->flags&(MEM_Ephem|MEM_Static)) || /* it's static or ephem, or */
+ (pMem->flags&MEM_Dyn && pMem->xDel) /* external management */
+ );
+ if( (pMem->flags & MEM_Term)!=0 || (pMem->flags & MEM_Str)==0 ){
+ return SQLITE_OK; /* Nothing to do */
+ }
+
+ if( pMem->flags & (MEM_Static|MEM_Ephem) ){
+ return sqlite3VdbeMemMakeWriteable(pMem);
+ }else{
+ char *z = sqliteMalloc(pMem->n+2);
+ if( !z ) return SQLITE_NOMEM;
+ memcpy(z, pMem->z, pMem->n);
+ z[pMem->n] = 0;
+ z[pMem->n+1] = 0;
+ pMem->xDel(pMem->z);
+ pMem->xDel = 0;
+ pMem->z = z;
+ }
+ 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.
+*/
+int sqlite3VdbeMemStringify(Mem *pMem, int enc){
+ int rc = SQLITE_OK;
+ int fg = pMem->flags;
+ u8 *z = pMem->zShort;
+
+ assert( !(fg&(MEM_Str|MEM_Blob)) );
+ assert( fg&(MEM_Int|MEM_Real) );
+
+ /* For a Real or Integer, use sqlite3_snprintf() 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_Real ){
+ sqlite3_snprintf(NBFS, z, "%!.15g", pMem->r);
+ }else{
+ assert( fg & MEM_Int );
+ sqlite3_snprintf(NBFS, z, "%lld", pMem->i);
+ }
+ pMem->n = strlen(z);
+ pMem->z = z;
+ pMem->enc = SQLITE_UTF8;
+ pMem->flags |= MEM_Str | MEM_Short | 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.
+*/
+void sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){
+ if( pFunc && pFunc->xFinalize ){
+ sqlite3_context ctx;
+ assert( (pMem->flags & MEM_Null)!=0 || pFunc==*(FuncDef**)&pMem->i );
+ ctx.s.flags = MEM_Null;
+ ctx.s.z = pMem->zShort;
+ ctx.pMem = pMem;
+ ctx.pFunc = pFunc;
+ pFunc->xFinalize(&ctx);
+ if( pMem->z && pMem->z!=pMem->zShort ){
+ sqliteFree( pMem->z );
+ }
+ *pMem = ctx.s;
+ if( pMem->flags & MEM_Short ){
+ pMem->z = pMem->zShort;
+ }
+ }
+}
+
+/*
+** 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).
+*/
+void sqlite3VdbeMemRelease(Mem *p){
+ if( p->flags & (MEM_Dyn|MEM_Agg) ){
+ if( p->xDel ){
+ if( p->flags & MEM_Agg ){
+ sqlite3VdbeMemFinalize(p, *(FuncDef**)&p->i);
+ assert( (p->flags & MEM_Agg)==0 );
+ sqlite3VdbeMemRelease(p);
+ }else{
+ p->xDel((void *)p->z);
+ }
+ }else{
+ sqliteFree(p->z);
+ }
+ p->z = 0;
+ p->xDel = 0;
+ }
+}
+
+/*
+** 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.
+*/
+i64 sqlite3VdbeIntValue(Mem *pMem){
+ int flags = pMem->flags;
+ if( flags & MEM_Int ){
+ return pMem->i;
+ }else if( flags & MEM_Real ){
+ return (i64)pMem->r;
+ }else if( flags & (MEM_Str|MEM_Blob) ){
+ i64 value;
+ if( sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8)
+ || sqlite3VdbeMemNulTerminate(pMem) ){
+ return SQLITE_NOMEM;
+ }
+ assert( pMem->z );
+ sqlite3atoi64(pMem->z, &value);
+ return value;
+ }else{
+ return 0;
+ }
+}
+
+/*
+** Convert pMem to type integer. Invalidate any prior representations.
+*/
+int sqlite3VdbeMemIntegerify(Mem *pMem){
+ pMem->i = sqlite3VdbeIntValue(pMem);
+ sqlite3VdbeMemRelease(pMem);
+ pMem->flags = MEM_Int;
+ return SQLITE_OK;
+}
+
+/*
+** 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.
+*/
+double sqlite3VdbeRealValue(Mem *pMem){
+ if( pMem->flags & MEM_Real ){
+ return pMem->r;
+ }else if( pMem->flags & MEM_Int ){
+ return (double)pMem->i;
+ }else if( pMem->flags & (MEM_Str|MEM_Blob) ){
+ double val = 0.0;
+ if( sqlite3VdbeChangeEncoding(pMem, SQLITE_UTF8)
+ || sqlite3VdbeMemNulTerminate(pMem) ){
+ return SQLITE_NOMEM;
+ }
+ assert( pMem->z );
+ sqlite3AtoF(pMem->z, &val);
+ return val;
+ }else{
+ return 0.0;
+ }
+}
+
+/*
+** Convert pMem so that it is of type MEM_Real. Invalidate any
+** prior representations.
+*/
+int sqlite3VdbeMemRealify(Mem *pMem){
+ pMem->r = sqlite3VdbeRealValue(pMem);
+ sqlite3VdbeMemRelease(pMem);
+ pMem->flags = MEM_Real;
+ return SQLITE_OK;
+}
+
+/*
+** Delete any previous value and set the value stored in *pMem to NULL.
+*/
+void sqlite3VdbeMemSetNull(Mem *pMem){
+ sqlite3VdbeMemRelease(pMem);
+ pMem->flags = MEM_Null;
+ pMem->type = SQLITE_NULL;
+ pMem->n = 0;
+}
+
+/*
+** Delete any previous value and set the value stored in *pMem to val,
+** manifest type INTEGER.
+*/
+void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){
+ sqlite3VdbeMemRelease(pMem);
+ pMem->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.
+*/
+void sqlite3VdbeMemSetDouble(Mem *pMem, double val){
+ sqlite3VdbeMemRelease(pMem);
+ pMem->r = val;
+ pMem->flags = MEM_Real;
+ pMem->type = SQLITE_FLOAT;
+}
+
+/*
+** Make an shallow copy of pFrom into pTo. Prior contents of
+** pTo are overwritten. 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).
+*/
+void sqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int srcType){
+ memcpy(pTo, pFrom, sizeof(*pFrom)-sizeof(pFrom->zShort));
+ pTo->xDel = 0;
+ if( pTo->flags & (MEM_Str|MEM_Blob) ){
+ pTo->flags &= ~(MEM_Dyn|MEM_Static|MEM_Short|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.
+*/
+int sqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){
+ int rc;
+ if( pTo->flags & MEM_Dyn ){
+ sqlite3VdbeMemRelease(pTo);
+ }
+ sqlite3VdbeMemShallowCopy(pTo, pFrom, MEM_Ephem);
+ if( pTo->flags & MEM_Ephem ){
+ rc = sqlite3VdbeMemMakeWriteable(pTo);
+ }else{
+ rc = SQLITE_OK;
+ }
+ 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_NOMEM
+** might be returned if pFrom held ephemeral data and we were unable
+** to allocate enough space to make a copy.
+*/
+int sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){
+ int rc;
+ if( pTo->flags & MEM_Dyn ){
+ sqlite3VdbeMemRelease(pTo);
+ }
+ memcpy(pTo, pFrom, sizeof(Mem));
+ if( pFrom->flags & MEM_Short ){
+ pTo->z = pTo->zShort;
+ }
+ pFrom->flags = MEM_Null;
+ pFrom->xDel = 0;
+ if( pTo->flags & MEM_Ephem ){
+ rc = sqlite3VdbeMemMakeWriteable(pTo);
+ }else{
+ rc = SQLITE_OK;
+ }
+ return rc;
+}
+
+/*
+** Change the value of a Mem to be a string or a BLOB.
+*/
+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 */
+){
+ sqlite3VdbeMemRelease(pMem);
+ if( !z ){
+ pMem->flags = MEM_Null;
+ pMem->type = SQLITE_NULL;
+ return SQLITE_OK;
+ }
+
+ pMem->z = (char *)z;
+ if( xDel==SQLITE_STATIC ){
+ pMem->flags = MEM_Static;
+ }else if( xDel==SQLITE_TRANSIENT ){
+ pMem->flags = MEM_Ephem;
+ }else{
+ pMem->flags = MEM_Dyn;
+ pMem->xDel = xDel;
+ }
+
+ pMem->enc = enc;
+ pMem->type = enc==0 ? SQLITE_BLOB : SQLITE_TEXT;
+ pMem->n = n;
+
+ assert( enc==0 || enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE
+ || enc==SQLITE_UTF16BE );
+ switch( enc ){
+ case 0:
+ pMem->flags |= MEM_Blob;
+ pMem->enc = SQLITE_UTF8;
+ break;
+
+ case SQLITE_UTF8:
+ pMem->flags |= MEM_Str;
+ if( n<0 ){
+ pMem->n = strlen(z);
+ pMem->flags |= MEM_Term;
+ }
+ break;
+
+#ifndef SQLITE_OMIT_UTF16
+ case SQLITE_UTF16LE:
+ case SQLITE_UTF16BE:
+ pMem->flags |= MEM_Str;
+ if( pMem->n<0 ){
+ pMem->n = sqlite3utf16ByteLen(pMem->z,-1);
+ pMem->flags |= MEM_Term;
+ }
+ if( sqlite3VdbeMemHandleBom(pMem) ){
+ return SQLITE_NOMEM;
+ }
+#endif /* SQLITE_OMIT_UTF16 */
+ }
+ if( pMem->flags&MEM_Ephem ){
+ return sqlite3VdbeMemMakeWriteable(pMem);
+ }
+ 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.
+*/
+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->i;
+ }else{
+ r1 = pMem1->r;
+ }
+ if( (f2&MEM_Real)==0 ){
+ r2 = pMem2->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->i < pMem2->i ) return -1;
+ if( pMem1->i > pMem2->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 );
+
+ /* This assert may fail if the collation sequence is deleted after this
+ ** vdbe program is compiled. The documentation defines this as an
+ ** undefined condition. A crash is usual result.
+ */
+ assert( !pColl || pColl->xCmp );
+
+ if( pColl ){
+ if( pMem1->enc==pColl->enc ){
+ return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z);
+ }else{
+ u8 origEnc = pMem1->enc;
+ rc = pColl->xCmp(
+ pColl->pUser,
+ sqlite3ValueBytes((sqlite3_value*)pMem1, pColl->enc),
+ sqlite3ValueText((sqlite3_value*)pMem1, pColl->enc),
+ sqlite3ValueBytes((sqlite3_value*)pMem2, pColl->enc),
+ sqlite3ValueText((sqlite3_value*)pMem2, pColl->enc)
+ );
+ sqlite3ValueBytes((sqlite3_value*)pMem1, origEnc);
+ sqlite3ValueText((sqlite3_value*)pMem1, origEnc);
+ sqlite3ValueBytes((sqlite3_value*)pMem2, 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.
+*/
+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; /* Number of bytes available on the local btree page */
+
+ if( key ){
+ zData = (char *)sqlite3BtreeKeyFetch(pCur, &available);
+ }else{
+ zData = (char *)sqlite3BtreeDataFetch(pCur, &available);
+ }
+
+ pMem->n = amt;
+ if( offset+amt<=available ){
+ pMem->z = &zData[offset];
+ pMem->flags = MEM_Blob|MEM_Ephem;
+ }else{
+ int rc;
+ if( amt>NBFS-2 ){
+ zData = (char *)sqliteMallocRaw(amt+2);
+ if( !zData ){
+ return SQLITE_NOMEM;
+ }
+ pMem->flags = MEM_Blob|MEM_Dyn|MEM_Term;
+ pMem->xDel = 0;
+ }else{
+ zData = &(pMem->zShort[0]);
+ pMem->flags = MEM_Blob|MEM_Short|MEM_Term;
+ }
+ pMem->z = zData;
+ pMem->enc = 0;
+ pMem->type = SQLITE_BLOB;
+
+ if( key ){
+ rc = sqlite3BtreeKey(pCur, offset, amt, zData);
+ }else{
+ rc = sqlite3BtreeData(pCur, offset, amt, zData);
+ }
+ zData[amt] = 0;
+ zData[amt+1] = 0;
+ if( rc!=SQLITE_OK ){
+ if( amt>NBFS-2 ){
+ assert( zData!=pMem->zShort );
+ assert( pMem->flags & MEM_Dyn );
+ sqliteFree(zData);
+ } else {
+ assert( zData==pMem->zShort );
+ assert( pMem->flags & MEM_Short );
+ }
+ return rc;
+ }
+ }
+
+ return SQLITE_OK;
+}
+
+#ifndef NDEBUG
+/*
+** Perform various checks on the memory cell pMem. An assert() will
+** fail if pMem is internally inconsistent.
+*/
+void sqlite3VdbeMemSanity(Mem *pMem, u8 db_enc){
+ int flags = pMem->flags;
+ assert( flags!=0 ); /* Must define some type */
+ if( pMem->flags & (MEM_Str|MEM_Blob) ){
+ int x = pMem->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( (pMem->flags & MEM_Short)==0 || pMem->z==pMem->zShort );
+ assert( (pMem->flags & 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->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.
+*/
+const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){
+ if( !pVal ) return 0;
+ assert( enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE || enc==SQLITE_UTF8);
+
+ if( pVal->flags&MEM_Null ){
+ return 0;
+ }
+ if( pVal->flags&MEM_Str ){
+ sqlite3VdbeChangeEncoding(pVal, enc);
+ }else if( !(pVal->flags&MEM_Blob) ){
+ sqlite3VdbeMemStringify(pVal, enc);
+ }
+ return (const void *)(pVal->z);
+}
+
+/*
+** Create a new sqlite3_value object.
+*/
+sqlite3_value* sqlite3ValueNew(void){
+ Mem *p = sqliteMalloc(sizeof(*p));
+ if( p ){
+ p->flags = MEM_Null;
+ p->type = SQLITE_NULL;
+ }
+ 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", "NULL", "'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.
+*/
+int sqlite3ValueFromExpr(
+ Expr *pExpr,
+ u8 enc,
+ u8 affinity,
+ sqlite3_value **ppVal
+){
+ 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 = sqliteStrNDup(pExpr->token.z, pExpr->token.n);
+ pVal = sqlite3ValueNew();
+ if( !zVal || !pVal ) goto no_mem;
+ sqlite3Dequote(zVal);
+ sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, sqlite3FreeX);
+ 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(pExpr->pLeft, enc, affinity, &pVal) ){
+ pVal->i = -1 * pVal->i;
+ pVal->r = -1.0 * pVal->r;
+ }
+ }
+#ifndef SQLITE_OMIT_BLOB_LITERAL
+ else if( op==TK_BLOB ){
+ int nVal;
+ pVal = sqlite3ValueNew();
+ zVal = sqliteStrNDup(pExpr->token.z+1, pExpr->token.n-1);
+ if( !zVal || !pVal ) goto no_mem;
+ sqlite3Dequote(zVal);
+ nVal = strlen(zVal)/2;
+ sqlite3VdbeMemSetStr(pVal, sqlite3HexToBlob(zVal), nVal, 0, sqlite3FreeX);
+ sqliteFree(zVal);
+ }
+#endif
+
+ *ppVal = pVal;
+ return SQLITE_OK;
+
+no_mem:
+ sqliteFree(zVal);
+ sqlite3ValueFree(pVal);
+ *ppVal = 0;
+ return SQLITE_NOMEM;
+}
+
+/*
+** Change the string value of an sqlite3_value object
+*/
+void sqlite3ValueSetStr(
+ sqlite3_value *v,
+ int n,
+ const void *z,
+ u8 enc,
+ void (*xDel)(void*)
+){
+ if( v ) sqlite3VdbeMemSetStr((Mem *)v, z, n, enc, xDel);
+}
+
+/*
+** Free an sqlite3_value object
+*/
+void sqlite3ValueFree(sqlite3_value *v){
+ if( !v ) return;
+ sqlite3ValueSetStr(v, 0, 0, SQLITE_UTF8, SQLITE_STATIC);
+ sqliteFree(v);
+}
+
+/*
+** Return the number of bytes in the sqlite3_value object assuming
+** that it uses the encoding "enc"
+*/
+int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){
+ Mem *p = (Mem*)pVal;
+ if( (p->flags & MEM_Blob)!=0 || sqlite3ValueText(pVal, enc) ){
+ return p->n;
+ }
+ return 0;
+}
diff --git a/kexi/3rdparty/kexisql3/src/where.c b/kexi/3rdparty/kexisql3/src/where.c
new file mode 100644
index 000000000..e63f2763c
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/src/where.c
@@ -0,0 +1,2052 @@
+/*
+** 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 548347 2006-06-05 10:53:00Z staniek $
+*/
+#include "sqliteInt.h"
+
+/*
+** The number of bits in a Bitmask. "BMS" means "BitMask Size".
+*/
+#define BMS (sizeof(Bitmask)*8)
+
+/*
+** Determine the number of elements in an array.
+*/
+#define ARRAYSIZE(X) (sizeof(X)/sizeof(X[0]))
+
+/*
+** Trace output macros
+*/
+#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
+int sqlite3_where_trace = 0;
+# define TRACE(X) if(sqlite3_where_trace) sqlite3DebugPrintf X
+#else
+# define TRACE(X)
+#endif
+
+/* Forward reference
+*/
+typedef struct WhereClause WhereClause;
+
+/*
+** 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 operator; /* 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 */
+ 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.
+*/
+typedef struct ExprMaskSet ExprMaskSet;
+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))
+
+/*
+** Value for flags returned by bestIndex()
+*/
+#define WHERE_ROWID_EQ 0x0001 /* rowid=EXPR or rowid IN (...) */
+#define WHERE_ROWID_RANGE 0x0002 /* rowid<EXPR and/or rowid>EXPR */
+#define WHERE_COLUMN_EQ 0x0010 /* x=EXPR or x IN (...) */
+#define WHERE_COLUMN_RANGE 0x0020 /* x<EXPR and/or x>EXPR */
+#define WHERE_COLUMN_IN 0x0040 /* x IN (...) */
+#define WHERE_TOP_LIMIT 0x0100 /* x<EXPR or x<=EXPR constraint */
+#define WHERE_BTM_LIMIT 0x0200 /* x>EXPR or x>=EXPR constraint */
+#define WHERE_IDX_ONLY 0x0800 /* Use index only - omit table */
+#define WHERE_ORDERBY 0x1000 /* Output will appear in correct order */
+#define WHERE_REVERSE 0x2000 /* Scan in reverse order */
+#define WHERE_UNIQUE 0x4000 /* Selects no more than one row */
+
+/*
+** Initialize a preallocated WhereClause structure.
+*/
+static void whereClauseInit(WhereClause *pWC, Parse *pParse){
+ pWC->pParse = pParse;
+ 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 ){
+ sqliteFree(pWC->a);
+ }
+}
+
+/*
+** Add a new entries to the WhereClause structure. Increase the allocated
+** space as necessary.
+**
+** 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 = sqliteMalloc( sizeof(pWC->a[0])*pWC->nSlot*2 );
+ if( pWC->a==0 ) return 0;
+ memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm);
+ if( pOld!=pWC->aStatic ){
+ sqliteFree(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;
+ if( pS==0 ){
+ mask = 0;
+ }else{
+ 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);
+ }
+ 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);
+}
+
+/*
+** 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".
+*/
+static void exprCommute(Expr *pExpr){
+ assert( allowedOp(pExpr->op) && pExpr->op!=TK_IN );
+ SWAP(CollSeq*,pExpr->pRight->pColl,pExpr->pLeft->pColl);
+ 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{
+ c = WO_EQ<<(op-TK_EQ);
+ }
+ 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->operator & op)!=0
+ ){
+ if( iCur>=0 && pIdx ){
+ Expr *pX = pTerm->pExpr;
+ CollSeq *pColl;
+ char idxaff;
+ int k;
+ Parse *pParse = pWC->pParse;
+
+ idxaff = pIdx->pTable->aCol[iColumn].affinity;
+ if( !sqlite3IndexAffinityOk(pX, idxaff) ) continue;
+ pColl = sqlite3ExprCollSeq(pParse, pX->pLeft);
+ if( !pColl ){
+ if( pX->pRight ){
+ pColl = sqlite3ExprCollSeq(pParse, pX->pRight);
+ }
+ if( !pColl ){
+ pColl = pParse->db->pDfltColl;
+ }
+ }
+ for(k=0; k<pIdx->nColumn && pIdx->aiColumn[k]!=iColumn; k++){}
+ assert( k<pIdx->nColumn );
+ if( pColl!=pIdx->keyInfo.aColl[k] ) continue;
+ }
+ return pTerm;
+ }
+ }
+ return 0;
+}
+
+/* Forward reference */
+static void exprAnalyze(SrcList*, ExprMaskSet*, WhereClause*, int);
+
+/*
+** Call exprAnalyze on all terms in a WHERE clause.
+**
+**
+*/
+static void exprAnalyzeAll(
+ SrcList *pTabList, /* the FROM clause */
+ ExprMaskSet *pMaskSet, /* table masks */
+ WhereClause *pWC /* the WHERE clause to be analyzed */
+){
+ int i;
+ for(i=pWC->nTerm-1; i>=0; i--){
+ exprAnalyze(pTabList, pMaskSet, 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 */
+){
+ const char *z;
+ Expr *pRight, *pLeft;
+ ExprList *pList;
+ int c, cnt;
+ int noCase;
+ char wc[3];
+ CollSeq *pColl;
+
+ if( !sqlite3IsLikeFunction(db, pExpr, &noCase, wc) ){
+ return 0;
+ }
+ pList = pExpr->pList;
+ pRight = pList->a[0].pExpr;
+ if( pRight->op!=TK_STRING ){
+ return 0;
+ }
+ pLeft = pList->a[1].pExpr;
+ if( pLeft->op!=TK_COLUMN ){
+ return 0;
+ }
+ pColl = pLeft->pColl;
+ if( pColl==0 ){
+ pColl = db->pDfltColl;
+ }
+ if( (pColl->type!=SQLITE_COLL_BINARY || noCase) &&
+ (pColl->type!=SQLITE_COLL_NOCASE || !noCase) ){
+ return 0;
+ }
+ sqlite3DequoteExpr(pRight);
+ z = pRight->token.z;
+ for(cnt=0; (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 */
+
+/*
+** 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 */
+ ExprMaskSet *pMaskSet, /* table masks */
+ WhereClause *pWC, /* the WHERE clause */
+ int idxTerm /* Index of the term to be analyzed */
+){
+ WhereTerm *pTerm = &pWC->a[idxTerm];
+ Expr *pExpr = pTerm->pExpr;
+ Bitmask prereqLeft;
+ Bitmask prereqAll;
+ int nPattern;
+ int isComplete;
+
+ if( sqlite3_malloc_failed ) return;
+ prereqLeft = exprTableUsage(pMaskSet, pExpr->pLeft);
+ if( pExpr->op==TK_IN ){
+ assert( pExpr->pRight==0 );
+ pTerm->prereqRight = exprListTableUsage(pMaskSet, pExpr->pList)
+ | exprSelectTableUsage(pMaskSet, pExpr->pSelect);
+ }else{
+ pTerm->prereqRight = exprTableUsage(pMaskSet, pExpr->pRight);
+ }
+ prereqAll = exprTableUsage(pMaskSet, pExpr);
+ if( ExprHasProperty(pExpr, EP_FromJoin) ){
+ prereqAll |= getMask(pMaskSet, pExpr->iRightJoinTable);
+ }
+ pTerm->prereqAll = prereqAll;
+ pTerm->leftCursor = -1;
+ pTerm->iParent = -1;
+ pTerm->operator = 0;
+ if( allowedOp(pExpr->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->operator = operatorMask(pExpr->op);
+ }
+ if( pRight && pRight->op==TK_COLUMN ){
+ WhereTerm *pNew;
+ Expr *pDup;
+ if( pTerm->leftCursor>=0 ){
+ int idxNew;
+ pDup = sqlite3ExprDup(pExpr);
+ 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->operator = 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(ops[i], sqlite3ExprDup(pExpr->pLeft),
+ sqlite3ExprDup(pList->a[i].pExpr), 0);
+ idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
+ exprAnalyze(pSrc, pMaskSet, pWC, idxNew);
+ pTerm = &pWC->a[idxTerm];
+ pWC->a[idxNew].iParent = idxTerm;
+ }
+ pTerm->nChild = 2;
+ }
+#endif /* SQLITE_OMIT_BETWEEN_OPTIMIZATION */
+
+#ifndef SQLITE_OMIT_OR_OPTIMIZATION
+ /* 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)
+ */
+ 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);
+ whereSplit(&sOr, pExpr, TK_OR);
+ exprAnalyzeAll(pSrc, pMaskSet, &sOr);
+ assert( sOr.nTerm>0 );
+ j = 0;
+ do{
+ 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->operator!=WO_EQ ){
+ goto or_not_possible;
+ }
+ if( pOrTerm->leftCursor==iCursor && pOrTerm->leftColumn==iColumn ){
+ pOrTerm->flags |= TERM_OR_OK;
+ }else if( (pOrTerm->flags & TERM_COPIED)!=0 ||
+ ((pOrTerm->flags & TERM_VIRTUAL)!=0 &&
+ (sOr.a[pOrTerm->iParent].flags & TERM_OR_OK)!=0) ){
+ pOrTerm->flags &= ~TERM_OR_OK;
+ }else{
+ ok = 0;
+ }
+ }
+ }while( !ok && (sOr.a[j++].flags & TERM_COPIED)!=0 && j<sOr.nTerm );
+ if( ok ){
+ ExprList *pList = 0;
+ Expr *pNew, *pDup;
+ for(i=sOr.nTerm-1, pOrTerm=sOr.a; i>=0 && ok; i--, pOrTerm++){
+ if( (pOrTerm->flags & TERM_OR_OK)==0 ) continue;
+ pDup = sqlite3ExprDup(pOrTerm->pExpr->pRight);
+ pList = sqlite3ExprListAppend(pList, pDup, 0);
+ }
+ pDup = sqlite3Expr(TK_COLUMN, 0, 0, 0);
+ if( pDup ){
+ pDup->iTable = iCursor;
+ pDup->iColumn = iColumn;
+ }
+ pNew = sqlite3Expr(TK_IN, pDup, 0, 0);
+ if( pNew ){
+ pNew->pList = pList;
+ }else{
+ sqlite3ExprListDelete(pList);
+ }
+ pTerm->pExpr = pNew;
+ pTerm->flags |= TERM_DYNAMIC;
+ exprAnalyze(pSrc, pMaskSet, pWC, idxTerm);
+ pTerm = &pWC->a[idxTerm];
+ }
+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.
+ */
+ if( isLikeOrGlob(pWC->pParse->db, pExpr, &nPattern, &isComplete) ){
+ 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 = sqlite3Expr(TK_STRING, 0, 0, 0);
+ if( pStr1 ){
+ sqlite3TokenCopy(&pStr1->token, &pRight->token);
+ pStr1->token.n = nPattern;
+ }
+ pStr2 = sqlite3ExprDup(pStr1);
+ if( pStr2 ){
+ assert( pStr2->token.dyn );
+ ++*(u8*)&pStr2->token.z[nPattern-1];
+ }
+ pNewExpr1 = sqlite3Expr(TK_GE, sqlite3ExprDup(pLeft), pStr1, 0);
+ idxNew1 = whereClauseInsert(pWC, pNewExpr1, TERM_VIRTUAL|TERM_DYNAMIC);
+ exprAnalyze(pSrc, pMaskSet, pWC, idxNew1);
+ pNewExpr2 = sqlite3Expr(TK_LT, sqlite3ExprDup(pLeft), pStr2, 0);
+ idxNew2 = whereClauseInsert(pWC, pNewExpr2, TERM_VIRTUAL|TERM_DYNAMIC);
+ exprAnalyze(pSrc, pMaskSet, 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 */
+}
+
+
+/*
+** 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 */
+ Index *pIdx, /* The index we are testing */
+ Table *pTab, /* The table to be sorted */
+ int base, /* Cursor number for pTab */
+ 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 = SQLITE_SO_ASC; /* Which direction we are sorting */
+ 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.
+ */
+ 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 */
+
+ 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 */
+ return 0;
+ }
+ pColl = sqlite3ExprCollSeq(pParse, pExpr);
+ if( !pColl ) pColl = db->pDfltColl;
+ if( pExpr->iColumn!=pIdx->aiColumn[i] || pColl!=pIdx->keyInfo.aColl[i] ){
+ /* 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;
+ }
+ }
+ if( i>nEqCol ){
+ if( pTerm->sortOrder!=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 = pTerm->sortOrder;
+ }
+ j++;
+ pTerm++;
+ }
+
+ /* The index can be used for sorting if all terms of the ORDER BY clause
+ ** are covered.
+ */
+ if( j>=nTerm ){
+ *pbRev = sortOrder==SQLITE_SO_DESC;
+ 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 */
+ 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( pOrderBy->nExpr==1 && p->op==TK_COLUMN && p->iTable==base
+ && p->iColumn==-1 ){
+ *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.0;
+ double x = 10.0;
+ while( N>x ){
+ logN += 1.0;
+ x *= 10;
+ }
+ return logN;
+}
+
+/*
+** 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 = 1.0e99; /* 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 */
+ double cost; /* Cost of using pProbe */
+
+ TRACE(("bestIndex: tbl=%s notReady=%x\n", pSrc->pTab->zName, notReady));
+
+ /* 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->operator & 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;
+ TRACE(("... 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.0;
+ }
+ TRACE(("... 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.
+ */
+ pProbe = pSrc->pTab->pIndex;
+ cost = pProbe ? pProbe->aiRowEst[0] : 1000000.0;
+ TRACE(("... 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 *= 0.333; /* 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 *= 0.333; /* Guess that rowid>EXPR eliminates two-thirds of rows */
+ }
+ TRACE(("... 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, &rev) ){
+ flags |= WHERE_ORDERBY|WHERE_ROWID_RANGE;
+ if( rev ){
+ flags |= WHERE_REVERSE;
+ }
+ }else{
+ cost += cost*estLog(cost);
+ TRACE(("... sorting increases cost to %.9g\n", cost));
+ }
+ }
+ if( cost<lowestCost ){
+ lowestCost = cost;
+ bestFlags = flags;
+ }
+
+ /* Look at each index.
+ */
+ for(; pProbe; pProbe=pProbe->pNext){
+ int i; /* Loop counter */
+ double inMultiplier = 1.0;
+
+ TRACE(("... 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, WO_EQ|WO_IN, pProbe);
+ if( pTerm==0 ) break;
+ flags |= WHERE_COLUMN_EQ;
+ if( pTerm->operator & WO_IN ){
+ Expr *pExpr = pTerm->pExpr;
+ flags |= WHERE_COLUMN_IN;
+ if( pExpr->pSelect!=0 ){
+ inMultiplier *= 100.0;
+ }else if( pExpr->pList!=0 ){
+ inMultiplier *= pExpr->pList->nExpr + 1.0;
+ }
+ }
+ }
+ 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;
+ }
+ TRACE(("...... 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 *= 0.333;
+ }
+ if( findTerm(pWC, iCur, j, notReady, WO_GT|WO_GE, pProbe) ){
+ flags |= WHERE_BTM_LIMIT;
+ cost *= 0.333;
+ }
+ TRACE(("...... 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,pProbe,pSrc->pTab,iCur,pOrderBy,nEq,&rev) ){
+ if( flags==0 ){
+ flags = WHERE_COLUMN_RANGE;
+ }
+ flags |= WHERE_ORDERBY;
+ if( rev ){
+ flags |= WHERE_REVERSE;
+ }
+ }else{
+ cost += cost*estLog(cost);
+ TRACE(("...... 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 *= 0.5;
+ TRACE(("...... idx-only reduces cost to %.9g\n", cost));
+ }
+ }
+
+ /* If this index has achieved the lowest cost so far, then use it.
+ */
+ if( cost < lowestCost ){
+ bestIdx = pProbe;
+ lowestCost = cost;
+ assert( flags!=0 );
+ bestFlags = flags;
+ bestNEq = nEq;
+ }
+ }
+
+ /* Report the best result
+ */
+ *ppIndex = bestIdx;
+ TRACE(("best index is %s, cost=%.9g, flags=%x, nEq=%d\n",
+ bestIdx ? bestIdx->zName : "(none)", lowestCost, bestFlags, bestNEq));
+ *pFlags = bestFlags;
+ *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);
+ }
+ }
+ }
+}
+
+/*
+** Generate code that builds a probe for an index. Details:
+**
+** * Check the top nColumn entries on the stack. If any
+** of those entries are NULL, jump immediately to brk,
+** which is the loop exit, since no index entry will match
+** if any part of the key is NULL.
+**
+** * Construct a probe entry from the top nColumn entries in
+** the stack with affinities appropriate for index pIdx.
+*/
+static void buildIndexProbe(Vdbe *v, int nColumn, int brk, Index *pIdx){
+ sqlite3VdbeAddOp(v, OP_NotNull, -nColumn, sqlite3VdbeCurrentAddr(v)+3);
+ sqlite3VdbeAddOp(v, OP_Pop, nColumn, 0);
+ sqlite3VdbeAddOp(v, OP_Goto, 0, brk);
+ sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, 0);
+ sqlite3IndexAffinityStr(v, pIdx);
+}
+
+
+/*
+** 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 on the top of the stack.
+**
+** 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 void codeEqualityTerm(
+ Parse *pParse, /* The parsing context */
+ WhereTerm *pTerm, /* The term of the WHERE clause to be coded */
+ int brk, /* Jump here to abandon the loop */
+ WhereLevel *pLevel /* When level of the FROM clause we are working on */
+){
+ Expr *pX = pTerm->pExpr;
+ if( pX->op!=TK_IN ){
+ assert( pX->op==TK_EQ );
+ sqlite3ExprCode(pParse, pX->pRight);
+#ifndef SQLITE_OMIT_SUBQUERY
+ }else{
+ int iTab;
+ int *aIn;
+ Vdbe *v = pParse->pVdbe;
+
+ sqlite3CodeSubselect(pParse, pX);
+ iTab = pX->iTable;
+ sqlite3VdbeAddOp(v, OP_Rewind, iTab, brk);
+ VdbeComment((v, "# %.*s", pX->span.n, pX->span.z));
+ pLevel->nIn++;
+ sqlite3ReallocOrFree((void**)&pLevel->aInLoop,
+ sizeof(pLevel->aInLoop[0])*3*pLevel->nIn);
+ aIn = pLevel->aInLoop;
+ if( aIn ){
+ aIn += pLevel->nIn*3 - 3;
+ aIn[0] = OP_Next;
+ aIn[1] = iTab;
+ aIn[2] = sqlite3VdbeAddOp(v, OP_Column, iTab, 0);
+ }else{
+ pLevel->nIn = 0;
+ }
+#endif
+ }
+ disableTerm(pLevel, pTerm);
+}
+
+/*
+** 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 void 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 brk /* Jump here to end the loop */
+){
+ int nEq = pLevel->nEq; /* The number of == or IN constraints to code */
+ int termsInMem = 0; /* If true, store value in mem[] cells */
+ 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 */
+
+ /* 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++;
+ if( pLevel->flags & WHERE_COLUMN_IN ){
+ pParse->nMem += pLevel->nEq;
+ termsInMem = 1;
+ }
+
+ /* Evaluate the equality constraints
+ */
+ for(j=0; j<pIdx->nColumn; j++){
+ int k = pIdx->aiColumn[j];
+ pTerm = findTerm(pWC, iCur, k, notReady, WO_EQ|WO_IN, pIdx);
+ if( pTerm==0 ) break;
+ assert( (pTerm->flags & TERM_CODED)==0 );
+ codeEqualityTerm(pParse, pTerm, brk, pLevel);
+ if( termsInMem ){
+ sqlite3VdbeAddOp(v, OP_MemStore, pLevel->iMem+j+1, 1);
+ }
+ }
+ assert( j==nEq );
+
+ /* Make sure all the constraint values are on the top of the stack
+ */
+ if( termsInMem ){
+ for(j=0; j<nEq; j++){
+ sqlite3VdbeAddOp(v, OP_MemLoad, pLevel->iMem+j+1, 0);
+ }
+ }
+}
+
+#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.
+*/
+char sqlite3_query_plan[BMS*2*40]; /* Text of the join */
+static int nQPlan = 0; /* Next free slow in _query_plan[] */
+
+#endif /* SQLITE_TEST */
+
+
+
+/*
+** 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.
+*/
+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 */
+){
+ 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 */
+
+ /* 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;
+ }
+
+ /* Split the WHERE clause into separate subexpressions where each
+ ** subexpression is separated by an AND operator.
+ */
+ initMaskSet(&maskSet);
+ whereClauseInit(&wc, pParse);
+ whereSplit(&wc, pWhere, TK_AND);
+
+ /* Allocate and initialize the WhereInfo structure that will become the
+ ** return value.
+ */
+ pWInfo = sqliteMalloc( sizeof(WhereInfo) + pTabList->nSrc*sizeof(WhereLevel));
+ if( sqlite3_malloc_failed ){
+ goto whereBeginNoMem;
+ }
+ 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 || sqlite3ExprIsConstant(pWhere)) ){
+ sqlite3ExprIfFalse(pParse, pWhere, pWInfo->iBreak, 1);
+ pWhere = 0;
+ }
+
+ /* 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.
+ */
+ for(i=0; i<pTabList->nSrc; i++){
+ createMask(&maskSet, pTabList->a[i].iCursor);
+ }
+ exprAnalyzeAll(pTabList, &maskSet, &wc);
+ if( sqlite3_malloc_failed ){
+ 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;
+ TRACE(("*** 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 = 1.0e99; /* Cost of the pBest */
+ int bestJ; /* The value of j */
+ Bitmask m; /* Bitmask value for j or bestJ */
+
+ for(j=iFrom, pTabItem=&pTabList->a[j]; j<pTabList->nSrc; j++, pTabItem++){
+ m = getMask(&maskSet, pTabItem->iCursor);
+ if( (m & notReady)==0 ){
+ if( j==iFrom ) iFrom++;
+ continue;
+ }
+ cost = bestIndex(pParse, &wc, pTabItem, notReady,
+ (i==0 && ppOrderBy) ? *ppOrderBy : 0,
+ &pIdx, &flags, &nEq);
+ if( cost<lowestCost ){
+ lowestCost = cost;
+ pBest = pIdx;
+ bestFlags = flags;
+ bestNEq = nEq;
+ bestJ = j;
+ }
+ if( (pTabItem->jointype & (JT_LEFT|JT_CROSS))!=0
+ || (j>0 && (pTabItem[-1].jointype & (JT_LEFT|JT_CROSS))!=0)
+ ){
+ break;
+ }
+ }
+ TRACE(("*** 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;
+ }
+ TRACE(("*** 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;
+ }
+
+ /* Open all tables in the pTabList and any indices selected for
+ ** searching those tables.
+ */
+ sqlite3CodeVerifySchema(pParse, -1); /* Insert the cookie verifier Goto */
+ pLevel = pWInfo->a;
+ for(i=0, pLevel=pWInfo->a; i<pTabList->nSrc; i++, pLevel++){
+ Table *pTab;
+ Index *pIx;
+ int iIdxCur = pLevel->iIdxCur;
+
+#ifndef SQLITE_OMIT_EXPLAIN
+ if( pParse->explain==2 ){
+ char *zMsg;
+ struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
+ zMsg = sqlite3MPrintf("TABLE %s", pItem->zName);
+ if( pItem->zAlias ){
+ zMsg = sqlite3MPrintf("%z AS %s", zMsg, pItem->zAlias);
+ }
+ if( (pIx = pLevel->pIdx)!=0 ){
+ zMsg = sqlite3MPrintf("%z WITH INDEX %s", zMsg, pIx->zName);
+ }
+ sqlite3VdbeOp3(v, OP_Explain, i, pLevel->iFrom, zMsg, P3_DYNAMIC);
+ }
+#endif /* SQLITE_OMIT_EXPLAIN */
+ pTabItem = &pTabList->a[pLevel->iFrom];
+ pTab = pTabItem->pTab;
+ if( pTab->isTransient || pTab->pSelect ) continue;
+ if( (pLevel->flags & WHERE_IDX_ONLY)==0 ){
+ sqlite3OpenTableForReading(v, pTabItem->iCursor, pTab);
+ }
+ pLevel->iTabCur = pTabItem->iCursor;
+ if( (pIx = pLevel->pIdx)!=0 ){
+ sqlite3VdbeAddOp(v, OP_Integer, pIx->iDb, 0);
+ VdbeComment((v, "# %s", pIx->zName));
+ sqlite3VdbeOp3(v, OP_OpenRead, iIdxCur, pIx->tnum,
+ (char*)&pIx->keyInfo, P3_KEYINFO);
+ }
+ if( (pLevel->flags & WHERE_IDX_ONLY)!=0 ){
+ sqlite3VdbeAddOp(v, OP_SetNumColumns, iIdxCur, pIx->nColumn+1);
+ }
+ sqlite3CodeVerifySchema(pParse, pTab->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 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.
+ */
+ brk = pLevel->brk = 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[-1].jointype & JT_LEFT)!=0 ){
+ if( !pParse->nMem ) pParse->nMem++;
+ pLevel->iLeftJoin = pParse->nMem++;
+ sqlite3VdbeAddOp(v, OP_MemInt, 0, pLevel->iLeftJoin);
+ VdbeComment((v, "# init LEFT JOIN no-match flag"));
+ }
+
+ 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.
+ */
+ 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 );
+ codeEqualityTerm(pParse, pTerm, brk, pLevel);
+ sqlite3VdbeAddOp(v, OP_MustBeInt, 1, brk);
+ sqlite3VdbeAddOp(v, OP_NotExists, iCur, brk);
+ 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;
+ pX = pStart->pExpr;
+ assert( pX!=0 );
+ assert( pStart->leftCursor==iCur );
+ sqlite3ExprCode(pParse, pX->pRight);
+ sqlite3VdbeAddOp(v, OP_ForceInt, pX->op==TK_LE || pX->op==TK_GT, brk);
+ sqlite3VdbeAddOp(v, bRev ? OP_MoveLt : OP_MoveGe, iCur, brk);
+ VdbeComment((v, "pk"));
+ disableTerm(pLevel, pStart);
+ }else{
+ sqlite3VdbeAddOp(v, bRev ? OP_Last : OP_Rewind, iCur, brk);
+ }
+ if( pEnd ){
+ Expr *pX;
+ pX = pEnd->pExpr;
+ assert( pX!=0 );
+ assert( pEnd->leftCursor==iCur );
+ sqlite3ExprCode(pParse, pX->pRight);
+ pLevel->iMem = pParse->nMem++;
+ sqlite3VdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
+ 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 ){
+ sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
+ sqlite3VdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
+ sqlite3VdbeAddOp(v, testOp, 'n', brk);
+ }
+ }else if( pLevel->flags & WHERE_COLUMN_RANGE ){
+ /* Case 3: 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 "==" and "IN" operators.
+ **
+ ** 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 start;
+ int nEq = pLevel->nEq;
+ int leFlag=0, geFlag=0;
+ int testOp;
+ int topLimit = (pLevel->flags & WHERE_TOP_LIMIT)!=0;
+ int btmLimit = (pLevel->flags & WHERE_BTM_LIMIT)!=0;
+
+ /* Generate code to evaluate all constraint terms using == or IN
+ ** and level the values of those terms on the stack.
+ */
+ codeAllEqualityTerms(pParse, pLevel, &wc, notReady, brk);
+
+ /* 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<nEq; j++){
+ sqlite3VdbeAddOp(v, OP_Dup, nEq-1, 0);
+ }
+
+ /* 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( topLimit ){
+ Expr *pX;
+ int k = pIdx->aiColumn[j];
+ pTerm = findTerm(&wc, iCur, k, notReady, WO_LT|WO_LE, pIdx);
+ assert( pTerm!=0 );
+ pX = pTerm->pExpr;
+ assert( (pTerm->flags & TERM_CODED)==0 );
+ sqlite3ExprCode(pParse, pX->pRight);
+ leFlag = pX->op==TK_LE;
+ disableTerm(pLevel, pTerm);
+ testOp = OP_IdxGE;
+ }else{
+ testOp = nEq>0 ? OP_IdxGE : OP_Noop;
+ leFlag = 1;
+ }
+ if( testOp!=OP_Noop ){
+ int nCol = nEq + topLimit;
+ pLevel->iMem = pParse->nMem++;
+ buildIndexProbe(v, nCol, brk, pIdx);
+ if( bRev ){
+ int op = leFlag ? OP_MoveLe : OP_MoveLt;
+ sqlite3VdbeAddOp(v, op, iIdxCur, brk);
+ }else{
+ sqlite3VdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
+ }
+ }else if( bRev ){
+ sqlite3VdbeAddOp(v, OP_Last, iIdxCur, 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( btmLimit ){
+ Expr *pX;
+ int k = pIdx->aiColumn[j];
+ pTerm = findTerm(&wc, iCur, k, notReady, WO_GT|WO_GE, pIdx);
+ assert( pTerm!=0 );
+ pX = pTerm->pExpr;
+ assert( (pTerm->flags & TERM_CODED)==0 );
+ sqlite3ExprCode(pParse, pX->pRight);
+ geFlag = pX->op==TK_GE;
+ disableTerm(pLevel, pTerm);
+ }else{
+ geFlag = 1;
+ }
+ if( nEq>0 || btmLimit ){
+ int nCol = nEq + btmLimit;
+ buildIndexProbe(v, nCol, brk, pIdx);
+ if( bRev ){
+ pLevel->iMem = pParse->nMem++;
+ sqlite3VdbeAddOp(v, OP_MemStore, pLevel->iMem, 1);
+ testOp = OP_IdxLT;
+ }else{
+ int op = geFlag ? OP_MoveGe : OP_MoveGt;
+ sqlite3VdbeAddOp(v, op, iIdxCur, brk);
+ }
+ }else if( bRev ){
+ testOp = OP_Noop;
+ }else{
+ sqlite3VdbeAddOp(v, OP_Rewind, iIdxCur, 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 = sqlite3VdbeCurrentAddr(v);
+ if( testOp!=OP_Noop ){
+ sqlite3VdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
+ sqlite3VdbeAddOp(v, testOp, iIdxCur, brk);
+ if( (leFlag && !bRev) || (!geFlag && bRev) ){
+ sqlite3VdbeChangeP3(v, -1, "+", P3_STATIC);
+ }
+ }
+ sqlite3VdbeAddOp(v, OP_RowKey, iIdxCur, 0);
+ sqlite3VdbeAddOp(v, OP_IdxIsNull, nEq + topLimit, cont);
+ if( !omitTable ){
+ sqlite3VdbeAddOp(v, OP_IdxRowid, iIdxCur, 0);
+ sqlite3VdbeAddOp(v, OP_MoveGe, iCur, 0);
+ }
+
+ /* Record the instruction used to terminate the loop.
+ */
+ pLevel->op = bRev ? OP_Prev : OP_Next;
+ pLevel->p1 = iIdxCur;
+ pLevel->p2 = start;
+ }else if( pLevel->flags & WHERE_COLUMN_EQ ){
+ /* Case 4: There is an index and all terms of the WHERE clause that
+ ** refer to the index using the "==" or "IN" operators.
+ */
+ int start;
+ int nEq = pLevel->nEq;
+
+ /* Generate code to evaluate all constraint terms using == or IN
+ ** and leave the values of those terms on the stack.
+ */
+ codeAllEqualityTerms(pParse, pLevel, &wc, notReady, brk);
+
+ /* Generate a single key that will be used to both start and terminate
+ ** the search
+ */
+ buildIndexProbe(v, nEq, brk, pIdx);
+ sqlite3VdbeAddOp(v, OP_MemStore, pLevel->iMem, 0);
+
+ /* Generate code (1) to move to the first matching element of the table.
+ ** Then generate code (2) that jumps to "brk" after the cursor is past
+ ** the last matching element of the table. The code (1) is executed
+ ** once to initialize the search, the code (2) is executed before each
+ ** iteration of the scan to see if the scan has finished. */
+ if( bRev ){
+ /* Scan in reverse order */
+ sqlite3VdbeAddOp(v, OP_MoveLe, iIdxCur, brk);
+ start = sqlite3VdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
+ sqlite3VdbeAddOp(v, OP_IdxLT, iIdxCur, brk);
+ pLevel->op = OP_Prev;
+ }else{
+ /* Scan in the forward order */
+ sqlite3VdbeAddOp(v, OP_MoveGe, iIdxCur, brk);
+ start = sqlite3VdbeAddOp(v, OP_MemLoad, pLevel->iMem, 0);
+ sqlite3VdbeOp3(v, OP_IdxGE, iIdxCur, brk, "+", P3_STATIC);
+ pLevel->op = OP_Next;
+ }
+ sqlite3VdbeAddOp(v, OP_RowKey, iIdxCur, 0);
+ sqlite3VdbeAddOp(v, OP_IdxIsNull, nEq, cont);
+ if( !omitTable ){
+ sqlite3VdbeAddOp(v, OP_IdxRowid, iIdxCur, 0);
+ sqlite3VdbeAddOp(v, OP_MoveGe, iCur, 0);
+ }
+ pLevel->p1 = iIdxCur;
+ pLevel->p2 = start;
+ }else{
+ /* Case 5: 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 + sqlite3VdbeAddOp(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;
+ 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, 1);
+ 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);
+ sqlite3VdbeAddOp(v, OP_MemInt, 1, pLevel->iLeftJoin);
+ VdbeComment((v, "# record LEFT JOIN hit"));
+ for(pTerm=wc.a, j=0; j<wc.nTerm; j++, pTerm++){
+ if( pTerm->flags & (TERM_VIRTUAL|TERM_CODED) ) continue;
+ if( (pTerm->prereqAll & notReady)!=0 ) continue;
+ assert( pTerm->pExpr );
+ sqlite3ExprIfFalse(pParse, pTerm->pExpr, cont, 1);
+ 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 ){
+ strcpy(&sqlite3_query_plan[nQPlan], "{}");
+ nQPlan += 2;
+ }else{
+ strcpy(&sqlite3_query_plan[nQPlan], z);
+ nQPlan += n;
+ }
+ sqlite3_query_plan[nQPlan++] = ' ';
+ }
+ if( pLevel->flags & (WHERE_ROWID_EQ|WHERE_ROWID_RANGE) ){
+ strcpy(&sqlite3_query_plan[nQPlan], "* ");
+ nQPlan += 2;
+ }else if( pLevel->pIdx==0 ){
+ strcpy(&sqlite3_query_plan[nQPlan], "{} ");
+ nQPlan += 3;
+ }else{
+ n = strlen(pLevel->pIdx->zName);
+ if( n+nQPlan < sizeof(sqlite3_query_plan)-2 ){
+ strcpy(&sqlite3_query_plan[nQPlan], pLevel->pIdx->zName);
+ 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);
+ sqliteFree(pWInfo);
+ return 0;
+}
+
+/*
+** Generate the end of the WHERE loop. See comments on
+** sqlite3WhereBegin() for additional information.
+*/
+void sqlite3WhereEnd(WhereInfo *pWInfo){
+ Vdbe *v = pWInfo->pParse->pVdbe;
+ int i;
+ WhereLevel *pLevel;
+ SrcList *pTabList = pWInfo->pTabList;
+
+ /* Generate loop termination code.
+ */
+ for(i=pTabList->nSrc-1; i>=0; i--){
+ pLevel = &pWInfo->a[i];
+ sqlite3VdbeResolveLabel(v, pLevel->cont);
+ if( pLevel->op!=OP_Noop ){
+ sqlite3VdbeAddOp(v, pLevel->op, pLevel->p1, pLevel->p2);
+ }
+ sqlite3VdbeResolveLabel(v, pLevel->brk);
+ if( pLevel->nIn ){
+ int *a;
+ int j;
+ for(j=pLevel->nIn, a=&pLevel->aInLoop[j*3-3]; j>0; j--, a-=3){
+ sqlite3VdbeAddOp(v, a[0], a[1], a[2]);
+ }
+ sqliteFree(pLevel->aInLoop);
+ }
+ if( pLevel->iLeftJoin ){
+ int addr;
+ addr = sqlite3VdbeAddOp(v, OP_IfMemPos, pLevel->iLeftJoin, 0);
+ sqlite3VdbeAddOp(v, OP_NullRow, pTabList->a[i].iCursor, 0);
+ if( pLevel->iIdxCur>=0 ){
+ sqlite3VdbeAddOp(v, OP_NullRow, pLevel->iIdxCur, 0);
+ }
+ sqlite3VdbeAddOp(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->isTransient || pTab->pSelect ) continue;
+ if( (pLevel->flags & WHERE_IDX_ONLY)==0 ){
+ sqlite3VdbeAddOp(v, OP_Close, pTabItem->iCursor, 0);
+ }
+ if( pLevel->pIdx!=0 ){
+ sqlite3VdbeAddOp(v, OP_Close, pLevel->iIdxCur, 0);
+ }
+
+ /* Make cursor substitutions for cases where we want to use
+ ** just the index and never reference the table.
+ **
+ ** 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->flags & WHERE_IDX_ONLY ){
+ int i, j, last;
+ VdbeOp *pOp;
+ Index *pIdx = pLevel->pIdx;
+
+ assert( pIdx!=0 );
+ pOp = sqlite3VdbeGetOp(v, pWInfo->iTop);
+ last = sqlite3VdbeCurrentAddr(v);
+ for(i=pWInfo->iTop; i<last; i++, pOp++){
+ if( pOp->p1!=pLevel->iTabCur ) continue;
+ if( pOp->opcode==OP_Column ){
+ pOp->p1 = pLevel->iIdxCur;
+ for(j=0; j<pIdx->nColumn; j++){
+ if( pOp->p2==pIdx->aiColumn[j] ){
+ pOp->p2 = j;
+ break;
+ }
+ }
+ }else if( pOp->opcode==OP_Rowid ){
+ pOp->p1 = pLevel->iIdxCur;
+ pOp->opcode = OP_IdxRowid;
+ }else if( pOp->opcode==OP_NullRow ){
+ pOp->opcode = OP_Noop;
+ }
+ }
+ }
+ }
+
+ /* Final cleanup
+ */
+ sqliteFree(pWInfo);
+ return;
+}
diff --git a/kexi/3rdparty/kexisql3/version b/kexi/3rdparty/kexisql3/version
new file mode 100644
index 000000000..f092941a7
--- /dev/null
+++ b/kexi/3rdparty/kexisql3/version
@@ -0,0 +1 @@
+3.2.8
diff --git a/kexi/3rdparty/kolibs/KoPageLayoutColumns.cpp b/kexi/3rdparty/kolibs/KoPageLayoutColumns.cpp
new file mode 100644
index 000000000..db5e3e25e
--- /dev/null
+++ b/kexi/3rdparty/kolibs/KoPageLayoutColumns.cpp
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+ * Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
+ * Copyright (C) 2005 Thomas Zander <zander@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; version 2.
+ *
+ * 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <KoPageLayoutColumns.h>
+#include <KoPageLayoutDia.h>
+#include <KoUnit.h>
+#include <KoUnitWidgets.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+
+KoPageLayoutColumns::KoPageLayoutColumns(QWidget *parent, const KoColumns& columns, KoUnit::Unit unit, const KoPageLayout& layout)
+ : KoPageLayoutColumnsBase(parent) {
+ m_columns = columns;
+ QHBoxLayout *lay = new QHBoxLayout(previewPane);
+ m_preview = new KoPagePreview( previewPane, "Preview", layout );
+ lay->addWidget(m_preview);
+ lay = new QHBoxLayout(columnSpacingPane);
+ m_spacing = new KoUnitDoubleSpinBox( columnSpacingPane );
+ m_spacing->setValue( m_columns.ptColumnSpacing );
+ m_spacing->setUnit( unit );
+ double dStep = KoUnit::fromUserValue( 0.2, unit );
+ m_spacing->setMinMaxStep( 0, layout.ptWidth/2, dStep );
+ lay->addWidget(m_spacing);
+ labelSpacing->setBuddy( m_spacing );
+ nColumns->setValue( m_columns.columns );
+ m_preview->setPageColumns( m_columns );
+
+ connect( nColumns, SIGNAL( valueChanged( int ) ), this, SLOT( nColChanged( int ) ) );
+ connect( m_spacing, SIGNAL( valueChangedPt(double) ), this, SLOT( nSpaceChanged( double ) ) );
+}
+
+void KoPageLayoutColumns::setEnableColumns(bool on) {
+ nColumns->setEnabled(on);
+ m_spacing->setEnabled(on);
+ nColChanged(on ? nColumns->value(): 1 );
+}
+
+void KoPageLayoutColumns::nColChanged( int columns ) {
+ m_columns.columns = columns;
+ m_preview->setPageColumns( m_columns );
+ emit propertyChange(m_columns);
+}
+
+void KoPageLayoutColumns::nSpaceChanged( double spacing ) {
+ m_columns.ptColumnSpacing = spacing;
+ emit propertyChange(m_columns);
+}
+
+void KoPageLayoutColumns::setLayout(KoPageLayout &layout) {
+ m_preview->setPageLayout( layout );
+}
+
+#include <KoPageLayoutColumns.moc>
diff --git a/kexi/3rdparty/kolibs/KoPageLayoutColumns.h b/kexi/3rdparty/kolibs/KoPageLayoutColumns.h
new file mode 100644
index 000000000..be0a4a493
--- /dev/null
+++ b/kexi/3rdparty/kolibs/KoPageLayoutColumns.h
@@ -0,0 +1,75 @@
+/* This file is part of the KDE project
+ * Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
+ * Copyright (C) 2005 Thomas Zander <zander@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; version 2.
+ *
+ * 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+// Description: Page Layout Dialog (sources)
+
+#ifndef kopagelayoutcolumns_h
+#define kopagelayoutcolumns_h
+
+#include <KoUnit.h>
+#include <KoPageLayout.h>
+#include <KoPageLayoutColumnsBase.h>
+
+class QWidget;
+class KoUnitDoubleSpinBox;
+class KoPagePreview;
+
+/**
+ * This class is a widget that shows the KoColumns data structure and allows the user to change it.
+ */
+class KoPageLayoutColumns : public KoPageLayoutColumnsBase {
+ Q_OBJECT
+
+public:
+ /**
+ * Contructor
+ * @param parent the parent widget
+ * @param columns the KoColumns data structure that this dialog should be initialzed with
+ * @param unit the unit-type (mm/cm/inch) that the dialog should show
+ * @param layout the page layout that the preview should be initialzed with.
+ */
+ KoPageLayoutColumns(QWidget *parent, const KoColumns& columns, KoUnit::Unit unit, const KoPageLayout& layout);
+
+ /**
+ * Update the page preview widget with the param layout.
+ * @param layout the new layout
+ */
+ void setLayout(KoPageLayout &layout);
+public slots:
+
+ /**
+ * Enable the user to edit the columns
+ * @param on if true enable the user to change the columns count
+ */
+ void setEnableColumns(bool on);
+
+signals:
+ void propertyChange(KoColumns &columns);
+
+protected:
+ KoColumns m_columns;
+ KoPagePreview *m_preview;
+ KoUnitDoubleSpinBox *m_spacing;
+
+private slots:
+ void nColChanged( int );
+ void nSpaceChanged( double );
+};
+
+#endif
diff --git a/kexi/3rdparty/kolibs/KoPageLayoutColumnsBase.ui b/kexi/3rdparty/kolibs/KoPageLayoutColumnsBase.ui
new file mode 100644
index 000000000..1ffe032d7
--- /dev/null
+++ b/kexi/3rdparty/kolibs/KoPageLayoutColumnsBase.ui
@@ -0,0 +1,99 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KoPageLayoutColumnsBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KoPageLayoutColumnsBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>361</width>
+ <height>169</height>
+ </rect>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QFrame" row="1" column="1">
+ <property name="name">
+ <cstring>columnSpacingPane</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Columns:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>nColumns</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>labelSpacing</cstring>
+ </property>
+ <property name="text">
+ <string>Column spacing:</string>
+ </property>
+ </widget>
+ <spacer row="2" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QFrame" row="0" column="2" rowspan="3" colspan="1">
+ <property name="name">
+ <cstring>previewPane</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="0" column="1">
+ <property name="name">
+ <cstring>nColumns</cstring>
+ </property>
+ <property name="maxValue">
+ <number>16</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ </widget>
+ </grid>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kexi/3rdparty/kolibs/KoPageLayoutHeader.cpp b/kexi/3rdparty/kolibs/KoPageLayoutHeader.cpp
new file mode 100644
index 000000000..ad5e018c3
--- /dev/null
+++ b/kexi/3rdparty/kolibs/KoPageLayoutHeader.cpp
@@ -0,0 +1,73 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2005 Thomas Zander <zander@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; version 2.
+ *
+ * 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <KoPageLayoutHeader.h>
+#include <KoPageLayoutHeader.moc>
+#include <KoUnitWidgets.h>
+
+#include <qlayout.h>
+#include <qcheckbox.h>
+
+KoPageLayoutHeader::KoPageLayoutHeader(QWidget *parent, KoUnit::Unit unit, const KoKWHeaderFooter &kwhf)
+ : KoPageLayoutHeaderBase(parent) {
+ m_headerFooters = kwhf;
+ QHBoxLayout *lay = new QHBoxLayout(headerSpacingPane);
+ m_headerSpacing = new KoUnitDoubleSpinBox( headerSpacingPane, 0.0, 999.0, 0.5, kwhf.ptHeaderBodySpacing, unit );
+ lay->addWidget(m_headerSpacing);
+
+ lay = new QHBoxLayout(footerSpacingPane);
+ m_footerSpacing = new KoUnitDoubleSpinBox( footerSpacingPane, 0.0, 999.0, 0.5, kwhf.ptFooterBodySpacing, unit );
+ lay->addWidget(m_footerSpacing);
+
+ lay = new QHBoxLayout(footnotePane);
+ m_footnoteSpacing = new KoUnitDoubleSpinBox( footnotePane, 0.0, 999.0, 0.5, kwhf.ptFootNoteBodySpacing, unit );
+ lay->addWidget(m_footnoteSpacing);
+
+ if ( kwhf.header == HF_FIRST_DIFF || kwhf.header == HF_FIRST_EO_DIFF )
+ rhFirst->setChecked( true );
+ if ( kwhf.header == HF_EO_DIFF || kwhf.header == HF_FIRST_EO_DIFF )
+ rhEvenOdd->setChecked( true );
+ if ( kwhf.footer == HF_FIRST_DIFF || kwhf.footer == HF_FIRST_EO_DIFF )
+ rfFirst->setChecked( true );
+ if ( kwhf.footer == HF_EO_DIFF || kwhf.footer == HF_FIRST_EO_DIFF )
+ rfEvenOdd->setChecked( true );
+}
+
+const KoKWHeaderFooter& KoPageLayoutHeader::headerFooter() {
+ if ( rhFirst->isChecked() && rhEvenOdd->isChecked() )
+ m_headerFooters.header = HF_FIRST_EO_DIFF;
+ else if ( rhFirst->isChecked() )
+ m_headerFooters.header = HF_FIRST_DIFF;
+ else if ( rhEvenOdd->isChecked() )
+ m_headerFooters.header = HF_EO_DIFF;
+ else
+ m_headerFooters.header = HF_SAME;
+
+ m_headerFooters.ptHeaderBodySpacing = m_headerSpacing->value();
+ m_headerFooters.ptFooterBodySpacing = m_footerSpacing->value();
+ m_headerFooters.ptFootNoteBodySpacing = m_footnoteSpacing->value();
+ if ( rfFirst->isChecked() && rfEvenOdd->isChecked() )
+ m_headerFooters.footer = HF_FIRST_EO_DIFF;
+ else if ( rfFirst->isChecked() )
+ m_headerFooters.footer = HF_FIRST_DIFF;
+ else if ( rfEvenOdd->isChecked() )
+ m_headerFooters.footer = HF_EO_DIFF;
+ else
+ m_headerFooters.footer = HF_SAME;
+ return m_headerFooters;
+}
diff --git a/kexi/3rdparty/kolibs/KoPageLayoutHeader.h b/kexi/3rdparty/kolibs/KoPageLayoutHeader.h
new file mode 100644
index 000000000..65449dc0c
--- /dev/null
+++ b/kexi/3rdparty/kolibs/KoPageLayoutHeader.h
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2005 Thomas Zander <zander@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; version 2.
+ *
+ * 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef kopagelayoutheader_h
+#define kopagelayoutheader_h
+
+#include <KoUnit.h>
+#include <KoPageLayout.h>
+#include <KoPageLayoutHeaderBase.h>
+
+class QWidget;
+class KoUnitDoubleSpinBox;
+class KoPagePreview;
+
+class KoPageLayoutHeader : public KoPageLayoutHeaderBase {
+ Q_OBJECT
+
+public:
+ KoPageLayoutHeader(QWidget *parent, KoUnit::Unit unit, const KoKWHeaderFooter &kwhf);
+ const KoKWHeaderFooter& headerFooter();
+
+private:
+ KoUnitDoubleSpinBox *m_headerSpacing, *m_footerSpacing, *m_footnoteSpacing;
+
+ KoKWHeaderFooter m_headerFooters;
+};
+
+#endif
+
diff --git a/kexi/3rdparty/kolibs/KoPageLayoutHeaderBase.ui b/kexi/3rdparty/kolibs/KoPageLayoutHeaderBase.ui
new file mode 100644
index 000000000..d46ca98ec
--- /dev/null
+++ b/kexi/3rdparty/kolibs/KoPageLayoutHeaderBase.ui
@@ -0,0 +1,221 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KoPageLayoutHeaderBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KoPageLayoutHeaderBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>445</width>
+ <height>437</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>groupBox1</cstring>
+ </property>
+ <property name="title">
+ <string>Header</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>rhFirst</cstring>
+ </property>
+ <property name="text">
+ <string>Different header for the first page</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>rhEvenOdd</cstring>
+ </property>
+ <property name="text">
+ <string>Different header for even and odd pages</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout1</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>21</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel3</cstring>
+ </property>
+ <property name="text">
+ <string>Spacing between header and body:</string>
+ </property>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>headerSpacingPane</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>groupBox2</cstring>
+ </property>
+ <property name="title">
+ <string>Footer</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>rfFirst</cstring>
+ </property>
+ <property name="text">
+ <string>Different footer for the first page</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>rfEvenOdd</cstring>
+ </property>
+ <property name="text">
+ <string>Different footer for even and odd pages</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout2</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer3</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>41</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel4</cstring>
+ </property>
+ <property name="text">
+ <string>Spacing between footer and body:</string>
+ </property>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>footerSpacingPane</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>groupBox3</cstring>
+ </property>
+ <property name="title">
+ <string>Footnote/Endnote</string>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer4</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel5</cstring>
+ </property>
+ <property name="text">
+ <string>Spacing between footnote and body:</string>
+ </property>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>footnotePane</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kexi/3rdparty/kolibs/KoPageLayoutSize.cpp b/kexi/3rdparty/kolibs/KoPageLayoutSize.cpp
new file mode 100644
index 000000000..cb22fe9c1
--- /dev/null
+++ b/kexi/3rdparty/kolibs/KoPageLayoutSize.cpp
@@ -0,0 +1,343 @@
+/* This file is part of the KDE project
+ * Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
+ * Copyright (C) 2005 Thomas Zander <zander@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; version 2.
+ *
+ * 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <KoPageLayoutDia.h>
+#include <KoPageLayoutSize.h>
+#include <KoUnit.h>
+#include <KoUnitWidgets.h>
+
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qradiobutton.h>
+#include <qhbox.h>
+#include <qvgroupbox.h>
+#include <qhbuttongroup.h>
+
+KoPageLayoutSize::KoPageLayoutSize(QWidget *parent, const KoPageLayout& layout, KoUnit::Unit unit,const KoColumns& columns, bool unitChooser, bool enableBorders)
+ : QWidget(parent) {
+ m_layout = layout;
+ m_unit = unit;
+
+ QGridLayout *grid1 = new QGridLayout( this, 5, 2, 0, KDialog::spacingHint() );
+ if ( unitChooser ) {
+ // ------------- unit _______________
+ QWidget* unitFrame = new QWidget( this );
+ grid1->addWidget( unitFrame, 0, 0, Qt::AlignLeft );
+ QBoxLayout* unitLayout = new QHBoxLayout( unitFrame, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // label unit
+ QLabel *lpgUnit = new QLabel( i18n( "Unit:" ), unitFrame );
+ unitLayout->addWidget( lpgUnit, 0, Qt::AlignRight | Qt::AlignVCenter );
+
+ // combo unit
+ cpgUnit = new QComboBox( false, unitFrame, "cpgUnit" );
+ lpgUnit->setBuddy( cpgUnit );
+ cpgUnit->insertStringList( KoUnit::listOfUnitName() );
+ cpgUnit->setCurrentItem( unit );
+ unitLayout->addWidget( cpgUnit, 0, Qt::AlignLeft | Qt::AlignVCenter );
+ connect( cpgUnit, SIGNAL( activated( int ) ), this, SLOT( setUnitInt( int ) ) );
+ }
+ else {
+ QString str=KoUnit::unitDescription(unit);
+
+ QLabel *lpgUnit = new QLabel( i18n("All values are given in %1.").arg(str), this );
+ grid1->addWidget( lpgUnit, 0, 0, Qt::AlignLeft );
+ }
+
+ // -------------- page size -----------------
+ QVGroupBox *formatFrame = new QVGroupBox( i18n( "Page Size" ), this );
+ grid1->addWidget( formatFrame, 1, 0 );
+
+ QHBox *formatPageSize = new QHBox( formatFrame );
+ formatPageSize->setSpacing( KDialog::spacingHint() );
+
+ // label page size
+ QLabel *lpgFormat = new QLabel( i18n( "&Size:" ), formatPageSize );
+
+ // combo size
+ cpgFormat = new QComboBox( false, formatPageSize, "cpgFormat" );
+ cpgFormat->insertStringList( KoPageFormat::allFormats() );
+ lpgFormat->setBuddy( cpgFormat );
+ connect( cpgFormat, SIGNAL( activated( int ) ), this, SLOT( formatChanged( int ) ) );
+
+ // spacer
+ formatPageSize->setStretchFactor( new QWidget( formatPageSize ), 10 );
+
+ QHBox *formatCustomSize = new QHBox( formatFrame );
+ formatCustomSize->setSpacing( KDialog::spacingHint() );
+
+ // label width
+ QLabel *lpgWidth = new QLabel( i18n( "&Width:" ), formatCustomSize );
+
+ // linedit width
+ epgWidth = new KoUnitDoubleSpinBox( formatCustomSize, "Width" );
+ lpgWidth->setBuddy( epgWidth );
+ if ( m_layout.format != PG_CUSTOM )
+ epgWidth->setEnabled( false );
+ connect( epgWidth, SIGNAL( valueChangedPt(double) ), this, SLOT( widthChanged(double) ) );
+
+ // label height
+ QLabel *lpgHeight = new QLabel( i18n( "&Height:" ), formatCustomSize );
+
+ // linedit height
+ epgHeight = new KoUnitDoubleSpinBox( formatCustomSize, "Height" );
+ lpgHeight->setBuddy( epgHeight );
+ if ( m_layout.format != PG_CUSTOM )
+ epgHeight->setEnabled( false );
+ connect( epgHeight, SIGNAL( valueChangedPt(double ) ), this, SLOT( heightChanged(double) ) );
+
+ // --------------- orientation ---------------
+ m_orientGroup = new QHButtonGroup( i18n( "Orientation" ), this );
+ m_orientGroup->setInsideSpacing( KDialog::spacingHint() );
+ grid1->addWidget( m_orientGroup, 2, 0 );
+
+ QLabel* lbPortrait = new QLabel( m_orientGroup );
+ lbPortrait->setPixmap( QPixmap( UserIcon( "koPortrait" ) ) );
+ lbPortrait->setMaximumWidth( lbPortrait->pixmap()->width() );
+ new QRadioButton( i18n("&Portrait"), m_orientGroup );
+
+ QLabel* lbLandscape = new QLabel( m_orientGroup );
+ lbLandscape->setPixmap( QPixmap( UserIcon( "koLandscape" ) ) );
+ lbLandscape->setMaximumWidth( lbLandscape->pixmap()->width() );
+ new QRadioButton( i18n("La&ndscape"), m_orientGroup );
+
+ connect( m_orientGroup, SIGNAL (clicked (int)), this, SLOT( orientationChanged(int) ));
+
+ // --------------- page margins ---------------
+ QVGroupBox *marginsFrame = new QVGroupBox( i18n( "Margins" ), this );
+ marginsFrame->setColumnLayout( 0, Qt::Vertical );
+ marginsFrame->setMargin( KDialog::marginHint() );
+ grid1->addWidget( marginsFrame, 3, 0 );
+
+ QGridLayout *marginsLayout = new QGridLayout( marginsFrame->layout(), 3, 3,
+ KDialog::spacingHint() );
+
+ // left margin
+ ebrLeft = new KoUnitDoubleSpinBox( marginsFrame, "Left" );
+ marginsLayout->addWidget( ebrLeft, 1, 0 );
+ connect( ebrLeft, SIGNAL( valueChangedPt( double ) ), this, SLOT( leftChanged( double ) ) );
+
+ // right margin
+ ebrRight = new KoUnitDoubleSpinBox( marginsFrame, "Right" );
+ marginsLayout->addWidget( ebrRight, 1, 2 );
+ connect( ebrRight, SIGNAL( valueChangedPt( double ) ), this, SLOT( rightChanged( double ) ) );
+
+ // top margin
+ ebrTop = new KoUnitDoubleSpinBox( marginsFrame, "Top" );
+ marginsLayout->addWidget( ebrTop, 0, 1 , Qt::AlignCenter );
+ connect( ebrTop, SIGNAL( valueChangedPt( double ) ), this, SLOT( topChanged( double ) ) );
+
+ // bottom margin
+ ebrBottom = new KoUnitDoubleSpinBox( marginsFrame, "Bottom" );
+ marginsLayout->addWidget( ebrBottom, 2, 1, Qt::AlignCenter );
+ connect( ebrBottom, SIGNAL( valueChangedPt( double ) ), this, SLOT( bottomChanged( double ) ) );
+
+ // ------------- preview -----------
+ pgPreview = new KoPagePreview( this, "Preview", m_layout );
+ grid1->addMultiCellWidget( pgPreview, 1, 3, 1, 1 );
+
+ // ------------- spacers -----------
+ QWidget* spacer1 = new QWidget( this );
+ QWidget* spacer2 = new QWidget( this );
+ spacer1->setSizePolicy( QSizePolicy( QSizePolicy::Expanding,
+ QSizePolicy::Expanding ) );
+ spacer2->setSizePolicy( QSizePolicy( QSizePolicy::Expanding,
+ QSizePolicy::Expanding ) );
+ grid1->addWidget( spacer1, 4, 0 );
+ grid1->addWidget( spacer2, 4, 1 );
+
+ setValues();
+ updatePreview();
+ pgPreview->setPageColumns( columns );
+ setEnableBorders(enableBorders);
+}
+
+void KoPageLayoutSize::setEnableBorders(bool on) {
+ ebrLeft->setEnabled( on );
+ ebrRight->setEnabled( on );
+ ebrTop->setEnabled( on );
+ ebrBottom->setEnabled( on );
+
+ // update m_layout
+ m_layout.ptLeft = on?ebrLeft->value():0;
+ m_layout.ptRight = on?ebrRight->value():0;
+ m_layout.ptTop = on?ebrTop->value():0;
+ m_layout.ptBottom = on?ebrBottom->value():0;
+
+ // use updated m_layout
+ updatePreview();
+ emit propertyChange(m_layout);
+}
+
+void KoPageLayoutSize::updatePreview() {
+ pgPreview->setPageLayout( m_layout );
+}
+
+void KoPageLayoutSize::setValues() {
+ // page format
+ cpgFormat->setCurrentItem( m_layout.format );
+ // orientation
+ m_orientGroup->setButton( m_layout.orientation == PG_PORTRAIT ? 0: 1 );
+
+ setUnit( m_unit );
+ pgPreview->setPageLayout( m_layout );
+}
+
+void KoPageLayoutSize::setUnit( KoUnit::Unit unit ) {
+ m_unit = unit;
+
+ //setUnit always befor changeValue
+ epgWidth->setUnit( m_unit );
+ epgWidth->setMinMaxStep( 0, KoUnit::fromUserValue( 9999, m_unit ), KoUnit::fromUserValue( 0.01, m_unit ) );
+ epgWidth->changeValue( m_layout.ptWidth );
+
+ epgHeight->setUnit( m_unit );
+ epgHeight->setMinMaxStep( 0, KoUnit::fromUserValue( 9999, m_unit ), KoUnit::fromUserValue( 0.01, m_unit ) );
+ epgHeight->changeValue( m_layout.ptHeight );
+
+ double dStep = KoUnit::fromUserValue( 0.2, m_unit );
+
+ ebrLeft->setUnit( m_unit );
+ ebrLeft->changeValue( m_layout.ptLeft );
+ ebrLeft->setMinMaxStep( 0, m_layout.ptWidth, dStep );
+
+ ebrRight->setUnit( m_unit );
+ ebrRight->changeValue( m_layout.ptRight );
+ ebrRight->setMinMaxStep( 0, m_layout.ptWidth, dStep );
+
+ ebrTop->setUnit( m_unit );
+ ebrTop->changeValue( m_layout.ptTop );
+ ebrTop->setMinMaxStep( 0, m_layout.ptHeight, dStep );
+
+ ebrBottom->setUnit( m_unit );
+ ebrBottom->changeValue( m_layout.ptBottom );
+ ebrBottom->setMinMaxStep( 0, m_layout.ptHeight, dStep );
+ emit propertyChange(m_layout);
+}
+
+void KoPageLayoutSize::setUnitInt( int unit ) {
+ setUnit((KoUnit::Unit)unit);
+}
+
+void KoPageLayoutSize::formatChanged( int format ) {
+ if ( ( KoFormat )format == m_layout.format )
+ return;
+ m_layout.format = ( KoFormat )format;
+ bool enable = (KoFormat) format == PG_CUSTOM;
+ epgWidth->setEnabled( enable );
+ epgHeight->setEnabled( enable );
+
+ if ( m_layout.format != PG_CUSTOM ) {
+ m_layout.ptWidth = MM_TO_POINT( KoPageFormat::width(
+ m_layout.format, m_layout.orientation ) );
+ m_layout.ptHeight = MM_TO_POINT( KoPageFormat::height(
+ m_layout.format, m_layout.orientation ) );
+ }
+
+ epgWidth->changeValue( m_layout.ptWidth );
+ epgHeight->changeValue( m_layout.ptHeight );
+
+ updatePreview( );
+ emit propertyChange(m_layout);
+}
+
+void KoPageLayoutSize::orientationChanged(int which) {
+ KoOrientation oldOrientation = m_layout.orientation;
+ m_layout.orientation = which == 0 ? PG_PORTRAIT : PG_LANDSCAPE;
+
+ // without this check, width & height would be swapped around (below)
+ // even though the orientation has not changed
+ if (m_layout.orientation == oldOrientation) return;
+
+ m_layout.ptWidth = epgWidth->value();
+ m_layout.ptHeight = epgHeight->value();
+ m_layout.ptLeft = ebrLeft->value();
+ m_layout.ptRight = ebrRight->value();
+ m_layout.ptTop = ebrTop->value();
+ m_layout.ptBottom = ebrBottom->value();
+
+ // swap dimension and adjust margins
+ qSwap( m_layout.ptWidth, m_layout.ptHeight );
+ double tmp = m_layout.ptTop;
+ m_layout.ptTop = m_layout.ptRight;
+ m_layout.ptRight = m_layout.ptBottom;
+ m_layout.ptBottom = m_layout.ptLeft;
+ m_layout.ptLeft = tmp;
+
+ setValues();
+ updatePreview( );
+ emit propertyChange(m_layout);
+}
+
+void KoPageLayoutSize::widthChanged(double width) {
+ m_layout.ptWidth = width;
+ updatePreview();
+ emit propertyChange(m_layout);
+}
+void KoPageLayoutSize::heightChanged(double height) {
+ m_layout.ptHeight = height;
+ updatePreview( );
+ emit propertyChange(m_layout);
+}
+void KoPageLayoutSize::leftChanged( double left ) {
+ m_layout.ptLeft = left;
+ updatePreview();
+ emit propertyChange(m_layout);
+}
+void KoPageLayoutSize::rightChanged(double right) {
+ m_layout.ptRight = right;
+ updatePreview();
+ emit propertyChange(m_layout);
+}
+void KoPageLayoutSize::topChanged(double top) {
+ m_layout.ptTop = top;
+ updatePreview();
+ emit propertyChange(m_layout);
+}
+void KoPageLayoutSize::bottomChanged(double bottom) {
+ m_layout.ptBottom = bottom;
+ updatePreview();
+ emit propertyChange(m_layout);
+}
+
+bool KoPageLayoutSize::queryClose() {
+ if ( m_layout.ptLeft + m_layout.ptRight > m_layout.ptWidth ) {
+ KMessageBox::error( this,
+ i18n("The page width is smaller than the left and right margins."),
+ i18n("Page Layout Problem") );
+ return false;
+ }
+ if ( m_layout.ptTop + m_layout.ptBottom > m_layout.ptHeight ) {
+ KMessageBox::error( this,
+ i18n("The page height is smaller than the top and bottom margins."),
+ i18n("Page Layout Problem") );
+ return false;
+ }
+ return true;
+}
+
+void KoPageLayoutSize::setColumns(KoColumns &columns) {
+ pgPreview->setPageColumns(columns);
+}
+
+#include <KoPageLayoutSize.moc>
diff --git a/kexi/3rdparty/kolibs/KoPageLayoutSize.h b/kexi/3rdparty/kolibs/KoPageLayoutSize.h
new file mode 100644
index 000000000..e32f96701
--- /dev/null
+++ b/kexi/3rdparty/kolibs/KoPageLayoutSize.h
@@ -0,0 +1,115 @@
+/* This file is part of the KDE project
+ * Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
+ * Copyright (C) 2005 Thomas Zander <zander@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; version 2.
+ *
+ * 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef kopagelayoutsize_h
+#define kopagelayoutsize_h
+
+#include <qgroupbox.h>
+#include <KoGlobal.h>
+#include <KoUnit.h>
+#include <kdialogbase.h>
+#include <KoPageLayout.h>
+#include <KoPageLayoutDia.h>
+
+class QComboBox;
+class KoUnitDoubleSpinBox;
+class KoPageLayoutColumns;
+
+/**
+ * This class is a widget that shows the KoPageLayout data structure and allows the user to change it.
+ */
+class KoPageLayoutSize : public QWidget {
+ Q_OBJECT
+
+public:
+ /**
+ * Contructor
+ * @param parent the parent widget
+ * @param layout the page layout that this widget should be initialzed with.
+ * @param unit the unit-type (mm/cm/inch) that the dialog should show
+ * @param columns the KoColumns (amout of columns) that the preview should be initialized with
+ * @param unitChooser if true a combobox with the unit-type is shown for the user to change
+ * @param enableBorders if true enable the user to change the margins (aka borders) of the page
+ */
+ KoPageLayoutSize(QWidget *parent, const KoPageLayout& layout, KoUnit::Unit unit,
+ const KoColumns& columns, bool unitChooser, bool enableBorders);
+
+ /**
+ * @return if the dialog is in a sane state and the values can be used.
+ */
+ bool queryClose();
+ /**
+ * Update the page preview widget with the param columns.
+ * @param columns the new columns
+ */
+ void setColumns(KoColumns &columns);
+
+public slots:
+ /**
+ * Set a new unit for the widget updating the widgets.
+ * @param unit the new unit
+ */
+ void setUnit( KoUnit::Unit unit );
+ /**
+ * Enable the user to edit the page border size
+ * @param on if true enable the user to change the margins (aka borders) of the page
+ */
+ void setEnableBorders(bool on);
+
+signals:
+ /**
+ * Emitted whenever the user changed something in the dialog.
+ * @param layout the update layout structure with currently displayed info.
+ * Note that the info may not be fully correct and physically possible (in which
+ * case queryClose will return false)
+ */
+ void propertyChange(KoPageLayout &layout);
+
+protected:
+ QComboBox *cpgUnit;
+ QComboBox *cpgFormat;
+ KoUnitDoubleSpinBox *epgWidth;
+ KoUnitDoubleSpinBox *epgHeight;
+ KoUnitDoubleSpinBox *ebrLeft;
+ KoUnitDoubleSpinBox *ebrRight;
+ KoUnitDoubleSpinBox *ebrTop;
+ KoUnitDoubleSpinBox *ebrBottom;
+ KoPagePreview *pgPreview;
+ QButtonGroup *m_orientGroup;
+
+protected slots:
+ void formatChanged( int );
+ void widthChanged( double );
+ void heightChanged( double );
+ void leftChanged( double );
+ void rightChanged( double );
+ void topChanged( double );
+ void bottomChanged( double );
+ void orientationChanged( int );
+ void setUnitInt( int unit );
+
+private:
+ void updatePreview();
+ void setValues();
+
+ KoUnit::Unit m_unit;
+ KoPageLayout m_layout;
+};
+
+#endif
diff --git a/kexi/3rdparty/kolibs/Makefile.am b/kexi/3rdparty/kolibs/Makefile.am
new file mode 100644
index 000000000..05d3fbd31
--- /dev/null
+++ b/kexi/3rdparty/kolibs/Makefile.am
@@ -0,0 +1,29 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_LTLIBRARIES = libkexikolibs.la
+
+libkexikolibs_la_SOURCES = \
+koGlobal.cc \
+koUnit.cc \
+koUnitWidgets.cc \
+koPageLayoutDia.cc \
+KoPageLayoutColumnsBase.ui \
+KoPageLayoutColumns.cpp \
+koPageLayout.cpp \
+KoPageLayoutHeaderBase.ui \
+KoPageLayoutHeader.cpp \
+KoPageLayoutSize.cpp
+
+noinst_HEADERS =
+
+libkexikolibs_la_LDFLAGS = $(all_libraries) -Wnounresolved
+libkexikolibs_la_LIBADD =
+libkexikolibs_la_METASOURCES = AUTO
+
+SUBDIRS = .
+
+# set the include path for X, qt and KDE
+
+INCLUDES= -I$(top_srcdir)/kexi $(all_includes)
+
+KDE_CXXFLAGS += -DSIMPLE_KOLIBS -DKOFFICECORE_EXPORT= -DKOFFICEUI_EXPORT=
diff --git a/kexi/3rdparty/kolibs/README b/kexi/3rdparty/kolibs/README
new file mode 100644
index 000000000..0adb99059
--- /dev/null
+++ b/kexi/3rdparty/kolibs/README
@@ -0,0 +1,13 @@
+kexikolibs
+----------
+
+Maintained by: Jaroslaw Staniek, js at iidea . pl
+
+
+This is a tiny selection of kofficeui and kofficecore classes taken
+to reuse Page Layout dialog.
+
+Will be removed when packagers realize kofficelibs package should
+be really shared among KOffice apps.
+
+The source code is almost unchanged except adding #ifdef SIMPLE_KOLIBS.
diff --git a/kexi/3rdparty/kolibs/koGlobal.cc b/kexi/3rdparty/kolibs/koGlobal.cc
new file mode 100644
index 000000000..09c74eabe
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koGlobal.cc
@@ -0,0 +1,202 @@
+/* This file is part of the KDE project
+ Copyright (C) 2001 David Faure <faure@kde.org>
+ Copyright 2003 Nicolas GOUTTE <goutte@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+#include <KoGlobal.h>
+#include <kdebug.h>
+#include <qfont.h>
+#include <qfontinfo.h>
+#include <kglobalsettings.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <ksimpleconfig.h>
+#include <kstandarddirs.h>
+#include <kstaticdeleter.h>
+#include <kimageio.h>
+#include <kiconloader.h>
+#include <kstandarddirs.h>
+
+
+KoGlobal* KoGlobal::s_global = 0L;
+static KStaticDeleter<KoGlobal> sdg;
+
+KoGlobal* KoGlobal::self()
+{
+ if ( !s_global )
+ sdg.setObject( s_global, new KoGlobal );
+ return s_global;
+}
+
+KoGlobal::KoGlobal()
+ : m_pointSize( -1 ), m_kofficeConfig( 0L )
+{
+ // Install the libkoffice* translations
+ KGlobal::locale()->insertCatalogue("koffice");
+
+ KImageIO::registerFormats();
+
+ // Tell KStandardDirs about the koffice prefix
+ KGlobal::dirs()->addPrefix(PREFIX);
+
+ // Tell the iconloader about share/apps/koffice/icons
+ KGlobal::iconLoader()->addAppDir("koffice");
+
+ // Another way to get the DPI of the display would be QPaintDeviceMetrics,
+ // but we have no widget here (and moving this to KoView wouldn't allow
+ // using this from the document easily).
+#ifdef Q_WS_X11
+ m_dpiX = QPaintDevice::x11AppDpiX();
+ m_dpiY = QPaintDevice::x11AppDpiY();
+#else
+ m_dpiX = 75;
+ m_dpiY = 75;
+#endif
+}
+
+KoGlobal::~KoGlobal()
+{
+ delete m_kofficeConfig;
+}
+
+QFont KoGlobal::_defaultFont()
+{
+ QFont font = KGlobalSettings::generalFont();
+ // we have to use QFontInfo, in case the font was specified with a pixel size
+ if ( font.pointSize() == -1 )
+ {
+ // cache size into m_pointSize, since QFontInfo loads the font -> slow
+ if ( m_pointSize == -1 )
+ m_pointSize = QFontInfo(font).pointSize();
+ Q_ASSERT( m_pointSize != -1 );
+ font.setPointSize( m_pointSize );
+ }
+ //kdDebug()<<k_funcinfo<<"QFontInfo(font).pointSize() :"<<QFontInfo(font).pointSize()<<endl;
+ //kdDebug()<<k_funcinfo<<"font.name() :"<<font.family ()<<endl;
+ return font;
+}
+
+QStringList KoGlobal::_listOfLanguageTags()
+{
+ if ( m_langMap.isEmpty() )
+ createListOfLanguages();
+ return m_langMap.values();
+}
+
+QStringList KoGlobal::_listOfLanguages()
+{
+ if ( m_langMap.empty() )
+ createListOfLanguages();
+ return m_langMap.keys();
+}
+
+void KoGlobal::createListOfLanguages()
+{
+ KConfig config( "all_languages", true, false, "locale" );
+ // Note that we could also use KLocale::allLanguagesTwoAlpha
+
+ QMap<QString, bool> seenLanguages;
+ const QStringList langlist = config.groupList();
+ for ( QStringList::ConstIterator itall = langlist.begin();
+ itall != langlist.end(); ++itall )
+ {
+ const QString tag = *itall;
+ config.setGroup( tag );
+ const QString name = config.readEntry("Name", tag);
+ // e.g. name is "French" and tag is "fr"
+
+ // The QMap does the sorting on the display-name, so that
+ // comboboxes are sorted.
+ m_langMap.insert( name, tag );
+
+ seenLanguages.insert( tag, true );
+ }
+
+ // Also take a look at the installed translations.
+ // Many of them are already in all_languages but all_languages doesn't
+ // currently have en_GB or en_US etc.
+
+ const QStringList translationList = KGlobal::dirs()->findAllResources("locale",
+ QString::fromLatin1("*/entry.desktop"));
+ for ( QStringList::ConstIterator it = translationList.begin();
+ it != translationList.end(); ++it )
+ {
+ // Extract the language tag from the directory name
+ QString tag = *it;
+ int index = tag.findRev('/');
+ tag = tag.left(index);
+ index = tag.findRev('/');
+ tag = tag.mid(index+1);
+
+ if ( seenLanguages.find( tag ) == seenLanguages.end() ) {
+ KSimpleConfig entry(*it);
+ entry.setGroup("KCM Locale");
+
+ const QString name = entry.readEntry("Name", tag);
+ // e.g. name is "US English" and tag is "en_US"
+ m_langMap.insert( name, tag );
+
+ // enable this if writing a third way of finding languages below
+ //seenLanguages.insert( tag, true );
+ }
+
+ }
+
+ // #### We also might not have an entry for a language where spellchecking is supported,
+ // but no KDE translation is available, like fr_CA.
+ // How to add them?
+}
+
+QString KoGlobal::tagOfLanguage( const QString & _lang)
+{
+ const LanguageMap& map = self()->m_langMap;
+ QMap<QString,QString>::ConstIterator it = map.find( _lang );
+ if ( it != map.end() )
+ return *it;
+ return QString::null;
+}
+
+QString KoGlobal::languageFromTag( const QString &langTag )
+{
+ const LanguageMap& map = self()->m_langMap;
+ QMap<QString,QString>::ConstIterator it = map.begin();
+ const QMap<QString,QString>::ConstIterator end = map.end();
+ for ( ; it != end; ++it )
+ if ( it.data() == langTag )
+ return it.key();
+
+ // Language code not found. Better return the code (tag) than nothing.
+ return langTag;
+}
+
+KConfig* KoGlobal::_kofficeConfig()
+{
+ if ( !m_kofficeConfig ) {
+ m_kofficeConfig = new KConfig( "kofficerc" );
+ }
+ return m_kofficeConfig;
+}
+
+void KoGlobal::setDPI( int x, int y )
+{
+ //kdDebug() << k_funcinfo << x << "," << y << endl;
+ KoGlobal* s = self();
+ s->m_dpiX = x;
+ s->m_dpiY = y;
+}
diff --git a/kexi/3rdparty/kolibs/koGlobal.h b/kexi/3rdparty/kolibs/koGlobal.h
new file mode 100644
index 000000000..bab9c2615
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koGlobal.h
@@ -0,0 +1,107 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
+ Copyright 2002, 2003 David Faure <faure@kde.org>
+ Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef koGlobal_h
+#define koGlobal_h
+
+#include <qstringlist.h>
+#include <qfont.h>
+#include <qmap.h>
+class KConfig;
+
+#include <koffice_export.h>
+class KOFFICECORE_EXPORT KoGlobal
+{
+public:
+ /// For KoApplication
+ static void initialize() {
+ (void)self(); // I don't want to make KGlobal instances public, so self() is private
+ }
+ /**
+ * Return the default font for KOffice programs.
+ * This is (currently) the same as the KDE-global default font,
+ * except that it is guaranteed to have a point size set,
+ * never a pixel size (see @ref QFont).
+ */
+ static QFont defaultFont() {
+ return self()->_defaultFont();
+ }
+
+ /**
+ * @return the global KConfig object around kofficerc.
+ * kofficerc is used for KOffice-wide settings, from totally unrelated classes,
+ * so this is the centralization of the KConfig object so that the file is
+ * parsed only once
+ */
+ static KConfig* kofficeConfig() {
+ return self()->_kofficeConfig();
+ }
+
+ static int dpiX() {
+ return self()->m_dpiX;
+ }
+ static int dpiY() {
+ return self()->m_dpiY;
+ }
+ /// @internal, for KoApplication
+ static void setDPI( int x, int y );
+
+ /// Return the list of available languages, in their displayable form
+ /// (translated names)
+ static QStringList listOfLanguages() {
+ return self()->_listOfLanguages();
+ }
+ /// Return the list of available languages, in their internal form
+ /// e.g. "fr" or "en_US", here called "tag"
+ static QStringList listTagOfLanguages() { // TODO rename to listOfLanguageTags
+ return self()->_listOfLanguageTags();
+ }
+ /// For a given language display name, return its tag
+ static QString tagOfLanguage( const QString & _lang );
+ /// For a given language tag, return its display name
+ static QString languageFromTag( const QString &_lang );
+
+ ~KoGlobal();
+
+private:
+ static KoGlobal* self();
+ KoGlobal();
+ QFont _defaultFont();
+ QStringList _listOfLanguages();
+ QStringList _listOfLanguageTags();
+ KConfig* _kofficeConfig();
+ void createListOfLanguages();
+
+ int m_pointSize;
+ typedef QMap<QString, QString> LanguageMap;
+ LanguageMap m_langMap; // display-name -> language tag
+ KConfig* m_kofficeConfig;
+ int m_dpiX;
+ int m_dpiY;
+ // No BC problem here, constructor is private, feel free to add members
+
+ // Singleton pattern. Maybe this should even be refcounted, so
+ // that it gets cleaned up when closing all koffice parts in e.g. konqueror?
+ static KoGlobal* s_global;
+ friend class this_is_a_singleton; // work around gcc warning
+};
+
+#endif // koGlobal
diff --git a/kexi/3rdparty/kolibs/koPageLayout.cpp b/kexi/3rdparty/kolibs/koPageLayout.cpp
new file mode 100644
index 000000000..4ec985f3b
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koPageLayout.cpp
@@ -0,0 +1,244 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
+ Copyright 2002, 2003 David Faure <faure@kde.org>
+ Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include "KoPageLayout.h"
+#include <KoUnit.h>
+#include <klocale.h>
+#include <kprinter.h>
+#include <kdebug.h>
+#include <kglobal.h>
+#ifndef SIMPLE_KOLIBS
+# include <kodom.h>
+# include <koxmlns.h>
+#endif
+#include <qdom.h>
+
+// paper formats ( mm )
+#define PG_A3_WIDTH 297.0
+#define PG_A3_HEIGHT 420.0
+#define PG_A4_WIDTH 210.0
+#define PG_A4_HEIGHT 297.0
+#define PG_A5_WIDTH 148.0
+#define PG_A5_HEIGHT 210.0
+#define PG_B5_WIDTH 182.0
+#define PG_B5_HEIGHT 257.0
+#define PG_US_LETTER_WIDTH 216.0
+#define PG_US_LETTER_HEIGHT 279.0
+#define PG_US_LEGAL_WIDTH 216.0
+#define PG_US_LEGAL_HEIGHT 356.0
+#define PG_US_EXECUTIVE_WIDTH 191.0
+#define PG_US_EXECUTIVE_HEIGHT 254.0
+
+#ifndef SIMPLE_KOLIBS
+KoGenStyle KoPageLayout::saveOasis() const
+{
+ KoGenStyle style(KoGenStyle::STYLE_PAGELAYOUT);
+ style.addPropertyPt("fo:page-width", ptWidth);
+ style.addPropertyPt("fo:page-height", ptHeight);
+ style.addPropertyPt("fo:margin-left", ptLeft);
+ style.addPropertyPt("fo:margin-right", ptRight);
+ style.addPropertyPt("fo:margin-top", ptTop);
+ style.addPropertyPt("fo:margin-bottom", ptBottom);
+ style.addProperty("style:print-orientation", (orientation == PG_LANDSCAPE ? "landscape" : "portrait"));
+ return style;
+}
+
+void KoPageLayout::loadOasis(const QDomElement &style)
+{
+ QDomElement properties( KoDom::namedItemNS( style, KoXmlNS::style, "page-layout-properties" ) );
+ if ( !properties.isNull() )
+ {
+ ptWidth = KoUnit::parseValue(properties.attributeNS( KoXmlNS::fo, "page-width", QString::null ) );
+ ptHeight = KoUnit::parseValue(properties.attributeNS( KoXmlNS::fo, "page-height", QString::null ) );
+ if (properties.attributeNS( KoXmlNS::style, "print-orientation", QString::null)=="portrait")
+ orientation=PG_PORTRAIT;
+ else
+ orientation=PG_LANDSCAPE;
+ ptRight = KoUnit::parseValue( properties.attributeNS( KoXmlNS::fo, "margin-right", QString::null ) );
+ ptBottom = KoUnit::parseValue( properties.attributeNS( KoXmlNS::fo, "margin-bottom", QString::null ) );
+ ptLeft = KoUnit::parseValue( properties.attributeNS( KoXmlNS::fo, "margin-left", QString::null ) );
+ ptTop = KoUnit::parseValue( properties.attributeNS( KoXmlNS::fo, "margin-top", QString::null ) );
+ // guessFormat takes millimeters
+ if ( orientation == PG_LANDSCAPE )
+ format = KoPageFormat::guessFormat( POINT_TO_MM(ptHeight), POINT_TO_MM(ptWidth) );
+ else
+ format = KoPageFormat::guessFormat( POINT_TO_MM(ptWidth), POINT_TO_MM(ptHeight) );
+ }
+}
+#endif
+
+KoPageLayout KoPageLayout::standardLayout()
+{
+ KoPageLayout layout;
+ layout.format = KoPageFormat::defaultFormat();
+ layout.orientation = PG_PORTRAIT;
+ layout.ptWidth = MM_TO_POINT( KoPageFormat::width( layout.format, layout.orientation ) );
+ layout.ptHeight = MM_TO_POINT( KoPageFormat::height( layout.format, layout.orientation ) );
+ layout.ptLeft = MM_TO_POINT( 20.0 );
+ layout.ptRight = MM_TO_POINT( 20.0 );
+ layout.ptTop = MM_TO_POINT( 20.0 );
+ layout.ptBottom = MM_TO_POINT( 20.0 );
+ return layout;
+}
+
+struct PageFormatInfo
+{
+ KoFormat format;
+ KPrinter::PageSize kprinter;
+ const char* shortName; // Short name
+ const char* descriptiveName; // Full name, which will be translated
+ double width; // in mm
+ double height; // in mm
+};
+
+// NOTES:
+// - the width and height of non-ISO formats are rounded
+// http://en.wikipedia.org/wiki/Paper_size can help
+// - the comments "should be..." indicates the exact values if the inch sizes would be multiplied by 25.4 mm/inch
+
+const PageFormatInfo pageFormatInfo[]=
+{
+ { PG_DIN_A3, KPrinter::A3, "A3", I18N_NOOP("ISO A3"), 297.0, 420.0 },
+ { PG_DIN_A4, KPrinter::A4, "A4", I18N_NOOP("ISO A4"), 210.0, 297.0 },
+ { PG_DIN_A5, KPrinter::A5, "A5", I18N_NOOP("ISO A5"), 148.0, 210.0 },
+ { PG_US_LETTER, KPrinter::Letter, "Letter", I18N_NOOP("US Letter"), 215.9, 279.4 },
+ { PG_US_LEGAL, KPrinter::Legal, "Legal", I18N_NOOP("US Legal"), 215.9, 355.6 },
+ { PG_SCREEN, KPrinter::A4, "Screen", I18N_NOOP("Screen"), PG_A4_HEIGHT, PG_A4_WIDTH }, // Custom, so fall back to A4
+ { PG_CUSTOM, KPrinter::A4, "Custom", I18N_NOOP2("Custom size", "Custom"), PG_A4_WIDTH, PG_A4_HEIGHT }, // Custom, so fall back to A4
+ { PG_DIN_B5, KPrinter::B5, "B5", I18N_NOOP("ISO B5"), 182.0, 257.0 },
+ // Hmm, wikipedia says 184.15 * 266.7 for executive !
+ { PG_US_EXECUTIVE, KPrinter::Executive, "Executive", I18N_NOOP("US Executive"), 191.0, 254.0 }, // should be 190.5 mm x 254.0 mm
+ { PG_DIN_A0, KPrinter::A0, "A0", I18N_NOOP("ISO A0"), 841.0, 1189.0 },
+ { PG_DIN_A1, KPrinter::A1, "A1", I18N_NOOP("ISO A1"), 594.0, 841.0 },
+ { PG_DIN_A2, KPrinter::A2, "A2", I18N_NOOP("ISO A2"), 420.0, 594.0 },
+ { PG_DIN_A6, KPrinter::A6, "A6", I18N_NOOP("ISO A6"), 105.0, 148.0 },
+ { PG_DIN_A7, KPrinter::A7, "A7", I18N_NOOP("ISO A7"), 74.0, 105.0 },
+ { PG_DIN_A8, KPrinter::A8, "A8", I18N_NOOP("ISO A8"), 52.0, 74.0 },
+ { PG_DIN_A9, KPrinter::A9, "A9", I18N_NOOP("ISO A9"), 37.0, 52.0 },
+ { PG_DIN_B0, KPrinter::B0, "B0", I18N_NOOP("ISO B0"), 1030.0, 1456.0 },
+ { PG_DIN_B1, KPrinter::B1, "B1", I18N_NOOP("ISO B1"), 728.0, 1030.0 },
+ { PG_DIN_B10, KPrinter::B10, "B10", I18N_NOOP("ISO B10"), 32.0, 45.0 },
+ { PG_DIN_B2, KPrinter::B2, "B2", I18N_NOOP("ISO B2"), 515.0, 728.0 },
+ { PG_DIN_B3, KPrinter::B3, "B3", I18N_NOOP("ISO B3"), 364.0, 515.0 },
+ { PG_DIN_B4, KPrinter::B4, "B4", I18N_NOOP("ISO B4"), 257.0, 364.0 },
+ { PG_DIN_B6, KPrinter::B6, "B6", I18N_NOOP("ISO B6"), 128.0, 182.0 },
+ { PG_ISO_C5, KPrinter::C5E, "C5", I18N_NOOP("ISO C5"), 163.0, 229.0 }, // Some sources tells: 162 mm x 228 mm
+ { PG_US_COMM10, KPrinter::Comm10E, "Comm10", I18N_NOOP("US Common 10"), 105.0, 241.0 }, // should be 104.775 mm x 241.3 mm
+ { PG_ISO_DL, KPrinter::DLE, "DL", I18N_NOOP("ISO DL"), 110.0, 220.0 },
+ { PG_US_FOLIO, KPrinter::Folio, "Folio", I18N_NOOP("US Folio"), 210.0, 330.0 }, // should be 209.54 mm x 330.2 mm
+ { PG_US_LEDGER, KPrinter::Ledger, "Ledger", I18N_NOOP("US Ledger"), 432.0, 279.0 }, // should be 431.8 mm x 297.4 mm
+ { PG_US_TABLOID, KPrinter::Tabloid, "Tabloid", I18N_NOOP("US Tabloid"), 279.0, 432.0 } // should be 297.4 mm x 431.8 mm
+};
+
+int KoPageFormat::printerPageSize( KoFormat format )
+{
+ if ( format == PG_SCREEN )
+ {
+ kdWarning() << "You use the page layout SCREEN. Printing in DIN A4 LANDSCAPE." << endl;
+ return KPrinter::A4;
+ }
+ else if ( format == PG_CUSTOM )
+ {
+ kdWarning() << "The used page layout (CUSTOM) is not supported by KPrinter. Printing in A4." << endl;
+ return KPrinter::A4;
+ }
+ else if ( format <= PG_LAST_FORMAT )
+ return pageFormatInfo[ format ].kprinter;
+ else
+ return KPrinter::A4;
+}
+
+double KoPageFormat::width( KoFormat format, KoOrientation orientation )
+{
+ if ( orientation == PG_LANDSCAPE )
+ return height( format, PG_PORTRAIT );
+ if ( format <= PG_LAST_FORMAT )
+ return pageFormatInfo[ format ].width;
+ return PG_A4_WIDTH; // should never happen
+}
+
+double KoPageFormat::height( KoFormat format, KoOrientation orientation )
+{
+ if ( orientation == PG_LANDSCAPE )
+ return width( format, PG_PORTRAIT );
+ if ( format <= PG_LAST_FORMAT )
+ return pageFormatInfo[ format ].height;
+ return PG_A4_HEIGHT;
+}
+
+KoFormat KoPageFormat::guessFormat( double width, double height )
+{
+ for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i )
+ {
+ // We have some tolerance. 1pt is a third of a mm, this is
+ // barely noticeable for a page size.
+ if ( i != PG_CUSTOM
+ && kAbs( width - pageFormatInfo[i].width ) < 1.0
+ && kAbs( height - pageFormatInfo[i].height ) < 1.0 )
+ return static_cast<KoFormat>(i);
+ }
+ return PG_CUSTOM;
+}
+
+QString KoPageFormat::formatString( KoFormat format )
+{
+ if ( format <= PG_LAST_FORMAT )
+ return QString::fromLatin1( pageFormatInfo[ format ].shortName );
+ return QString::fromLatin1( "A4" );
+}
+
+KoFormat KoPageFormat::formatFromString( const QString & string )
+{
+ for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i )
+ {
+ if (string == QString::fromLatin1( pageFormatInfo[ i ].shortName ))
+ return pageFormatInfo[ i ].format;
+ }
+ // We do not know the format name, so we have a custom format
+ return PG_CUSTOM;
+}
+
+KoFormat KoPageFormat::defaultFormat()
+{
+ int kprinter = KGlobal::locale()->pageSize();
+ for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i )
+ {
+ if ( pageFormatInfo[ i ].kprinter == kprinter )
+ return static_cast<KoFormat>(i);
+ }
+ return PG_DIN_A4;
+}
+
+QString KoPageFormat::name( KoFormat format )
+{
+ if ( format <= PG_LAST_FORMAT )
+ return i18n( pageFormatInfo[ format ].descriptiveName );
+ return i18n( pageFormatInfo[ PG_DIN_A4 ].descriptiveName );
+}
+
+QStringList KoPageFormat::allFormats()
+{
+ QStringList lst;
+ for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i )
+ {
+ lst << i18n( pageFormatInfo[ i ].descriptiveName );
+ }
+ return lst;
+}
diff --git a/kexi/3rdparty/kolibs/koPageLayout.h b/kexi/3rdparty/kolibs/koPageLayout.h
new file mode 100644
index 000000000..b526620e0
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koPageLayout.h
@@ -0,0 +1,260 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
+ Copyright 2002, 2003 David Faure <faure@kde.org>
+ Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KOPAGELAYOUT_H
+#define KOPAGELAYOUT_H
+
+#ifndef SIMPLE_KOLIBS
+# include <koGenStyles.h>
+#endif
+#include <qstringlist.h>
+#include <koffice_export.h>
+class QDomElement;
+
+/**
+ * @brief Represents the paper format a document shall be printed on.
+ *
+ * For compatibility reasons, and because of screen and custom,
+ * this enum doesn't map to QPrinter::PageSize but KoPageFormat::printerPageSize
+ * does the conversion.
+ *
+ * @todo convert DIN to ISO in the names
+ */
+enum KoFormat {
+ PG_DIN_A3 = 0,
+ PG_DIN_A4 = 1,
+ PG_DIN_A5 = 2,
+ PG_US_LETTER = 3,
+ PG_US_LEGAL = 4,
+ PG_SCREEN = 5,
+ PG_CUSTOM = 6,
+ PG_DIN_B5 = 7,
+ PG_US_EXECUTIVE = 8,
+ PG_DIN_A0 = 9,
+ PG_DIN_A1 = 10,
+ PG_DIN_A2 = 11,
+ PG_DIN_A6 = 12,
+ PG_DIN_A7 = 13,
+ PG_DIN_A8 = 14,
+ PG_DIN_A9 = 15,
+ PG_DIN_B0 = 16,
+ PG_DIN_B1 = 17,
+ PG_DIN_B10 = 18,
+ PG_DIN_B2 = 19,
+ PG_DIN_B3 = 20,
+ PG_DIN_B4 = 21,
+ PG_DIN_B6 = 22,
+ PG_ISO_C5 = 23,
+ PG_US_COMM10 = 24,
+ PG_ISO_DL = 25,
+ PG_US_FOLIO = 26,
+ PG_US_LEDGER = 27,
+ PG_US_TABLOID = 28,
+ // update the number below and the static arrays if you add more values to the enum
+ PG_LAST_FORMAT = PG_US_TABLOID // used by koPageLayout.cpp
+};
+
+/**
+ * Represents the orientation of a printed document.
+ */
+enum KoOrientation {
+ PG_PORTRAIT = 0,
+ PG_LANDSCAPE = 1
+};
+
+namespace KoPageFormat
+{
+ /**
+ * @brief Convert a KoFormat into a KPrinter::PageSize.
+ *
+ * If format is 'screen' it will use A4 landscape.
+ * If format is 'custom' it will use A4 portrait.
+ * (you may want to take care of those cases separately).
+ * Usually passed to KPrinter::setPageSize().
+ *
+ * @note We return int instead of the enum to avoid including kprinter.h
+ */
+ KOFFICECORE_EXPORT int /*KPrinter::PageSize*/ printerPageSize( KoFormat format );
+
+ /**
+ * Returns the width (in mm) for a given page format and orientation
+ * 'Custom' isn't supported by this function, obviously.
+ */
+ KOFFICECORE_EXPORT double width( KoFormat format, KoOrientation orientation );
+
+ /**
+ * Returns the height (in mm) for a given page format and orientation
+ * 'Custom' isn't supported by this function, obviously.
+ */
+ KOFFICECORE_EXPORT double height( KoFormat format, KoOrientation orientation );
+
+ /**
+ * Returns the internal name of the given page format.
+ * Use for saving.
+ */
+ KOFFICECORE_EXPORT QString formatString( KoFormat format );
+
+ /**
+ * Convert a format string (internal name) to a page format value.
+ * Use for loading.
+ */
+ KOFFICECORE_EXPORT KoFormat formatFromString( const QString & string );
+
+ /**
+ * Returns the default format (based on the KControl settings)
+ */
+ KOFFICECORE_EXPORT KoFormat defaultFormat();
+
+ /**
+ * Returns the translated name of the given page format.
+ * Use for showing the user.
+ */
+ KOFFICECORE_EXPORT QString name( KoFormat format );
+
+ /**
+ * Lists the translated names of all the available formats
+ */
+ KOFFICECORE_EXPORT QStringList allFormats();
+
+ /**
+ * Try to find the paper format for the given width and height (in mm).
+ * Useful to some import filters.
+ */
+ KOFFICECORE_EXPORT KoFormat guessFormat( double width, double height );
+}
+
+
+/**
+ * @brief Header/Footer type.
+ *
+ * @note Yes, this should have been a bitfield, but there was only 0, 2, 3 in koffice-1.0. Don't ask why.
+ * In the long run this should be replaced with a more flexible repetition/section concept.
+ */
+enum KoHFType {
+ HF_SAME = 0, ///< 0: Header/Footer is the same on all pages
+ HF_FIRST_EO_DIFF = 1, ///< 1: Header/Footer is different on first, even and odd pages (2&3)
+ HF_FIRST_DIFF = 2, ///< 2: Header/Footer for the first page differs
+ HF_EO_DIFF = 3 ///< 3: Header/Footer for even - odd pages are different
+};
+
+/**
+ * This structure defines the page layout, including
+ * its size in pt, its format (e.g. A4), orientation, unit, margins etc.
+ */
+struct KoPageLayout
+{
+ /** Page format */
+ KoFormat format;
+ /** Page orientation */
+ KoOrientation orientation;
+
+ /** Page width in pt */
+ double ptWidth;
+ /** Page height in pt */
+ double ptHeight;
+ /** Left margin in pt */
+ double ptLeft;
+ /** Right margin in pt */
+ double ptRight;
+ /** Top margin in pt */
+ double ptTop;
+ /** Bottom margin in pt */
+ double ptBottom;
+
+ bool operator==( const KoPageLayout& l ) const {
+ return ( ptWidth == l.ptWidth &&
+ ptHeight == l.ptHeight &&
+ ptLeft == l.ptLeft &&
+ ptRight == l.ptRight &&
+ ptTop == l.ptTop &&
+ ptBottom == l.ptBottom );
+ }
+ bool operator!=( const KoPageLayout& l ) const {
+ return !( (*this) == l );
+ }
+
+#ifndef SIMPLE_KOLIBS
+ /**
+ * Save this page layout to OASIS.
+ */
+ KOFFICECORE_EXPORT KoGenStyle saveOasis() const;
+
+ /**
+ * Load this page layout from OASIS
+ */
+ KOFFICECORE_EXPORT void loadOasis(const QDomElement &style);
+#endif
+
+ /**
+ * @return a page layout with the default page size depending on the locale settings,
+ * default margins (2 cm), and portrait orientation.
+ * @since 1.4
+ */
+ static KOFFICECORE_EXPORT KoPageLayout standardLayout();
+};
+
+/** structure for header-footer */
+struct KoHeadFoot
+{
+ QString headLeft;
+ QString headMid;
+ QString headRight;
+ QString footLeft;
+ QString footMid;
+ QString footRight;
+};
+
+/** structure for columns */
+struct KoColumns
+{
+ int columns;
+ double ptColumnSpacing;
+ bool operator==( const KoColumns& rhs ) const {
+ return columns == rhs.columns &&
+ QABS(ptColumnSpacing - rhs.ptColumnSpacing) <= 1E-10;
+ }
+ bool operator!=( const KoColumns& rhs ) const {
+ return columns != rhs.columns ||
+ QABS(ptColumnSpacing - rhs.ptColumnSpacing) > 1E-10;
+ }
+};
+
+/** structure for KWord header-footer */
+struct KoKWHeaderFooter
+{
+ KoHFType header;
+ KoHFType footer;
+ double ptHeaderBodySpacing;
+ double ptFooterBodySpacing;
+ double ptFootNoteBodySpacing;
+ bool operator==( const KoKWHeaderFooter& rhs ) const {
+ return header == rhs.header && footer == rhs.footer &&
+ QABS(ptHeaderBodySpacing - rhs.ptHeaderBodySpacing) <= 1E-10 &&
+ QABS(ptFooterBodySpacing - rhs.ptFooterBodySpacing) <= 1E-10 &&
+ QABS(ptFootNoteBodySpacing - rhs.ptFootNoteBodySpacing) <= 1E-10;
+ }
+ bool operator!=( const KoKWHeaderFooter& rhs ) const {
+ return !( *this == rhs );
+ }
+};
+
+#endif /* KOPAGELAYOUT_H */
+
diff --git a/kexi/3rdparty/kolibs/koPageLayoutDia.cc b/kexi/3rdparty/kolibs/koPageLayoutDia.cc
new file mode 100644
index 000000000..5fe8832ea
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koPageLayoutDia.cc
@@ -0,0 +1,402 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
+ Copyright (C) 2005 Thomas Zander <zander@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+// Description: Page Layout Dialog (sources)
+
+/******************************************************************/
+
+#include <KoPageLayoutDia.h>
+#include <KoPageLayoutColumns.h>
+#include <KoPageLayoutSize.h>
+#include <KoPageLayoutHeader.h>
+#include <KoUnit.h>
+#include <KoUnitWidgets.h>
+
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qlineedit.h>
+#include <qbuttongroup.h>
+#include <qradiobutton.h>
+#include <qcheckbox.h>
+#include <qhbox.h>
+#include <qvgroupbox.h>
+#include <qhbuttongroup.h>
+
+/******************************************************************/
+/* class KoPagePreview */
+/******************************************************************/
+
+/*===================== constrcutor ==============================*/
+KoPagePreview::KoPagePreview( QWidget* parent, const char *name, const KoPageLayout& layout )
+ : QGroupBox( i18n( "Page Preview" ), parent, name )
+{
+ setPageLayout( layout );
+ columns = 1;
+ setMinimumSize( 150, 150 );
+}
+
+/*====================== destructor ==============================*/
+KoPagePreview::~KoPagePreview()
+{
+}
+
+/*=================== set layout =================================*/
+void KoPagePreview::setPageLayout( const KoPageLayout &layout )
+{
+ // resolution[XY] is in pixel per pt
+ double resolutionX = POINT_TO_INCH( static_cast<double>(KoGlobal::dpiX()) );
+ double resolutionY = POINT_TO_INCH( static_cast<double>(KoGlobal::dpiY()) );
+
+ m_pageWidth = layout.ptWidth * resolutionX;
+ m_pageHeight = layout.ptHeight * resolutionY;
+
+ double zh = 110.0 / m_pageHeight;
+ double zw = 110.0 / m_pageWidth;
+ double z = QMIN( zw, zh );
+
+ m_pageWidth *= z;
+ m_pageHeight *= z;
+
+ m_textFrameX = layout.ptLeft * resolutionX * z;
+ m_textFrameY = layout.ptTop * resolutionY * z;
+ m_textFrameWidth = m_pageWidth - ( layout.ptLeft + layout.ptRight ) * resolutionX * z;
+ m_textFrameHeight = m_pageHeight - ( layout.ptTop + layout.ptBottom ) * resolutionY * z;
+
+ repaint( true );
+}
+
+/*=================== set layout =================================*/
+void KoPagePreview::setPageColumns( const KoColumns &_columns )
+{
+ columns = _columns.columns;
+ repaint( true );
+}
+
+/*======================== draw contents =========================*/
+void KoPagePreview::drawContents( QPainter *painter )
+{
+ double cw = m_textFrameWidth;
+ if(columns!=1)
+ cw/=static_cast<double>(columns);
+
+ painter->setBrush( white );
+ painter->setPen( QPen( black ) );
+
+ int x=static_cast<int>( ( width() - m_pageWidth ) * 0.5 );
+ int y=static_cast<int>( ( height() - m_pageHeight ) * 0.5 );
+ int w=static_cast<int>(m_pageWidth);
+ int h=static_cast<int>(m_pageHeight);
+ //painter->drawRect( x + 1, y + 1, w, h);
+ painter->drawRect( x, y, w, h );
+
+ painter->setBrush( QBrush( black, HorPattern ) );
+ if ( m_textFrameWidth == m_pageWidth || m_textFrameHeight == m_pageHeight )
+ painter->setPen( NoPen );
+ else
+ painter->setPen( lightGray );
+
+ for ( int i = 0; i < columns; ++i )
+ painter->drawRect( x + static_cast<int>(m_textFrameX) + static_cast<int>(i * cw),
+ y + static_cast<int>(m_textFrameY), static_cast<int>(cw),
+ static_cast<int>(m_textFrameHeight) );
+}
+
+/******************************************************************/
+/* class KoPageLayoutDia */
+/******************************************************************/
+
+/*==================== constructor ===============================*/
+KoPageLayoutDia::KoPageLayoutDia( QWidget* parent, const char* name,
+ const KoPageLayout& layout,
+ const KoHeadFoot& hf, int tabs,
+ KoUnit::Unit unit, bool modal )
+ : KDialogBase( KDialogBase::Tabbed, i18n("Page Layout"), KDialogBase::Ok | KDialogBase::Cancel,
+ KDialogBase::Ok, parent, name, modal)
+{
+
+ flags = tabs;
+ m_layout = layout;
+ m_unit = unit;
+ m_pageSizeTab = 0;
+ m_columnsTab = 0;
+ m_headerTab = 0;
+
+ m_column.columns = 1;
+
+ if ( tabs & FORMAT_AND_BORDERS ) setupTab1( true );
+ if ( tabs & HEADER_AND_FOOTER ) setupTab2( hf );
+
+ setFocusPolicy( QWidget::StrongFocus );
+ setFocus();
+}
+
+/*==================== constructor ===============================*/
+KoPageLayoutDia::KoPageLayoutDia( QWidget* parent, const char* name,
+ const KoPageLayout& layout,
+ const KoHeadFoot& hf,
+ const KoColumns& columns,
+ const KoKWHeaderFooter& kwhf,
+ int tabs, KoUnit::Unit unit )
+ : KDialogBase( KDialogBase::Tabbed, i18n("Page Layout"), KDialogBase::Ok | KDialogBase::Cancel,
+ KDialogBase::Ok, parent, name, true)
+{
+ flags = tabs;
+
+ m_layout = layout;
+ m_column = columns;
+ m_unit = unit;
+ m_pageSizeTab = 0;
+ m_columnsTab = 0;
+ m_headerTab = 0;
+
+ if ( tabs & FORMAT_AND_BORDERS ) setupTab1( !( tabs & DISABLE_BORDERS ) );
+ if ( tabs & HEADER_AND_FOOTER ) setupTab2( hf );
+ if ( tabs & COLUMNS ) setupTab3();
+ if ( tabs & KW_HEADER_AND_FOOTER ) setupTab4(kwhf);
+
+ setFocusPolicy( QWidget::StrongFocus );
+ setFocus();
+}
+
+/*===================== destructor ===============================*/
+KoPageLayoutDia::~KoPageLayoutDia()
+{
+}
+
+/*======================= show dialog ============================*/
+bool KoPageLayoutDia::pageLayout( KoPageLayout& layout, KoHeadFoot& hf, int tabs, KoUnit::Unit& unit, QWidget* parent )
+{
+ bool res = false;
+ KoPageLayoutDia *dlg = new KoPageLayoutDia( parent, "PageLayout", layout, hf, tabs, unit );
+
+ if ( dlg->exec() == QDialog::Accepted ) {
+ res = true;
+ if ( tabs & FORMAT_AND_BORDERS ) layout = dlg->layout();
+ if ( tabs & HEADER_AND_FOOTER ) hf = dlg->headFoot();
+ unit = dlg->unit();
+ }
+
+ delete dlg;
+
+ return res;
+}
+
+/*======================= show dialog ============================*/
+bool KoPageLayoutDia::pageLayout( KoPageLayout& layout, KoHeadFoot& hf, KoColumns& columns,
+ KoKWHeaderFooter &_kwhf, int tabs, KoUnit::Unit& unit, QWidget* parent )
+{
+ bool res = false;
+ KoPageLayoutDia *dlg = new KoPageLayoutDia( parent, "PageLayout", layout, hf, columns, _kwhf, tabs, unit );
+
+ if ( dlg->exec() == QDialog::Accepted ) {
+ res = true;
+ if ( tabs & FORMAT_AND_BORDERS ) layout = dlg->layout();
+ if ( tabs & HEADER_AND_FOOTER ) hf = dlg->headFoot();
+ if ( tabs & COLUMNS ) columns = dlg->columns();
+ if ( tabs & KW_HEADER_AND_FOOTER ) _kwhf = dlg->headerFooter();
+ unit = dlg->unit();
+ }
+
+ delete dlg;
+
+ return res;
+}
+
+/*===================== get a standard page layout ===============*/
+KoPageLayout KoPageLayoutDia::standardLayout()
+{
+ return KoPageLayout::standardLayout();
+}
+
+/*====================== get header - footer =====================*/
+KoHeadFoot KoPageLayoutDia::headFoot() const
+{
+ KoHeadFoot hf;
+ hf.headLeft = eHeadLeft->text();
+ hf.headMid = eHeadMid->text();
+ hf.headRight = eHeadRight->text();
+ hf.footLeft = eFootLeft->text();
+ hf.footMid = eFootMid->text();
+ hf.footRight = eFootRight->text();
+ return hf;
+}
+
+/*================================================================*/
+const KoKWHeaderFooter& KoPageLayoutDia::headerFooter()
+{
+ return m_headerTab->headerFooter();
+}
+
+/*================ setup page size & margins tab ==================*/
+void KoPageLayoutDia::setupTab1( bool enableBorders )
+{
+ QWidget *tab1 = addPage(i18n( "Page Size && &Margins" ));
+ QHBoxLayout *lay = new QHBoxLayout(tab1);
+ m_pageSizeTab = new KoPageLayoutSize(tab1, m_layout, m_unit, m_column, !(flags & DISABLE_UNIT), enableBorders );
+ lay->addWidget(m_pageSizeTab);
+ m_pageSizeTab->show();
+ connect (m_pageSizeTab, SIGNAL( propertyChange(KoPageLayout&)),
+ this, SLOT (sizeUpdated( KoPageLayout&)));
+}
+
+void KoPageLayoutDia::sizeUpdated(KoPageLayout &layout) {
+ m_layout.ptWidth = layout.ptWidth;
+ m_layout.ptHeight = layout.ptHeight;
+ m_layout.ptLeft = layout.ptLeft;
+ m_layout.ptRight = layout.ptRight;
+ m_layout.ptTop = layout.ptTop;
+ m_layout.ptBottom = layout.ptBottom;
+ m_layout.format = layout.format;
+ m_layout.orientation = layout.orientation;
+ if(m_columnsTab)
+ m_columnsTab->setLayout(layout);
+}
+
+/*================ setup header and footer tab ===================*/
+void KoPageLayoutDia::setupTab2( const KoHeadFoot& hf )
+{
+ QWidget *tab2 = addPage(i18n( "H&eader && Footer" ));
+ QGridLayout *grid2 = new QGridLayout( tab2, 7, 2, 0, KDialog::spacingHint() );
+
+ // ------------- header ---------------
+ QGroupBox *gHead = new QGroupBox( 0, Qt::Vertical, i18n( "Head Line" ), tab2 );
+ gHead->layout()->setSpacing(KDialog::spacingHint());
+ gHead->layout()->setMargin(KDialog::marginHint());
+ QGridLayout *headGrid = new QGridLayout( gHead->layout(), 2, 3 );
+
+ QLabel *lHeadLeft = new QLabel( i18n( "Left:" ), gHead );
+ headGrid->addWidget( lHeadLeft, 0, 0 );
+
+ eHeadLeft = new QLineEdit( gHead );
+ headGrid->addWidget( eHeadLeft, 1, 0 );
+ eHeadLeft->setText( hf.headLeft );
+
+ QLabel *lHeadMid = new QLabel( i18n( "Mid:" ), gHead );
+ headGrid->addWidget( lHeadMid, 0, 1 );
+
+ eHeadMid = new QLineEdit( gHead );
+ headGrid->addWidget( eHeadMid, 1, 1 );
+ eHeadMid->setText( hf.headMid );
+
+ QLabel *lHeadRight = new QLabel( i18n( "Right:" ), gHead );
+ headGrid->addWidget( lHeadRight, 0, 2 );
+
+ eHeadRight = new QLineEdit( gHead );
+ headGrid->addWidget( eHeadRight, 1, 2 );
+ eHeadRight->setText( hf.headRight );
+
+ grid2->addMultiCellWidget( gHead, 0, 1, 0, 1 );
+
+ // ------------- footer ---------------
+ QGroupBox *gFoot = new QGroupBox( 0, Qt::Vertical, i18n( "Foot Line" ), tab2 );
+ gFoot->layout()->setSpacing(KDialog::spacingHint());
+ gFoot->layout()->setMargin(KDialog::marginHint());
+ QGridLayout *footGrid = new QGridLayout( gFoot->layout(), 2, 3 );
+
+ QLabel *lFootLeft = new QLabel( i18n( "Left:" ), gFoot );
+ footGrid->addWidget( lFootLeft, 0, 0 );
+
+ eFootLeft = new QLineEdit( gFoot );
+ footGrid->addWidget( eFootLeft, 1, 0 );
+ eFootLeft->setText( hf.footLeft );
+
+ QLabel *lFootMid = new QLabel( i18n( "Mid:" ), gFoot );
+ footGrid->addWidget( lFootMid, 0, 1 );
+
+ eFootMid = new QLineEdit( gFoot );
+ footGrid->addWidget( eFootMid, 1, 1 );
+ eFootMid->setText( hf.footMid );
+
+ QLabel *lFootRight = new QLabel( i18n( "Right:" ), gFoot );
+ footGrid->addWidget( lFootRight, 0, 2 );
+
+ eFootRight = new QLineEdit( gFoot );
+ footGrid->addWidget( eFootRight, 1, 2 );
+ eFootRight->setText( hf.footRight );
+
+ grid2->addMultiCellWidget( gFoot, 2, 3, 0, 1 );
+
+ QLabel *lMacros2 = new QLabel( i18n( "You can insert several tags in the text:" ), tab2 );
+ grid2->addMultiCellWidget( lMacros2, 4, 4, 0, 1 );
+
+ QLabel *lMacros3 = new QLabel( i18n("<qt><ul><li>&lt;sheet&gt; The sheet name</li>"
+ "<li>&lt;page&gt; The current page</li>"
+ "<li>&lt;pages&gt; The total number of pages</li>"
+ "<li>&lt;name&gt; The filename or URL</li>"
+ "<li>&lt;file&gt; The filename with complete path or the URL</li></ul></qt>"), tab2 );
+ grid2->addMultiCellWidget( lMacros3, 5, 6, 0, 0, Qt::AlignTop );
+
+ QLabel *lMacros4 = new QLabel( i18n("<qt><ul><li>&lt;time&gt; The current time</li>"
+ "<li>&lt;date&gt; The current date</li>"
+ "<li>&lt;author&gt; Your full name</li>"
+ "<li>&lt;org&gt; Your organization</li>"
+ "<li>&lt;email&gt; Your email address</li></ul></qt>"), tab2 );
+ grid2->addMultiCellWidget( lMacros4, 5, 6, 1, 1, Qt::AlignTop );
+}
+
+/*================================================================*/
+void KoPageLayoutDia::setupTab3()
+{
+ QWidget *tab3 = addPage(i18n( "Col&umns" ));
+ QHBoxLayout *lay = new QHBoxLayout(tab3);
+ m_columnsTab = new KoPageLayoutColumns(tab3, m_column, m_unit, m_layout);
+ m_columnsTab->layout()->setMargin(0);
+ lay->addWidget(m_columnsTab);
+ m_columnsTab->show();
+ connect (m_columnsTab, SIGNAL( propertyChange(KoColumns&)),
+ this, SLOT (columnsUpdated( KoColumns&)));
+}
+
+void KoPageLayoutDia::columnsUpdated(KoColumns &columns) {
+ m_column.columns = columns.columns;
+ m_column.ptColumnSpacing = columns.ptColumnSpacing;
+ if(m_pageSizeTab)
+ m_pageSizeTab->setColumns(columns);
+}
+
+/*================================================================*/
+void KoPageLayoutDia::setupTab4(const KoKWHeaderFooter kwhf )
+{
+ QWidget *tab4 = addPage(i18n( "H&eader && Footer" ));
+ QHBoxLayout *lay = new QHBoxLayout(tab4);
+ m_headerTab = new KoPageLayoutHeader(tab4, m_unit, kwhf);
+ m_headerTab->layout()->setMargin(0);
+ lay->addWidget(m_headerTab);
+ m_headerTab->show();
+
+}
+
+
+/* Validation when closing. Error messages are never liked, but
+ better let the users enter all values in any order, and have one
+ final validation, than preventing them from entering values. */
+void KoPageLayoutDia::slotOk()
+{
+ if( m_pageSizeTab )
+ m_pageSizeTab->queryClose();
+ KDialogBase::slotOk(); // accept
+}
+
+#include <koPageLayoutDia.moc>
diff --git a/kexi/3rdparty/kolibs/koPageLayoutDia.h b/kexi/3rdparty/kolibs/koPageLayoutDia.h
new file mode 100644
index 000000000..f3f1d932e
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koPageLayoutDia.h
@@ -0,0 +1,200 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+// Description: Page Layout Dialog (header)
+
+#ifndef __KOPGLAYOUTDIA_H__
+#define __KOPGLAYOUTDIA_H__
+
+#include <qgroupbox.h>
+#include <KoGlobal.h>
+#include <KoUnit.h>
+#include <kdialogbase.h>
+#include <KoPageLayout.h>
+
+class QButtonGroup;
+class QComboBox;
+class QLineEdit;
+class QCheckBox;
+class KoUnitDoubleSpinBox;
+class KoPageLayoutColumns;
+class KoPageLayoutSize;
+class KoPageLayoutHeader;
+
+enum { FORMAT_AND_BORDERS = 1, HEADER_AND_FOOTER = 2, COLUMNS = 4, DISABLE_BORDERS = 8,
+ KW_HEADER_AND_FOOTER = 16, DISABLE_UNIT = 32 };
+
+/**
+ * KoPagePreview.
+ * Internal to KoPageLayoutDia.
+ */
+class KoPagePreview : public QGroupBox
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * constructor
+ */
+ KoPagePreview( QWidget*, const char*, const KoPageLayout & );
+ /**
+ * destructor
+ */
+ ~KoPagePreview();
+
+ /**
+ * set page layout
+ */
+ void setPageLayout( const KoPageLayout& );
+ void setPageColumns( const KoColumns& );
+
+protected:
+
+ // paint page
+ void drawContents( QPainter* );
+
+ double m_pageHeight, m_pageWidth, m_textFrameX, m_textFrameY, m_textFrameWidth, m_textFrameHeight;
+ int columns;
+};
+
+class KoPageLayoutDiaPrivate;
+
+/**
+ * With this dialog the user can specify the layout of the paper during printing.
+ */
+class KOFFICEUI_EXPORT KoPageLayoutDia : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor.
+ *
+ * @param parent The parent of the dialog.
+ * @param name The name of the dialog.
+ * @param layout The layout.
+ * @param headfoot The header and the footer.
+ * @param flags a variable with all features this dialog should show.
+ * @param unit The unit to use for displaying the values to the user.
+ * @param modal Whether the dialog is modal or not.
+ */
+ KoPageLayoutDia( QWidget* parent, const char* name,
+ const KoPageLayout& layout,
+ const KoHeadFoot& headfoot,
+ int flags, KoUnit::Unit unit, bool modal=true );
+
+ /**
+ * Constructor.
+ *
+ * @param parent The parent of the dialog.
+ * @param name The name of the dialog.
+ * @param layout The layout.
+ * @param headfoot The header and the footer.
+ * @param columns The number of columns on the page.
+ * @param kwheadfoot The KWord header and footer.
+ * @param tabs The number of tabs.
+ * @param unit The unit to use for displaying the values to the user
+ */
+ KoPageLayoutDia( QWidget* parent, const char* name,
+ const KoPageLayout& layout,
+ const KoHeadFoot& headfoot,
+ const KoColumns& columns,
+ const KoKWHeaderFooter& kwheadfoot,
+ int tabs, KoUnit::Unit unit );
+
+ /**
+ * Destructor.
+ */
+ ~KoPageLayoutDia();
+
+ /**
+ * Show page layout dialog.
+ * See constructor for documentation on the parameters
+ */
+ static bool pageLayout( KoPageLayout&, KoHeadFoot&, int tabs, KoUnit::Unit& unit, QWidget* parent = 0 );
+
+ /**
+ * Show page layout dialog.
+ * See constructor for documentation on the parameters
+ */
+ static bool pageLayout( KoPageLayout&, KoHeadFoot&, KoColumns&, KoKWHeaderFooter&, int tabs, KoUnit::Unit& unit, QWidget* parent = 0 );
+ /**
+ * Retrieves a standard page layout.
+ * Deprecated: better use KoPageLayout::standardLayout()
+ */
+ static KDE_DEPRECATED KoPageLayout standardLayout();
+
+ /**
+ * Returns the layout
+ */
+ const KoPageLayout& layout() const { return m_layout; }
+
+ /**
+ * Returns the header and footer information
+ */
+ KoHeadFoot headFoot() const;
+
+ /**
+ * Returns the unit
+ */
+ KoUnit::Unit unit() const { return m_unit; }
+
+private:
+ const KoColumns& columns() { return m_column; }
+ const KoKWHeaderFooter& headerFooter();
+
+ // setup tabs
+ void setupTab1( bool enableBorders );
+ void setupTab2( const KoHeadFoot& hf );
+ void setupTab3();
+ void setupTab4( const KoKWHeaderFooter kwhf );
+
+ // dialog objects
+ QLineEdit *eHeadLeft;
+ QLineEdit *eHeadMid;
+ QLineEdit *eHeadRight;
+ QLineEdit *eFootLeft;
+ QLineEdit *eFootMid;
+ QLineEdit *eFootRight;
+
+ // layout
+ KoPageLayout m_layout;
+ KoColumns m_column;
+
+ KoUnit::Unit m_unit;
+
+ int flags;
+
+protected slots:
+ virtual void slotOk();
+
+private slots:
+ void sizeUpdated(KoPageLayout &layout);
+ void columnsUpdated(KoColumns &columns);
+
+private:
+ KoPageLayoutSize *m_pageSizeTab;
+ KoPageLayoutColumns *m_columnsTab;
+ KoPageLayoutHeader *m_headerTab;
+ KoPageLayoutDiaPrivate *d;
+};
+
+#endif
diff --git a/kexi/3rdparty/kolibs/koUnit.cc b/kexi/3rdparty/kolibs/koUnit.cc
new file mode 100644
index 000000000..6c19a29f3
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koUnit.cc
@@ -0,0 +1,219 @@
+/* This file is part of the KDE project
+ Copyright (C) 2001 David Faure <faure@kde.org>
+ Copyright (C) 2004, Nicolas GOUTTE <goutte@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+//#include <KoGlobal.h>
+#include "KoUnit.h"
+#ifndef SIMPLE_KOLIBS
+# include <KoXmlWriter.h>
+#endif
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <kdebug.h>
+
+#include <qregexp.h>
+#include <qdom.h>
+
+QStringList KoUnit::listOfUnitName()
+{
+ QStringList lst;
+ for ( uint i = 0 ; i <= KoUnit::U_LASTUNIT ; ++i )
+ {
+ KoUnit::Unit unit = static_cast<KoUnit::Unit>( i );
+ lst.append( KoUnit::unitDescription( unit ) );
+ }
+ return lst;
+}
+
+QString KoUnit::unitDescription( Unit _unit )
+{
+ switch ( _unit )
+ {
+ case KoUnit::U_MM:
+ return i18n("Millimeters (mm)");
+ case KoUnit::U_CM:
+ return i18n("Centimeters (cm)");
+ case KoUnit::U_DM:
+ return i18n("Decimeters (dm)");
+ case KoUnit::U_INCH:
+ return i18n("Inches (in)");
+ case KoUnit::U_PI:
+ return i18n("Pica (pi)");
+ case KoUnit::U_DD:
+ return i18n("Didot (dd)");
+ case KoUnit::U_CC:
+ return i18n("Cicero (cc)");
+ case KoUnit::U_PT:
+ return i18n("Points (pt)" );
+ default:
+ return i18n("Error!");
+ }
+}
+
+double KoUnit::toUserValue( double ptValue, Unit unit )
+{
+ switch ( unit ) {
+ case U_MM:
+ return toMM( ptValue );
+ case U_CM:
+ return toCM( ptValue );
+ case U_DM:
+ return toDM( ptValue );
+ case U_INCH:
+ return toInch( ptValue );
+ case U_PI:
+ return toPI( ptValue );
+ case U_DD:
+ return toDD( ptValue );
+ case U_CC:
+ return toCC( ptValue );
+ case U_PT:
+ default:
+ return toPoint( ptValue );
+ }
+}
+
+double KoUnit::ptToUnit( const double ptValue, const Unit unit )
+{
+ switch ( unit )
+ {
+ case U_MM:
+ return POINT_TO_MM( ptValue );
+ case U_CM:
+ return POINT_TO_CM( ptValue );
+ case U_DM:
+ return POINT_TO_DM( ptValue );
+ case U_INCH:
+ return POINT_TO_INCH( ptValue );
+ case U_PI:
+ return POINT_TO_PI( ptValue );
+ case U_DD:
+ return POINT_TO_DD( ptValue );
+ case U_CC:
+ return POINT_TO_CC( ptValue );
+ case U_PT:
+ default:
+ return ptValue;
+ }
+}
+
+QString KoUnit::toUserStringValue( double ptValue, Unit unit )
+{
+ return KGlobal::locale()->formatNumber( toUserValue( ptValue, unit ) );
+}
+
+double KoUnit::fromUserValue( double value, Unit unit )
+{
+ switch ( unit ) {
+ case U_MM:
+ return MM_TO_POINT( value );
+ case U_CM:
+ return CM_TO_POINT( value );
+ case U_DM:
+ return DM_TO_POINT( value );
+ case U_INCH:
+ return INCH_TO_POINT( value );
+ case U_PI:
+ return PI_TO_POINT( value );
+ case U_DD:
+ return DD_TO_POINT( value );
+ case U_CC:
+ return CC_TO_POINT( value );
+ case U_PT:
+ default:
+ return value;
+ }
+}
+
+double KoUnit::fromUserValue( const QString& value, Unit unit, bool* ok )
+{
+ return fromUserValue( KGlobal::locale()->readNumber( value, ok ), unit );
+}
+
+double KoUnit::parseValue( QString value, double defaultVal )
+{
+ value.simplifyWhiteSpace();
+ value.remove( ' ' );
+
+ if( value.isEmpty() )
+ return defaultVal;
+
+ int index = value.find( QRegExp( "[a-z]+$" ) );
+ if ( index == -1 )
+ return value.toDouble();
+
+ QString unit = value.mid( index );
+ value.truncate ( index );
+ double val = value.toDouble();
+
+ if ( unit == "pt" )
+ return val;
+
+ bool ok;
+ Unit u = KoUnit::unit( unit, &ok );
+ if( ok )
+ return fromUserValue( val, u );
+
+ if( unit == "m" )
+ return fromUserValue( val * 10.0, U_DM );
+ else if( unit == "km" )
+ return fromUserValue( val * 10000.0, U_DM );
+ kdWarning() << "KoUnit::parseValue: Unit " << unit << " is not supported, please report." << endl;
+
+ // TODO : add support for mi/ft ?
+ return defaultVal;
+}
+
+KoUnit::Unit KoUnit::unit( const QString &_unitName, bool* ok )
+{
+ if ( ok )
+ *ok = true;
+ if ( _unitName == QString::fromLatin1( "mm" ) ) return U_MM;
+ if ( _unitName == QString::fromLatin1( "cm" ) ) return U_CM;
+ if ( _unitName == QString::fromLatin1( "dm" ) ) return U_DM;
+ if ( _unitName == QString::fromLatin1( "in" )
+ || _unitName == QString::fromLatin1("inch") /*compat*/ ) return U_INCH;
+ if ( _unitName == QString::fromLatin1( "pi" ) ) return U_PI;
+ if ( _unitName == QString::fromLatin1( "dd" ) ) return U_DD;
+ if ( _unitName == QString::fromLatin1( "cc" ) ) return U_CC;
+ if ( _unitName == QString::fromLatin1( "pt" ) ) return U_PT;
+ if ( ok )
+ *ok = false;
+ return U_PT;
+}
+
+QString KoUnit::unitName( Unit _unit )
+{
+ if ( _unit == U_MM ) return QString::fromLatin1( "mm" );
+ if ( _unit == U_CM ) return QString::fromLatin1( "cm" );
+ if ( _unit == U_DM ) return QString::fromLatin1( "dm" );
+ if ( _unit == U_INCH ) return QString::fromLatin1( "in" );
+ if ( _unit == U_PI ) return QString::fromLatin1( "pi" );
+ if ( _unit == U_DD ) return QString::fromLatin1( "dd" );
+ if ( _unit == U_CC ) return QString::fromLatin1( "cc" );
+ return QString::fromLatin1( "pt" );
+}
+
+#ifndef SIMPLE_KOLIBS
+void KoUnit::saveOasis(KoXmlWriter* settingsWriter, Unit _unit)
+{
+ settingsWriter->addConfigItem( "unit", unitName(_unit) );
+}
+#endif
diff --git a/kexi/3rdparty/kolibs/koUnit.h b/kexi/3rdparty/kolibs/koUnit.h
new file mode 100644
index 000000000..86f749d64
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koUnit.h
@@ -0,0 +1,177 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>, Torben Weis <weis@kde.org>
+ Copyright (C) 2004, Nicolas GOUTTE <goutte@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef kounit_h
+#define kounit_h
+#include <qstring.h>
+#include <qstringlist.h>
+#include <math.h> // for floor
+#include <koffice_export.h>
+
+class KoXmlWriter;
+class QDomElement;
+
+// 1 inch ^= 72 pt
+// 1 inch ^= 25.399956 mm (-pedantic ;p)
+// 1 pt = 1/12 pi
+// 1 pt ^= 0.0077880997 cc
+// 1 cc = 12 dd
+// Note: I don't use division but multiplication with the inverse value
+// because it's faster ;p (Werner)
+#define POINT_TO_MM(px) ((px)*0.352777167)
+#define MM_TO_POINT(mm) ((mm)*2.83465058)
+#define POINT_TO_CM(px) ((px)*0.0352777167)
+#define CM_TO_POINT(cm) ((cm)*28.3465058)
+#define POINT_TO_DM(px) ((px)*0.00352777167)
+#define DM_TO_POINT(dm) ((dm)*283.465058)
+#define POINT_TO_INCH(px) ((px)*0.01388888888889)
+#define INCH_TO_POINT(inch) ((inch)*72.0)
+#define MM_TO_INCH(mm) ((mm)*0.039370147)
+#define INCH_TO_MM(inch) ((inch)*25.399956)
+#define POINT_TO_PI(px)((px)*0.083333333)
+#define POINT_TO_DD(px)((px)*0.006490083)
+#define POINT_TO_CC(px)((px)*0.077880997)
+#define PI_TO_POINT(pi)((pi)*12)
+#define DD_TO_POINT(dd)((dd)*154.08124)
+#define CC_TO_POINT(cc)((cc)*12.840103)
+/**
+ * %KOffice stores everything in pt (using "double") internally.
+ * When displaying a value to the user, the value is converted to the user's unit
+ * of choice, and rounded to a reasonable precision to avoid 0.999999
+ */
+class KOFFICECORE_EXPORT KoUnit
+{
+public:
+ /** Length units supported by KOffice. */
+ enum Unit {
+ U_MM = 0,
+ U_PT = 1,
+ U_INCH = 2,
+ U_CM = 3,
+ U_DM = 4,
+ U_PI = 5, // pica
+ U_DD = 6, // didot
+ U_CC = 7, // cicero
+ U_LASTUNIT = U_CC // update when adding a new unit
+ // when adding a new unit, make sure to implement support for it in koRuler too
+ };
+
+ /// Prepare ptValue to be displayed in pt
+ static double toPoint( double ptValue ) {
+ // No conversion, only rounding (to 0.001 precision)
+ return floor( ptValue * 1000.0 ) / 1000.0;
+ }
+
+ /// Prepare ptValue to be displayed in mm
+ static double toMM( double ptValue ) {
+ // "mm" values are rounded to 0.0001 millimeters
+ return floor( POINT_TO_MM( ptValue ) * 10000.0 ) / 10000.0;
+ }
+
+ /// Prepare ptValue to be displayed in cm
+ static double toCM( double ptValue ) {
+ return floor( POINT_TO_CM( ptValue ) * 10000.0 ) / 10000.0;
+ }
+
+ /// Prepare ptValue to be displayed in dm
+ static double toDM( double ptValue ) {
+ return floor( POINT_TO_DM( ptValue ) * 10000.0 ) / 10000.0;
+ }
+
+ /// Prepare ptValue to be displayed in inch
+ static double toInch( double ptValue ) {
+ // "in" values are rounded to 0.00001 inches
+ return floor( POINT_TO_INCH( ptValue ) * 100000.0 ) / 100000.0;
+ }
+
+ /// Prepare ptValue to be displayed in pica
+ static double toPI( double ptValue ) {
+ // "pi" values are rounded to 0.00001 inches
+ return floor( POINT_TO_PI( ptValue ) * 100000.0 ) / 100000.0;
+ }
+
+ /// Prepare ptValue to be displayed in didot
+ static double toDD( double ptValue ) {
+ // "dd" values are rounded to 0.00001 inches
+ return floor( POINT_TO_DD( ptValue ) * 100000.0 ) / 100000.0;
+ }
+
+ /// Prepare ptValue to be displayed in cicero
+ static double toCC( double ptValue ) {
+ // "cc" values are rounded to 0.00001 inches
+ return floor( POINT_TO_CC( ptValue ) * 100000.0 ) / 100000.0;
+ }
+
+ /**
+ * This method is the one to use to display a value in a dialog
+ * \return the value @p ptValue converted to @p unit and rounded, ready to be displayed
+ * Old name: ptToUnit
+ */
+ static double toUserValue( double ptValue, Unit unit );
+
+ /**
+ * Convert the value @p ptValue to a given unit @p unit
+ * Unlike KoUnit::ptToUnit the return value remains unrounded, so that it can be used in complex calculation
+ * \return the converted value
+ * Old name: ptToUnitUnrounded
+ */
+ static double ptToUnit( const double ptValue, const Unit unit );
+
+ /// This method is the one to use to display a value in a dialog
+ /// @return the value @p ptValue converted to @p unit and rounded, ready to be displayed
+ /// Old name: userValue
+ static QString toUserStringValue( double ptValue, Unit unit );
+
+ /// This method is the one to use to read a value from a dialog
+ /// @return the value in @p unit, converted to points for internal use
+ /// Old name: ptFromUnit
+ static double fromUserValue( double value, Unit unit );
+
+ /// This method is the one to use to read a value from a dialog
+ /// @param value value entered by the user
+ /// @param unit unit type selected by the user
+ /// @param ok if set, the pointed bool is set to true if the value could be
+ /// converted to a double, and to false otherwise.
+ /// @return the value in @p unit, converted to points for internal use
+ static double fromUserValue( const QString& value, Unit unit, bool* ok = 0 );
+
+ /// Convert a unit name into a Unit enum
+ /// @param _unitName name to convert
+ /// @param ok if set, it will be true if the unit was known, false if unknown
+ static Unit unit( const QString &_unitName, bool* ok = 0 );
+ /// Get the name of a unit
+ static QString unitName( Unit _unit );
+ /// Get the full (translated) description of a unit
+ static QString unitDescription( Unit _unit );
+ static QStringList listOfUnitName();
+
+ /// parse common %KOffice and OO values, like "10cm", "5mm" to pt
+ static double parseValue( QString value, double defaultVal = 0.0 );
+ // Note: the above method doesn't take a const ref, since it modifies the arg.
+
+#ifndef SIMPLE_KOLIBS
+ /// Save a unit in OASIS format
+ static void saveOasis(KoXmlWriter* settingsWriter, Unit _unit);
+#endif
+
+};
+
+
+#endif
diff --git a/kexi/3rdparty/kolibs/koUnitWidgets.cc b/kexi/3rdparty/kolibs/koUnitWidgets.cc
new file mode 100644
index 000000000..0472caf53
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koUnitWidgets.cc
@@ -0,0 +1,454 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, Rob Buis(buis@kde.org)
+ Copyright (C) 2004, Nicolas GOUTTE <goutte@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "koUnitWidgets.moc"
+#include <kdebug.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+
+
+// ----------------------------------------------------------------
+// Support classes
+
+
+KoUnitDoubleValidator::KoUnitDoubleValidator( KoUnitDoubleBase *base, QObject *parent, const char *name )
+: KDoubleValidator( parent, name ), m_base( base )
+{
+}
+
+QValidator::State
+KoUnitDoubleValidator::validate( QString &s, int &pos ) const
+{
+
+ kdDebug(30004) << "KoUnitDoubleValidator::validate : " << s << " at " << pos << endl;
+ QValidator::State result = Acceptable;
+
+ QRegExp regexp ("([ a-zA-Z]+)$"); // Letters or spaces at end
+ const int res = regexp.search( s );
+
+ if ( res == -1 )
+ {
+ // Nothing like an unit? The user is probably editing the unit
+ kdDebug(30004) << "Intermediate (no unit)" << endl;
+ return Intermediate;
+ }
+
+ // ### TODO: are all the QString::stripWhiteSpace really necessary?
+ const QString number ( s.left( res ).stripWhiteSpace() );
+ const QString unitName ( regexp.cap( 1 ).stripWhiteSpace().lower() );
+
+ kdDebug(30004) << "Split:" << number << ":" << unitName << ":" << endl;
+
+ bool ok = false;
+ const double value = m_base->toDouble( number, &ok );
+ double newVal = 0.0;
+ if( ok )
+ {
+ KoUnit::Unit unit = KoUnit::unit( unitName, &ok );
+ if ( ok )
+ newVal = KoUnit::fromUserValue( value, unit );
+ else
+ {
+ // Probably the user is trying to edit the unit
+ kdDebug(30004) << "Intermediate (unknown unit)" << endl;
+ return Intermediate;
+ }
+ }
+ else
+ {
+ kdWarning(30004) << "Not a number: " << number << endl;
+ return Invalid;
+ }
+
+ newVal = KoUnit::ptToUnit( newVal, m_base->m_unit );
+
+ s = m_base->getVisibleText( newVal );
+
+ return result;
+}
+
+
+QString KoUnitDoubleBase::getVisibleText( double value ) const
+{
+ const QString num ( QString( "%1%2").arg( KGlobal::locale()->formatNumber( value, m_precision ), KoUnit::unitName( m_unit ) ) );
+ kdDebug(30004) << "getVisibleText: " << QString::number( value, 'f', 12 ) << " => " << num << endl;
+ return num;
+}
+
+double KoUnitDoubleBase::toDouble( const QString& str, bool* ok ) const
+{
+ QString str2( str );
+ /* KLocale::readNumber wants the thousand separator exactly at 1000.
+ But when editing, it might be anywhere. So we need to remove it. */
+ const QString sep( KGlobal::locale()->thousandsSeparator() );
+ if ( !sep.isEmpty() )
+ str2.remove( sep );
+ str2.remove( KoUnit::unitName( m_unit ) );
+ const double dbl = KGlobal::locale()->readNumber( str2, ok );
+ if ( ok )
+ kdDebug(30004) << "toDouble:" << str << ": => :" << str2 << ": => " << QString::number( dbl, 'f', 12 ) << endl;
+ else
+ kdWarning(30004) << "toDouble error:" << str << ": => :" << str2 << ":" << endl;
+ return dbl;
+}
+
+
+// ----------------------------------------------------------------
+// Widget classes
+
+
+KoUnitDoubleSpinBox::KoUnitDoubleSpinBox( QWidget *parent, const char *name )
+ : KDoubleSpinBox( parent, name ), KoUnitDoubleBase( KoUnit::U_PT, 2 )
+ , m_lowerInPoints( -9999 )
+ , m_upperInPoints( 9999 )
+ , m_stepInPoints( 1 )
+{
+ KDoubleSpinBox::setPrecision( 2 );
+ m_validator = new KoUnitDoubleValidator( this, this );
+ QSpinBox::setValidator( m_validator );
+ setAcceptLocalizedNumbers( true );
+ setUnit( KoUnit::U_PT );
+
+ connect(this, SIGNAL(valueChanged( double )), SLOT(privateValueChanged()));
+}
+
+
+KoUnitDoubleSpinBox::KoUnitDoubleSpinBox( QWidget *parent,
+ double lower, double upper,
+ double step,
+ double value,
+ KoUnit::Unit unit,
+ unsigned int precision,
+ const char *name )
+ : KDoubleSpinBox( lower, upper, step, value, precision, parent, name ),
+ KoUnitDoubleBase( unit, precision ),
+ m_lowerInPoints( lower ), m_upperInPoints( upper ), m_stepInPoints( step )
+{
+ m_unit = KoUnit::U_PT;
+ m_validator = new KoUnitDoubleValidator( this, this );
+ QSpinBox::setValidator( m_validator );
+ setAcceptLocalizedNumbers( true );
+ setUnit( unit );
+ changeValue( value );
+ setLineStep( 0.5 );
+
+ connect(this, SIGNAL(valueChanged( double )), SLOT(privateValueChanged()));
+}
+
+void
+KoUnitDoubleSpinBox::changeValue( double val )
+{
+ KDoubleSpinBox::setValue( KoUnit::toUserValue( val, m_unit ) );
+ // TODO: emit valueChanged ONLY if the value was out-of-bounds
+ // This will allow the 'user' dialog to set a dirty bool and ensure
+ // a proper value is getting saved.
+}
+
+void KoUnitDoubleSpinBox::privateValueChanged() {
+ emit valueChangedPt( value () );
+}
+
+void
+KoUnitDoubleSpinBox::setUnit( KoUnit::Unit unit )
+{
+ double oldvalue = KoUnit::fromUserValue( KDoubleSpinBox::value(), m_unit );
+ KDoubleSpinBox::setMinValue( KoUnit::toUserValue( m_lowerInPoints, unit ) );
+ KDoubleSpinBox::setMaxValue( KoUnit::toUserValue( m_upperInPoints, unit ) );
+ KDoubleSpinBox::setLineStep( KoUnit::toUserValue( m_stepInPoints, unit ) );
+ KDoubleSpinBox::setValue( KoUnit::ptToUnit( oldvalue, unit ) );
+ m_unit = unit;
+ setSuffix( KoUnit::unitName( unit ).prepend( ' ' ) );
+}
+
+double KoUnitDoubleSpinBox::value( void ) const
+{
+ return KoUnit::fromUserValue( KDoubleSpinBox::value(), m_unit );
+}
+
+void KoUnitDoubleSpinBox::setMinValue( double min )
+{
+ m_lowerInPoints = min;
+ KDoubleSpinBox::setMinValue( KoUnit::toUserValue( m_lowerInPoints, m_unit ) );
+}
+
+void KoUnitDoubleSpinBox::setMaxValue( double max )
+{
+ m_upperInPoints = max;
+ KDoubleSpinBox::setMaxValue( KoUnit::toUserValue( m_upperInPoints, m_unit ) );
+}
+
+void KoUnitDoubleSpinBox::setLineStep( double step )
+{
+ m_stepInPoints = KoUnit::toUserValue(step, KoUnit::U_PT );
+ KDoubleSpinBox::setLineStep( step );
+}
+
+void KoUnitDoubleSpinBox::setLineStepPt( double step )
+{
+ m_stepInPoints = step;
+ KDoubleSpinBox::setLineStep( KoUnit::toUserValue( m_stepInPoints, m_unit ) );
+}
+
+void KoUnitDoubleSpinBox::setMinMaxStep( double min, double max, double step )
+{
+ setMinValue( min );
+ setMaxValue( max );
+ setLineStepPt( step );
+}
+
+// ----------------------------------------------------------------
+
+
+KoUnitDoubleLineEdit::KoUnitDoubleLineEdit( QWidget *parent, const char *name )
+ : KLineEdit( parent, name ), KoUnitDoubleBase( KoUnit::U_PT, 2 ), m_value( 0.0 ), m_lower( 0.0 ), m_upper( 9999.99 ),
+ m_lowerInPoints( 0.0 ), m_upperInPoints( 9999.99 )
+{
+ setAlignment( Qt::AlignRight );
+ m_validator = new KoUnitDoubleValidator( this, this );
+ setValidator( m_validator );
+ setUnit( KoUnit::U_PT );
+ changeValue( KoUnit::ptToUnit( 0.0, KoUnit::U_PT ) );
+}
+
+KoUnitDoubleLineEdit::KoUnitDoubleLineEdit( QWidget *parent, double lower, double upper, double value, KoUnit::Unit unit,
+ unsigned int precision, const char *name )
+ : KLineEdit( parent, name ), KoUnitDoubleBase( unit, precision ), m_value( value ), m_lower( lower ), m_upper( upper ),
+ m_lowerInPoints( lower ), m_upperInPoints( upper )
+{
+ setAlignment( Qt::AlignRight );
+ m_validator = new KoUnitDoubleValidator( this, this );
+ setValidator( m_validator );
+ setUnit( unit );
+ changeValue( KoUnit::ptToUnit( value, unit ) );
+}
+
+void
+KoUnitDoubleLineEdit::changeValue( double value )
+{
+ m_value = value < m_lower ? m_lower : ( value > m_upper ? m_upper : value );
+ setText( getVisibleText( m_value ) );
+}
+
+void
+KoUnitDoubleLineEdit::setUnit( KoUnit::Unit unit )
+{
+ KoUnit::Unit old = m_unit;
+ m_unit = unit;
+ m_lower = KoUnit::ptToUnit( m_lowerInPoints, unit );
+ m_upper = KoUnit::ptToUnit( m_upperInPoints, unit );
+ changeValue( KoUnit::ptToUnit( KoUnit::fromUserValue( m_value, old ), unit ) );
+}
+
+bool
+KoUnitDoubleLineEdit::eventFilter( QObject* o, QEvent* ev )
+{
+#if 0
+ if( ev->type() == QEvent::FocusOut || ev->type() == QEvent::Leave || ev->type() == QEvent::Hide )
+ {
+ bool ok;
+ double value = toDouble( text(), &ok );
+ changeValue( value );
+ return false;
+ }
+ else
+#endif
+ return QLineEdit::eventFilter( o, ev );
+}
+
+double KoUnitDoubleLineEdit::value( void ) const
+{
+ return KoUnit::fromUserValue( m_value, m_unit );
+}
+
+
+// ----------------------------------------------------------------
+
+
+KoUnitDoubleComboBox::KoUnitDoubleComboBox( QWidget *parent, const char *name )
+ : KComboBox( true, parent, name ), KoUnitDoubleBase( KoUnit::U_PT, 2 ), m_value( 0.0 ), m_lower( 0.0 ), m_upper( 9999.99 ), m_lowerInPoints( 0.0 ), m_upperInPoints( 9999.99 )
+{
+ lineEdit()->setAlignment( Qt::AlignRight );
+ m_validator = new KoUnitDoubleValidator( this, this );
+ lineEdit()->setValidator( m_validator );
+ setUnit( KoUnit::U_PT );
+ changeValue( KoUnit::ptToUnit( 0.0, KoUnit::U_PT ) );
+ connect( this, SIGNAL( activated( int ) ), this, SLOT( slotActivated( int ) ) );
+}
+
+KoUnitDoubleComboBox::KoUnitDoubleComboBox( QWidget *parent, double lower, double upper, double value, KoUnit::Unit unit,
+ unsigned int precision, const char *name )
+ : KComboBox( true, parent, name ), KoUnitDoubleBase( unit, precision ), m_value( value ), m_lower( lower ), m_upper( upper ),
+ m_lowerInPoints( lower ), m_upperInPoints( upper )
+{
+ lineEdit()->setAlignment( Qt::AlignRight );
+ m_validator = new KoUnitDoubleValidator( this, this );
+ lineEdit()->setValidator( m_validator );
+ setUnit( unit );
+ changeValue( KoUnit::ptToUnit( value, unit ) );
+ connect( this, SIGNAL( activated( int ) ), this, SLOT( slotActivated( int ) ) );
+}
+
+void
+KoUnitDoubleComboBox::changeValue( double value )
+{
+ QString oldLabel = lineEdit()->text();
+ updateValue( value );
+ if( lineEdit()->text() != oldLabel )
+ emit valueChanged( m_value );
+}
+
+void
+KoUnitDoubleComboBox::updateValue( double value )
+{
+ m_value = value < m_lower ? m_lower : ( value > m_upper ? m_upper : value );
+ lineEdit()->setText( getVisibleText( m_value ) );
+}
+
+void
+KoUnitDoubleComboBox::insertItem( double value, int index )
+{
+ KComboBox::insertItem( getVisibleText( value ), index );
+}
+
+void
+KoUnitDoubleComboBox::slotActivated( int index )
+{
+ double oldvalue = m_value;
+ bool ok;
+ double value = toDouble( text( index ), &ok );
+ m_value = value < m_lower ? m_lower : ( value > m_upper ? m_upper : value );
+ if( m_value != oldvalue )
+ emit valueChanged( m_value );
+}
+
+void
+KoUnitDoubleComboBox::setUnit( KoUnit::Unit unit )
+{
+ KoUnit::Unit old = m_unit;
+ m_unit = unit;
+ m_lower = KoUnit::ptToUnit( m_lowerInPoints, unit );
+ m_upper = KoUnit::ptToUnit( m_upperInPoints, unit );
+ changeValue( KoUnit::ptToUnit( KoUnit::fromUserValue( m_value, old ), unit ) );
+}
+
+bool
+KoUnitDoubleComboBox::eventFilter( QObject* o, QEvent* ev )
+{
+#if 0
+ if( ev->type() == QEvent::FocusOut || ev->type() == QEvent::Leave || ev->type() == QEvent::Hide )
+ {
+ bool ok;
+ double value = toDouble( lineEdit()->text(), &ok );
+ changeValue( value );
+ return false;
+ }
+ else
+#endif
+ return QComboBox::eventFilter( o, ev );
+}
+
+double KoUnitDoubleComboBox::value( void ) const
+{
+ return KoUnit::fromUserValue( m_value, m_unit );
+}
+
+
+// ----------------------------------------------------------------
+
+
+KoUnitDoubleSpinComboBox::KoUnitDoubleSpinComboBox( QWidget *parent, const char *name )
+ : QWidget( parent ), m_step( 1.0 )
+{
+ QGridLayout *layout = new QGridLayout( this, 2, 3 );
+ //layout->setMargin( 2 );
+ QPushButton *up = new QPushButton( "+", this );
+ //up->setFlat( true );
+ up->setMaximumHeight( 15 );
+ up->setMaximumWidth( 15 );
+ layout->addWidget( up, 0, 0 );
+ connect( up, SIGNAL( clicked() ), this, SLOT( slotUpClicked() ) );
+
+ QPushButton *down = new QPushButton( "-", this );
+ down->setMaximumHeight( 15 );
+ down->setMaximumWidth( 15 );
+ layout->addWidget( down, 1, 0 );
+ connect( down, SIGNAL( clicked() ), this, SLOT( slotDownClicked() ) );
+
+ m_combo = new KoUnitDoubleComboBox( this, KoUnit::ptToUnit( 0.0, KoUnit::U_PT ), KoUnit::ptToUnit( 9999.99, KoUnit::U_PT ), 0.0, KoUnit::U_PT, 2, name );
+ connect( m_combo, SIGNAL( valueChanged( double ) ), this, SIGNAL( valueChanged( double ) ) );
+ layout->addMultiCellWidget( m_combo, 0, 1, 2, 2 );
+}
+
+KoUnitDoubleSpinComboBox::KoUnitDoubleSpinComboBox( QWidget *parent, double lower, double upper, double step, double value,
+ KoUnit::Unit unit, unsigned int precision, const char *name )
+ : QWidget( parent ), m_step( step )//, m_lowerInPoints( lower ), m_upperInPoints( upper )
+{
+ QGridLayout *layout = new QGridLayout( this, 2, 3 );
+ //layout->setMargin( 2 );
+ QPushButton *up = new QPushButton( "+", this );
+ //up->setFlat( true );
+ up->setMaximumHeight( 15 );
+ up->setMaximumWidth( 15 );
+ layout->addWidget( up, 0, 0 );
+ connect( up, SIGNAL( clicked() ), this, SLOT( slotUpClicked() ) );
+
+ QPushButton *down = new QPushButton( "-", this );
+ down->setMaximumHeight( 15 );
+ down->setMaximumWidth( 15 );
+ layout->addWidget( down, 1, 0 );
+ connect( down, SIGNAL( clicked() ), this, SLOT( slotDownClicked() ) );
+
+ m_combo = new KoUnitDoubleComboBox( this, KoUnit::ptToUnit( lower, unit ), KoUnit::ptToUnit( upper, unit ), value, unit, precision, name );
+ connect( m_combo, SIGNAL( valueChanged( double ) ), this, SIGNAL( valueChanged( double ) ) );
+ layout->addMultiCellWidget( m_combo, 0, 1, 2, 2 );
+}
+
+void
+KoUnitDoubleSpinComboBox::slotUpClicked()
+{
+ m_combo->changeValue( m_combo->value() + m_step );
+}
+
+void
+KoUnitDoubleSpinComboBox::slotDownClicked()
+{
+ m_combo->changeValue( m_combo->value() - m_step );
+}
+
+void
+KoUnitDoubleSpinComboBox::insertItem( double value, int index )
+{
+ m_combo->insertItem( value, index );
+}
+
+void
+KoUnitDoubleSpinComboBox::updateValue( double value )
+{
+ m_combo->updateValue( value );
+}
+
+double
+KoUnitDoubleSpinComboBox::value() const
+{
+ return m_combo->value();
+}
+
diff --git a/kexi/3rdparty/kolibs/koUnitWidgets.h b/kexi/3rdparty/kolibs/koUnitWidgets.h
new file mode 100644
index 000000000..957b1b36d
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koUnitWidgets.h
@@ -0,0 +1,246 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, Rob Buis(buis@kde.org)
+ Copyright (C) 2004, Nicolas GOUTTE <goutte@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef __KOUNITWIDGETS_H__
+#define __KOUNITWIDGETS_H__
+
+#include <knuminput.h>
+#include <knumvalidator.h>
+#include <klineedit.h>
+#include <kcombobox.h>
+#include <KoUnit.h>
+#include <koffice_export.h>
+
+
+// ----------------------------------------------------------------
+// Support classes
+
+
+class KoUnitDoubleBase;
+
+// ### TODO: put it out of the public header file (if possible)
+/**
+ * Validator for the unit widget classes
+ * \internal
+ * \since 1.4 (change of behavior)
+ */
+class KOFFICEUI_EXPORT KoUnitDoubleValidator : public KDoubleValidator
+{
+public:
+ KoUnitDoubleValidator( KoUnitDoubleBase *base, QObject *parent, const char *name = 0 );
+
+ virtual QValidator::State validate( QString &, int & ) const;
+
+private:
+ KoUnitDoubleBase *m_base;
+};
+
+
+/**
+ * Base for the unit widgets
+ * \since 1.4 (change of behavior)
+ */
+class KOFFICEUI_EXPORT KoUnitDoubleBase
+{
+public:
+ KoUnitDoubleBase( KoUnit::Unit unit, unsigned int precision ) : m_unit( unit ), m_precision( precision ) {}
+ virtual ~KoUnitDoubleBase() {}
+
+ virtual void changeValue( double ) = 0;
+ virtual void setUnit( KoUnit::Unit = KoUnit::U_PT ) = 0;
+
+ void setValueInUnit( double value, KoUnit::Unit unit )
+ {
+ changeValue( KoUnit::ptToUnit( KoUnit::fromUserValue( value, unit ), m_unit ) );
+ }
+
+ void setPrecision( unsigned int precision ) { m_precision = precision; };
+
+protected:
+ friend class KoUnitDoubleValidator;
+ /**
+ * Transform the double in a nice text, using locale symbols
+ * @param value the number as double
+ * @return the resulting string
+ */
+ QString getVisibleText( double value ) const;
+ /**
+ * Transfrom a string into a double, while taking care of locale specific symbols.
+ * @param str the string to transform into a number
+ * @param ok true, if the conversion was succesful
+ * @return the value as double
+ */
+ double toDouble( const QString& str, bool* ok ) const;
+
+protected:
+ KoUnitDoubleValidator *m_validator;
+ KoUnit::Unit m_unit;
+ unsigned int m_precision;
+};
+
+
+// ----------------------------------------------------------------
+// Widget classes
+
+
+/**
+ * Spin box for double precision numbers with unit display
+ * \since 1.4 (change of behavior)
+ */
+class KOFFICEUI_EXPORT KoUnitDoubleSpinBox : public KDoubleSpinBox, public KoUnitDoubleBase
+{
+ Q_OBJECT
+public:
+ KoUnitDoubleSpinBox( QWidget *parent = 0L, const char *name = 0L );
+ // lower, upper, step and value are in pt
+ KoUnitDoubleSpinBox( QWidget *parent, double lower, double upper, double step, double value = 0.0,
+ KoUnit::Unit unit = KoUnit::U_PT, unsigned int precision = 2, const char *name = 0 );
+ // added so the class can be used in .ui files(by Tymoteusz Majewski, maju7@o2.pl)
+ virtual void changeValue( double );
+ virtual void setUnit( KoUnit::Unit = KoUnit::U_PT );
+
+ /// @return the current value, converted in points
+ double value( void ) const;
+
+ /// Set minimum value in points.
+ void setMinValue(double min);
+
+ /// Set maximum value in points.
+ void setMaxValue(double max);
+
+ /// Set step size in the current unit.
+ void setLineStep(double step);
+
+ /// Set step size in points.
+ void setLineStepPt(double step);
+
+ /// Set minimum, maximum value and the step size (all in points) (by Tymoteusz Majewski, maju7@o2.pl)
+ void setMinMaxStep( double min, double max, double step );
+
+signals:
+ /// emitted like valueChanged in the parent, but this one emits the point value
+ void valueChangedPt( double );
+
+
+private:
+ double m_lowerInPoints; ///< lowest value in points
+ double m_upperInPoints; ///< highest value in points
+ double m_stepInPoints; ///< step in points
+
+private slots:
+ // exists to do emits for valueChangedPt
+ void privateValueChanged();
+};
+
+
+/**
+ * Line edit for double precision numbers with unit display
+ * \since 1.4 (change of behavior)
+ */
+class KOFFICEUI_EXPORT KoUnitDoubleLineEdit : public KLineEdit, public KoUnitDoubleBase
+{
+ Q_OBJECT
+public:
+ KoUnitDoubleLineEdit( QWidget *parent = 0L, const char *name = 0L );
+ KoUnitDoubleLineEdit( QWidget *parent, double lower, double upper, double value = 0.0, KoUnit::Unit unit = KoUnit::U_PT, unsigned int precision = 2, const char *name = 0 );
+
+ virtual void changeValue( double );
+ virtual void setUnit( KoUnit::Unit = KoUnit::U_PT );
+
+ /// @return the current value, converted in points
+ double value( void ) const;
+
+protected:
+ bool eventFilter( QObject* obj, QEvent* ev );
+
+private:
+ double m_value;
+ double m_lower;
+ double m_upper;
+ double m_lowerInPoints; ///< lowest value in points
+ double m_upperInPoints; ///< highest value in points
+};
+
+/**
+ * Combo box for double precision numbers with unit display
+ * \since 1.4 (change of behavior)
+ */
+class KOFFICEUI_EXPORT KoUnitDoubleComboBox : public KComboBox, public KoUnitDoubleBase
+{
+ Q_OBJECT
+public:
+ KoUnitDoubleComboBox( QWidget *parent = 0L, const char *name = 0L );
+ KoUnitDoubleComboBox( QWidget *parent, double lower, double upper, double value = 0.0, KoUnit::Unit unit = KoUnit::U_PT, unsigned int precision = 2, const char *name = 0 );
+
+ virtual void changeValue( double );
+ void updateValue( double );
+ virtual void setUnit( KoUnit::Unit = KoUnit::U_PT );
+
+ /// @return the current value, converted in points
+ double value( void ) const;
+ void insertItem( double, int index = -1 );
+
+protected:
+ bool eventFilter( QObject* obj, QEvent* ev );
+
+signals:
+ void valueChanged(double);
+
+private slots:
+ void slotActivated( int );
+
+protected:
+ double m_value;
+ double m_lower;
+ double m_upper;
+ double m_lowerInPoints; ///< lowest value in points
+ double m_upperInPoints; ///< highest value in points
+};
+
+/**
+ * Combo box (with spin control) for double precision numbers with unit display
+ * \since 1.4 (change of behavior)
+ */
+class KOFFICEUI_EXPORT KoUnitDoubleSpinComboBox : public QWidget
+{
+ Q_OBJECT
+public:
+ KoUnitDoubleSpinComboBox( QWidget *parent = 0L, const char *name = 0L );
+ KoUnitDoubleSpinComboBox( QWidget *parent, double lower, double upper, double step, double value = 0.0, KoUnit::Unit unit = KoUnit::U_PT, unsigned int precision = 2, const char *name = 0 );
+
+ void insertItem( double, int index = -1 );
+ void updateValue( double );
+ /// @return the current value, converted in points
+ double value( void ) const;
+
+signals:
+ void valueChanged(double);
+
+private slots:
+ void slotUpClicked();
+ void slotDownClicked();
+
+private:
+ KoUnitDoubleComboBox *m_combo;
+ double m_step;
+};
+
+#endif
+
diff --git a/kexi/3rdparty/kolibs/koffice_export.h b/kexi/3rdparty/kolibs/koffice_export.h
new file mode 100644
index 000000000..08b660ce6
--- /dev/null
+++ b/kexi/3rdparty/kolibs/koffice_export.h
@@ -0,0 +1,2 @@
+
+/* private code: no need to export anything */
diff --git a/kexi/3rdparty/uuid/ChangeLog b/kexi/3rdparty/uuid/ChangeLog
new file mode 100644
index 000000000..db8b00cd6
--- /dev/null
+++ b/kexi/3rdparty/uuid/ChangeLog
@@ -0,0 +1,435 @@
+2003-07-25 Theodore Ts'o <tytso@mit.edu>
+
+ * Release of E2fsprogs 1.34
+
+2003-04-21 Theodore Ts'o <tytso@mit.edu>
+
+ * Release of E2fsprogs 1.33
+
+2003-04-21 Theodore Ts'o <tytso@mit.edu>
+
+ * Makefile.in: Use DYLD_LIBRAY_PATH so that "make check" works on
+ Darwin systems when building with shared libraries.
+
+2003-04-12 Theodore Ts'o <tytso@mit.edu>
+
+ * gen_uuid.c: Add #ifdef checks around #include <sys/ioctl.h> and
+ <sys/socket.h>.
+
+2003-04-03 Theodore Ts'o <tytso@mit.edu>
+
+ * gen_uuid.c (get_random_bytes): Always xor in a stream of bytes
+ from the system PRNG (i.e., random/srandom, seeded from
+ the time, pid, and uid) in case /dev/random isn't doing
+ the right thing on a particular system. It doesn't hurt,
+ and it can help, in the case of a buggy /dev/random.
+
+2003-03-14 Theodore Ts'o <tytso@mit.edu>
+
+ * Makefile.in: Add support for Apple Darwin
+
+2003-03-06 Theodore Tso <tytso@mit.edu>
+
+ * uuid_types.h.in: Don't redefine types if other e2fsprogs
+ *_types.h files have been included already.
+
+ * Makefile.in (tst_uuid): Link against the static library instead
+ of all of the object files, so that we automatically pick
+ up -lsocket under Solaris.
+
+2003-03-02 Theodore Ts'o <tytso@mit.edu>
+
+ * Makefile.in, uuidP.h, uuid_types.h.in: Use uuid_types.h instead
+ of ext2_types.h
+
+2002-11-09 Theodore Ts'o <tytso@mit.edu>
+
+ * Release of E2fsprogs 1.32
+
+2002-11-08 Theodore Ts'o <tytso@mit.edu>
+
+ * Release of E2fsprogs 1.31
+
+2002-10-31 Theodore Ts'o <tytso@mit.edu>
+
+ * Release of E2fsprogs 1.30
+
+2002-10-31 Theodore Ts'o <tytso@mit.edu>
+
+ * gen_uuid.c (get_random_bytes): Don't spin forever if read()
+ returns EINTR or EAGAIN, so that when /dev/random is
+ opened O_NONBLOCK, we don't end up spinning forever.
+
+2001-09-24 Theodore Tso <tytso@mit.edu>
+
+ * Release of E2fsprogs 1.29
+
+2001-08-31 Theodore Tso <tytso@thunk.org>
+
+ * Release of E2fsprogs 1.28
+
+2002-07-15 Theodore Ts'o <tytso@mit.edu>
+
+ * parse.c (uuid_parse): Fix uuid parsing bug which didn't complain
+ for certain types of invalid input text. (Addresses
+ Debian bug #152891).
+
+ * tst_uuid.c: Add test cases for invalid text strings passed to
+ uuid_parse.
+
+2002-03-08 Theodore Tso <tytso@mit.edu>
+
+ * Release of E2fsprogs 1.27
+
+2002-02-24 Theodore Tso <tytso@mit.edu>
+
+ * Makefile.in (install): Install hard links to man pages for
+ uuid_generate_random and uuid_generate_time. Remove
+ any compressed man pages before installing the man pages.
+
+2002-02-03 Theodore Tso <tytso@thunk.org>
+
+ * Release of E2fsprogs 1.26
+
+2001-09-20 Theodore Tso <tytso@thunk.org>
+
+ * Release of E2fsprogs 1.25
+
+2001-09-10 Theodore Tso <tytso@mit.edu>
+
+ * compare.c (uuid_compare), copy.c (uuid_copy),
+ isnull.c (uuid_is_null), pack.c (uuid_pack),
+ parse.c (uuid_parse), unpack.c (uuid_unpack),
+ unparse.c (uuid_unparse), uuid.h, uuidP.h,
+ uuid_time.c (uuid_time, uuid_type, uuid_variant):
+ Use const for pointer variables that we don't modify. Add
+ the appropriate ifdef's in uuid.h to make it be C++ friendly.
+
+2001-09-02 Theodore Tso <tytso@thunk.org>
+
+ * Release of E2fsprogs 1.24a
+
+2001-08-30 Theodore Tso <tytso@thunk.org>
+
+ * Release of E2fsprogs 1.24
+
+2001-08-15 Theodore Tso <tytso@valinux.com>
+
+ * Release of E2fsprogs 1.23
+
+2001-06-23 Theodore Tso <tytso@valinux.com>
+
+ * Release of E2fsprogs 1.22
+
+2001-06-21 Theodore Tso <tytso@valinux.com>
+
+ * uuid.h: Add protection against multiple inclusion
+
+2001-06-15 Theodore Tso <tytso@valinux.com>
+
+ * Release of E2fsprogs 1.21
+
+2001-06-01 Theodore Tso <tytso@valinux.com>
+
+ * Makefile.in, uuidP.h: Move include/asm/types.h.in to
+ lib/ext2fs/ext2_types.h.in.
+
+2001-06-01 Theodore Tso <tytso@valinux.com>
+
+ * unpack.c, unparse.c, uuid_time.c: Update files to be under the
+ LGPL (that somehow were missed when libuuid was converted
+ to use the LGPL). Whoops.
+
+2001-05-25 Theodore Tso <tytso@valinux.com>
+
+ * Release of E2fsprogs 1.20
+
+2001-05-14 Theodore Tso <tytso@valinux.com>
+
+ * tst_uuid.c, uuid_time.c: Remove unneeded #include of ext2_fs.h
+
+2001-05-12 Theodore Tso <tytso@valinux.com>
+
+ * libuuid.3.in, uuid_clear.3.in, uuid_compare.3.in, uuid_copy.3.in,
+ uuid_generate.3.in, uuid_is_null.3.in, uuid_parse.3.in,
+ uuid_time.3.in, uuid_unparse.3.in: Update URL location of
+ e2fsprogs package.
+
+2001-05-01 Theodore Tso <tytso@valinux.com>
+
+ * parse.c, compare.c: Include string.h to fix gcc -Wall
+ complaints.
+
+ * gen_uuid.c: Define _SVID_SOURCE to avoid gcc -Wall errors
+ because some required structures wouldn't be otherwise
+ defined. Fix a minor gcc -Wall nit in the declaration of
+ get_random_fd().
+
+2001-01-12 Theodore Ts'o <tytso@valinux.com>
+
+ * uuid_time.c (main), tst_uuid.c (main): Fix gcc -Wall complaints.
+
+ * uuid.h, copy.c (uuid_copy): Change arguments to make it
+ clear which argument is the source and which is the
+ destination.
+
+ * gen_uuid.c (get_random_fd): Use gettimeofday to seed the PRNG,
+ so we can take advantage of tv_usec to do (slightly)
+ better at seeding it.
+
+2000-07-13 <tytso@valinux.com>
+
+ * Release of E2fsprogs 1.19
+
+2000-07-07 Theodore Ts'o <tytso@valinux.com>
+
+ * Makefile.in (uuid_time): Fix compilation rule so that
+ uuid_time.o doesn't get bashed in order to build the
+ command-line version of uuid_time.
+
+2000-07-04 Theodore Ts'o <tytso@valinux.com>
+
+ * Makefile.in: Remove explicit link of -lc in the shared library.
+ (It shouldn't be necessary, and is harmful in some cases).
+
+2000-06-12 Theodore Ts'o <tytso@valinux.com>
+
+ * gen_uuid.c (get_random_bytes): Use O_NONBLOCK when trying to
+ open /dev/random. Break out the /dev/random
+ initialization code into a get_random_fd() function, and
+ use that function in uuid_generate() to determine whether
+ to use uuid_generate_random() or uuid_generate_time().
+
+2000-05-25 <tytso@snap.thunk.org>
+
+ * Makefile: Add hack dependency rule so that parallel makes work
+ correctly.
+
+2000-04-07 Theodore Ts'o <tytso@valinux.com>
+
+ * clear.c, compare.c, copy.c, gen_uuid.c, isnull.c, pack.c,
+ parse.c, uuid.h, uuidP.h: Changed copyright to be the
+ LGPL.
+
+Thu Apr 6 17:38:58 2000 Theodore Y. Ts'o <tytso@signal.thunk.org>
+
+ * Makefile.in (uuid_time): Compile uuid_time in two steps (first
+ create .o, then link it against the libraries) to work
+ around bug in a.out linker.
+
+ * dll/jump.funcs, dll/jump.import, dll/jump.params: Update a.out
+ shared library control files to reflect new added files.
+
+2000-04-03 Theodore Ts'o <tytso@valinux.com>
+
+ * gen_uuid.c (get_clock): Fix bug where the last timeval wasn't
+ getting set, causing potentially duplicate UUID's to be
+ generated.
+
+2000-03-12 Theodore Ts'o <tytso@valinux.com>
+
+ * gen_uuid.c (get_random_bytes): Make more paranoid about
+ misbehaving /dev/urandom. If we get a return of zero
+ without an error more than 8 times in a row, we break out
+ and return an error. Also, if /dev/urandom doesn't exist,
+ try /dev/random.
+
+2000-01-18 Theodore Ts'o <tytso@valinux.com>
+
+ * Makefile.in: Since LIBUUID can sometimes include
+ "-lsocket" we need a separate DEPLIBUUID that can be used
+ in Makefile's dependency rules.
+
+1999-11-19 <tytso@valinux.com>
+
+ * Makefile.in (distclean): Remove TAGS and Makefile.in.old from
+ the source directory.
+
+1999-11-10 <tytso@valinux.com>
+
+ * Release of E2fsprogs 1.18
+
+1999-10-26 <tytso@valinux.com>
+
+ * Release of E2fsprogs 1.17
+
+1999-10-26 <tytso@valinux.com>
+
+ * uuid_time.c (variant_string): Declare to be static to avoid gcc
+ warnings.
+
+ * uuid.h: Add function prototypes for uuid_generate_random() and
+ uuid_generate_time().
+
+1999-10-25 <tytso@valinux.com>
+
+ * gen_uuid_nt.c (uuid_generate): W2K strikes again! An
+ incompatible interface change means we need to detect
+ whether the code is running on an NT4 or NT5 system.
+
+1999-10-22 <tytso@valinux.com>
+
+ * Release of E2fsprogs 1.16
+
+1999-10-21 <tytso@valinux.com>
+
+ * uuid_generate.8.in: Update man page to use a more standard
+ format (bold option flags and italicized variables), as
+ suggested by Andreas Dilger (adilger@enel.ucalgary.ca)
+
+1999-09-24 <tytso@valinux.com>
+
+ * gen_uuid_nt.c: New file which creates a UUID under Windows NT.
+
+1999-07-18 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs 1.15
+
+1999-05-17 <tytso@rsts-11.mit.edu>
+
+ * gen_uuid.c (get_random_bytes): Use a while loop when reading
+ from /dev/urandom so that if we get interrupted while
+ reading the right thing happens.
+ (uuid_generate_random): Add new function which uses the
+ new UUID format which uses 122 random bits to form the
+ 128-bit UUID.
+ (uuid_generate): Rename the old uuid_generate to be
+ uuid_generate_time, and create a new uuid_generate
+ function which calls either uuid_generate_random or
+ uuid_genereate_time depending on whether /dev/urandom is
+ present.
+
+ * uuid_generate.3.in: Update to reflect changesin uuid_generate
+ and its two new variants.
+
+ * tst_uuid.c: Updated to test new uuid_generate functions, and to
+ reflect new semantics of uuid_compare. Added tests to
+ make sure the UUID type and variant created by UUID
+ generate is correct.
+
+ * uuid_time.c (uuid_variant, uuid_type): Added new functions to
+ return the UUID variant and type information. The
+ debugging program now prints the UUID variant and type,
+ and warns if the unparsed time information is likely to be
+ incorrect.
+
+ * uuid_parse.3.in, libuuid.3.in: Miscellaneous text cleanups.
+
+1999-05-03 <tytso@rsts-11.mit.edu>
+
+ * compare.c (uuid_compare): Change sense of uuid_compare so that
+ its return values match that of memcpy and the
+ uuid_compare() found in Paul Leach's internet-draft.
+
+1999-03-11 Andreas Dilger <adilger@enel.ucalgary.ca>
+
+ * Created man pages for libuuid functions.
+
+1999-01-09 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs 1.14
+
+1998-12-15 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs 1.13
+
+1998-12-04 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Makefile.in: Update version numbers of the UUID shared library,
+ since we've added a new function (uuid_time()).
+
+ * uuid_time.c: New file which returns the time field of a UUID.
+ (Good for debugging purposes)
+
+1998-07-09 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs 1.12
+
+1998-06-25 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * tst_uuid.c (main): Fixed bogus declaration of the main's argv
+ parameter.
+
+1998-04-26 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * uuidP.h: Use asm/types.h instead of linux/types.h to avoid a
+ problem caused by glibc hack to prevent linux/types.h from
+ being included.
+
+1998-03-30 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Makefile.in: Change to use new installation directory variables
+ convention. Fix uninstall rules to take $(DESTDIR) into
+ account.
+
+Sun Mar 8 22:17:59 1998 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * gen_uuid.c (get_node_id): Use char * instead of caddr_t, which
+ doesn't always exist for glibc.
+
+Tue Oct 14 21:48:16 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * gen_uuid.c: Use clock_reg instead of clock, since clock
+ conflicts with a header file declaration.
+
+Tue Jun 17 01:33:20 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs 1.11
+
+Thu Apr 24 12:16:42 1997 Theodre Ts'o <tytso@localhost.mit.edu>
+
+ * Release of E2fsprogs version 1.10
+
+Thu Apr 17 12:23:38 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs version 1.09
+
+Fri Apr 11 18:56:26 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs version 1.08
+
+Wed Mar 12 13:32:05 1997 Theodore Y. Ts'o <tytso@mit.edu>
+
+ * Release of E2fsprogs version 1.07
+
+Sun Mar 2 16:45:36 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Makefile.in (ELF_VERSION): Change version to be 1.1
+
+Thu Feb 6 23:08:07 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * gen_uuid.c (uuid_generate): Set Multicast bit when picking a
+ random node_id, to prevent conflicts with IEEE 802
+ addresses obtained from network cards.
+
+Wed Jan 1 23:51:09 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * unpack.c, pack.c: Include string.h, since we use memcpy().
+
+Tue Dec 3 13:05:11 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * parse.c: Add #include of ctype.h and stdlib.h, to pull in the
+ required prototypes.
+
+Fri Oct 11 17:15:10 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Makefile.in (DLL_ADDRESS): Updated DLL address for libuuid.
+
+Tue Oct 8 02:02:03 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs version 1.06
+
+Thu Sep 12 15:23:07 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * Release of E2fsprogs version 1.05
+
+Tue Aug 27 16:50:43 1996 Miles Bader <miles@gnu.ai.mit.edu>
+
+ * uuid/gen_uuid.c [HAVE_NET_IF_H] <net/if.h>: Include guarded.
+ [HAVE_NETINET_IN_H] <netinet/in.h>: Include guarded.
+ (get_node_id): Surround bulk of function with #ifdef HAVE_NET_IF_H.
+
+Tue Aug 27 16:50:16 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * gen_uuid.c (get_node_id): Add a specific ifdef for the HURD,
+ since it is broken w.r.t getting hardware addresses.
diff --git a/kexi/3rdparty/uuid/Makefile.am b/kexi/3rdparty/uuid/Makefile.am
new file mode 100644
index 000000000..d56649676
--- /dev/null
+++ b/kexi/3rdparty/uuid/Makefile.am
@@ -0,0 +1,14 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_LTLIBRARIES = libkexiuuid.la
+
+INCLUDES = $(all_includes)
+
+SUBDIRS = .
+
+
+libkexiuuid_la_SOURCES = gen_uuid.c parse.c unparse.c compare.c isnull.c pack.c unpack.c copy.c
+libkexiuuid_la_LDFLAGS = -no-undefined $(VER_INFO)
+libkexiuuid_la_LIBADD =
+
+
diff --git a/kexi/3rdparty/uuid/clear.c b/kexi/3rdparty/uuid/clear.c
new file mode 100644
index 000000000..9cfe59aa9
--- /dev/null
+++ b/kexi/3rdparty/uuid/clear.c
@@ -0,0 +1,20 @@
+/*
+ * clear.c -- Clear a UUID
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include "string.h"
+
+#include "uuidP.h"
+
+void uuid_clear(uuid_t uu)
+{
+ memset(uu, 0, 16);
+}
+
diff --git a/kexi/3rdparty/uuid/compare.c b/kexi/3rdparty/uuid/compare.c
new file mode 100644
index 000000000..0f9737c5c
--- /dev/null
+++ b/kexi/3rdparty/uuid/compare.c
@@ -0,0 +1,32 @@
+/*
+ * compare.c --- compare whether or not two UUID's are the same
+ *
+ * Returns 0 if the two UUID's are different, and 1 if they are the same.
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+#include <string.h>
+
+#define UUCMP(u1,u2) if (u1 != u2) return((u1 < u2) ? -1 : 1);
+
+int uuid_compare(const uuid_t uu1, const uuid_t uu2)
+{
+ struct uuid uuid1, uuid2;
+
+ uuid_unpack(uu1, &uuid1);
+ uuid_unpack(uu2, &uuid2);
+
+ UUCMP(uuid1.time_low, uuid2.time_low);
+ UUCMP(uuid1.time_mid, uuid2.time_mid);
+ UUCMP(uuid1.time_hi_and_version, uuid2.time_hi_and_version);
+ UUCMP(uuid1.clock_seq, uuid2.clock_seq);
+ return memcmp(uuid1.node, uuid2.node, 6);
+}
+
diff --git a/kexi/3rdparty/uuid/configure.in b/kexi/3rdparty/uuid/configure.in
new file mode 100644
index 000000000..bf9509fec
--- /dev/null
+++ b/kexi/3rdparty/uuid/configure.in
@@ -0,0 +1,10 @@
+dnl
+dnl Not used now, for the future when uuid is separated out into its
+dnl own package.
+dnl
+AC_INIT(gen_uuid.c)
+AC_PREREQ(2.12)
+
+AC_CHECK_HEADERS(stdlib.h unistd.h sys/sockio.h net/if.h netinet/in.h)
+AC_CHECK_FUNCS(srandom)
+AC_OUTPUT(Makefile)
diff --git a/kexi/3rdparty/uuid/copy.c b/kexi/3rdparty/uuid/copy.c
new file mode 100644
index 000000000..b9a34dac5
--- /dev/null
+++ b/kexi/3rdparty/uuid/copy.c
@@ -0,0 +1,22 @@
+/*
+ * copy.c --- copy UUIDs
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+
+void uuid_copy(uuid_t dst, const uuid_t src)
+{
+ unsigned char *cp1;
+ const unsigned char *cp2;
+ int i;
+
+ for (i=0, cp1 = dst, cp2 = src; i < 16; i++)
+ *cp1++ = *cp2++;
+}
diff --git a/kexi/3rdparty/uuid/gen_uuid.c b/kexi/3rdparty/uuid/gen_uuid.c
new file mode 100644
index 000000000..2441931c6
--- /dev/null
+++ b/kexi/3rdparty/uuid/gen_uuid.c
@@ -0,0 +1,280 @@
+/*
+ * gen_uuid.c --- generate a DCE-compatible uuid
+ *
+ * Copyright (C) 1996, 1997, 1998, 1999 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+/*
+ * Force inclusion of SVID stuff since we need it if we're compiling in
+ * gcc-wall wall mode
+ */
+#define _SVID_SOURCE
+
+/*jowenn: #ifdef HAVE_UNISTD_H*/
+#include <unistd.h>
+/*jowenn: #endif*/
+/*jowenn: #ifdef HAVE_STDLIB_H*/
+#include <stdlib.h>
+/*jowenn: #endif*/
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+#ifdef HAVE_NET_IF_H
+#include <net/if.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#include "uuidP.h"
+
+#ifdef HAVE_SRANDOM
+#define srand(x) srandom(x)
+#define rand() random()
+#endif
+
+static int get_random_fd(void)
+{
+ struct timeval tv;
+ static int fd = -2;
+ int i;
+
+ if (fd == -2) {
+ gettimeofday(&tv, 0);
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd == -1)
+ fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
+ srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
+ }
+ /* Crank the random number generator a few times */
+ gettimeofday(&tv, 0);
+ for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--)
+ rand();
+ return fd;
+}
+
+
+/*
+ * Generate a series of random bytes. Use /dev/urandom if possible,
+ * and if not, use srandom/random.
+ */
+static void get_random_bytes(void *buf, int nbytes)
+{
+ int i, n = nbytes, fd = get_random_fd();
+ int lose_counter = 0;
+ unsigned char *cp = (unsigned char *) buf;
+
+ if (fd >= 0) {
+ while (n > 0) {
+ i = read(fd, cp, n);
+ if (i <= 0) {
+ if (lose_counter++ > 16)
+ break;
+ continue;
+ }
+ n -= i;
+ cp += i;
+ lose_counter = 0;
+ }
+ }
+
+ /*
+ * We do this all the time, but this is the only source of
+ * randomness if /dev/random/urandom is out to lunch.
+ */
+ for (cp = buf, i = 0; i < nbytes; i++)
+ *cp++ ^= (rand() >> 7) & 0xFF;
+ return;
+}
+
+/*
+ * Get the ethernet hardware address, if we can find it...
+ */
+static int get_node_id(unsigned char *node_id)
+{
+#ifdef HAVE_NET_IF_H
+ int sd;
+ struct ifreq ifr, *ifrp;
+ struct ifconf ifc;
+ char buf[1024];
+ int n, i;
+ unsigned char *a;
+
+/*
+ * BSD 4.4 defines the size of an ifreq to be
+ * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len
+ * However, under earlier systems, sa_len isn't present, so the size is
+ * just sizeof(struct ifreq)
+ */
+#ifdef HAVE_SA_LEN
+#ifndef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#define ifreq_size(i) max(sizeof(struct ifreq),\
+ sizeof((i).ifr_name)+(i).ifr_addr.sa_len)
+#else
+#define ifreq_size(i) sizeof(struct ifreq)
+#endif /* HAVE_SA_LEN*/
+
+ sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sd < 0) {
+ return -1;
+ }
+ memset(buf, 0, sizeof(buf));
+ ifc.ifc_len = sizeof(buf);
+ ifc.ifc_buf = buf;
+ if (ioctl (sd, SIOCGIFCONF, (char *)&ifc) < 0) {
+ close(sd);
+ return -1;
+ }
+ n = ifc.ifc_len;
+ for (i = 0; i < n; i+= ifreq_size(*ifr) ) {
+ ifrp = (struct ifreq *)((char *) ifc.ifc_buf+i);
+ strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ);
+#ifdef SIOCGIFHWADDR
+ if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0)
+ continue;
+ a = (unsigned char *) &ifr.ifr_hwaddr.sa_data;
+#else
+#ifdef SIOCGENADDR
+ if (ioctl(sd, SIOCGENADDR, &ifr) < 0)
+ continue;
+ a = (unsigned char *) ifr.ifr_enaddr;
+#else
+ /*
+ * XXX we don't have a way of getting the hardware
+ * address
+ */
+ close(sd);
+ return 0;
+#endif /* SIOCGENADDR */
+#endif /* SIOCGIFHWADDR */
+ if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5])
+ continue;
+ if (node_id) {
+ memcpy(node_id, a, 6);
+ close(sd);
+ return 1;
+ }
+ }
+ close(sd);
+#endif
+ return 0;
+}
+
+/* Assume that the gettimeofday() has microsecond granularity */
+#define MAX_ADJUSTMENT 10
+
+static int get_clock(__u32 *clock_high, __u32 *clock_low, __u16 *ret_clock_seq)
+{
+ static int adjustment = 0;
+ static struct timeval last = {0, 0};
+ static __u16 clock_seq;
+ struct timeval tv;
+ unsigned longlong clock_reg;
+
+try_again:
+ gettimeofday(&tv, 0);
+ if ((last.tv_sec == 0) && (last.tv_usec == 0)) {
+ get_random_bytes(&clock_seq, sizeof(clock_seq));
+ clock_seq &= 0x1FFF;
+ last = tv;
+ last.tv_sec--;
+ }
+ if ((tv.tv_sec < last.tv_sec) ||
+ ((tv.tv_sec == last.tv_sec) &&
+ (tv.tv_usec < last.tv_usec))) {
+ clock_seq = (clock_seq+1) & 0x1FFF;
+ adjustment = 0;
+ last = tv;
+ } else if ((tv.tv_sec == last.tv_sec) &&
+ (tv.tv_usec == last.tv_usec)) {
+ if (adjustment >= MAX_ADJUSTMENT)
+ goto try_again;
+ adjustment++;
+ } else {
+ adjustment = 0;
+ last = tv;
+ }
+
+ clock_reg = tv.tv_usec*10 + adjustment;
+ clock_reg += ((unsigned longlong) tv.tv_sec)*10000000;
+ clock_reg += (((unsigned longlong) 0x01B21DD2) << 32) + 0x13814000;
+
+ *clock_high = clock_reg >> 32;
+ *clock_low = clock_reg;
+ *ret_clock_seq = clock_seq;
+ return 0;
+}
+
+void uuid_generate_time(uuid_t out)
+{
+ static unsigned char node_id[6];
+ static int has_init = 0;
+ struct uuid uu;
+ __u32 clock_mid;
+
+ if (!has_init) {
+ if (get_node_id(node_id) <= 0) {
+ get_random_bytes(node_id, 6);
+ /*
+ * Set multicast bit, to prevent conflicts
+ * with IEEE 802 addresses obtained from
+ * network cards
+ */
+ node_id[0] |= 0x80;
+ }
+ has_init = 1;
+ }
+ get_clock(&clock_mid, &uu.time_low, &uu.clock_seq);
+ uu.clock_seq |= 0x8000;
+ uu.time_mid = (__u16) clock_mid;
+ uu.time_hi_and_version = (clock_mid >> 16) | 0x1000;
+ memcpy(uu.node, node_id, 6);
+ uuid_pack(&uu, out);
+}
+
+void uuid_generate_random(uuid_t out)
+{
+ uuid_t buf;
+ struct uuid uu;
+
+ get_random_bytes(buf, sizeof(buf));
+ uuid_unpack(buf, &uu);
+
+ uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;
+ uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000;
+ uuid_pack(&uu, out);
+}
+
+/*
+ * This is the generic front-end to uuid_generate_random and
+ * uuid_generate_time. It uses uuid_generate_random only if
+ * /dev/urandom is available, since otherwise we won't have
+ * high-quality randomness.
+ */
+void uuid_generate(uuid_t out)
+{
+ if (get_random_fd() >= 0)
+ uuid_generate_random(out);
+ else
+ uuid_generate_time(out);
+}
diff --git a/kexi/3rdparty/uuid/gen_uuid_nt.c b/kexi/3rdparty/uuid/gen_uuid_nt.c
new file mode 100644
index 000000000..aa44bfd3d
--- /dev/null
+++ b/kexi/3rdparty/uuid/gen_uuid_nt.c
@@ -0,0 +1,92 @@
+/*
+ * gen_uuid_nt.c -- Use NT api to generate uuid
+ *
+ * Written by Andrey Shedel (andreys@ns.cr.cyco.com)
+ */
+
+
+#include "uuidP.h"
+
+#pragma warning(push,4)
+
+#pragma comment(lib, "ntdll.lib")
+
+//
+// Here is a nice example why it's not a good idea
+// to use native API in ordinary applications.
+// Number of parameters in function below was changed from 3 to 4
+// for NT5.
+//
+//
+// NTSYSAPI
+// NTSTATUS
+// NTAPI
+// NtAllocateUuids(
+// OUT PULONG p1,
+// OUT PULONG p2,
+// OUT PULONG p3,
+// OUT PUCHAR Seed // 6 bytes
+// );
+//
+//
+
+unsigned long
+__stdcall
+NtAllocateUuids(
+ void* p1, // 8 bytes
+ void* p2, // 4 bytes
+ void* p3 // 4 bytes
+ );
+
+typedef
+unsigned long
+(__stdcall*
+NtAllocateUuids_2000)(
+ void* p1, // 8 bytes
+ void* p2, // 4 bytes
+ void* p3, // 4 bytes
+ void* seed // 6 bytes
+ );
+
+
+
+//
+// Nice, but instead of including ntddk.h ot winnt.h
+// I should define it here because they MISSED __stdcall in those headers.
+//
+
+__declspec(dllimport)
+struct _TEB*
+__stdcall
+NtCurrentTeb(void);
+
+
+//
+// The only way to get version information from the system is to examine
+// one stored in PEB. But it's pretty dangerouse because this value could
+// be altered in image header.
+//
+
+static
+int
+Nt5(void)
+{
+ //return NtCuttentTeb()->Peb->OSMajorVersion >= 5;
+ return (int)*(int*)((char*)(int)(*(int*)((char*)NtCurrentTeb() + 0x30)) + 0xA4) >= 5;
+}
+
+
+
+
+void uuid_generate(uuid_t out)
+{
+ if(Nt5())
+ {
+ unsigned char seed[6];
+ ((NtAllocateUuids_2000)NtAllocateUuids)(out, ((char*)out)+8, ((char*)out)+12, &seed[0] );
+ }
+ else
+ {
+ NtAllocateUuids(out, ((char*)out)+8, ((char*)out)+12);
+ }
+}
diff --git a/kexi/3rdparty/uuid/isnull.c b/kexi/3rdparty/uuid/isnull.c
new file mode 100644
index 000000000..8aba238a0
--- /dev/null
+++ b/kexi/3rdparty/uuid/isnull.c
@@ -0,0 +1,25 @@
+/*
+ * isnull.c --- Check whether or not the UUID is null
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+
+/* Returns 1 if the uuid is the NULL uuid */
+int uuid_is_null(const uuid_t uu)
+{
+ const unsigned char *cp;
+ int i;
+
+ for (i=0, cp = uu; i < 16; i++)
+ if (*cp++)
+ return 0;
+ return 1;
+}
+
diff --git a/kexi/3rdparty/uuid/libuuid.3.in b/kexi/3rdparty/uuid/libuuid.3.in
new file mode 100644
index 000000000..ec5e7cd6f
--- /dev/null
+++ b/kexi/3rdparty/uuid/libuuid.3.in
@@ -0,0 +1,70 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH LIBUUID 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+libuuid \- DCE compatible Universally Unique Identifier library
+.SH SYNOPSIS
+.B #include <uuid/uuid.h>
+.sp
+.B cc
+.I file.c
+.B \-luuid
+.SH DESCRIPTION
+The
+.B libuuid
+library is used to generate unique identifiers for objects that may be
+accessible beyond the local system. The Linux implementation was created
+to uniquely identify ext2 filesystems created by a machine. This library
+generates UUIDs compatible with those created by the Open Software
+Foundation (OSF) Distributed Computing Environment (DCE) utility
+.BR uuidgen .
+.sp
+The UUIDs generated by this library can be reasonably expected to be
+unique within a system, and unique across all systems. They could
+be used, for instance, to generate unique HTTP cookies across multiple
+web servers without communication between the servers, and without fear
+of a name clash.
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+.B libuuid
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B libuuid
+is part of the e2fsprogs package and is available from
+.UR http://e2fsprogs.sourceforge.net/
+http://e2fsprogs.sourceforge.net/
+.UE
+.SH COPYING
+.B libuuid
+is available under the terms of the GNU Library General Public License (LGPL),
+version 2 (or at your discretion any later version). A copy of the LGPL
+should be included with this library in the file COPYING. If not, write to
+.RS
+Free Software Foundation, Inc.
+.br
+59 Temple Place
+.br
+Suite 330
+.br
+Boston, MA 02110-1301 USA
+.RE
+.PP
+or visit
+.UR http://www.gnu.org/licenses/licenses.html#LGPL
+http://www.gnu.org/licenses/licenses.html#LGPL
+.UE
+.SH "SEE ALSO"
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_time (3),
+.BR uuid_unparse (3)
diff --git a/kexi/3rdparty/uuid/pack.c b/kexi/3rdparty/uuid/pack.c
new file mode 100644
index 000000000..1303ef523
--- /dev/null
+++ b/kexi/3rdparty/uuid/pack.c
@@ -0,0 +1,46 @@
+/*
+ * Internal routine for packing UUID's
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuidP.h"
+
+void uuid_pack(const struct uuid *uu, uuid_t ptr)
+{
+ __u32 tmp;
+ unsigned char *out = ptr;
+
+ tmp = uu->time_low;
+ out[3] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[2] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[1] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[0] = (unsigned char) tmp;
+
+ tmp = uu->time_mid;
+ out[5] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[4] = (unsigned char) tmp;
+
+ tmp = uu->time_hi_and_version;
+ out[7] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[6] = (unsigned char) tmp;
+
+ tmp = uu->clock_seq;
+ out[9] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[8] = (unsigned char) tmp;
+
+ memcpy(out+10, uu->node, 6);
+}
+
diff --git a/kexi/3rdparty/uuid/parse.c b/kexi/3rdparty/uuid/parse.c
new file mode 100644
index 000000000..f97f13ea4
--- /dev/null
+++ b/kexi/3rdparty/uuid/parse.c
@@ -0,0 +1,56 @@
+/*
+ * parse.c --- UUID parsing
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "uuidP.h"
+
+int uuid_parse(const char *in, uuid_t uu)
+{
+ struct uuid uuid;
+ int i;
+ const char *cp;
+ char buf[3];
+
+ if (strlen(in) != 36)
+ return -1;
+ for (i=0, cp = in; i <= 36; i++,cp++) {
+ if ((i == 8) || (i == 13) || (i == 18) ||
+ (i == 23)) {
+ if (*cp == '-')
+ continue;
+ else
+ return -1;
+ }
+ if (i== 36)
+ if (*cp == 0)
+ continue;
+ if (!isxdigit(*cp))
+ return -1;
+ }
+ uuid.time_low = strtoul(in, NULL, 16);
+ uuid.time_mid = strtoul(in+9, NULL, 16);
+ uuid.time_hi_and_version = strtoul(in+14, NULL, 16);
+ uuid.clock_seq = strtoul(in+19, NULL, 16);
+ cp = in+24;
+ buf[2] = 0;
+ for (i=0; i < 6; i++) {
+ buf[0] = *cp++;
+ buf[1] = *cp++;
+ uuid.node[i] = strtoul(buf, NULL, 16);
+ }
+
+ uuid_pack(&uuid, uu);
+ return 0;
+}
diff --git a/kexi/3rdparty/uuid/tst_uuid.c b/kexi/3rdparty/uuid/tst_uuid.c
new file mode 100644
index 000000000..f1f56f16a
--- /dev/null
+++ b/kexi/3rdparty/uuid/tst_uuid.c
@@ -0,0 +1,147 @@
+/*
+ * tst_uuid.c --- test program from the UUID library
+ *
+ * Copyright (C) 1996, 1997, 1998 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+
+#include "uuid.h"
+
+static int test_uuid(const char * uuid, int isValid)
+{
+ static const char * validStr[2] = {"invalid", "valid"};
+ uuid_t uuidBits;
+ int parsedOk;
+
+ parsedOk = uuid_parse(uuid, uuidBits) == 0;
+
+ printf("%s is %s", uuid, validStr[isValid]);
+ if (parsedOk != isValid) {
+ printf(" but uuid_parse says %s\n", validStr[parsedOk]);
+ return 1;
+ }
+ printf(", OK\n");
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ uuid_t buf, tst;
+ char str[100];
+ struct timeval tv;
+ time_t time_reg;
+ unsigned char *cp;
+ int i;
+ int failed = 0;
+ int type, variant;
+
+ uuid_generate(buf);
+ uuid_unparse(buf, str);
+ printf("UUID generate = %s\n", str);
+ printf("UUID: ");
+ for (i=0, cp = (unsigned char *) &buf; i < 16; i++) {
+ printf("%02x", *cp++);
+ }
+ printf("\n");
+ type = uuid_type(buf); variant = uuid_variant(buf);
+ printf("UUID type = %d, UUID variant = %d\n", type, variant);
+ if (variant != UUID_VARIANT_DCE) {
+ printf("Incorrect UUID Variant; was expecting DCE!\n");
+ failed++;
+ }
+ printf("\n");
+
+ uuid_generate_random(buf);
+ uuid_unparse(buf, str);
+ printf("UUID random string = %s\n", str);
+ printf("UUID: ");
+ for (i=0, cp = (unsigned char *) &buf; i < 16; i++) {
+ printf("%02x", *cp++);
+ }
+ printf("\n");
+ type = uuid_type(buf); variant = uuid_variant(buf);
+ printf("UUID type = %d, UUID variant = %d\n", type, variant);
+ if (variant != UUID_VARIANT_DCE) {
+ printf("Incorrect UUID Variant; was expecting DCE!\n");
+ failed++;
+ }
+ if (type != 4) {
+ printf("Incorrect UUID type; was expecting "
+ "4 (random type)!\n");
+ failed++;
+ }
+ printf("\n");
+
+ uuid_generate_time(buf);
+ uuid_unparse(buf, str);
+ printf("UUID string = %s\n", str);
+ printf("UUID time: ");
+ for (i=0, cp = (unsigned char *) &buf; i < 16; i++) {
+ printf("%02x", *cp++);
+ }
+ printf("\n");
+ type = uuid_type(buf); variant = uuid_variant(buf);
+ printf("UUID type = %d, UUID variant = %d\n", type, variant);
+ if (variant != UUID_VARIANT_DCE) {
+ printf("Incorrect UUID Variant; was expecting DCE!\n");
+ failed++;
+ }
+ if (type != 1) {
+ printf("Incorrect UUID type; was expecting "
+ "1 (time-based type)!\\n");
+ failed++;
+ }
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ time_reg = uuid_time(buf, &tv);
+ printf("UUID time is: (%ld, %ld): %s\n", tv.tv_sec, tv.tv_usec,
+ ctime(&time_reg));
+ uuid_parse(str, tst);
+ if (!uuid_compare(buf, tst))
+ printf("UUID parse and compare succeeded.\n");
+ else {
+ printf("UUID parse and compare failed!\n");
+ failed++;
+ }
+ uuid_clear(tst);
+ if (uuid_is_null(tst))
+ printf("UUID clear and is null succeeded.\n");
+ else {
+ printf("UUID clear and is null failed!\n");
+ failed++;
+ }
+ uuid_copy(buf, tst);
+ if (!uuid_compare(buf, tst))
+ printf("UUID copy and compare succeeded.\n");
+ else {
+ printf("UUID copy and compare failed!\n");
+ failed++;
+ }
+ failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981b", 1);
+ failed += test_uuid("84949CC5-4701-4A84-895B-354C584A981B", 1);
+ failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981bc", 0);
+ failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981", 0);
+ failed += test_uuid("84949cc5x4701-4a84-895b-354c584a981b", 0);
+ failed += test_uuid("84949cc504701-4a84-895b-354c584a981b", 0);
+ failed += test_uuid("84949cc5-470104a84-895b-354c584a981b", 0);
+ failed += test_uuid("84949cc5-4701-4a840895b-354c584a981b", 0);
+ failed += test_uuid("84949cc5-4701-4a84-895b0354c584a981b", 0);
+ failed += test_uuid("g4949cc5-4701-4a84-895b-354c584a981b", 0);
+ failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981g", 0);
+
+ if (failed) {
+ printf("%d failures.\n", failed);
+ exit(1);
+ }
+ return 0;
+}
+
+
+
diff --git a/kexi/3rdparty/uuid/unpack.c b/kexi/3rdparty/uuid/unpack.c
new file mode 100644
index 000000000..02005dde1
--- /dev/null
+++ b/kexi/3rdparty/uuid/unpack.c
@@ -0,0 +1,40 @@
+/*
+ * Internal routine for unpacking UUID
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuidP.h"
+
+void uuid_unpack(const uuid_t in, struct uuid *uu)
+{
+ const __u8 *ptr = in;
+ __u32 tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_low = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_mid = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_hi_and_version = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->clock_seq = tmp;
+
+ memcpy(uu->node, ptr, 6);
+}
+
diff --git a/kexi/3rdparty/uuid/unparse.c b/kexi/3rdparty/uuid/unparse.c
new file mode 100644
index 000000000..db3ef0480
--- /dev/null
+++ b/kexi/3rdparty/uuid/unparse.c
@@ -0,0 +1,28 @@
+/*
+ * unparse.c -- convert a UUID to string
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+
+#include "uuidP.h"
+
+void uuid_unparse(const uuid_t uu, char *out)
+{
+ struct uuid uuid;
+
+ uuid_unpack(uu, &uuid);
+ sprintf(out,
+ "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ uuid.time_low, uuid.time_mid, uuid.time_hi_and_version,
+ uuid.clock_seq >> 8, uuid.clock_seq & 0xFF,
+ uuid.node[0], uuid.node[1], uuid.node[2],
+ uuid.node[3], uuid.node[4], uuid.node[5]);
+}
+
diff --git a/kexi/3rdparty/uuid/uuid.h b/kexi/3rdparty/uuid/uuid.h
new file mode 100644
index 000000000..17a33a8a4
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid.h
@@ -0,0 +1,68 @@
+/*
+ * Public include file for the UUID library
+ *
+ * Copyright (C) 1996, 1997, 1998 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#ifndef _UUID_UUID_H
+#define _UUID_UUID_H
+
+/*(js)*/
+#ifndef KEXIUUID_EXPORT
+# define KEXIUUID_EXPORT
+#endif
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+
+typedef unsigned char uuid_t[16];
+
+/* UUID Variant definitions */
+#define UUID_VARIANT_NCS 0
+#define UUID_VARIANT_DCE 1
+#define UUID_VARIANT_MICROSOFT 2
+#define UUID_VARIANT_OTHER 3
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* clear.c */
+void KEXIUUID_EXPORT uuid_clear(uuid_t uu);
+
+/* compare.c */
+int KEXIUUID_EXPORT uuid_compare(const uuid_t uu1, const uuid_t uu2);
+
+/* copy.c */
+void KEXIUUID_EXPORT uuid_copy(uuid_t dst, const uuid_t src);
+
+/* gen_uuid.c */
+void KEXIUUID_EXPORT uuid_generate(uuid_t out);
+void KEXIUUID_EXPORT uuid_generate_random(uuid_t out);
+void KEXIUUID_EXPORT uuid_generate_time(uuid_t out);
+
+/* isnull.c */
+int KEXIUUID_EXPORT uuid_is_null(const uuid_t uu);
+
+/* parse.c */
+int KEXIUUID_EXPORT uuid_parse(const char *in, uuid_t uu);
+
+/* unparse.c */
+void KEXIUUID_EXPORT uuid_unparse(const uuid_t uu, char *out);
+
+/* uuid_time.c */
+time_t KEXIUUID_EXPORT uuid_time(const uuid_t uu, struct timeval *ret_tv);
+int KEXIUUID_EXPORT uuid_type(const uuid_t uu);
+int KEXIUUID_EXPORT uuid_variant(const uuid_t uu);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UUID_UUID_H */
diff --git a/kexi/3rdparty/uuid/uuid.pro b/kexi/3rdparty/uuid/uuid.pro
new file mode 100644
index 000000000..e4dbc8fe5
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid.pro
@@ -0,0 +1,24 @@
+
+TEMPLATE = lib
+
+win32:DEFINES += MAKE_KEXIUUID_LIB
+
+include( $(KEXI)/common.pro )
+
+TARGET = kexiuuid$$KDELIBDEBUG
+
+
+SOURCES = \
+clear.c \
+compare.c \
+copy.c \
+gen_uuid.c \
+isnull.c \
+pack.c \
+parse.c \
+unpack.c \
+unparse.c \
+uuid_time.c
+
+#tst_uuid.c \
+#gen_uuid_nt.c \
diff --git a/kexi/3rdparty/uuid/uuidP.h b/kexi/3rdparty/uuid/uuidP.h
new file mode 100644
index 000000000..2595277f2
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuidP.h
@@ -0,0 +1,58 @@
+/*
+ * uuid.h -- private header file for uuids
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include <sys/types.h>
+/*jowenn: #include <uuid/uuid_types.h>*/
+
+/*js*/
+#ifdef _WIN32
+/*.. */
+# define longlong __int64
+#else /* non-win32 systems */
+# define longlong long long
+#endif
+
+#ifdef __linux__
+# include <linux/types.h>
+#endif
+
+#ifdef __FreeBSD__
+typedef u_int32_t __u32;
+typedef u_int16_t __u16;
+typedef u_int8_t __u8;
+#endif
+
+#include "uuid.h"
+
+/*
+ * Offset between 15-Oct-1582 and 1-Jan-70
+ */
+#define TIME_OFFSET_HIGH 0x01B21DD2
+#define TIME_OFFSET_LOW 0x13814000
+
+struct uuid {
+ __u32 time_low;
+ __u16 time_mid;
+ __u16 time_hi_and_version;
+ __u16 clock_seq;
+ __u8 node[6];
+};
+
+
+/*
+ * prototypes
+ */
+void uuid_pack(const struct uuid *uu, uuid_t ptr);
+void uuid_unpack(const uuid_t in, struct uuid *uu);
+
+
+
+
diff --git a/kexi/3rdparty/uuid/uuid_clear.3.in b/kexi/3rdparty/uuid/uuid_clear.3.in
new file mode 100644
index 000000000..6f32b128b
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_clear.3.in
@@ -0,0 +1,35 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_CLEAR 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuid_clear \- reset value of UUID variable to the NULL value
+.SH SYNOPSIS
+.nf
+.B #include <uuid/uuid.h>
+.sp
+.BI "void uuid_clear(uuid_t " uu );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_clear
+function sets the value of the supplied uuid variable
+.I uu
+to the NULL value.
+.SH AUTHOR
+.B uuid_clear
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B uuid_clear
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3)
diff --git a/kexi/3rdparty/uuid/uuid_compare.3.in b/kexi/3rdparty/uuid/uuid_compare.3.in
new file mode 100644
index 000000000..a5759eb09
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_compare.3.in
@@ -0,0 +1,41 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_COMPARE 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuid_compare \- compare whether two UUIDs are the same
+.SH SYNOPSIS
+.nf
+.B #include <uuid/uuid.h>
+.sp
+.BI "int uuid_compare(uuid_t " uu1 ", uuid_t " uu2)
+.fi
+.SH DESCRIPTION
+The
+.B uuid_compare
+function compares the two supplied uuid variables
+.IR uu1 " and " uu2
+to each other.
+.SH RETURN VALUE
+Returns an integer less than, equal to, or greater than zero if
+.I uu1
+is found, respectively, to be lexigraphically less than, equal, or
+greater than
+.IR uu2 .
+.SH AUTHOR
+.B uuid_compare
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B uuid_compare
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuid_clear (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3)
diff --git a/kexi/3rdparty/uuid/uuid_copy.3.in b/kexi/3rdparty/uuid/uuid_copy.3.in
new file mode 100644
index 000000000..52fd1667f
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_copy.3.in
@@ -0,0 +1,37 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_COPY 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuid_copy \- copy a UUID value
+.SH SYNOPSIS
+.nf
+.B #include <uuid/uuid.h>
+.sp
+.BI "void uuid_copy(uuid_t " dst ", uuid_t " src);
+.fi
+.SH DESCRIPTION
+The
+.B uuid_copy
+function copies the UUID variable
+.IR src " to " dst .
+.SH RETURN VALUE
+The copied UUID is returned in the location pointed to by
+.IR dst .
+.SH AUTHOR
+.B uuid_copy
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B uuid_copy
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3)
diff --git a/kexi/3rdparty/uuid/uuid_generate.3.in b/kexi/3rdparty/uuid/uuid_generate.3.in
new file mode 100644
index 000000000..ed15dd605
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_generate.3.in
@@ -0,0 +1,81 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_GENERATE 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuid_generate, uuid_generate_random, uuid_generate_time \- create a new unique UUID value
+.SH SYNOPSIS
+.nf
+.B #include <uuid/uuid.h>
+.sp
+.BI "void uuid_generate(uuid_t " out );
+.BI "void uuid_generate_random(uuid_t " out );
+.BI "void uuid_generate_time(uuid_t " out );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_generate
+function creates a new universally unique identifier (UUID). The uuid will
+be generated based on high-quality randomness from
+.IR /dev/urandom ,
+if available. If it is not available, then
+.B uuid_generate
+will use an alternative algorithm which uses the current time, the
+local ethernet MAC address (if available), and random data generated
+using a pseudo-random generator.
+.sp
+The
+.B uuid_generate_random
+function forces the use of the all-random UUID format, even if
+a high-quality random number generator (i.e.,
+.IR /dev/urandom )
+is not available, in which case a pseudo-random
+generator will be subsituted. Note that the use of a pseudo-random
+generator may compromise the uniqueness of UUID's
+generated in this fashion.
+.sp
+The
+.B uuid_generate_time
+function forces the use of the alternative algorithm which uses the
+current time and the local ethernet MAC address (if available).
+This algorithm used to be the default one used to generate UUID, but
+because of the use of the ethernet MAC address, it can leak
+information about when and where the UUID was generated. This can cause
+privacy problems in some applications, so the
+.B uuid_generate
+function only uses this algorithm if a high-quality source of
+randomness is not available.
+.sp
+The UUID is 16 bytes (128 bits) long, which gives approximately 3.4x10^38
+unique values (there are approximately 10^80 elemntary particles in
+the universe according to Carl Sagan's
+.IR Cosmos ).
+The new UUID can reasonably be considered unique among all UUIDs created
+on the local system, and among UUIDs created on other systems in the past
+and in the future.
+.SH RETURN VALUE
+The newly created UUID is returned in the memory location pointed to by
+.IR out .
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+.B uuid_generate
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B uuid_generate
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuidgen (1),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_time (3),
+.BR uuid_unparse (3)
diff --git a/kexi/3rdparty/uuid/uuid_is_null.3.in b/kexi/3rdparty/uuid/uuid_is_null.3.in
new file mode 100644
index 000000000..94ad0afe2
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_is_null.3.in
@@ -0,0 +1,37 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_IS_NULL 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuid_is_null \- compare the value of the UUID to the NULL value
+.SH SYNOPSIS
+.nf
+.B #include <uuid/uuid.h>
+.sp
+.BI "int uuid_is_null(uuid_t " uu );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_is_null
+function compares the value of the supplied UUID variable
+.I uu
+to the NULL value. If the value is equal to the NULL UUID, 1 is returned,
+otherwise 0 is returned.
+.SH AUTHOR
+.B uuid_is_null
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B uuid_is_null
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_time (3)
diff --git a/kexi/3rdparty/uuid/uuid_parse.3.in b/kexi/3rdparty/uuid/uuid_parse.3.in
new file mode 100644
index 000000000..d0b646d29
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_parse.3.in
@@ -0,0 +1,50 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_PARSE 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuid_parse \- convert an input UUID string into the libuuid internal format
+.SH SYNOPSIS
+.nf
+.B #include <uuid/uuid.h>
+.sp
+.BI "int uuid_parse( char *" in ", uuid_t " uu );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_parse
+function converts the UUID string given by
+.I in
+into the internal
+.B uuid_t
+format. The input UUID is a string of the form
+1b4e28ba\-2fa1\-11d2\-883f\-b9a761bde3fb (in
+.BR printf (3)
+format "%08x\-%04x\-%04x\-%04x\-%012x", 36 bytes plus the trailing '\\0').
+.SH RETURN VALUE
+Upon successfully parsing the input string, 0 is returned, and the UUID is
+stored in the location pointed to by
+.IR uu ,
+otherwise \-1 is returned.
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+.B uuid_parse
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B uuid_parse
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_time (3),
+.BR uuid_unparse (3)
diff --git a/kexi/3rdparty/uuid/uuid_time.3.in b/kexi/3rdparty/uuid/uuid_time.3.in
new file mode 100644
index 000000000..b85aa233c
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_time.3.in
@@ -0,0 +1,52 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_TIME 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuid_time \- extract the time at which the UUID was created
+.SH SYNOPSIS
+.nf
+.B #include <uuid/uuid.h>
+.sp
+.BI "time_t uuid_time(uuid_t " uu ", struct timeval *" ret_tv )
+.fi
+.SH DESCRIPTION
+The
+.B uuid_time
+function extracts the time at which the supplied UUID
+.I uu
+was created. Note that the UUID creation time is encoded within the UUID,
+and this function can only reasonably expect to extract the creation time
+for UUIDs created with the
+.BR uuid_generate (3)
+function. It may or may not work with UUIDs created by OSF DCE
+.BR uuidgen .
+.SH "RETURN VALUES"
+The time at which the UUID was created, in seconds since January 1, 1970 GMT
+(the epoch), is returned (see
+.BR time "(2))."
+The time at which the UUID was created, in seconds and microseconds since
+the epoch, is also stored in the location pointed to by
+.I ret_tv
+(see
+.BR gettimeofday "(2))."
+.SH AUTHOR
+.B uuid_time
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B uuid_time
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_unparse (3)
diff --git a/kexi/3rdparty/uuid/uuid_time.c b/kexi/3rdparty/uuid/uuid_time.c
new file mode 100644
index 000000000..c19af48c9
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_time.c
@@ -0,0 +1,138 @@
+/*
+ * uuid_time.c --- Interpret the time field from a uuid. This program
+ * violates the UUID abstraction barrier by reaching into the guts
+ * of a UUID and interpreting it.
+ *
+ * Copyright (C) 1998, 1999 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU
+ * Library General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include "uuidP.h"
+
+time_t uuid_time(const uuid_t uu, struct timeval *ret_tv)
+{
+ struct uuid uuid;
+ __u32 high;
+ struct timeval tv;
+ unsigned longlong clock_reg;
+
+ uuid_unpack(uu, &uuid);
+
+ high = uuid.time_mid | ((uuid.time_hi_and_version & 0xFFF) << 16);
+ clock_reg = uuid.time_low | ((unsigned longlong) high << 32);
+
+ clock_reg -= (((unsigned longlong) 0x01B21DD2) << 32) + 0x13814000;
+ tv.tv_sec = clock_reg / 10000000;
+ tv.tv_usec = (clock_reg % 10000000) / 10;
+
+ if (ret_tv)
+ *ret_tv = tv;
+
+ return tv.tv_sec;
+}
+
+int uuid_type(const uuid_t uu)
+{
+ struct uuid uuid;
+
+ uuid_unpack(uu, &uuid);
+ return ((uuid.time_hi_and_version >> 12) & 0xF);
+}
+
+int uuid_variant(const uuid_t uu)
+{
+ struct uuid uuid;
+ int var;
+
+ uuid_unpack(uu, &uuid);
+ var = uuid.clock_seq;
+
+ if ((var & 0x8000) == 0)
+ return UUID_VARIANT_NCS;
+ if ((var & 0x4000) == 0)
+ return UUID_VARIANT_DCE;
+ if ((var & 0x2000) == 0)
+ return UUID_VARIANT_MICROSOFT;
+ return UUID_VARIANT_OTHER;
+}
+
+#ifdef DEBUG
+static const char *variant_string(int variant)
+{
+ switch (variant) {
+ case UUID_VARIANT_NCS:
+ return "NCS";
+ case UUID_VARIANT_DCE:
+ return "DCE";
+ case UUID_VARIANT_MICROSOFT:
+ return "Microsoft";
+ default:
+ return "Other";
+ }
+}
+
+
+int
+main(int argc, char **argv)
+{
+ uuid_t buf;
+ time_t time_reg;
+ struct timeval tv;
+ int type, variant;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s uuid\n", argv[0]);
+ exit(1);
+ }
+ if (uuid_parse(argv[1], buf)) {
+ fprintf(stderr, "Invalid UUID: %s\n", argv[1]);
+ exit(1);
+ }
+ variant = uuid_variant(buf);
+ type = uuid_type(buf);
+ time_reg = uuid_time(buf, &tv);
+
+ printf("UUID variant is %d (%s)\n", variant, variant_string(variant));
+ if (variant != UUID_VARIANT_DCE) {
+ printf("Warning: This program only knows how to interpret "
+ "DCE UUIDs.\n\tThe rest of the output is likely "
+ "to be incorrect!!\n");
+ }
+ printf("UUID type is %d", type);
+ switch (type) {
+ case 1:
+ printf(" (time based)\n");
+ break;
+ case 2:
+ printf(" (DCE)\n");
+ break;
+ case 3:
+ printf(" (name-based)\n");
+ break;
+ case 4:
+ printf(" (random)\n");
+ break;
+ default:
+ printf("\n");
+ }
+ if (type != 1) {
+ printf("Warning: not a time-based UUID, so UUID time "
+ "decoding will likely not work!\n");
+ }
+ printf("UUID time is: (%ld, %ld): %s\n", tv.tv_sec, tv.tv_usec,
+ ctime(&time_reg));
+
+ return 0;
+}
+#endif
diff --git a/kexi/3rdparty/uuid/uuid_types.h.in b/kexi/3rdparty/uuid/uuid_types.h.in
new file mode 100644
index 000000000..ba67feaf9
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_types.h.in
@@ -0,0 +1,51 @@
+/*
+ * If linux/types.h is already been included, assume it has defined
+ * everything we need. (cross fingers) Other header files may have
+ * also defined the types that we need.
+ */
+#if (!defined(_LINUX_TYPES_H) && !defined(_BLKID_TYPES_H) && \
+ !defined(_UUID_TYPES) && !defined(_EXT2_TYPES_H))
+#define _UUID_TYPES_H
+
+typedef unsigned char __u8;
+typedef signed char __s8;
+
+#if (@SIZEOF_INT@ == 8)
+typedef int __s64;
+typedef unsigned int __u64;
+#elif (@SIZEOF_LONG@ == 8)
+typedef long __s64;
+typedef unsigned long __u64;
+#elif (@SIZEOF_LONG_LONG@ == 8)
+#if defined(__GNUC__)
+typedef __signed__ long long __s64;
+#else
+typedef signed long long __s64;
+#endif
+typedef unsigned long long __u64;
+#endif
+
+#if (@SIZEOF_INT@ == 2)
+typedef int __s16;
+typedef unsigned int __u16;
+#elif (@SIZEOF_SHORT@ == 2)
+typedef short __s16;
+typedef unsigned short __u16;
+#else
+ ?==error: undefined 16 bit type
+#endif
+
+#if (@SIZEOF_INT@ == 4)
+typedef int __s32;
+typedef unsigned int __u32;
+#elif (@SIZEOF_LONG@ == 4)
+typedef long __s32;
+typedef unsigned long __u32;
+#elif (@SIZEOF_SHORT@ == 4)
+typedef short __s32;
+typedef unsigned short __u32;
+#else
+ ?== error: undefined 32 bit type
+#endif
+
+#endif /* _*_TYPES_H */
diff --git a/kexi/3rdparty/uuid/uuid_unparse.3.in b/kexi/3rdparty/uuid/uuid_unparse.3.in
new file mode 100644
index 000000000..7099ed22a
--- /dev/null
+++ b/kexi/3rdparty/uuid/uuid_unparse.3.in
@@ -0,0 +1,40 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_UNPARSE 3 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuid_unparse \- output UUID variable in string format
+.SH SYNOPSIS
+.nf
+.B #include <uuid/uuid.h>
+.sp
+.BI "void uuid_unparse(uuid_t " uu ", char *" out );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_unparse
+function converts the supplied UUID
+.I uu
+from the internal binary format into a 36\-byte string (plus tailing '\\0')
+of the form 1b4e28ba\-2fa1\-11d2\-883f\-b9a76 and stores this value in the
+character string pointed to by
+.IR out .
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+.B uuid_unparse
+was written by Theodore Y. Ts'o for the ext2 filesystem utilties.
+.SH AVAILABILITY
+.B uuid_unparse
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuid_clear (3),
+.BR uuid_generate (3),
+.BR uuid_parse (3),
+.BR uuid_time (3)
diff --git a/kexi/CHANGES b/kexi/CHANGES
new file mode 100644
index 000000000..06dca7aaf
--- /dev/null
+++ b/kexi/CHANGES
@@ -0,0 +1,92 @@
+== Kexi CHANGES ==
+
+1.1.1 "Access Granted" - within KOffice 1.6.1
+ * Support for parameter queries in Design and SQL view;
+ user is asked for entering value of a parameter in a dialog window.
+ * Data-aware combo box is for use with forms and tabular views;
+ data source for the combo box can be defined in the Table Designer.
+
+1.1 "Access Granted" - within KOffice 1.6
+ (alpha 1: 2006-07-01, beta 1: 2006-09-9, rc 1: 2006-09-26, stable: 2006-10-10)
+ * Object data type and data-aware image form widget
+ * Support for macros (technology preview)
+ * Improvements in Auto Field form widget
+ * Table design altering without removing the existing data;
+ Undo and Redo commands are available. (experimental, switched off by default)
+ * Support for Data-aware combo boxes in the Table View, i.e. lookup columns
+ (experimental, switched off by default, there is no user interface
+ for designing the lookup columns; you can try the feature by downloading
+ a specially prepared database)
+ * Improvements in Table View, including support for default values and tooltips
+ for content that is too large for its cell size
+
+1.0 "Black Mamba" - within KOffice 1.5
+ (beta 1: 2006-01-31, beta 2: 2006-03-11, rc 1: 2006-03-29, stable: 2006-04-11)
+ * Improved data-aware forms.
+ * Form Designer's Data Source Pane for assigning data source to forms and
+ widgets. Object tree view for easier navigating within widgets hierarchy.
+ * Import from CSV files and pasting tabular data from clipboard. Export data
+ to CSV files and copying tabular data to clipboard. Automatic detection of
+ delimiters and column types.
+ * Improved server connection dialog. Stored connection data.
+ * Support for images in forms (stored as BLOBs).
+ * New form widget: multiline editor.
+ * Improved MS Access (mdb/mde) file import (optional plugin).
+ * Improved import of server databases to a file-based projects. Entire Kexi
+ projects (not only tables) can be imported too.
+ * Scripting plugin (Python and Ruby) to extend functionality, including
+ example script for HTML export.
+ * Simple printouts, print settings and print preview for table and query
+ data.
+ * Handbook added (incomplete).
+ * More than two hundreds of overall improvements and bug fixes.
+
+0.9 "Shorthorn" - released independent of KOffice (beta 1: 2005-05-07, stable: 2005-05-31)
+ * better translation for messages compared to 0.8 version, especialy in Form Designer
+
+0.8 - within KOffice 1.4 (beta 1: 2005-04-29, stable: 2005-06-14)
+ * Database forms are now officially supported
+ * Many improvements in handling server databases
+ * For users' convenience, Tabular Data View's behaviour is similar
+ to Form Data View's behaviour
+ * Data and project migration from existing data sources (SQLite, MySQL, PostgreSQL)
+ * Microsoft Access MDB database file import available as optional KEXIMDB plugin.
+ * Tens of overall improvements and hundreds of bugfixes.
+
+0.1 beta 5 "Halloween" (2004-10-01)
+ * Tens of improvements in KexiDB, Database Support Library (readded MySQL support,
+ SQLite3 support and migration to/from SQLite2 with unicode characters, renewed
+ SQL Parsing module)
+ * Much improved Form Designer (more widgets and editing actions, subforms,
+ KDevelop plugin mode, KFormDesigner mode)
+ * Much improved Table Designer (autonumbers, both Visual and SQL mode, parser)
+ * Fixes in Query Designer
+ * Many fixes in Data Table View (new actions, more convenient navigation
+ and data editing)
+ * Overall application improvements (more error/problem messages, actions,
+ new command line options, better startup handling)
+
+0.1 beta 4 "FireDuck" (2004-07-16)
+ * Much improved Form Designer (widget editing, additional tools)
+ * Much improved Query Designer (both Visual and SQL mode, parser)
+ * Fixes in Table Designer
+ * Many fixes in Data Table View (universal data editing widget)
+ * Overall application improvements (error/problem messages, loading/saving, actions)
+
+0.1 beta 3 "United Europe" (2004-05-01)
+ * Improved Table Designer
+ * Much improved general-purpose Data Table View
+ * Properties window added (now used for table designer, more to come)
+ * Consistent window closing, data saving, removing, editing, consistent questions
+ * Table designs (and other design types, in the future) are now saved
+ only when needed, on demand
+ * Renewed Query Designer
+
+0.1 beta 2 "Warsaw by Night" (2004-01-20)
+* database library rewrite
+* core rewrite
+* extended blob support
+ - entering text
+ - showing a thumbnail image
+ - showing a icon
+* syntaxhighlighting in query history
diff --git a/kexi/Makefile.am b/kexi/Makefile.am
new file mode 100644
index 000000000..e8f5c21b5
--- /dev/null
+++ b/kexi/Makefile.am
@@ -0,0 +1,77 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES =
+kdeinit_LTLIBRARIES = kexi.la
+bin_PROGRAMS =
+kexi_la_SOURCES = main.cpp
+kexi_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO)
+kexi_la_LIBADD = ./core/libkexicore.la ./main/libkeximain.la
+
+SUBDIRS= 3rdparty kexiutils kexidb core widget formeditor \
+data pics main migration plugins . tests examples tools
+
+#(will be reenabled after main/startup move)
+
+xdg_apps_DATA = kexi.desktop
+
+install-data-local:
+# $(mkinstalldirs) $(kde_servicetypesdir)/
+# $(INSTALL_DATA) $(srcdir)/kexiinterface.desktop $(kde_servicetypesdir)/kexiinterface.desktop
+# $(mkinstalldirs) $(kde_servicedir)
+# $(INSTALL_DATA) $(srcdir)/interfaces/mysqlinterface.desktop $(kde_servicesdir)/mysqlinterface.desktop
+
+# kde_appsdir Where your application's menu entry (.desktop) should go to.
+# kde_icondir Where your icon should go to - better use KDE_ICON.
+# kde_sounddir Where your sounds should go to.
+# kde_htmldir Where your docs should go to. (contains lang subdirs)
+# kde_datadir Where you install application data. (Use a subdir)
+# kde_locale Where translation files should go to. (contains lang subdirs)
+# kde_cgidir Where cgi-bin executables should go to.
+# kde_confdir Where config files should go to (system-wide ones with default values).
+# kde_mimedir Where mimetypes .desktop files should go to.
+# kde_servicesdir Where services .desktop files should go to.
+# kde_servicetypesdir Where servicetypes .desktop files should go to.
+# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON).
+# kde_wallpaperdir Where general wallpapers should go to.
+# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to.
+# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS.
+# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES.
+# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES.
+# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3).
+# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3).
+
+# set the include path for X, qt and KDE
+INCLUDES = $(LIB_KEXI_KMDI_INCLUDES) $(all_includes)
+
+METASOURCES = AUTO
+
+# Note: koffice/kexi/formeditor should be kept out of kexi.pot
+# as it might get moved out of Kexi.
+#
+# If a second directory must be "pruned", the syntax becomes:
+# find . \( -name formeditor -o -name test \) -prune , -name \*.ui
+# (The comma is needed or the pruned directory name would be printed to stdout)
+#
+# Temporary omitted files: kexifinddialog*
+messages: rc.cpp
+ rm -f tips.cpp
+# (cd data && $(PREPARETIPS) > ../tips.cpp)
+ EXCLUDE="-path ./formeditor -o -path ./doc \
+ -o -path ./tests -o -path ./plugins/macros/tests \
+ -o -path ./scriptingplugins -o -path ./plugins/scripting -o -path ./3rdparty"; \
+ LIST="data/*.rc `find . \( \( $$EXCLUDE \) -prune -o -name \*.ui \) -type f | grep -v -e '/\.'`"; \
+ if test -n "$$LIST"; then \
+ $(EXTRACTRC) $$LIST >> rc.cpp; \
+ fi; \
+ LIST=`find . \( \( $$EXCLUDE \) -prune -o -name \*.h -o -name \*.cpp \) -type f | grep -v -e '/\.' -e kexidswelcome.cpp`; \
+ if test -n "$$LIST"; then \
+ $(XGETTEXT) $$LIST -o $(podir)/kexi.pot; \
+ fi
+ rm -f tips.cpp
+
+
+DOXYGEN_EXCLUDE = 3rdparty kexidb/parser/sqlparser.h kexidb/drivers/odbc
+
+include $(top_srcdir)/admin/Doxyfile.am
+
+
diff --git a/kexi/Makefile.global b/kexi/Makefile.global
new file mode 100644
index 000000000..304c5fead
--- /dev/null
+++ b/kexi/Makefile.global
@@ -0,0 +1,31 @@
+KEXI_OPTIONS =
+
+if compile_kross
+ SCRIPTINGDEFINES=-DKEXI_SCRIPTS_SUPPORT
+endif
+
+AM_CPPFLAGS=$(SCRIPTINGDEFINES)
+
+KDE_CXXFLAGS = \
+$(USE_EXCEPTIONS) $(KEXI_OPTIONS)
+
+KDE_CXXFLAGS += -include $(top_srcdir)/kexi/kexi_global.h
+
+# temporary:
+##KDE_CXXFLAGS += -DKEXI_NO_AUTOFIELD_WIDGET
+
+# temporary unless cursor works properly in the Designer
+KDE_CXXFLAGS += -DKEXI_NO_CURSOR_PROPERTY
+
+# temporary: turn off processEvents() to avoid possible crashes
+# KDE_CXXFLAGS += -DKEXI_NO_PROCESS_EVENTS
+
+# temp: turn off advanced alter table
+##KDE_CXXFLAGS += -DKEXI_NO_UNDOREDO_ALTERTABLE
+
+KDE_CXXFLAGS += -DKEXI_NO_CTXT_HELP -DKEXI_NO_SUBFORM -DKEXI_DB_COMBOBOX_WIDGET -DDB_TEMPLATES
+
+# TODO: undefine this before release!
+KDE_CXXFLAGS += -DKEXI_DEBUG_GUI
+
+VER_INFO = -version-info 2:0:0
diff --git a/kexi/README b/kexi/README
new file mode 100644
index 000000000..f16ee9115
--- /dev/null
+++ b/kexi/README
@@ -0,0 +1,43 @@
+
+Introduction:
+=============
+
+Kexi is an integrated data management application. It can be used for
+creating database schemas, inserting data, performing queries, and
+processing data. Forms can be created to provide a custom interface to your
+data. All database objects - tables, queries and forms - are stored in the
+database, making it easy to share data and design.
+
+Kexi is Free/Libre/Open-Source Software. As a real member of the KDE and
+KOffice projects, Kexi integrates fluently into both. It is designed to be
+fully usable also without KDE on Linux/Unix and MS Windows platforms.
+
+Kexi is considered as a long awaited Open Source competitor for Microsoft
+Access, FileMaker and Oracle Forms. Its development is motivated by the
+lack of Rapid Application Development (RAD) tools for database systems that
+are sufficiently powerful, inexpensive, open standards driven and portable
+across many operating systems and hardware platforms.
+
+Contacts:
+=========
+
+Developers: http://kexi-project.org/contact.html
+
+User mailing list: mailto:kexi@kde.org
+ (subscribe at https://mail.kde.org/mailman/listinfo/kexi)
+
+Developer and advanced user mailing list: mailto:kexi-devel@kde.org
+ (subscribe at https://mail.kde.org/mailman/listinfo/kexi-devel)
+
+If you have questions about this README file or about KOffice in general,
+please mail to the KOffice mailing list: mailto:koffice@kde.org
+
+
+Links:
+======
+Kexi at Koffice.org: http://koffice.org/kexi/
+Kexi Project Home Page: http://kexi-project.org/
+
+--
+Have fun,
+Kexi Team
diff --git a/kexi/chartable.txt b/kexi/chartable.txt
new file mode 100644
index 000000000..a52b0b55e
--- /dev/null
+++ b/kexi/chartable.txt
@@ -0,0 +1,444 @@
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+ 0x0000, /* */
+
+ 0x0001, /* */
+
+ 0x0012, /* ! */
+ 0x0013, /* " */
+
+ 0x0014, /* # */
+ 0x0014, /* $ */
+ 0x0014, /* % */
+ 0x0014, /* & */
+
+ 0x0015, /* ' */
+ 0x0016, /* ( */
+ 0x0016, /* ) */
+ 0x0017, /* * */
+ 0x0017, /* + */
+ 0x0018, /* , */
+ 0x0017, /* - */
+ 0x0018, /* . */
+ 0x0017, /* / */
+
+ 0x0030, /* 0 */
+ 0x0031, /* 1 */
+ 0x0032, /* 2 */
+ 0x0033, /* 3 */
+ 0x0034, /* 4 */
+ 0x0035, /* 5 */
+ 0x0036, /* 6 */
+ 0x0037, /* 7 */
+ 0x0038, /* 8 */
+ 0x0039, /* 9 */
+
+ 0x0018, /* : */
+ 0x0018, /* ; */
+ 0x0019, /* < */
+ 0x0018, /* = */
+ 0x0019, /* > */
+ 0x0012, /* ? */
+ 0x0014, /* @ */
+
+ 0x0040, /* A */
+ 0x0050, /* B */
+ 0x0060, /* C */
+ 0x0070, /* D */
+ 0x0080, /* E */
+ 0x0090, /* F */
+ 0x00a0, /* G */
+ 0x00b0, /* H */
+ 0x00c0, /* I */
+ 0x00d0, /* J */
+ 0x00e0, /* K */
+ 0x00f0, /* L */
+ 0x0100, /* M */
+ 0x0110, /* N */
+ 0x0120, /* O */
+ 0x0130, /* P */
+ 0x0140, /* Q */
+ 0x0150, /* R */
+ 0x0160, /* S */
+ 0x0170, /* T */
+ 0x0180, /* U */
+ 0x0190, /* V */
+ 0x01a0, /* W */
+ 0x01b0, /* X */
+ 0x01c0, /* Y */
+ 0x01d0, /* Z */
+
+ 0x0016, /* [ */
+ 0x0017, /* \ */
+ 0x0016, /* ] */
+ 0x0014, /* ^ */
+ 0x0014, /* _ */
+ 0x0015, /* ` */
+
+ 0x0040, /* a */
+ 0x0050, /* b */
+ 0x0060, /* c */
+ 0x0070, /* d */
+ 0x0080, /* e */
+ 0x0090, /* f */
+ 0x00a0, /* g */
+ 0x00b0, /* h */
+ 0x00c0, /* i */
+ 0x00d0, /* j */
+ 0x00e0, /* k */
+ 0x00f0, /* l */
+ 0x0100, /* m */
+ 0x0110, /* n */
+ 0x0120, /* o */
+ 0x0130, /* p */
+ 0x0140, /* q */
+ 0x0150, /* r */
+ 0x0160, /* s */
+ 0x0170, /* t */
+ 0x0180, /* u */
+ 0x0190, /* v */
+ 0x01a0, /* w */
+ 0x01b0, /* x */
+ 0x01c0, /* y */
+ 0x01d0, /* z */
+
+ 0x0016, /* { */
+ 0x0014, /* | */
+ 0x0016, /* } */
+ 0x0014, /* ~ */
+
+// 0x7f
+ 0xffff, /*  */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+ 0xffff, /* */
+
+// 0xa0
+ 0x0014, /* */
+ 0x0012, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+
+ 0x0013, /* */
+
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+
+ 0x0013, /* */
+
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+ 0x0014, /* */
+
+ 0x0041, /* */ // A
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */
+
+ 0x0061, /* */
+
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+
+ 0x0071, /* */
+
+ 0x0111, /* */
+
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+
+ 0x00d7, /* */
+
+ 0x0121, /* */
+
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+
+ 0x0171, /* */
+ 0x0161, /* */
+
+ 0x0041, /* */ // a
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */ // a + 1
+ 0x0041, /* */ // a
+ 0x0041, /* */
+
+ 0x0061, /* */ // c
+
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+
+ 0x0071, /* */ // D
+
+ 0x0111, /* */ // N
+
+ 0x0121, /* */ // O
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+
+ 0x00f7, /* */ // ###
+
+ 0x0121, /* */ // O
+
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+
+ 0x01c1, /* */ // Y
+
+ 0x0171, /* */ // T
+
+ 0x01c1, /* */ // Y
+
+ 0x0041, /* */ // A
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */
+ 0x0041, /* */
+
+ 0x0061, /* C' */ // C
+ 0x0061, /* c' */
+ 0x0061, /* */
+ 0x0061, /* */
+ 0x0061, /* */
+ 0x0061, /* */
+ 0x0061, /* cH */
+ 0x0061, /* ch */
+
+ 0x007e, /* */ // D
+ 0x007f, /* */
+ 0x0070, /* Dj */
+ 0x0071, /* dj */
+
+ 0x0081, /* */ // E
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+ 0x0081, /* */
+
+ 0x00a1, /* */ // G
+ 0x00a1, /* */
+ 0x00a1, /* */
+ 0x00a1, /* */
+ 0x00a1, /* */
+ 0x00a1, /* */
+ 0x00a1, /* */
+ 0x00a1, /* */
+
+ 0x00b1, /* */ // H
+ 0x00b1, /* */
+ 0x00b1, /* */
+ 0x00b1, /* */
+
+ 0x00c1, /* */ // I
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+ 0x00c1, /* */
+
+ 0x00d1, /* */ // J
+ 0x00d1, /* */
+
+ 0x00e1, /* */ // K
+ 0x00e1, /* */
+ 0x00e1, /* */
+
+ 0x00f1, /* */ // L
+ 0x00f1, /* */
+ 0x00f1, /* */
+ 0x00f1, /* */
+ 0x00f1, /* */
+ 0x00f1, /* */
+ 0x00f1, /* */
+ 0x00f1, /* */
+ 0x00f1, /* */
+ 0x00f1, /* */
+
+ 0x0111, /* */ // N
+ 0x0111, /* */
+ 0x0111, /* */
+ 0x0111, /* */
+ 0x0111, /* */
+ 0x0111, /* */
+ 0x0111, /* */
+ 0x0111, /* */
+ 0x0111, /* */
+
+ 0x0121, /* */ // O
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+ 0x0121, /* */
+
+ 0x0151, /* */ // R
+ 0x0151, /* */
+ 0x0151, /* */
+ 0x0151, /* */
+ 0x0151, /* */
+ 0x0151, /* */
+
+ 0x0161, /* */ // S
+ 0x0161, /* */
+ 0x0161, /* */
+ 0x0161, /* */
+ 0x0161, /* */
+ 0x0161, /* */
+ 0x0161, /* Sh */
+ 0x0161, /* sh */
+
+ 0x0171, /* */ // T
+ 0x0171, /* */
+ 0x0171, /* */
+ 0x0171, /* */
+ 0x0171, /* */
+ 0x0171, /* */
+
+ 0x0181, /* */ // U
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+ 0x0181, /* */
+
+ 0x01a1, /* */ // W
+ 0x01a1, /* */
+
+ 0x01c1, /* v */ // Y
+ 0x01c1, /* w */
+ 0x01c1, /* x */
+
+ 0x01d1, /* y */
+ 0x01d1, /* z */
+ 0x01d1, /* { */
+ 0x01d1, /* | */
+ 0x01d1, /* Zh */
+ 0x01d1 /* zh */
diff --git a/kexi/configure.in.in b/kexi/configure.in.in
new file mode 100644
index 000000000..9e74cf350
--- /dev/null
+++ b/kexi/configure.in.in
@@ -0,0 +1,5 @@
+
+#KEXI_VERSION=
+AC_SUBST(LIB_KEXI_KMDI, '-lkmdi')
+AC_SUBST(LIB_KEXI_KMDI_INCLUDES, '')
+
diff --git a/kexi/core/Makefile.am b/kexi/core/Makefile.am
new file mode 100644
index 000000000..4a69fedaa
--- /dev/null
+++ b/kexi/core/Makefile.am
@@ -0,0 +1,41 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexicore.la
+libkexicore_la_SOURCES = kexi_global.cpp kexi.cpp kexiaboutdata.cpp \
+ keximainwindow.cpp \
+ kexidbconnectionset.cpp kexiprojectset.cpp \
+ kexiactionproxy.cpp kexiactioncategories.cpp kexisharedactionhost.cpp \
+ kexiproject.cpp kexidialogbase.cpp kexiviewbase.cpp \
+ kexipartmanager.cpp kexipartinfo.cpp kexipartitem.cpp kexipart.cpp \
+ kexiprojectdata.cpp kexiinternalpart.cpp \
+ kexidragobjects.cpp \
+ kexiuseraction.cpp kexiuseractionmethod.cpp \
+ kexistartupdata.cpp kexiguimsghandler.cpp kexitextmsghandler.cpp \
+ kexidataiteminterface.cpp kexievents.cpp \
+ kexidbshortcutfile.cpp \
+ kexiblobbuffer.cpp kexistaticpart.cpp \
+ kexitabledesignerinterface.cpp kexisearchandreplaceiface.cpp \
+ kexitemplateloader.cpp
+
+#kexipartdatasource.cpp
+
+libkexicore_la_LDFLAGS = $(KDE_RPATH) $(all_libraries) $(VER_INFO) -Wnounresolved -no-undefined
+
+SUBDIRS = .
+
+libkexicore_la_LIBADD = $(LIB_KEXI_KMDI) \
+ $(top_builddir)/kexi/kexiutils/libkexiutils.la \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la
+
+INCLUDES = $(LIB_KEXI_KMDI_INCLUDES) \
+ -I$(top_srcdir)/lib/kofficecore \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/kexi \
+ $(all_includes)
+
+noinst_HEADERS = kexiactionproxy_p.h kexicontexthelp_p.h kexisharedactionhost_p.h \
+ kexipartinfo_p.h
+
+METASOURCES = AUTO
diff --git a/kexi/core/kexi.cpp b/kexi/core/kexi.cpp
new file mode 100644
index 000000000..74e158f6a
--- /dev/null
+++ b/kexi/core/kexi.cpp
@@ -0,0 +1,348 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexi.h"
+#include "kexiaboutdata.h"
+#include "kexicmdlineargs.h"
+
+#include <kexiutils/identifier.h>
+#include <kexidb/msghandler.h>
+
+#include <qtimer.h>
+#include <qimage.h>
+#include <qpixmap.h>
+#include <qpixmapcache.h>
+#include <qcolor.h>
+#include <qfileinfo.h>
+
+#include <kdebug.h>
+#include <kcursor.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kiconeffect.h>
+#include <ksharedptr.h>
+#include <kmimetype.h>
+#include <kstaticdeleter.h>
+#include <kglobalsettings.h>
+
+using namespace Kexi;
+
+//! used for speedup
+//! @internal
+class KexiInternal : public KShared
+{
+ public:
+ KexiInternal() : KShared()
+ , connset(0)
+ , smallFont(0)
+ {
+ }
+ ~KexiInternal()
+ {
+ delete connset;
+ delete smallFont;
+ }
+ KexiDBConnectionSet* connset;
+ KexiProjectSet recentProjects;
+ KexiDBConnectionSet recentConnections;
+ KexiDB::DriverManager driverManager;
+ KexiPart::Manager partManager;
+ QFont *smallFont;
+};
+
+static KStaticDeleter<KexiInternal> Kexi_intDeleter;
+KexiInternal* _int = 0;
+
+#define _INIT_SHARED { if (!_int) Kexi_intDeleter.setObject( _int, new KexiInternal() ); }
+
+KexiDBConnectionSet& Kexi::connset()
+{
+ _INIT_SHARED;
+ //delayed
+ if (!_int->connset) {
+ //load stored set data, OK?
+ _int->connset = new KexiDBConnectionSet();
+ _int->connset->load();
+ }
+ return *_int->connset;
+}
+
+KexiProjectSet& Kexi::recentProjects() {
+ _INIT_SHARED;
+ return _int->recentProjects;
+}
+
+KexiDB::DriverManager& Kexi::driverManager()
+{
+ _INIT_SHARED;
+ return _int->driverManager;
+}
+
+KexiPart::Manager& Kexi::partManager()
+{
+ _INIT_SHARED;
+ return _int->partManager;
+}
+
+void Kexi::deleteGlobalObjects()
+{
+ delete _int;
+}
+
+//temp
+bool _tempShowForms = true;
+bool& Kexi::tempShowForms() {
+#ifndef KEXI_FORMS_SUPPORT
+ _tempShowForms = false;
+#endif
+ return _tempShowForms;
+}
+
+bool _tempShowReports = true;
+bool& Kexi::tempShowReports() {
+#ifndef KEXI_REPORTS_SUPPORT
+ _tempShowReports = false;
+#endif
+ return _tempShowReports;
+}
+
+bool _tempShowMacros = true;
+bool& Kexi::tempShowMacros() {
+#ifndef KEXI_MACROS_SUPPORT
+ _tempShowMacros = false;
+#endif
+ return _tempShowMacros;
+}
+
+bool _tempShowScripts = true;
+bool& Kexi::tempShowScripts() {
+#ifndef KEXI_SCRIPTS_SUPPORT
+ _tempShowScripts = false;
+#endif
+ return _tempShowScripts;
+}
+
+//--------------------------------------------------------------------------------
+
+QFont Kexi::smallFont(QWidget *init)
+{
+ _INIT_SHARED;
+ if (!_int->smallFont) {
+ _int->smallFont = new QFont( init->font() );
+ const int wdth = KGlobalSettings::desktopGeometry(init).width();
+ int size = 10 + QMAX(0, wdth - 1100) / 100;
+ size = QMIN( init->fontInfo().pixelSize(), size );
+ _int->smallFont->setPixelSize( size );
+ }
+ return *_int->smallFont;
+}
+
+//--------------------------------------------------------------------------------
+QString Kexi::nameForViewMode(int m)
+{
+ if (m==NoViewMode) return i18n("No View");
+ else if (m==DataViewMode) return i18n("Data View");
+ else if (m==DesignViewMode) return i18n("Design View");
+ else if (m==TextViewMode) return i18n("Text View");
+
+ return i18n("Unknown");
+}
+
+//--------------------------------------------------------------------------------
+
+QString Kexi::msgYouCanImproveData() {
+ return i18n("You can correct data in this row or use \"Cancel row changes\" function.");
+}
+
+//--------------------------------------------------------------------------------
+
+ObjectStatus::ObjectStatus()
+: msgHandler(0)
+{
+}
+
+ObjectStatus::ObjectStatus(const QString& message, const QString& description)
+: msgHandler(0)
+{
+ setStatus(message, description);
+}
+
+ObjectStatus::ObjectStatus(KexiDB::Object* dbObject, const QString& message, const QString& description)
+: msgHandler(0)
+{
+ setStatus(dbObject, message, description);
+}
+
+ObjectStatus::~ObjectStatus()
+{
+ delete msgHandler;
+}
+
+const ObjectStatus& ObjectStatus::status() const
+{
+ return *this;
+}
+
+bool ObjectStatus::error() const
+{
+ return !message.isEmpty()
+ || (dynamic_cast<KexiDB::Object*>((QObject*)dbObj) && dynamic_cast<KexiDB::Object*>((QObject*)dbObj)->error());
+}
+
+void ObjectStatus::setStatus(const QString& message, const QString& description)
+{
+ this->dbObj=0;
+ this->message=message;
+ this->description=description;
+}
+
+void ObjectStatus::setStatus(KexiDB::Object* dbObject, const QString& message, const QString& description)
+{
+ if (dynamic_cast<QObject*>(dbObject)) {
+ dbObj = dynamic_cast<QObject*>(dbObject);
+ }
+ this->message=message;
+ this->description=description;
+}
+
+void ObjectStatus::setStatus(KexiDB::ResultInfo* result, const QString& message, const QString& description)
+{
+ if (result) {
+ if (message.isEmpty())
+ this->message = result->msg;
+ else
+ this->message = message + " " + result->msg;
+
+ if (description.isEmpty())
+ this->description = result->desc;
+ else
+ this->description = description + " " + result->desc;
+ }
+ else
+ clearStatus();
+}
+
+void ObjectStatus::setStatus(KexiDB::Object* dbObject, KexiDB::ResultInfo* result,
+ const QString& message, const QString& description)
+{
+ if (!dbObject)
+ setStatus(result, message, description);
+ else if (!result)
+ setStatus(dbObject, message, description);
+ else {
+ setStatus(dbObject, message, description);
+ setStatus(result, this->message, this->description);
+ }
+}
+
+void ObjectStatus::clearStatus()
+{
+ message=QString::null;
+ description=QString::null;
+}
+
+QString ObjectStatus::singleStatusString() const {
+ if (message.isEmpty() || description.isEmpty())
+ return message;
+ return message + " " + description;
+}
+
+void ObjectStatus::append( const ObjectStatus& otherStatus ) {
+ if (message.isEmpty()) {
+ message = otherStatus.message;
+ description = otherStatus.description;
+ return;
+ }
+ const QString s( otherStatus.singleStatusString() );
+ if (s.isEmpty())
+ return;
+ if (description.isEmpty()) {
+ description = s;
+ return;
+ }
+ description = description + " " + s;
+}
+
+//! @internal
+class ObjectStatusMessageHandler : public KexiDB::MessageHandler
+{
+ public:
+ ObjectStatusMessageHandler(ObjectStatus *status)
+ : KexiDB::MessageHandler()
+ , m_status(status)
+ {
+ }
+ virtual ~ObjectStatusMessageHandler()
+ {
+ }
+
+ virtual void showErrorMessage(const QString &title,
+ const QString &details = QString::null)
+ {
+ m_status->setStatus(title, details);
+ }
+
+ virtual void showErrorMessage(KexiDB::Object *obj, const QString& msg = QString::null)
+ {
+ m_status->setStatus(obj, msg);
+ }
+
+ ObjectStatus *m_status;
+};
+
+ObjectStatus::operator KexiDB::MessageHandler*()
+{
+ if (!msgHandler)
+ msgHandler = new ObjectStatusMessageHandler(this);
+ return msgHandler;
+}
+
+void Kexi::initCmdLineArgs(int argc, char *argv[], KAboutData* aboutData)
+{
+ KAboutData *about = aboutData;
+ if (!about)
+ about = Kexi::createAboutData();
+#ifdef CUSTOM_VERSION
+# include "../custom_startup.h"
+#endif
+ KCmdLineArgs::init( argc, argv, about );
+ KCmdLineArgs::addCmdLineOptions( options );
+}
+
+void KEXI_UNFINISHED(const QString& feature_name, const QString& extra_text)
+{
+ QString msg;
+ if (feature_name.isEmpty())
+ msg = i18n("This function is not available for version %1 of %2 application.")
+ .arg(KEXI_VERSION_STRING)
+ .arg(KEXI_APP_NAME);
+ else {
+ QString feature_name_(feature_name);
+ msg = i18n("\"%1\" function is not available for version %2 of %3 application.")
+ .arg(feature_name_.replace("&",""))
+ .arg(KEXI_VERSION_STRING)
+ .arg(KEXI_APP_NAME);
+ }
+
+ QString extra_text_(extra_text);
+ if (!extra_text_.isEmpty())
+ extra_text_.prepend("\n");
+
+ KMessageBox::sorry(0, msg + extra_text_);
+}
diff --git a/kexi/core/kexi.h b/kexi/core/kexi.h
new file mode 100644
index 000000000..8490ca290
--- /dev/null
+++ b/kexi/core/kexi.h
@@ -0,0 +1,147 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_H
+#define KEXI_H
+
+#include <qguardedptr.h>
+#include <qfont.h>
+
+#include <kexi_version.h>
+#include "kexiprojectdata.h"
+#include "kexipartmanager.h"
+#include "kexidbconnectionset.h"
+#include "kexiprojectset.h"
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+
+namespace Kexi
+{
+ KEXICORE_EXPORT void initCmdLineArgs(int argc, char *argv[], KAboutData* aboutData = 0);
+
+ /*! Modes of view for the dialogs. Used mostly for parts and KexiDialogBase. */
+ enum ViewMode {
+ AllViewModes = 0, //!< Usable primarily in KexiPart::initInstanceActions()
+ NoViewMode = 0, //!< In KexiViewBase::afterSwitchFrom() and KexiViewBase::beforeSwitchTo()
+ //!< means that parent dialog of the view has not yet view defined.
+ DataViewMode = 1,
+ DesignViewMode = 2,
+ TextViewMode = 4 //!< Also known as SQL View Mode
+ };
+ //! i18n'ed name of view mode \a m
+ KEXICORE_EXPORT QString nameForViewMode(int m);
+
+ //! A set of known connections
+ KEXICORE_EXPORT KexiDBConnectionSet& connset();
+
+ //! A set avaiulable of project infos
+ KEXICORE_EXPORT KexiProjectSet& recentProjects();
+
+ //! shared driver manager
+ KEXICORE_EXPORT KexiDB::DriverManager& driverManager();
+
+ //! shared part manager
+ KEXICORE_EXPORT KexiPart::Manager& partManager();
+
+ //! can be called to delete global objects like driverManager and partManager
+ //! (and thus, all loaded factories/plugins)
+ //! before KLibrary::~KLibrary() do this for us
+ KEXICORE_EXPORT void deleteGlobalObjects();
+
+ //some temporary flags
+
+ //! false by default, flag loaded on main window startup
+ KEXICORE_EXPORT bool& tempShowForms();
+
+ //! false by default, flag loaded on main window startup
+ KEXICORE_EXPORT bool& tempShowReports();
+
+ //! false by default, flag loaded on main window startup
+ KEXICORE_EXPORT bool& tempShowMacros();
+
+ //! false by default, flag loaded on main window startup
+ KEXICORE_EXPORT bool& tempShowScripts();
+
+ /*! A global setting for minimal readable font.
+ Note: this is defined because KDE has no such setting yet.
+ \a init is a widget that should be passed if no qApp->mainWidget() is available yet. */
+ KEXICORE_EXPORT QFont smallFont(QWidget *init = 0);
+
+ /*! Helper class for storing object status. */
+ class KEXICORE_EXPORT ObjectStatus
+ {
+ public:
+ ObjectStatus();
+
+ ObjectStatus(const QString& message, const QString& description);
+
+ ObjectStatus(KexiDB::Object* dbObject, const QString& message, const QString& description);
+
+ ~ObjectStatus();
+
+ const ObjectStatus& status() const;
+
+ bool error() const;
+
+ void setStatus(const QString& message, const QString& description);
+
+ //! Note: for safety, \a dbObject needs to be derived from QObject,
+ //! otherwise it won't be assigned
+ void setStatus(KexiDB::Object* dbObject,
+ const QString& message = QString::null, const QString& description = QString::null);
+
+ void setStatus(KexiDB::ResultInfo* result,
+ const QString& message = QString::null, const QString& description = QString::null);
+
+ void setStatus(KexiDB::Object* dbObject, KexiDB::ResultInfo* result,
+ const QString& message = QString::null, const QString& description = QString::null);
+
+ void clearStatus();
+
+ QString singleStatusString() const;
+
+ void append( const ObjectStatus& otherStatus );
+
+ KexiDB::Object *dbObject() const { return dynamic_cast<KexiDB::Object*>((QObject*)dbObj); }
+
+ //! Helper returning pseudo handler that just updates this ObjectStatus object
+ //! by receiving a message
+ operator KexiDB::MessageHandler*();
+
+ QString message, description;
+ protected:
+ QGuardedPtr<QObject> dbObj; //! This is in fact KexiDB::Object
+ KexiDB::MessageHandler* msgHandler;
+ };
+
+ KEXICORE_EXPORT QString msgYouCanImproveData();
+
+}//namespace Kexi
+
+//! Displays information that feature "feature_name" is not availabe in the current application version
+KEXICORE_EXPORT void KEXI_UNFINISHED(const QString& feature_name, const QString& extra_text = QString::null);
+
+//! Like above - for use inside KexiActionProxy subclass - reuses feature name from shared action's text
+#define KEXI_UNFINISHED_SHARED_ACTION(action_name) \
+ KEXI_UNFINISHED(sharedAction(action_name) ? sharedAction(action_name)->text() : QString::null)
+
+#endif
diff --git a/kexi/core/kexi_global.cpp b/kexi/core/kexi_global.cpp
new file mode 100644
index 000000000..db0b9b60c
--- /dev/null
+++ b/kexi/core/kexi_global.cpp
@@ -0,0 +1,50 @@
+/* This file is part of the KOffice libraries
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ (version information based on kofficeversion.h)
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexi_version.h"
+
+using namespace Kexi;
+
+KEXICORE_EXPORT unsigned int version()
+{
+ return KEXI_VERSION;
+}
+
+KEXICORE_EXPORT unsigned int versionMajor()
+{
+ return KEXI_VERSION_MAJOR;
+}
+
+KEXICORE_EXPORT unsigned int versionMinor()
+{
+ return KEXI_VERSION_MINOR;
+}
+
+KEXICORE_EXPORT unsigned int versionRelease()
+{
+ return KEXI_VERSION_RELEASE;
+}
+
+KEXICORE_EXPORT const char *versionString()
+{
+ return KEXI_VERSION_STRING;
+}
+
diff --git a/kexi/core/kexiaboutdata.cpp b/kexi/core/kexiaboutdata.cpp
new file mode 100644
index 000000000..eeaaf07e4
--- /dev/null
+++ b/kexi/core/kexiaboutdata.cpp
@@ -0,0 +1,81 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiaboutdata.h"
+#include <kexi_version.h>
+#include <kdeversion.h>
+#include <kofficeversion.h> //only for KOFFICE_VERSION_STRING
+#include <klocale.h>
+
+static const char *description =
+ I18N_NOOP("Database creation for everyone")
+#ifndef CUSTOM_VERSION
+#ifdef KEXI_STANDALONE
+ "\n\n" I18N_NOOP("This is standalone version of the application distributed outside of KOffice suite.")
+#else
+ "\n\n" I18N_NOOP("This application version is distributed with KOffice suite.")
+#endif
+#endif
+ ;
+
+using namespace Kexi;
+
+KAboutData* Kexi::createAboutData()
+{
+ KAboutData *aboutData=new KAboutData( "kexi", KEXI_APP_NAME,
+ KEXI_VERSION_STRING
+#ifndef CUSTOM_VERSION
+ " (KOffice " KOFFICE_VERSION_STRING ")"
+#endif
+ , description,
+ KAboutData::License_LGPL_V2,
+ I18N_NOOP( "(c) 2002-2007, Kexi Team\n"
+ "(c) 2003-2007, OpenOffice Polska LLC\n"),
+ I18N_NOOP( "This software is developed by Kexi Team - an international group\n"
+ "of independent developers, with additional assistance and support\n"
+ "from the OpenOffice Polska company.\n\n"
+ "Visit the company Home Page: http://www.openoffice.com.pl"),
+ "http://www.koffice.org/kexi",
+ "submit@bugs.kde.org"
+ );
+ // authors sorted by last contribution date
+ aboutData->addAuthor("Jarosław Staniek / OpenOffice Polska", I18N_NOOP("Project maintainer & developer, design, KexiDB, commercially supported version, win32 port"), "js@iidea.pl");
+ aboutData->addAuthor("Lucijan Busch",I18N_NOOP("Former project maintainer & developer"), "lucijan@kde.org");
+ aboutData->addAuthor("Cedric Pasteur", I18N_NOOP("KexiPropertyEditor and FormDesigner"), "cedric.pasteur@free.fr");
+ aboutData->addAuthor("Adam Pigg", I18N_NOOP("PostgreSQL database driver, Migration module"), "adam@piggz.fsnet.co.uk");
+ aboutData->addAuthor("Martin Ellis", I18N_NOOP("Contributions for MySQL and KexiDB, fixes, Migration module, MDB support"), "martin.ellis@kdemail.net");
+ aboutData->addAuthor("Sebastian Sauer", I18N_NOOP("Scripting module (KROSS), Python language bindings, design"), "mail@dipe.org");
+ aboutData->addAuthor("Christian Nitschkowski", I18N_NOOP("Graphics effects, helper dialogs"), "segfault_ii@web.de");
+ aboutData->addAuthor("Peter Simonsson",I18N_NOOP("Former developer"),"psn@linux.se");
+ aboutData->addAuthor("Joseph Wenninger", I18N_NOOP("Original Form Designer, original user interface & much more"), "jowenn@kde.org");
+ aboutData->addAuthor("Seth Kurzenberg",I18N_NOOP("CQL++, SQL assistance"), "seth@cql.com");
+ aboutData->addAuthor("Laurent Montel", I18N_NOOP("Original code cleanings"), "montel@kde.org");
+ aboutData->addAuthor("Till Busch", I18N_NOOP("Bugfixes, original Table Widget"), "till@bux.at");
+ aboutData->addCredit("Daniel Molkentin",I18N_NOOP("Initial design improvements"), "molkentin@kde.org");
+ aboutData->addCredit("Kristof Borrey", I18N_NOOP("Icons and user interface research"), "kristof.borrey@skynet.be");
+ aboutData->addCredit("Tomas Krassnig", I18N_NOOP("Coffee sponsoring"), "tkrass05@hak1.at");
+ aboutData->addCredit("Paweł Wirecki / OpenOffice Polska", I18N_NOOP("Numerous bug reports, usability tests, technical support"), "");
+ aboutData->setTranslator(I18N_NOOP("_: NAME OF TRANSLATORS\nYour names"), I18N_NOOP("_: EMAIL OF TRANSLATORS\nYour emails"));
+#if defined(CUSTOM_VERSION) && defined(Q_WS_WIN)
+ aboutData->setProgramLogo(KEXI_APP_LOGO);
+#endif
+ return aboutData;
+}
diff --git a/kexi/core/kexiaboutdata.h b/kexi/core/kexiaboutdata.h
new file mode 100644
index 000000000..948906c56
--- /dev/null
+++ b/kexi/core/kexiaboutdata.h
@@ -0,0 +1,33 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KEXI_ABOU_DATA_
+#define _KEXI_ABOU_DATA_
+
+#include <kaboutdata.h>
+
+namespace Kexi {
+
+KEXICORE_EXPORT KAboutData* createAboutData();
+
+}
+
+#endif
diff --git a/kexi/core/kexiactioncategories.cpp b/kexi/core/kexiactioncategories.cpp
new file mode 100644
index 000000000..4f3993425
--- /dev/null
+++ b/kexi/core/kexiactioncategories.cpp
@@ -0,0 +1,149 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiactioncategories.h"
+
+#include <kstaticdeleter.h>
+#include <kdebug.h>
+
+#include <qmap.h>
+#include <qasciidict.h>
+
+namespace Kexi {
+
+//! @internal
+class ActionInternal
+{
+ public:
+ ActionInternal(int _categories)
+ : categories(_categories)
+ , supportedObjectTypes(0)
+ , allObjectTypesAreSupported(false)
+ {
+ }
+ ~ActionInternal() {
+ delete supportedObjectTypes;
+ }
+ int categories;
+ QMap<int, bool> *supportedObjectTypes;
+ bool allObjectTypesAreSupported : 1;
+};
+
+static KStaticDeleter<ActionCategories> Kexi_actionCategoriesDeleter;
+ActionCategories* Kexi_actionCategories = 0;
+
+//! @internal
+class ActionCategories::Private
+{
+ public:
+ Private()
+ {
+ actions.setAutoDelete(true);
+ }
+
+ QAsciiDict<ActionInternal> actions;
+};
+
+KEXICORE_EXPORT ActionCategories *actionCategories()
+{
+ if (!Kexi_actionCategories)
+ Kexi_actionCategoriesDeleter.setObject( Kexi_actionCategories, new ActionCategories() );
+ return Kexi_actionCategories;
+}
+
+}
+
+using namespace Kexi;
+
+//----------------------------------
+
+ActionCategories::ActionCategories()
+ : d( new Private() )
+{
+}
+
+ActionCategories::~ActionCategories()
+{
+ delete d;
+}
+
+void ActionCategories::addAction(const char* name, int categories,
+ KexiPart::ObjectTypes supportedObjectType1, KexiPart::ObjectTypes supportedObjectType2,
+ KexiPart::ObjectTypes supportedObjectType3, KexiPart::ObjectTypes supportedObjectType4,
+ KexiPart::ObjectTypes supportedObjectType5, KexiPart::ObjectTypes supportedObjectType6,
+ KexiPart::ObjectTypes supportedObjectType7, KexiPart::ObjectTypes supportedObjectType8)
+{
+ ActionInternal * a = d->actions.find( name );
+ if (a) {
+ a->categories |= categories;
+ }
+ else {
+ a = new ActionInternal(categories);
+ d->actions.insert(name, a);
+ }
+ if (supportedObjectType1) {
+ if (!a->supportedObjectTypes)
+ a->supportedObjectTypes = new QMap<int, bool>();
+ a->supportedObjectTypes->insert(supportedObjectType1, true);
+ if (supportedObjectType2) {
+ a->supportedObjectTypes->insert(supportedObjectType2, true);
+ if (supportedObjectType3) {
+ a->supportedObjectTypes->insert(supportedObjectType3, true);
+ if (supportedObjectType4) {
+ a->supportedObjectTypes->insert(supportedObjectType4, true);
+ if (supportedObjectType5) {
+ a->supportedObjectTypes->insert(supportedObjectType5, true);
+ if (supportedObjectType6) {
+ a->supportedObjectTypes->insert(supportedObjectType6, true);
+ if (supportedObjectType7) {
+ a->supportedObjectTypes->insert(supportedObjectType7, true);
+ if (supportedObjectType8) {
+ a->supportedObjectTypes->insert(supportedObjectType8, true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void ActionCategories::setAllObjectTypesSupported(const char* name, bool set)
+{
+ ActionInternal * a = d->actions.find( name );
+ if (a)
+ a->allObjectTypesAreSupported = set;
+ else
+ kexiwarn << "ActionCategories::setAllObjectTypesSupported(): no such action \"" << name << "\"" << endl;
+}
+
+int ActionCategories::actionCategories(const char* name) const
+{
+ const ActionInternal * a = d->actions.find( name );
+ return a ? a->categories : 0;
+}
+
+bool ActionCategories::actionSupportsObjectType(const char* name, KexiPart::ObjectTypes objectType) const
+{
+ const ActionInternal * a = d->actions.find( name );
+ if (a && a->allObjectTypesAreSupported)
+ return true;
+ return (a && a->supportedObjectTypes) ? a->supportedObjectTypes->contains(objectType) : false;
+}
diff --git a/kexi/core/kexiactioncategories.h b/kexi/core/kexiactioncategories.h
new file mode 100644
index 000000000..6e672133c
--- /dev/null
+++ b/kexi/core/kexiactioncategories.h
@@ -0,0 +1,108 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_ACTION_CATEGORIES_H
+#define KEXI_ACTION_CATEGORIES_H
+
+#include <ksharedptr.h>
+#include "kexipart.h"
+
+namespace Kexi {
+
+enum ActionCategory
+{
+ NoActionCategory = 0, //!< no category at all
+ GlobalActionCategory = 1, //!< global application action like editcopy;
+ //!< can be applied to focused widget (of many types)
+ PartItemActionCategory = 2,//!< action related to part item, e.g. data_execute;
+ //!< requires context, used only in the navigator
+ WindowActionCategory = 4 //!< action related to active window, which can display
+ //!< table, query, form, report...
+};
+
+//! @short A set of functions used to declare action categories
+/*! Note: we do not declare actions used in design/text view modes,
+ because the categories are used in the data view,
+ for now in the 'assign action to a push button' function. */
+class KEXICORE_EXPORT ActionCategories : public KShared
+{
+ public:
+ ActionCategories();
+ ~ActionCategories();
+
+ /*! Declares action \a name for categories \a category (a combination of ActionCategory enum values).
+ The categories is merged with the previous declaration (if any).
+ \a supportedObjectTypes can be specified for ActionCategory::WindowAction to declare what object types
+ the action allows, it is a combination of KexiPart::ObjectTypes enum values. */
+ void addAction(const char* name, int categories,
+ KexiPart::ObjectTypes supportedObjectType1 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType2 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType3 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType4 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType5 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType6 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType7 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType8 = (KexiPart::ObjectTypes)0);
+
+ void addGlobalAction(const char* name)
+ { addAction(name, Kexi::GlobalActionCategory); }
+
+ //! Convenience function for adding action of category "part item", uses \ref addAction().
+ void addPartItemAction(const char* name)
+ { addAction(name, Kexi::PartItemActionCategory); }
+
+ /*! Convenience function for adding action of category "window", uses \ref addAction().
+ \a supportedObjectTypes is a combination of KexiPart::ObjectTypes enum values describing
+ object types supported by the action. */
+ void addWindowAction(const char* name,
+ KexiPart::ObjectTypes supportedObjectType1 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType2 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType3 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType4 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType5 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType6 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType7 = (KexiPart::ObjectTypes)0,
+ KexiPart::ObjectTypes supportedObjectType8 = (KexiPart::ObjectTypes)0)
+ { addAction(name, Kexi::WindowActionCategory, supportedObjectType1, supportedObjectType2,
+ supportedObjectType3, supportedObjectType4, supportedObjectType5, supportedObjectType6,
+ supportedObjectType7, supportedObjectType8); }
+
+ /*! If \a set is true, action with name \a name will support any possible object type
+ that can be checked by actionSupportsObjectType().
+ Makes sense for action of category Kexi::WindowActionCategory. */
+ void setAllObjectTypesSupported(const char* name, bool set);
+
+ //! \return categories for action \a name (a combination of ActionCategory enum values).
+ //! If there is no such actions declared at all, -1 is returned.
+ int actionCategories(const char* name) const;
+
+ /*! \return true if action \a name supports \a objectType.
+ Only works for actions of WindowAction category. */
+ bool actionSupportsObjectType(const char* name, KexiPart::ObjectTypes objectType) const;
+ protected:
+ class Private;
+ Private *d;
+};
+
+//! \return ActionCategories singleton object
+KEXICORE_EXPORT ActionCategories *actionCategories();
+
+}
+
+#endif
diff --git a/kexi/core/kexiactionproxy.cpp b/kexi/core/kexiactionproxy.cpp
new file mode 100644
index 000000000..0dbcf6378
--- /dev/null
+++ b/kexi/core/kexiactionproxy.cpp
@@ -0,0 +1,282 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiactionproxy.h"
+#include "kexiactionproxy_p.h"
+
+#include <kdebug.h>
+#include <kaction.h>
+#include <kmainwindow.h>
+#include <kshortcut.h>
+
+#include <qwidget.h>
+#include <qsignal.h>
+#include <qiconset.h>
+
+KAction_setEnabled_Helper::KAction_setEnabled_Helper(KexiActionProxy* proxy)
+ : QObject(0,"KAction_setEnabled_Helper")
+ , m_proxy( proxy )
+{
+}
+
+void KAction_setEnabled_Helper::slotSetEnabled(bool enabled)
+{
+ if (sender()->inherits("KAction")) {
+ const KAction *a = static_cast<const KAction*>(sender());
+ m_proxy->setAvailable(a->name(), enabled);
+ }
+}
+
+//=======================
+
+KexiSharedActionConnector::KexiSharedActionConnector( KexiActionProxy* proxy, QObject *obj )
+ : m_proxy(proxy)
+ , m_object(obj)
+{
+}
+
+KexiSharedActionConnector::~KexiSharedActionConnector()
+{
+}
+
+void KexiSharedActionConnector::plugSharedAction(const char *action_name, const char *slot)
+{
+ m_proxy->plugSharedAction(action_name, m_object, slot);
+}
+
+void KexiSharedActionConnector::plugSharedActionToExternalGUI(
+ const char *action_name, KXMLGUIClient *client)
+{
+ m_proxy->plugSharedActionToExternalGUI(action_name, client);
+}
+
+void KexiSharedActionConnector::plugSharedActionsToExternalGUI(
+ const QValueList<QCString>& action_names, KXMLGUIClient *client)
+{
+ m_proxy->plugSharedActionsToExternalGUI(action_names, client);
+}
+
+
+//=======================
+
+KexiActionProxy::KexiActionProxy(QObject *receiver, KexiSharedActionHost *host)
+ : m_host( host ? host : &KexiSharedActionHost::defaultHost() )
+ , m_receiver(receiver)
+ , m_signals(47)
+ , m_actionProxyParent(0)
+ , m_signal_parent( 0, "signal_parent" )
+ , m_KAction_setEnabled_helper( new KAction_setEnabled_Helper(this) )
+ , m_focusedChild(0)
+{
+ m_signals.setAutoDelete(true);
+ m_sharedActionChildren.setAutoDelete(false);
+ m_alternativeActions.setAutoDelete(true);
+ m_host->plugActionProxy( this );
+}
+
+KexiActionProxy::~KexiActionProxy()
+{
+ QPtrListIterator<KexiActionProxy> it(m_sharedActionChildren);
+ //detach myself from every child
+ for (;it.current();++it) {
+ it.current()->setActionProxyParent_internal( 0 );
+ }
+ //take me from parent
+ if (m_actionProxyParent)
+ m_actionProxyParent->takeActionProxyChild( this );
+
+ m_host->takeActionProxyFor(m_receiver);
+
+ delete m_KAction_setEnabled_helper;
+}
+
+void KexiActionProxy::plugSharedAction(const char *action_name, QObject* receiver, const char *slot)
+{
+ if (!action_name)// || !receiver || !slot)
+ return;
+ QPair<QSignal*,bool> *p = m_signals[action_name];
+ if (!p) {
+ p = new QPair<QSignal*,bool>( new QSignal(&m_signal_parent), true );
+ m_signals.insert(action_name, p);
+ }
+ if (receiver && slot)
+ p->first->connect( receiver, slot );
+}
+
+void KexiActionProxy::unplugSharedAction(const char *action_name)
+{
+ QPair<QSignal*,bool> *p = m_signals.take(action_name);
+ if (!p)
+ return;
+ delete p->first;
+ delete p;
+}
+
+int KexiActionProxy::plugSharedAction(const char *action_name, QWidget* w)
+{
+ KAction *a = sharedAction(action_name);
+ if (!a) {
+ kdWarning() << "KexiActionProxy::plugSharedAction(): NO SUCH ACTION: " << action_name << endl;
+ return -1;
+ }
+ return a->plug(w);
+}
+
+void KexiActionProxy::unplugSharedAction(const char *action_name, QWidget* w)
+{
+ KAction *a = sharedAction(action_name);
+ if (!a) {
+ kdWarning() << "KexiActionProxy::unplugSharedAction(): NO SUCH ACTION: " << action_name << endl;
+ return;
+ }
+ a->unplug(w);
+}
+
+KAction* KexiActionProxy::plugSharedAction(const char *action_name, const QString& alternativeText, QWidget* w)
+{
+ KAction *a = sharedAction(action_name);
+ if (!a) {
+ kdWarning() << "KexiActionProxy::plugSharedAction(): NO SUCH ACTION: " << action_name << endl;
+ return 0;
+ }
+ QCString altName = a->name();
+ altName += "_alt";
+ KAction *alt_act = new KAction(alternativeText, a->iconSet(), a->shortcut(),
+ 0, 0, a->parent(), altName);
+ QObject::connect(alt_act, SIGNAL(activated()), a, SLOT(activate()));
+ alt_act->plug(w);
+
+//OK?
+ m_host->updateActionAvailable(action_name, true, m_receiver);
+
+ return alt_act;
+}
+
+void KexiActionProxy::plugSharedActionToExternalGUI(const char *action_name, KXMLGUIClient *client)
+{
+ KAction *a = client->action(action_name);
+ if (!a)
+ return;
+ plugSharedAction(a->name(), a, SLOT(activate()));
+
+ //update availability
+ setAvailable(a->name(), a->isEnabled());
+ //changes will be signaled
+ QObject::connect(a, SIGNAL(enabled(bool)), m_KAction_setEnabled_helper, SLOT(slotSetEnabled(bool)));
+}
+
+void KexiActionProxy::plugSharedActionsToExternalGUI(
+ const QValueList<QCString>& action_names, KXMLGUIClient *client)
+{
+ for (QValueList<QCString>::const_iterator it = action_names.constBegin(); it!=action_names.constEnd(); ++it) {
+ plugSharedActionToExternalGUI(*it, client);
+ }
+}
+
+bool KexiActionProxy::activateSharedAction(const char *action_name, bool alsoCheckInChildren)
+{
+ QPair<QSignal*,bool> *p = m_signals[action_name];
+ if (!p || !p->second) {
+ //try in children...
+ if (alsoCheckInChildren) {
+ QPtrListIterator<KexiActionProxy> it( m_sharedActionChildren );
+ for( ; it.current(); ++it ) {
+ if (it.current()->activateSharedAction( action_name, alsoCheckInChildren ))
+ return true;
+ }
+ }
+ return m_actionProxyParent ? m_actionProxyParent->activateSharedAction(action_name, false) : false; //last chance: parent
+ }
+ //activate in this proxy...
+ p->first->activate();
+ return true;
+}
+
+KAction* KexiActionProxy::sharedAction(const char* action_name)
+{
+ return m_host->mainWindow()->actionCollection()->action(action_name);
+}
+
+bool KexiActionProxy::isSupported(const char* action_name) const
+{
+ QPair<QSignal*,bool> *p = m_signals[action_name];
+ if (!p) {
+ //not supported explicitly - try in children...
+ if (m_focusedChild)
+ return m_focusedChild->isSupported(action_name);
+ QPtrListIterator<KexiActionProxy> it( m_sharedActionChildren );
+ for( ; it.current(); ++it ) {
+ if (it.current()->isSupported(action_name))
+ return true;
+ }
+ return false; //not suported
+ }
+ return p != 0;
+}
+
+bool KexiActionProxy::isAvailable(const char* action_name, bool alsoCheckInChildren) const
+{
+ QPair<QSignal*,bool> *p = m_signals[action_name];
+ if (!p) {
+ //not supported explicitly - try in children...
+ if (alsoCheckInChildren) {
+ if (m_focusedChild)
+ return m_focusedChild->isAvailable(action_name, alsoCheckInChildren);
+ QPtrListIterator<KexiActionProxy> it( m_sharedActionChildren );
+ for( ; it.current(); ++it ) {
+ if (it.current()->isSupported(action_name))
+ return it.current()->isAvailable(action_name, alsoCheckInChildren);
+ }
+ }
+ return m_actionProxyParent ? m_actionProxyParent->isAvailable(action_name, false) : false; //last chance: parent
+ }
+ //supported explicitly:
+ return p->second != 0;
+}
+
+void KexiActionProxy::setAvailable(const char* action_name, bool set)
+{
+ QPair<QSignal*,bool> *p = m_signals[action_name];
+ if (!p)
+ return;
+ p->second = set;
+ m_host->updateActionAvailable(action_name, set, m_receiver);
+}
+
+void KexiActionProxy::addActionProxyChild( KexiActionProxy* child )
+{
+ if (!child || child==this)
+ return;
+ child->setActionProxyParent_internal( this );
+ m_sharedActionChildren.append( child );
+}
+
+void KexiActionProxy::takeActionProxyChild( KexiActionProxy* child )
+{
+ if (m_sharedActionChildren.findRef( child ) != -1)
+ m_sharedActionChildren.take();
+}
+
+void KexiActionProxy::setActionProxyParent_internal( KexiActionProxy* parent )
+{
+ m_actionProxyParent = parent;
+}
+
+#include "kexiactionproxy_p.moc"
+
diff --git a/kexi/core/kexiactionproxy.h b/kexi/core/kexiactionproxy.h
new file mode 100644
index 000000000..63093d291
--- /dev/null
+++ b/kexi/core/kexiactionproxy.h
@@ -0,0 +1,190 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIACTIONPROXY_H
+#define KEXIACTIONPROXY_H
+
+#include <qguardedptr.h>
+#include <qasciidict.h>
+#include <qobject.h>
+#include <qpair.h>
+#include <qptrlist.h>
+
+#include <kaction.h>
+
+#include "kexiproject.h"
+#include "kexisharedactionhost.h"
+
+class QSignal;
+class KAction;
+class KXMLGUIClient;
+class KAction_setEnabled_Helper;
+class KexiActionProxy;
+
+//! Abstract helper class used to connect shared actions from outside of shared-action-aware object.
+/*! Methods like KexiActionProxy::plugSharedAction() are not public, but
+ sometimes there's need for plugging an object that implements KexiActionProxy interface
+ from outside.
+
+ Reimplement KexiSharedActionConnector: do all needed connections in the constructor.
+
+ For example, with KexiQueryDesignerSQLEditor class we're using KTextEdit
+ (or KTextEditor::View) that's not shared-action-aware. So it's needed to conenct
+ e.g. "edit_undo" shared action to undo() slot, and so on. It is impelmented in more
+ generic way by implementing KTextEdit_SharedActionConnector class,
+ so the conenction can be reused many times by just allocating KTextEdit_SharedActionConnector
+ object for any KTextEditor when required (not only within KexiQueryDesignerSQLEditor).
+*/
+//TODO add method for setAvailable()
+class KEXICORE_EXPORT KexiSharedActionConnector
+{
+ public:
+ /* Connects shared actions offered by \a proxy to \a obj. */
+ KexiSharedActionConnector(KexiActionProxy* proxy, QObject *obj);
+ ~KexiSharedActionConnector();
+
+ protected:
+ void plugSharedAction(const char *action_name, const char *slot);
+
+ void plugSharedActionToExternalGUI(const char *action_name, KXMLGUIClient *client);
+
+ void plugSharedActionsToExternalGUI(
+ const QValueList<QCString>& action_names, KXMLGUIClient *client);
+
+ KexiActionProxy* m_proxy;
+ QObject *m_object;
+};
+
+//! An interface that acts as proxy for shared actions within the application.
+/*!
+ For example, edit->copy action can be reused to copy different types of items.
+ Availability and meaning of given action depends on the context, while
+ the context changes e.g. when another window is activated.
+ This class is mostly used by subclassing in KexiDialogBase or KexiDockBase
+ - you can subclass in a similar way.
+*/
+
+class KEXICORE_EXPORT KexiActionProxy
+{
+ public:
+ /*! Constructs action proxy for object \a receiver, using \a host.
+ If \a host is NULL, KexiSharedActionHost::defaultHost() is used.
+ (you must be sure that it's true) -- it is casted to QObject and assigned as the receiver.*/
+ KexiActionProxy(QObject *receiver , KexiSharedActionHost *host = 0 );
+ virtual ~KexiActionProxy();
+
+ /*! Activates action named \a action_name for this proxy. If the action is executed
+ (accepted), true is returned. */
+ bool activateSharedAction(const char *action_name, bool alsoCheckInChildren = true);
+
+ /*! Sets host to \a host; rerely used. */
+ void setSharedActionHost(KexiSharedActionHost& host) { m_host = &host; }
+
+ /*! \return true, if action named \a action_name is enabled within the proxy.
+ False is returned either if the action is not available or is not supported.
+ \ sa isSupported() */
+ bool isAvailable(const char* action_name, bool alsoCheckInChildren = true) const;
+
+ /*! \return true, if action named \a action_name is supported by the proxy. */
+ bool isSupported(const char* action_name) const;
+
+ protected:
+ /*! Plugs shared action named \a action_name to slot \a slot in \a receiver.
+ \a Receiver is usually a child of _this_ widget. */
+ void plugSharedAction(const char *action_name, QObject* receiver, const char *slot);
+
+ void unplugSharedAction(const char *action_name);
+
+ /*! Typical version of plugAction() method -- plugs action named \a action_name
+ to slot \a slot in _this_ widget. */
+ inline void plugSharedAction(const char *action_name, const char *slot) {
+ plugSharedAction(action_name, m_receiver, slot);
+ }
+
+ /*! Plugs action named \a action_name to a widget \a w, so the action is visible on this widget
+ as an item. \a w will typically be a menu, popup menu or a toolbar.
+ Does nothing if no action found, so generally this is safer than just caling e.g.
+ <code> action("myaction")->plug(myPopup); </code>
+ \return index of inserted item, or -1 if there is not action with name \a action_name.
+ \sa action(), KAction::plug(QWidget*, int) */
+ int plugSharedAction(const char *action_name, QWidget* w);
+
+ void plugSharedActionToExternalGUI(const char *action_name, KXMLGUIClient *client);
+
+ void plugSharedActionsToExternalGUI(
+ const QValueList<QCString>& action_names, KXMLGUIClient *client);
+
+ /*! Unplugs action named \a action_name from a widget \a w.
+ \sa plugSharedAction(const char *action_name, QWidget* w) */
+ void unplugSharedAction(const char *action_name, QWidget* w);
+
+ /*! Like above, but creates alternative action as a copy of \a action_name,
+ with \a alternativeText set. When this action is activated, just original action
+ specified by \a action_name is activated. The aternative action has autmatically set name as:
+ action_name + "_alt".
+ \return newly created action or 0 if \a action_name not found. */
+ KAction* plugSharedAction(const char *action_name, const QString& alternativeText, QWidget* w);
+
+ /*! \return action named with \a name or NULL if there is no such action. */
+ virtual KAction* sharedAction(const char* action_name);
+
+ inline QObject *receiver() const { return m_receiver; }
+
+ virtual void setAvailable(const char* action_name, bool set);
+
+ /*! Adds \a child of this proxy. Children will receive activateSharedAction() event,
+ If activateSharedAction() "event" is not consumed by the main proxy,
+ we start to iterate over proxy children (in unspecified order) to and call
+ activateSharedAction() on every child until one of them accept the "event".
+
+ If proxy child is destroyed, it is automatically detached from its parent proxy.
+ Parent proxy is 0 by default. This pointer is properly cleared when parent proxy is destroyed. */
+ void addActionProxyChild( KexiActionProxy* child );
+
+ void takeActionProxyChild( KexiActionProxy* child );
+
+ KexiSharedActionHost *m_host;
+ QGuardedPtr<QObject> m_receiver;
+ QAsciiDict< QPair<QSignal*,bool> > m_signals;
+
+ QPtrList<KexiActionProxy> m_sharedActionChildren;
+
+ QPtrList<KAction> m_alternativeActions;
+
+ KexiActionProxy* m_actionProxyParent;
+
+ QObject m_signal_parent; //!< it's just to have common parent for owned signals
+
+ //! For internal use by plugSharedActionToExternalGUI()
+ KAction_setEnabled_Helper *m_KAction_setEnabled_helper;
+
+ public:
+ //! For internal use by addActionProxyChild(). \a parent can be 0.
+ void setActionProxyParent_internal( KexiActionProxy* parent );
+
+ //! @internal
+ KexiActionProxy *m_focusedChild;
+
+ friend class KexiSharedActionHost;
+ friend class KAction_setEnabled_Helper;
+ friend class KexiSharedActionConnector;
+};
+
+#endif
+
diff --git a/kexi/core/kexiactionproxy_p.h b/kexi/core/kexiactionproxy_p.h
new file mode 100644
index 000000000..735792997
--- /dev/null
+++ b/kexi/core/kexiactionproxy_p.h
@@ -0,0 +1,42 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIACTIONPROXY_P_H
+#define KEXIACTIONPROXY_P_H
+
+#include <qobject.h>
+
+class KexiActionProxy;
+
+//! Helper class for KexiActionProxy::plugSharedActionToExternalGUI() method.
+class KAction_setEnabled_Helper : public QObject
+{
+ Q_OBJECT
+ public:
+ KAction_setEnabled_Helper(KexiActionProxy* proxy);
+
+ public slots:
+ void slotSetEnabled(bool enabled);
+
+ protected:
+ KexiActionProxy *m_proxy;
+};
+
+#endif
+
diff --git a/kexi/core/kexiblobbuffer.cpp b/kexi/core/kexiblobbuffer.cpp
new file mode 100644
index 000000000..d66d69af3
--- /dev/null
+++ b/kexi/core/kexiblobbuffer.cpp
@@ -0,0 +1,373 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiblobbuffer.h"
+
+#include <assert.h>
+
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qbuffer.h>
+
+#include <kdebug.h>
+#include <kstaticdeleter.h>
+#include <kimageio.h>
+
+#include <kexidb/connection.h>
+
+static KStaticDeleter<KexiBLOBBuffer> m_bufferDeleter;
+static KexiBLOBBuffer* m_buffer = 0;
+
+//-----------------
+
+class KexiBLOBBuffer::Private
+{
+ public:
+ Private()
+ : maxId(0)
+ , inMemoryItems(1009)
+ , storedItems(1009)
+ , itemsByURL(1009)
+ {
+ }
+ Id_t maxId; //!< Used to compute maximal recently used identifier for unstored BLOB
+//! @todo will be changed to QHash<quint64, Item>
+ QIntDict<Item> inMemoryItems; //!< for unstored BLOBs
+ QIntDict<Item> storedItems; //!< for stored items
+ QDict<Item> itemsByURL;
+ QGuardedPtr<KexiDB::Connection> conn;
+};
+
+//-----------------
+
+KexiBLOBBuffer::Handle::Handle(Item* item)
+ : m_item(item)
+{
+ if (m_item)
+ m_item->refs++;
+}
+
+KexiBLOBBuffer::Handle::Handle(const Handle& handle)
+{
+ *this = handle;
+}
+
+KexiBLOBBuffer::Handle::Handle()
+ : m_item(0)
+{
+}
+
+KexiBLOBBuffer::Handle::~Handle()
+{
+ if (m_item) {
+ m_item->refs--;
+ if (m_item->refs<=0)
+ KexiBLOBBuffer::self()->removeItem(m_item->id, m_item->stored);
+ }
+}
+
+KexiBLOBBuffer::Handle& KexiBLOBBuffer::Handle::operator=(const Handle& handle)
+{
+ m_item = handle.m_item;
+ if (m_item)
+ m_item->refs++;
+ return *this;
+}
+
+void KexiBLOBBuffer::Handle::setStoredWidthID(KexiBLOBBuffer::Id_t id)
+{
+ if (!m_item)
+ return;
+ if (m_item->stored) {
+ kdWarning() << "KexiBLOBBuffer::Handle::setStoredWidthID(): object for id=" << id
+ << " is aleady stored" << endl;
+ return;
+ }
+
+ KexiBLOBBuffer::self()->takeItem(m_item);
+ m_item->id = id; //new id
+ m_item->stored = true;
+//! @todo What about other handles for this item?
+//! @todo They were assuming it's unstored item, but it's stored now....
+ KexiBLOBBuffer::self()->insertItem(m_item);
+}
+
+//-----------------
+
+KexiBLOBBuffer::Item::Item(const QByteArray& data, KexiBLOBBuffer::Id_t ident, bool _stored,
+ const QString& _name, const QString& _caption, const QString& _mimeType,
+ Id_t _folderId, const QPixmap& pixmap)
+ : name(_name), caption(_caption), mimeType(_mimeType), refs(0),
+ id(ident), folderId(_folderId), stored(_stored),
+ m_pixmapLoaded(new bool(false)/*workaround for pixmap() const*/)
+{
+ if (pixmap.isNull())
+ m_pixmap = new QPixmap();
+ else
+ m_pixmap = new QPixmap(pixmap);
+
+ if (data.isEmpty())
+ m_data = new QByteArray();
+ else
+ m_data = new QByteArray(data);
+}
+
+KexiBLOBBuffer::Item::~Item()
+{
+ kexipluginsdbg << "KexiBLOBBuffer::Item::~Item()" << endl;
+ delete m_pixmap;
+ m_pixmap = 0;
+ delete m_data;
+ m_data = 0;
+ delete m_pixmapLoaded;
+}
+
+QPixmap KexiBLOBBuffer::Item::pixmap() const
+{
+ if (!*m_pixmapLoaded && m_pixmap->isNull() && !m_data->isEmpty()) {
+ QString type( KImageIO::typeForMime(mimeType) );
+ if (!KImageIO::canRead( type ) || !m_pixmap->loadFromData(*m_data, type.latin1())) {
+ //! @todo inform about error?
+ }
+ *m_pixmapLoaded = true;
+ }
+ return *m_pixmap;
+}
+
+QByteArray KexiBLOBBuffer::Item::data() const
+{
+ if (!m_data->isEmpty())
+ return *m_data;
+
+ if (m_data->isEmpty() && m_pixmap->isNull())
+ return QByteArray();
+
+ if (m_data->isEmpty() && !m_pixmap->isNull()) {
+ //convert pixmap to byte array
+ //(do it only on demand)
+ QBuffer buffer( *m_data );
+ buffer.open( IO_WriteOnly );
+ m_pixmap->save( &buffer, mimeType.isEmpty() ? (const char*)"PNG"/*! @todo default? */ : mimeType.latin1() );
+ }
+ return *m_data;
+}
+
+//-----------------
+
+KexiBLOBBuffer::KexiBLOBBuffer()
+ : QObject()
+ , d(new Private())
+{
+ Q_ASSERT(!m_buffer);
+ d->inMemoryItems.setAutoDelete(true);
+ d->storedItems.setAutoDelete(true);
+}
+
+KexiBLOBBuffer::~KexiBLOBBuffer()
+{
+ delete d;
+}
+
+KexiBLOBBuffer::Handle KexiBLOBBuffer::insertPixmap(const KURL& url)
+{
+ if (url.isEmpty() )
+ return KexiBLOBBuffer::Handle();
+ if (!url.isValid()) {
+ kexipluginswarn << "::insertPixmap: INVALID URL '" << url << "'" << endl;
+ return KexiBLOBBuffer::Handle();
+ }
+//! @todo what about searching by filename only and then compare data?
+ Item * item = d->itemsByURL.find(url.prettyURL());
+ if (item)
+ return KexiBLOBBuffer::Handle(item);
+
+ QString fileName = url.isLocalFile() ? url.path() : url.prettyURL();
+//! @todo download the file if remote, then set fileName properly
+ QFile f(fileName);
+ if (!f.open(IO_ReadOnly)) {
+ //! @todo err msg
+ return KexiBLOBBuffer::Handle();
+ }
+ QString mimeType( KImageIO::mimeType( fileName ) );
+
+ QByteArray data( f.readAll() );
+ if (f.status()!=IO_Ok) {
+ //! @todo err msg
+ return KexiBLOBBuffer::Handle();
+ }
+ QFileInfo fi(url.fileName());
+ QString caption(fi.baseName().replace('_', " ").simplifyWhiteSpace());
+
+ item = new Item(data, ++d->maxId, /*!stored*/false, url.fileName(), caption, mimeType);
+ insertItem(item);
+
+ //cache
+ item->prettyURL = url.prettyURL();
+ d->itemsByURL.replace(url.prettyURL(), item);
+ return KexiBLOBBuffer::Handle(item);
+}
+
+KexiBLOBBuffer::Handle KexiBLOBBuffer::insertObject(const QByteArray& data,
+ const QString& name, const QString& caption, const QString& mimeType, KexiBLOBBuffer::Id_t identifier)
+{
+ KexiBLOBBuffer::Id_t newIdentifier;
+ if (identifier>0)
+ newIdentifier = identifier;
+ else
+ newIdentifier = ++d->maxId;
+
+ Item *item = new Item(data, newIdentifier, identifier>0, name, caption, mimeType);
+ insertItem( item );
+ return KexiBLOBBuffer::Handle(item);
+}
+
+KexiBLOBBuffer::Handle KexiBLOBBuffer::insertPixmap(const QPixmap& pixmap)
+{
+ if (pixmap.isNull())
+ return KexiBLOBBuffer::Handle();
+
+ Item * item = new Item(
+ QByteArray(), //(pixmap will be converted to byte array on demand)
+ ++d->maxId,
+ false, //not stored
+ QString::null,
+ QString::null,
+ "image/png", //!< @todo OK? What about jpegs?
+ 0, //folder id
+ pixmap);
+
+ insertItem(item);
+ return KexiBLOBBuffer::Handle(item);
+}
+
+KexiBLOBBuffer::Handle KexiBLOBBuffer::objectForId(Id_t id, bool stored)
+{
+ if (id<=0)
+ return KexiBLOBBuffer::Handle();
+ if (stored) {
+ Item *item = d->storedItems.find(id);
+ if (item || !d->conn)
+ return KexiBLOBBuffer::Handle(item);
+ //retrieve stored BLOB:
+
+//#if 0
+ assert(d->conn);
+ KexiDB::TableSchema *blobsTable = d->conn->tableSchema("kexi__blobs");
+ if (!blobsTable) {
+ //! @todo err msg
+ return KexiBLOBBuffer::Handle();
+ }
+/* QStringList where;
+ where << "o_id";
+ KexiDB::PreparedStatement::Ptr st = d->conn->prepareStatement(
+ KexiDB::PreparedStatement::SelectStatement, *blobsTable, where);*/
+//! @todo use PreparedStatement
+ KexiDB::QuerySchema schema;
+ schema.addField( blobsTable->field("o_data") );
+ schema.addField( blobsTable->field("o_name") );
+ schema.addField( blobsTable->field("o_caption") );
+ schema.addField( blobsTable->field("o_mime") );
+ schema.addField( blobsTable->field("o_folder_id") );
+ schema.addToWhereExpression(blobsTable->field("o_id"), QVariant((Q_LLONG)id));
+
+ KexiDB::RowData rowData;
+ tristate res = d->conn->querySingleRecord(
+ schema,
+// QString::fromLatin1("SELECT o_data, o_name, o_caption, o_mime FROM kexi__blobs where o_id=")
+// +QString::number(id),
+ rowData);
+ if (res!=true || rowData.size()<4) {
+ //! @todo err msg
+ kdWarning() << "KexiBLOBBuffer::objectForId("<<id<<","<<stored
+ <<"): res!=true || rowData.size()<4; res=="<<res.toString()<<" rowData.size()=="<<rowData.size()<< endl;
+ return KexiBLOBBuffer::Handle();
+ }
+
+ item = new Item(
+ rowData[0].toByteArray(),
+ id,
+ true, //stored
+ rowData[1].toString(),
+ rowData[2].toString(),
+ rowData[3].toString(),
+ (Id_t)rowData[4].toInt() //!< @todo folder id: fix Id_t for Qt4
+ );
+
+ insertItem(item);
+ return KexiBLOBBuffer::Handle(item);
+//#endif
+ }
+ else
+ return KexiBLOBBuffer::Handle(d->inMemoryItems.find(id));
+}
+
+KexiBLOBBuffer::Handle KexiBLOBBuffer::objectForId(Id_t id)
+{
+ KexiBLOBBuffer::Handle h(objectForId(id, false/*!stored*/));
+ if (h)
+ return h;
+ return objectForId(id, true/*stored*/);
+}
+
+void KexiBLOBBuffer::removeItem(Id_t id, bool stored)
+{
+ Item *item;
+ if (stored)
+ item = d->storedItems.take(id);
+ else
+ item = d->inMemoryItems.take(id);
+
+ if (item && !item->prettyURL.isEmpty()) {
+ d->itemsByURL.remove(item->prettyURL);
+ }
+ delete item;
+}
+
+void KexiBLOBBuffer::takeItem(Item *item)
+{
+ assert(item);
+ if (item->stored)
+ d->storedItems.take(item->id);
+ else
+ d->inMemoryItems.take(item->id);
+}
+
+void KexiBLOBBuffer::insertItem(Item *item)
+{
+ assert(item);
+ if (item->stored)
+ d->storedItems.insert(item->id, item);
+ else
+ d->inMemoryItems.insert(item->id, item);
+}
+
+void KexiBLOBBuffer::setConnection(KexiDB::Connection *conn)
+{
+ KexiBLOBBuffer::self()->d->conn = conn;
+}
+
+KexiBLOBBuffer* KexiBLOBBuffer::self()
+{
+ if(!m_buffer) {
+ m_bufferDeleter.setObject( m_buffer, new KexiBLOBBuffer() );
+ }
+ return m_buffer;
+}
+
+#include "kexiblobbuffer.moc"
diff --git a/kexi/core/kexiblobbuffer.h b/kexi/core/kexiblobbuffer.h
new file mode 100644
index 000000000..bd593ab26
--- /dev/null
+++ b/kexi/core/kexiblobbuffer.h
@@ -0,0 +1,223 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIBLOBBUFFER_H
+#define KEXIBLOBBUFFER_H
+
+#include <qobject.h>
+#include <qintdict.h>
+#include <qdict.h>
+#include <qpixmap.h>
+
+#include <kurl.h>
+
+namespace KexiDB
+{
+ class Connection;
+}
+
+//! Application-wide buffer for local BLOB data like pixmaps.
+/*! For now only pixmaps are supported
+ @todo support any KPart-compatible objects and more...
+
+ Use this class by acessing to its singleton: KexiBLOBBuffer::self().
+
+ This class is used for buffering BLOB data,
+ to avoid duplicating object's data in memory and a need for loading (decoding)
+ the same object many times.
+ The data is always local, what means database storage is not employed here.
+
+ Each BLOB instance is identified by an unsigned integer number (Id_t type),
+ uniquely generated on BLOB loading. Each BLOB can have assigned source url
+ it has been loaded from (the url can be empty though, e.g. for data coming from clipboard).
+
+ References to KexiBLOBBuffer are counted, so when last reference is lost, data is freed
+ and the integer identifier is no longer pointing any valid data.
+ KexiBLOBBuffer::Handle is value-based class that describes handle based in an identifier.
+ Objects of this class are obtained e.g. from insertPixmap() method.
+
+ There are two kinds of identifiers:
+ - integers assigned for BLOBs already saved to a db backend,
+ when KexiBLOBBuffer::Handle::stored() is true
+ - temporary integers assigned for new BLOBs not yet saved to a db backend,
+ when KexiBLOBBuffer::Handle::stored() is false
+ KexiBLOBBuffer::Handle::setStoredWidthID() can be used to switch from unstored to stored state.
+ Among others, the state has effect on saving forms: only unstored BLOBs will be saved back
+ to the database; when a BLOB needs to be removed, only it will be physically removed only if it was stored.
+
+ KexiBLOBBuffer is also useful for two more reasons:
+ - Property editor's item for "image" property displays a preview of pixmap contents.
+ Without buffering, it would be needed to load pixmap data again: what if the file
+ it is loaded from is located remote and connection is slow? Memory would be also unnecessary doubled.
+ - Undo/Redo framework requires to store previous property values. Having a reference defined
+ by a single interger, memory can be handled more effectively.
+
+ Example use cases:
+ A large pixmap file "abc.jpg" is loaded as QByteArray <b>once</b> and buffered:
+ integer identifier is returned.
+ Then, multiple image widgets are using "abc.jpg" for displaying.
+ Duplicating an image widget means only duplicating it's properties
+ like position and BLOB's id: BLOB itself (data of "abc.jpg") is not duplicated.
+ Creating a new image widget and assiging the same "abc.jpg" pixmap, means only
+ referencing KexiBLOBBuffer using the same identifier.
+*/
+class KEXICORE_EXPORT KexiBLOBBuffer : public QObject
+{
+ Q_OBJECT
+
+ private:
+ class Item;
+ public:
+ //! long integer for unique identifying blobs
+//! @todo Qt4: will be changed
+ typedef long Id_t;
+
+ //! Access to KexiBLOBBuffer singleton
+ static KexiBLOBBuffer* self();
+
+ static void setConnection(KexiDB::Connection *conn);
+
+ //! Object handle used by KexiBLOBBuffer
+ class KEXICORE_EXPORT Handle {
+ public:
+ //! Constructs a null handle.
+ //! Null handles have empty pixap and data members, id == 0 and cast to boolean false.
+ Handle();
+
+ //! Constructs a copy of \a handle.
+ Handle(const Handle& handle);
+
+ ~Handle();
+
+ Id_t id() const { return m_item ? m_item->id : 0; }
+
+ /*! \return true if this BLOB data pointed by this handle is stored at the db backend
+ or false if it is kept in memory. Null handles return false. */
+ bool stored() const { return m_item ? m_item->stored : false; }
+
+ //! \return true if this is null handle (i.e. one not pointing to any data)
+ operator bool() const { return m_item; }
+
+ Handle& operator=(const Handle& handle);
+
+ QByteArray data() const { return m_item ? m_item->data() : QByteArray(); }
+
+ QPixmap pixmap() const { return m_item ? m_item->pixmap() : QPixmap(); }
+
+ /*! Sets "stored" flag to true by setting non-temporary identifier.
+ Only call this method for unstored (in memory) BLOBs */
+ void setStoredWidthID(Id_t id);
+
+ QString originalFileName() const { return m_item ? m_item->name: QString::null; }
+
+ QString mimeType() const { return m_item ? m_item->mimeType : QString::null; }
+
+ Id_t folderId() const { return m_item ? m_item->folderId : 0; }
+
+ protected:
+ //! Constructs a handle based on \a item. Null handle is constructed for null \a item.
+ Handle(Item* item);
+ private:
+ Item* m_item;
+ friend class KexiBLOBBuffer;
+ };
+
+ //! @internal
+ KexiBLOBBuffer();
+
+ ~KexiBLOBBuffer();
+
+ /*! Inserts a new pixmap loaded from a file at \a url.
+ If the same file has already been loaded before, it can be found in cache
+ and returned instantly. It is assumed that the BLOB is unstored, because it is loaded from
+ external source, so stored() will be equal to false for returned handle.
+ \return handle to the pixmap data or a null handle if such pixmap could not be loaded. */
+ Handle insertPixmap(const KURL& url);
+
+ /*! Inserts a new BLOB data.
+ @param data The data for BLOB object.
+ @param name The name for the object, usually a file name or empty
+ @param caption The more friendly than name, can be based on file name or empty or
+ defined by a user (this case is not yet used)
+ @param mimeType The mimeType for the object for easier and mor accurate decoding.
+ @param identifier Object's identifier. If positive, the "stored" flag for the data
+ will be set to true with \a identifer, otherwise (the default) the BLOB data will
+ have "stored" flag set to false, and a new temporary identifier will be assigned. */
+ Handle insertObject(const QByteArray& data, const QString& name,
+ const QString& caption, const QString& mimeType, Id_t identifier = 0);
+
+ /*! Inserts a new pixmap available in memory, e.g. coming from clipboard. */
+ Handle insertPixmap(const QPixmap& pixmap);
+
+ /*! \return an object for a given \a id. If \a stored is true, stored BLOBs buffer
+ is browsed, otherwise unstored (in memory) BLOBs buffer is browsed.
+ If no object is cached for this id, null handle is returned. */
+ Handle objectForId(Id_t id, bool stored);
+
+ /*! \return an object for a given \a id. First, unstored object is checked, then unstored,
+ if stored was not found. */
+ Handle objectForId(Id_t id);
+
+ protected:
+ /*! Removes an object for a given \a id. If \a stored is true, stored BLOB is removed,
+ otherwise unstored (in memory) BLOB is removed. */
+ void removeItem(Id_t id, bool stored);
+
+ /*! Takes an object for a \a item out of the buffer. */
+ void takeItem(Item* item);
+
+ /*! Inserts an object for a given \a id into the buffer. */
+ void insertItem(Item* item);
+
+ private:
+ class KEXICORE_EXPORT Item {
+ public:
+ Item(const QByteArray& data, Id_t ident,
+ bool stored,
+ const QString& name = QString::null,
+ const QString& caption = QString::null,
+ const QString& mimeType = QString::null,
+ Id_t folderId = 0,
+ const QPixmap& pixmap = QPixmap());
+ ~Item();
+ QPixmap pixmap() const;
+ QByteArray data() const;
+// KexiBLOBBuffer* buf;
+// KURL url;
+ QString name;
+ QString caption; //!< @todo for future use within image gallery
+ QString mimeType;
+ uint refs;
+ Id_t id;
+ Id_t folderId;
+ bool stored : 1;
+ QString prettyURL; //!< helper
+ private:
+ QByteArray *m_data;
+ QPixmap *m_pixmap;
+ bool *m_pixmapLoaded; //!< *m_pixmapLoaded will be set in Info::pixmap(),
+ //!< to avoid multiple pixmap decoding when it previously failed
+ friend class KexiBLOBBuffer;
+ };
+ class Private;
+ Private *d;
+ friend class Handle;
+};
+
+#endif
diff --git a/kexi/core/kexicmdlineargs.h b/kexi/core/kexicmdlineargs.h
new file mode 100644
index 000000000..d67c5e610
--- /dev/null
+++ b/kexi/core/kexicmdlineargs.h
@@ -0,0 +1,181 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXICMDLINEARGS_H
+#define KEXICMDLINEARGS_H
+
+#include <kcmdlineargs.h>
+#include <klocale.h>
+
+static KCmdLineOptions options[] =
+{
+ { ":", I18N_NOOP("Options related to entire projects:"), 0 },
+ { "createdb", I18N_NOOP(
+ "Create a new, blank project using specified\n"
+ "database driver and database name\n"
+ "and exit immediately.\n"
+ "You will be asked for confirmation\n"
+ "if overwriting is needed."), 0 },
+ { "create-opendb", I18N_NOOP(
+ "Like --createdb, but also open newly\n"
+ "created database.\n"), 0 },
+ { "dropdb", I18N_NOOP(
+ "Drop (remove) a project using specified\n"
+ "database driver and database name.\n"
+ "You will be asked for confirmation."), 0 },
+ { "drv", 0, 0 },
+ { "dbdriver <name>", I18N_NOOP(
+ "Database driver to be used\n"
+ "for connecting to a database project\n"
+ "(SQLite by default).\n"
+ "Ignored if a shortcut filename\n"
+ "is provided."), 0 },
+ { "t", 0, 0 },
+ { "type <name>", I18N_NOOP(
+ "Specify the type of file provided as an argument.\n"
+ "This option is only useful if the filename does\n"
+ "not have a valid extension set and its type\n"
+ "cannot be determined unambiguously by examining\n"
+ "its contents.\n"
+ "This option is ignored if no file is specified as\n"
+ "an argument.\n"
+ "Available file types are:\n"
+ "- \"project\" for a project file (the default)\n"
+ "- \"shortcut\" for a shortcut file pointing to a\n"
+ " project.\n"
+ "- \"connection\" for database connection data.\n"
+ ), 0 },
+ { "conn", 0, 0 },
+ { "connection <shortcut_filename>", I18N_NOOP(
+ "\nSpecify a database connection shortcut .kexic\n"
+ "file containing connection data.\n"
+ "Can be used with --createdb or --create-opendb\n"
+ "for convenience instead of using options like \n"
+ "--user, --host or --port.\n"
+ "Note: Options like --user, --host have\n"
+ "precedence over settings defined in the shortcut\n"
+ "file." ), 0 },
+ { "readonly", I18N_NOOP(
+ "Specify that any database connections will\n"
+ "be performed without write support. This option\n"
+ "is ignored when \"createdb\" option is present,\n"
+ "otherwise the database could not be created."), 0 },
+ { "user-mode", I18N_NOOP(
+ "Start project in User Mode, regardless \n"
+ "of the project settings."), 0 },
+ { "design-mode", I18N_NOOP(
+ "Start project in Design Mode, regardless \n"
+ "of the project settings."), 0 },
+ { "show-navigator", I18N_NOOP(
+ "Show the Project Navigator side pane even\n"
+ "if Kexi runs in User Mode."), 0 },
+ { "skip-startup-dialog", I18N_NOOP(
+ "Skip displaying startup dialog window.\n"
+ "If there is no project name specified to open,\n"
+ "empty application window will appear."), 0 },
+
+ { ":", I18N_NOOP("Options related to opening objects within a project:"), 0 },
+ { "open [<object_type>:]<object_name>", I18N_NOOP(
+ "\nOpen object of type <object_type>\n"
+ "and name <object_name> from specified project\n"
+ "on application start.\n"
+ "<object_type>: is optional, if omitted - table\n"
+ "type is assumed.\n"
+ "Other object types can be query, report, form,\n"
+ "script (may be more or less, depending on your\n"
+ "plugins installed).\n"
+ "Use \"\" chars to specify names containing spaces.\n"
+ "Examples: --open MyTable,\n"
+ " --open query:\"My very big query\""), 0 },
+ { "design [<object_type>:]<object_name>", I18N_NOOP(
+ "\nLike --open, but the object will\n"
+ "be opened in Design Mode, if one is available."), 0 },
+ { "edittext [<object_type>:]<object_name>", I18N_NOOP(
+ "\nLike --open, but the object will\n"
+ "be opened in Text Mode, if one is available."), 0 },
+ { "exec", 0, 0 },
+ { "execute [<object_type>:]<object_name>", I18N_NOOP(
+ "\nStart execution of object of type <object_type>\n"
+ "and name <object_name> on application start.\n"
+ "<object_type>: is optional, if omitted - macro\n"
+ "type is assumed.\n"
+ "Other object types can be script (may be more\n"
+ "or less, depending on your plugins installed).\n"
+ "Use \"\" chars to specify names containing spaces."), 0 },
+ { "new <object_type>", I18N_NOOP(
+ "Start new object design of type <object_type>."), 0 },
+ { "print [<object_type>:]<object_name>", I18N_NOOP(
+ "\nOpen the Print dialog window for an object of type\n"
+ "<object_type> and name <object_name> in the specified\n"
+ "project when the application starts, for quick printing\n"
+ "of the object's data.\n"
+ "<object_type>: is optional; if omitted, table\n"
+ "type is assumed. Object type can also be query."), 0 },
+ { "print-preview [<object_type>:]<object_name>", I18N_NOOP(
+ "\nOpen Print Preview window for object\n"
+ "of type <object_type> and name <object_name>\n"
+ "from specified project on application start.\n"
+ "See --print for more details."), 0 },
+
+ { ":", I18N_NOOP("Options related to database servers:"), 0 },
+ { "u", 0, 0 },
+ { "user <name>", I18N_NOOP(
+ "User name to be used\n"
+ "for connecting to a database project.\n"
+ "Ignored if a shortcut filename\n"
+ "is provided."), 0 },
+/* { "password <password>", I18N_NOOP(
+ "User password to be used\n"
+ "for connecting to a database project.\n"
+ "Ignored if a shortcut filename\n"
+ "is provided."), 0 },*/
+ { "h", 0, 0 },
+ { "host <name>", I18N_NOOP(
+ "Server (host) name to be used\n"
+ "for connecting to a database project.\n"
+ "Ignored if a shortcut filename\n"
+ "is provided."), 0 },
+ { "port <number>", I18N_NOOP(
+ "Server's port number to be used\n"
+ "for connecting to a database project.\n"
+ "Ignored if a shortcut filename\n"
+ "is provided."), 0 },
+ { "local-socket <filename>", I18N_NOOP(
+ "Server's local socket filename\n"
+ "to be used for connecting to a database\n"
+ "project. Ignored if a shortcut filename\n"
+ "is provided."), 0 },
+ { "skip-conn-dialog", I18N_NOOP(
+ "Skip displaying connection dialog window\n"
+ "and connect directly. Available when\n"
+ "opening .kexic or .kexis shortcut files."), 0 },
+
+ { "+[project-name]", I18N_NOOP(
+ "Kexi database project filename,\n"
+ "Kexi shortcut filename,\n"
+ "or name of a Kexi database\n"
+ "project on a server to open."), 0 },
+ // INSERT YOUR COMMANDLINE OPTIONS HERE
+ KCmdLineLastOption
+};
+
+#endif
+
diff --git a/kexi/core/kexicontexthelp.cpp b/kexi/core/kexicontexthelp.cpp
new file mode 100644
index 000000000..4ccfa2fee
--- /dev/null
+++ b/kexi/core/kexicontexthelp.cpp
@@ -0,0 +1,49 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Joseph Wenninger <jowenn@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicontexthelp.h"
+#include <KoContextCelp.h>
+#include <kxmlguiclient.h>
+#include <kapplication.h>
+#include <klocale.h>
+#include <kaction.h>
+#include <qlayout.h>
+#include <kdebug.h>
+#include <kiconloader.h>
+
+KexiContextHelp::KexiContextHelp(KexiMainWindow *view, QWidget *parent)
+ :KoContextHelpWidget(parent,"kexi_contexthelp")
+{
+ kdDebug()<<"KexiContextHelp::KexiContextHelp()"<<endl;
+ setCaption(i18n("Context Help"));
+ setIcon(SmallIcon("help"));
+ connect(this,SIGNAL(linkClicked( const QString& )),
+ this,SLOT(linkClickedInternal( const QString& )));
+}
+
+void KexiContextHelp::linkClickedInternal(const QString& link) {
+ kdDebug()<<"KexiContextHelp: Link: "<<link<<endl;
+ unhandledLink(link);
+}
+
+KexiContextHelp::~KexiContextHelp()
+{
+}
+
+#include "kexicontexthelp.moc"
diff --git a/kexi/core/kexicontexthelp.h b/kexi/core/kexicontexthelp.h
new file mode 100644
index 000000000..3a97e20c1
--- /dev/null
+++ b/kexi/core/kexicontexthelp.h
@@ -0,0 +1,41 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Joseph Wenninger <jowenn@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXICONTEXTHELP_H
+#define KEXICONTEXTHELP_H
+
+
+#include "keximainwindow.h"
+#include <KoContextCelp.h>
+
+class KEXICORE_EXPORT KexiContextHelp : public KoContextHelpWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiContextHelp(KexiMainWindow *view, QWidget *parent=0);
+ ~KexiContextHelp();
+ private slots:
+ void linkClickedInternal(const QString &link);
+
+ signals:
+ void unhandledLink( const QString& link );
+};
+
+#endif
diff --git a/kexi/core/kexicontexthelp_p.h b/kexi/core/kexicontexthelp_p.h
new file mode 100644
index 000000000..ab9af1665
--- /dev/null
+++ b/kexi/core/kexicontexthelp_p.h
@@ -0,0 +1,35 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Joseph Wenninger <jowenn@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _kexicontexthelp_p_h_
+#define _kexicontexthelp_p_h_
+
+class KexiContextHelpInfo {
+public:
+ KexiContextHelpInfo() {
+ caption="";
+ text="";
+ iconName="";
+ }
+ QString caption;
+ QString text;
+ QString iconName;
+
+};
+#endif
diff --git a/kexi/core/kexidataiteminterface.cpp b/kexi/core/kexidataiteminterface.cpp
new file mode 100644
index 000000000..8fe78c853
--- /dev/null
+++ b/kexi/core/kexidataiteminterface.cpp
@@ -0,0 +1,146 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidataiteminterface.h"
+
+#include <kdebug.h>
+
+KexiDataItemChangesListener::KexiDataItemChangesListener()
+{
+}
+
+KexiDataItemChangesListener::~KexiDataItemChangesListener()
+{
+}
+
+//-----------------------------------------------
+
+KexiDataItemInterface::KexiDataItemInterface()
+ : m_listener(0)
+ , m_listenerIsQObject(false)
+ , m_parentDataItemInterface(0)
+ , m_hasFocusableWidget(true)
+ , m_disable_signalValueChanged(false)
+ , m_acceptEditorAfterDeleteContents(false)
+{
+}
+
+KexiDataItemInterface::~KexiDataItemInterface()
+{
+}
+
+void KexiDataItemInterface::setValue(const QVariant& value, const QVariant& add,
+ bool removeOld, const QVariant* visibleValue)
+{
+ m_disable_signalValueChanged = true; //to prevent emmiting valueChanged()
+//needed? clear();
+ if (dynamic_cast<QObject*>(this)) {
+ kdDebug() << "KexiDataItemInterface::setValue(): " <<
+ dynamic_cast<QObject*>(this)->className() << " "
+ << dynamic_cast<QWidget*>(this)->name()
+ << " value=" << value << " add=" << add << endl;
+ }
+ m_origValue = value;
+ setValueInternal(add, removeOld);
+ if (visibleValue)
+ setVisibleValueInternal(*visibleValue);
+ m_disable_signalValueChanged = false;
+}
+
+void KexiDataItemInterface::setVisibleValueInternal(const QVariant& value)
+{
+ Q_UNUSED(value);
+}
+
+void KexiDataItemInterface::signalValueChanged()
+{
+ if (m_disable_signalValueChanged || isReadOnly())
+ return;
+ if (m_parentDataItemInterface) {
+ m_parentDataItemInterface->signalValueChanged();
+ return;
+ }
+ if (m_listener) {
+ beforeSignalValueChanged();
+ m_listener->valueChanged(this);
+ }
+}
+
+bool KexiDataItemInterface::valueChanged()
+{
+// bool ok;
+// kdDebug() << m_origValue.toString() << " ? " << value(ok).toString() << endl;
+// return (m_origValue != value(ok)) && ok;
+ kdDebug() << "KexiDataItemInterface::valueChanged(): " << m_origValue.toString() << " ? " << value().toString() << endl;
+ return m_origValue != value();
+}
+
+/*
+void KexiDataItemInterface::setValue(const QVariant& value)
+{
+ m_disable_signalValueChanged = true; //to prevent emmiting valueChanged()
+ setValueInternal( value );
+ m_disable_signalValueChanged = false;
+}*/
+
+KexiDataItemChangesListener* KexiDataItemInterface::listener()
+{
+ if (!m_listener || !m_listenerIsQObject)
+ return m_listener;
+ if (!m_listenerObject)
+ m_listener = 0; //destroyed, update pointer
+ return m_listener;
+}
+
+void KexiDataItemInterface::installListener(KexiDataItemChangesListener* listener)
+{
+ m_listener = listener;
+ m_listenerIsQObject = dynamic_cast<QObject*>(listener);
+ if (m_listenerIsQObject)
+ m_listenerObject = dynamic_cast<QObject*>(listener);
+}
+
+void KexiDataItemInterface::showFocus( const QRect& r, bool readOnly )
+{
+ Q_UNUSED(r);
+ Q_UNUSED(readOnly);
+}
+
+void KexiDataItemInterface::hideFocus()
+{
+}
+
+void KexiDataItemInterface::clickedOnContents()
+{
+}
+
+bool KexiDataItemInterface::valueIsValid()
+{
+ return true;
+}
+
+void KexiDataItemInterface::setParentDataItemInterface(KexiDataItemInterface* parentDataItemInterface)
+{
+ m_parentDataItemInterface = parentDataItemInterface;
+}
+
+bool KexiDataItemInterface::cursorAtNewRow()
+{
+ return listener() ? listener()->cursorAtNewRow() : false;
+}
diff --git a/kexi/core/kexidataiteminterface.h b/kexi/core/kexidataiteminterface.h
new file mode 100644
index 000000000..dbd38005e
--- /dev/null
+++ b/kexi/core/kexidataiteminterface.h
@@ -0,0 +1,249 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDATAITEMINTERFACE_H
+#define KEXIDATAITEMINTERFACE_H
+
+#include <qvariant.h>
+#include <qwidget.h>
+#include <qguardedptr.h>
+
+class KexiDataItemInterface;
+namespace KexiDB {
+ class Field;
+ class QueryColumnInfo;
+}
+
+//! An helper class used to react on KexiDataItemInterface objects' changes.
+class KEXICORE_EXPORT KexiDataItemChangesListener
+{
+ public:
+ KexiDataItemChangesListener();
+ virtual ~KexiDataItemChangesListener();
+
+ /*! Implement this to react for change of \a item.
+ Called by KexiDataItemInterface::valueChanged() */
+ virtual void valueChanged(KexiDataItemInterface* item) = 0;
+
+ /*! Implement this to return information whether we're currently at new row or not.
+ This can be used e.g. by data-aware widgets to determine if "(autonumber)"
+ label should be displayed. */
+ virtual bool cursorAtNewRow() const = 0;
+};
+
+//! An interface for declaring widgets to be data-aware.
+class KEXICORE_EXPORT KexiDataItemInterface
+{
+ public:
+ KexiDataItemInterface();
+ virtual ~KexiDataItemInterface();
+
+ /*! Just initializes \a value, and calls setValueInternal(const QString& add, bool removeOld).
+ If \a removeOld is true, current value is set up as \a add.
+ If \a removeOld if false, current value is set up as \a value + \a add.
+ \a value is stored as 'old value' -it'd be usable in the future
+ (e.g. Combo Box editor can use old value if current value does not
+ match any item on the list).
+
+ \a visibleValue (if not NULL) is passed to provide visible value to display instead of \a value.
+ This is currently used only in case of the combo box form widget, where displayed content
+ (usually a text of image) differs from the value of the widget (a numeric index).
+
+ This method is called by table view's and form's editors. */
+ void setValue(const QVariant& value, const QVariant& add = QVariant(), bool removeOld = false,
+ const QVariant* visibleValue = 0);
+
+ //! \return field information for this item
+ virtual KexiDB::Field *field() const = 0;
+
+ //! \return column information for this item
+ virtual KexiDB::QueryColumnInfo* columnInfo() const = 0;
+
+ //! Used internally to set column information.
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo) = 0;
+
+ //! Sets listener. No need to reimplement this.
+ virtual void installListener(KexiDataItemChangesListener* listener);
+
+// //! Sets value \a value for a widget.
+// //! Just calls setValueInternal(), but also blocks valueChanged()
+// //! as you we don't want to react on our own change
+// void setValue(const QVariant& value);
+
+ //! \return value currently represented by this item.
+ virtual QVariant value() = 0;
+
+ //! \return true if editor's value is valid for a given type
+ //! Used for checking if an entered value is valid,
+ //! E.g. a part of time value can be entered: "12:8" and this is invalid, not only null.
+ //! Null time or date is valid in Kexi, so it is not enough to test value().isValid().
+ //! Default implementation just returns true.
+ virtual bool valueIsValid();
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsNull() = 0;
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsEmpty() = 0;
+
+ //! \return value that should be displayed for this item.
+ //! Only used for items like combo box, where real value is an integer while
+ //! displayed value is usually a text. For other item types this method should be empty.
+ virtual QVariant visibleValue() { return QVariant(); }
+
+ /*! \return 'readOnly' flag for this item. The flag is usually taken from
+ the item's widget, e.g. KLineEdit::isReadOnly().
+ By default, always returns false. */
+ virtual bool isReadOnly() const { return false; }
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget() = 0;
+
+ /*! Hides item's widget, if available. */
+ virtual void hideWidget() { if (widget()) widget()->hide(); }
+
+ /*! Shows item's widget, if available. */
+ virtual void showWidget() { if (widget()) widget()->show(); }
+
+ //! \return true if editor's value is changed (compared to original value)
+ virtual bool valueChanged();
+
+ /*! \return true if the item widget's cursor (whatever that means, eg. line edit cursor)
+ is at the beginning of editor's contents. This can inform table/form view that
+ after pressing "left arrow" key should stop editing and move to a field on the left hand. */
+ virtual bool cursorAtStart() = 0;
+
+ /*! \return true if the item widget's cursor (whatever that means, eg. line edit cursor)
+ is at the end of editor's contents. This can inform table/form view that
+ after pressing "right arrow" key should stop editing and move to a field on the right hand. */
+ virtual bool cursorAtEnd() = 0;
+
+ /*! Moves cursor after the last character (or element).
+ For implementation in items supporting text cursor's movement; by default does nothing. */
+ virtual void moveCursorToEnd() {};
+
+ /*! Moves cursor before the first character (or element).
+ For implementation in items supporting text cursor's movement; by default does nothing. */
+ virtual void moveCursorToStart() {};
+
+ /*! Selects all characters (or elements) of the item.
+ For implementation in items supporting text or elements; by default does nothing. */
+ virtual void selectAll() {};
+
+ //! clears item's data, so the data will contain NULL data
+ virtual void clear() = 0;
+
+ /*! \return true if this editor offers a widget (e.g. line edit) that we can move focus to.
+ Editor for boolean values has this set to false (see KexiBoolTableEdit).
+ This is true by default. You can override this flag by changing
+ m_hasFocusableWidget in your subclass' constructor. */
+ inline bool hasFocusableWidget() const { return m_hasFocusableWidget; }
+
+ /*! Displays additional elements that are needed for indicating that the current cell
+ is selected. For example, combobox editor (KexiComboBoxTableEdit) moves and shows
+ dropdown button. \a r is the rectangle for the cell.
+ If \a readOnly is true, additional elements should be visually disabled,
+ e.g. dropdown button of the combobox editor should be disabled.
+ For reimplementation. By default does nothing. */
+ virtual void showFocus( const QRect& r, bool readOnly );
+
+ /*! Hides additional elements that are needed for indicating that the current cell
+ is selected.
+ For reimplementation. By default does nothing. */
+ virtual void hideFocus();
+
+ /*! Allows to define reaction for clicking on cell's contents.
+ Currently it's used for editor of type boolean, where we want to toggle true/false
+ on single mouse click. \sa hasFocusableWidget(), KexiBoolTableEdit.
+ Default implementation does nothing. */
+ virtual void clickedOnContents();
+
+ /*! \return true if editing should be accepted immediately after
+ deleting contents for the cell (usually using Delete key).
+ This flag is false by default, and is true e.g. for date, time and datetime types. */
+ bool acceptEditorAfterDeleteContents() const { return m_acceptEditorAfterDeleteContents; }
+
+ inline virtual void setFocus() { if (widget()) widget()->setFocus(); }
+
+ bool cursorAtNewRow();
+
+ /*! Sets a pointer to a Parent Data Item Interface. This pointer is 0 by default,
+ but can be set by parent widget if this interface is a building block of a larger data widget.
+ It is the case for KexiDBFieldEdit widget (see KexiDBFieldEdit::createEditor()). Use with care.
+ signalValueChanged() method will check this pointer, and if it's not 0,
+ m_parentDataItemInterface->signalValueChanged() is called, so a changes can be signalled at higher level. */
+ void setParentDataItemInterface(KexiDataItemInterface* parentDataItemInterface);
+
+ /*! \return a pointer to a Parent Data Item Interface.
+ @see setParentDataItemInterface() */
+ inline KexiDataItemInterface* parentInterface() const { return m_parentDataItemInterface; }
+
+ /*! Handles action having standard name \a actionName.
+ Action could be: "edit_cut", "edit_paste", etc.
+ For reimplementation. */
+ virtual void handleAction(const QString& actionName) { Q_UNUSED(actionName); }
+
+ protected:
+ /*! Initializes this editor with \a add value, which should be somewhat added to the current
+ value (already storted in m_origValue).
+ If \a removeOld is true, a value should be set to \a add, otherwise
+ -it should be set to current \a m_origValue + \a add, if possible.
+ Implement this. */
+ virtual void setValueInternal(const QVariant& add, bool removeOld) = 0;
+
+ /*! Initializes this editor with \a value visible value.
+ This is currently used only in case of the combo box form widget, where displayed content
+ (usually a text of image) differs from the value of the widget (a numeric index).
+ For implementation in the combo box widget, by default does nothing. */
+ virtual void setVisibleValueInternal(const QVariant& value);
+
+// //! Sets value \a value for a widget.
+// //! Implement this method to allow setting value for this widget item.
+// virtual void setValueInternal(const QVariant& value) = 0;
+
+ /*! Call this in your implementation when value changes,
+ so installed listener can react on this change. If there is a parent data item defined
+ (see setParentDataItemInterface()), parent's signalValueChanged() method will be called instead. */
+ virtual void signalValueChanged();
+
+ /*! Used to perform some actions before signalValueChanged() call.
+ We need this because the intrface is not QObject and thus has got no real signals.
+ Used in KexiDBComboBox. */
+ virtual void beforeSignalValueChanged() {};
+
+ KexiDataItemChangesListener* listener();
+
+//moved to KexiFormDataItemInterface: QString m_dataSource;
+ QGuardedPtr<QObject> m_listenerObject;
+ KexiDataItemChangesListener* m_listener;
+ bool m_listenerIsQObject;
+ QVariant m_origValue;
+
+ /*! @see parentDataItemInterface() */
+ KexiDataItemInterface* m_parentDataItemInterface;
+ bool m_hasFocusableWidget : 1;
+ bool m_disable_signalValueChanged : 1;
+ bool m_acceptEditorAfterDeleteContents : 1;
+};
+
+#endif
diff --git a/kexi/core/kexidbconnectionset.cpp b/kexi/core/kexidbconnectionset.cpp
new file mode 100644
index 000000000..1a0eb7745
--- /dev/null
+++ b/kexi/core/kexidbconnectionset.cpp
@@ -0,0 +1,183 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003,2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexidbconnectionset.h"
+#include "kexidbshortcutfile.h"
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+
+#include <qfile.h>
+
+//! @internal
+class KexiDBConnectionSetPrivate
+{
+public:
+ KexiDBConnectionSetPrivate()
+ : dataForFilenames(101)
+ {
+ list.setAutoDelete(true);
+ maxid=-1;
+ }
+ KexiDB::ConnectionData::List list;
+ QMap<KexiDB::ConnectionData*, QString> filenamesForData;
+ QDict<KexiDB::ConnectionData> dataForFilenames;
+ int maxid;
+};
+
+KexiDBConnectionSet::KexiDBConnectionSet()
+: QObject()
+, d(new KexiDBConnectionSetPrivate())
+{
+}
+
+KexiDBConnectionSet::~KexiDBConnectionSet()
+{
+ delete d;
+}
+
+bool KexiDBConnectionSet::addConnectionData(KexiDB::ConnectionData *data, const QString& _filename)
+{
+ if (!data)
+ return false;
+ if (data->id<0)
+ data->id = d->maxid+1;
+ //TODO: check for id-duplicates
+
+ d->maxid = QMAX(d->maxid,data->id);
+// d->list.append(data);
+
+ QString filename( _filename );
+ bool generateUniqueFilename = filename.isEmpty()
+ || !filename.isEmpty() && data==d->dataForFilenames[filename];
+
+ if (generateUniqueFilename) {
+ QString dir = KGlobal::dirs()->saveLocation("data", "kexi/connections/", false /*!create*/);
+ if (dir.isEmpty())
+ return false;
+ QString baseFilename( dir + (data->hostName.isEmpty() ? "localhost" : data->hostName) );
+ int i = 0;
+ while (KStandardDirs::exists(baseFilename+(i>0 ? QString::number(i) : QString::null)+".kexic"))
+ i++;
+ if (!KStandardDirs::exists(dir)) {
+ //make 'connections' dir and protect it
+ if (!KStandardDirs::makeDir(dir, 0700))
+ return false;
+ }
+ filename = baseFilename+(i>0 ? QString::number(i) : QString::null)+".kexic";
+ }
+ addConnectionDataInternal(data, filename);
+ bool result = saveConnectionData(data, data);
+ if (!result)
+ removeConnectionDataInternal(data);
+ return result;
+}
+
+void KexiDBConnectionSet::addConnectionDataInternal(KexiDB::ConnectionData *data, const QString& filename)
+{
+ d->filenamesForData.insert(data, filename);
+ d->dataForFilenames.insert(filename, data);
+ d->list.append(data);
+}
+
+bool KexiDBConnectionSet::saveConnectionData(KexiDB::ConnectionData *oldData,
+ KexiDB::ConnectionData *newData)
+{
+ if (!oldData || !newData)
+ return false;
+ QMap<KexiDB::ConnectionData*, QString>::ConstIterator it = d->filenamesForData.find( oldData );
+ if (it == d->filenamesForData.constEnd() || it.data().isEmpty())
+ return false;
+ const QString filename( it.data() );
+ KexiDBConnShortcutFile shortcutFile(filename);
+ if (!shortcutFile.saveConnectionData(*newData, newData->savePassword)) // true/*savePassword*/))
+ return false;
+ if (oldData!=newData)
+ *oldData = *newData;
+ return true;
+}
+
+void KexiDBConnectionSet::removeConnectionDataInternal(KexiDB::ConnectionData *data)
+{
+ QMap<KexiDB::ConnectionData*, QString>::ConstIterator it = d->filenamesForData.find( data );
+ const QString filename( it.data() );
+ d->filenamesForData.remove(data);
+ d->dataForFilenames.remove(filename);
+ d->list.removeRef(data);
+}
+
+bool KexiDBConnectionSet::removeConnectionData(KexiDB::ConnectionData *data)
+{
+ if (!data)
+ return false;
+ QMap<KexiDB::ConnectionData*, QString>::ConstIterator it = d->filenamesForData.find( data );
+ if (it == d->filenamesForData.constEnd() || it.data().isEmpty())
+ return false;
+ QFile file( it.data() );
+ if (!file.remove())
+ return false;
+ removeConnectionDataInternal(data);
+ return true;
+}
+
+const KexiDB::ConnectionData::List& KexiDBConnectionSet::list() const
+{
+ return d->list;
+}
+
+void KexiDBConnectionSet::clear()
+{
+ d->list.clear();
+ d->filenamesForData.clear();
+ d->dataForFilenames.clear();
+}
+
+void KexiDBConnectionSet::load()
+{
+ clear();
+// QStringList dirs( KGlobal::dirs()->findDirs("data", "kexi/connections") );
+// kexidbg << dirs << endl;
+ QStringList files( KGlobal::dirs()->findAllResources("data", "kexi/connections/*.kexic") );
+// //also try for capital file extension
+// files += KGlobal::dirs()->findAllResources("data", "kexi/connections/*.KEXIC");
+// kexidbg << files << endl;
+
+ foreach(QStringList::ConstIterator, it, files) {
+ KexiDB::ConnectionData *data = new KexiDB::ConnectionData();
+ KexiDBConnShortcutFile shortcutFile( *it );
+ if (!shortcutFile.loadConnectionData(*data)) {
+ delete data;
+ continue;
+ }
+ addConnectionDataInternal(data, *it);
+ }
+}
+
+QString KexiDBConnectionSet::fileNameForConnectionData(KexiDB::ConnectionData *data) const
+{
+ if (!data)
+ return QString::null;
+ QMap<KexiDB::ConnectionData*, QString>::ConstIterator it = d->filenamesForData.find( data );
+ return (it == d->filenamesForData.constEnd()) ? QString::null : it.data();
+}
+
+KexiDB::ConnectionData* KexiDBConnectionSet::connectionDataForFileName(const QString& fileName) const
+{
+ return d->dataForFilenames[fileName];
+}
diff --git a/kexi/core/kexidbconnectionset.h b/kexi/core/kexidbconnectionset.h
new file mode 100644
index 000000000..78d4649a3
--- /dev/null
+++ b/kexi/core/kexidbconnectionset.h
@@ -0,0 +1,77 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003,2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDBCONNSET_H
+#define KEXIDBCONNSET_H
+
+#include <qstring.h>
+#include <kexidb/connectiondata.h>
+
+class KexiDBConnectionSetPrivate;
+
+/*! Stores information about multiple connection-data items. */
+class KEXICORE_EXPORT KexiDBConnectionSet : public QObject
+{
+public:
+ KexiDBConnectionSet();
+ ~KexiDBConnectionSet();
+
+ /*! Loads connection data set from storage, currently from
+ .kexic files saved in dirs returned by
+ KStandardDirs::findDirs("data", "connections") */
+ void load();
+
+ /*! Adds \a data as connection data.
+ \a data will be owned by a KexiDBConnectionSet object.
+ If \a filename is not empty, it will be kept for use in saveConnectionData().
+ saveConnectionData() is called automatically, if there's no \a filename provided
+ or the filename is already used, a new unique will be generated.
+ \return true on successful creating corresponding .kexic file */
+ bool addConnectionData(KexiDB::ConnectionData *data, const QString& filename = QString::null);
+
+ /*! Saves changes made to \a oldData to a file which name has been provided by addConnectionData().
+ This function does nothing if \a oldData hasn't been added to this set.
+ \return true on success (data is then copied from \a newData to \a oldData) */
+ bool saveConnectionData(KexiDB::ConnectionData *oldData, KexiDB::ConnectionData *newData);
+
+ /*! Removed \a data from this set.
+ \return true on successful removing of corresponding .kexic file */
+ bool removeConnectionData(KexiDB::ConnectionData *data);
+
+ /*! \return the list of connection data items. */
+ const KexiDB::ConnectionData::List& list() const;
+
+ /*! \return a filename of a connection data file for \a data. */
+ QString fileNameForConnectionData(KexiDB::ConnectionData *data) const;
+
+ /*! \return a connection data for a .kexic shortcut filename.
+ 0 is returned if the filename does not match. */
+ KexiDB::ConnectionData* connectionDataForFileName(const QString& fileName) const;
+
+private:
+ /*! Removes all connection data items from this set. */
+ void clear();
+ void addConnectionDataInternal(KexiDB::ConnectionData *data, const QString& filename);
+ void removeConnectionDataInternal(KexiDB::ConnectionData *data);
+
+ KexiDBConnectionSetPrivate *d;
+};
+
+#endif // KEXIDBCONNSET_H
+
diff --git a/kexi/core/kexidbshortcutfile.cpp b/kexi/core/kexidbshortcutfile.cpp
new file mode 100644
index 000000000..4a503d432
--- /dev/null
+++ b/kexi/core/kexidbshortcutfile.cpp
@@ -0,0 +1,314 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbshortcutfile.h"
+#include <core/kexiprojectdata.h>
+#include <kexidb/connectiondata.h>
+#include <kexiutils/utils.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+
+#include <qstringlist.h>
+#include <qdir.h>
+
+//! Version of the KexiDBShortcutFile format.
+#define KexiDBShortcutFile_version 2
+/* CHANGELOG:
+ v1: initial version
+ v2: "encryptedPassword" field added.
+ For backward compatibility, it is not used if the connection data has been loaded from
+ a file saved with version 1. In such cases unencrypted "password" field is used.
+*/
+
+//! @internal
+class KexiDBShortcutFile::Private
+{
+ public:
+ Private()
+ : isDatabaseShortcut(true)
+ {
+ }
+ QString fileName;
+ bool isDatabaseShortcut : 1;
+};
+
+KexiDBShortcutFile::KexiDBShortcutFile( const QString& fileName )
+ : d( new KexiDBShortcutFile::Private() )
+{
+ d->fileName = QDir(fileName).absPath();
+}
+
+KexiDBShortcutFile::~KexiDBShortcutFile()
+{
+ delete d;
+}
+
+bool KexiDBShortcutFile::loadProjectData(KexiProjectData& data, QString* _groupKey)
+{
+ KConfig config(d->fileName, true /* readOnly */, false /* local */ );
+ config.setGroup("File Information");
+ data.formatVersion = config.readNumEntry("version", KexiDBShortcutFile_version);
+
+ QString groupKey;
+ if (!_groupKey || _groupKey->isEmpty()) {
+ QStringList groups(config.groupList());
+ foreach (QStringList::ConstIterator, it, groups) {
+ if ((*it).lower()!="file information") {
+ groupKey = *it;
+ break;
+ }
+ }
+ if (groupKey.isEmpty()) {
+ //ERR: "File %1 contains no connection information"
+ return false;
+ }
+ if (_groupKey)
+ *_groupKey = groupKey;
+ }
+ else {
+ if (!config.hasGroup(*_groupKey))
+ return false;
+ groupKey = *_groupKey;
+ }
+
+ config.setGroup(groupKey);
+ QString type( config.readEntry("type", "database").lower() );
+
+ if (type=="database") {
+ d->isDatabaseShortcut = true;
+ } else if (type=="connection") {
+ d->isDatabaseShortcut = false;
+ }
+ else {
+ //ERR: i18n("No valid "type" field specified for section \"%1\": unknown value \"%2\".").arg(group).arg(type)
+ return false;
+ }
+
+/* kexidbg << "version=" << version
+ << " using group key=" << groupKey
+ << " type=" << type
+ << " caption=" << config.readEntry("caption")
+ << " name=" << config.readEntry("name")
+ << " engine=" << config.readEntry("engine")
+ << " server=" << config.readEntry("server")
+ << " user=" << config.readEntry("user")
+ << " password=" << QString().fill('*', config.readEntry("password").length())
+ << " comment=" << config.readEntry("comment")
+ << endl;*/
+
+ //no filename by default
+ data.connectionData()->setFileName(QString::null);
+
+ if (d->isDatabaseShortcut) {
+ data.setCaption( config.readEntry("caption") );
+ data.setDescription( config.readEntry("comment") );
+ data.connectionData()->description = QString::null;
+ data.connectionData()->caption = QString::null; /* connection name is not specified... */
+ data.setDatabaseName( config.readEntry("name") );
+ }
+ else {
+ data.setCaption( QString::null );
+ data.connectionData()->caption = config.readEntry("caption");
+ data.setDescription( QString::null );
+ data.connectionData()->description = config.readEntry("comment");
+ data.setDatabaseName( QString::null ); /* db name is not specified... */
+ }
+ data.connectionData()->driverName = config.readEntry("engine");
+ if (data.connectionData()->driverName.isEmpty()) {
+ //ERR: "No valid "engine" field specified for %1 section" group
+ return false;
+ }
+ data.connectionData()->hostName = config.readEntry("server"); //empty allowed
+ data.connectionData()->port = config.readNumEntry("port", 0);
+ data.connectionData()->useLocalSocketFile = config.readBoolEntry("useLocalSocketFile", false);
+ data.connectionData()->localSocketFileName = config.readEntry("localSocketFile");
+ data.connectionData()->savePassword = config.hasKey("password") || config.hasKey("encryptedPassword");
+ if (data.formatVersion >= 2) {
+ kdDebug() << config.hasKey("encryptedPassword") << endl;
+ data.connectionData()->password = config.readEntry("encryptedPassword");
+ KexiUtils::simpleDecrypt(data.connectionData()->password);
+ }
+ if (data.connectionData()->password.isEmpty()) {//no "encryptedPassword", for compatibility
+ //UNSAFE
+ data.connectionData()->password = config.readEntry("password");
+ }
+// data.connectionData()->savePassword = !data.connectionData()->password.isEmpty();
+ data.connectionData()->userName = config.readEntry("user");
+/* @todo add "options=", eg. as string list? */
+ return true;
+}
+
+bool KexiDBShortcutFile::saveProjectData(const KexiProjectData& data,
+ bool savePassword, QString* _groupKey, bool overwriteFirstGroup)
+{
+ KConfig config(d->fileName, false /*rw*/, false /* local */);
+ config.setGroup("File Information");
+
+ uint realFormatVersion = data.formatVersion;
+ if (realFormatVersion == 0) /* 0 means "default version"*/
+ realFormatVersion = KexiDBShortcutFile_version;
+ config.writeEntry("version", realFormatVersion);
+
+ const bool thisIsConnectionData = data.databaseName().isEmpty();
+
+ //use or find a nonempty group key
+ QString groupKey;
+ if (_groupKey && !_groupKey->isEmpty()) {
+ groupKey = *_groupKey;
+ }
+ else {
+ QString groupPrefix;
+ const QStringList groups(config.groupList());
+ if (overwriteFirstGroup && !groups.isEmpty()) {
+// groupKey = groups.first(); //found
+ foreach (QStringList::ConstIterator, it, groups) {
+ if ((*it).lower()!="file information") {
+ groupKey = *it;
+ break;
+ }
+ }
+ }
+
+ if (groupKey.isEmpty()) {
+ //find a new unique name
+ if (thisIsConnectionData)
+ groupPrefix = "Connection%1"; //do not i18n!
+ else
+ groupPrefix = "Database%1"; //do not i18n!
+
+ int number = 1;
+ while (config.hasGroup(groupPrefix.arg(number))) //a new group key couldn't exist
+ number++;
+ groupKey = groupPrefix.arg(number);
+ }
+ if (_groupKey) //return this one (generated or found)
+ *_groupKey = groupKey;
+ }
+
+ config.deleteGroup(groupKey);
+ config.setGroup(groupKey);
+ if (thisIsConnectionData) {
+ config.writeEntry("type", "connection");
+ config.writeEntry("caption", data.constConnectionData()->caption);
+ if (!data.constConnectionData()->description.isEmpty())
+ config.writeEntry("comment", data.constConnectionData()->description);
+ }
+ else {//database
+ config.writeEntry("type", "database");
+ config.writeEntry("caption", data.caption());
+ config.writeEntry("name", data.databaseName());
+ if (!data.description().isEmpty())
+ config.writeEntry("comment", data.description());
+ }
+
+ config.writeEntry("engine", data.constConnectionData()->driverName);
+ if (!data.constConnectionData()->hostName.isEmpty())
+ config.writeEntry("server", data.constConnectionData()->hostName);
+
+ if (data.constConnectionData()->port!=0)
+ config.writeEntry("port", data.constConnectionData()->port);
+ config.writeEntry("useLocalSocketFile", data.constConnectionData()->useLocalSocketFile);
+ if (!data.constConnectionData()->localSocketFileName.isEmpty())
+ config.writeEntry("localSocketFile", data.constConnectionData()->localSocketFileName);
+
+ if (savePassword || data.constConnectionData()->savePassword) {
+ if (realFormatVersion < 2) {
+ config.writeEntry("password", data.constConnectionData()->password);
+ }
+ else {
+ QString encryptedPassword = data.constConnectionData()->password;
+ KexiUtils::simpleCrypt(encryptedPassword);
+ config.writeEntry("encryptedPassword", encryptedPassword);
+ encryptedPassword.fill(' '); //for security
+ }
+ }
+
+ if (!data.constConnectionData()->userName.isEmpty())
+ config.writeEntry("user", data.constConnectionData()->userName);
+/* @todo add "options=", eg. as string list? */
+ config.sync();
+ return true;
+}
+
+QString KexiDBShortcutFile::fileName() const
+{
+ return d->fileName;
+}
+
+//---------------------------------------------
+
+KexiDBConnShortcutFile::KexiDBConnShortcutFile( const QString& fileName )
+ : KexiDBShortcutFile( fileName )
+{
+}
+
+KexiDBConnShortcutFile::~KexiDBConnShortcutFile()
+{
+}
+
+bool KexiDBConnShortcutFile::loadConnectionData(KexiDB::ConnectionData& data, QString* _groupKey)
+{
+ KexiProjectData pdata(data);
+ if (!loadProjectData(pdata, _groupKey))
+ return false;
+ data = *pdata.connectionData();
+ return true;
+}
+
+bool KexiDBConnShortcutFile::saveConnectionData(const KexiDB::ConnectionData& data,
+ bool savePassword, QString* groupKey, bool overwriteFirstGroup)
+{
+ KexiProjectData pdata(data);
+ return saveProjectData(pdata, savePassword, groupKey, overwriteFirstGroup);
+}
+
+//---------------------------------------------
+
+#if 0
+/*! Loads connection data into \a data. */
+bool KexiDBConnSetShortcutFiles::loadConnectionDataSet(KexiDBConnectionSet& set)
+{
+ set.clear();
+// QStringList dirs( KGlobal::dirs()->findDirs("data", "kexi/connections") );
+// kexidbg << dirs << endl;
+ QStringList files( KGlobal::dirs()->findAllResources("data", "kexi/connections/*.kexic") );
+// //also try for capital file extension
+// files += KGlobal::dirs()->findAllResources("data", "kexi/connections/*.KEXIC");
+ kexidbg << files << endl;
+
+ foreach(QStringList::ConstIterator, it, files) {
+ KexiDB::ConnectionData *data = new KexiDB::ConnectionData();
+ KexiDBConnShortcutFile shortcutFile( *it );
+ if (!shortcutFile.loadConnectionData(*data)) {
+ delete data;
+ continue;
+ }
+ set.addConnectionData(data);
+ }
+}
+
+
+/*! Saves a set of connection data \a set to a shortcut files.
+ Existing files are overwritten with a new data. */
+bool KexiDBConnSetShortcutFiles::saveConnectionDataSet(const KexiDBConnectionSet& set)
+{
+}
+
+#endif
diff --git a/kexi/core/kexidbshortcutfile.h b/kexi/core/kexidbshortcutfile.h
new file mode 100644
index 000000000..3dfa9c13d
--- /dev/null
+++ b/kexi/core/kexidbshortcutfile.h
@@ -0,0 +1,124 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDBSHORTCUTFILE_H
+#define KEXIDBSHORTCUTFILE_H
+
+#include <qstring.h>
+
+class KexiProjectData;
+namespace KexiDB { class ConnectionData; }
+
+/*! Loads and saves information for a "shortcut to a connection" file containing
+ connection information with database name (i.e. ProjectData).
+ This is implementation for handling .KEXIS files.
+ See http://www.kexi-project.org/wiki/wikiview/index.php?KexiMimeTypes_DataSaving_Loading
+*/
+class KEXICORE_EXPORT KexiDBShortcutFile
+{
+ public:
+ /*! Creates a new object for \a fileName. */
+ KexiDBShortcutFile( const QString& fileName );
+
+ ~KexiDBShortcutFile();
+
+ /*! Loads project data (with connection data) into \a data.
+ Database name and caption can be set there but these are optional.
+ \a groupKey, if provided will be set to a group key,
+ so you can later use it in saveConnectionData().
+ \return true on success. */
+ bool loadProjectData(KexiProjectData& data, QString* groupKey = 0);
+
+ /*! Saves project data \a data (with connection data) to a shortcut file.
+ If \a storePassword is true, password will be saved in the file,
+ even if data.connectionData()->savePassword is false.
+ Existing data is merged with new data. \a groupKey is reused, if specified.
+ If \a overwriteFirstGroup is true (the default) first found group will be overwritten
+ instead of creating of a new unique group. This mode is usable for updating .kexic files
+ containing single connection data, what's used for storing connections repository.
+ \return true on success. */
+ bool saveProjectData(const KexiProjectData& data, bool savePassword,
+ QString* groupKey = 0, bool overwriteFirstGroup = true);
+
+ //! \return filename provided on this object's construction. */
+ QString fileName() const;
+
+ protected:
+ class Private;
+ Private *d;
+};
+
+/*! Loads and saves information for a "shortcut" file containing
+ connection information (i.e. KexiDB::ConnectionData).
+ This is implementation for handling .KEXIC files.
+ See http://www.kexi-project.org/wiki/wikiview/index.php?KexiMimeTypes_DataSaving_Loading
+*/
+class KEXICORE_EXPORT KexiDBConnShortcutFile : protected KexiDBShortcutFile
+{
+ public:
+ /*! Creates a new object for \a fileName. */
+ KexiDBConnShortcutFile( const QString& fileName );
+
+ ~KexiDBConnShortcutFile();
+
+ /*! Loads connection data into \a data.
+ \a groupKey, if provided will be set to a group key,
+ so you can later use it in saveConnectionData().
+ \return true on success. */
+ bool loadConnectionData(KexiDB::ConnectionData& data, QString* groupKey = 0);
+
+ /*! Saves connection data \a data to a shortcut file.
+ If \a storePassword is true, password will be saved in the file,
+ even if data.savePassword is false.
+ Existing data is merged with new data. \a groupKey is reused, if specified.
+ If \a overwriteFirstGroup is true (the default) first found group will be overwritten
+ instead of creating of a new unique group. This mode is usable for updating .kexic files
+ containing single connection data, what's used for storing connections repository.
+ \return true on success. */
+ bool saveConnectionData(const KexiDB::ConnectionData& data,
+ bool savePassword, QString* groupKey = 0, bool overwriteFirstGroup = true);
+
+ //! \return filename provided on this object's construction. */
+ QString fileName() const { return KexiDBShortcutFile::fileName(); }
+
+ protected:
+};
+
+#if 0
+//! Loads and saves information for a sef of "shortcut to a connection" file containing
+//! connection information (i.e. KexiDBConnectionSet).
+//! This is implementation for handling .KEXIC files.
+//! The set is loaded from files found using
+//! KGlobal::dirs()->findAllResources("data", "kexi/connections/*.kexic").
+class KexiDBConnSetShortcutFiles
+{
+ public:
+ KexiDBConnSetShortcutFiles();
+
+ /*! Loads connection data into \a set. The set is cleared before loading.
+ \retuirn true on successful loading. */
+ static bool loadConnectionDataSet(KexiDBConnectionSet& set);
+
+ /*! Saves a set of connection data \a set to a shortcut files.
+ Existing files are overwritten with a new data.
+ \retuirn true on successful saving. */
+ static bool saveConnectionDataSet(const KexiDBConnectionSet& set);
+}
+#endif
+#endif
diff --git a/kexi/core/kexidialogbase.cpp b/kexi/core/kexidialogbase.cpp
new file mode 100644
index 000000000..2f94e661f
--- /dev/null
+++ b/kexi/core/kexidialogbase.cpp
@@ -0,0 +1,661 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidialogbase.h"
+
+#include "keximainwindow.h"
+#include "kexiviewbase.h"
+#include "kexicontexthelp_p.h"
+#include "kexipart.h"
+#include "kexistaticpart.h"
+#include "kexipartitem.h"
+#include "kexipartinfo.h"
+#include "kexiproject.h"
+
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+#include <kexiutils/utils.h>
+
+#include <qwidgetstack.h>
+#include <qobjectlist.h>
+#include <qtimer.h>
+
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+
+KexiDialogBase::KexiDialogBase(KexiMainWindow *parent, const QString &caption)
+ : KMdiChildView(caption, parent, "KexiDialogBase")
+ , KexiActionProxy(this, parent)
+ , m_isRegistered(false)
+ , m_origCaption(caption)
+ , m_schemaData(0)
+ , m_destroying(false)
+ , m_disableDirtyChanged(false)
+// , m_neverSaved(false)
+{
+ m_supportedViewModes = 0; //will be set by KexiPart
+ m_openedViewModes = 0;
+ m_currentViewMode = Kexi::NoViewMode; //no view available yet
+ m_parentWindow = parent;
+ m_creatingViewsMode = -1;
+
+ QVBoxLayout *lyr = new QVBoxLayout(this);
+ m_stack = new QWidgetStack(this, "stack");
+ lyr->addWidget(m_stack);
+
+#ifdef KEXI_NO_CTXT_HELP
+ m_contextHelpInfo=new KexiContextHelpInfo();
+#endif
+// m_instance=parent->instance();
+ m_id = -1;
+ m_item = 0;
+
+ hide(); //will be shown later
+}
+
+KexiDialogBase::~KexiDialogBase()
+{
+ m_destroying = true;
+}
+
+KexiViewBase *KexiDialogBase::selectedView() const
+{
+ if (m_destroying)
+ return 0;
+// return static_cast<KexiViewBase*>(m_stack->visibleWidget());
+ return static_cast<KexiViewBase*>( m_stack->widget(m_currentViewMode) );
+}
+
+KexiViewBase *KexiDialogBase::viewForMode(int mode) const
+{
+ return static_cast<KexiViewBase*>( m_stack->widget(mode) );
+}
+
+void KexiDialogBase::addView(KexiViewBase *view)
+{
+ addView(view,0);
+}
+
+void KexiDialogBase::addView(KexiViewBase *view, int mode)
+{
+ m_stack->addWidget(view, mode);
+// addActionProxyChild( view );
+
+ //set focus proxy inside this view
+ QWidget *ch = static_cast<QWidget*>(view->child( 0, "QWidget", false ));
+ if (ch)
+ view->setFocusProxy(ch);
+
+ m_openedViewModes |= mode;
+}
+
+void KexiDialogBase::removeView(int mode)
+{
+ KexiViewBase *view = viewForMode(mode);
+ if (view)
+ m_stack->removeWidget(view);
+
+ m_openedViewModes |= mode;
+ m_openedViewModes ^= mode;
+}
+
+QSize KexiDialogBase::minimumSizeHint() const
+{
+ KexiViewBase *v = selectedView();
+ if (!v)
+ return KMdiChildView::minimumSizeHint();
+ return v->minimumSizeHint() + QSize(0, mdiParent() ? mdiParent()->captionHeight() : 0);
+}
+
+QSize KexiDialogBase::sizeHint() const
+{
+ KexiViewBase *v = selectedView();
+ if (!v)
+ return KMdiChildView::sizeHint();
+ return v->preferredSizeHint( v->sizeHint() );
+}
+
+/*
+KInstance *KexiDialogBase::instance() {
+ return m_instance;
+}*/
+
+void KexiDialogBase::registerDialog() {
+ if (m_isRegistered)
+ return;
+ m_parentWindow->registerChild(this);
+ m_isRegistered=true;
+ if ( m_parentWindow->mdiMode() == KMdi::ToplevelMode ) {
+ m_parentWindow->addWindow( (KMdiChildView *)this, KMdi::Detach );
+ m_parentWindow->detachWindow((KMdiChildView *)this, true);
+ }
+ else
+ m_parentWindow->addWindow((KMdiChildView *)this);
+//later show();
+// m_parentWindow->activeWindowChanged(this);
+}
+
+bool KexiDialogBase::isRegistered(){
+ return m_isRegistered;
+}
+
+void KexiDialogBase::attachToGUIClient() {
+ if (!guiClient())
+ return;
+
+}
+
+void KexiDialogBase::detachFromGUIClient() {
+ if (!guiClient())
+ return;
+ //TODO
+}
+
+int KexiDialogBase::id() const
+{
+ return (partItem() && partItem()->identifier()>0) ? partItem()->identifier() : m_id;
+}
+
+void KexiDialogBase::setContextHelp(const QString& caption, const QString& text, const QString& iconName) {
+#ifdef KEXI_NO_CTXT_HELP
+ m_contextHelpInfo->caption=caption;
+ m_contextHelpInfo->text=text;
+ m_contextHelpInfo->text=iconName;
+ updateContextHelp();
+#endif
+}
+
+void KexiDialogBase::closeEvent( QCloseEvent * e )
+{
+ m_parentWindow->acceptPropertySetEditing();
+
+ //let any view send "closing" signal
+ QObjectList *list = m_stack->queryList( "KexiViewBase", 0, false, false);
+ KexiViewBase *view;
+ QObjectListIt it( *list );
+ for ( ;(view = static_cast<KexiViewBase*>(it.current()) ) != 0; ++it ) {
+ bool cancel = false;
+ emit view->closing(cancel);
+ if (cancel) {
+ e->ignore();
+ return;
+ }
+ }
+ delete list;
+ emit closing();
+ KMdiChildView::closeEvent(e);
+}
+
+#if 0
+//js removed
+bool KexiDialogBase::tryClose(bool dontSaveChanges)
+{
+ if (!dontSaveChanges && dirty()) {
+/*TODO if (KMessageBox::questionYesNo(this, "<b>"+i18n("Do you want save:")
+ +"<p>"+typeName+" \""+ item->name() + "\"?</b>",
+ 0, KStdGuiItem::yes(), KStdGuiItem::no(), ???????)==KMessageBox::No)
+ return false;*/
+ //js TODO: save data using saveChanges()
+ }
+ close(true);
+ return true;
+}
+#endif
+
+bool KexiDialogBase::dirty() const
+{
+ //look for "dirty" flag
+ int m = m_openedViewModes, mode = 1;
+ while (m>0) {
+ if (m & 1) {
+ if (static_cast<KexiViewBase*>(m_stack->widget(mode))->dirty())
+ return true;
+ }
+ m >>= 1;
+ mode <<= 1;
+ }
+ return false;
+/* KexiViewBase *v = m_newlySelectedView ? m_newlySelectedView : selectedView();
+ return v ? v->dirty() : false;*/
+}
+
+void KexiDialogBase::setDirty(bool dirty)
+{
+ m_disableDirtyChanged = true;
+ int m = m_openedViewModes, mode = 1;
+ while (m>0) {
+ if (m & 1) {
+ static_cast<KexiViewBase*>(m_stack->widget(mode))->setDirty(dirty);
+ }
+ m >>= 1;
+ mode <<= 1;
+ }
+ m_disableDirtyChanged = false;
+ dirtyChanged(m_viewThatRecentlySetDirtyFlag); //update
+}
+
+QString KexiDialogBase::itemIcon()
+{
+ if (!m_part || !m_part->info()) {
+ KexiViewBase *v = selectedView();
+ if (v) {//m_stack->visibleWidget() && m_stack->visibleWidget()->inherits("KexiViewBase")) {
+ return v->m_defaultIconName;
+ }
+ return QString::null;
+ }
+ return m_part->info()->itemIcon();
+}
+
+KexiPart::GUIClient* KexiDialogBase::guiClient() const
+{
+ if (!m_part || m_currentViewMode<1)
+ return 0;
+ return m_part->instanceGuiClient(m_currentViewMode);
+}
+
+KexiPart::GUIClient* KexiDialogBase::commonGUIClient() const
+{
+ if (!m_part)
+ return 0;
+ return m_part->instanceGuiClient(0);
+}
+
+bool KexiDialogBase::isDesignModePreloadedForTextModeHackUsed(int newViewMode) const
+{
+ return newViewMode==Kexi::TextViewMode
+ && !viewForMode(Kexi::DesignViewMode)
+ && supportsViewMode(Kexi::DesignViewMode);
+}
+
+tristate KexiDialogBase::switchToViewMode( int newViewMode, QMap<QString,QString>* staticObjectArgs,
+ bool& proposeOpeningInTextViewModeBecauseOfProblems)
+{
+ m_parentWindow->acceptPropertySetEditing();
+
+ const bool designModePreloadedForTextModeHack = isDesignModePreloadedForTextModeHackUsed(newViewMode);
+ tristate res = true;
+ if (designModePreloadedForTextModeHack) {
+ /* A HACK: open design BEFORE text mode: otherwise Query schema becames crazy */
+ bool _proposeOpeningInTextViewModeBecauseOfProblems = false; // used because even if opening the view failed,
+ // text view can be opened
+ res = switchToViewMode( Kexi::DesignViewMode, staticObjectArgs, _proposeOpeningInTextViewModeBecauseOfProblems);
+ if ((!res && !_proposeOpeningInTextViewModeBecauseOfProblems) || ~res)
+ return res;
+ }
+
+ kdDebug() << "KexiDialogBase::switchToViewMode()" << endl;
+ bool dontStore = false;
+ KexiViewBase *view = selectedView();
+
+ if (m_currentViewMode == newViewMode)
+ return true;
+ if (!supportsViewMode(newViewMode))
+ return false;
+
+ if (view) {
+ res = true;
+ if (!designModePreloadedForTextModeHack) {
+ res = view->beforeSwitchTo(newViewMode, dontStore);
+ }
+ if (~res || !res)
+ return res;
+ if (!dontStore && view->dirty()) {
+ res = m_parentWindow->saveObject(this, i18n("Design has been changed. "
+ "You must save it before switching to other view."));
+ if (~res || !res)
+ return res;
+// KMessageBox::questionYesNo(0, i18n("Design has been changed. You must save it before switching to other view."))
+// ==KMessageBox::No
+ }
+ }
+
+ //get view for viewMode
+ KexiViewBase *newView
+ = (m_stack->widget(newViewMode) && m_stack->widget(newViewMode)->inherits("KexiViewBase"))
+ ? static_cast<KexiViewBase*>(m_stack->widget(newViewMode)) : 0;
+ if (!newView) {
+ KexiUtils::setWaitCursor();
+ //ask the part to create view for the new mode
+ m_creatingViewsMode = newViewMode;
+ KexiPart::StaticPart *staticPart = dynamic_cast<KexiPart::StaticPart*>((KexiPart::Part*)m_part);
+ if (staticPart)
+ newView = staticPart->createView(m_stack, this, *m_item, newViewMode, staticObjectArgs);
+ else
+ newView = m_part->createView(m_stack, this, *m_item, newViewMode, staticObjectArgs);
+ KexiUtils::removeWaitCursor();
+ if (!newView) {
+ //js TODO error?
+ kdDebug() << "Switching to mode " << newViewMode << " failed. Previous mode "
+ << m_currentViewMode << " restored." << endl;
+ return false;
+ }
+ m_creatingViewsMode = -1;
+ addView(newView, newViewMode);
+ }
+ const int prevViewMode = m_currentViewMode;
+ res = true;
+ if (designModePreloadedForTextModeHack) {
+ m_currentViewMode = Kexi::NoViewMode; //SAFE?
+ }
+ res = newView->beforeSwitchTo(newViewMode, dontStore);
+ proposeOpeningInTextViewModeBecauseOfProblems = tempData()->proposeOpeningInTextViewModeBecauseOfProblems;
+ if (!res) {
+ removeView(newViewMode);
+ delete newView;
+ kdDebug() << "Switching to mode " << newViewMode << " failed. Previous mode "
+ << m_currentViewMode << " restored." << endl;
+ return false;
+ }
+ m_currentViewMode = newViewMode;
+ m_newlySelectedView = newView;
+ if (prevViewMode==Kexi::NoViewMode)
+ m_newlySelectedView->setDirty(false);
+
+ res = newView->afterSwitchFrom(
+ designModePreloadedForTextModeHack ? Kexi::NoViewMode : prevViewMode);
+ proposeOpeningInTextViewModeBecauseOfProblems = tempData()->proposeOpeningInTextViewModeBecauseOfProblems;
+ if (!res) {
+ removeView(newViewMode);
+ delete newView;
+ kdDebug() << "Switching to mode " << newViewMode << " failed. Previous mode "
+ << prevViewMode << " restored." << endl;
+ const Kexi::ObjectStatus status(*this);
+ setStatus(mainWin()->project()->dbConnection(),
+ i18n("Switching to other view failed (%1).").arg(Kexi::nameForViewMode(newViewMode)),"");
+ append( status );
+ m_currentViewMode = prevViewMode;
+ return false;
+ }
+ m_newlySelectedView = 0;
+ if (~res) {
+ m_currentViewMode = prevViewMode;
+ return cancelled;
+ }
+ if (view)
+ takeActionProxyChild( view ); //take current proxy child
+ addActionProxyChild( newView ); //new proxy child
+ m_stack->raiseWidget( newView );
+ newView->propertySetSwitched();
+ m_parentWindow->invalidateSharedActions( newView );
+ QTimer::singleShot(10, newView, SLOT(setFocus())); //newView->setFocus(); //js ok?
+// setFocus();
+ return true;
+}
+
+tristate KexiDialogBase::switchToViewMode( int newViewMode )
+{
+ bool dummy;
+ return switchToViewMode( newViewMode, 0, dummy );
+}
+
+void KexiDialogBase::setFocus()
+{
+ if (m_stack->visibleWidget()) {
+ if (m_stack->visibleWidget()->inherits("KexiViewBase"))
+ static_cast<KexiViewBase*>( m_stack->visibleWidget() )->setFocus();
+ else
+ m_stack->visibleWidget()->setFocus();
+ }
+ else {
+ KMdiChildView::setFocus();
+ }
+ activate();
+}
+
+KoProperty::Set*
+KexiDialogBase::propertySet()
+{
+ KexiViewBase *v = selectedView();
+ if (!v)
+ return 0;
+ return v->propertySet();
+}
+
+bool KexiDialogBase::eventFilter(QObject *obj, QEvent *e)
+{
+ if (KMdiChildView::eventFilter(obj, e))
+ return true;
+/* if (e->type()==QEvent::FocusIn) {
+ QWidget *w = m_parentWindow->activeWindow();
+ w=0;
+ }*/
+ if ((e->type()==QEvent::FocusIn && m_parentWindow->activeWindow()==this)
+ || e->type()==QEvent::MouseButtonPress) {
+ if (m_stack->visibleWidget() && KexiUtils::hasParent(m_stack->visibleWidget(), obj)) {
+ //pass the activation
+ activate();
+ }
+ }
+ return false;
+}
+
+void KexiDialogBase::dirtyChanged(KexiViewBase* view)
+{
+ if (m_disableDirtyChanged)
+ return;
+ m_viewThatRecentlySetDirtyFlag = dirty() ? view : 0;
+/* if (!dirty()) {
+ if (caption()!=m_origCaption)
+ KMdiChildView::setCaption(m_origCaption);
+ }
+ else {
+ if (caption()!=(m_origCaption+"*"))
+ KMdiChildView::setCaption(m_origCaption+"*");
+ }*/
+ updateCaption();
+ emit dirtyChanged(this);
+}
+
+/*QString KexiDialogBase::caption() const
+{
+ return m_origCaption;
+ if (dirty())
+ return KMdiChildView::caption()+;
+
+ return KMdiChildView::caption();
+}*/
+
+void KexiDialogBase::updateCaption()
+{
+ if (!m_item || !m_part || !m_origCaption.isEmpty())
+ return;
+// m_origCaption = c;
+ QString capt = m_item->name();
+ QString fullCapt = capt;
+ if (m_part)
+ fullCapt += (" : " + m_part->instanceCaption());
+ if (dirty()) {
+ KMdiChildView::setCaption(fullCapt+"*");
+ KMdiChildView::setTabCaption(capt+"*");
+ }
+ else {
+ KMdiChildView::setCaption(fullCapt);
+ KMdiChildView::setTabCaption(capt);
+ }
+}
+
+bool KexiDialogBase::neverSaved() const
+{
+ return m_item ? m_item->neverSaved() : true;
+}
+
+tristate KexiDialogBase::storeNewData()
+{
+ if (!neverSaved())
+ return false;
+ KexiViewBase *v = selectedView();
+ if (m_schemaData)
+ return false; //schema must not exist
+ if (!v)
+ return false;
+ //create schema object and assign information
+ KexiDB::SchemaData sdata(m_part->info()->projectPartID());
+ sdata.setName( m_item->name() );
+ sdata.setCaption( m_item->caption() );
+ sdata.setDescription( m_item->description() );
+
+ bool cancel = false;
+ m_schemaData = v->storeNewData(sdata, cancel);
+ if (cancel)
+ return cancelled;
+ if (!m_schemaData) {
+ setStatus(m_parentWindow->project()->dbConnection(), i18n("Saving object's definition failed."),"");
+ return false;
+ }
+
+ if (!part()->info()->isIdStoredInPartDatabase()) {
+ //this part's ID is not stored within kexi__parts:
+ KexiDB::TableSchema *ts = m_parentWindow->project()->dbConnection()->tableSchema("kexi__parts");
+ kdDebug() << "KexiDialogBase::storeNewData(): schema: " << ts << endl;
+ if (!ts)
+ return false;
+
+ //temp. hack: avoid problems with autonumber
+ // see http://bugs.kde.org/show_bug.cgi?id=89381
+ int p_id = part()->info()->projectPartID();
+
+ if (p_id<0) {
+ // Find first available custom part ID by taking the greatest
+ // existing custom ID (if it exists) and adding 1.
+ p_id = (int)KexiPart::UserObjectType;
+ tristate success = m_parentWindow->project()->dbConnection()->querySingleNumber(
+ "SELECT max(p_id) FROM kexi__parts", p_id);
+ if (!success) {
+ // Couldn't read part id's from the kexi__parts table
+ return false;
+ } else {
+ // Got a maximum part ID, or there were no parts
+ p_id = p_id + 1;
+ p_id = QMAX(p_id, (int)KexiPart::UserObjectType);
+ }
+ }
+
+ KexiDB::FieldList *fl = ts->subList("p_id", "p_name", "p_mime", "p_url");
+ kexidbg << "KexiDialogBase::storeNewData(): fieldlist: "
+ << (fl ? fl->debugString() : QString::null) << endl;
+ if (!fl)
+ return false;
+
+ kexidbg << part()->info()->ptr()->untranslatedGenericName() << endl;
+// QStringList sl = part()->info()->ptr()->propertyNames();
+// for (QStringList::ConstIterator it=sl.constBegin();it!=sl.constEnd();++it)
+// kexidbg << *it << " " << part()->info()->ptr()->property(*it).toString() << endl;
+ if (!m_parentWindow->project()->dbConnection()->insertRecord(
+ *fl,
+ QVariant(p_id),
+ QVariant(part()->info()->ptr()->untranslatedGenericName()),
+ QVariant(part()->info()->mimeType()), QVariant("http://www.koffice.org/kexi/" /*always ok?*/)))
+ return false;
+
+ kdDebug() << "KexiDialogBase::storeNewData(): insert success!" << endl;
+ part()->info()->setProjectPartID( p_id );
+ //(int) project()->dbConnection()->lastInsertedAutoIncValue("p_id", "kexi__parts"));
+ kdDebug() << "KexiDialogBase::storeNewData(): new id is: "
+ << part()->info()->projectPartID() << endl;
+
+ part()->info()->setIdStoredInPartDatabase(true);
+ }
+
+ /* Sets 'dirty' flag on every dialog's view. */
+ setDirty(false);
+// v->setDirty(false);
+ //new schema data has now ID updated to a unique value
+ //-assign that to item's identifier
+ m_item->setIdentifier( m_schemaData->id() );
+ m_parentWindow->project()->addStoredItem( part()->info(), m_item );
+
+ return true;
+}
+
+tristate KexiDialogBase::storeData(bool dontAsk)
+{
+ if (neverSaved())
+ return false;
+ KexiViewBase *v = selectedView();
+ if (!v)
+ return false;
+
+#define storeData_ERR \
+ setStatus(m_parentWindow->project()->dbConnection(), i18n("Saving object's data failed."),"");
+
+ //save changes using transaction
+ KexiDB::Transaction transaction = m_parentWindow->project()->dbConnection()->beginTransaction();
+ if (transaction.isNull()) {
+ storeData_ERR;
+ return false;
+ }
+ KexiDB::TransactionGuard tg(transaction);
+
+ const tristate res = v->storeData(dontAsk);
+ if (~res) //trans. will be cancelled
+ return res;
+ if (!res) {
+ storeData_ERR;
+ return res;
+ }
+ if (!tg.commit()) {
+ storeData_ERR;
+ return false;
+ }
+ /* Sets 'dirty' flag on every dialog's view. */
+ setDirty(false);
+// v->setDirty(false);
+ return true;
+}
+
+void KexiDialogBase::activate()
+{
+ KexiViewBase *v = selectedView();
+ //kdDebug() << "focusWidget(): " << focusWidget()->name() << endl;
+ if (KexiUtils::hasParent( v, KMdiChildView::focusedChildWidget()))//focusWidget()))
+ KMdiChildView::activate();
+ else {//ah, focused widget is not in this view, move focus:
+ if (v)
+ v->setFocus();
+ }
+ if (v)
+ v->updateActions(true);
+//js: not neeed?? m_parentWindow->invalidateSharedActions(this);
+}
+
+void KexiDialogBase::deactivate()
+{
+ KexiViewBase *v = selectedView();
+ if (v)
+ v->updateActions(false);
+}
+
+void KexiDialogBase::sendDetachedStateToCurrentView()
+{
+ KexiViewBase *v = selectedView();
+ if (v)
+ v->parentDialogDetached();
+}
+
+void KexiDialogBase::sendAttachedStateToCurrentView()
+{
+ KexiViewBase *v = selectedView();
+ if (v)
+ v->parentDialogAttached();
+}
+
+#include "kexidialogbase.moc"
+
diff --git a/kexi/core/kexidialogbase.h b/kexi/core/kexidialogbase.h
new file mode 100644
index 000000000..43dc1ff4e
--- /dev/null
+++ b/kexi/core/kexidialogbase.h
@@ -0,0 +1,352 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDIALOGBASE_H
+#define KEXIDIALOGBASE_H
+
+#include "kexipartguiclient.h"
+#include "kexiactionproxy.h"
+#include "kexi.h"
+#include "kexipart.h"
+
+#include <qguardedptr.h>
+
+#include <kmdichildview.h>
+#include <kxmlguiclient.h>
+
+class QWidgetStack;
+class KexiMainWindow;
+class KexiViewBase;
+class KActionCollection;
+class KexiContextHelpInfo;
+namespace KexiPart {
+ class Part;
+}
+
+namespace KoProperty {
+ class Set;
+}
+
+//! Privides temporary data shared between KexiDialogBase's views (KexiView's)
+/*! Designed for reimplementation, if needed. */
+class KEXICORE_EXPORT KexiDialogTempData : public QObject
+{
+ public:
+ KexiDialogTempData(QObject* parent)
+ : QObject(parent, "KexiDialogTempData")
+ , proposeOpeningInTextViewModeBecauseOfProblems(false)
+ {}
+ /*! Initially false, KexiPart::Part implementation can set this to true
+ on data loading (e.g. in loadSchemaData()), to indicate that TextView mode
+ could be used instead of DataView or DesignView, because there are problems
+ with opening object.
+
+ For example, in KexiQueryPart::loadSchemaData() query statement can be invalid,
+ and thus could not be displayed in DesignView mode or executed for DataView.
+ So, this flag is set to true and user is asked for confirmation for switching
+ to TextView (SQL Editor).
+
+ After switching to TextView, this flag is cleared.
+ */
+ bool proposeOpeningInTextViewModeBecauseOfProblems : 1;
+};
+
+//! Base class for child window of Kexi's main application window.
+/*! This class can contain a number of configurable views, switchable using toggle action.
+ It also automatically works as a proxy for shared (application-wide) actions.
+*/
+class KEXICORE_EXPORT KexiDialogBase :
+ public KMdiChildView,
+ public KexiActionProxy,
+ public Kexi::ObjectStatus
+{
+ Q_OBJECT
+
+ public:
+ KexiDialogBase(KexiMainWindow *parent, const QString &caption = QString::null);
+ virtual ~KexiDialogBase();
+
+ bool isRegistered();
+
+ //! \return currently selected view or 0 if there is no current view
+ KexiViewBase *selectedView() const;
+
+ /*! \return a view for a given \a mode or 0 if there's no such mode available (or opened).
+ This does not open mode if it's not opened. */
+ KexiViewBase *viewForMode(int mode) const;
+
+ //! Adds \a view for the dialog. It will be the _only_ view (of unspecified mode) for the dialog
+ void addView(KexiViewBase *view);
+
+ /*! \return main (top level) widget inside this dialog.
+ This widget is used for e.g. determining minimum size hint and size hint. */
+// virtual QWidget* mainWidget() = 0;
+
+ /*! reimplemented: minimum size hint is inherited from currently visible view. */
+ virtual QSize minimumSizeHint() const;
+
+ /*! reimplemented: size hint is inherited from currently visible view. */
+ virtual QSize sizeHint() const;
+
+ KexiMainWindow *mainWin() const { return m_parentWindow; }
+
+ //js todo: maybe remove this since it's often the same as partItem()->identifier()?:
+
+ /*! This method sets internal identifier for the dialog, but
+ if there is a part item associated with this dialog (see partItem()),
+ partItem()->identifier() will be is used as identifier, so this method is noop.
+ Thus, it's usable only for dialog types when no part item is assigned. */
+ void setId(int id) { m_id = id; }
+
+ /*! If there is a part item associated with this dialog (see partItem()),
+ partItem()->identifier() is returned, otherwise internal dialog's identifier
+ (previously set by setID()) is returned. */
+ int id() const;
+
+ //! \return Kexi part used to create this window
+ inline KexiPart::Part* part() const { return m_part; }
+
+ //! \return Kexi part item used to create this window
+ KexiPart::Item *partItem() const { return m_item; }
+
+ //! Kexi dialog's gui COMMON client.
+ //! It's obtained by querying part object for this dialog.
+ KexiPart::GUIClient* commonGUIClient() const;
+
+ //! Kexi dialog's gui client for currently selected view.
+ //! It's obtained by querying part object for this dialog.
+ KexiPart::GUIClient* guiClient() const;
+
+ /*! Tries to close the dialog. \return true if closing is accepted
+ (sometimes, user may not want to close the dialog by pressing cancel).
+ If \a dontSaveChanges if true, changes are not saved even if this dialog is dirty. */
+//js removed bool tryClose(bool dontSaveChanges);
+
+ /*! \return name of icon provided by part that created this dialog.
+ The name is used by KexiMainWindow to set/reset icon for this dialog. */
+ virtual QString itemIcon();
+
+ /*! \return true if this dialog supports switching to \a mode.
+ \a mode is one of Kexi::ViewMode enum elements.
+ The flags are used e.g. for testing by KexiMainWindow, if actions
+ of switching to given view mode is available.
+ This member is intialised in KexiPart that creates this KexiDialogBase object. */
+ bool supportsViewMode( int mode ) const { return m_supportedViewModes & mode; }
+
+ /*! \return current view mode for this dialog. */
+ int currentViewMode() const { return m_currentViewMode; }
+
+ /*! Switches this dialog to \a newViewMode.
+ \a viewMode is one of Kexi::ViewMode enum elements.
+ \return true for successful switching
+ True is returned also if user has cancelled switching
+ (rarely, but for any reason) - cancelled is returned.
+ */
+ tristate switchToViewMode( int newViewMode );
+
+ void setContextHelp(const QString& caption, const QString& text, const QString& iconName);
+
+ /*! Internal reimplementation. */
+ virtual bool eventFilter(QObject *obj, QEvent *e);
+
+ /*! Used by Main Window
+ \todo js: PROBABLY REMOVE THESE TWO?
+ */
+ virtual void attachToGUIClient();
+ virtual void detachFromGUIClient();
+
+ /*! True if contents (data) of the dialog is dirty and need to be saved
+ This may or not be used, depending if changes in the dialog
+ are saved immediately (e.g. like in datatableview) or saved by hand (by user)
+ (e.g. like in alter-table dialog).
+ \return true if at least on "dirty" flag is set for one of the dialog's view. */
+ bool dirty() const;
+
+ /*! \return a pointer to view that has recently set dirty flag.
+ This value is cleared when dirty flag is set to false (i.e. upon saving changes). */
+ KexiViewBase* viewThatRecentlySetDirtyFlag() const { return m_viewThatRecentlySetDirtyFlag; }
+
+ /*! \return true, if this dialog's data were never saved.
+ If it's true we're usually try to ask a user if the dialog's
+ data should be saved somewhere. After dialog construction,
+ "neverSaved" flag is set to appropriate value.
+ KexiPart::Item::neverSaved() is reused.
+ */
+ bool neverSaved() const;
+
+ /*! \return property set provided by a current view,
+ or NULL if there is no view set (or the view has no set assigned). */
+ KoProperty::Set *propertySet();
+
+ KexiDB::SchemaData* schemaData() const { return m_schemaData; }
+ /*! Reimpelmented: "*" is added if for 'dirty' dialog's data. */
+// QString caption() const;
+
+ /*! Used by KexiViewBase subclasses. \return temporary data shared between views */
+ KexiDialogTempData *tempData() const { return m_tempData; }
+
+// /*! Used by KexiViewBase subclasses. Sets temporary data shared between views. */
+// void setTempData( KexiDialogTempData* data ) { m_tempData = data; }
+
+ /*! Called primarily by KexiMainWindowImpl to activate dialog.
+ Selected view (if present) is also informed about activation. */
+ void activate();
+
+ /*! Called primarily by KexiMainWindowImpl to deactivate dialog.
+ Selected view (if present) is also informed about deactivation. */
+ void deactivate();
+
+ public slots:
+ virtual void setFocus();
+
+ void updateCaption();
+
+ /*! Internal. Called by KexiMainWindowImpl::saveObject().
+ Tells this dialog to save changes of the existing object
+ to the backend. If \a dontAsk is true, no question dialog will
+ be shown to the user. The default is false.
+ \sa storeNewData()
+ \return true on success, false on failure and cancelled when storing has been cancelled. */
+ tristate storeData(bool dontAsk = false);
+
+ /*! Internal. Called by KexiMainWindowImpl::saveObject().
+ Tells this dialog to create and store data of the new object
+ to the backend.
+ Object's schema data has been never stored,
+ so it is created automatically, using information obtained
+ form part item. On success, part item's ID is updated to new value,
+ and m_schemaData is set. \sa schemaData().
+ \return true on success, false on failure and cancelled when storing has been cancelled. */
+ tristate storeNewData();
+
+ /*! Reimplemented - we're informing the current view about performed detaching by calling
+ KexiViewBase::parentDialogDetached(), so the view can react on this event
+ (by default KexiViewBase::parentDialogDetached() does nothing, you can reimplement it). */
+ void sendDetachedStateToCurrentView();
+
+ /*! W're informing the current view about performed atttaching by calling
+ KexiViewBase::parentDialogAttached(), so the view can react on this event
+ (by default KexiViewBase::parentDialogAttached() does nothing, you can reimplement it). */
+ void sendAttachedStateToCurrentView();
+
+ signals:
+ void updateContextHelp();
+
+ //! emitted when the window is about to close
+ void closing();
+
+ /*! Emited to inform the world that 'dirty' flag changes.
+ Activated by KexiViewBase::setDirty(). */
+ void dirtyChanged(KexiDialogBase*);
+
+ protected slots:
+ /*! Sets 'dirty' flag on every dialog's view. */
+ void setDirty(bool dirty);
+
+ protected:
+ /*! Used by Part::openInstance(),
+ like switchToViewMode( int newViewMode ), but passed \a staticObjectArgs.
+ Only used for parts of class KexiPart::StaticPart. */
+ tristate switchToViewMode( int newViewMode, QMap<QString,QString>* staticObjectArgs,
+ bool& proposeOpeningInTextViewModeBecauseOfProblems);
+
+ void registerDialog();
+
+ virtual void closeEvent( QCloseEvent * e );
+
+ //! \internal
+ void addView(KexiViewBase *view, int mode);
+
+ //! \internal
+ void removeView(int mode);
+
+ int m_supportedViewModes;
+ int m_openedViewModes;
+ int m_currentViewMode;
+
+ inline QWidgetStack * stack() const { return m_stack; }
+
+ //! Used by \a view to inform the dialog about changing state of the "dirty" flag.
+ void dirtyChanged(KexiViewBase* view);
+#if 0
+ /*! Loads large string data \a dataString block (e.g. xml form's representation),
+ indexed with optional \a dataID, from the database backend.
+ \return true on success
+ \sa storeDataBlock(). */
+ bool loadDataBlock( QString &dataString, const QString& dataID = QString::null);
+
+ /*! Stores (potentially large) string data \a dataString, block (e.g. xml form's representation),
+ at the database backend. Block will be stored in "kexi__objectdata" table pointed by
+ this object's id and an optional \a dataID identifier.
+ If there is already such record in the table, it's simply overwritten.
+ \return true on success
+ */
+ bool storeDataBlock( const QString &dataString, const QString& dataID = QString::null );
+
+ /*! Removes (potentially large) string data (e.g. xml form's representation),
+ pointed by optional \a dataID, from the database backend.
+ \return true on success. Does not fail if the block doe not exists.
+ Note that if \a dataID is not specified, all data blocks for this dialog will be removed.
+ \sa storeDataBlock(). */
+ bool removeDataBlock( QString &dataString, const QString& dataID = QString::null);
+
+ /*! @internal
+ Used by KexiDialogBase::storeDataBlock() and by KexiViewBase::storeDataBlock().
+ */
+ bool storeDataBlock_internal( const QString &dataString, int o_id, const QString& dataID );
+#endif
+// void setError(const QString& message, const QString& details);
+
+ bool isDesignModePreloadedForTextModeHackUsed(int newViewMode) const;
+
+ private:
+ KexiMainWindow *m_parentWindow;
+ bool m_isRegistered;
+#ifdef KEXI_NO_CTXT_HELP
+ KexiContextHelpInfo *m_contextHelpInfo;
+#endif
+ int m_id;
+ QGuardedPtr<KexiPart::Part> m_part;
+ KexiPart::Item *m_item;
+ QWidgetStack *m_stack;
+ QString m_origCaption; //!< helper
+ KexiDB::SchemaData* m_schemaData;
+ QGuardedPtr<KexiViewBase> m_newlySelectedView; //!< Used in dirty(), temporary set in switchToViewMode()
+ //!< during view setup, when a new view is not yet raised.
+ //! Used in viewThatRecentlySetDirtyFlag(), modified in dirtyChanged().
+ QGuardedPtr<KexiViewBase> m_viewThatRecentlySetDirtyFlag;
+ QGuardedPtr<KexiDialogTempData> m_tempData; //!< temporary data shared between views
+
+ /*! Created view's mode - helper for switchToViewMode(),
+ KexiViewBase ctor uses that info. >0 values are useful. */
+ int m_creatingViewsMode;
+
+ bool m_destroying : 1; //!< true after entering to the dctor
+ bool m_disableDirtyChanged; //!< used in setDirty(), affects dirtyChanged()
+
+ friend class KexiMainWindow;
+// friend class KexiMainWindowImpl;
+ friend class KexiPart::Part;
+ friend class KexiInternalPart;
+ friend class KexiViewBase;
+};
+
+#endif
+
diff --git a/kexi/core/kexidragobjects.cpp b/kexi/core/kexidragobjects.cpp
new file mode 100644
index 000000000..5cddbca0b
--- /dev/null
+++ b/kexi/core/kexidragobjects.cpp
@@ -0,0 +1,146 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidragobjects.h"
+
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <kdebug.h>
+
+/// implementation of KexiFieldDrag
+
+KexiFieldDrag::KexiFieldDrag(const QString& sourceMimeType, const QString& sourceName,
+ const QString& field, QWidget *parent, const char *name)
+ : QStoredDrag("kexi/field", parent, name)
+{
+ QByteArray data;
+ QDataStream stream1(data,IO_WriteOnly);
+ stream1 << sourceMimeType << sourceName << field;
+ setEncodedData(data);
+}
+
+KexiFieldDrag::KexiFieldDrag(const QString& sourceMimeType, const QString& sourceName,
+ const QStringList& fields, QWidget *parent, const char *name)
+ : QStoredDrag((fields.count() > 1) ? "kexi/fields" : "kexi/field", parent, name)
+{
+ QByteArray data;
+ QDataStream stream1(data,IO_WriteOnly);
+ if (fields.count() > 1)
+ stream1 << sourceMimeType << sourceName << fields;
+ else {
+ QString field;
+ if (fields.count() == 1)
+ field = fields.first();
+ else
+ kexidbg << "KexiFieldDrag::KexiFieldDrag(): fields list is empty!" << endl;
+ stream1 << sourceMimeType << sourceName << field;
+ }
+ setEncodedData(data);
+}
+
+KexiFieldDrag::~KexiFieldDrag()
+{
+}
+
+bool
+KexiFieldDrag::canDecodeSingle(QMimeSource *e)
+{
+ return e->provides("kexi/field");
+}
+
+bool
+KexiFieldDrag::canDecodeMultiple(QMimeSource *e)
+{
+ return e->provides("kexi/field") || e->provides("kexi/fields");
+}
+
+bool
+KexiFieldDrag::decodeSingle( QDropEvent* e, QString& sourceMimeType,
+ QString& sourceName, QString& field )
+{
+ QByteArray payload( e->data("kexi/field") );
+ if (payload.isEmpty())
+ return false;
+ e->accept();
+ QDataStream stream1(payload, IO_ReadOnly);
+ stream1 >> sourceMimeType;
+ stream1 >> sourceName;
+ stream1 >> field;
+// kdDebug() << "KexiFieldDrag::decode() decoded: " << sourceMimeType<<"/"<<sourceName<<"/"<<field << endl;
+ return true;
+}
+
+bool
+KexiFieldDrag::decodeMultiple( QDropEvent* e, QString& sourceMimeType,
+ QString& sourceName, QStringList& fields )
+{
+ QByteArray payload( e->data("kexi/fields") );
+ if (payload.isEmpty()) {//try single
+ QString field;
+ bool res = KexiFieldDrag::decodeSingle( e, sourceMimeType, sourceName, field );
+ if (!res)
+ return false;
+ fields.append(field);
+ return true;
+ }
+ e->accept();
+ QDataStream stream1(payload, IO_ReadOnly);
+ stream1 >> sourceMimeType;
+ stream1 >> sourceName;
+ stream1 >> fields;
+// kdDebug() << "KexiFieldDrag::decode() decoded: " << sourceMimeType<<"/"<<sourceName<<"/"<<fields << endl;
+ return true;
+}
+
+/// implementation of KexiDataProviderDrag
+
+KexiDataProviderDrag::KexiDataProviderDrag(const QString& sourceMimeType, const QString& sourceName,
+ QWidget *parent, const char *name)
+ : QStoredDrag("kexi/dataprovider", parent, name)
+{
+ QByteArray data;
+ QDataStream stream1(data,IO_WriteOnly);
+ stream1 << sourceMimeType << sourceName;
+ setEncodedData(data);
+}
+
+
+bool
+KexiDataProviderDrag::canDecode(QDragMoveEvent *e)
+{
+ return e->provides("kexi/dataprovider");
+}
+
+bool
+KexiDataProviderDrag::decode( QDropEvent* e, QString& sourceMimeType, QString& sourceName)
+{
+ QCString tmp;
+ QByteArray payload = e->data("kexidataprovider");
+ if(payload.size())
+ {
+ e->accept();
+ QDataStream stream1(payload, IO_ReadOnly);
+ stream1 >> sourceMimeType;
+ stream1 >> sourceName;
+// kdDebug() << "KexiDataProviderDrag::decode() decoded: " << sourceMimeType <<"/"<<sourceName<< endl;
+ return true;
+ }
+ return false;
+}
diff --git a/kexi/core/kexidragobjects.h b/kexi/core/kexidragobjects.h
new file mode 100644
index 000000000..9b7a42ec3
--- /dev/null
+++ b/kexi/core/kexidragobjects.h
@@ -0,0 +1,82 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_DRAGOBJECTS_H_
+#define KEXI_DRAGOBJECTS_H_
+
+#include <qdragobject.h>
+
+class QString;
+class QStringList;
+class QWidget;
+
+//! Drag object containing information about field(s).
+class KEXICORE_EXPORT KexiFieldDrag : public QStoredDrag
+{
+ public:
+ /*! Creates drag object for a single field \a field. */
+ KexiFieldDrag(const QString& sourceMimeType, const QString& sourceName,
+ const QString& field, QWidget *parent, const char *name);
+
+ /*! Creates drag object for multiple fields \a fields.
+ If there's less than two elements in the list, data is set up as for above ctor. */
+ KexiFieldDrag(const QString& sourceMimeType, const QString& sourceName,
+ const QStringList& field, QWidget *parent=0, const char *name=0);
+
+ ~KexiFieldDrag();
+
+ void addField(const QString& field);
+
+ /*! \return true if event \a e (of class QDragMoveEvent or QDropEvent)
+ can be decoded as "kexi/field" data */
+ static bool canDecodeSingle( QMimeSource* e );
+
+ /*! \return true if event \a e (of class QDragMoveEvent or QDropEvent)
+ can be decoded as "kexi/fields" data. If decoding of "kexi/field"
+ type is supported, decoding of "kexi/fields" is always supported.
+ */
+ static bool canDecodeMultiple( QMimeSource* e );
+
+ /*! Decodes data of single-field drag ("kexi/field" mime type) coming with event \a e.
+ Sets \a sourceMimeType, \a sourceName and \a field.
+ \return true on successful decoding (\a e will be accepted in such case). */
+ static bool decodeSingle( QDropEvent* e, QString& sourceMimeType,
+ QString& sourceName, QString& field );
+
+ /*! Decodes data of multiple-field drag ("kexi/fields" mime type) coming with event \a e.
+ Sets \a sourceMimeType, \a sourceName and \a fields. Also works with "kexi/field" data.
+ \return true on successful decoding (\a e will be accepted in such case). */
+ static bool decodeMultiple( QDropEvent* e, QString& sourceMimeType,
+ QString& sourceName, QStringList& fields );
+};
+
+class KEXICORE_EXPORT KexiDataProviderDrag : public QStoredDrag
+{
+ public:
+ KexiDataProviderDrag(const QString& sourceMimeType, const QString& sourceName,
+ QWidget *parent=0, const char *name=0);
+ ~KexiDataProviderDrag() { };
+
+ static bool canDecode( QDragMoveEvent* e);
+ static bool decode( QDropEvent* e, QString& sourceMimeType, QString& sourceName);
+
+};
+
+#endif
diff --git a/kexi/core/kexievents.cpp b/kexi/core/kexievents.cpp
new file mode 100644
index 000000000..2cafe2d3c
--- /dev/null
+++ b/kexi/core/kexievents.cpp
@@ -0,0 +1,92 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexievents.h"
+
+Event::Event(QObject *sender, const QCString &signal,
+ QObject *receiver, const QCString &slot)
+{
+ m_sender = sender;
+ m_receiver = receiver;
+ m_slot = slot;
+ m_signal = signal;
+}
+
+Event::Event(QObject *sender, const QCString &signal,
+ const QCString &functionName)
+{
+ m_sender = sender;
+ m_signal = signal;
+ m_slot = functionName;
+}
+
+void
+EventList::addEvent(Event *event)
+{
+ if(event)
+ append(event);
+}
+
+void
+EventList::addEvent(QObject *sender, const QCString &signal, QObject *receiver, const QCString &slot)
+{
+ Event *ev = new Event(sender, signal, receiver, slot);
+ append(ev);
+}
+
+void
+EventList::addEvent(QObject *sender, const QCString &signal, const QCString &function)
+{
+ Event *ev = new Event(sender, signal, function);
+ append(ev);
+}
+
+void
+EventList::removeEvent(Event *event)
+{
+ if(!event) return;
+ remove(event);
+ delete event;
+}
+
+EventList*
+EventList::allEventsForObject(QObject *widget)
+{
+ if(!widget) return 0;
+
+ EventList *l = new EventList();
+ EventList::ConstIterator endIt = constEnd();
+ for(EventList::ConstIterator it = constBegin(); it != endIt; ++it) {
+ if( ((*it)->sender() == widget) || ( (*it)->receiver() == widget) )
+ l->addEvent(*it);
+ }
+
+ return l;
+}
+
+void
+EventList::removeAllEventsForObject(QObject *widget)
+{
+ EventList::ConstIterator endIt = constEnd();
+ for(EventList::ConstIterator it = constBegin(); it != endIt; ++it) {
+ if( ((*it)->sender() == widget) || ( (*it)->receiver() == widget) )
+ removeEvent(*it);
+ }
+}
+
diff --git a/kexi/core/kexievents.h b/kexi/core/kexievents.h
new file mode 100644
index 000000000..145ee68d2
--- /dev/null
+++ b/kexi/core/kexievents.h
@@ -0,0 +1,100 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNEREVENTS_H
+#define KFORMDESIGNEREVENTS_H
+
+#include <qvaluelist.h>
+#include <qguardedptr.h>
+
+class QDomNode;
+class QObject;
+
+//! A simple class to store events
+/*! There are three different types of events (an maybe more in the future):
+ * signal to slot: sender and receiver are both widgets.
+ * signal to user function: whenever the signal is emitted, a function in the form script is called.
+ * signal to action: the signal activates an application action (eg addNewRecord in Kexi)
+ (other :* global signal to user function: an application global signal (new window opened, etc.) calls a user script function)
+
+ \todo add aliases for slot()?? (eg actionName())
+ */
+class KEXICORE_EXPORT Event
+{
+ public:
+ Event(QObject *sender, const QCString &signal,
+ QObject *receiver, const QCString &slot);
+ Event(QObject *sender, const QCString &signal,
+ const QCString &functionName);
+ Event() : m_type(Slot) {;}
+ ~Event() {;}
+
+ enum { Slot=1000, UserFunction, Action }; //! Event types
+ int type() {return m_type; }
+ void setType(int type) { m_type = type; }
+
+ QObject* sender() const { return m_sender; }
+ QObject* receiver() const { return m_receiver; }
+ QCString signal() const { return m_signal; }
+ QCString slot() const { return m_slot; }
+
+ void setSender(QObject *o) { m_sender = o; }
+ void setReceiver(QObject *o) { m_receiver = o; }
+ void setSignal(const QCString &s) { m_signal = s; }
+ void setSlot(const QCString &s) { m_slot = s; }
+
+ protected:
+ QGuardedPtr<QObject> m_sender;
+ QCString m_signal;
+ QGuardedPtr<QObject> m_receiver;
+ QCString m_slot;
+ int m_type;
+};
+
+class KEXICORE_EXPORT EventList : protected QValueList<Event*>
+{
+ public:
+ EventList() {;}
+ ~EventList() {;}
+
+ /*! Adds an event in list. Other overload are available, so that
+ other classes don't have to use Event class in simple cases. */
+ void addEvent(Event *event);
+ void addEvent(QObject *sender, const QCString &signal, QObject *receiver, const QCString &slot);
+ void addEvent(QObject *sender, const QCString &signal, const QCString &action);
+ /*! Removes the Event \a event from the FormScript's list. */
+ void removeEvent(Event *event);
+
+ /*! \return A list of events related to widget \a name (ie where Event::sender()
+ or Event::receiver() == name). */
+ EventList* allEventsForObject(QObject *object);
+ /*! Replace all ocurrences of \a oldname with \a newName inside the list. */
+ //void renameWidget(const QCString &oldName, const QCString &newName);
+ /*! Removes all events related to widget \a name. Called eg when widget is destroyed. */
+ void removeAllEventsForObject(QObject *object);
+
+ // make some QValueList function accessible by other classes
+ QValueListConstIterator<Event*> constBegin() const { return QValueList<Event*>::constBegin(); }
+ QValueListConstIterator<Event*> constEnd() const { return QValueList<Event*>::constEnd(); }
+ bool isEmpty() const { return QValueList<Event*>::isEmpty(); }
+};
+
+
+#endif
+
diff --git a/kexi/core/kexiguimsghandler.cpp b/kexi/core/kexiguimsghandler.cpp
new file mode 100644
index 000000000..af5f3a6e1
--- /dev/null
+++ b/kexi/core/kexiguimsghandler.cpp
@@ -0,0 +1,176 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiguimsghandler.h"
+
+#include "kexi.h"
+#include <kexidb/utils.h>
+#include <kexiutils/utils.h>
+
+#include <kmessagebox.h>
+#include <kdialogbase.h>
+
+KexiGUIMessageHandler::KexiGUIMessageHandler(QWidget *parent)
+: KexiDB::MessageHandler(parent)
+{
+}
+
+KexiGUIMessageHandler::~KexiGUIMessageHandler()
+{
+}
+
+/*virtual*/
+void
+KexiGUIMessageHandler::showErrorMessage(KexiDB::Object *obj,
+ const QString& msg)
+{
+ QString _msg(msg);
+ if (!obj) {
+ showErrorMessage(_msg);
+ return;
+ }
+ QString details;
+ KexiDB::getHTMLErrorMesage(obj, _msg, details);
+ showErrorMessage(_msg, details);
+}
+
+/*virtual*/
+void
+KexiGUIMessageHandler::showErrorMessage(const QString &title, const QString &details)
+{
+ showMessage(Error, title, details);
+}
+
+void
+KexiGUIMessageHandler::showSorryMessage(const QString &title, const QString &details)
+{
+ showMessage(Sorry, title, details);
+}
+
+void KexiGUIMessageHandler::showErrorMessage(const QString &msg, const QString &details,
+ KexiDB::Object *obj)
+{
+ QString _msg(msg);
+ if (!obj) {
+ showErrorMessage(_msg, details);
+ return;
+ }
+ QString _details(details);
+ KexiDB::getHTMLErrorMesage(obj, _msg, _details);
+ showErrorMessage(_msg, _details);
+}
+
+void
+KexiGUIMessageHandler::showErrorMessage(Kexi::ObjectStatus *status)
+{
+ showErrorMessage("", status);
+}
+
+void
+KexiGUIMessageHandler::showErrorMessage(const QString &message, Kexi::ObjectStatus *status)
+{
+ if (status && status->error()) {
+ QString msg(message);
+ if (msg.isEmpty() || msg==status->message) {
+ msg = status->message;
+ status->message = status->description;
+ status->description = "";
+ }
+ QString desc;
+ if (!status->message.isEmpty()) {
+ if (status->description.isEmpty()) {
+ desc = status->message;
+ } else {
+ msg += (QString("<br><br>") + status->message);
+ desc = status->description;
+ }
+ }
+ showErrorMessage(msg, desc, status->dbObject());
+ }
+ else {
+ showErrorMessage(message);
+ }
+ status->clearStatus();
+}
+
+void
+KexiGUIMessageHandler::showMessage(MessageType type,
+ const QString &title, const QString &details, const QString& dontShowAgainName)
+{
+ if (!m_enableMessages)
+ return;
+
+ //'wait' cursor is a nonsense now
+ KexiUtils::removeWaitCursor();
+
+ QString msg(title);
+ if (title.isEmpty())
+ msg = i18n("Unknown error");
+ msg = "<qt><p>"+msg+"</p>";
+ if (!details.isEmpty()) {
+ switch (type) {
+ case Error:
+ KMessageBox::detailedError(m_messageHandlerParentWidget, msg, details);
+ break;
+ case Warning:
+ showWarningContinueMessage(title, details, dontShowAgainName);
+ break;
+ default: //Sorry
+ KMessageBox::detailedSorry(m_messageHandlerParentWidget, msg, details);
+ }
+ }
+ else {
+ KMessageBox::messageBox(m_messageHandlerParentWidget,
+ type==Error ? KMessageBox::Error : KMessageBox::Sorry, msg);
+ }
+}
+
+void KexiGUIMessageHandler::showWarningContinueMessage(const QString &title, const QString &details,
+ const QString& dontShowAgainName)
+{
+ if (!KMessageBox::shouldBeShownContinue(dontShowAgainName))
+ return;
+ KDialogBase *dialog = new KDialogBase(
+ i18n("Warning"), KDialogBase::Yes, KDialogBase::Yes, KDialogBase::No,
+ m_messageHandlerParentWidget, "warningContinue", true, true, KStdGuiItem::cont() );
+ bool checkboxResult = false;
+ KMessageBox::createKMessageBox(dialog, QMessageBox::Warning,
+ title + (details.isEmpty() ? QString::null : (QString("\n")+details)), QStringList(),
+ dontShowAgainName.isEmpty() ? QString::null : i18n("Do not show this message again"),
+ &checkboxResult, 0);
+ if (checkboxResult)
+ KMessageBox::saveDontShowAgainContinue(dontShowAgainName);
+}
+
+int KexiGUIMessageHandler::askQuestion( const QString& message,
+ KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult,
+ const KGuiItem &buttonYes,
+ const KGuiItem &buttonNo,
+ const QString &dontShowAskAgainName,
+ int options )
+{
+ Q_UNUSED(defaultResult);
+ if (KMessageBox::WarningContinueCancel == dlgType)
+ return KMessageBox::warningContinueCancel(m_messageHandlerParentWidget,
+ message, QString::null, buttonYes, dontShowAskAgainName, options);
+ else
+ return KMessageBox::messageBox(m_messageHandlerParentWidget,
+ dlgType, message, QString::null, buttonYes, buttonNo, dontShowAskAgainName, options);
+}
+
diff --git a/kexi/core/kexiguimsghandler.h b/kexi/core/kexiguimsghandler.h
new file mode 100644
index 000000000..6105f3115
--- /dev/null
+++ b/kexi/core/kexiguimsghandler.h
@@ -0,0 +1,62 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIGUIMSGHANDLER_H
+#define KEXIGUIMSGHANDLER_H
+
+#include <core/kexi.h>
+#include <kexidb/msghandler.h>
+
+class KEXICORE_EXPORT KexiGUIMessageHandler : public KexiDB::MessageHandler
+{
+ public:
+ KexiGUIMessageHandler(QWidget *parent = 0);
+ virtual ~KexiGUIMessageHandler();
+ virtual void showErrorMessage(const QString &title, const QString &details = QString::null);
+ virtual void showErrorMessage(KexiDB::Object *obj, const QString& msg = QString::null);
+
+ void showErrorMessage(const QString&,const QString&,KexiDB::Object *obj);
+ void showErrorMessage(Kexi::ObjectStatus *status);
+ void showErrorMessage(const QString &message, Kexi::ObjectStatus *status);
+
+ /*! Displays a "Sorry" message with \a title text and optional \a details. */
+ void showSorryMessage(const QString &title, const QString &details = QString::null);
+
+ /*! Displays a message of a type \a type, with \a title text and optional \a details.
+ \a dontShowAgainName can be specified to add "Do not show again" option if \a type is Warning. */
+ virtual void showMessage(MessageType type, const QString &title, const QString &details,
+ const QString& dontShowAgainName = QString::null);
+
+ /*! Displays a Warning message with \a title text and optional \a details
+ with "Continue" button instead "OK".
+ \a dontShowAgainName can be specified to add "Do not show again" option. */
+ virtual void showWarningContinueMessage(const QString &title, const QString &details = QString::null,
+ const QString& dontShowAgainName = QString::null);
+
+ /*! Interactively asks a question using KMessageBox.
+ See KexiDB::MessageHandler::askQuestion() for details. */
+ virtual int askQuestion( const QString& message,
+ KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult,
+ const KGuiItem &buttonYes=KStdGuiItem::yes(),
+ const KGuiItem &buttonNo=KStdGuiItem::no(),
+ const QString &dontShowAskAgainName = QString::null,
+ int options = KMessageBox::Notify );
+};
+
+#endif
diff --git a/kexi/core/kexiinternalpart.cpp b/kexi/core/kexiinternalpart.cpp
new file mode 100644
index 000000000..fcbb8f17d
--- /dev/null
+++ b/kexi/core/kexiinternalpart.cpp
@@ -0,0 +1,209 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiinternalpart.h"
+
+#include "kexidialogbase.h"
+#include "kexiviewbase.h"
+#include "keximainwindow.h"
+
+#include <qasciidict.h>
+#include <qdialog.h>
+
+#include <kdebug.h>
+#include <klibloader.h>
+#include <klocale.h>
+#include <ktrader.h>
+#include <kparts/componentfactory.h>
+#include <kexidb/msghandler.h>
+
+//! @internal
+class KexiInternalPartManager
+{
+ public:
+ KexiInternalPartManager()
+ : m_parts(101, false)
+ {
+ m_parts.setAutoDelete(false);
+ }
+
+ KexiInternalPart* findPart(KexiDB::MessageHandler *msgHdr, const char* partName)
+ {
+ KexiInternalPart *part = m_parts[partName];
+ if (!part) {
+ QCString fullname("kexihandler_");
+ fullname += QCString(partName).lower();
+ part = KParts::ComponentFactory::createInstanceFromLibrary<KexiInternalPart>(
+ fullname, 0, fullname);
+ if (!part) {
+ if (msgHdr)
+ msgHdr->showErrorMessage(i18n("Could not load \"%1\" plugin.").arg(partName));
+ }
+ else
+ m_parts.insert(partName, part);
+ }
+ return part;
+ }
+
+ private:
+
+ QAsciiDict<KexiInternalPart> m_parts;
+};
+
+KexiInternalPartManager internalPartManager;
+
+//----------------------------------------------
+
+KexiInternalPart::KexiInternalPart(QObject *parent, const char *name, const QStringList &)
+ : QObject(parent, name)
+ , m_uniqueDialog(true)
+ , m_cancelled(false)
+{
+}
+
+KexiInternalPart::~KexiInternalPart()
+{
+}
+
+//static
+const KexiInternalPart *
+KexiInternalPart::part(KexiDB::MessageHandler *msgHdr, const char* partName)
+{
+ return internalPartManager.findPart(msgHdr, partName);
+}
+
+//static
+QWidget* KexiInternalPart::createWidgetInstance(const char* partName,
+ const char* widgetClass, KexiDB::MessageHandler *msgHdr, KexiMainWindow* mainWin,
+ QWidget *parent, const char *objName, QMap<QString,QString>* args)
+{
+ KexiInternalPart *part = internalPartManager.findPart(msgHdr, partName);
+ if (!part)
+ return 0; //fatal!
+ return part->createWidget(widgetClass, mainWin, parent, objName ? objName : partName, args);
+}
+
+KexiDialogBase* KexiInternalPart::findOrCreateKexiDialog(
+ KexiMainWindow* mainWin, const char *objName)
+{
+ if (m_uniqueDialog && !m_uniqueWidget.isNull())
+ return dynamic_cast<KexiDialogBase*>((QWidget*)m_uniqueWidget);
+// KexiDialogBase *dlg = createDialog(mainWin, objName);
+ KexiDialogBase * dlg = new KexiDialogBase(mainWin, "");
+ KexiViewBase *view = createView(mainWin, 0, objName);
+ if (!view)
+ return 0;
+
+// dlg->show();
+
+ if (m_uniqueDialog)
+ m_uniqueWidget = dlg; //recall unique!
+ dlg->addView(view);
+ dlg->setCaption( view->caption() );
+ dlg->setTabCaption( view->caption() );
+ dlg->resize(view->sizeHint());
+ dlg->setMinimumSize(view->minimumSizeHint().width(),view->minimumSizeHint().height());
+ dlg->setId( mainWin->generatePrivateID() );
+ dlg->registerDialog();
+ return dlg;
+}
+
+//static
+KexiDialogBase* KexiInternalPart::createKexiDialogInstance(
+ const char* partName,
+ KexiDB::MessageHandler *msgHdr, KexiMainWindow* mainWin, const char *objName)
+{
+ KexiInternalPart *part = internalPartManager.findPart(msgHdr, partName);
+ if (!part) {
+ kdDebug() << "KexiInternalPart::createDialogInstance() !part" << endl;
+ return 0; //fatal!
+ }
+ return part->findOrCreateKexiDialog(mainWin, objName ? objName : partName);
+}
+
+//static
+QDialog* KexiInternalPart::createModalDialogInstance(const char* partName,
+ const char* dialogClass, KexiDB::MessageHandler *msgHdr, KexiMainWindow* mainWin,
+ const char *objName, QMap<QString,QString>* args)
+{
+ KexiInternalPart *part = internalPartManager.findPart(msgHdr, partName);
+ if (!part) {
+ kdDebug() << "KexiInternalPart::createDialogInstance() !part" << endl;
+ return 0; //fatal!
+ }
+ QWidget *w;
+ if (part->uniqueDialog() && !part->m_uniqueWidget.isNull())
+ w = part->m_uniqueWidget;
+ else
+ w = part->createWidget(dialogClass, mainWin, mainWin, objName ? objName : partName, args);
+
+ if (dynamic_cast<QDialog*>(w)) {
+ if (part->uniqueDialog())
+ part->m_uniqueWidget = w;
+ return dynamic_cast<QDialog*>(w);
+ }
+ //sanity
+ if (! (part->uniqueDialog() && !part->m_uniqueWidget.isNull()))
+ delete w;
+ return 0;
+}
+
+//static
+bool KexiInternalPart::executeCommand(const char* partName,
+ KexiMainWindow* mainWin, const char* commandName, QMap<QString,QString>* args)
+{
+ KexiInternalPart *part = internalPartManager.findPart(0, partName);
+ if (!part) {
+ kdDebug() << "KexiInternalPart::createDialogInstance() !part" << endl;
+ return 0; //fatal!
+ }
+ return part->executeCommand(mainWin, commandName, args);
+}
+
+QWidget* KexiInternalPart::createWidget(const char* widgetClass, KexiMainWindow* mainWin,
+ QWidget * parent, const char * objName, QMap<QString,QString>* args)
+{
+ Q_UNUSED(widgetClass);
+ Q_UNUSED(mainWin);
+ Q_UNUSED(parent);
+ Q_UNUSED(objName);
+ Q_UNUSED(args);
+ return 0;
+}
+
+KexiViewBase* KexiInternalPart::createView(KexiMainWindow* mainWin, QWidget * parent,
+ const char * objName)
+{
+ Q_UNUSED(mainWin);
+ Q_UNUSED(parent);
+ Q_UNUSED(objName);
+ return 0;
+}
+
+bool KexiInternalPart::executeCommand(KexiMainWindow* mainWin, const char* commandName,
+ QMap<QString,QString>* args)
+{
+ Q_UNUSED(mainWin);
+ Q_UNUSED(commandName);
+ Q_UNUSED(args);
+ return false;
+}
+
+#include "kexiinternalpart.moc"
diff --git a/kexi/core/kexiinternalpart.h b/kexi/core/kexiinternalpart.h
new file mode 100644
index 000000000..232c3f843
--- /dev/null
+++ b/kexi/core/kexiinternalpart.h
@@ -0,0 +1,159 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIINTERNALPART_H
+#define KEXIINTERNALPART_H
+
+#include <qobject.h>
+#include <qguardedptr.h>
+#include <qvariant.h>
+
+class KexiMainWindow;
+class KexiDialogBase;
+class KexiViewBase;
+
+namespace KexiDB {
+ class MessageHandler;
+}
+
+class QWidget;
+
+/**
+ * @short A prototype for Kexi Internal Parts (plugins) implementation.
+ *
+ * Internal Kexi parts are parts that are not available for users, but loaded
+ * internally be application when needed. Example of such part is Relations Window.
+ * The internal part instance is unique and has no explicitly stored data.
+ * Parts may be able to create widgets or/and dialogs, depending on implementation
+ * (createWidgetInstance(), createDialogInstance()).
+ * Parts can have unique flag set for dialogs (true by default)
+ * - then a dialog created by createDialogInstance() is unique.
+ */
+class KEXICORE_EXPORT KexiInternalPart : public QObject
+{
+ Q_OBJECT
+
+ public:
+ KexiInternalPart(QObject *parent, const char *name, const QStringList &);
+ virtual ~KexiInternalPart();
+
+ KexiDialogBase *instance(KexiMainWindow *parent);
+
+ /*! Creates a new widget instance using part \a partName.
+ \a widgetClass is a pseudo class used in case when the part offers more
+ than one widget type.
+ \a msgHdr is a message handler for displaying error messages.
+ \a args is two-way optional argument: it can contain custom options used
+ on widget's creation. Depending on implementation, the created widget can write its
+ state (e.g. result or status information) back to this argument.
+ Created widget will have assigned \a parent widget and \a objName name. */
+ static QWidget* createWidgetInstance(const char* partName, const char* widgetClass,
+ KexiDB::MessageHandler *msgHdr, KexiMainWindow* mainWin,
+ QWidget *parent, const char *objName = 0, QMap<QString,QString>* args = 0);
+
+ /*! For convenience. */
+ static QWidget* createWidgetInstance(const char* partName,
+ KexiDB::MessageHandler *msgHdr, KexiMainWindow* mainWin,
+ QWidget *parent, const char *objName = 0, QMap<QString,QString>* args = 0)
+ { return createWidgetInstance(partName, 0, msgHdr, mainWin, parent, objName, args); }
+
+ /*! Creates a new dialog instance. If such instance already exists,
+ and is unique (see uniqueDialog()) it is just returned.
+ The part knows about destroying its dialog instance, (if it is uinque),
+ so on another call the dialog will be created again.
+ \a msgHdr is a message handler for displaying error messages.
+ The dialog is assigned to \a mainWin as its parent,
+ and \a objName name is set. */
+ static KexiDialogBase* createKexiDialogInstance(const char* partName,
+ KexiDB::MessageHandler *msgHdr, KexiMainWindow* mainWin, const char *objName = 0);
+
+ /*! Creates a new modal dialog instance (QDialog or a subclass).
+ If such instance already exists, and is unique (see uniqueDialog())
+ it is just returned.
+ \a dialogClass is a pseudo class used in case when the part offers more
+ than one dialog type.
+ \a msgHdr is a message handler for displaying error messages.
+ \a args is two-way optional argument: it can contain custom options used
+ on widget's creation. Depending on implementation, the created dialog can write its
+ state (e.g. result or status information) back to this argument.
+ The part knows about destroying its dialog instance, (if it is uinque),
+ so on another call the dialog will be created again.
+ The dialog is assigned to \a mainWin as its parent,
+ and \a objName name is set. */
+ static QDialog* createModalDialogInstance(const char* partName,
+ const char* dialogClass, KexiDB::MessageHandler *msgHdr, KexiMainWindow* mainWin,
+ const char *objName = 0, QMap<QString,QString>* args = 0);
+
+ /*! Adeded For convenience. */
+ static QDialog* createModalDialogInstance(const char* partName,
+ KexiDB::MessageHandler *msgHdr, KexiMainWindow* mainWin, const char *objName = 0,
+ QMap<QString,QString>* args = 0)
+ { return createModalDialogInstance(partName, 0, msgHdr, mainWin, objName, args); }
+
+ /*! Executes a command \a commandName (usually nonvisual) using part called \a partName.
+ The result can be put into the \a args. \return true on successful calling. */
+ static bool executeCommand(const char* partName,
+ KexiMainWindow* mainWin, const char* commandName, QMap<QString,QString>* args = 0);
+
+ /*! \return internal part of a name \a partName. Shouldn't be usable. */
+ static const KexiInternalPart* part(KexiDB::MessageHandler *msgHdr, const char* partName);
+
+ /*! \return true if the part can create only one (unique) dialog. */
+ inline bool uniqueDialog() const { return m_uniqueDialog; }
+
+ /*! \return true if the part creation has been cancelled (eg. by a user)
+ so it wasn't an error. Internal part's impelmentation should set it to true when needed.
+ False by default. */
+ inline bool cancelled() const { return m_cancelled; }
+
+ protected:
+ /*! Used internally */
+ KexiDialogBase *findOrCreateKexiDialog(KexiMainWindow* mainWin,
+ const char *objName);
+
+ /*! Reimplement this if your internal part has to return widgets
+ or QDialog objects. */
+ virtual QWidget *createWidget(const char* widgetClass, KexiMainWindow* mainWin,
+ QWidget * parent, const char * objName = 0, QMap<QString,QString>* args = 0);
+
+// //! Reimplement this if your internal part has to return dialogs
+// virtual KexiDialogBase *createDialog(KexiMainWindow* /*mainWin*/,
+// const char * /*objName*/ =0)
+// { return 0; }
+
+ /*! Reimplement this if your internal part has to return a view object. */
+ virtual KexiViewBase *createView(KexiMainWindow* mainWin, QWidget * parent,
+ const char *objName = 0);
+
+ /*! Reimplement this if your internal part has to execute a command \a commandName
+ (usually nonvisual). Arguments are put into \a args and the result can be put into the \a args.
+ \return true on successful calling. */
+ virtual bool executeCommand(KexiMainWindow* mainWin, const char* commandName,
+ QMap<QString,QString>* args = 0);
+
+ //! Unique dialog - we're using guarded ptr for the dialog so can know if it has been closed
+ QGuardedPtr<QWidget> m_uniqueWidget;
+
+ bool m_uniqueDialog : 1; //!< true if createDialogInstance() should return only one dialog
+
+ bool m_cancelled : 1; //!< Used in cancelled()
+};
+
+#endif
diff --git a/kexi/core/keximainwindow.cpp b/kexi/core/keximainwindow.cpp
new file mode 100644
index 000000000..bf66e6383
--- /dev/null
+++ b/kexi/core/keximainwindow.cpp
@@ -0,0 +1,36 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "keximainwindow.h"
+
+#include <kdebug.h>
+
+KexiMainWindow::KexiMainWindow()
+ : KMdiMainFrm(0L, "keximainwindow")
+ , KexiSharedActionHost(this)
+{
+}
+
+KexiMainWindow::~KexiMainWindow()
+{
+}
+
+#include "keximainwindow.moc"
+
diff --git a/kexi/core/keximainwindow.h b/kexi/core/keximainwindow.h
new file mode 100644
index 000000000..ec1db6333
--- /dev/null
+++ b/kexi/core/keximainwindow.h
@@ -0,0 +1,189 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIMAINWINDOW_H
+#define KEXIMAINWINDOW_H
+
+#include <qmap.h>
+#include <qintdict.h>
+
+#include <kmdimainfrm.h>
+#include <kexiutils/tristate.h>
+
+#include "kexisharedactionhost.h"
+#include "kexi.h"
+
+class KexiDialogBase;
+class KexiProject;
+namespace KexiPart {
+ class Item;
+}
+
+/**
+ * @short Kexi's main window interface
+ * This interface is implemented by KexiMainWindowImpl class.
+ * KexiMainWindow offers simple features what lowers cross-dependency (and also avoids
+ * circular dependencies between Kexi modules).
+ */
+class KEXICORE_EXPORT KexiMainWindow : public KMdiMainFrm, public KexiSharedActionHost
+{
+ Q_OBJECT
+ public:
+ //! Used by printActionForItem()
+ enum PrintActionType {
+ PrintItem,
+ PreviewItem,
+ PageSetupForItem
+ };
+
+ KexiMainWindow();
+ virtual ~KexiMainWindow();
+
+ //! Project data of currently opened project or NULL if no project here yet.
+ virtual KexiProject *project() = 0;
+
+ /*! Registers dialog \a dlg for watching and adds it to the main window's stack. */
+ virtual void registerChild(KexiDialogBase *dlg) = 0;
+
+ virtual QPopupMenu* findPopupMenu(const char *popupName) = 0;
+
+ /*! Generates ID for private "document" like Relations window.
+ Private IDs are negative numbers (while ID regular part instance's IDs are >0)
+ Private means that the object is not stored as-is in the project but is somewhat
+ generated and in most cases there is at most one unique instance document of such type (part).
+ To generate this ID, just app-wide internal counter is used. */
+ virtual int generatePrivateID() = 0;
+
+ /*! \return a list of all actions defined by application.
+ Not all of them are shared. Don't use plug these actions
+ in your windows by hand but user methods from KexiViewBase! */
+ virtual KActionPtrList allActions() const = 0;
+
+ /*! \return currently active dialog (window) od 0 if there is no active dialog. */
+ virtual KexiDialogBase* currentDialog() const = 0;
+
+ /*! \return true if this window is in the User Mode. */
+ virtual bool userMode() const = 0;
+
+ signals:
+ //! Emitted to make sure the project can be close.
+ //! Connect a slot here and set \a cancel to true to cancel the closing.
+ void acceptProjectClosingRequested(bool& cancel);
+
+ //! Emitted before closing the project (and destroying all it's data members).
+ //! You can do you cleanup of your structures here.
+ void beforeProjectClosing();
+
+ //! Emitted after closing the project.
+ void projectClosed();
+
+ public slots:
+ /*! Creates new object of type defined by \a info part info.
+ \a openingCancelled is set to true is opening has been cancelled.
+ \return true on success. */
+ virtual bool newObject( KexiPart::Info *info, bool& openingCancelled ) = 0;
+
+ //! Opens object pointed by \a item in a view \a viewMode
+ virtual KexiDialogBase * openObject(KexiPart::Item *item, int viewMode,
+ bool &openingCancelled, QMap<QString,QString>* staticObjectArgs = 0,
+ QString* errorMessage = 0) = 0;
+
+ //! For convenience
+ virtual KexiDialogBase * openObject(const QCString& mime, const QString& name,
+ int viewMode, bool &openingCancelled, QMap<QString,QString>* staticObjectArgs = 0) = 0;
+
+ /*! Closes the object for \a item.
+ \return true on success (closing can be dealyed though), false on failure and cancelled
+ if the object has "opening" job assigned. */
+ virtual tristate closeObject(KexiPart::Item* item) = 0;
+
+ /*! Called to accept property butter editing. */
+ virtual void acceptPropertySetEditing() = 0;
+
+ /*! Received information from active view that \a dlg has switched
+ its property set, so property editor contents should be reloaded.
+ If \a force is true, property editor's data is reloaded even
+ if the currently pointed property set is the same as before.
+ If \a preservePrevSelection is true and there was a property set
+ set before call, previously selected item will be preselected
+ in the editor (if found). */
+ virtual void propertySetSwitched(KexiDialogBase *dlg, bool force=false,
+ bool preservePrevSelection = true, const QCString& propertyToSelect = QCString()) = 0;
+
+ /*! Saves dialog's \a dlg data. If dialog's data is never saved,
+ user is asked for name and title, before saving (see getNewObjectInfo()).
+ \return true on successul saving or false on error.
+ If saving was cancelled by user, cancelled is returned.
+ \a messageWhenAskingForName is a i18n'ed text that will be visible
+ within name/caption dialog (see KexiNameDialog), which is popped
+ up for never saved objects. */
+ virtual tristate saveObject( KexiDialogBase *dlg,
+ const QString& messageWhenAskingForName = QString::null, bool dontAsk = false ) = 0;
+
+ /*! Closes dialog \a dlg. If dialog's data (see KexiDialoBase::dirty()) is unsaved,
+ used will be asked if saving should be perforemed.
+ \return true on successull closing or false on closing error.
+ If closing was cancelled by user, cancelled is returned. */
+ virtual tristate closeDialog(KexiDialogBase *dlg) = 0;
+
+ /*! Displays a dialog for entering object's name and title.
+ Used on new object saving.
+ \return true on successul closing or cancelled on cancel returned.
+ It's unlikely to have false returned here.
+ \a messageWhenAskingForName is a i18n'ed text that will be visible
+ within name/caption dialog (see KexiNameDialog).
+ If \a allowOverwriting is true, user will be asked for existing
+ object's overwriting, else it will be impossible to enter
+ a name of existing object.
+ You can check \a allowOverwriting after calling this method.
+ If it's true, user agreed on overwriting, if it's false, user picked
+ nonexisting name, so no overwrite will be needed. */
+ virtual tristate getNewObjectInfo( KexiPart::Item *partItem, KexiPart::Part *part,
+ bool& allowOverwriting, const QString& messageWhenAskingForName = QString::null ) = 0;
+
+ /*! Highlights object of mime \a mime and name \a name.
+ This can be done in the Project Navigator or so.
+ If a window for the object is opened (in any mode), it should be raised. */
+ virtual void highlightObject(const QCString& mime, const QCString& name) = 0;
+
+ //! Shows "print" dialog for \a item.
+ //! \return true on success.
+ virtual tristate printItem(KexiPart::Item* item) = 0;
+
+ //! Shows "print preview" dialog.
+ //! \return true on success.
+ virtual tristate printPreviewForItem(KexiPart::Item* item) = 0;
+
+ //! Shows "page setup" dialog for \a item.
+ //! \return true on success and cancelled when the action was cancelled.
+ virtual tristate showPageSetupForItem(KexiPart::Item* item) = 0;
+
+ /*! Executes custom action for the main window, usually provided by a plugin.
+ Also used by KexiFormEventAction. */
+ virtual tristate executeCustomActionForObject(KexiPart::Item* item, const QString& actionName) = 0;
+
+ protected slots:
+ virtual void slotObjectRenamed(const KexiPart::Item &item, const QCString& oldName) = 0;
+
+};
+
+
+#endif
+
diff --git a/kexi/core/kexipart.cpp b/kexi/core/kexipart.cpp
new file mode 100644
index 000000000..3f97e9c3d
--- /dev/null
+++ b/kexi/core/kexipart.cpp
@@ -0,0 +1,452 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexipart.h"
+#include "kexipartinfo.h"
+#include "kexipartitem.h"
+#include "kexistaticpart.h"
+#include "kexidialogbase.h"
+#include "kexiviewbase.h"
+
+#include "kexipartguiclient.h"
+#include "keximainwindow.h"
+//#include "kexipartdatasource.h"
+#include "kexi.h"
+
+#include <kexidb/connection.h>
+#include <kexiutils/identifier.h>
+#include <kexiutils/utils.h>
+
+#include <qwidgetstack.h>
+
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+
+namespace KexiPart {
+//! @internal
+class PartPrivate
+{
+public:
+ PartPrivate()
+ : instanceActionsInitialized(false)
+ {
+ }
+
+ //! Helper, used in Part::openInstance()
+ tristate askForOpeningInTextMode(KexiDialogBase *dlg, KexiPart::Item &item,
+ int supportedViewModes, int viewMode)
+ {
+ if (viewMode != Kexi::TextViewMode
+ && supportedViewModes & Kexi::TextViewMode
+ && dlg->tempData()->proposeOpeningInTextViewModeBecauseOfProblems)
+ {
+ //ask
+ KexiUtils::WaitCursorRemover remover;
+ //! @todo use message handler for this to enable non-gui apps
+ QString singleStatusString( dlg->singleStatusString() );
+ if (!singleStatusString.isEmpty())
+ singleStatusString.prepend(QString("\n\n")+i18n("Details:")+" ");
+ if (KMessageBox::No==KMessageBox::questionYesNo(0,
+ ((viewMode == Kexi::DesignViewMode)
+ ? i18n("Object \"%1\" could not be opened in Design View.").arg(item.name())
+ : i18n("Object could not be opened in Data View."))+"\n"
+ + i18n("Do you want to open it in Text View?") + singleStatusString, 0,
+ KStdGuiItem::open(), KStdGuiItem::cancel()))
+ {
+ // dlg->close(); //this will destroy dlg
+ return false;
+ }
+ return true;
+ }
+ return cancelled;
+ }
+
+ bool instanceActionsInitialized : 1;
+};
+}
+
+//----------------------------------------------------------------
+
+using namespace KexiPart;
+
+Part::Part(QObject *parent, const char *name, const QStringList &)
+: QObject(parent, name)
+, m_guiClient(0)
+, m_registeredPartID(-1) //no registered ID by default
+, d(new PartPrivate())
+{
+ m_info = 0;
+ m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode;
+ m_supportedUserViewModes = Kexi::DataViewMode;
+ m_mainWin = 0;
+ m_newObjectsAreDirty = false;
+}
+
+Part::Part(QObject* parent, StaticInfo *info)
+: QObject(parent, "StaticPart")
+, m_guiClient(0)
+, m_registeredPartID(-1) //no registered ID by default
+, d(new PartPrivate())
+{
+ m_info = info;
+ m_supportedViewModes = Kexi::DesignViewMode;
+ m_supportedUserViewModes = 0;
+ m_mainWin = 0;
+ m_newObjectsAreDirty = false;
+}
+
+Part::~Part()
+{
+ delete d;
+}
+
+void Part::createGUIClients(KexiMainWindow *win)
+{
+ m_mainWin = win;
+ if (!m_guiClient) {
+ //create part's gui client
+ m_guiClient = new GUIClient(m_mainWin, this, false, "part");
+
+ //default actions for part's gui client:
+ KAction *act = new KAction(m_names["instanceCaption"]+"...", info()->createItemIcon(), 0, this,
+ SLOT(slotCreate()), m_mainWin->actionCollection(),
+ KexiPart::nameForCreateAction(*info()));
+ act->plug( m_mainWin->findPopupMenu("insert") );
+// new KAction(m_names["instance"]+"...", info()->itemIcon(), 0, this,
+// SLOT(create()), m_guiClient->actionCollection(), (info()->objectName()+"part_create").latin1());
+ //let init specific actions for parts
+// initPartActions( m_guiClient->actionCollection() );
+ m_mainWin->guiFactory()->addClient(m_guiClient); //this client is added permanently
+
+ //create part instance's gui client
+// m_instanceGuiClient = new GUIClient(win, this, true);
+
+ //default actions for part instance's gui client:
+ //NONE
+ //let init specific actions for part instances
+ for (int mode = 1; mode <= 0x01000; mode <<= 1) {
+ if (m_supportedViewModes & mode) {
+ GUIClient *instanceGuiClient = new GUIClient(m_mainWin,
+ this, true, Kexi::nameForViewMode(mode).latin1());
+ m_instanceGuiClients.insert(mode, instanceGuiClient);
+// initInstanceActions( mode, instanceGuiClient->actionCollection() );
+ }
+ }
+ // also add an instance common for all modes (mode==0)
+ GUIClient *instanceGuiClient = new GUIClient(m_mainWin, this, true, "allViews");
+ m_instanceGuiClients.insert(Kexi::AllViewModes, instanceGuiClient);
+// initInstanceActions( Kexi::AllViewModes , instanceGuiClient->actionCollection() );
+
+//todo
+ initPartActions();
+// initActions();
+ }
+}
+
+KActionCollection* Part::actionCollectionForMode(int viewMode) const
+{
+ KXMLGUIClient *cli = m_instanceGuiClients[viewMode];
+ return cli ? cli->actionCollection() : 0;
+}
+
+KAction* Part::createSharedAction(int mode, const QString &text,
+ const QString &pix_name, const KShortcut &cut, const char *name,
+ const char *subclassName)
+{
+ GUIClient *instanceGuiClient = m_instanceGuiClients[mode];
+ if (!instanceGuiClient) {
+ kdDebug() << "KexiPart::createSharedAction(): no gui client for mode " << mode << "!" << endl;
+ return 0;
+ }
+ return m_mainWin->createSharedAction(text, pix_name, cut, name,
+ instanceGuiClient->actionCollection(), subclassName);
+}
+
+KAction* Part::createSharedPartAction(const QString &text,
+ const QString &pix_name, const KShortcut &cut, const char *name,
+ const char *subclassName)
+{
+ if (!m_guiClient)
+ return 0;
+ return m_mainWin->createSharedAction(text, pix_name, cut, name,
+ m_guiClient->actionCollection(), subclassName);
+}
+
+KAction* Part::createSharedToggleAction(int mode, const QString &text,
+ const QString &pix_name, const KShortcut &cut, const char *name)
+{
+ return createSharedAction(mode, text, pix_name, cut, name, "KToggleAction");
+}
+
+KAction* Part::createSharedPartToggleAction(const QString &text,
+ const QString &pix_name, const KShortcut &cut, const char *name)
+{
+ return createSharedPartAction(text, pix_name, cut, name, "KToggleAction");
+}
+
+/*KAction* Part::sharedAction(int mode, const char* name, const char *classname)
+{
+ GUIClient *instanceGuiClient = m_instanceGuiClients[mode];
+ if (!instanceGuiClient) {
+ kdDebug() << "KexiPart::createSharedAction(): no gui client for mode " << mode << "!" << endl;
+ return 0;
+ }
+ return instanceGuiClient->actionCollection()->action(name, classname);
+}
+
+KAction* Part::sharedPartAction(int mode, const char* name, const char *classname)
+{
+ if (!m_guiClient)
+ return 0;
+ return m_guiClient->actionCollection()->action(name, classname);
+}*/
+
+void Part::setActionAvailable(const char *action_name, bool avail)
+{
+ QIntDictIterator<GUIClient> it( m_instanceGuiClients );
+ for (;it.current();++it) {
+ KAction *act = it.current()->actionCollection()->action(action_name);
+ if (act) {
+ act->setEnabled(avail);
+ return;
+ }
+ }
+
+ m_mainWin->setActionAvailable(action_name, avail);
+}
+
+KexiDialogBase* Part::openInstance(KexiMainWindow *win, KexiPart::Item &item, int viewMode,
+ QMap<QString,QString>* staticObjectArgs)
+{
+ //now it's the time for creating instance actions
+ if (!d->instanceActionsInitialized) {
+ initInstanceActions();
+ d->instanceActionsInitialized = true;
+ }
+
+ m_status.clearStatus();
+// KexiDialogBase *dlg = createInstance(win,item,viewMode);
+// if (!dlg)
+// return 0;
+// QString capt = QString("%1 : %2").arg(item.name()).arg(instanceName());
+ KexiDialogBase *dlg = new KexiDialogBase(win);
+ dlg->m_supportedViewModes = m_supportedViewModes;
+// dlg->m_neverSaved = item.neverSaved();
+// dlg->m_currentViewMode = viewMode;
+ dlg->m_part = this;
+ dlg->m_item = &item;
+ dlg->updateCaption();
+
+ KexiDB::SchemaData sdata(m_info->projectPartID());
+ sdata.setName( item.name() );
+ sdata.setCaption( item.caption() );
+ sdata.setDescription( item.description() );
+
+/*! @todo js: apply settings for caption displaying method; there can be option for
+ - displaying item.caption() as caption, if not empty, without instanceName
+ - displaying the same as above in tabCaption (or not) */
+// dlg->setCaption( capt );
+// dlg->setTabCaption( item.name() );
+ dlg->setId(item.identifier()); //not needed, but we did it
+//moved down dlg->registerDialog();
+ dlg->setIcon( SmallIcon( dlg->itemIcon() ) );
+ if (dlg->mdiParent())
+ dlg->mdiParent()->setIcon( *dlg->icon() );
+// if (dlg->mainWidget())
+// dlg->mainWidget()->setIcon( *dlg->icon() );
+ dlg->stack()->setIcon( *dlg->icon() );
+ dlg->m_tempData = createTempData(dlg);
+
+ if (!item.neverSaved()) {
+ //we have to load schema data for this dialog
+ dlg->m_schemaData = loadSchemaData(dlg, sdata, viewMode);
+ if (!dlg->m_schemaData) {
+ //last chance:
+ if (false == d->askForOpeningInTextMode(dlg, item, dlg->m_supportedViewModes, viewMode)) {
+ delete dlg;
+ return 0;
+ }
+ viewMode = Kexi::TextViewMode;
+ dlg->m_schemaData = loadSchemaData(dlg, sdata, viewMode);
+ }
+ if (!dlg->m_schemaData) {
+ if (!m_status.error())
+ m_status = Kexi::ObjectStatus( dlg->mainWin()->project()->dbConnection(),
+ i18n("Could not load object's definition."), i18n("Object design may be corrupted."));
+ m_status.append(
+ Kexi::ObjectStatus(i18n("You can delete \"%1\" object and create it again.")
+ .arg(item.name()), QString::null) );
+
+ dlg->close();
+ delete dlg;
+ return 0;
+ }
+ }
+
+ bool switchingFailed = false;
+ bool dummy;
+ tristate res = dlg->switchToViewMode( viewMode, staticObjectArgs, dummy );
+ if (!res) {
+ tristate askForOpeningInTextModeRes
+ = d->askForOpeningInTextMode(dlg, item, dlg->m_supportedViewModes, viewMode);
+// if (viewMode==Kexi::DesignViewMode && dlg->isDesignModePreloadedForTextModeHackUsed(Kexi::TextViewMode))
+// askForOpeningInTextModeRes = cancelled; //do not try
+// else
+ if (true == askForOpeningInTextModeRes) {
+ delete dlg->m_schemaData; //old one
+ dlg->close();
+ delete dlg;
+ //try in text mode
+ return openInstance(win, item, Kexi::TextViewMode, staticObjectArgs);
+ }
+ else if (false == askForOpeningInTextModeRes) {
+ delete dlg->m_schemaData; //old one
+ dlg->close();
+ delete dlg;
+ return 0;
+ }
+ //dlg has an error info
+ switchingFailed = true;
+ }
+ if (~res)
+ switchingFailed = true;
+
+ if (switchingFailed) {
+ m_status = dlg->status();
+ dlg->close();
+ delete dlg;
+ return 0;
+ }
+ dlg->registerDialog(); //ok?
+ dlg->show();
+
+ if (dlg->mdiParent() && dlg->mdiParent()->state()==KMdiChildFrm::Normal) //only resize dialog if it is in normal state
+ dlg->resize(dlg->sizeHint());
+
+ dlg->setMinimumSize(dlg->minimumSizeHint().width(),dlg->minimumSizeHint().height());
+
+ //dirty only if it's a new object
+ if (dlg->selectedView())
+ dlg->selectedView()->setDirty( m_newObjectsAreDirty ? item.neverSaved() : false );
+
+ return dlg;
+}
+
+void Part::slotCreate()
+{
+ emit newObjectRequest( m_info );
+}
+
+KexiDB::SchemaData* Part::loadSchemaData(KexiDialogBase * /*dlg*/, const KexiDB::SchemaData& sdata,
+ int /*viewMode*/)
+{
+ KexiDB::SchemaData *new_schema = new KexiDB::SchemaData();
+ *new_schema = sdata;
+ return new_schema;
+}
+
+bool Part::loadDataBlock( KexiDialogBase *dlg, QString &dataString, const QString& dataID)
+{
+ if (!dlg->mainWin()->project()->dbConnection()->loadDataBlock( dlg->id(), dataString, dataID )) {
+ m_status = Kexi::ObjectStatus( dlg->mainWin()->project()->dbConnection(),
+ i18n("Could not load object's data."), i18n("Data identifier: \"%1\".").arg(dataID) );
+ m_status.append( *dlg );
+ return false;
+ }
+ return true;
+}
+
+void Part::initPartActions()
+{
+}
+
+void Part::initInstanceActions()
+{
+}
+
+bool Part::remove(KexiMainWindow *win, KexiPart::Item &item)
+{
+ if (!win || !win->project() || !win->project()->dbConnection())
+ return false;
+ KexiDB::Connection *conn = win->project()->dbConnection();
+ return conn->removeObject( item.identifier() );
+}
+
+KexiDialogTempData* Part::createTempData(KexiDialogBase* dialog)
+{
+ return new KexiDialogTempData(dialog);
+}
+
+QString Part::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const
+{
+ Q_UNUSED(dlg);
+ return QString(englishMessage).startsWith(":") ? QString::null : englishMessage;
+}
+
+void Part::setupCustomPropertyPanelTabs(KTabWidget *, KexiMainWindow*)
+{
+}
+
+QCString Part::instanceName() const
+{
+ // "instanceName" should be already valid identifier but we're using
+ // KexiUtils::string2Identifier() to be sure translators did it right.
+ return KexiUtils::string2Identifier(m_names["instanceName"]).lower().latin1();
+}
+
+QString Part::instanceCaption() const
+{
+ return m_names["instanceCaption"];
+}
+
+tristate Part::rename(KexiMainWindow *win, KexiPart::Item &item, const QString& newName)
+{
+ Q_UNUSED(win);
+ Q_UNUSED(item);
+ Q_UNUSED(newName);
+ return true;
+}
+
+//-------------------------------------------------------------------------
+
+
+GUIClient::GUIClient(KexiMainWindow *win, Part* part, bool partInstanceClient, const char* nameSuffix)
+ : QObject(part,
+ (part->info()->objectName()
+ + (nameSuffix ? QString(":%1").arg(nameSuffix) : QString())).latin1() )
+ , KXMLGUIClient(win)
+{
+ if(!win->project()->data()->userMode())
+ setXMLFile(QString::fromLatin1("kexi")+part->info()->objectName()
+ +"part"+(partInstanceClient?"inst":"")+"ui.rc");
+
+// new KAction(part->m_names["new"], part->info()->itemIcon(), 0, this,
+// SLOT(create()), actionCollection(), (part->info()->objectName()+"part_create").latin1());
+
+// new KAction(i18nInstanceName+"...", part->info()->itemIcon(), 0, this,
+// SLOT(create()), actionCollection(), (part->info()->objectName()+"part_create").latin1());
+
+// win->guiFactory()->addClient(this);
+}
+
+
+#include "kexipart.moc"
+
diff --git a/kexi/core/kexipart.h b/kexi/core/kexipart.h
new file mode 100644
index 000000000..4045c4c6c
--- /dev/null
+++ b/kexi/core/kexipart.h
@@ -0,0 +1,333 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPART_H
+#define KEXIPART_H
+
+#include <qobject.h>
+#include <qmap.h>
+
+#include <kexiutils/tristate.h>
+#include "kexi.h"
+#include "keximainwindow.h"
+
+class KActionCollection;
+class KexiDialogBase;
+class KexiDialogTempData;
+class KexiViewBase;
+class KexiMainWindowImpl;
+class KAction;
+class KShortcut;
+class KTabWidget;
+
+namespace KexiPart
+{
+ class Info;
+ class Item;
+ class GUIClient;
+ class PartPrivate;
+ class StaticInfo;
+
+/*! Official (registered) type IDs for objects like table, query, form... */
+enum ObjectTypes {
+ TableObjectType = KexiDB::TableObjectType, //!< 1, like in KexiDB::ObjectTypes
+ QueryObjectType = KexiDB::QueryObjectType, //!< 2, like in KexiDB::ObjectTypes
+ FormObjectType = 3,
+ ReportObjectType = 4,
+ ScriptObjectType = 5,
+ WebObjectType = 6,
+ MacroObjectType = 7,
+ LastObjectType = 7, //ALWAYS UPDATE THIS
+
+ UserObjectType = 100 //!< external types
+};
+
+/**
+ * The main class for kexi frontend parts like tables, queries, forms and reports
+ */
+class KEXICORE_EXPORT Part : public QObject
+{
+ Q_OBJECT
+
+ public:
+ /*! Constructor. */
+ Part(QObject *parent, const char *name, const QStringList &);
+ /*! Destructor. */
+ virtual ~Part();
+
+//! @todo make it protected, outside world should use KexiProject
+ /*! Try to execute the part. Implementations of this \a Part
+ are able to overwrite this method to offer execution.
+ \param item The \a KexiPart::Item that should be executed.
+ \param sender The sender QObject which likes to execute this \a Part or
+ NULL if there is no sender. The KFormDesigner uses this to pass
+ the actual widget (e.g. the button that was pressed).
+ \return true if execution was successfully else false.
+ */
+ virtual bool execute(KexiPart::Item* item, QObject* sender = 0) {
+ Q_UNUSED(item);
+ Q_UNUSED(sender);
+ return false;
+ }
+
+ /*! \return supported modes for dialogs created by this part, i.e. a combination
+ of Kexi::ViewMode enum elements.
+ Set this member in your KexiPart subclass' ctor, if you need to override the default value
+ that equals Kexi::DataViewMode | Kexi::DesignViewMode,
+ or Kexi::DesignViewMode in case of Kexi::PartStaticPart object.
+ This information is used to set supported view modes for every
+ KexiDialogBase-derived object created by this KexiPart. */
+ inline int supportedViewModes() const { return m_supportedViewModes; }
+
+ /*! \return supported modes for dialogs created by this part in "user mode", i.e. a combination
+ of Kexi::ViewMode enum elements.
+ Set this member in your KexiPart subclass' ctor, if you need to override the default value
+ that equals Kexi::DataViewMode. or 0 in case of Kexi::PartStaticPart object.
+ This information is used to set supported view modes for every
+ KexiDialogBase-derived object created by this KexiPart. */
+ inline int supportedUserViewModes() const { return m_supportedUserViewModes; }
+
+//! @todo make it protected, outside world should use KexiProject
+ /*! "Opens" an instance that the part provides, pointed by \a item in a mode \a viewMode.
+ \a viewMode is one of Kexi::ViewMode enum.
+ \a staticObjectArgs can be passed for static Kexi Parts. */
+ KexiDialogBase* openInstance(KexiMainWindow *win, KexiPart::Item &item,
+ int viewMode = Kexi::DataViewMode, QMap<QString,QString>* staticObjectArgs = 0);
+
+//! @todo make it protected, outside world should use KexiProject
+ /*! Removes any stored data pointed by \a item (example: table is dropped for table part).
+ From now this data is inaccesible, and \a item disappear.
+ You do not need to remove \a item, or remove object's schema stored in the database,
+ beacuse this will be done automatically by KexiProject after successful
+ call of this method. All object's data blocks are also automatically removed from database
+ (from "kexi__objectdata" table).
+ For this, a database connection associated with kexi project owned by \a win can be used.
+
+ Database transaction is started by KexiProject before calling this method,
+ and it will be rolled back if you return false here.
+ You shouldn't use by hand transactions here.
+
+ Default implementation just removes object from kexi__* system structures
+ at the database backend using KexiDB::Connection::removeObject(). */
+ virtual bool remove(KexiMainWindow *win, KexiPart::Item & item);
+
+ /*! Renames stored data pointed by \a item to \a newName
+ (example: table name is altered in the database).
+ For this, a database connection associated with kexi project owned by \a win can be used.
+ You do not need to change \a item, and change object's schema stored in the database,
+ beacuse this is automatically handled by KexiProject.
+
+ Database transaction is started by KexiProject before calling this method,
+ and it will be rolled back if you return false here.
+ You shouldn't use by hand transactions here.
+
+ Default implementation does nothing and returns true. */
+ virtual tristate rename(KexiMainWindow *win, KexiPart::Item &item, const QString& newName);
+
+ /*! Creates and returns a new temporary data for a dialog \a dialog.
+ This method is called on openInstance() once per dialog.
+ Reimplement this to return KexiDialogTempData subclass instance.
+ Default implemention just returns empty KexiDialogTempData object. */
+ virtual KexiDialogTempData* createTempData(KexiDialogBase* dialog);
+
+ /*! Creates a new view for mode \a viewMode, \a item and \a parent. The view will be
+ used inside \a dialog. */
+ virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode = Kexi::DataViewMode, QMap<QString,QString>* staticObjectArgs = 0) = 0;
+
+ /*! i18n'd instance name usable for displaying in gui as object's name.
+ The name is valid identifier - contains latin1 lowercase characters only.
+ @todo move this to Info class when the name could be moved as localized property
+ to service's .desktop file. */
+ QCString instanceName() const;
+
+ /*! i18n'd instance name usable for displaying in gui as object's caption.
+ @todo move this to Info class when the name could be moved as localized property
+ to service's .desktop file. */
+ QString instanceCaption() const;
+
+ inline Info *info() const { return m_info; }
+
+ /*! \return part's GUI Client, so you can
+ create part-wide actions using this client. */
+ inline GUIClient *guiClient() const { return m_guiClient; }
+
+ /*! \return part's GUI Client, so you can
+ create instance-wide actions using this client. */
+ inline GUIClient *instanceGuiClient(int mode = 0) const
+ { return m_instanceGuiClients[mode]; }
+
+#if 0
+ /**
+ * @returns the datasource object of this part
+ * reeimplement it to make a part work as dataprovider ;)
+ */
+ virtual DataSource *dataSource() { return 0; }
+#endif
+
+ /*! \return action collection for mode \a viewMode. */
+ KActionCollection* actionCollectionForMode(int viewMode) const;
+
+ const Kexi::ObjectStatus& lastOperationStatus() const { return m_status; }
+
+ /*! \return i18n'd message translated from \a englishMessage.
+ This method is useful for messages like:
+ "<p>Table \"%1\" has been modified.</p>",
+ -- such messages can be accurately translated,
+ while this could not: "<p>%1 \"%2\" has been modified.</p>".
+ See implementation of this method in KexiTablePart to see
+ what strings are needed for translation.
+
+ Default implementation returns generic \a englishMessage.
+ In special cases, \a englishMessage can start with ":",
+ to indicate that empty string will be generated if
+ a part does not offer a message for such \a englishMessage.
+ This is used e.g. in KexiMainWindowImpl::closeDialog().
+ */
+ virtual QString i18nMessage(const QCString& englishMessage,
+ KexiDialogBase* dlg) const;
+
+ signals:
+ void newObjectRequest( KexiPart::Info *info );
+
+ protected slots:
+ void slotCreate();
+
+ protected:
+ //! Used by StaticPart
+ Part(QObject* parent, StaticInfo *info);
+
+// virtual KexiDialogBase* createInstance(KexiMainWindow *win, const KexiPart::Item &item, int viewMode = Kexi::DataViewMode) = 0;
+
+ //! Creates GUICLients for this part, attached to \a win
+ //! This method is called from KexiMainWindow
+ void createGUIClients(KexiMainWindow *win);
+
+#if 0
+ /*! For reimplementation. Create here all part actions (KAction or similar).
+ "Part action" is an action that is bound to given part, not for dialogs
+ created with this part, eg. "Open external project" action for Form part.
+ Default implementation does nothing.
+ */
+ virtual void initPartActions( KActionCollection * ) {};
+
+ /*! For reimplementation. You should here create all instance actions (KAction or similar)
+ for \a mode (this method called for every value given by Kexi::ViewMode enum,
+ and in special cases, in the future - for user-defined part-specific modes).
+ Actions should be bound to action collection \a col.
+ "Instance action" is an action that is bound to given dialog instance (created with a part),
+ for specific view. \a mo; eg. "Filter data" action for DataViewMode of Table part.
+ By creating actions here, you can ensure that after switching to other view mode (eg. from
+ Design view to Data view), appropriate actions will be switched/hidden.
+ \a mode equal Kexi::AllViewModes means that given actions will be available for
+ all supported views.
+ Default implementation does nothing.
+ */
+ virtual void initInstanceActions( int mode, KActionCollection *col ) {};
+#endif
+
+ virtual void initPartActions();
+ virtual void initInstanceActions();
+
+ virtual KexiDB::SchemaData* loadSchemaData(KexiDialogBase *dlg,
+ const KexiDB::SchemaData& sdata, int viewMode);
+
+ bool loadDataBlock( KexiDialogBase *dlg, QString &dataString, const QString& dataID = QString::null);
+
+ /*! Creates shared action for action collection declared
+ for 'instance actions' of this part.
+ See KexiSharedActionHost::createSharedAction() for details.
+ Pass desired KAction subclass with \a subclassName (e.g. "KToggleAction") to have
+ that subclass allocated instead just KAction (what is the default). */
+ KAction* createSharedAction(int mode, const QString &text,
+ const QString &pix_name, const KShortcut &cut, const char *name,
+ const char *subclassName = 0);
+
+ /*! Convenience version of above method - creates shared toggle action. */
+ KAction* createSharedToggleAction(int mode, const QString &text,
+ const QString &pix_name, const KShortcut &cut, const char *name);
+
+ /*! Creates shared action for action collection declared
+ for 'part actions' of this part.
+ See KexiSharedActionHost::createSharedAction() for details.
+ Pass desired KAction subclass with \a subclassName (e.g. "KToggleAction") to have
+ that subclass allocated instead just KAction (what is the default). */
+ KAction* createSharedPartAction(const QString &text,
+ const QString &pix_name, const KShortcut &cut, const char *name,
+ const char *subclassName = 0);
+
+ /*! Convenience version of above method - creates shared toggle action
+ for 'part actions' of this part. */
+ KAction* createSharedPartToggleAction(const QString &text,
+ const QString &pix_name, const KShortcut &cut, const char *name);
+
+ void setActionAvailable(const char *action_name, bool avail);
+
+ inline void setInfo(Info *info) { m_info = info; }
+
+ /*! This method can be reimplemented to setup additional tabs
+ in the property editor panel. Default implementation does nothing.
+ This method is called whenever current dialog (KexiDialogBase) is switched and
+ type (mime type) of its contents differs from previous one.
+ For example, if a user switched from Table Designer to Form Designer,
+ additional tab containing Form Designer's object tree should be shown. */
+ virtual void setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin);
+
+ //! Set of i18n'd action names for, initialised on KexiPart::Part subclass ctor
+ //! The names are useful because the same action can have other name for each part
+ //! E.g. "New table" vs "New query" can have different forms for some languages...
+ QMap<QString,QString> m_names;
+
+ /*! Supported modes for dialogs created by this part.
+ @see supportedViewModes() */
+ int m_supportedViewModes;
+
+ /*! Supported modes for dialogs created by this part in "user mode".
+ The default is Kexi::DataViewMode. It is altered in classes like KexiSimplePrintingPart.
+ @see supportedUserViewModes() */
+ int m_supportedUserViewModes;
+
+ Info *m_info;
+ GUIClient *m_guiClient;
+ QIntDict<GUIClient> m_instanceGuiClients;
+ KexiMainWindow* m_mainWin;
+ Kexi::ObjectStatus m_status;
+
+ /*! If you're implementing a new part, set this to value >0 in your ctor
+ if you have well known (ie registered ID) for your part.
+ So far, table, query, form, report and script part have defined their IDs
+ (see KexiPart::ObjectTypes). */
+ int m_registeredPartID;
+
+ /*! True if newwly created, unsaved objects are dirty. False by default.
+ You can change it in your subclass' constructor. */
+ bool m_newObjectsAreDirty : 1;
+
+ PartPrivate *d;
+
+ friend class Manager;
+ friend class ::KexiMainWindow;
+ friend class ::KexiMainWindowImpl;
+ friend class GUIClient;
+};
+
+}
+
+#endif
diff --git a/kexi/core/kexipartdatasource.cpp b/kexi/core/kexipartdatasource.cpp
new file mode 100644
index 000000000..f32685942
--- /dev/null
+++ b/kexi/core/kexipartdatasource.cpp
@@ -0,0 +1,49 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexipartdatasource.h"
+#include "kexipart.h"
+
+namespace KexiPart {
+
+class DataSourcePrivate
+{
+ public:
+ DataSourcePrivate() {}
+ ~DataSourcePrivate() {}
+ Part *part;
+};
+
+}
+
+using namespace KexiPart;
+
+DataSource::DataSource(Part *part)
+ : d(new DataSourcePrivate())
+{
+ d->part = part;
+}
+
+DataSource::~DataSource()
+{
+ delete d;
+}
+
+Part* DataSource::part() const { return d->part; }
+
diff --git a/kexi/core/kexipartdatasource.h b/kexi/core/kexipartdatasource.h
new file mode 100644
index 000000000..e7ce9a96d
--- /dev/null
+++ b/kexi/core/kexipartdatasource.h
@@ -0,0 +1,72 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPARTDATASOURCE_H
+#define KEXIPARTDATASOURCE_H
+
+class KexiProject;
+namespace KexiDB
+{
+ class FieldList;
+ class Cursor;
+}
+
+namespace KexiPart
+{
+ class DataSourcePrivate;
+ class Item;
+ class Part;
+
+/**
+ * this class provides a datasource framework for e.g. tables and queries
+ * using this framework one can query for
+ * - a list of datasources
+ * - the fileds in datasources
+ * - variables (e.g. query variables)
+ */
+class KEXICORE_EXPORT DataSource
+{
+ public:
+ DataSource(Part *part);
+ virtual ~DataSource();
+
+ /**
+ * @returns a list of fileds for the datasource
+ * @arg id is the document id for the source
+ */
+ virtual KexiDB::FieldList *fields(KexiProject *project, const KexiPart::Item &i)=0;
+
+ /**
+ * @returns the cursor
+ */
+ virtual KexiDB::Cursor *cursor(KexiProject *project, const KexiPart::Item &i, bool buffer)=0;
+
+ /**
+ * @returns the part providing this datasource
+ */
+ Part *part() const;
+
+ private:
+ DataSourcePrivate *d;
+};
+
+}
+
+#endif
+
diff --git a/kexi/core/kexipartguiclient.h b/kexi/core/kexipartguiclient.h
new file mode 100644
index 000000000..bb0d16eab
--- /dev/null
+++ b/kexi/core/kexipartguiclient.h
@@ -0,0 +1,56 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPARTGUICL_H
+#define KEXIPARTGUICL_H
+
+#include "kexipart.h"
+
+#include <qobject.h>
+
+#include <kxmlguiclient.h>
+
+class KexiMainWindow;
+class KexiDialogBase;
+
+namespace KexiPart
+{
+
+/** @internal A GUI Client used by KexiPart::Part objects within KexiMainWindow
+*/
+class GUIClient : public QObject, public KXMLGUIClient
+{
+ public:
+ virtual ~GUIClient() {};
+
+ inline Part *part() { return static_cast<Part*>(QObject::parent()); }
+
+ protected:
+ /*! Creates a new GUI Client. If \a partInstanceClient is true, the part will be
+ used as "instance" client, otherwise it will be defined per-view.
+ \a nameSuffix is used in constructing client's name (only useful for debugging purposes). */
+ GUIClient(KexiMainWindow *win, Part* part, bool partInstanceClient, const char* nameSuffix);
+
+ friend class Part;
+};
+
+}
+
+#endif
+
diff --git a/kexi/core/kexipartinfo.cpp b/kexi/core/kexipartinfo.cpp
new file mode 100644
index 000000000..22f7cc554
--- /dev/null
+++ b/kexi/core/kexipartinfo.cpp
@@ -0,0 +1,133 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexipartinfo_p.h"
+
+#include <kexidb/global.h>
+
+using namespace KexiPart;
+
+Info::Private::Private(const KService::Ptr& aPtr)
+ : ptr(aPtr)
+ , groupName(aPtr->name())
+ , mimeType(aPtr->property("X-Kexi-TypeMime").toCString())
+ , itemIcon(aPtr->property("X-Kexi-ItemIcon").toString())
+ , objectName(aPtr->property("X-Kexi-TypeName").toString())
+ , broken(false)
+ , idStoredInPartDatabase(false)
+{
+ QVariant val = ptr->property("X-Kexi-NoObject");
+ isVisibleInNavigator = val.isValid() ? (val.toInt() != 1) : true;
+
+//! @todo (js)..... now it's hardcoded!
+ if(objectName == "table")
+ projectPartID = KexiDB::TableObjectType;
+ else if(objectName == "query")
+ projectPartID = KexiDB::QueryObjectType;
+// else if(objectName == "html")
+// m_projectPartID = KexiDB::WebObjectType;
+ else
+ projectPartID = -1; //TODO!!
+}
+
+Info::Private::Private()
+ : projectPartID(-1) //OK?
+ , broken(false)
+ , isVisibleInNavigator(false)
+ , idStoredInPartDatabase(false)
+{
+}
+
+//------------------------------
+
+Info::Info(KService::Ptr ptr)
+ : d(new Private(ptr))
+{
+}
+
+Info::Info()
+ : d(new Private())
+{
+}
+
+Info::~Info()
+{
+ delete d;
+}
+
+QString Info::groupName() const { return d->groupName; }
+
+QCString Info::mimeType() const { return d->mimeType; }
+
+QString Info::itemIcon() const { return d->itemIcon; }
+
+QString Info::createItemIcon() const { return d->itemIcon+"_newobj"; }
+
+QString Info::objectName() const { return d->objectName; }
+
+KService::Ptr Info::ptr() const { return d->ptr; }
+
+bool Info::isBroken() const { return d->broken; }
+
+bool Info::isVisibleInNavigator() const { return d->isVisibleInNavigator; }
+
+int Info::projectPartID() const { return d->projectPartID; }
+
+void Info::setProjectPartID(int id) { d->projectPartID=id; }
+
+void Info::setBroken(bool broken, const QString& errorMessage)
+{ d->broken = broken; d->errorMessage = errorMessage; }
+
+QString Info::errorMessage() const { return d->errorMessage; }
+
+void Info::setIdStoredInPartDatabase(bool set)
+{
+ d->idStoredInPartDatabase = set;
+}
+
+bool Info::isIdStoredInPartDatabase() const
+{
+ return d->idStoredInPartDatabase;
+}
+
+bool Info::isDataExportSupported() const
+{
+ QVariant val = d->ptr ? d->ptr->property("X-Kexi-SupportsDataExport") : QVariant();
+ return val.isValid() ? val.toBool() : false;
+}
+
+bool Info::isPrintingSupported() const
+{
+ QVariant val = d->ptr ? d->ptr->property("X-Kexi-SupportsPrinting") : QVariant();
+ return val.isValid() ? val.toBool() : false;
+}
+
+bool Info::isExecuteSupported() const
+{
+ QVariant val = d->ptr ? d->ptr->property("X-Kexi-SupportsExecution") : QVariant();
+ return val.isValid() ? val.toBool() : false;
+}
+
+//--------------
+
+QCString KexiPart::nameForCreateAction(const Info& info)
+{
+ return (info.objectName()+"part_create").latin1();
+}
diff --git a/kexi/core/kexipartinfo.h b/kexi/core/kexipartinfo.h
new file mode 100644
index 000000000..43cf5b871
--- /dev/null
+++ b/kexi/core/kexipartinfo.h
@@ -0,0 +1,160 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003,2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPARTINFO_H
+#define KEXIPARTINFO_H
+
+#include "kexipartmanager.h"
+
+class KexiMainWindowImpl;
+class KexiProject;
+class KexiDialogBase;
+
+namespace KexiPart
+{
+
+ class Manager;
+ class Item;
+ class Part;
+
+/**
+ * @short Information about a Kexi Part (plugin).
+ */
+class KEXICORE_EXPORT Info
+{
+ public:
+ Info(KService::Ptr service);
+ ~Info();
+
+ /**
+ * @return a i18n'ed group name e.g. "Tables"
+ */
+ QString groupName() const;
+
+ /**
+ * @return the internal mime type of this part
+ */
+ QCString mimeType() const;
+
+// /**
+// * @return the icon for groups
+// */
+// inline QString groupIcon() const { return m_groupIcon; }
+
+ /**
+ * @return the icon for a item
+ */
+ QString itemIcon() const;
+
+ /**
+ * @return the icon for a item
+ */
+ QString createItemIcon() const;
+
+ /**
+ * @return the object name associated with this part (e.g. "table")
+ */
+ QString objectName() const;
+
+ /**
+ * @return the project-part-id
+ */
+ int projectPartID() const;
+
+ /**
+ * @return the KService::Ptr associated with this part
+ */
+ KService::Ptr ptr() const;
+
+ /**
+ * @return true if loading was tried but failed
+ */
+ bool isBroken() const;
+
+ /**
+ * \return true if the part should be visible in the Project Navigator (as a folder).
+ */
+ bool isVisibleInNavigator() const;
+
+ /**
+ * \return true if the part supports data exporting.
+ */
+ bool isDataExportSupported() const;
+
+ /**
+ * \return true if the part supports data printing.
+ */
+ bool isPrintingSupported() const;
+
+ /**
+ * \return true if the part supports execution. This is as
+ * example the case for the Macro and the Scripting plugins.
+ */
+ bool isExecuteSupported() const;
+
+ protected:
+ /**
+ * Used in StaticInfo
+ */
+ Info();
+
+ friend class Manager;
+ friend class ::KexiProject;
+ friend class ::KexiMainWindowImpl;
+ friend class ::KexiDialogBase;
+
+ /**
+ * Sets the project-part-id.
+ */
+ void setProjectPartID(int id);
+
+ /**
+ * Sets the broken flag and error message.
+ * Most likely to be called by @ref KexiPart::Manager
+ */
+ void setBroken(bool broken, const QString& errorMessage);
+
+ /**
+ * \return i18n'd error message set by setBroken().
+ */
+ QString errorMessage() const;
+
+ void setIdStoredInPartDatabase(bool set);
+
+ /**
+ * \return true if ID of the part is stored in project's database
+ * false by default. This flag is updated in Manager::checkProject()
+ * and set to true on first successful execution of KexiDialogBase::storeNewData()
+ * @internal
+ */
+ bool isIdStoredInPartDatabase() const;
+
+ class Private;
+ Private *d;
+};
+
+/*! \return "create" KAction's name for part defined by \a info.
+ The result is like "tablepart_create". Used in Part::createGUIClients()
+ and KexiBrowser. */
+KEXICORE_EXPORT QCString nameForCreateAction(const Info& info);
+
+}
+
+#endif
diff --git a/kexi/core/kexipartinfo_p.h b/kexi/core/kexipartinfo_p.h
new file mode 100644
index 000000000..5904ff978
--- /dev/null
+++ b/kexi/core/kexipartinfo_p.h
@@ -0,0 +1,51 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPROJECTPARTITEM_P_H
+#define KEXIPROJECTPARTITEM_P_H
+
+#include "kexipartinfo.h"
+#include <kservice.h>
+
+namespace KexiPart
+{
+//! @internal
+class Info::Private
+{
+ public:
+ Private(const KService::Ptr& aPtr);
+
+ //! used in StaticItem class
+ Private();
+
+ KService::Ptr ptr;
+ QString errorMessage;
+ QString groupName;
+ QCString mimeType;
+ QString itemIcon;
+ QString objectName;
+ int projectPartID;
+ bool broken : 1;
+ bool isVisibleInNavigator : 1;
+ bool idStoredInPartDatabase : 1;
+};
+}
+
+#endif
diff --git a/kexi/core/kexipartitem.cpp b/kexi/core/kexipartitem.cpp
new file mode 100644
index 000000000..4ef777836
--- /dev/null
+++ b/kexi/core/kexipartitem.cpp
@@ -0,0 +1,33 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexipartitem.h"
+
+using namespace KexiPart;
+
+Item::Item()
+ : m_id(0) //- null
+ , m_neverSaved(false)
+{
+}
+
+Item::~Item()
+{
+}
+
diff --git a/kexi/core/kexipartitem.h b/kexi/core/kexipartitem.h
new file mode 100644
index 000000000..46eebf47f
--- /dev/null
+++ b/kexi/core/kexipartitem.h
@@ -0,0 +1,117 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPROJECTPARTITEM_H
+#define KEXIPROJECTPARTITEM_H
+
+#include <qobject.h>
+#include <qintdict.h>
+#include <qptrlist.h>
+
+namespace KexiDB
+{
+ class Connection;
+}
+
+namespace KexiPart
+{
+
+class Info;
+
+/*!
+ @short Information about a single object that can be instantiated using Kexi Part
+
+ KexiPart::Item stores:
+ - identifier ident (low-level name, for example: table name)
+ - mime type name, eg. "kexi/table"
+ - caption (visible, i18n'd hight level name, eg. table or query title)
+*/
+class KEXICORE_EXPORT Item
+{
+ public:
+
+ Item();
+ ~Item();
+
+ int identifier() const { return m_id; }
+ void setIdentifier(int id) { m_id = id; }
+
+ QCString mimeType() const { return m_mime; }
+ void setMimeType(const QCString &mime) { m_mime = mime; }
+
+ QString name() const { return m_name; }
+ void setName(const QString &name) { m_name = name; }
+
+ QString caption() const { return m_caption; }
+ void setCaption(const QString &c) { m_caption = c; }
+
+ QString description() const { return m_desc; }
+ void setDescription(const QString &d) { m_desc = d; }
+
+ /*! \return "neverSaved" flag for this item what mean
+ that is used when new item is created in-memory-only,
+ so we need to indicate for KexiProject about that state.
+ By default this flag is false.
+ Used by KexiMainWindowImpl::newObject(). */
+ bool neverSaved() const { return m_neverSaved; }
+
+ /*! \sa neverSaved().
+ Used by KexiMainWindowImpl::newObject(). */
+ void setNeverSaved(bool set) { m_neverSaved = set; }
+
+ bool isNull() const { return m_id==0; }
+
+ //! \return caption if not empty, else returns name.
+ inline QString captionOrName() const { return m_caption.isEmpty() ? m_name : m_caption; }
+
+ private:
+ QCString m_mime;
+ QString m_name;
+ QString m_caption;
+ QString m_desc;
+ int m_id;
+ bool m_neverSaved : 1;
+};
+
+typedef QIntDict<KexiPart::Item> ItemDict;
+typedef QIntDictIterator<KexiPart::Item> ItemDictIterator;
+typedef QPtrListIterator<KexiPart::Item> ItemListIterator;
+
+/*!
+ @short Part item list with reimplemented compareItems() method.
+
+ Such a list is returend by KexiProject::getSortedItems(KexiPart::ItemList& list, KexiPart::Info *i);
+ so you can call sort() on the list to sort it by item name.
+*/
+class KEXICORE_EXPORT ItemList : public QPtrList<KexiPart::Item> {
+ public:
+ ItemList() {}
+ protected:
+ virtual int compareItems( QPtrCollection::Item item1, QPtrCollection::Item item2 ) {
+ return QString::compare(
+ static_cast<KexiPart::Item*>(item1)->name(),
+ static_cast<KexiPart::Item*>(item2)->name());
+ }
+};
+
+}
+
+#endif
+
diff --git a/kexi/core/kexipartmanager.cpp b/kexi/core/kexipartmanager.cpp
new file mode 100644
index 000000000..8525c2d78
--- /dev/null
+++ b/kexi/core/kexipartmanager.cpp
@@ -0,0 +1,280 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <klibloader.h>
+#include <ktrader.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kparts/componentfactory.h>
+
+#include "kexipartmanager.h"
+#include "kexipart.h"
+#include "kexipartinfo.h"
+#include "kexistaticpart.h"
+#include "kexi_version.h"
+
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+
+using namespace KexiPart;
+
+Manager::Manager(QObject *parent)
+ : QObject(parent)
+{
+ m_lookupDone = false;
+ m_partlist.setAutoDelete(true);
+ m_partsByMime.setAutoDelete(false);
+ m_parts.setAutoDelete(false);//KApp will remove parts
+ m_nextTempProjectPartID = -1;
+}
+
+void
+Manager::lookup()
+{
+//js: TODO: allow refreshing!!!! (will need calling removeClient() by Part objects)
+ if (m_lookupDone)
+ return;
+ m_lookupDone = true;
+ m_partlist.clear();
+ m_partsByMime.clear();
+ m_parts.clear();
+ KTrader::OfferList tlist = KTrader::self()->query("Kexi/Handler",
+ "[X-Kexi-PartVersion] == " + QString::number(KEXI_PART_VERSION));
+
+ KConfig conf("kexirc", true);
+ conf.setGroup("Parts");
+ QStringList sl_order = QStringList::split( ",", conf.readEntry("Order") );//we'll set parts in defined order
+ const int size = QMAX( tlist.count(), sl_order.count() );
+ QPtrVector<KService> ordered( size*2 );
+ int offset = size; //we will insert not described parts from #offset
+
+ //compute order
+ for(KTrader::OfferList::ConstIterator it(tlist.constBegin()); it != tlist.constEnd(); ++it)
+ {
+ KService::Ptr ptr = (*it);
+ QCString mime = ptr->property("X-Kexi-TypeMime").toCString();
+ kdDebug() << "Manager::lookup(): " << mime << endl;
+//<TEMP>: disable some parts if needed
+ if (!Kexi::tempShowForms() && mime=="kexi/form")
+ continue;
+ if (!Kexi::tempShowReports() && mime=="kexi/report")
+ continue;
+ if (!Kexi::tempShowMacros() && mime=="kexi/macro")
+ continue;
+ if (!Kexi::tempShowScripts() && mime=="kexi/script")
+ continue;
+//</TEMP>
+ int idx = sl_order.findIndex( ptr->library() );
+ if (idx!=-1)
+ ordered.insert(idx, ptr);
+ else //add to end
+ ordered.insert(offset++, ptr);
+ }
+ //fill final list using computed order
+ for (int i = 0; i< (int)ordered.size(); i++) {
+ KService::Ptr ptr = ordered[i];
+ if (ptr) {
+ Info *info = new Info(ptr);
+ info->setProjectPartID(m_nextTempProjectPartID--); // temp. part id are -1, -2, and so on,
+ // to avoid duplicates
+ if (!info->mimeType().isEmpty()) {
+ m_partsByMime.insert(info->mimeType(), info);
+ kdDebug() << "Manager::lookup(): inserting info to " << info->mimeType() << endl;
+ }
+ m_partlist.append(info);
+ }
+ }
+}
+
+Manager::~Manager()
+{
+}
+
+Part *
+Manager::part(Info *i)
+{
+ clearError();
+ if(!i)
+ return 0;
+
+// kdDebug() << "Manager::part( id = " << i->projectPartID() << " )" << endl;
+
+ if (i->isBroken()) {
+ setError(i->errorMessage());
+ return 0;
+ }
+
+ Part *p = m_parts[i->projectPartID()];
+
+ if(!p) {
+// kdDebug() << "Manager::part().." << endl;
+ int error=0;
+ p = KParts::ComponentFactory::createInstanceFromService<Part>(i->ptr(), this,
+ QString(i->objectName()+"_part").latin1(), QStringList(), &error);
+ if(!p) {
+ kdDebug() << "Manager::part(): failed :( (ERROR #" << error << ")" << endl;
+ kdDebug() << " " << KLibLoader::self()->lastErrorMessage() << endl;
+ i->setBroken(true, i18n("Error while loading plugin \"%1\"").arg(i->objectName()));
+ setError(i->errorMessage());
+ return 0;
+ }
+ if (p->m_registeredPartID>0) {
+ i->setProjectPartID( p->m_registeredPartID );
+ }
+
+ p->setInfo(i);
+ m_parts.insert(i->projectPartID(),p);
+ emit partLoaded(p);
+ }
+ else {
+// kdDebug() << "Manager::part(): cached: " << i->groupName() << endl;
+ }
+
+// kdDebug() << "Manager::part(): fine!" << endl;
+ return p;
+}
+
+#if 0
+void
+Manager::unloadPart(Info *i)
+{
+ m_parts.setAutoDelete(true);
+ m_parts.remove(i->projectPartID());
+ m_parts.setAutoDelete(false);
+/* if (!p)
+ return;
+ m_partsByMime.take(i->mime());
+ m_partlist.removeRef(p);*/
+}
+
+void
+Manager::unloadAllParts()
+{
+// m_partsByMime.clear();
+ m_parts.setAutoDelete(true);
+ m_parts.clear();
+ m_parts.setAutoDelete(false);
+// m_partlist.clear();
+}
+#endif
+
+/*void
+Manager::removeClients( KexiMainWindow *win )
+{
+ if (!win)
+ return;
+ QIntDictIterator<Part> it(m_parts);
+ for (;i.current();++it) {
+ i.current()->removeClient(win->guiFactory());
+ }
+}*/
+
+Part *
+Manager::partForMimeType(const QString &mimeType)
+{
+ return mimeType.isEmpty() ? 0 : part(m_partsByMime[mimeType.latin1()]);
+}
+
+Info *
+Manager::infoForMimeType(const QString &mimeType)
+{
+ Info *i = mimeType.isEmpty() ? 0 : m_partsByMime[mimeType.latin1()];
+ if (i)
+ return i;
+ setError(i18n("No plugin for mime type \"%1\"").arg(mimeType));
+ return 0;
+}
+
+
+bool
+Manager::checkProject(KexiDB::Connection *conn)
+{
+ clearError();
+// QString errmsg = i18n("Invalid project contents.");
+
+//TODO: catch errors!
+ if(!conn->isDatabaseUsed()) {
+ setError(conn);
+ return false;
+ }
+
+ KexiDB::Cursor *cursor = conn->executeQuery("SELECT * FROM kexi__parts");//, KexiDB::Cursor::Buffered);
+ if(!cursor) {
+ setError(conn);
+ return false;
+ }
+
+// int id=0;
+// QStringList parts_found;
+ for(cursor->moveFirst(); !cursor->eof(); cursor->moveNext())
+ {
+// id++;
+ Info *i = infoForMimeType(cursor->value(2).toCString());
+ if(!i)
+ {
+ Missing m;
+ m.name = cursor->value(1).toString();
+ m.mime = cursor->value(2).toCString();
+ m.url = cursor->value(3).toString();
+
+ m_missing.append(m);
+ }
+ else
+ {
+ i->setProjectPartID(cursor->value(0).toInt());
+ i->setIdStoredInPartDatabase(true);
+// parts_found+=cursor->value(2).toString();
+ }
+ }
+
+ conn->deleteCursor(cursor);
+
+#if 0 //js: moved to Connection::createDatabase()
+ //add missing default part entries
+ KexiDB::TableSchema *ts = conn->tableSchema("kexi__parts");
+ if (!ts)
+ return false;
+ KexiDB::FieldList *fl = ts->subList("p_id", "p_name", "p_mime", "p_url");
+ if (!fl)
+ return false;
+ if (!parts_found.contains("kexi/table")) {
+ if (!conn->insertRecord(*fl, QVariant(1), QVariant("Tables"), QVariant("kexi/table"), QVariant("http://")))
+ return false;
+ }
+ if (!parts_found.contains("kexi/query")) {
+ if (!conn->insertRecord(*fl, QVariant(2), QVariant("Queries"), QVariant("kexi/query"), QVariant("http://")))
+ return false;
+ }
+#endif
+ return true;
+}
+
+void Manager::insertStaticPart(StaticPart* part)
+{
+ if (!part)
+ return;
+ part->info()->setProjectPartID(m_nextTempProjectPartID--); // temp. part id are -1, -2, and so on,
+ m_partlist.append(part->info());
+ if (!part->info()->mimeType().isEmpty())
+ m_partsByMime.insert(part->info()->mimeType(), part->info());
+ m_parts.insert(part->info()->projectPartID(), part);
+}
+
+#include "kexipartmanager.moc"
diff --git a/kexi/core/kexipartmanager.h b/kexi/core/kexipartmanager.h
new file mode 100644
index 000000000..1756e965a
--- /dev/null
+++ b/kexi/core/kexipartmanager.h
@@ -0,0 +1,141 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPARTMANAGER_H
+#define KEXIPARTMANAGER_H
+
+#include <qobject.h>
+#include <qdict.h>
+#include <qasciidict.h>
+#include <qintdict.h>
+#include <qvaluelist.h>
+#include <qptrlist.h>
+
+#include <kservice.h>
+
+#include <kexidb/object.h>
+
+//#include "kexipartdatasource.h"
+
+namespace KexiDB
+{
+ class Connection;
+}
+
+namespace KexiPart
+{
+ class Info;
+ class Part;
+ class StaticPart;
+
+ struct Missing
+ {
+ QString name;
+ QCString mime;
+ QString url;
+ };
+
+ typedef QAsciiDict<Info> PartInfoDict;
+ typedef QDictIterator<Info> PartInfoDictIterator;
+ typedef QValueList<Missing> MissingList;
+ typedef QPtrList<Info> PartInfoList;
+ typedef QPtrListIterator<Info> PartInfoListIterator;
+ typedef QIntDict<Part> PartDict;
+// typedef QPtrList<DataSource> DataSourceList;
+
+/**
+ * @short KexiPart's manager: looks up and instantiates them
+ *
+ * It dlopens them when needed, they aren't dlopened at startup is not necessary.
+ */
+class KEXICORE_EXPORT Manager : public QObject, public KexiDB::Object
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * creates an empty instance
+ */
+ Manager(QObject *parent = 0);
+ ~Manager();
+
+ /**
+ * queries ktrader and creates a list of available parts
+ */
+ void lookup();
+
+ /**
+ * \return a part object for specified mime type. Dlopens a part using KexiPart::Info
+ * if needed. Return 0 if loading failed.
+ */
+ Part *partForMimeType(const QString& mimeTypt);
+
+ /**
+ * \return a part object for specified info. Dlopens a part using KexiPart::Info
+ * if needed. Return 0 if loading failed.
+ */
+ Part *part(Info *);
+
+ /**
+ * \return the info for a corresponding internal mime
+ */
+ Info *infoForMimeType(const QString& mimeType);
+
+ /**
+ * checks project's kexi__part table
+ * and checks if all parts used in a project are available locally
+ *
+ * use @ref missingParts() to get a list of missing parts
+ */
+ bool checkProject(KexiDB::Connection *conn);
+
+ /**
+ * @returns parts metioned in the project meta tables but not available locally
+ */
+ MissingList missingParts() const { return m_missing; }
+
+
+ /**
+ * @returns a list of the available KexiParts in well-defined order
+ */
+ PartInfoList *partInfoList() { return &m_partlist; }
+
+ signals:
+ void partLoaded(KexiPart::Part*);
+
+ protected:
+ //! Used by StaticPart
+ void insertStaticPart(KexiPart::StaticPart* part);
+
+ private:
+ PartDict m_parts;
+ PartInfoList m_partlist;
+ PartInfoDict m_partsByMime;
+ MissingList m_missing;
+ int m_nextTempProjectPartID;
+ bool m_lookupDone : 1;
+
+ friend class StaticPart;
+};
+
+}
+
+#endif
+
diff --git a/kexi/core/kexiproject.cpp b/kexi/core/kexiproject.cpp
new file mode 100644
index 000000000..741fb67a4
--- /dev/null
+++ b/kexi/core/kexiproject.cpp
@@ -0,0 +1,1023 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qfile.h>
+#include <qapplication.h>
+#include <qdom.h>
+
+#include <kmimetype.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <kexiutils/identifier.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+#include <kexidb/driver.h>
+#include <kexidb/drivermanager.h>
+#include <kexidb/utils.h>
+#include <kexidb/parser/parser.h>
+#include <kexidb/msghandler.h>
+#include <kexidb/dbproperties.h>
+#include <kexiutils/utils.h>
+
+#include "kexiproject.h"
+#include "kexipartmanager.h"
+#include "kexipartitem.h"
+#include "kexipartinfo.h"
+#include "kexipart.h"
+#include "kexidialogbase.h"
+#include "kexi.h"
+#include "keximainwindow.h"
+#include "kexiblobbuffer.h"
+#include "kexiguimsghandler.h"
+
+#include <assert.h>
+
+class KexiProject::Private
+{
+ public:
+ Private()
+ : data(0)
+ , itemDictsCache(199)
+ , unstoredItems(199)
+ , tempPartItemID_Counter(-1)
+ , sqlParser(0)
+ , versionMajor(0)
+ , versionMinor(0)
+ {
+ itemDictsCache.setAutoDelete(true);
+ unstoredItems.setAutoDelete(true);
+ }
+ ~Private() {
+ delete data;
+ data=0;
+ delete sqlParser;
+ }
+
+ QGuardedPtr<KexiDB::Connection> connection;
+ QGuardedPtr<KexiProjectData> data;
+
+ QString error_title;
+
+ //! a cache for item() method, indexed by project part's ids
+ QIntDict<KexiPart::ItemDict> itemDictsCache;
+
+ QPtrDict<KexiPart::Item> unstoredItems;
+ int tempPartItemID_Counter; //!< helper for getting unique
+ //!< temporary identifiers for unstored items
+ KexiDB::Parser* sqlParser;
+
+ int versionMajor;
+ int versionMinor;
+};
+
+//---------------------------
+
+/*
+ Helper for setting temporary error title.
+class KexiProject::ErrorTitle
+{
+ public:
+ ErrorTitle(KexiProject* p, const QString& msg = QString::null)
+ : prj(p)
+ , prev_err_title(p->m_error_title)
+ {
+ p->m_error_title = msg;
+ }
+ ~ErrorTitle()
+ {
+ prj->m_error_title = prev_err_title;
+ }
+ KexiProject* prj;
+ QString prev_err_title;
+};*/
+
+KexiProject::KexiProject(KexiProjectData *pdata, KexiDB::MessageHandler* handler)
+ : QObject(), Object(handler)
+ , d(new Private())
+{
+ d->data = pdata;
+//! @todo partmanager is outside project, so can be initialised just once:
+ Kexi::partManager().lookup();
+}
+
+KexiProject::KexiProject(KexiProjectData *pdata, KexiDB::MessageHandler* handler,
+ KexiDB::Connection* conn)
+ : QObject(), Object(handler)
+ , d(new Private())
+{
+ d->data = pdata;
+ if (d->data->connectionData() == d->connection->data())
+ d->connection = conn;
+ else
+ kdWarning() << "KexiProject::KexiProject(): passed connection's data ("
+ << conn->data()->serverInfoString() << ") is not compatible with project's conn. data ("
+ << d->data->connectionData()->serverInfoString() << ")" << endl;
+//! @todo partmanager is outside project, so can be initialised just once:
+ Kexi::partManager().lookup();
+}
+
+KexiProject::~KexiProject()
+{
+ closeConnection();
+ delete d;
+}
+
+KexiDB::Connection *KexiProject::dbConnection() const
+{
+ return d->connection;
+}
+
+KexiProjectData* KexiProject::data() const
+{
+ return d->data;
+}
+
+int KexiProject::versionMajor() const
+{
+ return d->versionMajor;
+}
+
+int KexiProject::versionMinor() const
+{
+ return d->versionMinor;
+}
+
+tristate
+KexiProject::open(bool &incompatibleWithKexi)
+{
+ return openInternal(&incompatibleWithKexi);
+}
+
+tristate
+KexiProject::open()
+{
+ return openInternal(0);
+}
+
+tristate
+KexiProject::openInternal(bool *incompatibleWithKexi)
+{
+ if (incompatibleWithKexi)
+ *incompatibleWithKexi = false;
+ kdDebug() << "KexiProject::open(): " << d->data->databaseName() <<" "<< d->data->connectionData()->driverName << endl;
+ KexiDB::MessageTitle et(this,
+ i18n("Could not open project \"%1\".").arg(d->data->databaseName()));
+
+ if (!createConnection()) {
+ kdDebug() << "KexiProject::open(): !createConnection()" << endl;
+ return false;
+ }
+ bool cancel = false;
+ KexiGUIMessageHandler msgHandler;
+ if (!d->connection->useDatabase(d->data->databaseName(), true, &cancel, &msgHandler))
+ {
+ if (cancel) {
+ return cancelled;
+ }
+ kdDebug() << "KexiProject::open(): !d->connection->useDatabase() "
+ << d->data->databaseName() <<" "<< d->data->connectionData()->driverName << endl;
+
+ if (d->connection->errorNum() == ERR_NO_DB_PROPERTY) {
+//<temp>
+//! @todo this is temporary workaround as we have no import driver for SQLite
+ if (/*supported?*/ !d->data->connectionData()->driverName.lower().startsWith("sqlite")) {
+//</temp>
+ if (incompatibleWithKexi)
+ *incompatibleWithKexi = true;
+ }
+ else
+ setError(d->connection);
+ closeConnection();
+ return false;
+ }
+
+ setError(d->connection);
+ closeConnection();
+ return false;
+ }
+
+ if (!initProject())
+ return false;
+
+ return createInternalStructures(/*insideTransaction*/true);
+}
+
+tristate
+KexiProject::create(bool forceOverwrite)
+{
+ KexiDB::MessageTitle et(this,
+ i18n("Could not create project \"%1\".").arg(d->data->databaseName()));
+
+ if (!createConnection())
+ return false;
+ if (!checkWritable())
+ return false;
+ if (d->connection->databaseExists( d->data->databaseName() )) {
+ if (!forceOverwrite)
+ return cancelled;
+ if (!d->connection->dropDatabase( d->data->databaseName() )) {
+ setError(d->connection);
+ closeConnection();
+ return false;
+ }
+ kdDebug() << "--- DB '" << d->data->databaseName() << "' dropped ---"<< endl;
+ }
+ if (!d->connection->createDatabase( d->data->databaseName() )) {
+ setError(d->connection);
+ closeConnection();
+ return false;
+ }
+ kdDebug() << "--- DB '" << d->data->databaseName() << "' created ---"<< endl;
+ // and now: open
+ if (!d->connection->useDatabase(d->data->databaseName()))
+ {
+ kdDebug() << "--- DB '" << d->data->databaseName() << "' USE ERROR ---"<< endl;
+ setError(d->connection);
+ closeConnection();
+ return false;
+ }
+ kdDebug() << "--- DB '" << d->data->databaseName() << "' used ---"<< endl;
+
+ //<add some data>
+ KexiDB::Transaction trans = d->connection->beginTransaction();
+ if (trans.isNull())
+ return false;
+
+ if (!createInternalStructures(/*!insideTransaction*/false))
+ return false;
+
+ //add some metadata
+//! @todo put more props. todo - creator, created date, etc. (also to KexiProjectData)
+ KexiDB::DatabaseProperties &props = d->connection->databaseProperties();
+ if (!props.setValue("kexiproject_major_ver", d->versionMajor)
+ || !props.setCaption("kexiproject_major_ver", i18n("Project major version"))
+ || !props.setValue("kexiproject_minor_ver", d->versionMinor)
+ || !props.setCaption("kexiproject_minor_ver", i18n("Project minor version"))
+ || !props.setValue("project_caption", d->data->caption())
+ || !props.setCaption("project_caption", i18n("Project caption"))
+ || !props.setValue("project_desc", d->data->description())
+ || !props.setCaption("project_desc", i18n("Project description")) )
+ return false;
+
+/* KexiDB::TableSchema *t_db = d->connection->tableSchema("kexi__db");
+ //caption:
+ if (!t_db)
+ return false;
+
+ if (!KexiDB::replaceRow(*d->connection, t_db, "db_property", "project_caption",
+ "db_value", QVariant( d->data->caption() ), KexiDB::Field::Text)
+ || !KexiDB::replaceRow(*d->connection, t_db, "db_property", "project_desc",
+ "db_value", QVariant( d->data->description() ), KexiDB::Field::Text) )
+ return false;
+*/
+ if (trans.active() && !d->connection->commitTransaction(trans))
+ return false;
+ //</add some data>
+
+ return initProject();
+}
+
+bool KexiProject::createInternalStructures(bool insideTransaction)
+{
+ KexiDB::TransactionGuard tg;
+ if (insideTransaction) {
+ tg.setTransaction( d->connection->beginTransaction() );
+ if (tg.transaction().isNull())
+ return false;
+ }
+
+ //Get information about kexiproject version.
+ //kexiproject version is a version of data layer above kexidb layer.
+ KexiDB::DatabaseProperties &props = d->connection->databaseProperties();
+ bool ok;
+ int storedMajorVersion = props.value("kexiproject_major_ver").toInt(&ok);
+ if (!ok)
+ storedMajorVersion = 0;
+ int storedMinorVersion = props.value("kexiproject_minor_ver").toInt(&ok);
+ if (!ok)
+ storedMinorVersion = 1;
+
+ bool containsKexi__blobsTable = d->connection->drv_containsTable("kexi__blobs");
+ int dummy;
+ bool contains_o_folder_id = containsKexi__blobsTable && true == d->connection->querySingleNumber(
+ "SELECT COUNT(o_folder_id) FROM kexi__blobs", dummy, 0, false/*addLimitTo1*/);
+ bool add_folder_id_column = false;
+
+//! @todo what about read-only db access?
+ if (storedMajorVersion<=0) {
+ d->versionMajor = KEXIPROJECT_VERSION_MAJOR;
+ d->versionMinor = KEXIPROJECT_VERSION_MINOR;
+ //For compatibility for projects created before Kexi 1.0 beta 1:
+ //1. no kexiproject_major_ver and kexiproject_minor_ver -> add them
+ if (!d->connection->isReadOnly()) {
+ if (!props.setValue("kexiproject_major_ver", d->versionMajor)
+ || !props.setCaption("kexiproject_major_ver", i18n("Project major version"))
+ || !props.setValue("kexiproject_minor_ver", d->versionMinor)
+ || !props.setCaption("kexiproject_minor_ver", i18n("Project minor version")) ) {
+ return false;
+ }
+ }
+
+ if (containsKexi__blobsTable) {
+//! @todo what to do for readonly connections? Should we alter kexi__blobs in memory?
+ if (!d->connection->isReadOnly()) {
+ if (!contains_o_folder_id) {
+ add_folder_id_column = true;
+ }
+ }
+ }
+ }
+ if (storedMajorVersion!=d->versionMajor || storedMajorVersion!=d->versionMinor) {
+ //! @todo version differs: should we change something?
+ d->versionMajor = storedMajorVersion;
+ d->versionMinor = storedMinorVersion;
+ }
+
+ KexiDB::InternalTableSchema *t_blobs = new KexiDB::InternalTableSchema("kexi__blobs");
+ t_blobs->addField( new KexiDB::Field("o_id", KexiDB::Field::Integer,
+ KexiDB::Field::PrimaryKey | KexiDB::Field::AutoInc, KexiDB::Field::Unsigned) )
+ .addField( new KexiDB::Field("o_data", KexiDB::Field::BLOB) )
+ .addField( new KexiDB::Field("o_name", KexiDB::Field::Text ) )
+ .addField( new KexiDB::Field("o_caption", KexiDB::Field::Text ) )
+ .addField( new KexiDB::Field("o_mime", KexiDB::Field::Text, KexiDB::Field::NotNull) )
+ .addField( new KexiDB::Field("o_folder_id",
+ KexiDB::Field::Integer, 0, KexiDB::Field::Unsigned) //references kexi__gallery_folders.f_id
+ //If null, the BLOB only points to virtual "All" folder
+ //WILL BE USED in Kexi >=2.0
+ );
+
+ //*** create global BLOB container, if not present
+ if (containsKexi__blobsTable) {
+ //! just insert this schema
+ d->connection->insertInternalTableSchema(t_blobs);
+ if (add_folder_id_column && !d->connection->isReadOnly()) {
+ // 2. "kexi__blobs" table contains no "o_folder_id" column -> add it
+ // (by copying table to avoid data loss)
+ KexiDB::TableSchema *kexi__blobsCopy = new KexiDB::TableSchema( *t_blobs );
+ kexi__blobsCopy->setName("kexi__blobs__copy");
+ if (!d->connection->drv_createTable( *kexi__blobsCopy )) {
+ delete kexi__blobsCopy;
+ delete t_blobs;
+ return false;
+ }
+ // 2.1 copy data (insert 0's into o_folder_id column)
+ if (!d->connection->executeSQL(
+ QString::fromLatin1("INSERT INTO kexi__blobs (o_data, o_name, o_caption, o_mime, o_folder_id) "
+ "SELECT o_data, o_name, o_caption, o_mime, 0 FROM kexi__blobs") )
+ // 2.2 remove the original kexi__blobs
+ || !d->connection->executeSQL(QString::fromLatin1("DROP TABLE kexi__blobs")) //lowlevel
+ // 2.3 rename the copy back into kexi__blobs
+ || !d->connection->drv_alterTableName(*kexi__blobsCopy, "kexi__blobs")
+ )
+ {
+ //(no need to drop the copy, ROLLBACK will drop it)
+ delete kexi__blobsCopy;
+ delete t_blobs;
+ return false;
+ }
+ delete kexi__blobsCopy; //not needed - physically renamed to kexi_blobs
+ }
+ }
+ else {
+// if (!d->connection->createTable( t_blobs, false/*!replaceExisting*/ )) {
+ if (!d->connection->isReadOnly()) {
+ if (!d->connection->createTable( t_blobs, true/*replaceExisting*/ )) {
+ delete t_blobs;
+ return false;
+ }
+ }
+ }
+
+ //Store default part infos.
+ //Infos for other parts (forms, reports...) are created on demand in KexiDialogBase::storeNewData()
+ KexiDB::InternalTableSchema *t_parts = new KexiDB::InternalTableSchema("kexi__parts"); //newKexiDBSystemTableSchema("kexi__parts");
+ t_parts->addField(
+ new KexiDB::Field("p_id", KexiDB::Field::Integer, KexiDB::Field::PrimaryKey | KexiDB::Field::AutoInc, KexiDB::Field::Unsigned)
+ )
+ .addField( new KexiDB::Field("p_name", KexiDB::Field::Text) )
+ .addField( new KexiDB::Field("p_mime", KexiDB::Field::Text ) )
+ .addField( new KexiDB::Field("p_url", KexiDB::Field::Text ) );
+
+ bool containsKexi__partsTable = d->connection->drv_containsTable("kexi__parts");
+ bool partsTableOk = true;
+ if (containsKexi__partsTable) {
+ //! just insert this schema
+ d->connection->insertInternalTableSchema(t_parts);
+ }
+ else {
+ if (!d->connection->isReadOnly()) {
+ partsTableOk = d->connection->createTable( t_parts, true/*replaceExisting*/ );
+
+ KexiDB::FieldList *fl = t_parts->subList("p_id", "p_name", "p_mime", "p_url");
+ if (partsTableOk)
+ partsTableOk = d->connection->insertRecord(*fl, QVariant(1), QVariant("Tables"),
+ QVariant("kexi/table"), QVariant("http://koffice.org/kexi/"));
+
+ if (partsTableOk)
+ partsTableOk = d->connection->insertRecord(*fl, QVariant(2), QVariant("Queries"),
+ QVariant("kexi/query"), QVariant("http://koffice.org/kexi/"));
+ }
+ }
+
+ if (!partsTableOk) {
+ delete t_parts;
+ return false;
+ }
+
+ if (insideTransaction) {
+ if (tg.transaction().active() && !tg.commit())
+ return false;
+ }
+ return true;
+}
+
+bool
+KexiProject::createConnection()
+{
+ if (d->connection)
+ return true;
+
+ clearError();
+// closeConnection();//for sanity
+ KexiDB::MessageTitle et(this);
+
+ KexiDB::Driver *driver = Kexi::driverManager().driver(d->data->connectionData()->driverName);
+ if(!driver) {
+ setError(&Kexi::driverManager());
+ return false;
+ }
+
+ int connectionOptions = 0;
+ if (d->data->isReadOnly())
+ connectionOptions |= KexiDB::Driver::ReadOnlyConnection;
+ d->connection = driver->createConnection(*d->data->connectionData(), connectionOptions);
+ if (!d->connection)
+ {
+ kdDebug() << "KexiProject::open(): uuups failed " << driver->errorMsg() << endl;
+ setError(driver);
+ return false;
+ }
+
+ if (!d->connection->connect())
+ {
+ setError(d->connection);
+ kdDebug() << "KexiProject::createConnection(): error connecting: " << (d->connection ? d->connection->errorMsg() : QString::null) << endl;
+ closeConnection();
+ return false;
+ }
+
+ //re-init BLOB buffer
+//! @todo won't work for subsequent connection
+ KexiBLOBBuffer::setConnection(d->connection);
+ return true;
+}
+
+
+bool
+KexiProject::closeConnection()
+{
+ if (!d->connection)
+ return true;
+
+ if (!d->connection->disconnect()) {
+ setError(d->connection);
+ return false;
+ }
+
+ delete d->connection; //this will also clear connection for BLOB buffer
+ d->connection = 0;
+ return true;
+}
+
+bool
+KexiProject::initProject()
+{
+// emit dbAvailable();
+ kdDebug() << "KexiProject::open(): checking project parts..." << endl;
+
+ if (!Kexi::partManager().checkProject(d->connection)) {
+ setError(Kexi::partManager().error() ? (KexiDB::Object*)&Kexi::partManager() : (KexiDB::Connection*)d->connection);
+ return false;
+ }
+
+// !@todo put more props. todo - creator, created date, etc. (also to KexiProjectData)
+ KexiDB::DatabaseProperties &props = d->connection->databaseProperties();
+ QString str( props.value("project_caption").toString() );
+ if (!str.isEmpty())
+ d->data->setCaption( str );
+ str = props.value("project_desc").toString();
+ if (!str.isEmpty())
+ d->data->setDescription( str );
+/* KexiDB::RowData data;
+ QString sql = "select db_value from kexi__db where db_property='%1'";
+ if (d->connection->querySingleRecord( sql.arg("project_caption"), data ) && !data[0].toString().isEmpty())
+ d->data->setCaption(data[0].toString());
+ if (d->connection->querySingleRecord( sql.arg("project_desc"), data) && !data[0].toString().isEmpty())
+ d->data->setDescription(data[0].toString());*/
+
+ return true;
+}
+
+bool
+KexiProject::isConnected()
+{
+ if(d->connection && d->connection->isDatabaseUsed())
+ return true;
+
+ return false;
+}
+
+KexiPart::ItemDict*
+KexiProject::items(KexiPart::Info *i)
+{
+ kdDebug() << "KexiProject::items()" << endl;
+ if(!i || !isConnected())
+ return 0;
+
+ //trying in cache...
+ KexiPart::ItemDict *dict = d->itemDictsCache[ i->projectPartID() ];
+ if (dict)
+ return dict;
+ //retrieve:
+ KexiDB::Cursor *cursor = d->connection->executeQuery(
+ "SELECT o_id, o_name, o_caption FROM kexi__objects WHERE o_type = "
+ + QString::number(i->projectPartID()));//, KexiDB::Cursor::Buffered);
+// kdDebug() << "KexiProject::items(): cursor handle is:" << cursor << endl;
+ if(!cursor)
+ return 0;
+
+ dict = new KexiPart::ItemDict(1009);
+ dict->setAutoDelete(true);
+
+ for(cursor->moveFirst(); !cursor->eof(); cursor->moveNext())
+ {
+ KexiPart::Item *it = new KexiPart::Item();
+ bool ok;
+ int ident = cursor->value(0).toInt(&ok);
+ QString objName( cursor->value(1).toString() );
+
+ if ( ok && (ident>0) && !d->connection->isInternalTableSchema(objName)
+ && KexiUtils::isIdentifier(objName) )
+ {
+ it->setIdentifier(ident);
+ it->setMimeType(i->mimeType()); //js: may be not null???
+ it->setName(objName);
+ it->setCaption(cursor->value(2).toString());
+ }
+ dict->insert(it->identifier(), it);
+// kdDebug() << "KexiProject::items(): ITEM ADDED == "<<objName <<" id="<<ident<<endl;
+ }
+
+ d->connection->deleteCursor(cursor);
+// kdDebug() << "KexiProject::items(): end with count " << dict->count() << endl;
+ d->itemDictsCache.insert( i->projectPartID(), dict );
+ return dict;
+}
+
+KexiPart::ItemDict*
+KexiProject::itemsForMimeType(const QCString &mimeType)
+{
+ KexiPart::Info *info = Kexi::partManager().infoForMimeType(mimeType);
+ return items(info);
+}
+
+void
+KexiProject::getSortedItems(KexiPart::ItemList& list, KexiPart::Info *i)
+{
+ list.clear();
+ KexiPart::ItemDict* dict = items(i);
+ if (!dict)
+ return;
+ for (KexiPart::ItemDictIterator it(*dict); it.current(); ++it)
+ list.append(it.current());
+}
+
+void
+KexiProject::getSortedItemsForMimeType(KexiPart::ItemList& list, const QCString &mimeType)
+{
+ KexiPart::Info *info = Kexi::partManager().infoForMimeType(mimeType);
+ getSortedItems(list, info);
+}
+
+void
+KexiProject::addStoredItem(KexiPart::Info *info, KexiPart::Item *item)
+{
+ if (!info || !item)
+ return;
+ KexiPart::ItemDict *dict = items(info);
+ item->setNeverSaved( false );
+ d->unstoredItems.take(item); //no longer unstored
+ dict->insert( item->identifier(), item );
+ //let's update e.g. navigator
+ emit newItemStored(*item);
+}
+
+KexiPart::Item*
+KexiProject::itemForMimeType(const QCString &mimeType, const QString &name)
+{
+ KexiPart::ItemDict *dict = itemsForMimeType(mimeType);
+ if (!dict)
+ return 0;
+ const QString l_name = name.lower();
+ for (KexiPart::ItemDictIterator it( *dict ); it.current(); ++it) {
+ if (it.current()->name().lower()==l_name)
+ return it.current();
+ }
+ return 0;
+}
+
+KexiPart::Item*
+KexiProject::item(KexiPart::Info *i, const QString &name)
+{
+ KexiPart::ItemDict *dict = items(i);
+ if (!dict)
+ return 0;
+ const QString l_name = name.lower();
+ for (KexiPart::ItemDictIterator it( *dict ); it.current(); ++it) {
+ if (it.current()->name().lower()==l_name)
+ return it.current();
+ }
+ return 0;
+}
+
+KexiPart::Item*
+KexiProject::item(int identifier)
+{
+ KexiPart::ItemDict *dict;
+ for (QIntDictIterator<KexiPart::ItemDict> it(d->itemDictsCache); (dict = it.current()); ++it) {
+ KexiPart::Item *item = dict->find(identifier);
+ if (item)
+ return item;
+ }
+ return 0;
+}
+
+/*void KexiProject::clearMsg()
+{
+ clearError();
+// d->error_title=QString::null;
+}
+
+void KexiProject::setError(int code, const QString &msg )
+{
+ Object::setError(code, msg);
+ if (Object::error())
+ ERRMSG(d->error_title, this);
+// emit error(d->error_title, this);
+}
+
+
+void KexiProject::setError( const QString &msg )
+{
+ Object::setError(msg);
+ if (Object::error())
+ ERRMSG(d->error_title, this);
+// emit error(d->error_title, this);
+}
+
+void KexiProject::setError( KexiDB::Object *obj )
+{
+ if (!obj)
+ return;
+ Object::setError(obj);
+ if (Object::error())
+ ERRMSG(d->error_title, obj);
+// emit error(d->error_title, obj);
+}
+
+void KexiProject::setError(const QString &msg, const QString &desc)
+{
+ Object::setError(msg); //ok?
+ ERRMSG(msg, desc); //not KexiDB-related
+// emit error(msg, desc); //not KexiDB-related
+}
+*/
+
+KexiPart::Part *KexiProject::findPartFor(KexiPart::Item& item)
+{
+ clearError();
+ KexiDB::MessageTitle et(this);
+ KexiPart::Part *part = Kexi::partManager().partForMimeType(item.mimeType());
+ if (!part)
+ setError(&Kexi::partManager());
+ return part;
+}
+
+KexiDialogBase* KexiProject::openObject(KexiMainWindow *wnd, KexiPart::Item& item,
+ int viewMode, QMap<QString,QString>* staticObjectArgs)
+{
+ clearError();
+ if (viewMode!=Kexi::DataViewMode && data()->userMode())
+ return 0;
+
+ KexiDB::MessageTitle et(this);
+ KexiPart::Part *part = findPartFor(item);
+ if (!part)
+ return 0;
+ KexiDialogBase *dlg = part->openInstance(wnd, item, viewMode, staticObjectArgs);
+ if (!dlg) {
+ if (part->lastOperationStatus().error())
+ setError(i18n("Opening object \"%1\" failed.").arg(item.name())+"<br>"
+ +part->lastOperationStatus().message,
+ part->lastOperationStatus().description);
+ return 0;
+ }
+ return dlg;
+}
+
+KexiDialogBase* KexiProject::openObject(KexiMainWindow *wnd, const QCString &mimeType,
+ const QString& name, int viewMode)
+{
+ KexiPart::Item *it = itemForMimeType(mimeType, name);
+ return it ? openObject(wnd, *it, viewMode) : 0;
+}
+
+bool KexiProject::checkWritable()
+{
+ if (!d->connection->isReadOnly())
+ return true;
+ setError(i18n("This project is opened as read only."));
+ return false;
+}
+
+bool KexiProject::removeObject(KexiMainWindow *wnd, KexiPart::Item& item)
+{
+ clearError();
+ if (data()->userMode())
+ return false;
+
+ KexiDB::MessageTitle et(this);
+ if (!checkWritable())
+ return false;
+ KexiPart::Part *part = findPartFor(item);
+ if (!part)
+ return false;
+ if (!item.neverSaved() && !part->remove(wnd, item)) {
+ //js TODO check for errors
+ return false;
+ }
+ if (!item.neverSaved()) {
+ KexiDB::TransactionGuard tg( *d->connection );
+ if (!tg.transaction().active()) {
+ setError(d->connection);
+ return false;
+ }
+ if (!d->connection->removeObject( item.identifier() )) {
+ setError(d->connection);
+ return false;
+ }
+ if (!tg.commit()) {
+ setError(d->connection);
+ return false;
+ }
+ }
+ emit itemRemoved(item);
+
+ //now: remove this item from cache
+ if (part->info()) {
+ KexiPart::ItemDict *dict = d->itemDictsCache[ part->info()->projectPartID() ];
+ if (!(dict && dict->remove( item.identifier() )))
+ d->unstoredItems.remove(&item);//remove temp.
+ }
+ return true;
+}
+
+bool KexiProject::renameObject( KexiMainWindow *wnd, KexiPart::Item& item, const QString& _newName )
+{
+ clearError();
+ if (data()->userMode())
+ return 0;
+
+ KexiUtils::WaitCursor wait;
+ QString newName = _newName.stripWhiteSpace();
+ {
+ KexiDB::MessageTitle et(this);
+ if (newName.isEmpty()) {
+ setError( i18n("Could not set empty name for this object.") );
+ return false;
+ }
+ if (this->itemForMimeType(item.mimeType(), newName)!=0) {
+ setError( i18n("Could not use this name. Object with name \"%1\" already exists.")
+ .arg(newName) );
+ return false;
+ }
+ }
+
+ KexiDB::MessageTitle et(this,
+ i18n("Could not rename object \"%1\".").arg(item.name()) );
+ if (!checkWritable())
+ return false;
+ KexiPart::Part *part = findPartFor(item);
+ if (!part)
+ return false;
+ KexiDB::TransactionGuard tg( *d->connection );
+ if (!tg.transaction().active()) {
+ setError(d->connection);
+ return false;
+ }
+ if (!part->rename(wnd, item, newName)) {
+ setError(part->lastOperationStatus().message, part->lastOperationStatus().description);
+ return false;
+ }
+ if (!d->connection->executeSQL( "update kexi__objects set o_name="
+ + d->connection->driver()->valueToSQL( KexiDB::Field::Text, newName )
+ + " where o_id=" + QString::number(item.identifier()) )) {
+ setError(d->connection);
+ return false;
+ }
+ if (!tg.commit()) {
+ setError(d->connection);
+ return false;
+ }
+ QCString oldName( item.name().latin1() );
+ item.setName( newName );
+ emit itemRenamed(item, oldName);
+ return true;
+}
+
+KexiPart::Item* KexiProject::createPartItem(KexiPart::Info *info, const QString& suggestedCaption)
+{
+ clearError();
+ if (data()->userMode())
+ return 0;
+
+ KexiDB::MessageTitle et(this);
+ KexiPart::Part *part = Kexi::partManager().part(info);
+ if (!part) {
+ setError(&Kexi::partManager());
+ return 0;
+ }
+
+ KexiPart::ItemDict *dict = items(info);
+
+ //find new, unique default name for this item
+ int n;
+ QString new_name;
+ QString base_name;
+ if (suggestedCaption.isEmpty()) {
+ n = 1;
+ base_name = part->instanceName();
+ }
+ else {
+ n = 0; //means: try not to add 'n'
+ base_name = KexiUtils::string2Identifier(suggestedCaption).lower();
+ }
+ base_name = KexiUtils::string2Identifier(base_name).lower();
+ KexiPart::ItemDictIterator it(*dict);
+ QPtrDictIterator<KexiPart::Item> itUnstored(d->unstoredItems);
+ do {
+ new_name = base_name;
+ if (n>=1)
+ new_name += QString::number(n);
+ for (it.toFirst(); it.current(); ++it) {
+ if (it.current()->name().lower()==new_name)
+ break;
+ }
+ if ( it.current() ) {
+ n++;
+ continue; //stored exists!
+ }
+ for (itUnstored.toFirst(); itUnstored.current(); ++itUnstored) {
+ if (itUnstored.current()->name().lower()==new_name)
+ break;
+ }
+ if ( !itUnstored.current() )
+ break; //unstored doesn't exist
+ n++;
+ } while (n<1000/*sanity*/);
+
+ if (n>=1000)
+ return 0;
+
+ QString new_caption( suggestedCaption.isEmpty() ? part->instanceCaption() : suggestedCaption);
+ if (n>=1)
+ new_caption += QString::number(n);
+
+ KexiPart::Item *item = new KexiPart::Item();
+ item->setIdentifier( --d->tempPartItemID_Counter );//temporary
+ item->setMimeType(info->mimeType());
+ item->setName(new_name);
+ item->setCaption(new_caption);
+ item->setNeverSaved(true);
+ d->unstoredItems.insert(item, item);
+ return item;
+}
+
+KexiPart::Item* KexiProject::createPartItem(KexiPart::Part *part, const QString& suggestedCaption)
+{
+ return createPartItem(part->info(), suggestedCaption);
+}
+
+void KexiProject::deleteUnstoredItem(KexiPart::Item *item)
+{
+ if (!item)
+ return;
+ d->unstoredItems.remove(item);
+}
+
+KexiDB::Parser* KexiProject::sqlParser()
+{
+ if (!d->sqlParser) {
+ if (!d->connection)
+ return 0;
+ d->sqlParser = new KexiDB::Parser(d->connection);
+ }
+ return d->sqlParser;
+}
+
+static const QString warningNoUndo = i18n("Warning: entire project's data will be removed.");
+
+/*static*/
+KexiProject*
+KexiProject::createBlankProject(bool &cancelled, KexiProjectData* data,
+ KexiDB::MessageHandler* handler)
+{
+ cancelled = false;
+ KexiProject *prj = new KexiProject( new KexiProjectData(*data), handler );
+
+ tristate res = prj->create(false);
+ if (~res) {
+//! @todo move to KexiMessageHandler
+ if (KMessageBox::Yes != KMessageBox::warningYesNo(0, "<qt>"+i18n(
+ "The project %1 already exists.\n"
+ "Do you want to replace it with a new, blank one?")
+ .arg(prj->data()->infoString())+"\n"+warningNoUndo+"</qt>",
+ QString::null, KGuiItem(i18n("Replace")), KStdGuiItem::cancel() ))
+//todo add serverInfoString() for server-based prj
+ {
+ delete prj;
+ cancelled = true;
+ return 0;
+ }
+ res = prj->create(true/*overwrite*/);
+ }
+ if (res != true) {
+ delete prj;
+ return 0;
+ }
+ kdDebug() << "KexiProject::createBlankProject(): new project created --- " << endl;
+//todo? Kexi::recentProjects().addProjectData( data );
+
+ return prj;
+}
+
+/*static*/
+tristate KexiProject::dropProject(KexiProjectData* data,
+ KexiDB::MessageHandler* handler, bool dontAsk)
+{
+ if (!dontAsk && KMessageBox::Yes != KMessageBox::warningYesNo(0,
+ i18n("Do you want to drop the project \"%1\"?").arg(data->objectName())+"\n"+warningNoUndo ))
+ return cancelled;
+
+ KexiProject prj( new KexiProjectData(*data), handler );
+ if (!prj.open())
+ return false;
+
+ if (prj.dbConnection()->isReadOnly()) {
+ handler->showErrorMessage(
+ i18n("Could not drop this project. Database connection for this project has been opened as read only."));
+ return false;
+ }
+
+ return prj.dbConnection()->dropDatabase();
+}
+
+/*void KexiProject::reloadPartItem( KexiDialogBase* dialog )
+{
+ if (!dialog)
+ return;
+
+ KexiPart::Item* item = dialog->partItem();
+
+ if (dialog || !d->connection->setQuerySchemaObsolete( queryName ))
+ return;
+ KexiPart::Info *partInfo = Kexi::partManager().infoForMimeType("kexi/query");
+ if (!partInfo)
+ return; //err?
+ item(partInfo, queryName);
+ if (!item)
+ return; //err?
+ emit itemSetO
+
+}*/
+
+#include "kexiproject.moc"
diff --git a/kexi/core/kexiproject.h b/kexi/core/kexiproject.h
new file mode 100644
index 000000000..1128ffe42
--- /dev/null
+++ b/kexi/core/kexiproject.h
@@ -0,0 +1,334 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPROJECT_H
+#define KEXIPROJECT_H
+
+#include <qobject.h>
+#include <qintdict.h>
+#include <qptrdict.h>
+#include <qguardedptr.h>
+
+#include <kexiutils/tristate.h>
+#include <kexidb/object.h>
+#include "kexiprojectdata.h"
+#include "kexipartitem.h"
+#include "kexi.h"
+
+/*! KexiProject implementation version.
+ It is altered after every change:
+ - major number is increased after KexiProject storage format change,
+ - minor is increased after adding binary-incompatible change.
+ Use KexiProject::versionMajor() and KexiProject::versionMinor() to get real project's version.
+*/
+
+#define KEXIPROJECT_VERSION_MAJOR 1
+#define KEXIPROJECT_VERSION_MINOR 0
+
+namespace KexiDB
+{
+ class DriverManager;
+ class Driver;
+ class Connection;
+ class Parser;
+}
+
+namespace KexiPart
+{
+ class Part;
+ class Info;
+}
+
+class KexiMainWindow;
+class KexiDialogBase;
+
+/**
+ * @short A project's main controller.
+ * It also contains connection data,
+ * current file state, etc.
+ */
+class KEXICORE_EXPORT KexiProject : public QObject, public KexiDB::Object
+{
+ Q_OBJECT
+
+ public:
+ /*! Constructor 1. Creates a new object using \a pdata.
+ \a pdata which will be then owned by KexiProject object.
+ \a handler can be provided to receive error messages during
+ entire KexiProject object's lifetime. */
+ KexiProject(KexiProjectData* pdata, KexiDB::MessageHandler* handler = 0);
+
+ /*! Constructor 2. Like above but sets predefined connections \a conn.
+ The connection should be created using the same connection data
+ as pdata->connectionData(). The connection will become owned by created KexiProject
+ object, so do not destroy it. */
+ KexiProject(KexiProjectData *pdata, KexiDB::MessageHandler* handler,
+ KexiDB::Connection* conn);
+
+// KexiProject(KexiDB::ConnectionData *cdata);
+
+ ~KexiProject();
+
+ /*! \return major version of KexiProject object.
+ This information is retrieved from database when existing project is opened. */
+ int versionMajor() const;
+
+ /*! \return minor version of KexiProject object.
+ @see versionMajor() */
+ int versionMinor() const;
+
+ /*! Opens existing project using project data.
+ \return true on success */
+ tristate open();
+
+ /*! Like open().
+ \return true on success.
+ Additional \a incompatibleWithKexi, is set to false on failure when
+ connection for the project was successfully started bu the project
+ is probably not compatible with Kexi - no valid "kexidb_major_ver"
+ value in "kexi__db" table.
+ This is often the case for native server-based databases.
+ If so, Kexi application can propose importing the database
+ or linking it to parent project (the latter isn't yet implemented).
+ For other types of errors the variable is set to true. */
+ tristate open(bool &incompatibleWithKexi);
+
+ /*! Creates new, empty project using project data.
+ If \a forceOverwrite is true, existing database project is silently overwritten.
+ Connection is created (accessible then with KexiProject::dbConnection()).
+
+ Since KexiProject inherits KexiDB::Object, it is possible to get error message
+ and other information on error.
+
+ \return true on success, false on failure, and cancelled when database exists
+ but \a forceOverwrite is false. */
+ tristate create(bool forceOverwrite = false);
+
+ /*! \return true if there was error during last operation on the object. */
+ bool error() const { return KexiDB::Object::error(); }
+
+ /**
+ * @return true if a we are connected to a database
+ */
+ bool isConnected();
+
+ /**
+ * @return all items of a type \a i in this project
+ */
+ KexiPart::ItemDict* items(KexiPart::Info *i);
+
+ /**
+ * @return all items of a type \a mime in this project
+ * It is a convenience function.
+ */
+ KexiPart::ItemDict* itemsForMimeType(const QCString &mimeType);
+
+ /**
+ * Puts a list of items of a type \a i in this project into \a list.
+ * You can then sort this list using ItemList::sort().
+ */
+ void getSortedItems(KexiPart::ItemList& list, KexiPart::Info *i);
+
+ /**
+ * Puts a sorted list of items of a type \a mimeType in this project into \a list.
+ * You can then sort this list using ItemList::sort().
+ */
+ void getSortedItemsForMimeType(KexiPart::ItemList& list, const QCString &mimeType);
+
+ /**
+ * @return item of type \a mime and name \a name
+ */
+ KexiPart::Item* itemForMimeType(const QCString &mimeType, const QString &name);
+
+ /**
+ * @return item of type \a i and name \a name
+ */
+ KexiPart::Item* item(KexiPart::Info *i, const QString &name);
+
+ /**
+ * @return item for \a identifier
+ */
+ KexiPart::Item* item(int identifier);
+
+ /**
+ * @return the database connection associated with this project
+ */
+ KexiDB::Connection *dbConnection() const;
+
+ /**
+ * @return the project's data
+ */
+ KexiProjectData *data() const;
+
+ /*! Opens object pointed by \a item in a view \a viewMode.
+ \a staticObjectArgs can be passed for static object
+ (only works when part for this item is of type KexiPart::StaticPart) */
+ KexiDialogBase* openObject(KexiMainWindow *wnd, KexiPart::Item& item,
+ int viewMode = Kexi::DataViewMode, QMap<QString,QString>* staticObjectArgs = 0);
+
+ //! For convenience
+ KexiDialogBase* openObject(KexiMainWindow *wnd, const QCString &mimeType,
+ const QString& name, int viewMode = Kexi::DataViewMode);
+
+ /*! Remove a part instance pointed by \a item.
+ \return true on success. */
+ bool removeObject(KexiMainWindow *wnd, KexiPart::Item& item);
+
+ /*! Renames a part instance pointed by \a item to a new name \a newName.
+ \return true on success. */
+ bool renameObject(KexiMainWindow *wnd, KexiPart::Item& item, const QString& newName);
+
+ /*! Creates part item for given part \a info.
+ Newly item will not be saved to the backend but stored in memory only
+ (owned by project), and marked as "neverSaved" (see KexiPart::Item::neverSaved()).
+ The item will have assigned a new unique caption like e.g. "Table15",
+ and unique name like "table15", but no specific identifier
+ (because id will be assigned on creation at the backend side).
+
+ If \a suggestedCaption is not empty, it will be set as a caption
+ (with number suffix, to avoid duplicated, e.g. "employees7"
+ for "employees" sugested name). Name will be then built based
+ on this caption using KexiUtils::string2Identifier().
+
+ This method is used before creating new object.
+ \return newly created part item or NULL on any error. */
+ KexiPart::Item* createPartItem(KexiPart::Info *info,
+ const QString& suggestedCaption = QString::null );
+
+ //! Added for convenience.
+ KexiPart::Item* createPartItem(KexiPart::Part *part,
+ const QString& suggestedCaption = QString::null);
+
+ /*! Adds item \a item after it is succesfully stored as an instance of part
+ pointed by \a info. Also clears 'neverSaved' flag if \a item.
+ Used by KexiDialogBase::storeNewData().
+ @internal */
+ void addStoredItem(KexiPart::Info *info, KexiPart::Item *item);
+
+ /*! removes \a item from internal dictionaries. The item is destroyed
+ after successful removal.
+ Used to delete an unstored part item previusly created with createPartItem(). */
+ void deleteUnstoredItem(KexiPart::Item *item);
+
+#if 0 //remove?
+ /*! Creates object using data provided by \a dlg dialog.
+ Dialog's \a item (KexiDialog::partItem()) must not be stored
+ (KexiPart::Item::neverStored()==false) and created
+ by KexiProject::createPartItem().
+ Identifier of the item will be updated to a final value
+ (stored in the backend), because previously there was temporary one set.
+ \return true for successfully created object or false on any error. */
+ bool createObject(KexiDialogBase *dlg);
+#endif
+
+ KexiDB::Parser* sqlParser();
+
+ /*! Shows dialog for creating new blank project,
+ ans creates one. Dialog is not shown if option for automatic creation
+ is checked or Kexi::startupHandler().projectData() was provided from command line.
+ \a cancelled is set to true if creation has been cancelled (e.g. user answered
+ no when asked for database overwriting, etc.
+ \return true if database was created, false on error or when cancel was pressed */
+ static KexiProject* createBlankProject(bool &cancelled, KexiProjectData* data,
+ KexiDB::MessageHandler* handler = 0);
+
+ /*! Drops project described by \a data. \return true on success.
+ Use with care: Any KexiProject objects allocated for this project will become invalid! */
+ static tristate dropProject(KexiProjectData* data,
+ KexiDB::MessageHandler* handler, bool dontAsk = false);
+
+ /*! @see KexiDB::Connection::setQuerySchemaObsolete( const QString& queryName ) */
+// void setQuerySchemaObsolete( const QString& queryName );
+
+// /** used to emit objectCreated() signal */
+// void emitObjectCreated(const QCString &mime, const QCString& name) { emit objectCreated(mime, name); }
+// void emitTableCreated(KexiDB::TableSchema& schema) { emit tableCreated(schema); }
+
+ protected:
+ /*! Creates connection using project data.
+ The connection will be readonly if data()->isReadOnly().
+ \return true on success, otherwise false and appropriate error is set. */
+ bool createConnection();
+
+ bool closeConnection();
+
+ bool initProject();
+
+ //! Used in open() and open(bool&).
+ tristate openInternal(bool *incompatibleWithKexi);
+
+ /*! Kexi itself can define a number of internal database objects (mostly data structures),
+ usually tables for it's own purposes.
+ Even while at KexiDB library level, such "system" tables, like "kexi__objects", "kexi__objectdata"
+ are created automatically on database project creation, this is not enough: there are objects
+ needed specifically for Kexi but not for other applications utilizing KexiDB library.
+ Example table created here for now is "kexi__blobs".
+
+ This method is called on create() and open(): creates necessary objects
+ if they are not yet existing. This especially allows to create to create these objects
+ (on open) within a project made with previous Kexi version not supporting
+ all currently defined structurtes. We're trying to be here as much backward compatible as possible.
+ For this purpose, here's the complete list of currently created objects:
+ - "kexi__blobs" - a table containing BLOBs data that can be accessed globally at Kexi projects
+ from within any database-aware view (table views, forms, reports, etc.)
+
+ @param insideTransaction Embed entire creation process inside a transaction
+
+ \return true on successful object's creation. Objects are created only once, they are not overwritten.
+ */
+ bool createInternalStructures(bool insideTransaction);
+
+ /*! \return Kexi part for \a item. */
+ KexiPart::Part *findPartFor(KexiPart::Item& item);
+
+ signals:
+ /** signal emitted on error */
+ void error(const QString &title, KexiDB::Object *obj);
+
+ /** signal emitted on error (not KexiDB-related) */
+ void error(const QString &msg, const QString &desc);
+
+ /** New \a item has been stored. */
+ void newItemStored(KexiPart::Item& item);
+
+ /** instance pointed by \a item is removed */
+ void itemRemoved(const KexiPart::Item &item);
+
+ /** instance pointed by \a item is renamed */
+ void itemRenamed(const KexiPart::Item &item, const QCString& oldName);
+
+// /** new table \a schema created */
+// void tableCreated(KexiDB::TableSchema& schema);
+// /** New object of mimetype \a mime and \a name has been created. */
+// void objectCreated(const QCString &mime, const QCString& name);
+
+ protected:
+ /*! Checks whether the project's connection is read-only.
+ If so, error message is set and false is returned. */
+ bool checkWritable();
+
+ class Private;
+ Private *d;
+
+ friend class KexiMainWindowImpl;
+};
+
+
+#endif
diff --git a/kexi/core/kexiprojectconnectiondata.cpp b/kexi/core/kexiprojectconnectiondata.cpp
new file mode 100644
index 000000000..6a73342f2
--- /dev/null
+++ b/kexi/core/kexiprojectconnectiondata.cpp
@@ -0,0 +1,152 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <qdom.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qregexp.h>
+
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <kdebug.h>
+#include <kio/netaccess.h>
+#include <kurl.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <kexidb/connectiondata.h>
+#include <kexidb/drivermanager.h>
+#include "kexiprojectconnectiondata.h"
+
+KexiProjectConnectionData::KexiProjectConnectionData(): KexiDB::ConnectionData()
+{
+}
+
+KexiProjectConnectionData::KexiProjectConnectionData(const QString& driverName, const QString& databaseName, const QString &host,
+ unsigned short int rport, const QString& user, const QString &pass, const QString& file):KexiDB::ConnectionData()
+{
+ m_driverName=driverName;
+ m_databaseName=databaseName;
+ hostName=host;
+ port=rport;
+ userName=user;
+ password=pass;
+ setFileName(file);
+}
+
+KexiProjectConnectionData::KexiProjectConnectionData(const QString &driverName, const QString &fileName)
+ : KexiDB::ConnectionData()
+{
+ m_driverName=driverName;
+ setFileName(fileName);
+}
+
+const QString &
+KexiProjectConnectionData::generateTmpName()
+{
+ return QString::null;
+}
+
+KexiProjectConnectionData*
+KexiProjectConnectionData::loadInfo(QDomElement &rootElement)
+{
+ QDomElement engineElement = rootElement.namedItem("engine").toElement();
+ QDomElement hostElement = rootElement.namedItem("host").toElement();
+ QDomElement portElement = rootElement.namedItem("port").toElement();
+ QDomElement nameElement = rootElement.namedItem("name").toElement();
+ QDomElement userElement = rootElement.namedItem("user").toElement();
+ QDomElement passElement = rootElement.namedItem("password").toElement();
+ QDomElement persElement = rootElement.namedItem("persistant").toElement();
+ QDomElement encodingElement = rootElement.namedItem("encoding").toElement();
+
+ KexiProjectConnectionData *tmp=new KexiProjectConnectionData(
+ engineElement.text(), nameElement.text(),hostElement.text(),portElement.text().toInt(),
+ userElement.text(),passElement.text(),"");
+
+ return tmp;
+}
+
+void KexiProjectConnectionData::setDriverName(const QString &driverName) {
+ m_driverName=driverName;
+}
+
+void KexiProjectConnectionData::setDatabaseName(const QString &databaseName) {
+ m_databaseName=databaseName;
+}
+
+QString KexiProjectConnectionData::driverName() const {
+ return m_driverName;
+}
+
+QString KexiProjectConnectionData::databaseName() const {
+ return m_databaseName;
+}
+
+
+void
+KexiProjectConnectionData::writeInfo(QDomDocument &domDoc)
+{
+ QDomElement connectionElement = domDoc.createElement("KexiDBConnection");
+ domDoc.documentElement().appendChild(connectionElement);
+
+//DB ENGINE
+ QDomElement engineElement = domDoc.createElement("engine");
+ connectionElement.appendChild(engineElement);
+
+ QDomText tEngine = domDoc.createTextNode(m_driverName);
+ engineElement.appendChild(tEngine);
+
+//HOST
+ QDomElement hostElement = domDoc.createElement("host");
+ connectionElement.appendChild(hostElement);
+
+ QDomText tHost = domDoc.createTextNode(hostName);
+ hostElement.appendChild(tHost);
+
+//DATABASE NAME
+ QDomElement nameElement = domDoc.createElement("name");
+ connectionElement.appendChild(nameElement);
+
+ QDomText tName = domDoc.createTextNode(m_databaseName);
+ nameElement.appendChild(tName);
+
+//USER
+ QDomElement userElement = domDoc.createElement("user");
+ connectionElement.appendChild(userElement);
+
+ QDomText tUser = domDoc.createTextNode(userName);
+ userElement.appendChild(tUser);
+
+//PASSWORD STUFF
+ QDomElement passElement = domDoc.createElement("password");
+ connectionElement.appendChild(passElement);
+
+ QDomText tPass=domDoc.createTextNode(password);
+ passElement.appendChild(tPass);
+
+}
+
+
+
+KexiProjectConnectionData::~KexiProjectConnectionData()
+{
+}
diff --git a/kexi/core/kexiprojectconnectiondata.h b/kexi/core/kexiprojectconnectiondata.h
new file mode 100644
index 000000000..04ff40ff4
--- /dev/null
+++ b/kexi/core/kexiprojectconnectiondata.h
@@ -0,0 +1,69 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPROJECTCONNECTIONDATA_H
+#define KEXIPROJECTCONNECTIONDATA_H
+
+#include <kexidb/connectiondata.h>
+
+class QDomElement;
+class QDomDocument;
+class KoStore;
+/**
+ * This class aims to provide
+ * methods to store/load database settings
+ * especially for file based engines. Extends KexiDB::ConnectionData with
+ * additional information (selected driver name and database name)
+ * that allows fully-automatic reconnect eg. on next application startup.
+ */
+
+class KEXICORE_EXPORT KexiProjectConnectionData:public KexiDB::ConnectionData
+{
+ public:
+
+ KexiProjectConnectionData();
+
+ KexiProjectConnectionData(const QString& driverName, const QString& databaseName, const QString &hostName, unsigned short int port,
+ const QString& userName, const QString &password, const QString& fileName);
+
+ /**
+ * connect to a embedded database
+ */
+ KexiProjectConnectionData(const QString &driverName, const QString &fileName=QString::null);
+
+ ~KexiProjectConnectionData();
+
+ static const QString &generateTmpName();
+
+ static KexiProjectConnectionData* loadInfo(QDomElement &e);
+ void writeInfo(QDomDocument &doc);
+
+ void setDriverName(const QString &driverName);
+ void setDatabaseName(const QString &databaseName);
+
+ QString driverName() const;
+ QString databaseName() const;
+
+ private:
+ QString m_driverName;
+ QString m_databaseName;
+
+};
+
+#endif
diff --git a/kexi/core/kexiprojectdata.cpp b/kexi/core/kexiprojectdata.cpp
new file mode 100644
index 000000000..68966a114
--- /dev/null
+++ b/kexi/core/kexiprojectdata.cpp
@@ -0,0 +1,176 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <qdom.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qregexp.h>
+
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <kdebug.h>
+#include <kio/netaccess.h>
+#include <kurl.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <kexidb/drivermanager.h>
+#include "kexiprojectdata.h"
+
+
+//! @internal
+class KexiProjectDataPrivate
+{
+public:
+ KexiProjectDataPrivate()
+ : userMode(false)
+ , readOnly(false)
+ {}
+
+ KexiDB::ConnectionData connData;
+ QDateTime lastOpened;
+ bool userMode : 1;
+ bool readOnly : 1;
+};
+
+//---------------------------------------
+
+KexiProjectData::KexiProjectData()
+ : QObject(0, "KexiProjectData")
+ , KexiDB::SchemaData()
+ , formatVersion(0)
+ , d( new KexiProjectDataPrivate() )
+{
+}
+
+KexiProjectData::KexiProjectData(
+ const KexiDB::ConnectionData &cdata, const QString& dbname, const QString& caption )
+ : QObject(0, "KexiProjectData")
+ , KexiDB::SchemaData()
+ , formatVersion(0)
+ , d( new KexiProjectDataPrivate() )
+{
+ d->connData = cdata;
+ setDatabaseName(dbname);
+ setCaption(caption);
+}
+
+KexiProjectData::KexiProjectData( const KexiProjectData& pdata )
+ : QObject(0, "KexiProjectData"), KexiDB::SchemaData()
+ , d( 0 )
+{
+ *this = pdata;
+ autoopenObjects = pdata.autoopenObjects;
+/*
+ d->connData = *pdata.connectionData();
+ setDatabaseName(pdata.databaseName());
+ setCaption(pdata.caption());*/
+}
+
+KexiProjectData::~KexiProjectData()
+{
+ delete d;
+}
+
+KexiProjectData& KexiProjectData::operator=(const KexiProjectData& pdata)
+{
+ delete d; //this is old
+ static_cast<KexiDB::SchemaData&>(*this) = static_cast<const KexiDB::SchemaData&>(pdata);
+ //deep copy
+ d = new KexiProjectDataPrivate();
+ *d = *pdata.d;
+// d->connData = *pdata.constConnectionData();
+// setDatabaseName(pdata.databaseName());
+// setCaption(pdata.caption());
+// setDescription(pdata.description());
+ return *this;
+}
+
+KexiDB::ConnectionData* KexiProjectData::connectionData()
+{
+ return &d->connData;
+}
+
+const KexiDB::ConnectionData* KexiProjectData::constConnectionData() const
+{
+ return &d->connData;
+}
+
+QString KexiProjectData::databaseName() const
+{
+ return KexiDB::SchemaData::name();
+}
+
+void KexiProjectData::setDatabaseName(const QString& dbName)
+{
+ KexiDB::SchemaData::setName(dbName);
+}
+
+bool KexiProjectData::userMode() const
+{
+ return d->userMode;
+}
+
+QDateTime KexiProjectData::lastOpened() const
+{
+ return d->lastOpened;
+}
+
+void KexiProjectData::setLastOpened(const QDateTime& lastOpened)
+{
+ d->lastOpened=lastOpened;
+
+}
+QString KexiProjectData::description() const
+{
+ return KexiDB::SchemaData::description();
+}
+
+void KexiProjectData::setDescription(const QString& desc)
+{
+ return KexiDB::SchemaData::setDescription(desc);
+}
+
+QString KexiProjectData::infoString(bool nobr) const
+{
+ if (constConnectionData()->fileName().isEmpty()) {
+ //server-based
+ return QString(nobr ? "<nobr>" : "") + QString("\"%1\"").arg(databaseName()) + (nobr ? "</nobr>" : "")
+ + (nobr ? " <nobr>" : " ") + i18n("database connection", "(connection %1)")
+ .arg(constConnectionData()->serverInfoString()) + (nobr ? "</nobr>" : "");
+ }
+ //file-based
+ return QString(nobr ? "<nobr>" : "")
+ + QString("\"%1\"").arg(constConnectionData()->fileName()) + (nobr ? "</nobr>" : "");
+}
+
+void KexiProjectData::setReadOnly(bool set)
+{
+ d->readOnly = set;
+}
+
+bool KexiProjectData::isReadOnly() const
+{
+ return d->readOnly;
+}
+
diff --git a/kexi/core/kexiprojectdata.h b/kexi/core/kexiprojectdata.h
new file mode 100644
index 000000000..b4fc99d3f
--- /dev/null
+++ b/kexi/core/kexiprojectdata.h
@@ -0,0 +1,108 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPROJECTDATA_H
+#define KEXIPROJECTDATA_H
+
+#include <kexidb/connectiondata.h>
+#include <kexidb/schemadata.h>
+
+#include <qdatetime.h>
+
+class KexiProjectDataPrivate;
+
+/** @short Kexi project core data member
+
+ Contains:
+ - project name
+ - database name
+ - connection data
+ - date and time of last opening
+*/
+class KEXICORE_EXPORT KexiProjectData : public QObject, public KexiDB::SchemaData
+{
+ public:
+ typedef QPtrList<KexiProjectData> List;
+ typedef QMap<QCString,QString> ObjectInfo;
+
+ KexiProjectData();
+
+ KexiProjectData( const KexiDB::ConnectionData &cdata,
+ const QString& dbname = QString::null, const QString& caption = QString::null );
+
+ /*! Constructs a copy of \a pdata */
+ KexiProjectData( const KexiProjectData& pdata );
+
+ ~KexiProjectData();
+
+ KexiProjectData& operator=(const KexiProjectData& pdata);
+
+ /*! \return true if there is the User Mode set in internal
+ project settings. */
+ bool userMode() const;
+
+ KexiDB::ConnectionData* connectionData();
+
+ const KexiDB::ConnectionData* constConnectionData() const;
+
+ /*! \return database name.
+ In fact, this is the same as KexiDB::SchemaData::name() */
+ QString databaseName() const;
+ void setDatabaseName(const QString& dbName);
+
+ /*! \return user-visible string better describing the project than just databaseName().
+ For server-based projects returns i18n'd string:
+ "<project name>" (connection: user\@server:port).
+ For file-based projects returns project's filename.
+ If \a nobr is true, \<nobr\> tags are added around '(connection: user\@server:port)'
+ (useful for displaying in message boxes). */
+ QString infoString(bool nobr = true) const;
+
+ QDateTime lastOpened() const;
+ void setLastOpened(const QDateTime& lastOpened);
+
+ QString description() const;
+ void setDescription(const QString& desc);
+
+ /*! If \a set is true, sets readonly flag for this data, so any connection opened for the project will
+ be readonly. Change this flag before using this data in KexiProject instance,
+ otherwise you will need to reopen the project. */
+ void setReadOnly(bool set);
+
+ /*! \return readonly flag. False by default.
+ @see setReadOnly() */
+ bool isReadOnly() const;
+
+ /*! objects to open on startup (come from command line "-open" option)
+ It's public for convenience */
+ QValueList<ObjectInfo> autoopenObjects;
+
+ /*! @internal
+ Format version used when saving the data to a shortcut file.
+ This is set to 0 by default what means KexiDBShortcutFile_version should be used on saving.
+ If KexiDBShortcutFile was used to create this KexiProjectData object,
+ the version information is be retrieved from the file. */
+ uint formatVersion;
+
+ private:
+ KexiProjectDataPrivate *d;
+};
+
+#endif
diff --git a/kexi/core/kexiprojectset.cpp b/kexi/core/kexiprojectset.cpp
new file mode 100644
index 000000000..51e804dd1
--- /dev/null
+++ b/kexi/core/kexiprojectset.cpp
@@ -0,0 +1,112 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexiprojectset.h"
+#include "kexi.h"
+
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/msghandler.h>
+
+#include <kdebug.h>
+
+//#define ERRMSG(a1, a2)
+// { if (m_msgHandler) m_msgHandler->showErrorMessage(a1, a2); }
+
+//! @internal
+class KexiProjectSetPrivate
+{
+public:
+ KexiProjectSetPrivate()
+ {
+// list.setAutoDelete(true);
+ }
+ KexiProjectData::List list;
+// KexiDB::MessageHandler* msgHandler;
+};
+
+KexiProjectSet::KexiProjectSet(KexiDB::MessageHandler* handler)
+: KexiDB::Object(handler)
+, d(new KexiProjectSetPrivate())
+{
+}
+
+KexiProjectSet::KexiProjectSet(KexiDB::ConnectionData &conndata,
+ KexiDB::MessageHandler* handler)
+: KexiDB::Object(handler)
+, d(new KexiProjectSetPrivate())
+{
+ KexiDB::Driver *drv = Kexi::driverManager().driver(conndata.driverName);
+ if (!drv) {
+ setError(&Kexi::driverManager());
+ return;
+ }
+ KexiDB::Connection *conn = drv->createConnection(conndata);
+ if (!conn) {
+ setError(drv);
+ return;
+ }
+ if (!conn->connect()) {
+ setError(conn);
+ delete conn;
+ return;
+ }
+ QStringList dbnames = conn->databaseNames(false/*skip system*/);
+// kexidbg << dbnames.count() << endl;
+ if (conn->error()) {
+ setError(conn);
+ delete conn;
+ return;
+ }
+ delete conn;
+ conn = 0;
+ for (QStringList::ConstIterator it = dbnames.constBegin(); it!=dbnames.constEnd(); ++it) {
+ // project's caption is just the same as database name - nothing better is available
+ KexiProjectData *pdata = new KexiProjectData(conndata, *it, *it);
+ d->list.append( pdata );
+ }
+ clearError();
+}
+
+
+KexiProjectSet::~KexiProjectSet()
+{
+ delete d;
+}
+
+void KexiProjectSet::addProjectData(KexiProjectData *data)
+{
+ d->list.append(data);
+}
+
+KexiProjectData::List KexiProjectSet::list() const
+{
+ return d->list;
+}
+
+KexiProjectData* KexiProjectSet::findProject(const QString &dbName) const
+{
+ const QString _dbName = dbName.lower();
+ QPtrListIterator<KexiProjectData> it( d->list );
+ for (;it.current();++it) {
+ if (it.current()->databaseName().lower()==_dbName)
+ return it.current();
+ }
+ return 0;
+}
diff --git a/kexi/core/kexiprojectset.h b/kexi/core/kexiprojectset.h
new file mode 100644
index 000000000..af2517226
--- /dev/null
+++ b/kexi/core/kexiprojectset.h
@@ -0,0 +1,67 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIPROJECTSET_H
+#define KEXIPROJECTSET_H
+
+#include <kexidb/connectiondata.h>
+#include <kexidb/object.h>
+
+#include "kexiprojectdata.h"
+
+class KexiProjectSetPrivate;
+namespace KexiDB {
+ class MessageHandler;
+}
+
+/*! @short Stores information about multiple kexi project-data items */
+class KEXICORE_EXPORT KexiProjectSet : public KexiDB::Object
+{
+ public:
+
+ /*! Creates empty project set. Use addProjectData to add a project data.
+ \a handler can be provided to receive error messages. */
+ KexiProjectSet(KexiDB::MessageHandler* handler = 0);
+
+ /*! Creates project set filled with all projects found using \a conndata.
+ There may be error during project list retrieving - use appropriate
+ KexiDB::Object::error(), and similar methods to get error message.
+ \a handler can be provided to receive error messages. */
+ KexiProjectSet(KexiDB::ConnectionData &conndata,
+ KexiDB::MessageHandler* handler = 0);
+
+ ~KexiProjectSet();
+
+ /*! Adds \a data as project data.
+ \a data will be owned by this object. */
+ void addProjectData(KexiProjectData *data);
+
+ //! \return list object
+ KexiProjectData::List list() const;
+
+ //! Case insensitive lookup.
+ //! \return project data for databased \a dbName or NULL if not found
+ KexiProjectData* findProject(const QString &dbName) const;
+
+ private:
+ KexiProjectSetPrivate *d;
+};
+
+#endif // KEXINEWDBCONNDIALOG_H
+
diff --git a/kexi/core/kexisearchandreplaceiface.cpp b/kexi/core/kexisearchandreplaceiface.cpp
new file mode 100644
index 000000000..0c533d938
--- /dev/null
+++ b/kexi/core/kexisearchandreplaceiface.cpp
@@ -0,0 +1,41 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexisearchandreplaceiface.h"
+
+KexiSearchAndReplaceViewInterface::KexiSearchAndReplaceViewInterface()
+{
+}
+
+KexiSearchAndReplaceViewInterface::~KexiSearchAndReplaceViewInterface()
+{
+}
+
+//-----------------------------------------------------------------------
+
+KexiSearchAndReplaceViewInterface::Options::Options()
+ : columnNumber(AllColumns)
+ , textMatching(MatchAnyPartOfField)
+ , searchDirection(DefaultSearchDirection)
+ , caseSensitive(false)
+ , wholeWordsOnly(false)
+ , promptOnReplace(true)
+{
+}
+
diff --git a/kexi/core/kexisearchandreplaceiface.h b/kexi/core/kexisearchandreplaceiface.h
new file mode 100644
index 000000000..f17d83587
--- /dev/null
+++ b/kexi/core/kexisearchandreplaceiface.h
@@ -0,0 +1,106 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiSearchAndReplaceViewInterface_H
+#define KexiSearchAndReplaceViewInterface_H
+
+#include <kexiutils/tristate.h>
+#include <qstring.h>
+class QVariant;
+class QStringList;
+
+//! @short An interface used by Kexi views (KexiViewBase) supporting search/replace features
+class KEXICORE_EXPORT KexiSearchAndReplaceViewInterface
+{
+ public:
+ KexiSearchAndReplaceViewInterface();
+ virtual ~KexiSearchAndReplaceViewInterface();
+
+ //! @short Specifies options for find and replace operations.
+ /*! A GUI for setting these options is provided by KexiFindDialog class. */
+ class KEXICORE_EXPORT Options {
+ public:
+ Options();
+
+ //! Special values for columnNumber.
+ enum SpecialLookInValue {
+ AllColumns = -1, //!< "all columns" (the default)
+ CurrentColumn = -2 //!< "current column"
+ };
+ //! Column number to look in, AllColumns means "all columns" (the default)
+ //! and CurrentColumn means "current column".
+ int columnNumber;
+
+ //! Specifies possible options for text matching
+ enum TextMatching {
+ MatchAnyPartOfField = 0, //!< Matched text can be any part of field (the default)
+ MatchWholeField = 1, //!< Matched text must be the whole field
+ MatchStartOfField = 2 //!< Matched text must be at the start of field
+ };
+
+ //! Specifies possible options for text matching
+ TextMatching textMatching;
+
+ //! Specifies search direction
+ enum SearchDirection {
+ SearchUp = 0, //!< Search up (previous) from the current position
+ SearchDown = 1, //!< Search down (next) from the current position (the default)
+ SearchAllRows = 2, //!< Search from the first to the last row
+ DefaultSearchDirection = SearchDown //! Used to mark the default
+ };
+
+ //! Specifies search direction
+ SearchDirection searchDirection;
+
+ //! True for searching is case-sensitive (false by default)
+ bool caseSensitive : 1;
+
+ //! True for searching for whole words only (false by default)
+ bool wholeWordsOnly : 1;
+
+ //! True if question should be displayed before every replacement made (true by default)
+ bool promptOnReplace : 1;
+ };
+
+ /*! Sets up data for find/replace dialog, based on view's data model.
+ \a columnNames should contain column name, \a columnCaptions should contain column captions,
+ and \a currentColumnName should beset to current column's name.
+ Implementation should set up values and return true if find/replace dialog should be filled. */
+ virtual bool setupFindAndReplace(QStringList& columnNames, QStringList& columnCaptions,
+ QString& currentColumnName) = 0;
+
+ /*! Finds \a valueToFind within the view.
+ \a options are used to control the process. Selection is moved to found value.
+ \return true if value has been found, false if value has not been found,
+ and cancelled if there is nothing to find or there is no data to search in.
+ If \a next is true, "find next" is performed, else "find previous" is performed. */
+ virtual tristate find(const QVariant& valueToFind,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool next) = 0;
+
+ /*! Finds \a valueToFind within the view and replaces with \a replacement
+ \a options are used to control the process.
+ \return true if value has been found and replaced, false if value
+ has not been found and replaced, and cancelled if there is nothing
+ to find or there is no data to search in or the data is read only.
+ If \a replaceAll is true, all found values are replaced. */
+ virtual tristate findNextAndReplace(const QVariant& valueToFind, const QVariant& replacement,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll) = 0;
+};
+
+#endif
diff --git a/kexi/core/kexisharedactionhost.cpp b/kexi/core/kexisharedactionhost.cpp
new file mode 100644
index 000000000..11ab94dbc
--- /dev/null
+++ b/kexi/core/kexisharedactionhost.cpp
@@ -0,0 +1,291 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexisharedactionhost.h"
+#include "kexisharedactionhost_p.h"
+#include "kexiactionproxy.h"
+#include "kexidialogbase.h"
+
+#include <kexiutils/utils.h>
+
+#include <kiconloader.h>
+#include <kguiitem.h>
+#include <kdebug.h>
+
+KexiSharedActionHostPrivate::KexiSharedActionHostPrivate(KexiSharedActionHost *h)
+: QObject(0,"KexiSharedActionHostPrivate")
+, actionProxies(401)
+, actionMapper( this )
+, volatileActions(401)
+, enablers(401, false)
+, host(h)
+{
+ volatileActions.setAutoDelete(true);
+ connect(&actionMapper, SIGNAL(mapped(const QString &)), this, SLOT(slotAction(const QString &)));
+}
+
+void KexiSharedActionHostPrivate::slotAction(const QString& act_id)
+{
+ QWidget *w = host->focusWindow(); //focusWidget();
+// while (w && !w->inherits("KexiDialogBase") && !w->inherits("KexiDockBase"))
+// w = w->parentWidget();
+
+ KexiActionProxy *proxy = w ? actionProxies[ w ] : 0;
+
+ if (!proxy || !proxy->activateSharedAction(act_id.latin1())) {
+ //also try to find previous enabler
+ w = enablers[act_id.latin1()];
+ if (!w)
+ return;
+ proxy = actionProxies[ w ];
+ if (!proxy)
+ return;
+ proxy->activateSharedAction(act_id.latin1());
+ }
+}
+
+//--------------------------------------------------
+
+//! dummy host to avoid crashes
+KexiSharedActionHost KexiSharedActionHost_dummy = KexiSharedActionHost(0);
+
+//! default host
+KexiSharedActionHost* KexiSharedActionHost_defaultHost = &KexiSharedActionHost_dummy;
+
+KexiSharedActionHost& KexiSharedActionHost::defaultHost()
+{
+ return *KexiSharedActionHost_defaultHost;
+}
+
+void KexiSharedActionHost::setAsDefaultHost()
+{
+ KexiSharedActionHost_defaultHost = this;
+}
+
+//--------------------------------------------------
+
+KexiSharedActionHost::KexiSharedActionHost(KMainWindow* mainWin)
+: d( new KexiSharedActionHostPrivate(this) )
+{
+ d->mainWin = mainWin;
+}
+
+KexiSharedActionHost::~KexiSharedActionHost()
+{
+ if (KexiSharedActionHost_defaultHost == this) {
+ //default host is destroyed! - restore dummy
+ KexiSharedActionHost_defaultHost = &KexiSharedActionHost_dummy;
+ }
+ delete d;
+ d=0; //! to let takeActionProxyFor() know that we are almost dead :)
+}
+
+void KexiSharedActionHost::setActionAvailable(const char *action_name, bool avail)
+{
+ KAction *act = d->mainWin->actionCollection()->action(action_name);
+ if (act) {
+ act->setEnabled(avail);
+ }
+}
+
+void KexiSharedActionHost::updateActionAvailable(const char *action_name, bool avail, QObject *obj)
+{
+/*test if (qstrcmp(action_name, "tablepart_toggle_pkey")==0) {
+ kdDebug() << "tablepart_toggle_pkey" << endl;
+ }*/
+ if (!d)
+ return; //sanity
+ QWidget *fw = d->mainWin->focusWidget();
+ while (fw && obj!=fw)
+ fw = fw->parentWidget();
+ if (!fw)
+ return;
+
+ setActionAvailable(action_name, avail);
+ if (avail) {
+ d->enablers.replace(action_name, fw);
+ }
+ else {
+ d->enablers.take(action_name);
+ }
+}
+
+void KexiSharedActionHost::plugActionProxy(KexiActionProxy *proxy)
+{
+// kdDebug() << "KexiSharedActionHost::plugActionProxy():" << proxy->receiver()->name() << endl;
+ d->actionProxies.insert( proxy->receiver(), proxy );
+}
+
+KMainWindow* KexiSharedActionHost::mainWindow() const
+{
+ return d->mainWin;
+}
+
+void KexiSharedActionHost::invalidateSharedActions(QObject *o)
+{
+ if (!d)
+ return;
+ bool insideDialogBase = o && (o->inherits("KexiDialogBase") || 0!=KexiUtils::findParent<KexiDialogBase>(o, "KexiDialogBase"));
+
+ KexiActionProxy *p = o ? d->actionProxies[ o ] : 0;
+ for (KActionPtrList::ConstIterator it=d->sharedActions.constBegin(); it!=d->sharedActions.constEnd(); ++it) {
+// setActionAvailable((*it)->name(),p && p->isAvailable((*it)->name()));
+ KAction *a = *it;
+ if (!insideDialogBase && d->mainWin->actionCollection()!=a->parentCollection()) {
+ //o is not KexiDialogBase or its child:
+ // only invalidate action if it comes from mainwindow's KActionCollection
+ // (thus part-actions are untouched when the focus is e.g. in the Property Editor)
+ continue;
+ }
+ const bool avail = p && p->isAvailable(a->name());
+ KexiVolatileActionData *va = d->volatileActions[ a ];
+ if (va != 0) {
+ if (p && p->isSupported(a->name())) {
+ QPtrList<KAction> actions_list;
+ actions_list.append( a );
+ if (!va->plugged) {
+ va->plugged=true;
+ // d->mainWin->unplugActionList( a->name() );
+ d->mainWin->plugActionList( a->name(), actions_list );
+ }
+ }
+ else {
+ if (va->plugged) {
+ va->plugged=false;
+ d->mainWin->unplugActionList( a->name() );
+ }
+ }
+ }
+// a->setEnabled(p && p->isAvailable(a->name()));
+ a->setEnabled(avail);
+// kdDebug() << "Action " << a->name() << (avail ? " enabled." : " disabled.") << endl;
+ }
+}
+
+KexiActionProxy* KexiSharedActionHost::actionProxyFor(QObject *o) const
+{
+ return d->actionProxies[ o ];
+}
+
+KexiActionProxy* KexiSharedActionHost::takeActionProxyFor(QObject *o)
+{
+ if (d)
+ return d->actionProxies.take( o );
+ return 0;
+}
+
+bool KexiSharedActionHost::acceptsSharedActions(QObject *)
+{
+ return false;
+}
+
+QWidget* KexiSharedActionHost::focusWindow()
+{
+ QWidget *fw;
+ if (dynamic_cast<KMdiMainFrm*>(d->mainWin)) {
+ fw = dynamic_cast<KMdiMainFrm*>(d->mainWin)->activeWindow();
+ }
+ else {
+ QWidget *aw = qApp->activeWindow();
+ if (!aw)
+ aw = d->mainWin;
+ fw = aw->focusWidget();
+ }
+ while (fw && !acceptsSharedActions(fw))
+ fw = fw->parentWidget();
+ return fw;
+}
+
+KAction* KexiSharedActionHost::createSharedActionInternal( KAction *action )
+{
+ QObject::connect(action,SIGNAL(activated()), &d->actionMapper, SLOT(map()));
+ d->actionMapper.setMapping(action, action->name());
+ d->sharedActions.append( action );
+ return action;
+}
+
+KActionPtrList KexiSharedActionHost::sharedActions() const
+{
+ return d->sharedActions;
+}
+
+/*class KexiAction : public KAction
+{
+ public:
+ KexiAction(const QString &text, const QIconSet &pix,
+ const KShortcut &cut, const QObject *receiver,
+ const char *slot, KActionCollection *parent, const char *name)
+ : KAction(text,pix,cut,receiver,slot,parent,name)
+ {
+ }
+
+ QPtrDict<QWidget> unplugged;
+};*/
+
+KAction* KexiSharedActionHost::createSharedAction(const QString &text, const QString &pix_name,
+ const KShortcut &cut, const char *name, KActionCollection* col, const char *subclassName)
+{
+ if (subclassName==0)
+ return createSharedActionInternal(
+ new KAction(text, pix_name,
+ cut, 0/*receiver*/, 0/*slot*/, col ? col : d->mainWin->actionCollection(), name)
+ );
+ else if (qstricmp(subclassName,"KToggleAction")==0)
+ return createSharedActionInternal(
+ new KToggleAction(text, pix_name,
+ cut, 0/*receiver*/, 0/*slot*/, col ? col : d->mainWin->actionCollection(), name)
+ );
+ else if (qstricmp(subclassName,"KActionMenu")==0)
+ return createSharedActionInternal(
+ new KActionMenu(text, pix_name, col ? col : d->mainWin->actionCollection(), name)
+ );
+//TODO: more KAction subclasses
+
+ return 0;
+}
+
+KAction* KexiSharedActionHost::createSharedAction( KStdAction::StdAction id, const char *name,
+ KActionCollection* col)
+{
+ return createSharedActionInternal(
+ KStdAction::create( id, name, 0/*receiver*/, 0/*slot*/, col ? col : d->mainWin->actionCollection() )
+ );
+}
+
+KAction* KexiSharedActionHost::createSharedAction(const KGuiItem& guiItem, const KShortcut &cut,
+ const char *name, KActionCollection* col)
+{
+ return createSharedActionInternal(
+ new KAction(guiItem, cut, 0/*receiver*/, 0/*slot*/,
+ col ? col : d->mainWin->actionCollection(), name));
+}
+
+void KexiSharedActionHost::setActionVolatile( KAction *a, bool set )
+{
+ if (!set) {
+ d->volatileActions.remove( a );
+ return;
+ }
+ if (d->volatileActions[ a ])
+ return;
+ d->volatileActions.insert( a, new KexiVolatileActionData() );
+}
+
+#include "kexisharedactionhost_p.moc"
+
diff --git a/kexi/core/kexisharedactionhost.h b/kexi/core/kexisharedactionhost.h
new file mode 100644
index 000000000..abb960bfb
--- /dev/null
+++ b/kexi/core/kexisharedactionhost.h
@@ -0,0 +1,165 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISHAREDACTIONHOST_H
+#define KEXISHAREDACTIONHOST_H
+
+#include <qguardedptr.h>
+#include <qasciidict.h>
+#include <qobject.h>
+#include <qpair.h>
+
+#include <kstdaction.h>
+#include <kaction.h>
+
+class KShortcut;
+class KGuiItem;
+class KMainWindow;
+class KexiActionProxy;
+class KexiSharedActionHostPrivate;
+
+namespace KexiPart {
+ class Part;
+}
+
+//! Acts as application-wide host that offers shared actions.
+/*!
+ You can inherit this class together with KMainWindow (or any KMainWindow).
+ Call setAsDefaultHost() to make the host default for all shared actions that have
+ not explicitly specified host.
+
+ For example how all this is done, see KexiMainWindow implementation.
+
+ \sa KexiActionProxy, KexiMainWindow
+*/
+
+class KEXICORE_EXPORT KexiSharedActionHost
+{
+ public:
+
+ /*! Constructs host for main window \a mainWin. */
+ KexiSharedActionHost(KMainWindow* mainWin);
+
+ virtual ~KexiSharedActionHost();
+
+ /*! \return true if \a w can accept shared actions.
+ This method is used by focusWindow() to look up widgets hierarchy
+ for widget that can accept shared actions.
+ Default implementation always returns false.
+ You can reimplement it e.g. like in KexiMainWindowImpl::acceptsSharedActions():
+ \code
+ return o->inherits("KexiDialogBase") || o->inherits("KexiViewBase");
+ \endcode
+ */
+ virtual bool acceptsSharedActions(QObject *o);
+
+ /*! \return window widget that is currently focused (using QWidget::focusWidget())
+ and matches acceptsSharedActions(). If focused widget does not match,
+ it's parent, grandparent, and so on is checked. If all this fails,
+ or no widget has focus, NULL is returned.
+ Also works if currently focused window is detached (as in KMDI).
+ */
+ QWidget* focusWindow();
+
+ /*! Sets this host as default shared actions host. */
+ void setAsDefaultHost();
+
+ /*! \return default shared actions host, used when no host
+ is explicitly specified for shared actions.
+ There can be exactly one deault shared actions host. */
+ static KexiSharedActionHost& defaultHost();
+
+ /*! \return shared actions list. */
+ KActionPtrList sharedActions() const;
+
+ /*! PROTOTYPE, DO NOT USE YET */
+ void setActionVolatile( KAction *a, bool set );
+
+
+ protected:
+
+ /*! Invalidates all shared actions declared using createSharedAction().
+ Any shared action will be enabled if \a o (typically: a child window or a dock window)
+ has this action plugged _and_ it is available (i.e. enabled).
+ Otherwise the action is disabled.
+
+ If \a o is not KexiDialogBase or its child,
+ actions are only invalidated if these come from mainwindow's KActionCollection
+ (thus part-actions are untouched when the focus is e.g. in the Property Editor.
+
+ Call this method when it is known that some actions need invalidation
+ (e.g. when new window is activated). See how it is used in KexiMainWindowImpl.
+ */
+ virtual void invalidateSharedActions(QObject *o);
+
+ void setActionAvailable(const char *action_name, bool avail);
+
+ /*! Plugs shared actions proxy \a proxy for this host. */
+ void plugActionProxy(KexiActionProxy *proxy);
+
+ /*! Updates availability of action \a action_name for object \a obj.
+ Object is mainly the window. */
+ void updateActionAvailable(const char *action_name, bool avail, QObject *obj);
+
+ /*! \return main window for which this host is defined. */
+ KMainWindow* mainWindow() const;
+
+ /*! Creates shared action using \a text, \a pix_name pixmap, shortcut \a cut,
+ optional \a name. You can pass your own action collection as \a col.
+ If \a col action collection is null, main window's action will be used.
+ Pass desired KAction subclass with \a subclassName (e.g. "KToggleAction") to have
+ that subclass allocated instead just KAction (what is the default).
+ Created action's data is owned by the main window. */
+ KAction* createSharedAction(const QString &text, const QString &pix_name,
+ const KShortcut &cut, const char *name, KActionCollection* col = 0,
+ const char *subclassName = 0);
+
+ /*! Like above - creates shared action, but from standard action identified by \a id.
+ Action's data is owned by the main window. */
+ KAction* createSharedAction( KStdAction::StdAction id, const char *name,
+ KActionCollection* col = 0);
+
+ /*! Creates shared action with name \a name and shortcut \a cut
+ by copying all properties of \a guiItem.
+ If \a col action collection is null, main window's action will be used. */
+ KAction* createSharedAction(const KGuiItem& guiItem, const KShortcut &cut, const char *name,
+ KActionCollection* col = 0);
+
+ /*! \return action proxy for object \a o, or NULL if this object has
+ no plugged shared actions. */
+ KexiActionProxy* actionProxyFor(QObject *o) const;
+
+ /*! Like actionProxyFor(), but takes the proxy from the host completely.
+ This is called by KExiActionProxy on its destruction. */
+ KexiActionProxy* takeActionProxyFor(QObject *o);
+
+ private:
+ /*! Helper function for createSharedAction(). */
+ KAction* createSharedActionInternal( KAction *action );
+
+ KexiSharedActionHostPrivate *d;
+
+ friend class KexiActionProxy;
+ friend class KexiPart::Part;
+ friend class KexiViewBase;
+ friend class KexiDialogBase;
+};
+
+#endif
+
diff --git a/kexi/core/kexisharedactionhost_p.h b/kexi/core/kexisharedactionhost_p.h
new file mode 100644
index 000000000..f56db586a
--- /dev/null
+++ b/kexi/core/kexisharedactionhost_p.h
@@ -0,0 +1,64 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISHAREDACTIONHOST_P_H
+#define KEXISHAREDACTIONHOST_P_H
+
+#include <qobject.h>
+#include <qptrdict.h>
+#include <qsignalmapper.h>
+
+#include <kmainwindow.h>
+
+#include "kexiactionproxy.h"
+
+class KexiSharedActionHost;
+
+class KexiVolatileActionData
+{
+ public:
+ KexiVolatileActionData() { plugged=false; }
+// KAction *kaction;
+ bool plugged : 1;
+};
+
+//! @internal
+class KEXICORE_EXPORT KexiSharedActionHostPrivate : public QObject
+{
+ Q_OBJECT
+
+ public:
+ KexiSharedActionHostPrivate(KexiSharedActionHost *h);
+
+ public slots:
+ void slotAction(const QString& act_id);
+
+ public:
+ QPtrDict<KexiActionProxy> actionProxies;
+ KMainWindow *mainWin;
+ KActionPtrList sharedActions;
+ QSignalMapper actionMapper;
+ QPtrDict<KexiVolatileActionData> volatileActions;
+ QAsciiDict<QWidget> enablers;
+
+ KexiSharedActionHost *host;
+};
+
+#endif
+
diff --git a/kexi/core/kexistartupdata.cpp b/kexi/core/kexistartupdata.cpp
new file mode 100644
index 000000000..301ebdbd6
--- /dev/null
+++ b/kexi/core/kexistartupdata.cpp
@@ -0,0 +1,84 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexistartupdata.h"
+#include "kexi.h"
+
+#include <kexidb/driver.h>
+#include <kexidb/drivermanager.h>
+
+#include <qfileinfo.h>
+#include <qcstring.h>
+
+KexiStartupData::KexiStartupData()
+ : m_projectData(0)
+ , m_action(KexiStartupData::DoNothing)
+ , m_forcedUserMode(false)
+ , m_forcedDesignMode(false)
+ , m_isProjectNavigatorVisible(false)
+// , m_createDB(false)
+// , m_dropDB(false)
+// , m_alsoOpenDB(false)
+{
+}
+
+KexiStartupData::~KexiStartupData()
+{
+}
+
+KexiProjectData *KexiStartupData::projectData() const
+{
+ return m_projectData;
+}
+
+KexiStartupData::Action KexiStartupData::action() const
+{
+ return m_action;
+}
+
+bool KexiStartupData::forcedUserMode() const
+{
+ return m_forcedUserMode;
+}
+
+bool KexiStartupData::forcedDesignMode() const
+{
+ return m_forcedDesignMode;
+}
+
+bool KexiStartupData::isProjectNavigatorVisible() const
+{
+ if (m_forcedUserMode && !m_forcedDesignMode)
+ return m_isProjectNavigatorVisible;
+ return true;
+}
+
+KexiStartupData::Import KexiStartupData::importActionData() const
+{
+ return m_importActionData;
+}
+
+KexiStartupData::Import::Import()
+{
+}
+
+KexiStartupData::Import::operator bool() const
+{
+ return !fileName.isEmpty() && !mimeType.isEmpty();
+}
diff --git a/kexi/core/kexistartupdata.h b/kexi/core/kexistartupdata.h
new file mode 100644
index 000000000..71999bcb6
--- /dev/null
+++ b/kexi/core/kexistartupdata.h
@@ -0,0 +1,90 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_STARTUPDATA_H
+#define KEXI_STARTUPDATA_H
+
+#include <qstring.h>
+
+class KexiProjectData;
+
+//! Startup data used for storing results of startup operations in Kexi.
+//! @see KexiStartupHandler
+class KEXICORE_EXPORT KexiStartupData
+{
+ public:
+ typedef enum Action {
+ DoNothing,
+ CreateBlankProject,
+ CreateFromTemplate,
+ OpenProject,
+ ImportProject,
+ Exit
+ };
+
+ /*! Data required to perform import action.
+ It is set by KexiStartupHandler::detectActionForFile()
+ if a need for project/data importing has been detected. */
+ class KEXICORE_EXPORT Import
+ {
+ public:
+ Import();
+ operator bool() const;
+ QString fileName;
+ QString mimeType;
+ };
+
+ KexiStartupData();
+ virtual ~KexiStartupData();
+
+ virtual bool init() { return true; };
+
+ Action action() const;
+
+ //! \return project data of a project that should be opened (for action()==OpenProject)
+ KexiProjectData *projectData() const;
+
+ //! \return import action's data needed to perform import (for action()==ImportProject)
+ KexiStartupData::Import importActionData() const;
+
+ /*! \return true is the Design Mode is forced for this project.
+ Used on startup (by --design-mode comman line switch). */
+ bool forcedDesignMode() const;
+
+ /*! \return true is the User Mode is forced for this project.
+ Used on startup (by --user-mode comman line switch).
+ By default this is false. */
+ bool forcedUserMode() const;
+
+ /*! \return true if the Project Navigator should be visible even if User Mode is on. */
+ bool isProjectNavigatorVisible() const;
+
+ protected:
+ KexiProjectData *m_projectData;
+ Action m_action;
+ KexiStartupData::Import m_importActionData;
+ bool m_forcedUserMode : 1;
+ bool m_forcedDesignMode : 1;
+ bool m_isProjectNavigatorVisible : 1;
+ bool m_createDB : 1;
+ bool m_dropDB : 1;
+ bool m_alsoOpenDB : 1;
+};
+
+#endif
diff --git a/kexi/core/kexistaticpart.cpp b/kexi/core/kexistaticpart.cpp
new file mode 100644
index 000000000..c7e323e1f
--- /dev/null
+++ b/kexi/core/kexistaticpart.cpp
@@ -0,0 +1,63 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexistaticpart.h"
+#include "kexipartinfo_p.h"
+#include "kexipartitem.h"
+#include "kexi.h"
+
+using namespace KexiPart;
+
+//------------------------------
+
+StaticInfo::StaticInfo(const QCString& mimeType, const QString& itemIcon, const QString& objectName)
+ : Info()
+{
+ d->mimeType = mimeType;
+ d->itemIcon = itemIcon;
+ d->objectName = objectName;
+}
+
+StaticInfo::~StaticInfo()
+{
+}
+
+//------------------------------
+
+StaticPart::StaticPart(const QCString& mimeType, const QString& itemIcon, const QString& objectName)
+ : Part(&Kexi::partManager(), new StaticInfo(mimeType, itemIcon, objectName))
+{
+ Kexi::partManager().insertStaticPart(this);
+}
+
+StaticPart::~StaticPart()
+{
+}
+
+KexiViewBase* StaticPart::createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode)
+{
+ Q_UNUSED(parent);
+ Q_UNUSED(dialog);
+ Q_UNUSED(item);
+ Q_UNUSED(viewMode);
+ //unused
+ return 0;
+}
diff --git a/kexi/core/kexistaticpart.h b/kexi/core/kexistaticpart.h
new file mode 100644
index 000000000..fe127fe48
--- /dev/null
+++ b/kexi/core/kexistaticpart.h
@@ -0,0 +1,64 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003,2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISTATICPART_H
+#define KEXISTATICPART_H
+
+#include "kexipart.h"
+#include "kexipartinfo.h"
+
+namespace KexiPart
+{
+
+/**
+ * @short Information about a static Kexi Part (plugin).
+ */
+class KEXICORE_EXPORT StaticInfo : public Info
+{
+ public:
+ StaticInfo(const QCString& mimeType, const QString& itemIcon, const QString& objectName);
+ ~StaticInfo();
+
+ protected:
+};
+
+/**
+ * @short Static Kexi Part (plugin).
+ */
+class KEXICORE_EXPORT StaticPart : public Part
+{
+ public:
+ StaticPart(const QCString& mimeType, const QString& itemIcon, const QString& objectName);
+ virtual ~StaticPart();
+
+ /*! Creates a new view for mode \a viewMode, \a item and \a parent. The view will be
+ used inside \a dialog. \a args arguments can be passed. */
+ virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode, QMap<QString,QString>* args) = 0;
+
+ protected:
+ //! unused by static parts
+ KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode = Kexi::DataViewMode);
+};
+
+}
+
+#endif
diff --git a/kexi/core/kexitabledesignerinterface.cpp b/kexi/core/kexitabledesignerinterface.cpp
new file mode 100644
index 000000000..9d6e1dc02
--- /dev/null
+++ b/kexi/core/kexitabledesignerinterface.cpp
@@ -0,0 +1,28 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitabledesignerinterface.h"
+
+KexiTableDesignerInterface::KexiTableDesignerInterface()
+{
+}
+
+KexiTableDesignerInterface::~KexiTableDesignerInterface()
+{
+}
diff --git a/kexi/core/kexitabledesignerinterface.h b/kexi/core/kexitabledesignerinterface.h
new file mode 100644
index 000000000..60d7d0c77
--- /dev/null
+++ b/kexi/core/kexitabledesignerinterface.h
@@ -0,0 +1,104 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXITABLEDESIGNERINTERFACE_H
+#define KEXITABLEDESIGNERINTERFACE_H
+
+#include <koproperty/property.h>
+#include <kexiutils/tristate.h>
+
+namespace KoProperty {
+ class Set;
+}
+
+//! Interface for main Table Designer's commands
+/*! This interface has been specified to enable invoking Table Designer's commands
+ at application's level. This is used in the "altertable" test suite, available in
+ kexi/tests/altertable Kexi source code directory.
+ KexiTableDesignerInterface is implemented by KexiTableDesignerView, so it's enough
+ to use dynamic_cast:
+ \code
+ KexiDialogBase *dlg = KexiMainWindowImpl::self()->currentDialog();
+ if (dlg) {
+ KexiTableDesignerInterface* designerIface
+ = dynamic_cast<KexiTableDesignerInterface*>( dlg->selectedView() );
+ if (designerIface) {
+ //for example, delete row #3
+ designerIface->deleteRow( 3, true );
+ }
+ }
+ \endcode
+ Methods of KexiTableDesignerInterface are also used by classes of KexiTableDesignerCommands
+ namespace (KCommand derivatives) for implementing commands execution and unexecution.
+
+ All the methods contain addCommand argument. Set if to true to get the command added
+ to the undo/redo buffer, what will look like real user's action. This is also needed
+ to poperly generate arguments for commiting the "alter table" operation.
+*/
+class KEXICORE_EXPORT KexiTableDesignerInterface
+{
+ public:
+ KexiTableDesignerInterface();
+
+ virtual ~KexiTableDesignerInterface();
+
+ /*! Clears field information entered for row.
+ This is performed by removing values from caption and data type columns. */
+ virtual void clearRow(int row, bool addCommand = false) = 0;
+
+ /*! Inserts a new field with \a caption for \a row.
+ Property set is also created.
+ Existing field will be overwritten, so use insertEmptyRow()
+ is you want to move subsequent fields down. */
+ virtual void insertField(int row, const QString& caption, bool addCommand = false) = 0;
+
+ /*! Inserts a new \a field for \a row.
+ Property set is also created. \a set will be deeply-copied into the new set.
+ Existing field will be overwritten, so use insertEmptyRow()
+ is you want to move subsequent fields down. */
+ virtual void insertField(int row, KoProperty::Set& set, bool addCommand = false) = 0;
+
+ /*! Inserts a new empty row at position \a row. */
+ virtual void insertEmptyRow( int row, bool addCommand = false ) = 0;
+
+ /*! Deletes \a row from the table view. Property set is also deleted.
+ All the subsequent fields are moved up. */
+ virtual void deleteRow( int row, bool addCommand = false ) = 0;
+
+ /*! Changes property \a propertyName to \a newValue for a field pointed by \a fieldUID.
+ If \a listData is not NULL and not empty, a deep copy of it is passed to Property::setListData().
+ If \a listData \a nlist if not NULL but empty, Property::setListData(0) is called. */
+ virtual void changeFieldPropertyForRow( int fieldUID, const QCString& propertyName,
+ const QVariant& newValue, KoProperty::Property::ListData* const listData = 0,
+ bool addCommand = false ) = 0;
+
+ /*! Creates temporary table for the current design and returns debug string for it. */
+ virtual QString debugStringForCurrentTableSchema(tristate& result) = 0;
+
+ /*! Simulates execution of alter table, and puts debug into \a debugTarget.
+ A case when debugTarget is not 0 is true for the alter table test suite. */
+ virtual tristate simulateAlterTableExecution(QString *debugTarget) = 0;
+
+ /*! Real execution of the Alter Table. For debugging of the real alter table.
+ \return true on success, false on failure and cancelled if user has cancelled
+ execution. */
+ virtual tristate executeRealAlterTable() = 0;
+};
+
+#endif
diff --git a/kexi/core/kexitemplateloader.cpp b/kexi/core/kexitemplateloader.cpp
new file mode 100644
index 000000000..489d5f51d
--- /dev/null
+++ b/kexi/core/kexitemplateloader.cpp
@@ -0,0 +1,112 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitemplateloader.h"
+
+#include <kstandarddirs.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <kapplication.h>
+
+#include <qdir.h>
+
+//static
+KexiTemplateInfo::List KexiTemplateLoader::loadListInfo()
+{
+ KexiTemplateInfo::List list;
+ const QString subdir = QString(kapp->instanceName()) + "/templates";
+ QString lang( KGlobal::locale()->language() );
+ QStringList dirs( kapp->dirs()->findDirs("data", subdir) );
+ while (true) {
+ foreach( QStringList::ConstIterator, it, dirs) {
+ QDir dir((*it)+lang);
+ if (!dir.exists())
+ continue;
+ if (!dir.isReadable()) {
+ kdWarning() << "KexiTemplateLoader::loadListInfo() \"" << dir.absPath() << "\" not readable!" << endl;
+ continue;
+ }
+ const QStringList templateDirs( dir.entryList(QDir::Dirs, QDir::Name) );
+ const QString absDirPath( dir.absPath() + '/' );
+ foreach(QStringList::ConstIterator, templateIt, templateDirs) {
+ if ((*templateIt)=="." || (*templateIt==".."))
+ continue;
+ KexiTemplateInfo info = KexiTemplateLoader::loadInfo( absDirPath + *templateIt );
+ if (!info.name.isEmpty())
+ list.append( info );
+ }
+ }
+ if (lang != "en" && list.isEmpty()) //not found for current locale, try "en"
+ lang = "en";
+ else
+ break;
+ }
+ return list;
+}
+
+//static
+KexiTemplateInfo KexiTemplateLoader::loadInfo(const QString& directory)
+{
+ QDir dir(directory);
+ if (!dir.isReadable()) {
+ kdWarning() << "KexiTemplateLoader::loadInfo() \""
+ << directory << "\" not readable!" << endl;
+ return KexiTemplateInfo();
+ }
+ if (!QFileInfo(directory+"/info.txt").isReadable())
+ return KexiTemplateInfo();
+ KConfig infoTxt(directory+"/info.txt", true/*readonly*/, false/*local*/);
+ KexiTemplateInfo info;
+ info.name = infoTxt.readEntry("Name");
+ if (info.name.isEmpty()) {
+ kdWarning() << "KexiTemplateLoader::loadInfo() \"" << (directory+"/info.txt") << "\" contains no \"name\" field" << endl;
+ return KexiTemplateInfo();
+ }
+ const QStringList templateFiles( dir.entryList("*.kexi", QDir::Files|QDir::Readable, QDir::Name) );
+ if (templateFiles.isEmpty()) {
+ kdWarning() << "KexiTemplateLoader::loadInfo() no readable .kexi template file found in \"" << directory << "\"" << endl;
+ return KexiTemplateInfo();
+ }
+ info.filename = directory+"/"+templateFiles.first();
+ info.description = infoTxt.readEntry("Description");
+ const QString iconFileName( infoTxt.readEntry("Icon") );
+ if (!iconFileName.isEmpty())
+ info.icon = QPixmap(directory+'/'+iconFileName);
+ if (info.icon.isNull())
+ info.icon = DesktopIcon("kexiproject_sqlite"); //default
+ QStringList autoopenObjectsString = infoTxt.readListEntry("AutoOpenObjects");
+ foreach( QStringList::ConstIterator, it, autoopenObjectsString) {
+ KexiProjectData::ObjectInfo autoopenObject;
+ QStringList autoopenObjectNameSplitted( QStringList::split(':', *it) );
+ if (autoopenObjectNameSplitted.count()>1) {
+ autoopenObject["type"] = autoopenObjectNameSplitted[0];
+ autoopenObject["name"] = autoopenObjectNameSplitted[1];
+ }
+ else {
+ autoopenObject["type"] = "table";
+ autoopenObject["name"] = autoopenObjectNameSplitted[0];
+ }
+ autoopenObject["action"] = "open";
+ info.autoopenObjects.append( autoopenObject );
+ }
+ return info;
+}
diff --git a/kexi/core/kexitemplateloader.h b/kexi/core/kexitemplateloader.h
new file mode 100644
index 000000000..131eda316
--- /dev/null
+++ b/kexi/core/kexitemplateloader.h
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_TEMPLLOADER_H
+#define KEXI_TEMPLLOADER_H
+
+#include <qpixmap.h>
+#include "kexiprojectdata.h"
+
+//! A structure providing information about single kexi database template file
+struct KEXICORE_EXPORT KexiTemplateInfo
+{
+ typedef QValueList<KexiTemplateInfo> List;
+
+ QString filename, name, description;
+ QPixmap icon;
+ QValueList<KexiProjectData::ObjectInfo> autoopenObjects;
+};
+
+//! Handles retrieving information about templates
+class KEXICORE_EXPORT KexiTemplateLoader
+{
+ public:
+ static KexiTemplateInfo::List loadListInfo();
+ static KexiTemplateInfo loadInfo(const QString& directory);
+};
+
+#endif
diff --git a/kexi/core/kexitextmsghandler.cpp b/kexi/core/kexitextmsghandler.cpp
new file mode 100644
index 000000000..a75c7eb31
--- /dev/null
+++ b/kexi/core/kexitextmsghandler.cpp
@@ -0,0 +1,57 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitextmsghandler.h"
+
+#include "kexi.h"
+#include <kexidb/utils.h>
+#include <kexiutils/utils.h>
+
+KexiTextMessageHandler::KexiTextMessageHandler(QString &messageTarget, QString &detailsTarget)
+ : KexiGUIMessageHandler(0)
+ , m_messageTarget(&messageTarget)
+ , m_detailsTarget(&detailsTarget)
+{
+ *m_messageTarget = QString::null;
+ *m_detailsTarget = QString::null;
+}
+
+KexiTextMessageHandler::~KexiTextMessageHandler()
+{
+}
+
+void
+KexiTextMessageHandler::showMessage(MessageType type,
+ const QString &title, const QString &details)
+{
+ Q_UNUSED(type);
+ if (!m_enableMessages)
+ return;
+
+ //'wait' cursor is a nonsense now
+ KexiUtils::removeWaitCursor();
+
+ QString msg(title);
+ if (title.isEmpty())
+ msg = i18n("Unknown error");
+ msg = "<qt><p>"+msg+"</p>";
+ *m_messageTarget = msg;
+ *m_detailsTarget = details;
+}
+
diff --git a/kexi/core/kexitextmsghandler.h b/kexi/core/kexitextmsghandler.h
new file mode 100644
index 000000000..74463cc76
--- /dev/null
+++ b/kexi/core/kexitextmsghandler.h
@@ -0,0 +1,37 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXITXTMSGHANDLER_H
+#define KEXITXTMSGHANDLER_H
+
+#include "kexiguimsghandler.h"
+
+//! @short A wrapper that redirects messages to a string variables instead of displaying them.
+//! See KexiTextMessageHandler constructor for details.
+class KEXICORE_EXPORT KexiTextMessageHandler : public KexiGUIMessageHandler
+{
+ public:
+ KexiTextMessageHandler(QString &messageTarget, QString &detailsTarget);
+ virtual ~KexiTextMessageHandler();
+ virtual void showMessage(MessageType type, const QString &title, const QString &details);
+
+ QString *m_messageTarget, *m_detailsTarget;
+};
+
+#endif
diff --git a/kexi/core/kexiuseraction.cpp b/kexi/core/kexiuseraction.cpp
new file mode 100644
index 000000000..eb521de54
--- /dev/null
+++ b/kexi/core/kexiuseraction.cpp
@@ -0,0 +1,108 @@
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kshortcut.h>
+
+#include <kexidb/cursor.h>
+
+#include "kexipart.h"
+#include "kexipartmanager.h"
+
+#include "kexiproject.h"
+#include "keximainwindow.h"
+#include "kexiuseraction.h"
+
+KexiUserAction::KexiUserAction(KexiMainWindow *win, KActionCollection *parent, const QString &name, const QString &text, const QString &pixmap)
+ : KAction(text, pixmap, KShortcut::null(), this, SLOT(execute()), parent, name.latin1())
+{
+ m_win = win;
+ m_method = 0;
+ connect(this, SIGNAL(activated()), this, SLOT(execute()));
+}
+
+void
+KexiUserAction::setMethod(int method, Arguments args)
+{
+ m_method = method;
+ m_args = args;
+}
+
+void
+KexiUserAction::execute()
+{
+ kdDebug() << "KexiUserAction::execute(): " << KexiUserActionMethod::methodName(m_method) << endl;
+
+ switch(m_method)
+ {
+ case OpenObject: //open a project object
+ {
+ //get partinfo
+ KexiPart::Info *i = Kexi::partManager().infoForMimeType(m_args[0].toString().latin1());
+ if (!i) {
+ KMessageBox::error(m_win, i18n("Specified part does not exist"));
+ return;
+ }
+
+ Kexi::partManager().part(i); //load part if doesn't exists
+ KexiPart::Item *item = m_win->project()->item(i, m_args[1].toString());
+ bool openingCancelled;
+ if(!m_win->openObject(item, Kexi::DataViewMode, openingCancelled) && !openingCancelled) {
+ KMessageBox::error(m_win, i18n("Specified document could not be opened."));
+ return;
+ }
+ if (openingCancelled)
+ return;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+KexiUserAction *
+KexiUserAction::fromCurrentRecord(KexiMainWindow *context, KActionCollection *parent, KexiDB::Cursor *c)
+{
+ if(!c || c->bof() || c->eof())
+ return 0;
+
+ KexiUserAction *a = new KexiUserAction(context, parent, c->value(1).toString(), c->value(2).toString(), c->value(3).toString());
+ QString args = c->value(5).toString();
+ bool quote = false;
+
+ Arguments arg;
+ QString tmp;
+ const int len = args.length();
+ for(int i=0; i < len; i++)
+ {
+ if(args[i] == '"') // if current char is quoted unqote or other way round
+ {
+ quote = !quote;
+ }
+ else if(args[i] == ',' && !quote) //if item end add tmp to argumentstack and strip quotes if nessesery
+ {
+ if(tmp.left(1)=="\"" && tmp.right(1)=="\"")
+ tmp = tmp.mid(1, tmp.length()-2);
+
+ arg.append(QVariant(tmp));
+ tmp = "";
+ }
+ else //else simply add char to tmp
+ {
+ tmp += args[i];
+ }
+ }
+
+ if(tmp.left(1)=="\"" && tmp.right(1)=="\"")
+ tmp = tmp.mid(1, tmp.length()-2);
+
+ arg.append(QVariant(tmp));
+
+ a->setMethod(c->value(4).toInt(), arg);
+ return a;
+}
+
+KexiUserAction::~KexiUserAction()
+{
+}
+
+#include "kexiuseraction.moc"
+
diff --git a/kexi/core/kexiuseraction.h b/kexi/core/kexiuseraction.h
new file mode 100644
index 000000000..822220f09
--- /dev/null
+++ b/kexi/core/kexiuseraction.h
@@ -0,0 +1,81 @@
+#ifndef KEXIUSERACTION_H
+#define KEXIUSERACTION_H
+
+#include <kaction.h>
+
+#include "kexiuseractionmethod.h"
+
+namespace KexiDB
+{
+ class Cursor;
+}
+class KexiMainWindow;
+typedef QValueVector<QVariant> Arguments;
+
+/*! action that can be defined by a user for a special scope e.g. main, form ...
+ the actions can have some predefined \ref Methods which are described in \ref KexiUserActionMethod
+ e.g. OpenObject, ExecuteScript ... those methods take different arguments also described in \ref KexiUserActionMethod
+*/
+class KEXICORE_EXPORT KexiUserAction : public KAction
+{
+ Q_OBJECT
+
+ public:
+ /*! bytecode of available methods */
+ enum Methods
+ {
+ MethodNone = 0,
+ OpenObject = 1,
+ CloseObject = 2,
+ DeleteObject = 3,
+ ExecuteScript = 4,
+ ExitKexi = 5,
+
+ LastMethod = 6 //use the last integer here... so we can stop iteration
+ };
+
+ /*! argument types */
+ enum ArgTypes
+ {
+ String = 0,
+ Integer = 1,
+ Bool = 2,
+ KexiPart = 3,
+ KexiItem = 4
+ };
+
+ /*! constructs an action
+ \note methods are associated using setMethod()
+ */
+ KexiUserAction(KexiMainWindow *context, KActionCollection *parent, const QString &name, const QString &text, const QString &pixmap);
+ ~KexiUserAction();
+
+ /*! sets execution information associated with this action this will mostly look like
+ \code
+ KexiUserAction *action = new KexiUserAction(...);
+ Arguments arg;
+ arg.append(QVariant("kexi/form"));
+ arg.append(QVariant("main"));
+ action->setMethod(KexiUserAction::OpenAction, arg);
+ \endcode
+ */
+ void setMethod(int method, Arguments args);
+
+ /*! creates a KexiUserAction from current record in \a c
+ mostly needed for creation from kexi__useractions table */
+ static KexiUserAction *fromCurrentRecord(KexiMainWindow *context, KActionCollection *parent, KexiDB::Cursor *c);
+
+ protected slots:
+ /*! actually executes the associated method
+ \note KexiUserAction automatically connects KAction::activated() to KexiUserAction::execute()
+ */
+ void execute();
+
+ private:
+ KexiMainWindow *m_win;
+ int m_method;
+ Arguments m_args;
+};
+
+#endif
+
diff --git a/kexi/core/kexiuseractionmethod.cpp b/kexi/core/kexiuseractionmethod.cpp
new file mode 100644
index 000000000..a2ec29145
--- /dev/null
+++ b/kexi/core/kexiuseractionmethod.cpp
@@ -0,0 +1,32 @@
+#include <klocale.h>
+
+#include "kexiuseraction.h"
+#include "kexiuseractionmethod.h"
+
+KexiUserActionMethod::KexiUserActionMethod(int method, ArgTypes types, ArgNames names)
+{
+ m_method = method;
+ m_types = types;
+ m_names = names;
+}
+
+QString
+KexiUserActionMethod::methodName(int method)
+{
+ switch(method)
+ {
+ case KexiUserAction::OpenObject:
+ return i18n("Open Object");
+ case KexiUserAction::CloseObject:
+ return i18n("Close Object");
+ case KexiUserAction::DeleteObject:
+ return i18n("Delete Object");
+ case KexiUserAction::ExecuteScript:
+ return i18n("Execute Script");
+ case KexiUserAction::ExitKexi:
+ return i18n("Exit Main Application");
+ default:
+ return QString::null;
+ }
+}
+
diff --git a/kexi/core/kexiuseractionmethod.h b/kexi/core/kexiuseractionmethod.h
new file mode 100644
index 000000000..5bfae22ce
--- /dev/null
+++ b/kexi/core/kexiuseractionmethod.h
@@ -0,0 +1,42 @@
+#ifndef KEXIUSERACTIONMETHOD_H
+#define KEXIUSERACTIONMETHOD_H
+
+#include <qvaluevector.h>
+#include <qstring.h>
+#include <qvariant.h>
+
+typedef QValueVector<int> ArgTypes;
+typedef QValueVector<QString> ArgNames;
+
+/*! describes a UserActionCommand */
+class KEXICORE_EXPORT KexiUserActionMethod
+{
+ public:
+ /*! constructs a UserActionCommand describtion */
+ KexiUserActionMethod(int method, ArgTypes types, ArgNames names);
+
+ /*! \return method id of this method */
+ int method() { return m_method; }
+
+ /*! \return argument type information of this method */
+ ArgTypes types() { return m_types; }
+
+ /*! \return i18n argument names of this method */
+ ArgNames names() { return m_names; }
+
+
+
+ /*! \return i18n method name for \a method */
+ static QString methodName(int method);
+
+ /*! \return an i18n string for \a type */
+ static QString typeName(int type);
+
+ private:
+ int m_method;
+ ArgTypes m_types;
+ ArgNames m_names;
+};
+
+#endif
+
diff --git a/kexi/core/kexiviewbase.cpp b/kexi/core/kexiviewbase.cpp
new file mode 100644
index 000000000..1696f5025
--- /dev/null
+++ b/kexi/core/kexiviewbase.cpp
@@ -0,0 +1,328 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiviewbase.h"
+
+#include "keximainwindow.h"
+#include "kexidialogbase.h"
+#include "kexiproject.h"
+#include <koproperty/set.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+#include <kexiutils/utils.h>
+
+#include <kdebug.h>
+
+KexiViewBase::KexiViewBase(KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : QWidget(parent, name)
+ , KexiActionProxy(this, mainWin)
+ , m_mainWin(mainWin)
+ , m_viewWidget(0)
+ , m_parentView(0)
+ , m_newlyAssignedID(-1)
+ , m_viewMode(0) //unknown!
+ , m_dirty(false)
+{
+ QWidget *wi=this;
+ while ((wi = wi->parentWidget()) && !wi->inherits("KexiDialogBase"))
+ ;
+ m_dialog = (wi && wi->inherits("KexiDialogBase")) ? static_cast<KexiDialogBase*>(wi) : 0;
+ if (m_dialog) {
+ //init view mode number for this view (obtained from dialog where this view is created)
+ if (m_dialog->supportsViewMode(m_dialog->m_creatingViewsMode))
+ m_viewMode = m_dialog->m_creatingViewsMode;
+ }
+
+ installEventFilter(this);
+}
+
+KexiViewBase::~KexiViewBase()
+{
+}
+
+KexiPart::Part* KexiViewBase::part() const
+{
+ return m_dialog ? m_dialog->part() : 0;
+}
+
+tristate KexiViewBase::beforeSwitchTo(int /* mode */, bool & /*dontStore*/)
+{
+ return true;
+}
+
+tristate KexiViewBase::afterSwitchFrom(int /* mode */)
+{
+ return true;
+}
+
+QSize KexiViewBase::preferredSizeHint(const QSize& otherSize)
+{
+ KexiDialogBase* dlg = parentDialog();
+ if (dlg && dlg->mdiParent()) {
+ QRect r = dlg->mdiParent()->mdiAreaContentsRect();
+ return otherSize.boundedTo( QSize(
+ r.width() - 10,
+ r.height() - dlg->mdiParent()->captionHeight() - dlg->pos().y() - 10
+ ) );
+ }
+ return otherSize;
+}
+
+void KexiViewBase::closeEvent( QCloseEvent * e )
+{
+ bool cancel = false;
+ emit closing(cancel);
+ if (cancel) {
+ e->ignore();
+ return;
+ }
+ QWidget::closeEvent(e);
+}
+
+KoProperty::Set *KexiViewBase::propertySet()
+{
+ return 0;
+}
+
+void KexiViewBase::propertySetSwitched()
+{
+ if (parentDialog())
+ m_mainWin->propertySetSwitched( parentDialog(), false );
+}
+
+void KexiViewBase::propertySetReloaded(bool preservePrevSelection, const QCString& propertyToSelect)
+{
+ if (parentDialog())
+ m_mainWin->propertySetSwitched( parentDialog(), true, preservePrevSelection, propertyToSelect );
+}
+
+void KexiViewBase::setDirty(bool set)
+{
+/* if (m_dirty == set) {//no change here
+ if (m_dialog) {
+ // however, it's a change from dialog perspective
+ if (m_dialog->dirty()!=set)
+ m_dialog->dirtyChanged();
+ }
+ return;
+ }*/
+ const bool changed = (m_dirty != set);
+ m_dirty = set;
+ m_dirty = dirty();
+// if (m_dirty!=set)//eventually didn't change
+// return;
+ if (m_parentView) {
+ m_parentView->setDirty(m_dirty);
+ }
+ else {
+ if (changed && m_dialog)
+ m_dialog->dirtyChanged(this);
+ }
+}
+
+/*bool KexiViewBase::saveData()
+{
+ //TODO....
+
+ //finally:
+ setDirty(false);
+ return true;
+}*/
+
+KexiDB::SchemaData* KexiViewBase::storeNewData(const KexiDB::SchemaData& sdata, bool & /*cancel*/)
+{
+ KexiDB::SchemaData *new_schema = new KexiDB::SchemaData();
+ *new_schema = sdata;
+
+ if (!m_mainWin->project()->dbConnection()
+ ->storeObjectSchemaData( *new_schema, true ))
+ {
+ delete new_schema;
+ new_schema=0;
+ }
+ m_newlyAssignedID = new_schema->id();
+ return new_schema;
+}
+
+tristate KexiViewBase::storeData(bool dontAsk)
+{
+ Q_UNUSED(dontAsk);
+ if (!m_dialog || !m_dialog->schemaData())
+ return false;
+ if (!m_mainWin->project()->dbConnection()
+ ->storeObjectSchemaData( *m_dialog->schemaData(), false /*existing object*/ ))
+ {
+ return false;
+ }
+ setDirty(false);
+ return true;
+}
+
+bool KexiViewBase::loadDataBlock( QString &dataString, const QString& dataID, bool canBeEmpty )
+{
+ if (!m_dialog)
+ return false;
+ const tristate res = m_mainWin->project()->dbConnection()->loadDataBlock(m_dialog->id(), dataString, dataID);
+ if (canBeEmpty && ~res) {
+ dataString = QString::null;
+ return true;
+ }
+ return res == true;
+}
+
+bool KexiViewBase::storeDataBlock( const QString &dataString, const QString &dataID )
+{
+ if (!m_dialog)
+ return false;
+ int effectiveID;
+ if (m_newlyAssignedID>0) {//ID not yet stored within dialog, but we've got ID here
+ effectiveID = m_newlyAssignedID;
+ m_newlyAssignedID = -1;
+ }
+ else
+ effectiveID = m_dialog->id();
+
+ return effectiveID>0
+ && m_mainWin->project()->dbConnection()->storeDataBlock(effectiveID, dataString, dataID);
+}
+
+bool KexiViewBase::removeDataBlock( const QString& dataID )
+{
+ if (!m_dialog)
+ return false;
+ return m_mainWin->project()->dbConnection()->removeDataBlock(m_dialog->id(), dataID);
+}
+
+bool KexiViewBase::eventFilter( QObject *o, QEvent *e )
+{
+ if (e->type()==QEvent::FocusIn || e->type()==QEvent::FocusOut) {// && o->inherits("QWidget")) {
+// //hp==true if currently focused widget is a child of this table view
+// const bool hp = Kexi::hasParent( static_cast<QWidget*>(o), focusWidget());
+// kexidbg << "KexiViewBase::eventFilter(): " << o->name() << " " << e->type() << endl;
+ if (KexiUtils::hasParent( this, static_cast<QWidget*>(o))) {
+ if (e->type()==QEvent::FocusOut && focusWidget() && !KexiUtils::hasParent( this, focusWidget())) {
+ //focus out: when currently focused widget is not a parent of this view
+ emit focus(false);
+ } else if (e->type()==QEvent::FocusIn) {
+ emit focus(true);
+ }
+ if (e->type()==QEvent::FocusOut) { // && focusWidget() && Kexi::hasParent( this, focusWidget())) { // && focusWidget()->inherits("KexiViewBase")) {
+// kdDebug() << focusWidget()->className() << " " << focusWidget()->name()<< endl;
+// kdDebug() << o->className() << " " << o->name()<< endl;
+ KexiViewBase *v = KexiUtils::findParent<KexiViewBase>(o, "KexiViewBase") ;
+// QWidget *www=v->focusWidget();
+ if (v) {
+ while (v->m_parentView)
+ v = v->m_parentView;
+ if (KexiUtils::hasParent( this, static_cast<QWidget*>(v->focusWidget()) ))
+ v->m_lastFocusedChildBeforeFocusOut = static_cast<QWidget*>(v->focusWidget());
+// v->m_lastFocusedChildBeforeFocusOut = static_cast<QWidget*>(o); //focusWidget();
+ }
+ }
+
+ if (e->type()==QEvent::FocusIn && m_actionProxyParent) {
+ m_actionProxyParent->m_focusedChild = this;
+ }
+// m_mainWin->invalidateSharedActions(this);
+ }
+ }
+ return false;
+}
+
+void KexiViewBase::setViewWidget(QWidget* w, bool focusProxy)
+{
+ if (m_viewWidget == w)
+ return;
+ if (m_viewWidget) {
+ m_viewWidget->removeEventFilter(this);
+ }
+ m_viewWidget = w;
+ if (m_viewWidget) {
+ m_viewWidget->installEventFilter(this);
+ if (focusProxy)
+ setFocusProxy(m_viewWidget); //js: ok?
+ }
+}
+
+void KexiViewBase::addChildView( KexiViewBase* childView )
+{
+ m_children.append( childView );
+ addActionProxyChild( childView );
+ childView->m_parentView = this;
+// if (m_parentView)
+// childView->installEventFilter(m_parentView);
+ childView->installEventFilter(this);
+
+}
+
+void KexiViewBase::setFocus()
+{
+ if (!m_lastFocusedChildBeforeFocusOut.isNull()) {
+// kdDebug() << "FOCUS: " << m_lastFocusedChildBeforeFocusOut->className() << " " << m_lastFocusedChildBeforeFocusOut->name()<< endl;
+ QWidget *w = m_lastFocusedChildBeforeFocusOut;
+ m_lastFocusedChildBeforeFocusOut = 0;
+ w->setFocus();
+ }
+ else {
+ if (hasFocus())
+ setFocusInternal();
+ else
+ setFocusInternal();
+ }
+ m_mainWin->invalidateSharedActions(this);
+}
+
+KAction* KexiViewBase::sharedAction( const char *action_name )
+{
+ if (part()) {
+ KActionCollection *ac;
+ if ( (ac = part()->actionCollectionForMode( viewMode() )) ) {
+ KAction* a = ac->action( action_name );
+ if (a)
+ return a;
+ }
+ }
+ return KexiActionProxy::sharedAction(action_name);
+}
+
+void KexiViewBase::setAvailable(const char* action_name, bool set)
+{
+ if (part()) {
+ KActionCollection *ac;
+ KAction* a;
+ if ( (ac = part()->actionCollectionForMode( viewMode() )) && (a = ac->action( action_name )) ) {
+ a->setEnabled(set);
+//why? return;
+ }
+ }
+ KexiActionProxy::setAvailable(action_name, set);
+}
+
+void KexiViewBase::updateActions(bool activated)
+{
+ //do nothing here
+ //do the same for children :)
+ for (QPtrListIterator<KexiViewBase> it(m_children); it.current(); ++it) {
+ it.current()->updateActions(activated);
+ }
+}
+
+#include "kexiviewbase.moc"
+
diff --git a/kexi/core/kexiviewbase.h b/kexi/core/kexiviewbase.h
new file mode 100644
index 000000000..18cd056d0
--- /dev/null
+++ b/kexi/core/kexiviewbase.h
@@ -0,0 +1,280 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIVIEWBASE_H
+#define KEXIVIEWBASE_H
+
+#include <qwidget.h>
+
+#include "kexiactionproxy.h"
+
+class KexiMainWindow;
+class KexiDialogBase;
+
+namespace KoProperty {
+ class Set;
+}
+
+namespace KexiDB {
+ class SchemaData;
+}
+
+//! Base class for single view embeddable of in KexiDialogBase.
+/*! This class automatically works as a proxy for shared (application-wide) actions.
+ KexiViewBase has 'dirty' flag to indicate that view's data has changed.
+ This flag's state is reused by KexiDialogBase object that contain the view.
+ KexiViewBase objects can be also nested, using addChildView(): any actions and 'dirty' flag
+ are transmited to parent view in this case.
+
+ KexiViewBase objects are usually allocated within KexiDialogBase objects by implementing
+ KexiPart::createView() method. See query or table part code for examples.
+
+ KexiViewBase object can be also allocated without attaching it KexiDialogBase,
+ especially withinn dock window. see KexiMainWindowImpl::initNavigator() to see example
+ how KexiBrowser does this.
+*/
+class KEXICORE_EXPORT KexiViewBase : public QWidget, public KexiActionProxy
+{
+ Q_OBJECT
+
+ public:
+ KexiViewBase(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0);
+ virtual ~KexiViewBase();
+
+ //! \return kexi main window that contain this view
+ inline KexiMainWindow *mainWin() const { return m_mainWin; }
+
+ //! \return parent KexiDialogBase that contains this view, or 0 if no dialog contain this view
+ KexiDialogBase* parentDialog() const { return m_dialog; }
+
+ /*! Added for convenience.
+ \return KexiPart object that was used to create this view (with a dialog)
+ or 0 if this view is not created using KexiPart. \sa parentDialog() */
+ KexiPart::Part* part() const;
+
+ /*! \return preferred size hint, that can be used to resize the view.
+ It is computed using maximum of (a) \a otherSize and (b) current KMDI dock area's size,
+ so the view won't exceed this maximum size. The method is used e.g. in KexiDialogBase::sizeHint().
+ If you reimplement this method, do not forget to return value of
+ yoursize.boundedTo( KexiViewBase::preferredSizeHint(otherSize) ). */
+ virtual QSize preferredSizeHint(const QSize& otherSize);
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ void addChildView( KexiViewBase* childView );
+
+ /*! True if contents (data) of the view is dirty and need to be saved
+ This may or not be used, depending if changes in the dialog
+ are saved immediately (e.g. like in datatableview) or saved by hand (by user)
+ (e.g. like in alter-table dialog).
+ "Dirty" flag is reused by KexiDialogBase::dirty().
+ Default implementation just uses internal m_dirty flag, that is false by default.
+ Reimplement this if you e.g. want reuse other "dirty"
+ flag from internal structures that may be changed. */
+ virtual bool dirty() const { return m_dirty; }
+
+ /*! \return the view mode for this view. */
+ int viewMode() const { return m_viewMode; }
+
+ /*! Reimpelmented from KexiActionProxy.
+ \return shared action with name \a action_name for this view.
+ If there's no such action declared in Kexi Part (part()),
+ global shared action is returned (if exists). */
+ virtual KAction* sharedAction( const char *action_name );
+
+ /*! Enables or disables shared action declared in Kexi Part (part()).
+ If there's no such action, global shared action is enabled or disabled (if exists). */
+ virtual void setAvailable(const char* action_name, bool set);
+
+ public slots:
+ virtual void setFocus();
+
+ /*! Call this in your view's implementation whenever current property set
+ (returned by propertySet()) is switched to other,
+ so property editor contents need to be completely replaced. */
+ virtual void propertySetSwitched();
+
+ /*! Sets dirty flag on or off. It the flag changes,
+ dirty(bool) signal is emitted by parent dialog (KexiDialog),
+ to inform the world about that. If this view has a parent view, setDirty()
+ is called also on parent view.
+ Always use this function to update 'dirty' flag information. */
+ void setDirty(bool set);
+
+ /*! Equal to setDirty(true). */
+ void setDirty() { setDirty(true); }
+
+ signals:
+ //! emitted when the view is about to close
+ void closing(bool& cancel);
+
+ void focus(bool in);
+
+ protected:
+ /*! called by KexiDialogBase::switchToViewMode() right before dialog is switched to new mode
+ By default does nothing. Reimplement this if you need to do something
+ before switching to this view.
+ \return true if you accept or false if a error occupied and view shouldn't change
+ If there is no error but switching should be just cancelled
+ (probably after showing some info messages), you need to return cancelled.
+ Set \a dontStore to true (it's false by default) if you want to avoid data storing
+ by storeData() or storeNewData(). */
+ virtual tristate beforeSwitchTo(int mode, bool &dontStore);
+
+ /*! called by KexiDialogBase::switchToViewMode() right after dialog is switched to new mode
+ By default does nothing. Reimplement this if you need to do something
+ after switching to this view.
+ \return true if you accept or false if a error occupied and view shouldn't change
+ If there is no error but switching should be just cancelled
+ (probably after showing some info messages), you need to return cancelled. */
+ virtual tristate afterSwitchFrom(int mode);
+
+ virtual void closeEvent( QCloseEvent * e );
+
+ /*! \return a property set for this view. For reimplementation. By default returns NULL. */
+ virtual KoProperty::Set *propertySet();
+
+ /*! Call this in your view's implementation whenever current property set
+ is changed that few properties are now visible and/or few other are invisible,
+ so property editor operating on this property set should be completely reloaded.
+ If \a preservePrevSelection is true and there was a property set
+ assigned before call, previously selected item will be preselected
+ in the editor (if found). */
+ void propertySetReloaded(bool preservePrevSelection = false, const QCString& propertyToSelect = QCString());
+
+ /*! Tells this dialog to create and store data of the new object
+ pointed by \a sdata on the backend.
+ Called by KexiDialogBase::storeNewData().
+ Default implementation:
+ - makes a deep copy of \a sdata
+ - stores object schema data \a sdata in 'kexi__objects' internal table
+ using Connection::storeObjectSchemaData().
+ Reimpelment this for your needs.
+ Requirements:
+ - deep copy of \a sdata should be made
+ - schema data should be created at the backend
+ (by calling KexiViewBase::storeNewData(const KexiDB::SchemaData& sdata)),
+ or using Connection::storeObjectSchemaData() or more specialized
+ method. For example, KexiAlterTableDialog
+ uses Connection::createTable(TableSchema) for this
+ (tableschema is SchemaData subclass) to store more information than
+ just a schem adata. You should use such subclasses if needed.
+ Should return newly created schema data object on success.
+ In this case, do not store schema object yourself (make deep copy if needed). */
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+
+ /*! Loads large string data \a dataString block (e.g. xml form's representation),
+ indexed with optional \a dataID, from the database backend.
+ If \a canBeEmpty is true and there is no data block for dataID, true is returned
+ and \a dataString is set to null string. The default is false.
+ \return true on success
+ \sa storeDataBlock(). */
+ bool loadDataBlock( QString &dataString, const QString& dataID = QString::null, bool canBeEmpty = false );
+
+ /*! Tells this view to store data changes on the backend.
+ Called by KexiDialogBase::storeData().
+ Default implementation:
+ - makes a deep copy of \a sdata
+ - stores object schema data \a sdata in 'kexi__objects' internal table
+ using Connection::storeObjectSchemaData().
+ If \a dontAsk is true, no question dialog will
+ be shown to the user. The default is false.
+
+ Reimpelment this for your needs. Should return true on success
+ or cancelled when the task should be cancelled.
+ \sa storeNewData() */
+ virtual tristate storeData(bool dontAsk = false);
+
+ /*! Stores (potentially large) string data \a dataString, block (e.g. xml form's representation),
+ at the database backend. Block will be stored in "kexi__objectdata" table pointed by
+ this object's id and an optional \a dataID identifier.
+
+ If dialog's id is not available (KexiDialogBase::id()),
+ then ID that was just created in storeNewData() is used
+ (see description of m_newlyAssignedID member).
+ If there is already such record in the table, it's simply overwritten.
+ \return true on success
+ */
+ bool storeDataBlock( const QString &dataString, const QString &dataID = QString::null );
+
+ /*! Removes (potentially large) string data (e.g. xml form's representation),
+ pointed by optional \a dataID, from the database backend.
+ \return true on success. Does not fail if the block doe not exists.
+ Note that if \a dataID is not specified, all data blocks for this view will be removed.
+ \sa storeDataBlock(). */
+ bool removeDataBlock( const QString& dataID = QString::null);
+
+ void setViewWidget(QWidget* w, bool focusProxy = false);
+
+ /*! Updates actions (e.g. availability). Reimplement it, if needed (you must
+ call superclass impelmentation at the end!).
+ This implementation does nothing for this view but calls updateActions()
+ for every child-view of this view.
+ called by KexiDialogBase on dialog's activation (\a activated is true)
+ or deactivation. */
+ virtual void updateActions(bool activated);
+
+ virtual void setFocusInternal() { QWidget::setFocus(); }
+
+ /*! Allows to react on parent dialog's detaching (only for KMDI's ChildFrame mode)
+ - it is called by KexiDialogBase::youAreDetached().
+ Default implementation does nothing.
+ Implement it if you want to perform some appropriate actions. */
+ virtual void parentDialogDetached() {};
+
+ /*! Allows to react on parent dialog's attaching (only for KMDI's ChildFrame mode)
+ - it is called by KexiDialogBase::youAreAttached().
+ Default implementation does nothing.
+ Implement it if you want to perform some appropriate actions. */
+ virtual void parentDialogAttached() {};
+
+ QString m_defaultIconName;
+
+ KexiMainWindow *m_mainWin;
+
+ KexiDialogBase *m_dialog;
+
+ QWidget *m_viewWidget;
+
+ KexiViewBase *m_parentView;
+
+ QGuardedPtr<QWidget> m_lastFocusedChildBeforeFocusOut;
+
+ private:
+ /*! Member set to newly assigned object's ID in storeNewData()
+ and used in storeDataBlock(). This is needed because usually,
+ storeDataBlock() can be called from storeNewData() and in this case
+ dialog has not yet assigned valid identifier (it has just negative temp. number).
+ \sa KexiDialogBase::id()
+ */
+ int m_newlyAssignedID;
+
+ /*! Mode for this view. Initialized by KexiDialogBase::switchToViewMode().
+ Can be useful when single class is used for more than one view (e.g. KexiDBForm). */
+ int m_viewMode;
+
+ QPtrList<KexiViewBase> m_children;
+
+ bool m_dirty : 1;
+
+ friend class KexiDialogBase;
+};
+
+#endif
+
diff --git a/kexi/data/Makefile.am b/kexi/data/Makefile.am
new file mode 100644
index 000000000..f9a03d8d3
--- /dev/null
+++ b/kexi/data/Makefile.am
@@ -0,0 +1,52 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+# kde_appsdir Where your application's menu entry (.desktop) should go to.
+# kde_icondir Where your icon should go to - better use KDE_ICON.
+# kde_sounddir Where your sounds should go to.
+# kde_htmldir Where your docs should go to. (contains lang subdirs)
+# kde_datadir Where you install application data. (Use a subdir)
+# kde_locale Where translation files should go to. (contains lang subdirs)
+# kde_cgidir Where cgi-bin executables should go to.
+# kde_confdir Where config files should go to (system-wide ones with default values).
+# kde_mimedir Where mimetypes .desktop files should go to.
+# kde_servicesdir Where services .desktop files should go to.
+# kde_servicetypesdir Where servicetypes .desktop files should go to.
+# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON).
+# kde_wallpaperdir Where general wallpapers should go to.
+# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to.
+# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS.
+# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES.
+# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES.
+# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3).
+# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3).
+
+rcdir = $(kde_datadir)/kexi
+rc_DATA = kexiui.rc
+
+kde_conf_DATA = kexirc
+
+servicetypesdir = $(kde_servicetypesdir)
+servicetypes_DATA=kexihandler.desktop
+
+#servicesdir=$(kde_servicesdir)/kexi
+#services_DATA=
+
+magicdir = $(kde_confdir)/magic
+magic_DATA = kexi.magic
+
+
+mimetypedir = $(kde_mimedir)/application
+mimetype_DATA = x-kexiproject-sqlite.desktop \
+x-kexi-connectiondata.desktop \
+x-kexiproject-sqlite2.desktop \
+x-kexiproject-sqlite3.desktop \
+x-kexiproject-shortcut.desktop
+
+#TODO: add x-kexi-shortcut-table etc.
+
+#kde <=3.4
+if need_kde34_compatibility
+ KDE34COMPAT = kde34compat
+endif
+
+SUBDIRS = $(KDE34COMPAT)
diff --git a/kexi/data/kde34compat/Makefile.am b/kexi/data/kde34compat/Makefile.am
new file mode 100644
index 000000000..89c03dc0f
--- /dev/null
+++ b/kexi/data/kde34compat/Makefile.am
@@ -0,0 +1,10 @@
+application_DATA = \
+ x-sqlite2.desktop \
+ x-sqlite3.desktop
+
+applicationdir = $(kde_mimedir)/application
+
+magic_DATA = sqlite.magic msaccess.magic
+magicdir = $(kde_confdir)/magic
+
+EXTRA_DIST = $(application_DATA)
diff --git a/kexi/data/kde34compat/msaccess.magic b/kexi/data/kde34compat/msaccess.magic
new file mode 100644
index 000000000..9b318b149
--- /dev/null
+++ b/kexi/data/kde34compat/msaccess.magic
@@ -0,0 +1,5 @@
+# Following magic data is provided here because KDE < 3.5 lacks it:
+
+# MS Access database (95 or newer, i.e. MS Jet 3.0 or newer)
+4 string Standard\ Jet\ DB application/x-msaccess
+
diff --git a/kexi/data/kde34compat/sqlite.magic b/kexi/data/kde34compat/sqlite.magic
new file mode 100644
index 000000000..a31592fb1
--- /dev/null
+++ b/kexi/data/kde34compat/sqlite.magic
@@ -0,0 +1,2 @@
+0 string **\ This\ file\ contains\ an\ SQLite application/x-sqlite2
+0 string SQLite\ format\ 3 application/x-sqlite3
diff --git a/kexi/data/kde34compat/x-sqlite2.desktop b/kexi/data/kde34compat/x-sqlite2.desktop
new file mode 100644
index 000000000..63f0f1d10
--- /dev/null
+++ b/kexi/data/kde34compat/x-sqlite2.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Type=MimeType
+MimeType=application/x-sqlite2
+Icon=empty
+Patterns=
+Comment=SQLite2 Database File
+Comment[bg]=Файл на SQLite2
+Comment[br]=Restr stlennvon SQLite2
+Comment[ca]=Fitxer de base de dades de SQLite2
+Comment[cy]=Ffeil Gronfa DdataSQLite2
+Comment[da]=SQLite2 databasefil
+Comment[de]=SQLite2-Datenbankdatei
+Comment[el]=Αρχείο βάσης δεδομένων SQLite2
+Comment[eo]=SQLite2-datumbazdosiero
+Comment[es]=Archivo de base de datos SQLite2
+Comment[et]=SQLite2 andmebaasi fail
+Comment[eu]=SQLite2 Datu-basearen fitxategia
+Comment[fa]=پروندۀ دادگان SQLite2
+Comment[fi]=SQLite2 tietokantatiedosto
+Comment[fr]=Fichier de base de données SQLite2
+Comment[fy]=SQLite2 Databanktriem
+Comment[ga]=Comhad Bunachair Shonraí SQLite2
+Comment[gl]=Ficheiro de Base de Datos de SQLite2
+Comment[he]=קובץ מסד נתונים של SQLite2
+Comment[hr]=SQLite2 datoteka baze podataka
+Comment[hu]=SQLite2-adatbázisfájl
+Comment[is]=SQLite2 gagnagrunnsskrá
+Comment[it]=File di banca dati SQLite2
+Comment[ja]=SQLite2 データベースファイル
+Comment[km]=ឯកសារ​មូលដ្ឋាន​ទិន្នន័យ​ SQLite2
+Comment[lt]=SQLite2 duomenų bazės byla
+Comment[lv]=SQLite2 datu bāzes fails
+Comment[ms]=Fail Pangkalan Data SQLite2
+Comment[nb]=SQLite2-databasefil
+Comment[nds]=SQLite2-Datenbankdatei
+Comment[ne]=SQLite2 डाटाबेस फाइल
+Comment[nl]=SQLite2 databasebestand
+Comment[nn]=SQLite2-databasefil
+Comment[pl]=Plik bazy danych SQLite2
+Comment[pt]=Ficheiro de Base de Dados do SQLite2
+Comment[pt_BR]=Arquivo de Banco de Dados do SQLite2
+Comment[ru]=База данных SQLite2
+Comment[se]=SQLite2-diehtovuođđofiila
+Comment[sk]=Databázový súbor SQLite2
+Comment[sl]=Datoteka zbirke podatkov SQLite2
+Comment[sr]=Фајл базе података SQLite2
+Comment[sr@Latn]=Fajl baze podataka SQLite2
+Comment[sv]=SQLite2-databasfil
+Comment[uk]=Файл бази даних SQLite2
+Comment[uz]=SQLite2 maʼlumot baza fayli
+Comment[uz@cyrillic]=SQLite2 маълумот база файли
+Comment[zh_CN]=SQLite2 数据库文件
+Comment[zh_TW]=SQLite2 資料庫檔案
diff --git a/kexi/data/kde34compat/x-sqlite3.desktop b/kexi/data/kde34compat/x-sqlite3.desktop
new file mode 100644
index 000000000..ee2756d8f
--- /dev/null
+++ b/kexi/data/kde34compat/x-sqlite3.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Type=MimeType
+MimeType=application/x-sqlite3
+Icon=empty
+Patterns=
+Comment=SQLite3 Database File
+Comment[bg]=Файл на SQLite3
+Comment[br]=Restr stlennvon SQLite3
+Comment[ca]=Fitxer de base de dades de SQLite3
+Comment[cy]=Ffeil Gronfa DdataSQLite3
+Comment[da]=SQLite3 databasefil
+Comment[de]=SQLite3-Datenbankdatei
+Comment[el]=Αρχείο βάσης δεδομένων SQLite3
+Comment[eo]=SQLite3-datumbazdosiero
+Comment[es]=Archivo de base de datos SQLite3
+Comment[et]=SQLite3 andmebaasi fail
+Comment[eu]=SQLite3 Datu-basearen fitxategia
+Comment[fa]=پروندۀ دادگان SQLite3
+Comment[fi]=SQLite3-tietokantatiedosto
+Comment[fr]=Fichier de base de données SQLite3
+Comment[fy]=SQLite3 Databanktriem
+Comment[ga]=Comhad Bunachair Shonraí SQLite3
+Comment[gl]=Ficheiro de Base de Datos de SQLite3
+Comment[he]=קובץ מסד נתונים של SQLite3
+Comment[hr]=SQLite3 datoteka baze podataka
+Comment[hu]=SQLite3-adatbázisfájl
+Comment[is]=SQLite3 gagnagrunnsskrá
+Comment[it]=File di banca dati SQLite3
+Comment[ja]=SQLite3 データベースファイル
+Comment[km]=ឯកសារ​មូលដ្ឋាន​ទិន្នន័យ SQLite3
+Comment[lt]=SQLite3 duomenų bazės byla
+Comment[lv]=SQLite3 datu bāzes fails
+Comment[ms]=Fail Pangkalan Data SQLite3
+Comment[nb]=SQLite3-databasefil
+Comment[nds]=SQLite3-Datenbankdatei
+Comment[ne]=SQLite3 डाटाबेस फाइल
+Comment[nl]=SQLite3 databasebestand
+Comment[nn]=SQLite3-databasefil
+Comment[pl]=Plik bazy danych SQLite3
+Comment[pt]=Ficheiro de Base de Dados do SQLite3
+Comment[pt_BR]=Arquivo de Banco de Dados do SQLite3
+Comment[ru]=База данных SQLite3
+Comment[se]=SQLite3-diehtovuođđofiila
+Comment[sk]=Databázový súbor SQLite3
+Comment[sl]=Datoteka zbirke podatkov SQLite3
+Comment[sr]=Фајл базе података SQLite3
+Comment[sr@Latn]=Fajl baze podataka SQLite3
+Comment[sv]=SQLite3-databasfil
+Comment[uk]=Файл бази даних SQLite3
+Comment[uz]=SQLite3 maʼlumot baza fayli
+Comment[uz@cyrillic]=SQLite3 маълумот база файли
+Comment[zh_CN]=SQLite3 数据库文件
+Comment[zh_TW]=SQLite3 資料庫檔案
diff --git a/kexi/data/kexi.magic b/kexi/data/kexi.magic
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/kexi/data/kexi.magic
@@ -0,0 +1 @@
+
diff --git a/kexi/data/kexihandler.desktop b/kexi/data/kexihandler.desktop
new file mode 100644
index 000000000..9a51860bf
--- /dev/null
+++ b/kexi/data/kexihandler.desktop
@@ -0,0 +1,77 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=Kexi/Handler
+Comment=Kexi Project Handlers
+Comment[ar]=مقابض مشروع Kexi
+Comment[bg]=Обработка на проекти в Kexi
+Comment[ca]=Manejadors de projectes de Kexi
+Comment[cs]=Ovladač projektů Kexi
+Comment[cy]=Trinyddion Prosiect Kexi
+Comment[da]=Kexi projekthåndtering
+Comment[de]=Kexi-Projektverwaltung
+Comment[el]=Χειριστές έργων του Kexi
+Comment[eo]=Kexi-projektmastrumiloj
+Comment[es]=Manejadores de proyecto de kexi
+Comment[et]=Kexi projektide käsitleja
+Comment[eu]=Kexi proiektuen maneiatzaileak
+Comment[fa]=گردانندگان پروژۀ Kexi
+Comment[fi]=Kexin projektikäsittelijät
+Comment[fr]=Gestionnaires de projet Kexi
+Comment[fy]=Kexi projekthandlers
+Comment[gl]=Xestores de Proxecto do Kexi
+Comment[he]=מנהלי פרוייקטים של Kexi
+Comment[hi]=केक्साई प्रोजेक्ट हैंडलर्स
+Comment[hr]=Upravljači Kexi projektom
+Comment[hu]=Kexi projektkezelők
+Comment[is]=Kexi verkefnis meðhöndlarar
+Comment[it]=Gestione progetto di Kexi
+Comment[ja]=Kexi プロジェクトハンドラ
+Comment[km]=កម្មវិធី​ដោះស្រាយ​គម្រោង Kexi
+Comment[lo]=ສ້າງແຟ້ມຂໍ້ຄວາມໃໝ่
+Comment[lv]=Kexi projekta apstrādātāji
+Comment[ms]=Pengendali Projek Kexi
+Comment[nb]=Kexi prosjektbehandlere
+Comment[nds]=Kexi-Projektpleeg
+Comment[ne]=केक्सी परियोजना ह्यान्डलरहरू
+Comment[nl]=Kexi projecthandlers
+Comment[nn]=Kexi-prosjekthandtering
+Comment[pl]=Pliki obsługi projektu Kexi
+Comment[pt]=Gestores de Projecto do Kexi
+Comment[pt_BR]=Manipuladores de Projeto do Kexi
+Comment[ru]=Среда проектов Kexi
+Comment[se]=Kexi-prošeaktagieđaheaddjit
+Comment[sk]=Spracovanie projektov Kexi
+Comment[sl]=Ravnalniki s projekti za Kexi
+Comment[sr]=Kexi-јеви руковаоци пројектима
+Comment[sr@Latn]=Kexi-jevi rukovaoci projektima
+Comment[sv]=Kexi-projektfil
+Comment[ta]=kexi திட்டப்பணி கையாளி
+Comment[tg]=Kexi Лоиҳаи дастнавис
+Comment[tr]=Kexi Proje İşleci
+Comment[uk]=Обробники проекту Kexi
+Comment[zh_CN]=Kexi 项目处理器
+Comment[zh_TW]=Kexi 專案控管程式
+
+[PropertyDef::X-Kexi-PartVersion]
+Type=int
+
+[PropertyDef::X-Kexi-TypeName]
+Type=QString
+
+[PropertyDef::X-Kexi-ItemIcon]
+Type=QString
+
+[PropertyDef::X-Kexi-TypeMime]
+Type=QString
+
+[PropertyDef::X-Kexi-NoObject]
+Type=bool
+
+[PropertyDef::X-Kexi-SupportsExecution]
+Type=bool
+
+[PropertyDef::X-Kexi-SupportsDataExport]
+Type=bool
+
+[PropertyDef::X-Kexi-SupportsPrinting]
+Type=bool
diff --git a/kexi/data/kexirc b/kexi/data/kexirc
new file mode 100644
index 000000000..9b6a1af10
--- /dev/null
+++ b/kexi/data/kexirc
@@ -0,0 +1,5 @@
+[Parts]
+Order=kexihandler_table,kexihandler_query,kexihandler_form,kexihandler_report,kexihandler_macro,kexihandler_script
+
+[MainWindow]
+HoverCloseButtonForTabs=false
diff --git a/kexi/data/kexiui.rc b/kexi/data/kexiui.rc
new file mode 100644
index 000000000..8612e41b1
--- /dev/null
+++ b/kexi/data/kexiui.rc
@@ -0,0 +1,204 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kexi" version="164">
+<MenuBar>
+ <Menu name="file" noMerge="0">
+ <text>&amp;Project</text>
+ <Action name="project_new" />
+ <Action name="project_open"/>
+ <Action name="project_download_examples"/>
+ <Action name="project_open_recent"/>
+ <!-- Menu name="project_open_recent">
+ <text>Open Recent</text>
+ <MergeLocal name="recent_actions"/>
+ <Separator />
+ <Action name="project_open_recent_more"/>
+ </Menu -->
+ <Separator />
+ <Action name="project_save"/>
+ <Action name="project_saveas"/>
+ <Separator />
+ <Menu name="project_import">
+ <text>&amp;Import</text>
+ <Action name="project_import_data_table" />
+ <Action name="project_import_objects" />
+ </Menu>
+ <Menu name="project_export">
+ <text>&amp;Export</text>
+ <Action name="project_export_data_table" />
+ <Action name="project_export_objects" />
+ </Menu>
+ <Separator />
+ <Action name="project_print"/>
+ <Action name="project_print_preview"/>
+ <Action name="project_print_setup"/>
+ <Separator />
+ <Action name="project_relations"/>
+ <Action name="project_properties"/>
+ <Separator />
+ <Action name="project_close"/>
+ <Separator />
+ <Action name="quit"/>
+ </Menu>
+
+ <Menu name="edit" noMerge="1">
+ <text>&amp;Edit</text>
+ <Action name="edit_undo"/>
+ <Action name="edit_redo"/>
+ <MergeLocal name="edit_undo_merge"/>
+ <Separator/>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="edit_paste"/>
+ <Menu name="edit_copy_special">
+ <text>Copy &amp;Special</text>
+ <Action name="edit_copy_special_data_table"/>
+ </Menu>
+ <Menu name="edit_paste_special">
+ <text>Paste &amp;Special</text>
+ <Action name="edit_paste_special_data_table"/>
+ </Menu>
+ <MergeLocal name="edit_paste_merge"/>
+ <Separator/>
+ <Action name="edit_find"/>
+ <Action name="edit_replace"/>
+ <Separator/>
+ <Action name="edit_select_all"/>
+ <Separator/>
+ <Action name="edit_delete"/>
+ <Action name="edit_delete_row"/>
+ <ActionList name="edit_clear_table"/>
+ <ActionList name="edit_insert_empty_row"/>
+ <Merge/>
+ </Menu>
+
+ <Menu name="view" noMerge="1">
+ <text>&amp;View</text>
+ <Action name="view_data_mode" type="toggle" />
+ <Action name="view_design_mode" type="toggle" />
+ <Action name="view_text_mode" type="toggle" />
+ <Separator/>
+ <Action name="view_navigator"/>
+ <Action name="view_mainarea"/>
+ <Action name="view_propeditor"/>
+ <Separator/>
+ <MergeLocal/>
+ </Menu>
+
+ <Menu name="insert" noMerge="1">
+ <text>&amp;Insert</text>
+ </Menu>
+
+ <Menu name="format" noMerge="0">
+ <text>&amp;Format</text>
+ <Action name="format_font"/>
+ <Separator/>
+ <MergeLocal/>
+ </Menu>
+
+ <Menu name="data" noMerge="1">
+ <text>&amp;Data</text>
+ <Action name="data_save_row"/>
+ <Action name="data_cancel_row_changes"/>
+ <!-- Action name="data_filter"/ -->
+ <Menu name="data_sort">
+ <text>Sort</text>
+ <Action name="data_sort_az"/>
+ <Action name="data_sort_za"/>
+ </Menu>
+ <Action name="data_execute"/>
+ </Menu>
+
+ <Merge/>
+
+ <Menu name="tools" noMerge="0">
+ <text>&amp;Tools</text>
+ <!-- Menu name="tools_migrate">
+ <text>&amp;Migrate</text>
+ </Menu -->
+ <Action name="tools_import_project"/>
+ <Action name="tools_compact_database"/>
+ <Action name="tools_scripts"/>
+ <MergeLocal/>
+ </Menu>
+
+ <Menu name="window" noMerge="0">
+ <text>&amp;Window</text>
+ <MergeLocal/>
+ </Menu>
+
+ <Menu name="settings" noMerge="1">
+ <text>&amp;Settings</text>
+ <Action name="options_show_menubar"/>
+ <Action name="options_show_toolbar"/>
+ <Merge name="StandardToolBarMenuHandler"/>
+ <Merge name="KMDIViewActions"/>
+ <MergeLocal name="show_toolbar_merge"/>
+ <Action name="options_show_statusbar"/>
+ <MergeLocal name="show_merge"/>
+ <Separator/>
+ <!-- Menu name="options_show_other">
+ <Text>&amp;Other</Text>
+ <Action name="options_show_nav"/>
+ <Action name="options_show_contexthelp"/>
+ <Action name="options_enable_forms"/>
+ </Menu -->
+ <Separator/>
+ <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_show_important_info" noMerge="0"/>
+ <Action name="help_start_feedback_agent" noMerge="0"/>
+ <!-- not yet needed: Menu name="help_licenses" noMerge="0">
+ <text>Other &amp;Licenses</text>
+ <Merge />
+ </Menu -->
+ </Menu>
+</MenuBar>
+
+<ToolBar name="mainToolBar" noMerge="1" fullWidth="false" index="0">
+ <text>Main Toolbar</text>
+ <Action name="project_save"/>
+ <Separator/>
+ <Action name="edit_undo"/>
+ <Action name="edit_redo"/>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="edit_paste"/>
+ <!-- Separator/ -->
+ <!-- Action name="view_zoom"/ -->
+ <!-- Separator/ -->
+ <Action name="project_relations"/>
+</ToolBar>
+
+<ToolBar name="viewToolBar" fullWidth="false" noMerge="1" index="1">
+ <text>View</text>
+ <Action name="view_data_mode" type="toggle" />
+ <Action name="view_design_mode" type="toggle" />
+ <Action name="view_text_mode" type="toggle" />
+</ToolBar>
+
+<ToolBar name="dataToolBar" fullWidth="false" noMerge="1" index="2">
+ <text>Data</text>
+ <Action name="data_cancel_row_changes"/>
+ <Action name="data_save_row"/>
+ <Separator/>
+ <Action name="data_sort_az"/>
+ <Action name="data_sort_za"/>
+ <Separator/>
+ <Action name="edit_find"/>
+ <!-- Action name="data_filter"/ -->
+</ToolBar>
+
+<ToolBar name="designToolBar" fullWidth="false" noMerge="1" index="3">
+ <text>Design</text>
+</ToolBar>
+
+<ToolBar name="format" fullWidth="false" noMerge="1" index="4">
+ <text>Format</text>
+</ToolBar>
+
+</kpartgui>
diff --git a/kexi/data/tips b/kexi/data/tips
new file mode 100644
index 000000000..59aebead1
--- /dev/null
+++ b/kexi/data/tips
@@ -0,0 +1,9 @@
+<tip category="Kexi|app">
+<html>
+<h3>Kexi - Database Creation for Everyone</h3>
+
+<h3>Welcome</h3>
+<p>Kexi is under active development.</p>
+<p>Visit kexi home page at http://www.kexi-project.org/</p>
+</html>
+</tip>
diff --git a/kexi/data/x-kexi-connectiondata.desktop b/kexi/data/x-kexi-connectiondata.desktop
new file mode 100644
index 000000000..36e381094
--- /dev/null
+++ b/kexi/data/x-kexi-connectiondata.desktop
@@ -0,0 +1,45 @@
+[Desktop Entry]
+Type=MimeType
+MimeType=application/x-kexi-connectiondata
+Icon=kexi_connectiondata
+Patterns=*.kexic
+Comment=Data for Database Server Connection
+Comment[bg]=Данни за връзка към СУБД
+Comment[ca]=Dades per a la connexió a servidor de bases de dades
+Comment[cy]=Data ar gyfer Cysylltiad Gweinydd Cronfa Ddata
+Comment[da]=Data for forbindelse til databaseserver
+Comment[de]=Daten für Verbindung mit Datenbankserver
+Comment[el]=Δεδομένα σύνδεσης εξυπηρετητή βάσεων δεδομένων
+Comment[eo]=Datumoj de la datumbazoservila konekto
+Comment[es]=Datos para la conexión con el servidor de bases de datos
+Comment[et]=Andmebaasiserveri ühenduse andmed
+Comment[fa]=داده برای اتصال کارساز دادگان
+Comment[fr]=Données de connexion au serveur de bases de données
+Comment[fy]=Gegevens foar in databank ferbining
+Comment[gl]=Datos de Conexón á Base de Datos
+Comment[he]=נתון לשרת בסיס נתונים
+Comment[hr]=Podaci za povezivanje s poslužiteljem baze podataka
+Comment[hu]=Egy adatbázis-kezelő eléréséhez szükséges adatok
+Comment[is]=Gögn fyrir tengingu við gagnagrunnsþjón
+Comment[it]=Dati per la connessione a un server di banche dati
+Comment[ja]=データベースサーバ接続のためのデータ
+Comment[km]=ទិន្នន័យ​សម្រាប់​ការ​តភ្ជាប់​ម៉ាស៊ីនបម្រើ​មូលដ្ឋានទិន្នន័យ
+Comment[lv]=Dati savienojumam ar datu bāzu serveri
+Comment[nb]=Data for tilkobling til database-tjener
+Comment[nds]=Daten för Datenbankserver-Verbinnen
+Comment[ne]=डाटाबेस सर्भर जडानका लागि डेटा
+Comment[nl]=Gegevens van een databaseverbinding
+Comment[pl]=Dane do połączenia z serwerem baz danych
+Comment[pt]=Dados de Ligação à Base de Dados
+Comment[pt_BR]=Dados para Conexão ao Banco de Dados
+Comment[ru]=Соединение с сервером баз данных
+Comment[sk]=Dáta na pripojenie k databázovému servru
+Comment[sl]=Podatki za povezavo s strežnikom z zbirko podatkov
+Comment[sr]=Подаци за везу са сервером база података
+Comment[sr@Latn]=Podaci za vezu sa serverom baza podataka
+Comment[sv]=Data för anslutning till databasserver
+Comment[uk]=Дані для з'єднання з сервером бази даних
+Comment[uz]=Maʼlumot bazasi serveri bilan bogʻlanish
+Comment[uz@cyrillic]=Маълумот базаси сервери билан боғланиш
+Comment[zh_CN]=数据库服务器连接的数据
+Comment[zh_TW]=用於資料庫伺服器連線的資料
diff --git a/kexi/data/x-kexiproject-shortcut.desktop b/kexi/data/x-kexiproject-shortcut.desktop
new file mode 100644
index 000000000..de82c6e1b
--- /dev/null
+++ b/kexi/data/x-kexiproject-shortcut.desktop
@@ -0,0 +1,54 @@
+[Desktop Entry]
+Type=MimeType
+MimeType=application/x-kexiproject-shortcut
+Icon=kexiproject_shortcut
+Patterns=*.kexis
+Comment=Shortcut to Kexi Project on Database Server
+Comment[bg]=Връзка към проект на Kexi за СУБД
+Comment[ca]=Drecera a un projecte Kexi en un servidor de bases de dades
+Comment[cy]=Byrlwybr i Gywaith Kexi ar Weinydd Cronfa Ddata
+Comment[da]=Genvej til Kexi projekt på databaseserver
+Comment[de]=Kurzbefehl für Kexi-Projekt auf Datenbankserver
+Comment[el]=Συντόμευση έργου Kexi σε εξυπηρετητή βάσεων δεδομένων
+Comment[eo]=Simbola ligo al Kexi-projekto en datumbazoservilo
+Comment[es]=Enlace al proyecto Kexi en el servidor de la base de datos
+Comment[et]=Kexi projekti viit andmebaasiserveris
+Comment[eu]=Datu-base zerbitzariko Kexi-ren proiektu bateko laster-bidea
+Comment[fa]=میان‌بر برای پروژۀ Kexi در کارساز دادگان
+Comment[fi]=Pikakuvake Kexi-projektiin tietokantapalvelimella
+Comment[fr]=Raccourci vers le projet Kexi sur le serveur de bases de données
+Comment[fy]=Fluchtoets nei Kexi-projekt op Databanktsjinner
+Comment[gl]=Atallo para Proxecto Kexi no Servidor de Base de Datos
+Comment[he]=קיצור דרך לפרוייקט של Kexi על גבי שרת מסד נתונים
+Comment[hi]=डेटाबेस सर्वर पर केएक्साई परियोजना का शॉर्टकट
+Comment[hr]=Prečac za Kexi Projekt na poslužitelju baze podataka
+Comment[hu]=A Kexi projekt linkje az adatbázis-kiszolgálón
+Comment[is]=Flýtileið að Kexi verkefni á gagnagrunnsþjóni
+Comment[it]=Scorciatoia verso un progetto Kexi sul server della banca dati
+Comment[ja]=データベースサーバの Kexi プロジェクトへのショートカット
+Comment[km]=ផ្លូវកាត់​ទៅ​កាន់​គម្រោង Kexi នៅ​លើ​ម៉ាស៊ីនបម្រើ​មូលដ្ឋាន​ទិន្នន័យ
+Comment[lv]=Norāde uz Kexi projektu datu bāzu serverī
+Comment[ms]=Jalan Pintas bagi Projek Kexi dalam Pelayan Pangkalan Data
+Comment[nb]=Snarvei til Kexi-prosjektet med databasetjener
+Comment[nds]=Tastkombinatschoon na Kexi-Projekt op Datenbankserver
+Comment[ne]=डाटाबेस सर्भर भित्र केक्सी परियोजनामा सर्टकट
+Comment[nl]=Snelkoppeling naar Kexi-project op Databaseserver
+Comment[nn]=Snarveg til Kexi-prosjekt på ein databasetenar
+Comment[pl]=Skrót do projektu Kexi na serwerze baz danych
+Comment[pt]=Atalho para Projecto Kexi no Servidor de Base de Dados
+Comment[pt_BR]=Atalho para Projeto do Kexi no Servidor de Banco de Dados
+Comment[ru]=Ссылка на проект Kexi
+Comment[se]=Njuolggoluotta Kexi-prošektii diehtovuođđobálvvás
+Comment[sk]=Skratka k projektu Kexi na databázovom servri
+Comment[sl]=Bližnjica do projekta Kexi na podatkovnem strežniku
+Comment[sr]=Прећица до Kexi-јевог пројекта на серверу база података
+Comment[sr@Latn]=Prećica do Kexi-jevog projekta na serveru baza podataka
+Comment[sv]=Genväg till Kexi-projekt på databasserver
+Comment[ta]= குறுவழி kexi திட்டப்பணிக் தரவுத்தள சேவையகம்
+Comment[tg]=Истинод ба лоиҳаи Kexi дар сервери асосӣ маълумот
+Comment[tr]=Veritabanı Sunucusundaki Kexi Proje'ine Kısayol
+Comment[uk]=Скорочення для проекту Kexi на сервері бази даних
+Comment[uz]=Maʼlumot bazasidagi Kexi loyihasiga bogʻlama
+Comment[uz@cyrillic]=Маълумот базасидаги Kexi лойиҳасига боғлама
+Comment[zh_CN]=数据库服务器上 Kexi 项目的快捷方式
+Comment[zh_TW]=Kexi 專案在資料庫伺服器上的捷徑
diff --git a/kexi/data/x-kexiproject-sqlite.desktop b/kexi/data/x-kexiproject-sqlite.desktop
new file mode 100644
index 000000000..e11796104
--- /dev/null
+++ b/kexi/data/x-kexiproject-sqlite.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Type=MimeType
+MimeType=application/x-kexiproject-sqlite
+Hidden=true
+Icon=kexiproject_sqlite
+Patterns=
+Comment=Kexi Database File-Based Project
+Comment[bg]=Проект на Kexi, базиран на файлове на СУБД
+Comment[ca]=Projecte Kexi en una base de dades basada en fitxers
+Comment[cy]=Cywaith Ffeil ar gyfer Cronfa Ddata Kexi
+Comment[da]=Kexi database filbaseret projekt
+Comment[de]=Dateibasiertes Kexi-Datenbankprojekt
+Comment[el]=Έργο Kexi βάσης δεδομένων βασισμένη σε αρχείο
+Comment[es]=Proyecto de base de datos de Kexi guardado en archivo
+Comment[et]=Kexi andmebaasi failipõhine projekt
+Comment[eu]=Kexi datu-baseko fitxategian oinarritutako proiektua
+Comment[fa]=پروژه بر اساس پروندۀ دادگان Kexi
+Comment[fi]=Kexin tietokantatiedostopohjainen projekti
+Comment[fr]=Projet de base de données Kexi basé sur un fichier
+Comment[fy]=Kexi Databanktriem-basearre projekt
+Comment[gl]=Proxecto de Kexi en Ficheiro de Base de Datos
+Comment[he]=פרוייקט מסד נתונים מבוסס קבצים של Kexi
+Comment[hr]=Kexi projekt baze podataka datoteka
+Comment[hu]=Kexi adatbázisfájl-alapú projekt
+Comment[is]=Kexi verkefni byggt á gagnagrunni
+Comment[it]=File di progetto banca dati con Kexi
+Comment[ja]=Kexi データベース ファイルベースのプロジェクト
+Comment[km]=គម្រោង​មូលដ្ឋាន​ទិន្នន័យ Kexi ដែល​ផ្អែក​លើ​ឯកសារ
+Comment[lv]=Uz Kexi datu bāzes faila bāzēts projekts
+Comment[ms]=Projek Pangkalan Data Kexi Berasaskan Fail
+Comment[nb]=Kexi database filbasert prosjekt
+Comment[nds]=Dateibaseert Kexi-Datenbankprojekt
+Comment[ne]=केक्सी डाटाबेस फाइल आधारित परियोजना
+Comment[nl]=Kexi Databasebestand-gebaseerd project
+Comment[nn]=Databasefilbasert Kexi-prosjekt
+Comment[pl]=Projekt Kexi zapisany w pliku
+Comment[pt]=Projecto do Kexi em Ficheiro de Base de Dados
+Comment[pt_BR]=Projeto Baseado em Arquivo de Banco de Dados do Kexi
+Comment[ru]=Проект Kexi с хранилищем данных в файлах
+Comment[se]=Kexi-diehtovuođđu fiilavuođđoduvvon prošeakta
+Comment[sk]=Súborovo orientovaný databázový projekt Kexi
+Comment[sl]=Na datotekah zasnovan projekt zbirke podatkov Kexi
+Comment[sr]=Kexi-јев пројекат базе података заснован на фајлу
+Comment[sr@Latn]=Kexi-jev projekat baze podataka zasnovan na fajlu
+Comment[sv]=Kexi filbaserat databasprojekt
+Comment[uk]=Проект Kexi із збереженням даних у файлах
+Comment[uz]=Kexi uchun fayl asosidagi maʼlumot baza loyihasi
+Comment[uz@cyrillic]=Kexi учун файл асосидаги маълумот база лойиҳаси
+Comment[zh_CN]=基于 Kexi 数据库文件的项目
+Comment[zh_TW]=Kexi 資料庫檔案式專案
diff --git a/kexi/data/x-kexiproject-sqlite2.desktop b/kexi/data/x-kexiproject-sqlite2.desktop
new file mode 100644
index 000000000..fda98e9ab
--- /dev/null
+++ b/kexi/data/x-kexiproject-sqlite2.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Type=MimeType
+MimeType=application/x-kexiproject-sqlite2
+Icon=kexiproject_sqlite2
+Patterns=*.kexi
+X-KDE-IsAlso=application/x-sqlite2
+Comment=Kexi Database File-Based Project
+Comment[bg]=Проект на Kexi, базиран на файлове на СУБД
+Comment[ca]=Projecte Kexi en una base de dades basada en fitxers
+Comment[cy]=Cywaith Ffeil ar gyfer Cronfa Ddata Kexi
+Comment[da]=Kexi database filbaseret projekt
+Comment[de]=Dateibasiertes Kexi-Datenbankprojekt
+Comment[el]=Έργο Kexi βάσης δεδομένων βασισμένη σε αρχείο
+Comment[es]=Proyecto de base de datos de Kexi guardado en archivo
+Comment[et]=Kexi andmebaasi failipõhine projekt
+Comment[eu]=Kexi datu-baseko fitxategian oinarritutako proiektua
+Comment[fa]=پروژه بر اساس پروندۀ دادگان Kexi
+Comment[fi]=Kexin tietokantatiedostopohjainen projekti
+Comment[fr]=Projet de base de données Kexi basé sur un fichier
+Comment[fy]=Kexi Databanktriem-basearre projekt
+Comment[gl]=Proxecto de Kexi en Ficheiro de Base de Datos
+Comment[he]=פרוייקט מסד נתונים מבוסס קבצים של Kexi
+Comment[hr]=Kexi projekt baze podataka datoteka
+Comment[hu]=Kexi adatbázisfájl-alapú projekt
+Comment[is]=Kexi verkefni byggt á gagnagrunni
+Comment[it]=File di progetto banca dati con Kexi
+Comment[ja]=Kexi データベース ファイルベースのプロジェクト
+Comment[km]=គម្រោង​មូលដ្ឋាន​ទិន្នន័យ Kexi ដែល​ផ្អែក​លើ​ឯកសារ
+Comment[lv]=Uz Kexi datu bāzes faila bāzēts projekts
+Comment[ms]=Projek Pangkalan Data Kexi Berasaskan Fail
+Comment[nb]=Kexi database filbasert prosjekt
+Comment[nds]=Dateibaseert Kexi-Datenbankprojekt
+Comment[ne]=केक्सी डाटाबेस फाइल आधारित परियोजना
+Comment[nl]=Kexi Databasebestand-gebaseerd project
+Comment[nn]=Databasefilbasert Kexi-prosjekt
+Comment[pl]=Projekt Kexi zapisany w pliku
+Comment[pt]=Projecto do Kexi em Ficheiro de Base de Dados
+Comment[pt_BR]=Projeto Baseado em Arquivo de Banco de Dados do Kexi
+Comment[ru]=Проект Kexi с хранилищем данных в файлах
+Comment[se]=Kexi-diehtovuođđu fiilavuođđoduvvon prošeakta
+Comment[sk]=Súborovo orientovaný databázový projekt Kexi
+Comment[sl]=Na datotekah zasnovan projekt zbirke podatkov Kexi
+Comment[sr]=Kexi-јев пројекат базе података заснован на фајлу
+Comment[sr@Latn]=Kexi-jev projekat baze podataka zasnovan na fajlu
+Comment[sv]=Kexi filbaserat databasprojekt
+Comment[uk]=Проект Kexi із збереженням даних у файлах
+Comment[uz]=Kexi uchun fayl asosidagi maʼlumot baza loyihasi
+Comment[uz@cyrillic]=Kexi учун файл асосидаги маълумот база лойиҳаси
+Comment[zh_CN]=基于 Kexi 数据库文件的项目
+Comment[zh_TW]=Kexi 資料庫檔案式專案
diff --git a/kexi/data/x-kexiproject-sqlite3.desktop b/kexi/data/x-kexiproject-sqlite3.desktop
new file mode 100644
index 000000000..deed6b38e
--- /dev/null
+++ b/kexi/data/x-kexiproject-sqlite3.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Type=MimeType
+MimeType=application/x-kexiproject-sqlite3
+Icon=kexiproject_sqlite
+Patterns=*.kexi
+X-KDE-IsAlso=application/x-sqlite3
+Comment=Kexi Database File-Based Project
+Comment[bg]=Проект на Kexi, базиран на файлове на СУБД
+Comment[ca]=Projecte Kexi en una base de dades basada en fitxers
+Comment[cy]=Cywaith Ffeil ar gyfer Cronfa Ddata Kexi
+Comment[da]=Kexi database filbaseret projekt
+Comment[de]=Dateibasiertes Kexi-Datenbankprojekt
+Comment[el]=Έργο Kexi βάσης δεδομένων βασισμένη σε αρχείο
+Comment[es]=Proyecto de base de datos de Kexi guardado en archivo
+Comment[et]=Kexi andmebaasi failipõhine projekt
+Comment[eu]=Kexi datu-baseko fitxategian oinarritutako proiektua
+Comment[fa]=پروژه بر اساس پروندۀ دادگان Kexi
+Comment[fi]=Kexin tietokantatiedostopohjainen projekti
+Comment[fr]=Projet de base de données Kexi basé sur un fichier
+Comment[fy]=Kexi Databanktriem-basearre projekt
+Comment[gl]=Proxecto de Kexi en Ficheiro de Base de Datos
+Comment[he]=פרוייקט מסד נתונים מבוסס קבצים של Kexi
+Comment[hr]=Kexi projekt baze podataka datoteka
+Comment[hu]=Kexi adatbázisfájl-alapú projekt
+Comment[is]=Kexi verkefni byggt á gagnagrunni
+Comment[it]=File di progetto banca dati con Kexi
+Comment[ja]=Kexi データベース ファイルベースのプロジェクト
+Comment[km]=គម្រោង​មូលដ្ឋាន​ទិន្នន័យ Kexi ដែល​ផ្អែក​លើ​ឯកសារ
+Comment[lv]=Uz Kexi datu bāzes faila bāzēts projekts
+Comment[ms]=Projek Pangkalan Data Kexi Berasaskan Fail
+Comment[nb]=Kexi database filbasert prosjekt
+Comment[nds]=Dateibaseert Kexi-Datenbankprojekt
+Comment[ne]=केक्सी डाटाबेस फाइल आधारित परियोजना
+Comment[nl]=Kexi Databasebestand-gebaseerd project
+Comment[nn]=Databasefilbasert Kexi-prosjekt
+Comment[pl]=Projekt Kexi zapisany w pliku
+Comment[pt]=Projecto do Kexi em Ficheiro de Base de Dados
+Comment[pt_BR]=Projeto Baseado em Arquivo de Banco de Dados do Kexi
+Comment[ru]=Проект Kexi с хранилищем данных в файлах
+Comment[se]=Kexi-diehtovuođđu fiilavuođđoduvvon prošeakta
+Comment[sk]=Súborovo orientovaný databázový projekt Kexi
+Comment[sl]=Na datotekah zasnovan projekt zbirke podatkov Kexi
+Comment[sr]=Kexi-јев пројекат базе података заснован на фајлу
+Comment[sr@Latn]=Kexi-jev projekat baze podataka zasnovan na fajlu
+Comment[sv]=Kexi filbaserat databasprojekt
+Comment[uk]=Проект Kexi із збереженням даних у файлах
+Comment[uz]=Kexi uchun fayl asosidagi maʼlumot baza loyihasi
+Comment[uz@cyrillic]=Kexi учун файл асосидаги маълумот база лойиҳаси
+Comment[zh_CN]=基于 Kexi 数据库文件的项目
+Comment[zh_TW]=Kexi 資料庫檔案式專案
diff --git a/kexi/debian/changelog b/kexi/debian/changelog
new file mode 100644
index 000000000..d0b174d3f
--- /dev/null
+++ b/kexi/debian/changelog
@@ -0,0 +1,73 @@
+kexi (0.1cvs20050408-1) unstable; urgency=low
+
+ * New CVS Snapshot
+ * new upload packages do not have missing files more and closes
+ all the related bugs (Closes: #303525, #260613, #298172)
+
+ -- Igor Genibel <igenibel@debian.org> Fri, 8 Apr 2005 09:49:27 +0200
+
+kexi (0.1cvs20050407-3) unstable; urgency=low
+
+ * Add missing ${shlibs:Depends} in control file for
+ kexi-postgresql-driver, kexi-mysql-driver, libkexi-dev
+
+ -- Igor Genibel <igenibel@debian.org> Thu, 7 Apr 2005 18:31:11 +0200
+
+kexi (0.1cvs20050407-2) unstable; urgency=low
+
+ * Add the missing files that renders the package unusable
+ * Add debian/$(package)/usr/lib/kde3 for the dh_shlibdeps calls
+
+ -- Igor Genibel <igenibel@debian.org> Thu, 7 Apr 2005 17:04:40 +0200
+
+kexi (0.1cvs20050407-1) unstable; urgency=low
+
+ * New CVS snapshot
+ * Fix bug that renders kexi uninstalable because libpqxx-2.4.1
+ has disapeared from sid (Closes: #303525)
+
+ -- Igor Genibel <igenibel@debian.org> Thu, 7 Apr 2005 11:31:32 +0200
+
+kexi (0.1cvs20050404-1) unstable; urgency=low
+
+ * New CVS snapshot (0.1 beta5+cvs20050404)
+ * First upload to unstable (Closes: #260613)
+ * split kexi package in several package
+ * Added recommends to kexi-i18n (not yet packaged)
+ * Added recommends to kexi-mdb-driver which is external to
+ upstream but present in kdenonbeta
+ * Modify the menu sections from Apps/Tools to Apps/Databases
+ (Closes: #298172)
+ * Remove patch for libtool on arm (03_libtool_arm.diff)
+ * Regenerate manpages for kexi and kformdesigner
+ * Generate new manpage for ksqlite2to3
+ * Modify sqlite and sqlite3 manpages for the use of ksqlite2 and
+ ksqlite
+
+ -- Igor Genibel <igenibel@debian.org> Tue, 5 Apr 2005 16:18:09 +0200
+
+kexi (0.1beta4-1) experimental; urgency=low
+
+ * First release uploaded in Debian (Closes: #260613)
+
+ -- Igor Genibel <igor@caramon.osa-tlse.int> Fri, 23 Jul 2004 15:25:52 +0200
+
+kexi (0.1beta4-0.2) unstable; urgency=low
+
+ * Remove non-free build-depends
+
+ -- Igor Genibel <igenibel@debian.org> Fri, 23 Jul 2004 10:06:05 +0200
+
+kexi (0.1beta4-0.1) unstable; urgency=low
+
+ * Modify the control
+
+ -- Igor Genibel <igenibel@debian.org> Wed, 21 Jul 2004 13:12:48 +0200
+
+kexi (0.1beta4-0.0) unstable; urgency=low
+
+ * Initial Release.
+ * Add patch for libtool on arm (03_libtool_arm.diff)
+ * Generate man pages for kexi and kformdesigner with kdemangen.pl
+
+ -- Igor Genibel <igenibel@debian.org> Mon, 19 Jul 2004 14:50:55 +0200
diff --git a/kexi/debian/compat b/kexi/debian/compat
new file mode 100644
index 000000000..b8626c4cf
--- /dev/null
+++ b/kexi/debian/compat
@@ -0,0 +1 @@
+4
diff --git a/kexi/debian/control b/kexi/debian/control
new file mode 100644
index 000000000..8f0159dda
--- /dev/null
+++ b/kexi/debian/control
@@ -0,0 +1,45 @@
+Source: kexi
+Section: kde
+Priority: optional
+Maintainer: Igor Genibel <igenibel@debian.org>
+Build-Depends: automake1.7, debhelper (>= 4.2.0), kdelibs4-dev (>= 4:3.2.0), flex, libpqxx-dev, libmysqlclient-dev, libreadline5-dev, cdbs
+Standards-Version: 3.6.1.0
+
+Package: kexi
+Architecture: any
+Depends: ${shlibs:Depends}
+Recommends: kexi-postgresql-driver (= ${Source-Version}), kexi-mysql-driver (= ${Source-Version}), kexi-i18n
+Suggests: kexi-mdb-driver
+Description: Integrated data management application for KDE
+ Kexi is an integrated environment for managing data. It helps creating
+ database schemas, inserting, querying and processing data.
+ .
+ The idea of this development effort came because of noticeable lack of
+ application like MS Access, FoxPro, Oracle Forms or File Maker that is
+ powerful enough, inexpensive, open standards driven and highly portable
+ between many OSes and hardware platforms.
+
+Package: kexi-postgresql-driver
+Architecture: any
+Depends: kexi (= ${Source-Version}), ${shlibs:Depends}
+Description: PostgreSQL driver for Kexi
+ The PostgreSQL driver for Kexi databases, and the PostgreSQL
+ migration driver for converting arbitrary PostgreSQL databases into
+ Kexi databases.
+
+Package: kexi-mysql-driver
+Architecture: any
+Depends: kexi (= ${Source-Version}), ${shlibs:Depends}
+Description: MySQL driver for Kexi
+ The MySQL driver for Kexi databases, and the MySQL migration driver
+ for converting arbitrary MySQL databases into Kexi databases.
+
+Package: libkexi-dev
+Architecture: any
+Depends: kexi (= ${Source-Version}), ${shlibs:Depends}
+Description: Kexi development files
+ This package is necessary for developing new Kexi drivers or Kexi
+ migration drivers. It will be needed for compiling drivers that are not
+ part of the Kexi distribution (e.g. keximdb for importing MS Access files)
+
+
diff --git a/kexi/debian/copyright b/kexi/debian/copyright
new file mode 100644
index 000000000..088c74cf7
--- /dev/null
+++ b/kexi/debian/copyright
@@ -0,0 +1,25 @@
+This package was debianized by Igor Genibel <igenibel@debian.org> on
+Sun, 28 Mar 2004 22:55:09 -0500.
+
+It was downloaded from http://www.kexi-project.org/
+
+Upstream Authors:
+ Jaroslaw Staniek / OpenOffice Polska <js@iidea.pl>
+ Lucijan Busch <lucijan@kde.org>
+ Cedric Pasteur <cedric.pasteur@free.fr>
+ Adam Pigg <adam@piggz.fsnet.co.uk>
+ Martin Ellis <martin.ellis@kdemail.net>
+ Sebastian Sauer <mail@dipe.org>
+ Christian Nitschkowski <segfault_ii@web.de>
+ Peter Simonsson <psn@linux.se>
+ Joseph Wenninger <jowenn@kde.org>
+ Seth Kurzenberg <seth@cql.com>
+ Laurent Montel <montel@kde.org>
+ Till Busch <till@bux.at>
+
+This software is copyright (c) Kexi Team
+
+You are free to distribute this software under the terms of the
+GNU General Public License. On Debian systems, the complete
+text of the GNU General Public License can be found in the file
+`/usr/share/common-licenses/GPL'.
diff --git a/kexi/debian/debiandirs b/kexi/debian/debiandirs
new file mode 100644
index 000000000..62b4b368a
--- /dev/null
+++ b/kexi/debian/debiandirs
@@ -0,0 +1,10 @@
+export kde_prefix=/usr
+export sysconfdir=/etc
+export kde_includedir=/usr/include/kde
+export infodir=/usr/share/info
+export mandir=/usr/share/man
+export qtdir=/usr/share/qt3
+export kde_cgidir=/usr/lib/cgi-bin
+export kde_confdir=/etc/kde3
+export kde_htmldir=/usr/share/doc/kde/HTML
+configkde=--disable-debug --disable-rpath --prefix=$(kde_prefix) --sysconfdir=$(sysconfdir) --includedir=$(kde_includedir) --infodir=$(infodir) --mandir=$(mandir) --with-qt-dir=$(qtdir)
diff --git a/kexi/debian/kexi-mysql-driver.install b/kexi/debian/kexi-mysql-driver.install
new file mode 100644
index 000000000..bb2916820
--- /dev/null
+++ b/kexi/debian/kexi-mysql-driver.install
@@ -0,0 +1,6 @@
+debian/tmp/usr/lib/kde3/kexidb_mysqldriver.so
+debian/tmp/usr/lib/kde3/kexidb_mysqldriver.la
+debian/tmp/usr/lib/kde3/keximigrate_mysql.so
+debian/tmp/usr/lib/kde3/keximigrate_mysql.la
+debian/tmp/usr/share/services/kexidb_mysqldriver.desktop
+debian/tmp/usr/share/services/keximigrate_mysql.desktop
diff --git a/kexi/debian/kexi-mysql-driver.postinst b/kexi/debian/kexi-mysql-driver.postinst
new file mode 100644
index 000000000..a2c66fa23
--- /dev/null
+++ b/kexi/debian/kexi-mysql-driver.postinst
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+#DEBHELPER#
diff --git a/kexi/debian/kexi-mysql-driver.postrm b/kexi/debian/kexi-mysql-driver.postrm
new file mode 100644
index 000000000..dadbb97d0
--- /dev/null
+++ b/kexi/debian/kexi-mysql-driver.postrm
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#DEBHELPER#
+
diff --git a/kexi/debian/kexi-postgresql-driver.install b/kexi/debian/kexi-postgresql-driver.install
new file mode 100644
index 000000000..d605a36c6
--- /dev/null
+++ b/kexi/debian/kexi-postgresql-driver.install
@@ -0,0 +1,8 @@
+debian/tmp/usr/lib/kde3/kexidb_pqxxsqldriver.so
+debian/tmp/usr/lib/kde3/kexidb_pqxxsqldriver.la
+debian/tmp/usr/lib/kde3/keximigrate_pqxx.so
+debian/tmp/usr/lib/kde3/keximigrate_pqxx.la
+debian/tmp/usr/share/services/kexidb_pqxxsqldriver.desktop
+debian/tmp/usr/share/services/keximigrate_pqxx.desktop
+
+debian/overrides/kexi-postgresql-driver /usr/share/lintian/overrides
diff --git a/kexi/debian/kexi-postgresql-driver.postinst b/kexi/debian/kexi-postgresql-driver.postinst
new file mode 100644
index 000000000..a2c66fa23
--- /dev/null
+++ b/kexi/debian/kexi-postgresql-driver.postinst
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+#DEBHELPER#
diff --git a/kexi/debian/kexi-postgresql-driver.postrm b/kexi/debian/kexi-postgresql-driver.postrm
new file mode 100644
index 000000000..dadbb97d0
--- /dev/null
+++ b/kexi/debian/kexi-postgresql-driver.postrm
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#DEBHELPER#
+
diff --git a/kexi/debian/kexi.install b/kexi/debian/kexi.install
new file mode 100644
index 000000000..ddde726c6
--- /dev/null
+++ b/kexi/debian/kexi.install
@@ -0,0 +1,124 @@
+debian/tmp/usr/bin/ksqlite
+debian/tmp/usr/bin/ksqlite2to3
+debian/tmp/usr/bin/ksqlite2
+debian/tmp/usr/bin/kformdesigner
+debian/tmp/usr/bin/kexi
+
+debian/tmp/usr/lib/libkdeinit_kexi.la
+debian/tmp/usr/lib/libkdeinit_kexi.so
+debian/tmp/usr/lib/libkexicore.la
+debian/tmp/usr/lib/libkexidatatable.la
+debian/tmp/usr/lib/libkexiextendedwidgets.la
+debian/tmp/usr/lib/libkexiformutils.la
+debian/tmp/usr/lib/libkexiguiutils.la
+debian/tmp/usr/lib/libkeximain.la
+debian/tmp/usr/lib/libkeximigrate.la
+debian/tmp/usr/lib/libkexipropertyeditor.la
+debian/tmp/usr/lib/libkexirelationsview.la
+debian/tmp/usr/lib/libkexisql2.la
+debian/tmp/usr/lib/libkexisql3.la
+debian/tmp/usr/lib/libkformdesigner.la
+debian/tmp/usr/lib/kde3/kexidb_sqlite2driver.la
+debian/tmp/usr/lib/kde3/kexidb_sqlite3driver.la
+debian/tmp/usr/lib/kde3/containers.la
+debian/tmp/usr/lib/kde3/stdwidgets.la
+debian/tmp/usr/lib/kde3/libkformdesigner_part.la
+debian/tmp/usr/lib/kde3/kexihandler_form.la
+debian/tmp/usr/lib/kde3/kexihandler_migration.la
+debian/tmp/usr/lib/kde3/kexihandler_query.la
+debian/tmp/usr/lib/kde3/kexihandler_relation.la
+debian/tmp/usr/lib/kde3/kexihandler_table.la
+debian/tmp/usr/lib/kde3/kexidbwidgets.la
+debian/tmp/usr/lib/kde3/kexi.la
+debian/tmp/usr/lib/libkexisql3.so.3.0.0
+debian/tmp/usr/lib/libkexisql3.so.3
+debian/tmp/usr/lib/libkexisql3.so
+debian/tmp/usr/lib/libkexisql2.so.2.0.8
+debian/tmp/usr/lib/libkexisql2.so.2
+debian/tmp/usr/lib/libkexisql2.so
+debian/tmp/usr/lib/libkexidb.so.0.0.0
+debian/tmp/usr/lib/libkexidb.so.0
+debian/tmp/usr/lib/libkexidbparser.so.0.0.1
+debian/tmp/usr/lib/libkexidbparser.so.0
+debian/tmp/usr/lib/libkexicore.so.0.0.1
+debian/tmp/usr/lib/libkexicore.so.0
+debian/tmp/usr/lib/libkexicore.so
+debian/tmp/usr/lib/libkexiguiutils.so.0.0.1
+debian/tmp/usr/lib/libkexiguiutils.so.0
+debian/tmp/usr/lib/libkexiguiutils.so
+debian/tmp/usr/lib/libkexidatatable.so.0.0.1
+debian/tmp/usr/lib/libkexidatatable.so.0
+debian/tmp/usr/lib/libkexidatatable.so
+debian/tmp/usr/lib/libkexiextendedwidgets.so.0.0.1
+debian/tmp/usr/lib/libkexiextendedwidgets.so.0
+debian/tmp/usr/lib/libkexiextendedwidgets.so
+debian/tmp/usr/lib/libkexipropertyeditor.so.0.0.1
+debian/tmp/usr/lib/libkexipropertyeditor.so.0
+debian/tmp/usr/lib/libkexipropertyeditor.so
+debian/tmp/usr/lib/libkexirelationsview.so.0.0.1
+debian/tmp/usr/lib/libkexirelationsview.so.0
+debian/tmp/usr/lib/libkexirelationsview.so
+debian/tmp/usr/lib/libkformdesigner.so.0.0.1
+debian/tmp/usr/lib/libkformdesigner.so.0
+debian/tmp/usr/lib/libkformdesigner.so
+debian/tmp/usr/lib/libkeximain.so.0.0.1
+debian/tmp/usr/lib/libkeximain.so.0
+debian/tmp/usr/lib/libkeximain.so
+debian/tmp/usr/lib/libkeximigrate.so.0.0.1
+debian/tmp/usr/lib/libkeximigrate.so.0
+debian/tmp/usr/lib/libkeximigrate.so
+debian/tmp/usr/lib/libkexiformutils.so.0.0.1
+debian/tmp/usr/lib/libkexiformutils.so.0
+debian/tmp/usr/lib/libkexiformutils.so
+
+
+
+debian/tmp/usr/lib/kde3/kexidb_sqlite*
+debian/tmp/usr/lib/kde3/containers*
+debian/tmp/usr/lib/kde3/stdwidgets*
+debian/tmp/usr/lib/kde3/lib*
+debian/tmp/usr/lib/kde3/kexihandler*
+debian/tmp/usr/lib/kde3/kexidbwidgets*
+debian/tmp/usr/lib/kde3/kexi.*
+debian/tmp/usr/share/applications/
+debian/tmp/usr/share/icons/
+debian/tmp/usr/share/mimelnk/
+debian/tmp/usr/share/apps/kexi/
+debian/tmp/usr/share/apps/kformdesigner_part/kformdesigner_part*
+debian/tmp/usr/share/apps/kformdesigner/kfd_mainwindow.rc
+debian/tmp/usr/share/applnk/
+debian/tmp/usr/share/services/kexi/
+debian/tmp/usr/share/services/kformdesigner*
+
+debian/tmp/usr/share/applications/kde/kexi.desktop
+debian/tmp/usr/share/mimelnk/application/x-kexiproject-sqlite.desktop
+debian/tmp/usr/share/mimelnk/application/x-kexiproject-sqlite2.desktop
+debian/tmp/usr/share/mimelnk/application/x-kexiproject-sqlite3.desktop
+debian/tmp/usr/share/mimelnk/application/x-kexiproject-shortcut.desktop
+debian/tmp/usr/share/applnk/Utilities/kformdesigner.desktop
+debian/tmp/usr/share/services/kexi/kexitablehandler.desktop
+debian/tmp/usr/share/services/kexi/kexiqueryhandler.desktop
+debian/tmp/usr/share/services/kexi/kexirelationhandler.desktop
+debian/tmp/usr/share/services/kexi/kexiformhandler.desktop
+debian/tmp/usr/share/services/kexi/kexireporthandler.desktop
+debian/tmp/usr/share/services/kexi/keximigrationhandler.desktop
+debian/tmp/usr/share/services/kformdesigner/containerfactory.desktop
+debian/tmp/usr/share/services/kformdesigner/stdwidgets.desktop
+debian/tmp/usr/share/services/kformdesigner/kexidbfactory.desktop
+debian/tmp/usr/share/services/kformdesigner_part.desktop
+debian/tmp/usr/share/services/kexidb_sqlite2driver.desktop
+debian/tmp/usr/share/services/kexidb_sqlite3driver.desktop
+debian/tmp/usr/share/servicetypes/kexidb_driver.desktop
+debian/tmp/usr/share/servicetypes/widgetfactory.desktop
+debian/tmp/usr/share/servicetypes/kexihandler.desktop
+debian/tmp/usr/share/servicetypes/kexitablefilter.desktop
+debian/tmp/usr/share/servicetypes/kexiimportfilter.desktop
+debian/tmp/usr/share/servicetypes/kexifileimportfilter.desktop
+debian/tmp/usr/share/servicetypes/kexiserverimportfilter.desktop
+debian/tmp/usr/share/servicetypes/keximigration_driver.desktop
+
+
+debian/tmp/usr/share/servicetypes/
+debian/tmp/etc/*
+
+debian/overrides/kexi /usr/share/lintian/overrides
diff --git a/kexi/debian/kexi.manpages b/kexi/debian/kexi.manpages
new file mode 100644
index 000000000..21af74412
--- /dev/null
+++ b/kexi/debian/kexi.manpages
@@ -0,0 +1,5 @@
+debian/man/kexi.1
+debian/man/kformdesigner.1
+debian/man/ksqlite.1
+debian/man/ksqlite2.1
+debian/man/ksqlite2to3.1
diff --git a/kexi/debian/kexi.menu b/kexi/debian/kexi.menu
new file mode 100644
index 000000000..df28b506f
--- /dev/null
+++ b/kexi/debian/kexi.menu
@@ -0,0 +1,6 @@
+?package(kexi):\
+ needs="X11"\
+ section="Apps/Databases"\
+ hints="KDE"\
+ title="Kexi"\
+ command="/usr/bin/kexi"
diff --git a/kexi/debian/kexi.postinst b/kexi/debian/kexi.postinst
new file mode 100644
index 000000000..0f31771ec
--- /dev/null
+++ b/kexi/debian/kexi.postinst
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+if [ "$1" = "configure" ]
+then
+ ldconfig
+fi
+
+#DEBHELPER#
diff --git a/kexi/debian/kexi.postrm b/kexi/debian/kexi.postrm
new file mode 100644
index 000000000..edd1e9873
--- /dev/null
+++ b/kexi/debian/kexi.postrm
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+if [ "$1" = "remove" ]
+then
+ ldconfig
+fi
+
+#DEBHELPER#
+
diff --git a/kexi/debian/kformdesigner.menu b/kexi/debian/kformdesigner.menu
new file mode 100644
index 000000000..57378ecfb
--- /dev/null
+++ b/kexi/debian/kformdesigner.menu
@@ -0,0 +1,6 @@
+?package(kexi):\
+ needs="X11"\
+ section="Apps/Databases"\
+ hints="KDE"\
+ title="KFormDesigner"\
+ command="/usr/bin/kformdesigner"
diff --git a/kexi/debian/libkexi-dev.install b/kexi/debian/libkexi-dev.install
new file mode 100644
index 000000000..27f2a0a73
--- /dev/null
+++ b/kexi/debian/libkexi-dev.install
@@ -0,0 +1,5 @@
+debian/tmp/usr/include/kde/kexidb/
+debian/tmp/usr/lib/libkexidb.la
+debian/tmp/usr/lib/libkexidb.so
+debian/tmp/usr/lib/libkexidbparser.so
+debian/tmp/usr/lib/libkexidbparser.la
diff --git a/kexi/debian/libkexi-dev.postinst b/kexi/debian/libkexi-dev.postinst
new file mode 100644
index 000000000..a2c66fa23
--- /dev/null
+++ b/kexi/debian/libkexi-dev.postinst
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+#DEBHELPER#
diff --git a/kexi/debian/libkexi-dev.postrm b/kexi/debian/libkexi-dev.postrm
new file mode 100644
index 000000000..dadbb97d0
--- /dev/null
+++ b/kexi/debian/libkexi-dev.postrm
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#DEBHELPER#
+
diff --git a/kexi/debian/man/kexi.1 b/kexi/debian/man/kexi.1
new file mode 100644
index 000000000..a01ad87e6
--- /dev/null
+++ b/kexi/debian/man/kexi.1
@@ -0,0 +1,266 @@
+.\" This file was generated by kdemangen.pl
+.TH KEXI 1 "Apr 2005" "K Desktop Environment" "Database creation for everyone"
+.SH NAME
+kexi
+- Database creation for everyone
+.SH SYNOPSIS
+kexi [Qt-options] [KDE-options] [options] [database-name]
+.SH DESCRIPTION
+Database creation for everyone
+.SH OPTIONS
+.SS
+.SS Arguments:
+database-name Database project filename
+or shortcut filename
+or database name on a server to open
+.SS Options related to entire projects:
+.TP
+.B --createdb
+Create a new, blank project using specified
+database driver and database name
+and exit immediately.
+You will be asked for confirmation
+if overwriting is needed.
+.TP
+.B --create-opendb
+Like --createdb, but also open newly
+created database.
+
+.TP
+.B --dropdb
+Drop (remove) a project using specified
+database driver and database name.
+You will be asked for confirmation.
+.TP
+.B --drv, --dbdriver <name>
+Database driver to be used
+for connecting to a database project
+(SQLite by default).
+Ignored if a shortcut filename
+is provided.
+.TP
+.B -t, --type <name>
+Specifies a type of a file provided
+as argument. This option is useful
+if your filename has not set a valid
+extension and it's type cannot be detected
+unambiguously by looking at it's contents.
+This option is ignored if no file
+is specified as an argument.
+Available file types are:
+- "project" for a project file (the default)
+- "shortcut" for a shortcut file pointing
+you to a project.
+
+.SS
+.SS Options related to opening objects within a project:
+.TP
+.B --open
+[<object_type>:]<object_name>
+Open object of type <object_type>
+and name <object_name> from specified project
+on application start.
+<object_type>: is optional, if omitted - table
+type is assumed.
+Other object types can be query, report, form,
+script (may be more or less, depending on your
+plugins installed).
+Use "" chars to specify names containing spaces.
+Examples: --open MyTable,
+--open query:"My very big query"
+.TP
+.B --design
+[<object_type>:]<object_name>
+Like --open, but the object will
+be opened in Design Mode, if one is available
+.TP
+.B --edittext
+[<object_type>:]<object_name>
+Like --open, but the object will
+be opened in Text Mode, if one is available
+.TP
+.B --new
+<object_type> Start new object design of type <object_type>
+.SS
+.SS Options related to database servers:
+.TP
+.B -u, --user <name>
+User name to be used
+for connecting to a database project.
+Ignored if a shortcut filename
+is provided.
+.TP
+.B -h, --host <name>
+Server (host) name to be used
+for connecting to a database project.
+Ignored if a shortcut filename
+is provided.
+.TP
+.B --port <number>
+Server's port name to be used
+for connecting to a database project.
+Ignored if a shortcut filename
+is provided.
+.TP
+.B --local-socket <filename>
+Server's local socket filename
+to be used for connecting to a database
+project. Ignored if a shortcut filename
+is provided.
+.SS
+.SS Generic options:
+.TP
+.B --help
+Show help about options
+.TP
+.B --help-qt
+Show Qt specific options
+.TP
+.B --help-kde
+Show KDE specific options
+.TP
+.B --help-all
+Show all options
+.TP
+.B --author
+Show author information
+.TP
+.B -v, --version
+Show version information
+.TP
+.B --license
+Show license information
+.TP
+.B --
+End of options
+.SS
+.SS KDE options:
+.TP
+.B --caption <caption>
+Use 'caption' as name in the titlebar
+.TP
+.B --icon <icon>
+Use 'icon' as the application icon
+.TP
+.B --miniicon <icon>
+Use 'icon' as the icon in the titlebar
+.TP
+.B --config <filename>
+Use alternative configuration file
+.TP
+.B --dcopserver <server>
+Use the DCOP Server specified by 'server'
+.TP
+.B --nocrashhandler
+Disable crash handler, to get core dumps
+.TP
+.B --waitforwm
+Waits for a WM_NET compatible windowmanager
+.TP
+.B --style <style>
+sets the application GUI style
+.TP
+.B --geometry <geometry>
+sets the client geometry of the main widget - see man X for the argument format
+.SS
+.SS Qt options:
+.TP
+.B --display <displayname>
+Use the X-server display 'displayname'
+.TP
+.B --session <sessionId>
+Restore the application for the given 'sessionId'
+.TP
+.B --cmap
+Causes the application to install a private color
+map on an 8-bit display
+.TP
+.B --ncols <count>
+Limits the number of colors allocated in the color
+cube on an 8-bit display, if the application is
+using the QApplication::ManyColor color
+specification
+.TP
+.B --nograb
+tells Qt to never grab the mouse or the keyboard
+.TP
+.B --dograb
+running under a debugger can cause an implicit
+-nograb, use -dograb to override
+.TP
+.B --sync
+switches to synchronous mode for debugging
+.TP
+.B --fn, --font <fontname>
+defines the application font
+.TP
+.B --bg, --background <color>
+sets the default background color and an
+application palette (light and dark shades are
+calculated)
+.TP
+.B --fg, --foreground <color>
+sets the default foreground color
+.TP
+.B --btn, --button <color>
+sets the default button color
+.TP
+.B --name <name>
+sets the application name
+.TP
+.B --title <title>
+sets the application title (caption)
+.TP
+.B --visual TrueColor
+forces the application to use a TrueColor visual on
+an 8-bit display
+.TP
+.B --inputstyle <inputstyle>
+sets XIM (X Input Method) input style. Possible
+values are onthespot, overthespot, offthespot and
+root
+.TP
+.B --im <XIM server>
+set XIM server
+.TP
+.B --noxim
+disable XIM
+.TP
+.B --reverse
+mirrors the whole layout of widgets
+.SS
+
+.SH SEE ALSO
+Full user documentation is available through the KDE Help Center. You can also enter the URL
+.BR help:/kexi/
+directly into konqueror or you can run
+.BR "`khelpcenter help:/kexi/'"
+from the command-line.
+.br
+.SH AUTHORS
+.nf
+Jaroslaw Staniek / OpenOffice Polska <js@iidea.pl>
+.br
+Lucijan Busch <lucijan@kde.org>
+.br
+Cedric Pasteur <cedric.pasteur@free.fr>
+.br
+Adam Pigg <adam@piggz.fsnet.co.uk>
+.br
+Martin Ellis <martin.ellis@kdemail.net>
+.br
+Sebastian Sauer <mail@dipe.org>
+.br
+Christian Nitschkowski <segfault_ii@web.de>
+.br
+Peter Simonsson <psn@linux.se>
+.br
+Joseph Wenninger <jowenn@kde.org>
+.br
+Seth Kurzenberg <seth@cql.com>
+.br
+Laurent Montel <montel@kde.org>
+.br
+Till Busch <till@bux.at>
+.br
+
diff --git a/kexi/debian/man/kformdesigner.1 b/kexi/debian/man/kformdesigner.1
new file mode 100644
index 000000000..3b018a312
--- /dev/null
+++ b/kexi/debian/man/kformdesigner.1
@@ -0,0 +1,151 @@
+.\" This file was generated by kdemangen.pl
+.TH KFORMDESIGNER 1 "Jui 2004" "K Desktop Environment" "KFormDesigner"
+.SH NAME
+kformdesigner
+- KFormDesigner
+.SH SYNOPSIS
+kformdesigner [Qt-options] [KDE-options] [URL]
+.SH DESCRIPTION
+KFormDesigner
+.SH OPTIONS
+.SS
+.SS Arguments:
+.TP
+.B URL
+Document to open
+.SS Generic options:
+.TP
+.B --help
+Show help about options
+.TP
+.B --help-qt
+Show Qt specific options
+.TP
+.B --help-kde
+Show KDE specific options
+.TP
+.B --help-all
+Show all options
+.TP
+.B --author
+Show author information
+.TP
+.B -v, --version
+Show version information
+.TP
+.B --license
+Show license information
+.TP
+.B --
+End of options
+.SS
+.SS KDE options:
+.TP
+.B --caption <caption>
+Use 'caption' as name in the titlebar.
+.TP
+.B --icon <icon>
+Use 'icon' as the application icon.
+.TP
+.B --miniicon <icon>
+Use 'icon' as the icon in the titlebar.
+.TP
+.B --config <filename>
+Use alternative configuration file.
+.TP
+.B --dcopserver <server>
+Use the DCOP Server specified by 'server'.
+.TP
+.B --nocrashhandler
+Disable crash handler, to get core dumps.
+.TP
+.B --waitforwm
+Waits for a WM_NET compatible windowmanager.
+.TP
+.B --style <style>
+sets the application GUI style.
+.TP
+.B --geometry <geometry>
+sets the client geometry of the main widget.
+.SS
+.SS Qt options:
+.TP
+.B --display <displayname>
+Use the X-server display 'displayname'.
+.TP
+.B --session <sessionId>
+Restore the application for the given 'sessionId'.
+.TP
+.B --cmap
+Causes the application to install a private color
+map on an 8-bit display.
+.TP
+.B --ncols <count>
+Limits the number of colors allocated in the color
+cube on an 8-bit display, if the application is
+using the QApplication::ManyColor color
+specification.
+.TP
+.B --nograb
+tells Qt to never grab the mouse or the keyboard.
+.TP
+.B --dograb
+running under a debugger can cause an implicit
+-nograb, use -dograb to override.
+.TP
+.B --sync
+switches to synchronous mode for debugging.
+.TP
+.B --fn, --font <fontname>
+defines the application font.
+.TP
+.B --bg, --background <color>
+sets the default background color and an
+application palette (light and dark shades are
+calculated).
+.TP
+.B --fg, --foreground <color>
+sets the default foreground color.
+.TP
+.B --btn, --button <color>
+sets the default button color.
+.TP
+.B --name <name>
+sets the application name.
+.TP
+.B --title <title>
+sets the application title (caption).
+.TP
+.B --visual TrueColor
+forces the application to use a TrueColor visual on
+an 8-bit display.
+.TP
+.B --inputstyle <inputstyle>
+sets XIM (X Input Method) input style. Possible
+values are onthespot, overthespot, offthespot and
+root.
+.TP
+.B --im <XIM server>
+set XIM server.
+.TP
+.B --noxim
+disable XIM.
+.TP
+.B --reverse
+mirrors the whole layout of widgets.
+.SS
+
+.SH SEE ALSO
+Full user documentation is available through the KDE Help Center. You can also enter the URL
+.BR help:/kformdesigner/
+directly into konqueror or you can run
+.BR "`khelpcenter help:/kformdesigner/'"
+from the command-line.
+.br
+.SH AUTHORS
+.nf
+Lucijan Busch <lucijan@kde.org>
+.br
+Cedric Pasteur <cedric.pasteur@free.fr>
+.br
+
diff --git a/kexi/debian/man/ksqlite.1 b/kexi/debian/man/ksqlite.1
new file mode 100644
index 000000000..86b10795d
--- /dev/null
+++ b/kexi/debian/man/ksqlite.1
@@ -0,0 +1,230 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH KSQLITE 1 "Tue Apr 5 16:38:35 CEST 2005"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+.B ksqlite
+\- A command line interface for SQLite version 3
+
+.SH SYNOPSIS
+.B ksqlite
+.RI [ options ]
+.RI [ databasefile ]
+.RI [ SQL ]
+
+.SH SUMMARY
+.PP
+.B ksqlite
+is a terminal-based front-end to the SQLite library that can evaluate
+queries interactively and display the results in multiple formats.
+.B ksqlite
+can also be used within shell scripts and other applications to provide
+batch processing features.
+
+.SH DESCRIPTION
+To start a
+.B ksqlite
+interactive session, invoke the
+.B ksqlite
+command and optionally provide the name of a database file. If the
+database file does not exist, it will be created. If the database file
+does exist, it will be opened.
+
+For example, to create a new database file named "mydata.db", create
+a table named "memos" and insert a couple of records into that table:
+.sp
+$
+.B ksqlite mydata.db
+.br
+SQLite version 3.0.7 (bundled with Kexi)
+.br
+Enter ".help" for instructions
+.br
+sqlite>
+.B create table memos(text, priority INTEGER);
+.br
+sqlite>
+.B insert into memos values('deliver project description', 10);
+.br
+sqlite>
+.B insert into memos values('lunch with Christine', 100);
+.br
+sqlite>
+.B select * from memos;
+.br
+deliver project description|10
+.br
+lunch with Christine|100
+.br
+sqlite>
+.sp
+
+If no database name is supplied, the ATTACH sql command can be used
+to attach to existing or create new database files. ATTACH can also
+be used to attach to multiple databases within the same interactive
+session. This is useful for migrating data between databases,
+possibly changing the schema along the way.
+
+Optionally, a SQL statement or set of SQL statements can be supplied as
+a single argument. Multiple statements should be separated by
+semi-colons.
+
+For example:
+.sp
+$
+.B ksqlite -line mydata.db 'select * from memos where priority > 20;'
+.br
+ text = lunch with Christine
+.br
+priority = 100
+.br
+.sp
+
+.SS SQLITE META-COMMANDS
+.PP
+The interactive interpreter offers a set of meta-commands that can be
+used to control the output format, examine the currently attached
+database files, or perform administrative operations upon the
+attached databases (such as rebuilding indices). Meta-commands are
+always prefixed with a dot (.).
+
+A list of available meta-commands can be viewed at any time by issuing
+the '.help' command. For example:
+.sp
+sqlite>
+.B .help
+.nf
+.cc |
+.databases List names and files of attached databases
+.dump ?TABLE? ... Dump the database in an SQL text format
+.echo ON|OFF Turn command echo on or off
+.exit Exit this program
+.explain ON|OFF Turn output mode suitable for EXPLAIN on or off.
+.header(s) ON|OFF Turn display of headers on or off
+.help Show this message
+.import FILE TABLE Import data from FILE into TABLE
+.indices TABLE Show names of all indices on TABLE
+.mode MODE ?TABLE? Set output mode where MODE is one of:
+ csv Comma-separated values
+ column Left-aligned columns. (See .width)
+ html HTML <table> code
+ insert SQL insert statements for TABLE
+ line One value per line
+ list Values delimited by .separator string
+ tabs Tab-separated values
+ tcl TCL list elements
+.nullvalue STRING Print STRING in place of NULL values
+.output FILENAME Send output to FILENAME
+.output stdout Send output to the screen
+.prompt MAIN CONTINUE Replace the standard prompts
+.quit Exit this program
+.read FILENAME Execute SQL in FILENAME
+.schema ?TABLE? Show the CREATE statements
+.separator STRING Change separator used by output mode and .import
+.show Show the current values for various settings
+.tables ?PATTERN? List names of tables matching a LIKE pattern
+.timeout MS Try opening locked tables for MS milliseconds
+.width NUM NUM ... Set column widths for "column" mode
+sqlite>
+|cc .
+.sp
+.fi
+
+.SH OPTIONS
+.B ksqlite
+has the following options:
+.TP
+.BI \-init\ file
+Read and execute commands from
+.I file
+, which can contain a mix of SQL statements and meta-commands.
+.TP
+.B \-echo
+Print commands before execution.
+.TP
+.B \-[no]header
+Turn headers on or off.
+.TP
+.B \-column
+Query results will be displayed in a table like form, using
+whitespace characters to separate the columns and align the
+output.
+.TP
+.B \-html
+Query results will be output as simple HTML tables.
+.TP
+.B \-line
+Query results will be displayed with one value per line, rows
+separated by a blank line. Designed to be easily parsed by
+scripts or other programs
+.TP
+.B \-list
+Query results will be displayed with the separator (|, by default)
+character between each field value. The default.
+.TP
+.BI \-separator\ separator
+Set output field separator. Default is '|'.
+.TP
+.BI \-nullvalue\ string
+Set string used to represent NULL values. Default is ''
+(empty string).
+.TP
+.B \-version
+Show SQLite version.
+.TP
+.B \-help
+Show help on options and exit.
+
+
+.SH INIT FILE
+.B ksqlite
+reads an initialization file to set the configuration of the
+interactive environment. Throughout initialization, any previously
+specified setting can be overridden. The sequence of initialization is
+as follows:
+
+o The default configuration is established as follows:
+
+.sp
+.nf
+.cc |
+mode = LIST
+separator = "|"
+main prompt = "sqlite> "
+continue prompt = " ...> "
+|cc .
+.sp
+.fi
+
+o If the file
+.B ~/.sqliterc
+exists, it is processed first.
+can be found in the user's home directory, it is
+read and processed. It should generally only contain meta-commands.
+
+o If the -init option is present, the specified file is processed.
+
+o All other command line options are processed.
+
+.SH SEE ALSO
+http://www.sqlite.org/
+http://www.kexi-project.org/
+
+.SH AUTHOR
+This manual page was originally written by Andreas Rottmann
+<rotty@debian.org>, for the Debian GNU/Linux system (but may be used
+by others). It was subsequently revised by Bill Bumgarner <bbum@mac.com>.
+It was adapted to Kexi by Igor Genibel <igenibel@debian.org>
diff --git a/kexi/debian/man/ksqlite2.1 b/kexi/debian/man/ksqlite2.1
new file mode 100644
index 000000000..50e1afd41
--- /dev/null
+++ b/kexi/debian/man/ksqlite2.1
@@ -0,0 +1,203 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH KSQLITE2 1 "Tue Apr 5 16:38:35 CEST 2005"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+ksqlite2 \- A command line interface for SQLite
+.SH SYNOPSIS
+.B ksqlite2
+.RI [ options ] " filename " [ SQL ]
+.SS SUMMARY
+.PP
+ksqlite2 is a terminal-based front-end to the SQLite library. It enables
+you to type in queries interactively, issue them to SQLite and see the
+results. Alternatively, you can specify SQL code on the command-line. In
+addition it provides a number of meta-commands.
+
+.SH DESCRIPTION
+This manual page documents briefly the
+.B ksqlite2
+command.
+This manual page was written for the Debian GNU/Linux distribution
+because the original program does not have a manual page.
+.SS GETTING STARTED
+.PP
+To start the ksqlite2 program, just type "ksqlite2" followed by the name
+the file that holds the SQLite database. If the file does not exist, a
+new one is created automatically. The ksqlite2 program will then prompt
+you to enter SQL. Type in SQL statements (terminated by a semicolon),
+press "Enter" and the SQL will be executed.
+
+For example, to create a new SQLite database named "ex1" with a single
+table named "tbl1", you might do this:
+.sp
+.nf
+$ ksqlite2 ex1
+SQLite version 2.8.15 (bundled with Kexi)
+Enter ".help" for instructions
+sqlite> create table tbl1(one varchar(10), two smallint);
+sqlite> insert into tbl1 values('hello!',10);
+sqlite> insert into tbl1 values('goodbye', 20);
+sqlite> select * from tbl1;
+hello!|10
+goodbye|20
+sqlite>
+.sp
+.fi
+
+.SS SQLITE META-COMMANDS
+.PP
+Most of the time, ksqlite2 just reads lines of input and passes them on
+to the SQLite library for execution. But if an input line begins with
+a dot ("."), then that line is intercepted and interpreted by the
+ksqlite2 program itself. These "dot commands" are typically used to
+change the output format of queries, or to execute certain prepackaged
+query statements.
+
+For a listing of the available dot commands, you can enter ".help" at
+any time. For example:
+.sp
+.nf
+.cc |
+sqlite> .help
+.dump ?TABLE? ... Dump the database in an text format
+.echo ON|OFF Turn command echo on or off
+.exit Exit this program
+.explain ON|OFF Turn output mode suitable for EXPLAIN on or off.
+ "off" will revert to the output mode that was
+ previously in effect
+.header(s) ON|OFF Turn display of headers on or off
+.help Show this message
+.indices TABLE Show names of all indices on TABLE
+.mode MODE Set mode to one of "line(s)", "column(s)",
+ "insert", "list", or "html"
+.mode insert TABLE Generate SQL insert statements for TABLE
+.nullvalue STRING Print STRING instead of nothing for NULL data
+.output FILENAME Send output to FILENAME
+.output stdout Send output to the screen
+.prompt MAIN CONTINUE Replace the standard prompts
+ "ksqlite2 > " and " ...> "
+ with the strings MAIN and CONTINUE
+ CONTINUE is optional.
+.quit Exit this program
+.read FILENAME Execute SQL in FILENAME
+.reindex ?TABLE? Rebuild indices
+.schema ?TABLE? Show the CREATE statements
+.separator STRING Change separator string for "list" mode
+.show Show the current values for the following:
+ .echo
+ .explain
+ .mode
+ .nullvalue
+ .output
+ .separator
+ .width
+.tables ?PATTERN? List names of tables matching a pattern
+.timeout MS Try opening locked tables for MS milliseconds
+.width NUM NUM ... Set column widths for "column" mode
+sqlite>
+|cc .
+.sp
+.fi
+
+.SH OPTIONS
+The program has the following options:
+.TP
+.BI \-init\ file
+Read in and process 'file', which contains "dot commands".
+You can use this file to initialize display settings.
+.TP
+.B \-html
+Set output mode to HTML.
+.TP
+.B \-list
+Set output mode to 'list'.
+.TP
+.B \-line
+Set output mode to 'line'.
+.TP
+.B \-column
+Set output mode to 'column'.
+.TP
+.BI \-separator\ separator
+Specify which output field separator for 'list' mode to use.
+Default is '|'.
+.TP
+.BI \-nullvalue\ string
+When a null is encountered, print 'string'. Default is no string.
+.TP
+.B \-[no]header
+Turn headers on or off. Default is off.
+.TP
+.B \-echo
+Print commands before execution.
+
+
+.SH OUTPUT MODE
+The SQLite program has different output modes, which define the way
+the output (from queries) is formatted.
+
+In 'list' mode, which is the default, one record per line is output,
+each field separated by the separator specified with the
+\fB-separator\fP option or \fB.separator\fP command.
+
+In 'line' mode, each column is output on its own line, records are
+separated by blank lines.
+
+In HTML mode, an XHTML table is generated.
+
+In 'column' mode, one record per line is output, aligned neatly in colums.
+
+.SH INIT FILE
+ksqlite2 can be initialized using resource files. These can be combined with
+command line arguments to set up ksqlite2 exactly the way you want it.
+Initialization proceeds as follows:
+
+o The defaults of
+
+.sp
+.nf
+.cc |
+mode = LIST
+separator = "|"
+main prompt = "sqlite> "
+continue prompt = " ...> "
+|cc .
+.sp
+.fi
+
+are established.
+
+o If a file .sqliterc can be found in the user's home directory, it is
+read and processed. It should only contain "dot commands". If the
+file is not found or cannot be read, processing continues without
+notification.
+
+o If a file is specified on the command line with the -init option, it
+is processed in the same manner as .sqliterc
+
+o All other command line options are processed
+
+o The database is opened and you are now ready to begin.
+
+.SH SEE ALSO
+http://www.hwaci.com/sw/sqlite/
+http://www.kexi-project.org/
+
+.SH AUTHOR
+This manual page was originally written by Andreas Rottmann
+<rotty@debian.org>, for the Debian GNU/Linux system (but may be used
+by others). It was adapted to Kexi by Igor Genibel <igenibel@debian.org>
diff --git a/kexi/debian/man/ksqlite2to3.1 b/kexi/debian/man/ksqlite2to3.1
new file mode 100644
index 000000000..fb28e3906
--- /dev/null
+++ b/kexi/debian/man/ksqlite2to3.1
@@ -0,0 +1,22 @@
+.TH KSQLITE2TO3 1 "Tue Apr 5 16:38:35 CEST 2005"
+.SH NAME
+.B ksqlite2to3
+\- A script used to migrate kexi sqlite version 2 databases to version 3
+
+.SH SYNOPSIS
+ksqlite2to3 <sqlite2-db-file>
+.SH DESCRIPTION
+This tool is needed if only if you have generated sqlite2 database for Kexi.
+Note this tool is currently Kexi-independent. You can use it with regular
+SQLite2 files.
+
+.SH SEE ALSO
+http://www.sqlite.org/
+http://www.kexi-project.org/
+
+.SH AUTHOR
+This manual page was originally written by Igor Genibel
+<igenibel@debian.org>, for the Debian GNU/Linux system (but may be used
+by others).
+
+
diff --git a/kexi/debian/overrides/kexi b/kexi/debian/overrides/kexi
new file mode 100644
index 000000000..0a779192b
--- /dev/null
+++ b/kexi/debian/overrides/kexi
@@ -0,0 +1,15 @@
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexicore.so.0.0.1 usr/lib/libkexicore.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexidatatable.so.0.0.1 usr/lib/libkexidatatable.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexidbparser.so.0.0.0 usr/lib/libkexidbparser.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexidb.so.0.0.0 usr/lib/libkexidb.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexiextendedwidgets.so.0.0.1 usr/lib/libkexiextendedwidgets.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexiformutils.so.0.0.1 usr/lib/libkexiformutils.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexiguiutils.so.0.0.1 usr/lib/libkexiguiutils.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkeximain.so.0.0.1 usr/lib/libkeximain.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkeximigrate.so.0.0.1 usr/lib/libkeximigrate.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexipropertyeditor.so.0.0.1 usr/lib/libkexipropertyeditor.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexirelationsview.so.0.0.1 usr/lib/libkexirelationsview.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexisql2.so.2.0.8 usr/lib/libkexisql2.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkexisql3.so.3.0.0 usr/lib/libkexisql3.so
+kexi: non-dev-pkg-with-shlib-symlink usr/lib/libkformdesigner.so.0.0.1 usr/lib/libkformdesigner.so
+kexi: shlib-missing-in-control-file libkdeinit_kexi.so usr/lib/libkdeinit_kexi.so
diff --git a/kexi/debian/overrides/kexi-postgresql-driver b/kexi/debian/overrides/kexi-postgresql-driver
new file mode 100644
index 000000000..75108ab85
--- /dev/null
+++ b/kexi/debian/overrides/kexi-postgresql-driver
@@ -0,0 +1,2 @@
+kexi-postgresql-driver: binary-or-shlib-defines-rpath ./usr/lib/kde3/keximigrate_pqxx.so /usr/lib
+kexi-postgresql-driver: binary-or-shlib-defines-rpath ./usr/lib/kde3/kexidb_pqxxsqldriver.so /usr/lib
diff --git a/kexi/debian/rules b/kexi/debian/rules
new file mode 100644
index 000000000..6fd389127
--- /dev/null
+++ b/kexi/debian/rules
@@ -0,0 +1,32 @@
+#!/usr/bin/make -f
+
+### CDBS INCLUDES
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/rules/simple-patchsys.mk
+include /usr/share/cdbs/1/class/kde.mk
+
+### VARIABLES
+DEB_DH_MAKESHLIBS_ARGS_ALL := -n
+
+PACKAGES_WITH_LIBS := kexi kexi-postgresql-driver kexi-mysql-driver \
+ libkexi-dev
+
+DEB_SHLIBDEPS_INCLUDE = $(foreach p,$(PACKAGES_WITH_LIBS),debian/$(p)/usr/lib:debian/$(p)/usr/lib/kde3)
+
+# We need this commented out until sqlite 3.0 is available in Debian
+# DEB_EXTRA_CONNFIGURE_FLAGS := --without-included-sqlite
+
+### POST-INSTALL HACKING
+# Make directories in /usr/share/doc be symlinks for packages other than amarok
+common-binary-post-install-arch::
+ for p in $(filter-out kexi,$(DEB_ALL_PACKAGES)); do \
+ rm -rf debian/$$p/usr/share/doc/$$p; \
+ ln -sf kexi debian/$$p/usr/share/doc/$$p; \
+ done
+
+ # Check for non installed files
+ dh_install --no-act --list-missing
+
+binary-install/kexi::
+ install -p -D -m644 debian/kformdesigner.menu debian/kexi/usr/lib/menu/kformdesigner.menu
+
diff --git a/kexi/doc/README b/kexi/doc/README
new file mode 100644
index 000000000..6eabdfcf9
--- /dev/null
+++ b/kexi/doc/README
@@ -0,0 +1,7 @@
+
+common/ - Common files for Doxygen-generated html documentation
+dev/ - Development Documentation: thoughts, ideas, notes
+handbook/ - temporary files for Kexi Handbook; do not look there
+ unless you're the Handbook author/reviewer
+kexidb/ - place for Doxygen-generated KexiDB Module Documentation
+plan/ - other file(s) related to development
diff --git a/kexi/doc/common/bottom1.png b/kexi/doc/common/bottom1.png
new file mode 100644
index 000000000..25abc2042
--- /dev/null
+++ b/kexi/doc/common/bottom1.png
Binary files differ
diff --git a/kexi/doc/common/bottom2.png b/kexi/doc/common/bottom2.png
new file mode 100644
index 000000000..bdf86daaf
--- /dev/null
+++ b/kexi/doc/common/bottom2.png
Binary files differ
diff --git a/kexi/doc/common/docheadergears.png b/kexi/doc/common/docheadergears.png
new file mode 100644
index 000000000..ac8dab963
--- /dev/null
+++ b/kexi/doc/common/docheadergears.png
Binary files differ
diff --git a/kexi/doc/common/doctop1a-online.png b/kexi/doc/common/doctop1a-online.png
new file mode 100644
index 000000000..1b65b7d97
--- /dev/null
+++ b/kexi/doc/common/doctop1a-online.png
Binary files differ
diff --git a/kexi/doc/common/doxygen.css b/kexi/doc/common/doxygen.css
new file mode 100644
index 000000000..a8be4adc0
--- /dev/null
+++ b/kexi/doc/common/doxygen.css
@@ -0,0 +1,147 @@
+/*******************************************************************************
+
+ Style sheet for kdelibs doxygen documentation.
+ Copyright (c) Anders Lund <anders@alweb.dk> 2002
+
+ This script is made available under the terms of the General Public Licence.
+ You should have received a copy of the GNU General Public License
+ along with this software; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+********************************************************************************/
+
+body {
+ margin: 0px;
+ background-color: white;
+}
+
+/* decorative header */
+div.header {
+ background-color: #0855C5;
+ background-image: url("headerbg.png");
+}
+div.header table {
+ padding: 0px;
+ margin: 0px
+}
+div.header table tr td h1 {
+ color: white;
+ font: bold 20pt courier;
+ padding-top: 0.5em;
+}
+
+/* global lind buttons at bottom of decorative header */
+table.links {
+ float: right;
+ border:0;
+ padding-left:1px
+}
+table.links td a {
+ color: white
+}
+table.links td:hover {
+ background-color:#0C4293;
+}
+table.links td a:hover {
+ color: white;
+ background-color:#0C4293
+}
+table.links td {
+ border-left: 1px solid #4A81D5;
+ padding: 0px 12px 0px 12px;
+ background-color:#0E4EAF;
+ font-size:9pt;
+ /*font-weight: bold;*/
+}
+
+/* little gradient below decorative/navigation header */
+div#hgrad {
+ height: 12px;
+ background-image: url("grad.png");
+}
+
+/* contents part of page */
+div.text {
+ margin: 12px
+}
+
+/* contents, mostly equivalent to the default doxugen style sheet */
+H1 { text-align: center; }
+CAPTION { font-weight: bold }
+A.qindex {}
+A.qindexRef {}
+A.el { text-decoration: none; font-weight: bold }
+A.elRef { font-weight: bold }
+A.code { text-decoration: none; font-weight: normal; color: #4444ee }
+A.codeRef { font-weight: normal; color: #4444ee }
+A:hover { text-decoration: none; background-color: #ececec }
+DL.el { margin-left: -1cm }
+DIV.fragment { width: 100%; border: none; background-color: #ffffee; padding: 12px }
+DIV.ah { background-color: navy; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px }
+TD.md { background-color: #ececec; font-weight: bold; }
+TD.mdname1 { background-color: #ececec; font-weight: bold; color: #602020; }
+TD.mdname { background-color: #ececec; font-weight: bold; color: #602020; width: 600px; }
+DIV.groupHeader { margin-left: 16px; margin-top: 12px; margin-bottom: 6px; font-weight: bold }
+DIV.groupText { margin-left: 16px; font-style: italic; font-size: smaller }
+BODY { background: white }
+TD.indexkey {
+ background-color: #e0eef8;
+ font-weight: bold;
+ padding-right : 10px;
+ padding-top : 2px;
+ padding-left : 10px;
+ padding-bottom : 2px;
+ margin-left : 0px;
+ margin-right : 0px;
+ margin-top : 2px;
+ margin-bottom : 2px
+}
+TD.indexvalue {
+ background-color: #e0eef8;
+ font-style: normal;
+ padding-right : 10px;
+ padding-top : 2px;
+ padding-left : 10px;
+ padding-bottom : 2px;
+ margin-left : 0px;
+ margin-right : 0px;
+ margin-top : 2px;
+ margin-bottom : 2px
+}
+FONT.keyword { color: #008000 }
+FONT.keywordtype { color: #604020 }
+FONT.keywordflow { color: #e08000 }
+FONT.comment { color: #800000 }
+FONT.preprocessor { color: #806020 }
+FONT.stringliteral { color: #002080 }
+FONT.charliteral { color: #008080 }
+
+
+
+/* kde decoration at bottom */
+div#bottom-nav {
+ position : relative;
+ background-color: transparent;
+ /*width : 100%;*/
+ /*top: 0px;
+ left: 0px;
+ right: 0px;
+ margin-left: 0px;
+ margin-right:0px;*/
+ margin-top: 12px;
+ height: 50px;
+ background-image : url('bottom1.png');
+ background-repeat : repeat-x;
+}
+
+/* copyright etc at bottom */
+div.bottom {
+ margin: 12px;
+ font-size: 9pt;
+ text-align: right;
+}
+div.bottom a {
+ color: #aaaaaa
+}
+
diff --git a/kexi/doc/common/footer.html b/kexi/doc/common/footer.html
new file mode 100644
index 000000000..34a49967e
--- /dev/null
+++ b/kexi/doc/common/footer.html
@@ -0,0 +1,14 @@
+</div>
+<div id="bottom-nav">
+<img src="../../common/bottom2.png" align="right" height="59" width="227" alt="KDE Logo">
+</div>
+<div class="bottom">
+This file is part of the documentation for $projectname $projectnumber.
+</div>
+<div class="bottom" style="color:#cccccc">
+Documentation copyright &copy; 2003 the Kexi Team.<br>
+Generated on $datetime by
+<a href="http://www.doxygen.org/index.html">doxygen</a> $doxygenversion written by <a href="mailto:dimitri@stack.nl">Dimitri van Heesch</a>, &copy;&nbsp;1997-2003
+</div>
+</body>
+</html>
diff --git a/kexi/doc/common/grad.png b/kexi/doc/common/grad.png
new file mode 100644
index 000000000..06c5028db
--- /dev/null
+++ b/kexi/doc/common/grad.png
Binary files differ
diff --git a/kexi/doc/common/header.html b/kexi/doc/common/header.html
new file mode 100644
index 000000000..63f47eb91
--- /dev/null
+++ b/kexi/doc/common/header.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1">
+ <title>$title ($projectname)</title>
+ <link href="../../common/doxygen.css" rel="stylesheet" type="text/css">
+</head>
+<div class="header">
+<table border="0" cellspacing="0" cellpadding="0">
+<tr>
+<td rowspan="2" width="92">
+<a href="index.html" title="The API documentation main page"><img src="../../common/docheadergears.png" style="border:0px;" alt=""></a>
+</td>
+<td width="95%">
+<h1>$projectname API Documentation</h1>
+</td>
+</tr>
+<tr>
+<td valign="bottom">
+<table class="links" border=0 cellspacing=0>
+<tr>
+<td><a href="index.html">Overview</a></td>
+<td><a href="hierarchy.html" title="Inheritance list, sorted roughly alphabetically">Class&nbsp;Hierarchy</a></td>
+<td><a href="classes.html" title="Alphabetical list of classes">Classes</a></td>
+<td><a href="annotated.html" title="Classes, structs, unions and interfaces with brief descriptions">Classes&nbsp;(annotated)</a></td>
+<td><a href="functions.html" title="Documented class members with links to the classes they belong to">Members</a></td>
+<!-- td><a href="namespaces.html" title="All documented namespaces with brief descriptions">Namespaces</a></td-->
+<td><a href="files.html" title="A list of all documented files with brief descriptions">Source&nbsp;Files</a></td>
+</tr>
+</table>
+</td></tr></table>
+</div>
+<div id="hgrad"></div>
+<div class="text">
diff --git a/kexi/doc/common/headerbg.png b/kexi/doc/common/headerbg.png
new file mode 100644
index 000000000..b72738a24
--- /dev/null
+++ b/kexi/doc/common/headerbg.png
Binary files differ
diff --git a/kexi/doc/common/kde-common.css b/kexi/doc/common/kde-common.css
new file mode 100644
index 000000000..5898199ba
--- /dev/null
+++ b/kexi/doc/common/kde-common.css
@@ -0,0 +1,32 @@
+/*
+ KDE-wide persistent CSS for HTML documentation (all media types).
+ Copyright (C) 2000 Frederik Fouvry
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the 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
+
+ Send comments, suggestions, etc. to Frederik Fouvry
+ <fouvry@sfs.nphil.uni-tuebingen.de>.
+*/
+
+/*
+ Important note: these setting cannot be overridden, except by switching
+ off style sheets alltogether.
+
+ Any updates should be validated, e.g. http://jigsaw.w3.org/css-validator/
+
+ Check out http://www.richinstyle.com/ where many of the declarations and
+ setup in the KDE CSS are obtained from. In fact, do not change
+ anything without checking on those pages whether it is wise to do so.
+*/
diff --git a/kexi/doc/common/kde-default.css b/kexi/doc/common/kde-default.css
new file mode 100644
index 000000000..032a00db5
--- /dev/null
+++ b/kexi/doc/common/kde-default.css
@@ -0,0 +1,187 @@
+/*
+ KDE-wide default CSS for HTML documentation (all media types).
+ Copyright (C) 2000 Frederik Fouvry
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the 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
+
+ Send comments, suggestions, etc. to Frederik Fouvry
+ <fouvry@sfs.nphil.uni-tuebingen.de>. */
+
+/*
+ Important note: these setting may be overridden by localised CSS. Do not
+ add here any localisation-sensitive style declarations.
+
+ Any updates should be validated, e.g. http://jigsaw.w3.org/css-validator/ */
+
+/* Note: "should be inherit" means that in a proper browser inherit should work.
+ Somehow Netscape manages to interpret "inherit" as bright green.
+ Yuck. */
+
+/* Make the main contents of the page not slide up under the top banner */
+
+/* #body_text { position: relative;
+ top: 100px; } */
+
+/* This is a horrible awful hack.. to once again fix the sliding
+navigation problem. */
+
+body { padding-top: 100px; background-color:
+white; color: black;}
+
+/* Nice link colors for the main text, and bottom navigation
+ Setting text-decoration seems to fix a nasty redraw problem in KHC */
+
+:link { color: rgb(170,0,0);
+ text-decoration: underline;
+ background-color: white; /* should be inherit */ }
+:visited { color: rgb(170,0,85);
+ text-decoration: underline;
+ background-color: white; /* should be inherit */ }
+:active { color: rgb(170,0,0);
+ text-decoration: underline;
+ background-color: white; /* should be inherit */ }
+
+/* Link colors for the top navigation */
+
+.left :link, .center :link, .right :link { color: rgb(255,255,255);
+ text-decoration: underline;
+ background-color: rgb(82,80,82); }
+
+.left :visited, .center :visited, .right :visited { color: rgb(186,182,186);
+ text-decoration: underline;
+ background-color: rgb(82,80,82); }
+
+/* Giving this a background color breaks things, and it's not necessary for
+ either navigation or orientation.. so transparent */
+
+.center {color: rgb(255,255,255);
+ background-color: transparent; }
+
+
+:active {color: rgb(186,182,186);
+ background-color: transparent; }
+
+/* A little bit of padding makes the tables for keybindings etc much easier to read */
+
+TABLE { padding: 5px; }
+DT { margin-top: 1em; }
+DIV.toc DT { margin-top: 0px; }
+DIV.SCREENSHOT { margin-bottom: 1em;
+ margin-top: 1em; }
+DIV.INFORMALEXAMPLE { border-style: dotted;
+ padding: 10px; }
+
+/* But no padding for navigation elements */
+
+.shadow, .headline, .bulb, .end { padding: 0px; }
+
+TABLE.programlisting
+TABLE.screen { border-style: none;
+ background-color: rgb(224,224,224);
+ table-layout: auto; /* 100%? */
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+/* Same as previous block, but more general (previous is HTML only)
+ Not all browsers understand this yet.
+TABLE[class~=programlisting]
+TABLE[class~=screen] { border-style: none;
+ background-color: rgb(224,224,224);
+ table-layout: auto;
+ color: inherit;
+ }
+*/
+
+P { text-align: justify; }
+
+/* More specific settings */
+
+DIV.navbar P { text-align: right; }
+
+/* Temporary patch: browsers break on bad HTML */
+/* P, H1, H2, H3, H4, H5, TD, TH { font-family: Helvetica, Arial, sans-serif;
+ } */
+
+P, H1, H2, H3, H4, H5, TD, TH { font-family: sans-serif;
+ }
+
+
+
+/* Visual cues for GUI elements etc in the text */
+
+.guimenu, .guimenuitem, .guisubmenu { background-color: rgb(240,240,240);
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+.guilabel, .interface, .guibutton { background-color: rgb(240,240,240);
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+.shortcut { background-color: rgb(240,240,240);
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+.shortcut .keycap { background-color: rgb(240,240,240);
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+.question { font-weight: bolder; }
+
+.accel { background-color: rgb(240,240,240);
+ color: rgb(0,0,0);
+ text-decoration: underline;
+ }
+
+.option, .command { background-color: rgb(255,255,255);
+ color: rgb(0,96,160);
+ font-weight: bold;
+ }
+
+h1, h2, h3, h4, h5, h6 { color: rgb(82,80,82);
+ }
+.arg, .parameter, .replaceable{ background-color: rgb(255,255,255);
+ color: rgb(0,128,64);
+ font-style: italic;
+ }
+
+DIV.mediaobject { /* float: right; */
+ /* might make it much nicer. although someone has to
+ understand the rules ;-) You also don't want it to be
+ surrounded by text it doesn't refer to ... But that
+ may be among others a question of writing style. */
+ text-align: center; /* a bit of a hack: it should
+ position _images_ */
+ }
+
+.caption { margin: 0em 2em 3em 2em; }
+
+.inlinemediaobject { vertical-align: baseline;
+ padding-right: 1em;
+ padding-left: 1em; }
+
+/* An idea that could be nice: a search engine looking for specific
+classes could display them in some conspicuous colour. All that is
+needed is an on the fly generated style element/style sheet. */
+
+/* Only used in the hand-made HTML license texts */
+BODY.license { background-color: rgb(255,255,255);
+ text-align: justify;
+ color: rgb(0,0,0);
+ }
+PRE.license { background-color: rgb(255,255,255);
+ font-family: monospace;
+ color: rgb(0,0,0);
+ }
+
diff --git a/kexi/doc/common/kde-web.css b/kexi/doc/common/kde-web.css
new file mode 100644
index 000000000..e61fa32a0
--- /dev/null
+++ b/kexi/doc/common/kde-web.css
@@ -0,0 +1,178 @@
+/*
+ KDE-wide default CSS for HTML documentation (all media types).
+ Copyright (C) 2000 Frederik Fouvry
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the 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
+
+ Send comments, suggestions, etc. to Frederik Fouvry
+ <fouvry@sfs.nphil.uni-tuebingen.de>. */
+
+/*
+ Important note: these setting may be overridden by localised CSS. Do not
+ add here any localisation-sensitive style declarations.
+
+ Any updates should be validated, e.g. http://jigsaw.w3.org/css-validator/ */
+
+/* Note: "should be inherit" means that in a proper browser inherit should work.
+ Somehow Netscape manages to interpret "inherit" as bright green.
+ Yuck. */
+
+/* Make the main contents of the page not slide up under the top banner */
+
+/* #body_text { position: relative;
+ top: 100px; } */
+
+/* This is a horrible awful hack.. to once again fix the sliding
+navigation problem. */
+
+body { background-color: white;
+ width: 596px;
+ color: black;
+ align: center;}
+
+/* Nice link colors for the main text, and bottom navigation */
+
+:link { color: rgb(170,0,0);
+ background-color: white; /* should be inherit */ }
+:visited { color: rgb(170,0,85);
+ background-color: white; /* should be inherit */ }
+:active { color: rgb(170,0,0);
+ background-color: white; /* should be inherit */ }
+
+/* Link colors for the top navigation */
+
+.left :link, .center :link, .right :link { color: rgb(255,255,255);
+ background-color: rgb(82,80,82); }
+
+.left :visited, .center :visited, .right :visited { color: rgb(186,182,186);
+ background-color: rgb(82,80,82); }
+
+/* Giving this a background color breaks things, and it's not necessary for
+ either navigation or orientation.. so transparent */
+
+.center {color: rgb(255,255,255);
+ background-color: transparent; }
+
+
+:active {color: rgb(186,182,186);
+ background-color: transparent; }
+
+/* A little bit of padding makes the tables for keybindings etc much easier to read */
+
+TABLE { padding: 5px; }
+DT { margin-top: 1em; }
+DIV.SCREENSHOT { margin-bottom: 1em;
+ margin-top: 1em; }
+DIV.INFORMALEXAMPLE { border-style: dotted;
+ padding: 10px; }
+
+/* But no padding for navigation elements */
+
+.shadow, .headline, .bulb, .end { padding: 0px; }
+
+TABLE.programlisting
+TABLE.screen { border-style: none;
+ background-color: rgb(224,224,224);
+ table-layout: auto; /* 100%? */
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+/* Same as previous block, but more general (previous is HTML only)
+ Not all browsers understand this yet.
+TABLE[class~=programlisting]
+TABLE[class~=screen] { border-style: none;
+ background-color: rgb(224,224,224);
+ table-layout: auto;
+ color: inherit;
+ }
+*/
+
+P { text-align: justify; }
+
+/* More specific settings */
+
+DIV.navbar P { text-align: right; }
+
+/* Temporary patch: browsers break on bad HTML */
+/* P, H1, H2, H3, H4, H5, TD, TH { font-family: Helvetica, Arial, sans-serif;
+ } */
+
+P, H1, H2, H3, H4, H5, TD, TH { font-family: sans-serif;
+ }
+
+
+
+/* Visual cues for GUI elements etc in the text */
+
+.guimenu, .guimenuitem, .guisubmenu { background-color: rgb(240,240,240);
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+.guilabel, .interface, .guibutton { background-color: rgb(240,240,240);
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+.shortcut { background-color: rgb(240,240,240);
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+.shortcut .keycap { background-color: rgb(240,240,240);
+ color: rgb(0,0,0); /* should be inherit */
+ }
+
+.question { font-weight: bolder; }
+
+.accel { background-color: rgb(240,240,240);
+ color: rgb(0,0,0);
+ text-decoration: underline;
+ }
+
+.option, .command { background-color: rgb(255,255,255);
+ color: rgb(0,96,160);
+ font-weight: bold;
+ }
+
+h1, h2, h3, h4, h5, h6 { color: rgb(82,80,82);
+ }
+.arg, .parameter, .replaceable{ background-color: rgb(255,255,255);
+ color: rgb(0,128,64);
+ font-style: italic;
+ }
+
+DIV.mediaobject { /* float: right; */
+ /* might make it much nicer. although someone has to
+ understand the rules ;-) You also don't want it to be
+ surrounded by text it doesn't refer to ... But that
+ may be among others a question of writing style. */
+ text-align: center; /* a bit of a hack: it should
+ position _images_ */
+ }
+
+/* An idea that could be nice: a search engine looking for specific
+classes could display them in some conspicuous colour. All that is
+needed is an on the fly generated style element/style sheet. */
+
+/* Only used in the hand-made HTML license texts */
+BODY.license { background-color: rgb(255,255,255);
+ text-align: justify;
+ color: rgb(0,0,0);
+ }
+PRE.license { background-color: rgb(255,255,255);
+ font-family: monospace;
+ color: rgb(0,0,0);
+ }
+
+.topbanner { background-repeat: no-repeat;
+ } \ No newline at end of file
diff --git a/kexi/doc/common/kmenu.png b/kexi/doc/common/kmenu.png
new file mode 100644
index 000000000..5efb18b6e
--- /dev/null
+++ b/kexi/doc/common/kmenu.png
Binary files differ
diff --git a/kexi/doc/common/shadow.png b/kexi/doc/common/shadow.png
new file mode 100644
index 000000000..7f8370833
--- /dev/null
+++ b/kexi/doc/common/shadow.png
Binary files differ
diff --git a/kexi/doc/common/web-docbottom.png b/kexi/doc/common/web-docbottom.png
new file mode 100644
index 000000000..eb43ef358
--- /dev/null
+++ b/kexi/doc/common/web-docbottom.png
Binary files differ
diff --git a/kexi/doc/dev/CHANGELOG-Kexi-js b/kexi/doc/dev/CHANGELOG-Kexi-js
new file mode 100755
index 000000000..9b74a65d6
--- /dev/null
+++ b/kexi/doc/dev/CHANGELOG-Kexi-js
@@ -0,0 +1,5867 @@
+-----------------------------------------------------------------------------
+ Kexi Development CHANGELOG Document
+ From jstaniek's Point of View
+
+ Copyright (C) 2003-2007 Jaroslaw Staniek js at iidea.pl / OpenOffice Polska
+ Kexi home page: http://www.kexi-project.org/
+-----------------------------------------------------------------------------
+
+For most important commits use: CCMAIL:kexi-devel@kde.org
+For interesting commits use:
+FEATURE: <desctiption>
+CCMAIL:danny@commit-digest.org
+For bugfixes use CCMAIL:###-done@bugs.kde.org
+Legend:
+= -a fix for recently introduced bug or a minor/too technical fix not worth
+ mentioning in the public changelog
+
+TODO: fix displaying default values in comboboxes (form and tableview)
+TODO: use KCompletionBox in (editable/noneditable) comboboxes
+
+2007-04-11
+Forms
+- forms having parameter query as data source now ask for parameters
+2.0: merged
+
+2007-03-21, 22
+Table Designer
+- clear command history after successful saving of the design
+- on switching to data view, do not warn about removing data,
+ if the data will stay untouched
+2.0: merged
+Query Designer
+- removed possible crash in data view
+2.0: merged
+
+2007-03-16
+Query Designer
+- fixed table sizes in the Design View
+- fixed displaying relation connection lines when connected field is not visible
+- fixed mouse drop position when target points on a field of table with
+ scrolled-down contents
+- do not show internal relationships for visible lookup fields (JOINs) in SQL view
+2.0: merged
+Table View
+- make column readonly if query-based data source itself is stored (i.e. has connection)
+ and lookup column is defined
+2.0: merged
+
+2007-03-15
+KexiDB
+- SQL parser can work recursively now (needed when multiple parsers are used
+ in the same thread, removed possible crash)
+2.0: merged
+
+2007-03-12..14
+Main Window
+- main window is activated after closing "Insert image" file dialog
+= "find" action: restart searching from start for "Search all rows" mode if:
+== user has changed the value to find since previous searching, or
+== user has recently changed direction to "Search all rows"
+- searching is performed within lookup values as well
+Simple Printouts
+- fixed problem with records printed between page boundaries
+- fixed problem with updating print preview's page navigator when number of pages differs
+ compared to previous preview
+2.0: merged
+
+Table View
+- usability: within cells of type "Image" pressing Tab, Shift+Tab, Left
+ or Right keys now closes the popup and moves the cursor
+2.0: merged
+
+2007-03-07
+Simple Printouts
+- fixed refreshing print preview after table data or query results changed
+2.0: merged
+
+2007-03-02
+Simple Printouts
+- added support for printing and previewing images
+- improved previewing quality
+- fixed landscape mode
+2.0: merged
+
+2007-02-27
+Forms
+- hide popup in combo box or image box as soon as it loses focus
+- changes made to combo box selection is cancelled if the popup
+ loses focus without accepting
+- hide popup in combo box or image box as soon as user moves
+ to other record (usually with a shortcut)
+- fixed handling page down/page up keys within combo box popups
+ (previously it moved to other record)
+2.0: merged
+
+2007-02-23
+Forms
+- for buttons, left/up and right/down keys act like tab/backtab
+2.0: merged
+
+2007-02-19
+Forms
+- fixed displaying margin for image boxes
+- fixed assigning static images to image boxes without saving form's design;
+ the images can now be saved also in data view mode
+- fixed handling tab key for image boxes
+- automatic tab order:
+-- fixed ordering for widgets within containers (e.g. a group box)
+-- widgets within tab widget pages are ordered by page number
+-- widgets inside tab widget that are not visible are skipped when moving
+ focus using Tab/Shift+Tab key
+- Ctrl+Tab shortcut (for switching between tabs) works on windows
+ if multiline text editor is focused
+2.0: merged
+
+2007-02-12
+Forms
+- fields dragging & dropping works for containers (tab widgets, frames and group boxes)
+2.0: merged
+
+2007-02-09..14
+Main Window
+- Edit->Find action added, works within table, query and form views as a global,
+ context-dependent tool window.
+2.0: merged
+
+~~~~~~~~~~~~~~~~ 1.1.2 bugfix release (with KOffice 1.6.2) ~~~~~~~~~~~~~~~~
+
+2007-02-09
+Table View
+- added more actions to the context menu in data view
+
+2007-02-08
+Tools
+- added "Delete column" tool for deleting table columns without removing data;
+ translation enabled; with GUI wrapper
+- fixes for "Add column" tool
+2.0: merged
+
+2007-02-07
+KexiDB
+= fixed possible crash when running a query without primary key included
+ (encountered e.g. when a form contains static images)
+2.0: merged
+Tools
+- added GUI version of "Add column" tool (with translation enabled)
+- the tool is now installed
+2.0: merged
+
+2007-02-06
+KexiDB
+- queries are supported as row source for lookup fields
+- sort query results by primary key fields if there is no explicit sorting specified
+ (especially useful when there are complex joins)
+2.0: merged
+
+2007-02-05
+Query Designer
+- fixed crash if column being removed has sorting set up
+2.0: merged
+
+2007-01-26..2007-02-05
+Table Designer
+- added support for multiple visible columns in the combo box editor
+ (the same for forms)
+- on altering table, remove old lookup field schema objects;
+ make sure you do not save empty type/name fields to XML
+- fixed updating "lookup column" tab's contents
+2.0: merged
+
+2007-01-24..25
+Table Designer
+- fixed altering table schema when lookup column has been changed
+2.0: merged
+Forms
+- align autofield's label to left if it is on the top
+- do not show popup after clicking on combo box
+- fixed popup position of combo boxes inside a container
+- fixed saving property values of type "enum" for subwidgets
+2.0: merged
+
+2007-01-23
+Table Designer
+- fixed crash on saving design with lookup columns defined
+2.0: merged
+Forms
+- fixed handling "align" property for image boxes when scaling is off
+2.0: merged
+
+2007-01-09
+Table Designer
+- some changes do not require table removing now:
+ e.g. field's caption, description, text width, defaultValue,
+ visibleDecimalPlaces
+- fixed updating default value when field type changes
+2.0: merged
+Forms
+- floating point values:
+-- values are now formatted in the same way as in table view,
+ so "decimal places" property of the field is honoured
+-- locale-dependent decimal symbol can be entered instead of '.'
+2.0: merged
+
+2007-01-08
+Build system
+= fixed generating examples when Kexi binaries are not installed
+ (it's the case for fresh compilation)
+2.0: merged
+General
+- Fixed crash related to QtCurve widget style.
+2.0: merged
+
+2007-01-03
+Kexi
+- use transliteration table generated by a shell script
+ to generate identifiers out of unicode characters;
+ plus some adjustments made by hand
+BUG: 133170
+2.0: merged
+
+2007-01-02
+Startup
+- Command line option "--show-navigator" added - shows the Project Navigator
+ side pane even if Kexi runs in User Mode.
+2.0: merged
+Kexi
+= Example files are generated usign a standard automake rule and are installed
+to the Kexi's data dir (so packagers can ship it).
+2.0: merged
+
+2006-12-21..28
+Kexi 2.0
+= formeditor lib ported to KDE4 (not tested)
+Startup, Main Window
+- "User Mode" implemented; command line option --final-mode replaced with --user-mode
+ as described at http://kexi-project.org/wiki/wikiview/index.php?UserMode;
+ all actions related to design are hidden as well as property editor and navigator panes
+2.0: merged
+General
+= removed usage of KEXI_SERVER_SUPPORT macro
+2.0: merged
+
+2006-12-18..20
+Forms
+- "Assign Action" dialog:
+-- added 'create new' and 'close view' actions with a given context
+-- "Current form" actions category added with actions like
+ "go to next record", "go to new record"
+-- more design actions like 'undo' moved to the global category
+2.0: merged
+Main Window
+- action tooltips made generic (not only for tables),
+ e.g. "Save changes made to the current row"
+2.0: merged
+
+2006-12-13..15
+Startup
+= do not recreate kexi__blobs table when connection is readonly;
+- projects created with older Kexi versions can be now opened
+ in readonly mode too
+2.0: merged
+Project Migration
+- fixed copying existing objects to the destination database
+- MySQL and PostgreSQL
+-- fixed importing unicode text and static images if present in the source database (BLOBs)
+-- data types are better handled for import
+2.0: merged
+Forms
+- action selection dialog:
+-- the list of menu commands now contains tooltips instead of action texts to improve
+ readability and is displayed with full width
+-- actions are now categorized, so actions that have no practical use are hidden
+2.0: merged
+
+General
+= typos fixed in API docs
+BUG:138542
+2.0: merged
+
+2006-12-07
+Tools
+- added 'add column' utility:
+ This script adds a new empty column to a table in a .kexi (SQLite 3)
+ database file without removing data from the table.
+2.0: merged
+
+2006-12-06..12
+Forms
+- action selection dialog:
+-- moved "Macros" and "Scripts" to "category" list,
+ which also supports opening tables/queries/forms, running macros/scripts
+-- added "Action type" column so it is possible to assign printing
+ and opening in objects design view
+-- added "Execute form's action" category
+2.0: merged
+Main Window
+- Project Navigator:
+== widget moved to widgets/ for reuse
+-- double/single clicking on executable objects (macro, script) executes them
+2.0: merged
+
+2006-12-05
+General
+- missing i18n()
+2.0: merged
+Query Designer
+- display "(All Columns)" near the "*"
+- SQL editor on Unix: initially, clear undo/redo buffer
+ (otherwise Ctrl+Z clears the contents)
+2.0: merged
+KexiDB
+- fixed problem with constructing SQL statements:
+ for multi-table queries "ORDER BY column" prepends "table" only if "column"
+ is not a name of alias
+2.0: merged
+
+~~~~~~~~~~~~~~~~ 2007 (OOPL) ~~~~~~~~~~~~~~~~
+
+2006-11-28..29
+Examples
+- added parametrized query 'Owners by age'
+= update the parameter query: remove joins because there's still one related bug
+2.0: merged
+
+2006-11-27
+Examples
+- "Simple Database" example updated: added "Ownership" form and a macro
+- "Simple Database": added "write_simple_text_report" Python script
+2.0: merged
+
+2006-11-21
+Table View
+- fixed crash when combo box drop down button is clicked but bound column
+ is not defined for lookup field
+2.0: merged
+Forms
+- fixed possible crash when auto field is used for field with lookup
+ column defined
+- sane icon for the Auto Field widget and Label, improved button icon
+2.0: merged
+
+2006-11-17
+CSV Export
+- export visible values for lookup columns instead of internal values
+Simple Printouts
+- print visible values for lookup columns instead of internal values
+2.0: merged
+KexiDB
+- generate aliases for lookup table names to avoid errors because
+ of ambiguous table names when the same lookup table is used more than once;
+ For example, use persons1 and persons1 aliases here:
+ SELECT service.id, service.emploee, service.service_man, persons1.surname,
+ persons2.surname, service.OID FROM service
+ LEFT OUTER JOIN persons AS persons1 ON service.emploee=persons1.id
+ LEFT OUTER JOIN persons AS persons2 ON service.service_man=persons2.id;
+2.0: merged
+
+~~~~~~~~~~~~~~~~ 1.1.1 bugfix release (with KOffice 1.6.1) ~~~~~~~~~~~~~~~~
+Query Designer
+- propery initialize newly appended rows
+2.0: merged
+
+2006-11-16
+Table View
+- fixed updating contents of the combobox's internal text editor after
+ pressing F2 or Enter key
+- fixed possible crash on application's close
+- fixed copying/pasting values from the combo box (visible text is now copied/pasted)
+2.0: merged
+
+2006-11-15
+Query Designer
+- fixed setting up sorting in the design view
+- fixed setting up string constants as criteria
+2.0: merged
+Table View
+- fixed copying time and date/time values to clipboard
+- fixed pasting time, date and date/time values from clipboard
+2.0: merged
+
+2006-11-14
+Forms
+- fixed displaying Date/Time values
+- fixed crash when "Esc" key is pressed to "cancel row changes"
+ and a combobox widget is focused
+- fixed handling Ctrl+Delete key for "Delete current row" action
+- fixed crash when an autofield's subwidget is painted but the parent
+ is about to be deleted
+2.0: merged
+Table View
+- fixed displaying visible values (usually a text) for lookup columns
+ when there's a default value defined in the referenced table
+- visible values of types other than text or numbers are properly
+ displayed (including images)
+2.0: merged
+Queries
+- fixed retrieving parameters of type Text
+2.0: merged
+
+2006-11-07..13
+Main Window
+- switch contents of custom property tabs after switching the current tab/window
+ (needed for Table Designer's tab)
+- added a warning message box with "Errors encountered during loading plugins"
+ message, controled by dontShowWarningsRelatedToPluginsLoading configuration setting.
+- groups and items for nonexisting plugins are not visible now
+2.0: merged
+Query Designer
+- show more error messages on opening query in design view
+ (e.g. for unsupported column expressions)
+- fixed saving relationships: removed too strict checking for datatypes when creating
+ relationships (signedness)
+2.0: merged
+KexiDB
+- Parser: improved types evaluation for expressions
+- kexi__objectdata.o_data field type changed from BLOB to LongText (backward compatible)
+ to improve human-readability
+2.0: merged
+Forms
+- fixed problem with setting "invalid" flag for fields with invalid data source
+2.0: merged
+Core
+= fixed possible crash on application exit when data changes listener is removed
+2.0: merged
+General
+- Simple_Database.kexi example updated: combo boxes are now used; example data looks better
+2.0: merged
+
+~~~~~~~~~~~~~~~~ 1.1.1-pre ~~~~~~~~~~~~~~~~
+
+2006-10-30..2006-11-06
+Table View
+- better displaying tooltips for columns with lookup data
+- sorting columns with lookup data works
+= various improvements for combo boxes
+2.0: merged
+Forms
+- database-aware combo box form widget works, improved data displaying,
+ mouse and keyboard handling
+- fixed bug #136688 - displaying dates (e.g. with dd.mm.yyyy format)
+= do not block Escape key if there are no data changes to cancel;
+ do not block shortcuts like Shift+Left arrow
+2.0: merged
+
+2006-10-27
+Forms
+- display proper record when a new row was saved and user moved to previous record
+= fixed possible crash inside the row buffer
+2.0: merged
+Table View
+= fixed handling data in lookup fields and cursor moving
+= fixed adjusting width of column containing lookup field
+2.0: merged
+
+2006-10-24..26
+Query Designer
+- New: Added support for parameter queries in design and SQL view.
+ User is asked for entering value of a parameter in a dialog window.
+ Paramers can be of any type except Object.
+ (see also: MSA 2002 Bible, p. 767)
+2.0: merged
+Table Designer
+- Byte datatype is always set to unsigned now
+- "Double precision" datatype is now the default for the floating point
+ type group instead of Float (so we get better precision by default)
+2.0: merged
+Table View
+- fixed validating Big Integer numbers
+2.0: merged
+Forms
+- fixed validating Big Integer numbers
+2.0: merged
+
+2006-10-20
+Forms
+= fixed default size of combo box widgets
+= removed crash when a combobox without a data source was clicked
+2.0: merged
+Main Window
+- accessibility: property editor can be focused using an accelerator key;
+ moreover, Alt+2 now focuses main area and Alt+3 focuses Property Editor
+2.0: merged
+Table Designer
+- "combo" icon is displayed near the field name that have lookup column defined
+2.0: merged
+
+2006-10-16
+Query Designer
+- refresh design view after a sequence of switches:
+ text view -> data view -> design view
+2.0: merged
+KexiDB
+= saving "lookup column" metadata supported
+2.0: merged
+
+2006-10-12
+Forms
+- fixed displaying contents "Data Source" property for multiple selections;
+ unnecessary updates removed (GUI is updated only after adding
+ the last widget to the selection)
+2.0: merged
+Table Designer
+- "Lookup column" property tab added to the property pane
+2.0: merged
+
+2006-10-11
+KFormDesigner
+- removed crash on close and problems with displaying properties
+2.0: merged
+Forms
+- do not update the property editor for every form (only one is active anyway)
+2.0: merged
+
+2006-10-09..10
+Table View
+- fixed drawing selection highlighting for the horizontal header immediately after
+ scrolling the table
+2.0: merged
+Query Designer
+- column sorting is also supported for columns with cleared "visible" flag
+- SQL view's section header now provides an accelerator
+2.0: merged
+
+~~~~~~~~~~~~~~~~ 1.1 stable (with KOffice 1.6) ~~~~~~~~~~~~~~~~
+
+2006-10-06
+KexiDB
+= fixed constructing ORDER BY part of SQL statements (ambiguous fields)
+= when creating "columns by name" cache, also remeber "table.alias" identifiers
+= QuerySchema: retrieving infromation about columns much improved
+ (needed by the parser and designer)
+2.0: merged
+Query Designer
+= fixes for handling ordering information in design mode when there're asterisks
+ in the query
+2.0: merged
+Forms
+- fixed bug #134976: Yes/No field default value does not work properly
+ (for required field: if you add a checkbox that points to a Yes/No
+ field that has a default value the default value is not accepted)
+2.0: merged
+Table View
+- fixed problem with editing floating-point values when there's default
+ value set to 0.0
+2.0: merged
+
+2006-10-04, 05
+Table View
+= fixed displaying values for combobox fields
+= it is now possible to have empty combobox item: selecting it clears
+ the value of combobox
+2.0: merged
+Query Designer
+- invalid input (e.g. criteria or sorting) now displays a message box with
+ "Correct" and "Discard Changes" buttons
+- added support for setting order of columns in Design and SQL views (ORDER BY)
+- fields were sometimes added twice
+2.0: merged
+KexiDB
+= fixed handling information about columns ordering
+2.0: ported
+KoProperty
+= Property: added a method for clearing "modified" flag
+2.0: ported
+
+2006-10-03
+Forms
+- solution for bug #134773: hide "Editor type" property for auto fields
+ Kexi 2.0 will have it unhidden.
+2.0: ported
+- fixed updating property values of enum type
+2.0: merged
+Core
+- fixed bug #134977: enabling/disabling of actions in query editor
+2.0: merged
+Main Window
+- fixed bug #134975: "Data actions do not appear on first open"
+ workaround used before Kexi 2.0: the Data toolbar is always visible
+2.0: merged
+
+~ ~ ~ ~ End of aKademy 2006, Dublin ~ ~ ~ ~
+
+2006-09-30
+KexiDB
+= support for ORDER BY while generating SQL statements
+2.0: ported
+
+2006-09-29
+Form
+- fixed bug #134504 "Several glitches in Kexi GUI"
+2.0: ported
+Docs
+- fixed bug #134503 "Typos in several Kexi docs"
+
+2006-09-28
+KexiDB
+= use COUNT(*), not COUNT() -- fixes MySQL support
+= missing ORDER BY usage while generating SQL statements
+2.0: ported
+2.0: 'parser' target added
+
+2006-09-25
+Forms
+- The first widget of a given kind should be named "widget" by default,
+ the second - "widget2" and so on. (thanks to Natalie)
+2.0: ported
+
+2006-09-21..27
+KexiDB
+- added support for ORDER BY clause
+2.0: ported
+
+~ ~ ~ ~ aKademy 2006, Dublin ~ ~ ~ ~
+
+2006-09-19
+Forms
+- double clicking items of "Available fields" list inserts a new auto field
+- fixed form's selecting data source via the data source pane
+2.0: merged
+KexiDB
+= fixed retrieving autonumber data
+2.0: merged
+
+~~~~~~~~~~~~ 1.1 RC1 release (with KOffice 1.6 RC1) ~~~~~~~~~~~~
+
+2006-09-14
+KoProperty
+- property is not changed when new and old value are both null
+- spin boxes for integer and double values: deleting the contents
+ now sets null value
+= consider unsigner long long and long long values as compatible
+2.0: merged
+KexiDB
+- do not try to insert default values into a column with unique
+ flag declared (or even PK)
+2.0: merged
+Table View
+- Fixed repainting after "Edit->Clear Table Contents" action
+= display question mark for null values in boolean column even
+ if it has to be "notNull"
+2.0: merged
+Table Designer
+- set "false" as default value for columns with Yes/No types
+- when column type changes, default value is updated to match the new type
+ (e.g. casted from integer to text; incompatible values are removed)
+2.0: merged
+
+2006-09-11..13
+KoProperty
+= fixed group widget's height
+2.0: merged
+Table View
+= fixed drawing drop-down arrow for styles like ThinKeramik
+2.0: merged
+- more shortcuts added, shortcuts are now usable and a bit MSA-like
+-- CTRL+End now moves to the last field in the last record
+ (previously: the last record)
+-- CTRL+Home now moves to the first field in the first record
+ (previously: the first record)
+-- CTRL+PgDown now moves to the next record
+-- CTRL+PgUp now moves to the previous record
+-- CTRL+Plus or CTRL+= now moves to the new record
+= some common code moved to KexiDataAwareObjectInterface from KexiTableView,
+ thus allowing to reuse it in forms
+2.0: merged
+Forms
+- fixed tab/backtab key handling
+- fixed displaying custom label text when "Auto Label" property is off
+- handling of global shortcuts like CTRL+PgDown is now shared with
+ Table View (accesibility: it is now possible to move to next/previous
+ record using keyboard)
+- moving to new row sets the focus in the first data-aware widget
+ (in tab order)
+- fixed leaving from "editing" state on "accept row changes" or moving
+ to other record
+2.0: merged
+
+2006-09-08
+Table Designer
+- fixed inserting fields of type "Object"
+= "subType" has no longer a special meaning for BLOB types,
+ "objectType" is used for this purpose
+2.0: merged
+KexiDB
+= fixed saving custom property values
+2.0: merged
+
+2006-09-07
+Forms
+- custom widget factories can be now loaded properly
+= X-KFormDesigner-XMLGUIFileName variable added
+ to the KFormDesigner/WidgetFactory service type, so it's now
+ possible to specify XMLGUI file for defining toolbar actions
+ for custom widget factories
+2.0: merged
+KexiDB
+= Added SimpleCommandLineApp class - an utility for writing small
+ command line programs and tests that support database connections
+ out of the box and provide required arguments.
+= debug areas fixed
+2.0: merged
+
+2006-09-04
+Kexi docs
+- things like 'e.g.' moved to &eg;
+- use <procedure> where appropriate
+- a chapter about forms translated to english
+i18n
+= Stephan Binner's fixes #579553 and #579330 ported to 2.0
+Forms
+- hide QRadioButton widget - current implementation is no-op
+
+~~~~~~~~~~~~ 1.1 beta 1 release (with KOffice 1.6 beta 1) ~~~~~~~~~~~~
+
+2006-09-01
+Forms
+= added KEXI_DB_COMBOBOX_WIDGET define required
+ to add support for combobox form widget (disabled by default for beta 1)
+2.0: merged
+KexiDB
+= dependency on newer (>=4.1) client library removed:
+ MySQL driver now uses 'SELECT @@version' SQL statement instead of dedicated
+ API function
+= added possibility for switching off "LIMIT 1" optmization
+ for methods like Connection::querySingleString()
+ (e.g. needed for 'SELECT @@version')
+2.0: merged
+
+2006-08-29
+General
+- Added unicode->latin1 mappings for Czech characters
+2.0: merged
+
+2006-08-25
+Table View
+= tooltips are displayed also for "insert" row, if needed
+Forms
+= fixed displaying widget names for unbound auto fields
+ and image boxes while in design mode
+= fixed setting data source for auto field
+- initial version of the combo box widget (will be disabled for beta 1)
+2.0: merged
+
+2006-08-24
+Table View
+= displaying cell's selection background fixed for nonstandard font
+2.0: merged
+
+2006-08-22..23
+Forms
+- display default values in the new row;
+ default text is displayed with a special style (italic blue) as in table view;
+ cancelling changes in a widget reverts its contents to default value
+ (if available); the special style is removed as soon as the editing starts
+- "redo" action removed from the data-aware widgets' context menu
+ (to avoid problems with data handling)
+2.0: merged
+Table Designer
+= fixed setting subtype by user
+2.0: merged
+
+2006-08-21
+CSV Import Dialog
+= fixed delimiter change using the combo box (introduced by the prev commit)
+= unnecessary rows are removed after delimiter changes
+2.0: merged
+Table View
+- do not display default values for autonumbered fields
+- do not fill default values for already filled fields
+= boolean editor in tristate mode: fixed switching between true/false/NULL values
+- display default boolean values in blue
+- display tooltip with row number when the vertical scrollbar is dragged
+= fixed displaying tooltips for default values in the "insert" row
+2.0: merged
+
+2006-08-17
+Table View
+- tooltips are displayed for cells with too large contents
+2.0: merged
+CSV Import Dialog
+- delimiter detection is now even more clever, allowing to import more complex data:
+ characters outside quotes have higher priority; additional algorithm counting
+ number of candidates for delimiters is used.
+2.0: merged
+
+2006-08-14
+Table View
+- default values are displayed and work properly with editing
+- current row and column are marked as selection on the vertical
+ and horizontal header sections
+2.0: merged
+KexiDB
+= improved table name altering
+= row edit buffer now handles default values
+2.0: merged
+
+2006-08-11
+KoProperty
+= "3rdState" option for boolean types now uses a combo box with 3 items
+= removed unmaintained QT_ONLY code
+2.0: merged
+= 2.0: some dependency on Qt3 removed
+
+CSV Export
+= fixed crash on exporting to clipboard
+- time and object values: fixed exporting or copying to clipboard
+2.0: merged
+Main Window
+= "copy special" action is available after switching back to table data view
+2.0: merged
+
+2006-08-09
+KoProperty
+= "3rdState" option added for BoolEdit - i18n'd QString, if not empty,
+ the the editor's button accept third "null" state with name equal to this string.
+ When this value is selected, Widget::value() returns null QVariant.
+ This option is used for example in the "defaultValue" property for a field
+ of type boolean (in Kexi Table Designer). Third, "null" value of the property
+ means there is no "defaultValue" specified.
+Table Designer
+- Added "defaultValue" property to the designer
+= Alter Table Test Suite: added "i" variable support (including operator++),
+ closeWindow command, quit command, and optional "clipboard" flag
+ for showSchema, showActions, showTableData. Improved showTableData command.
+ Added "defaultvalues" test.
+2.0: merged
+KexiDB
+= added BLOB-encoding/decoding-related functions
+= improved storing and retrieving "defaultValue" property of table field
+2.0: merged
+
+2006-08-08
+General
+= operator tristate::bool() removed (unsafe, comparison with false was broken)
+2.0: merged
+Table Designer, KexiDB
+- fields of type yes/no have default value set to false (by default)
+= schema editing improved; on table altering, default values are inserted
+ and not-empty values are used is there's no default
+2.0: merged
+
+2006-08-07
+KexiDB
+= improved version-related parts of the API
+= server version information is retrieved
+2.0: merged
+
+2006-08-03
+KexiDB
+= PostgreSQL: use TIMESTAMP, not DATETIME for date/time data type
+2.0: merged
+
+2006-08-02
+CSV Export
+= GUI-less exporting is supported in the API (usable for tests)
+2.0: merged
+General
+- "skip-startup-dialog" command line option added
+2.0: merged
+KexiDB
+= Field::typeForString() and Field::typeGroupForString() are now case-insensitive.
+= added missing support for "indexed" and "type" properties for functions
+ like KexiDB::setFieldProperty()
+2.0: merged
+KoProperty
+= Property::oldValue() should not return value() if no value is available.
+2.0: merged
+Table Designer
+= fixed setting types and subtypes
+= fixed a crash when there are more than 50 "alter table" actions
+2.0: merged
+
+2006-08-01
+Table Designer
+= A test suite added for table altering
+2.0: merged
+
+~~~~~~~~~~~~ 1.1 alpha 1 release (with KOffice 1.6 alpha 1) ~~~~~~~~~~~~
+
+2006-07-31
+KexiDB
+= custom properties can be set for Field objects
+2.0: merged
+Table View
+- deleting cell contents containing Yes/No values
+- do not display drop-down buttons for read-only data
+2.0: merged
+Query Designer
+- possible crash fixed when new design is saved in the data view:
+ only rebuild schema if it has not been rebuilt previously
+1.0/2.0: ported
+
+2006-07-28
+Table Designer
+- fields of Yes/No type have "not null" set by default
+- fixed clearing "not null" property
+2.0: merged
+Table View
+- boolean "not null" field displays "false" instead of null in the new row
+= fixed column width adjustment
+= unused code removed
+2.0: merged
+
+2006-07-26..27
+Property Editor
+- fixed image scaling in the image editor
+2.0: merged
+Forms
+- image box: (in data view) do not allow to show context menu using keyboard
+ for static image (i.e. if no data source is assigned);
+ such a widget also ignores focus events (effective focus policy is NoFocus then);
+ focus frame is painted when needed
+2.0: merged
+
+2006-07-24
+Main Window
+- fixed setting focus after switching between views
+2.0: merged
+
+2006-07-18..2006-07-25
+KexiDB
+- added support for db-aware lookup fields
+2.0: merged
+Table View
+- added support for db-aware combobox fields
+- displaying values in combo box popup fixed
+2.0: merged
+CSV Import Dialog
+- leading 0xFEFF "BOM" unicode character is skipped
+ (http://www.unicode.org/charts/PDF/UFFF0.pdf)
+- added "Strip leading and trailing blanks off of text values" option
+- fixed changing options
+2.0: merged
+
+2006-07-17
+Forms
+- fixed maintaining focus when menu has been clicked
+- fixed tab/backtab for button widget (non-db-aware widgets in general)
+2.0: merged
+
+2006-07-10..14
+Forms
+- context menus now have consistent title: "<objectname> : <objecttype>"
+- cut/copy/paste shared actions work for form widgets in data view
+2.0: merged
+Table View
+- context menu from image box reused in the Table View's cell editor
+- cut/copy/paste/clear actions work for the image cells, including shortcuts
+- cut/copy/paste/clear actions work for text/number/boolean cells
+- fractional character ("," or ".") can be entered as the first character for
+ floating-point data types, what means 0.xxxxx
+- fixed updating row number info in the record navigator and updating the current
+ cell after column sorting
+- BLOB data is sorted by size
+2.0: merged
+KexiDB
+- SQLite driver: read only connections work again
+2.0: merged
+
+2006-07-07
+Forms
+- AutoField, Image Box: fixed setting background color;
+ drop-down button keeps its default color
+2.0: merged
+Table View
+- added drop down button as image cell editor
+2.0: merged
+
+~~~~~~~~~~~~~~~~ 1.0.2 bugfix release (with KOffice 1.5.2) ~~~~~~~~~~~~~~~~
+~ Now, as 1.0.x is closed for changes, all the changes are merged into 2.0 only ~~
+
+2006-07-06
+Forms
+- Data Source tab of the Property Pane: fixed entering field or table/query
+ name by hand: pressing return accepts selection; focus-out accepts selection;
+ entering in-progress clears previous selection
+2.0: merged
+- displaying images optimized: no background is cached or painted
+2.0: merged
+- text box widget now displays text aligned to the left after loosing the focus
+2.0: merged
+- possible crash removed when changing property of enum type
+1.0/2.0: ported
+Table View
+- Image cell editor: appearance improved, tooltip with a thumbnail added,
+ dropdown button with context menu added (the same as in Image Box form widget)
+2.0: merged
+KoProperty
+- fixed handling groups
+(1.0.2)
+Forms
+- improvements related to data editing within forms backported from 1.1
+(1.0.2)
+
+2006-07-05
+Forms
+- AutoField: fixed handling foreground and background colors;
+ added "foregroundLabelColor" and "backgroundLabelColor" properties;
+ subproperties coming from super classes are now visible;
+ better handling internal layout depending on label's position
+2.0: merged
+- "tristate" property for check box widget redefined:
+ supports DefaultTristate, TristateYes, TristateNo values.
+ Thus, for bound widgets null values can be set
+ and for unbound widgets only Yes or No values can be set (by default).
+2.0: merged
+- fixed creating modified property objects of enum type
+2.0: merged
+KoProperty
+- fixed possible crash when clicking "undo" button causing
+ the full property set's reload (it was the case for "Editor Type"
+ property of the AutoField widget)
+1.0/2.0: ported
+
+2006-07-04
+KexiDB
+- fixed copying index and table objects
+1.0/2.0: ported
+
+2006-07-03
+Forms
+- AutoField: focus label's associated widget (editor)
+ when user clicked the label
+2.0: merged
+- AutoField: update data source and values for subproperties on form loading
+ also when we're switching from design to data view
+1.0/2.0: ported
+- AutoField: removed crash when table field has been renamed in the data source
+1.0/2.0: ported
+
+2006-06-28
+KexiDB
+- SQLite 3.2: fixed opening databases with non-latin1 pathnames
+2.0: merged
+CSV Import Dialog
+- parsing data improved: values without leading zeros, like 1/2/2006 work;
+ Since apps like MS Access in the USA creates csv files with date
+ format month/day/year, (and without leading zeros), text with "/"
+ inside is assumed to be in this format.
+1.0/2.0: ported
+
+2006-06-27
+Forms
+- Image Box widget: now supports saving data in the database!
+ Fixed filling duplicated data widgets; fixed editing;
+ "Drop-Down Button Visible" property added.
+2.0: merged
+
+2006-06-26
+Forms
+- AutoField widget: changing value of "Widget Type" property reloads the entire
+ property set, so new properties can appear and unused properties can be hidden
+ in the Property Editor.
+2.0: merged
+KoProperty
+- delayed deleting of editors on setting a new property set
+1.0/2.0: ported
+- added additional, internal "property change" signal so it will be always emitted
+ _before_ the public one; thus e.g. it is legal to reload the property set within
+ the "property change" handler
+2.0: merged
+
+2006-06-19..23
+Forms
+- ImageBox widget: drop-down button moved to bottom-right, usability improved
+ (e.g. keyboard handling for popup), db-awareness added,
+ frame-related properties added, tab/backtab handling fixed,
+ fixed updating position of image when the frame changes
+2.0: merged
+- AutoField widget: added support for Image (Object) type,
+ fixed handling color properties, internal editor's properties are
+ automatically inherited by the AutoField widget as so-called subproperties
+2.0: merged
+- removed flickering of the Property Pane after dropping a new field
+ onto the form
+2.0: merged
+
+2006-06-06
+CSV Import Dialog
+- fixed recursion when importing clipboard data
+1.0/2.0: ported
+- allow to change the delimiter when importing clipboard data
+1.0/2.0: ported
+Migration
+- improved usability for selecting type of destination database
+ (no mention of SQLite for now)
+2.0: merged
+KexiDB
+- renaming table fields works, table schema is recreated when needed,
+ and just altered in-place when it's enough to do so
+2.0: merged
+Table View
+- cell editor: the contents are marked as changed only when the original
+ value differs from the current
+1.0/2.0: ported
+Table Designer
+- changing field's description is supported by undo/redo commands
+ and by alter table actions.
+2.0: merged
+- Internal Debugger: added "Real alter table" button,
+ for convenience, "save" action now uses the old alter table code
+2.0: merged
+
+2006-06-05
+KoProperty
+- clear all properties and group names when using operator =
+1.0/2.0: ported
+KexiDB
+- SQLite library upgraded from 3.0.7 to 3.2.8 (backward compatible),
+ to get "table renaming" function.
+2.0: merged
+- optimization: "rename table" action now uses "ALTER TABLE RENAME TO"
+ SQL statement, O(1).
+2.0: merged
+- X-Kexi-DoNotAllowProjectImportingTo KexiDB driver property added, false by defaut
+2.0: merged
+
+2006-06-02
+KexiDB
+- fixed setting database filename when relative path has been provided
+1.0/2.0: ported
+- minor utilities added usable for altering
+2.0: merged
+- table altering improved for SQLite: changing data types will be possible soon
+2.0: merged
+
+2006-05-31
+KoProperty
+- methods in Set::Iterator are now const
+2.0: merged
+- added propertyValues(const Set& set) utility function
+2.0: merged
+
+2006-05-30
+Table View
+- spreadsheet mode: fixed inserting empty rows and deleting rows
+1.0/2.0: ported
+Table Designer
+- improved undo/redo actions: deleting and redoing actions fixed
+2.0: merged
+- added title to the context menu
+2.0: merged
+KexiDB
+- improved alter table machinery; now fields are identified by UIDs
+ to avoid problems with multiple fields sharing the same name
+2.0: merged
+
+2006-05-29
+KoProperty
+- animate clicking on the group title's area
+2.0: merged
+- added a container widget that can be used to split information into hideable sections
+2.0: merged
+Form Designer
+- data source pane splitted vertically into two sections for clarity
+2.0: merged
+Table Designer
+- clear PK when selecting non-integer type
+2.0: merged
+
+2006-05-27
+KoProperty
+- Possible fix for crash. Not sure this is the definitive fix for bug 124917.
+ Thanks to Jaime Torres
+2.0: merged
+- group items now have rather pretty appearance styled with KDE style
+2.0: merged
+- fixed groups order, API changed: now it's possible to get order of groups
+2.0: merged
+- sorting disabled when groups are present
+2.0: merged
+- icons can be defined and displayed within the group items
+2.0: merged
+
+2006-05-26
+KexiDB
+- improved handling extended table schema information
+- improved alter table
+2.0: merged
+Table Designer
+- improved undo/redo actions; prepared for reuse
+ in the table-altering machinery of KexiDB
+2.0: merged
+
+2006-05-25
+Forms
+- set the data source readonly if it comes from query
+1.0/2.0: ported
+- "Read only" property is now supported by all data-aware widgets,
+ and form itself
+2.0: merged
+- background color of the line edit in read-only mode is blended with white
+ to get lighter gray
+2.0: merged
+- accessibility+usability: read only line edit widget now shows the caret
+2.0: merged
+- display field caption as title for editor's context menu
+2.0: merged
+
+~~~~~~~~~~~~~~~~ 2006.1.1 (OOPL, bugfix) ~~~~~~~~~~~~~~~~
+
+2006-05-18
+Table Designer
+- improved support Undo/Redo actions
+2.0: merged
+Forms
+- fixed saving data changes for db-aware checkboxes
+ (fixed by setting StrongFocus policy as default)
+1.0/2.0: ported
+
+~~~~~~~~~~~~~~~~ 1.0.1 bugfix release (with KOffice 1.5.1) ~~~~~~~~~~~~~~~~
+
+2006-05-17
+Main Window
+- Project Navigator: remove Enter and CTRL+Enter accels,
+ as these blocks other actions
+- Project Navigator: fix setting focus
+1.0/2.0: ported
+
+2006-05-16
+Migration
+- display error for not found import/export driver
+1.0/2.0: ported
+Startup
+- (temporary) do not propose importing broken SQLite databases files
+ as there is no import driver implemented yet; display error message instead
+1.0/2.0: ported
+Table Designer
+- fixed saving boolean types for table design
+1.0/2.0: ported
+
+2006-05-15
+General
+- -execute command line option added; useful for executing macros, scripts, etc.
+2.0: merged
+Table View
+- small API and source code improvements (s/buf/set)
+2.0: merged
+KoProperty
+- fixed making a deep copy of Set object containing custom properties
+- deep copy of Set object now maintains the original order
+- fixed possible crash on deleting Set object created by copy constructor
+1.0/2.0: ported
+
+2006-05-09
+KoProperty
+- API changed: a few methods are now const; optimization
+2.0: merged
+
+2006-05-08
+KexiDB
+- fixed converting floating-point values to SQL-compatible strings when the number
+ was provided as string QVariant
+1.0/2.0: ported
+Forms
+- fixed entering floating-point values for locales where ',', not '.' is the fractional point
+1.0/2.0: ported
+Query Designer
+- show error message when switching to other view failed (i18n-safe)
+- just show empty editor when opening a design in text view and there is no sql
+ statement found in the backend
+1.0/2.0: ported
+MDB Import
+- fixed importing decimal (numeric) values
+ (by fixing setting fractional point's position and adding negative sign when needed)
+1.0: ported
+
+2006-05-05
+Table Designer
+- Undo/Redo actions added and integrated with table altering
+Internal
+- "AlterTable" tab added to the "Kexi Internal Debugger" window
+
+~~~~~~~~~~~~~~~~ 2006.1 (OOPL) ~~~~~~~~~~~~~~~~
+
+2006-05-04
+Main Window
+- fixed crash when double (or single, depending on the settings) clicking
+ on the empty area of the Project Browser
+1.0/2.0: ported
+Query Designer
+- fix saving design changes while in Data View
+1.0/2.0: ported
+
+2006-05-02
+CSV Import Dialog
+- fixed importing floating-point values where there are integer
+ and floating-point values mixed
+1.0/2.0: ported
+MDB Import
+- fixed importing floating-point or decimal values (there is still a bug in mdbtools
+ with fetching decimal values)
+KexiDB
+- fixed building SQL INSERT statements for "prepared statement"
+1.0/2.0: ported
+
+2006-05-01
+KexiDB
+- ported to Qt4; tests/newapi works
+
+2006-04-28
+KexiDB
+- SQLite3 driver: report error when closing database failed
+1.0/2.0: ported
+Core
+- KexiProject: report error when closing database failed
+Query Data View
+- properly destroy cursor on view removing; this also fixes the problem
+ with compacting Kexi database when it cannot be reopened read/write when
+ there was opened view with query data because of db cursor blocking the database
+- properly close as soon as the data is fetched (temporary solution)
+General
+- "Kexi Internal Debugger" introduced to help in Kexi development;
+ "showKexiDBDebuger" in [General] should be set to true and KEXI_DEBUG_GUI defined
+ to use this.
+Form Designer
+- fixed possible crash on closing form
+1.0/2.0: ported
+
+2006-04-27
+KexiDB
+- "visibleDecimalPlaces" property added for table fields of type floating point
+ (will be added for decimal and currency too); "Auto" state is supported
+ as default so only meaningful fractional digits are be displayed
+Table Designer
+- "visibleDecimalPlaces" property supported for floating point types
+Table View
+- "visibleDecimalPlaces" property supported when displaying and editing
+ floating point values
+MDB Import
+- on importing table rows: report failure when inserting row failed
+
+2006-04-25
+CSV Import
+- simplify whitespace of text in cells (for preview only)
+- fixed delimiter detecting
+- priority in delimiter detection changed: now ";" has priority over ","
+1.0/2.0: ported
+Form Designer
+- hide minimumSize and maximumSize widget properties
+1.0/2.0: ported
+
+2006-04-13
+Kexi 2.0
+- merged changes since revision 523723
+KexiDB
+- fixed loading length of text fields within table schema
+0.9/1.0/2.0: ported
+
+2006-04-12
+CSV Import
+- fixed problem with importing data with more than 100 columns (for default settings)
+1.0/2.0: ported
+- only the first 10KiB of data is loaded for the preview by default; can be adjusted
+ by setting ImportExport/MaximumBytesForPreviewInImportDialog option in kexirc
+1.0/2.0: ported
+- display rows number on the top of the window
+2.0: merged
+
+2006-04-11
+Forms Designer
+- fixed prossible crash while clearing "data source" field name;
+ now setting the autofield to unbound state works
+
+2006-04-10
+KexiDB
+- QuerySchema(TableSchema* tableSchema) ctor:
+ To avoid problems (e.g. with fields added outside of Kexi using ALTER TABLE)
+ we do not use "all-tables query asterisk" (see QueryAsterisk) item
+ but instead we add all the fields explicity.
+0.9/1.0/2.0: ported
+- PostgreSQL driver: fixed crash when a new field has been added to existing
+ table outside of Kexi
+0.9/1.0/2.0: ported
+Table Designer
+- More intrusive warning when altering table design
+1.0: ported (except the message)
+2.0: merged
+Forms Designer
+- fixed possible crash when switching to data view mode unbound autofield widgets
+- while in data mode, do not display "(unbound)" text for unbound autofield widgets
+
+2006-04-08
+KexiDB
+- use SQL-compliant TRUE and FALSE constants for PostgreSQL
+ (1 and 0 are unsupported)
+0.9/1.0/2.0: ported
+
+2006-04-07
+Table View & Form View
+- "invalid data warning" tool tip improved and reused in Kexi forms
+- many fixes related to handling date, time and date/time values
+
+2006-04-05
+Form Designer
+- KexiDBDoubleSpinBox and KexiDBIntSpinBox replaced line edit with validator because
+ of problems with handling null data and signalling changes.
+ (there's no backward compatibility problems as these two widgets were used internally
+ in the AutoField data widget)
+- fixed saving data changes made within auto field widgets
+- fixed support for tab/backtab key within auto field widgets
+- fixed problem when sometimes form was not filled with data after change of the design
+1.0/2.0: ported
+
+2006-04-04
+CSV Import
+- possible crash fixed after switching character encoding
+1.0/2.0: ported
+Main Window
+- "Compact Database": now closing the current project is required;
+ if there's no project is opened, user is asked for picking one
+Property Editor
+- possible crash fixed when bool editor's state is quickly switched
+1.0/2.0: ported
+
+2006-03-29
+Main Window
+- "Compact Database" admin action added
+KexiDB
+- API version increased to 1.8
+- "Compact Database" implemented for SQLite3 databases
+
+2006-03-28
+Startup
+- --readonly command line option added
+TableView
+- boolean editor: only accept clicking on the [x] rect
+1.0/2.0: ported
+
+TODO: merge trunk with revision 523723
+
+~~~~~~~~~~~~~~~~ 1.0 stable (with KOffice 1.5) ~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~ 1.0 rc1 (with KOffice 1.5 rc1) ~~~~~~~~~~~~~~~~
+
+2006-03-20
+KexiDB
+- use local sockets by default for local server connections, if possible
+
+2006-03-16
+KexiDB
+- added simple encryption for stored passwords
+- allow to save empty password in .kexis and .kexic files
+Startup
+- ask for password (without storing it) for connection data without stored password
+
+~~~~~~~~~~~~~~~~ 1.0 beta 2 (with KOffice 1.5) ~~~~~~~~~~~~~~~~
+
+2006-02-28, 03-03
+PostgreSQL driver
+- fix for displaying unicode error messages
+- fixed escaping binary data (octal values are used for special characters)
+- fixed loading and saving BLOB data (images within forms work now)
+
+~~~~~~~~~~~~~~~~ 2006 (OOPL) ~~~~~~~~~~~~~~~~
+
+2006-02-22
+CSV Import Dialog
+- fixed updating "primary key" checkbox
+
+2006-02-21
+Table View
+- row highlighting fixed for combo box popup, now it's used for any table view
+
+2006-02-20
+Project Import Wizard
+- do not ask about overwriting file if server-based destination has been selected
+
+2006-02-17
+Query Designer
+- fixed crash on saving new query design
+Main Window
+- disabled complex and redundant action 'View -> Tool Docks' menu.
+ This comes from KMDI, not Kexi itself and will be completely removed
+ in KDE4 version. IIRC, we could not hide it now as this may cause crashes.
+
+Form Designer
+- text label widgets: "word wrap" is turned on by default
+- fixed update "word wrap" property for label widgets
+- fixed random cursor setting for the form surface
+- fixed problems with inserting widgets in a random position
+ and random rectangles painting problems
+ (all this due to MouseButtonRelease event coming BEFORE MouseButtonPress event)
+
+2006-02-16
+Query Designer
+- Row editing within the "Columns" area is accepted before saving.
+ If the editing cannot be accepted, saving is aborted.
+Table Designer
+- set property editor to read-only mode for read-only database connections
+Data Table
+- disable combobox editor's dropdown button for read-only database connections
+Property Editor
+- fix for read-only mode: support it also globally at property set level
+- disable editing for read-only widgets
+- fix for displaying double values for current locale
+
+2006-02-15
+General
+- when query design has been changed and saved, subsequent openings of a form using
+ it will reload its definition. The same for 'page setup' dialogs, etc.
+- fixed crash when 'page setup' dialog is opened, closed, and opened again for
+ the same data object
+
+2006-02-10, 14
+Main Window
+- closing dialog is now marked as 'pending job', the same as for opening
+- When there are pending jobs, 'quit' and 'close project' actions are
+ delayed and executed after last pending job finishes. This removed possible
+ crashes when user closed application's main window (or closed project)
+ when there are pending jobs.
+
+2006-02-03,07
+Forms
+- fixed painting "geometry" rectangle when inserting new widget
+- update tab stops information before displaying "Tab Stops" dialog
+- fixed invalid resizing for widget's inline editor
+- 'edit->tab order' action is available also when form design is not focused
+
+2006-02-01
+KexiDB
+- MySQL: added temporary (unoptimized) implementation of Prepared Statement
+- MySQL, SQLite: fixed inserting BLOB data (without Prepared Statements)
+- MySQL: fixed fetching BLOB data (without Prepared Statements)
+- MySQL, PostgreSQL: fixed entering strings containing characters like ' \ "
+- most debug lines commented out
+CSV Import Dialog
+- fixed importing for MySQL backend
+Form Designer
+- fixed saving images (binary data) to MySQL backend
+
+2006-01-31
+CSV Export Wizard
+- When data is copied to clipboard, default delimiter is tabulator
+ and options are hidden.
+
+2006-01-30
+Form Designer
+- fixed saving new forms for MySQL databases
+CSV Import Dialog
+- fixed crash when importing CSV files smaller than 200 bytes
+
+2006-01-25,26
+KexiDB
+- SQLite3 driver & kexisql3: fixes for read-only opening
+Main Window
+- fixed usability: read-only flag of the db connection is now visible
+ in the statusbar
+- added question about opening db file as read-only when it is already opened
+- disable actions that require write access to the db if it's opened as readonly
+Startup
+- fixed support for "-print-preview" command line option
+Simple Printing
+- enlarge default zoom
+Property Editor
+- fixed editor refreshing after contents loading
+Table View
+- 'inserting enabled' flag is always false for read-only objects
+
+2006-01-24
+KexiDB
+- Unicode support fixed for MySQL
+
+2006-01-23
+Table View
+- fixed acceping am/pm for entering time values
+- various fixes for entering date, time and date/time values
+- time can be entered without seconds
+- small passive warning box is displayed when entered value is invalid
+ (currently for date, time and date/time types)
+
+~~~~~~~~~~~~~~~~ 1.0 beta 1 (with KOffice 1.5) ~~~~~~~~~~~~~~~~
+
+Main Window
+- 'single click opens' is mode is off by default, can be turned on
+Table Designer
+- Object type hidden for beta1
+Table View
+- date, time and date/time values are displayed using KDE settings
+
+2006-01-20
+General
+- examples/ directory in the source code tree now contains
+ example database(s)
+Form Designer
+- Data source pane: disable widget's data source combo box
+ when there is no form's data source defined
+- temporary disable AutoField
+- temporary disable cursor property
+Query Designer
+- fixed displaying criteria if WHERE section contains '('
+- tables positions are preserved if possible when
+ relations view updating is needed
+
+2006-01-18, 19
+Form Designer
+- widget library naming made more verbose to avoid potential
+ conflicts between packages
+- versioning added for widget library plugins; this can also
+ avoid conflicts between plugins as we've just renamed them
+Query Designer
+- fixed displaying criteria for table view rows when 'visible'
+ column is set to false
+- all-tables-asterisk is also hidden when last table is hidden
+- temporary data member isolated; fixed possible crash when
+ switching back from SQL View to Design View
+
+2006-01-17
+KexiDB
+- even more verbose error messages for failed SQL statement execution
+CSV Import Dialog
+- tabulator is default field delimiter for clipboard data
+- delimiter detection improved
+General
+- "Dangerous" flag set for potentially destructive dialogs
+- a few redundant i18n strings removed
+Table View
+- data for large table view is destroyed without blocking GUI
+
+2006-01-16
+Project Import Wizard
+- hide Kexi file extensions in source connection file dialog's filter
+Main Window
+- fixed saving Project Navigator Side Panel's visibility
+- do not allow to open the same dialog twice - it was possible
+ for slowly loading dialogs with large data (e.g. a table view with 10^4 rows);
+ pending 'open' actions are now known
+Simple Printing
+- do not paint invalid values of type time, date or date/time
+Table Designer
+- fixed selecting subtypes
+KexiDB
+- SQLite driver: missing {} in PreparedStatement
+- testing connection executes "USE DATABASE" if needed (e.g. for PostgreSQL)
+
+2006-01-12
+Table View
+- Usability: Date, Date/Time and Time cell editors reimplemented
+
+2006-01-11
+Migration
+- fixed opening project after import if user wanted this
+
+2006-01-09
+Form Designer
+- infinite loop removed when updating multiple selection
+0.9: backported
+0.8: backported
+- "Assign Action" dialog cleaned
+KexiDB
+- generate cleaner query statements:
+ if there's single table in query schema, skip "table." prefix in resulting
+ SQL string, e.g. build "SELECT a, b FROM t" instead of "SELECT t.a, t.b from t".
+Query Designer
+- "Totals" column hidden as it is not implemented
+
+2006-01-05..6, 10
+CSV Import Dialog
+- optimalization for large data: only about 100 rows are loaded for preview
+- progress dialog added on dialog creation (opening can be cancelled)
+- layout fixes
+- missing column with primary key is added if user wants it
+- optimized importing large (many thousands of rows) tables:
+ GUI is not used to store entire table before importing;
+ only current record is kept in memory buffer
+- progress dialog added on importing (importing can be cancelled)
+
+2006-01-02..04
+Startup
+- "--skip-dialog" command line option added.
+ Skips displaying connection dialog\n"
+ and connects directly. Available when opening .kexic
+ or .kexis shortcut files.
+- opening server-based projects works in File->Open when
+ there's already project opened.
+- "--connection <shortcut_filename>" command line option added.
+ Specifies a database connection shortcut .kexic file containing
+ connection data. Can be used with --createdb or --create-opendb
+ for convenience instead of using options like --user, --host
+ or --port. Note: Options like --user, --host have precedence
+ over settings defined in the shortcut file.
+- creating server-based projects works in File->New when
+ there's already project opened.
+Migration
+- Project Migration Wizard is proposed if a server-based database
+ is incompatible with Kexi and cannot be opened
+- if a destination for imported project is a server, it is also
+ opened after import, if user wanted this
+
+2005-12-23, 29
+General
+- (data forms, table view): fixed entering row number in the row navigator
+- win32: fixed saving recently displayed directories in file dialogs
+KexiDB
+- SQLite: opened files are now locked for read/write by default;
+ related error messages improved
+Simple Printouts
+- pages to print can be selected
+- number of copies can be entered
+- pages collation switch on or off (i.e. 1-2..n; 1-2..n.... for collation
+ and 1-1-...1; 2-2..2; .. for "no collation")
+- page count is properly computed and printed on the footer as "Page x of y"
+- "Page Setup" action added;
+ now it's window is only presented when user really wants this
+- text of the page title properly updated
+
+2005-12-12..22
+Simple Printouts
+- added Simple Printouts window with print preview and settings
+Table View
+- failed accepting of row editing now cancels window closing
+- fixes for updating row data (this was sometimes visible in Table Designer)
+Table Designer
+- avoid asking twice for saving changes to the design on window closing
+- find unique name for autogenerated primary key fields
+Kexi Core
+- possibility of adding non-db-aware windows/tabs in Kexi
+General
+- added --print and --print-preview command line options
+- (temporary) mini-kolibs added to get print settings stuff and remain
+ independent on kolibs
+
+2005-12-09
+Forms
+- memory for data is now properly freed before closing a form
+0.9: backported
+0.8: backported
+CSV Export
+- CRLF characters are used as in RFC 4180
+- fixed gcc compiler-specific crash
+- quote characters are escaped inside strings
+- date/time values are exported without "T" inside (from ISO format)
+CSV Import
+- fixed handling line endings inside strings
+- time and date/time types import and detection added
+
+2005-12-05..08
+Property Editor
+- repaint fixes when column width changes or when persistent selection is set
+- Property::captionForDisplaying() added: beter support for multiline property captions
+- method names improved in Factory
+- do not steal focus when selection is preserved
+- possible crashes fixed when property values are changed in an instant reaction
+ on changing a property value in the editor; this is the case e.g. in Kexi Table Designer
+- API docs updated
+General
+- custom "Identifier" property type and editor added (allows only entering identifiers);
+ used in Table/Form/Report Designers
+Table Designer
+- Field captions can be now entered by users instead of names.
+ Field names are then automatically generated.
+- fixed problem with undo button for subtype combo box
+- support for BLOB (Object) type; "Image" is now the only subtype of this type
+- "unique" and "indexed" properties hidden for BLOB type
+Table View
+- clearing text removes value in combo box field for "enum" mode
+
+2005-12-02
+Main Window
+- usability: for IDEAl mode tabs are also visible when one window is opened
+ (otherwise user could not know what the window's caption is)
+- usability: added "close" button to the tab widget for IDEAl mode
+- 'hover close button' is now turned off by default for IDEAl mode
+ (added HoverCloseButtonForTabs setting to kexirc for changing this)
+
+2005-11-29..12-01
+General
+- file dialog's filters now contain only filters that make sense,
+ e.g. there's no "mdb" filter when we're writing to a file.
+- usability: file dialogs now store recent directory paths
+ in the config file, and this is now used everywhere in Kexi
+- default extension is properly added when there's multiple filters set
+ in the file dialog
+Startup Dialog
+- check for existence of a file user has selected to open
+CSV Files Export
+- CSV Export dialog added (also for copying to clipboard)
+ with storable export options
+CSV Files Import
+- usability: integer spin box used for "Start at line" option instead
+ of a combo box
+
+2005-11-28
+Main Window
+- "File->Export->Data Table" and "Edit->Copy Special->As Data Table"
+ actions added
+Parts
+- X-Kexi-PartVersion increased to 2
+- added boolean servicetype property: X-Kexi-SupportsDataExport;
+ true for tables and queries
+CSV Import
+- space saved: radio buttons replaced by combo boxes
+- added "Always use this encoding when importing CSV data files" option
+ to "CSV Import Options" dialog (saved to Kexi config file).
+
+2005-11-26
+Project Migration
+- added "Always use this encoding in similar situations" option
+ to "Advanced options" dialog (saved to Kexi config file).
+
+2005-11-25
+Project Migration
+(API CHANGED)
+- Added versioning for migration drivers (current one is 1.1)
+- Added properties support for migration drivers
+- All error messages are displayed on the last "Failure" page
+- Added "Advanced options" button and dialog, currently with "Encoding" parameter
+ for source database. Currently used by MDB driver for MSJet3 files.
+General
+- KexiCharacterEncodingComboBox added - a combobox widget providing
+ a list of possible character encodings
+
+2005-11-22..23
+Project Migration
+- "All Supported Files" filter added to Kexi file dialogs
+- Files like MS Access .mdb can be now clicked and then Import Wizard will pop up
+ with preselected parameters. This also works using command line.
+- Import Wizard's GUI: improved messages, added real Finish page
+- No need to select source driver, it's enought to select a filename or db connection
+Startup
+- Better usablity of 'Open Existing' tab page
+- Better usablity of 'New project wizard'
+
+General
+- compile fix for some compilers
+0.9: backported
+0.8: backported
+
+2005-11-15
+Forms
+- AutoField widget: update m_label for font/palette change
+- AutoField widget: display "Unbounded Auto Field" text when
+ no data source is set (in design time, like for image box)
+
+2005-11-09..14
+Forms
+- AutoField widget: improved behaviour in design time
+ (setting caption/type/label position)
+- frameColor property added for widgets like text label or frame:
+ behaves well also for "raised" frame shapes
+- new widget KexiFrame, will provide more features; backward compatible
+ with QFrame
+- do not allow to move top-level widget's position using arrow keys
+Property Editor
+- fix entering text into string-type editors with 'autoSync' flag
+
+2005-11-07
+Startup
+- When opening connection (most often using .kexic file), connection dialog is shown
+ so user can adjust connetion parameters, (optionally save them) and finally perform
+ the connection to see available databases. If connection failed, connection dialog
+ is popping up again.
+
+2005-11-02..04
+2005-10-31
+Project Migration
+- call KexiProject is used to create destination database instead of KexiDB::Connection
+ because tables like kexi__parts and kexi__blobs have to be created.
+- Moving kexi projects from database servers to database files works.
+ kexi__objects table contents are properly copied if existed in source database,
+ so queries, forms etc. are properly imported
+- Wizard's GUI improvements
+KexiDB
+- More verbose error messages:
+ Error messages and result numbers can be now inherited by using
+ Object::setError( KexiDB::Object *obj, const QString& prependMessage ),
+ so less information will be lost while displaying message e.g.
+ on failed cursor opening.
+- mysql/pgsql/sqlite/mdb drivers updated for the current API
+
+- Tables: fixed retrieving field captions and descriptions
+ 0.9: backported
+ 0.8: backported
+
+2005-10-27
+KexiDB
+- kexi__parts internal table is now created within KexiProject,
+ not KexiDB::Connection
+- kexi__useractions and kexi__final are no longer created
+Project Migration
+- speedup: table creation and data copying is performed within transactions
+- Added possibility of migrating Kexi projects from one backend to another
+ (e.g. from MySql server to SQLite file) internal kexi__ tables are properly recreated;
+ data, e.g. for kexi__parts and kexi__blobs is properly copied
+
+2005-10-24..26
+Forms
+- Property editor widget for pixmaps now uses KexiBLOBBuffer
+- forms manager turned into singleton,
+ and two widget libraries are now created: one for forms and one for reports
+Property Editor
+- show preview box in visible area
+KexiDB
+- DatabaseProperties utility class added to API
+Table View
+- Fixed ugly bug for switching sorting order (thanks Aaron)
+ 0.9: backported
+ 0.8: backported
+- Better sorting:
+-- NULL values are smaller than everything else
+-- Special sorting for data/time/datetime/bin integers/floating point numbers
+ 0.9: backported
+ 0.8: backported
+
+2005-10-19
+Forms
+- image box doesn't provide popup in data view mode when
+ no data source is assigned
+- Kexi Form widgets code splitted
+
+2005-10-18
+KexiDB
+- Connection::querySingleRecord() improved
+- QuerySchema::addToWhereExpression() added
+- SQLite driver: fixed BLOBs retrieval
+
+2005-10-15
+Table View
+- crash fixed in combo box
+ CCBUG: 114697
+ 0.9: backported
+ 0.8: backported
+- alt+down shortcut can be used to drop down combo box (previously only F4 worked)
+ 0.9: backported
+ 0.8: backported
+- combo box editor's popup:
+ row repainting fixed after current highlighted row has changed
+ 0.9: backported
+ 0.8: backported
+Table Designer
+- avoid possible infinite recursion
+- selecting non-integer type group using 'type combo' clears PKEY flagm, if set
+
+2005-10-14
+Forms
+- It was not possible to change the tab order of
+widgets in the Edit Tab Order dialog with keyboard.
+
+2005-10-12
+Project Import Wizard
+- usability++: after successful import, the project is opened
+
+Query Designer
+- conditions: ambiguous expressions fixed:
+ "t1.id > 100" is used instead of "id > 100" in queries like
+ SELECT * FROM t1, t2 WHERE t1.id = t2.id AND t1.id > 100
+- fixed possible crash when invalid query design is loaded in design view
+- fixed switching to design mode after invalid query was opened directly in text mode
+- eat parentheses on criteria loading, in WHERE conditions like (a.id = value)
+- also accept tableName.fieldName on criteria loading, not only tableName
+- also accept e.g. "2 = variable" as condition, not only "variable = 2"
+- graphical representation of joins is generated back from SQL statement not only
+ based on explicit query schema's relations but are also retrieved
+ from "[t1.]f1 = [t2.]f2" expressions within WHERE section
+
+TODO: joins are not visible when switching from design to text mode (and conversely)
+ when there are no columns selected in FROM section, eg. "select from a, b"
+
+2005-10-06
+KexiCore
+- Fixed autogenerating object names (e.g. tables, queries) based on caption:
+ latin1 base name can be now chosen by translator
+BUG: 104858
+
+2005-10-05
+KexiDB
+- PreparedStatement introduced
+- mysql, sqlite drivers: avoid double freeing data member in ConnectionInternal implementations
+
+2005-09-27..29
+Forms
+- Images are stored in memory in original form, converting to QPixmap is only performed
+ for displayoing purposes. Such original pixmap can be saved back to file.
+ Original file name is also kept. KexiImageBox handles pixmap data with QByteArray member
+- KexiBLOBBuffer introduced: Application-wide buffer for local (static) BLOB data like pixmaps.
+ For now only pixmaps are supported. Used on design time before objects are stored.
+ This allows to copy multimegabyte pixmaps within Kexi without unnecessary memory loss.
+- KexiCustomPropertyFactory introduced: handles Kexi-specific property editor items
+
+2005-09-26
+KoProperty lib
+- Editor: possible crash fixed
+- Property: guarded ptrs are used to store pointers to Set objects
+ to avoid dangling pointers
+
+2005-09-24
+General
+- KEXI_STANDALONE compile time definition added
+- description in 'about data' mentions whether it is a standalone or 'bundled'
+ Kexi version
+- .po files are now prefixed with "standalone_" for standalone versions
+ to avoid conflicts with KOffice installations
+- yes, 1.0 is marked as standalone version
+- custom defines cleaned up
+
+2005-09-21..22
+MDB Driver
+- all strings are now properly decoded (object names and data):
+ MDB(UCS2)->MDBTOOLS(UTF8, using libiconv)->KEXI_MDB(QString)
+- non latin1 filenames handling fixed
+- fixed date, date/time types handling (including Y2k problem for years >=1930)
+- fixed currency handling (double type is assumed for now)
+Table View; Data Interface
+- fixed possible crashes for comboboxes within non-db-aware table views
+Table Designer
+- fixed 'subtype' combo refreshing within property editor
+KexiDB
+- DriverManager: to avoid conflicts with other copies of KexiDB,
+ eg. in ShowImg application, "kexidb_" prefix is required for KexiDB
+ driver files (KDE modules)
+- some docs and above information added for Doxygen
+
+2005-09-17..20
+KexiProject
+- createInternalStructures() added; semi-internal (not well suited for kexidb itself)
+ structures like kexi__blobs table are created now if not yet exist
+- internal kexi__blobs table introduced: for BLOB storage
+General
+- some i18n strings fixed
+- KEXI_NO_AUTOFIELD_WIDGET, KEXI_NO_IMAGEBOX_WIDGET temp. compile time options added
+
+2005-09-16
+Forms
+- FormIO: some fixes in method's naming
+KexiDB
+- tristate type is used in some more methods, so it's now possible
+ to catch "no data" cases, not only "server errors"
+- InternalTableSchema class added (to avoid polluting TableSchema/Conenction APIs)
+
+2005-09-15
+Kexi supports static and data-aware images boxes
+- properties implemented: scaling (aspect ration can be kept),
+ horizontal/vertical alignment
+Project Migration
+- cleaned up the code a bit
+- 'back' button works well on "dest. type" page when file-based driver is used
+- user is warned when no source filename was specified
+Forms
+- view mode (design/preview) is now also passed as option
+ to WidgetFactory::createWidget() so widget's look&feel can differ depending
+ on the view mode
+
+2005-09-14
+Property Editor
+- PixmapEditor improved displaying, and value changes support
+- Editor's widgets have proper geometry set a bit earlier
+
+2005-09-06 (aKademy)
+2005-09-08..13
+Forms
+- data-aware image box widget
+(partial implementation started during flight back from Girona to Warsaw)
+-- "insertFromFile", "saveAs", "cut", "copy", "paste", "delete", "properties"
+ popup actions added
+- "readOnly" property added for image, spinboxes and dbfield widgets
+
+2005-09-03 (aKademy)
+Relations view
+- fixed redrawing (in an unoptimized way)
+Property Editor
+- updating combobox items after change of list data
+TableView
+- combo box popup behaves more like a normal combo (highlighting, selecting)
+- added "what's this" support for table columns
+
+2005-08-27
+Forms
+- a fix for 'value changed' flag and datachangeslistener in autofields
+
+2005-08-19
+Startup
+- "-type connection" command line arg added
+ (i.e. support for applications/x-kexi-connectiondata mime type)
+Main Window
+- project navigator visibility flag is now stored in config file (usable for IDEAl)
+
+2005-08-16..18
+Main Window
+- Toplevel (buggy) and Tabbed (redundant) KMDI modes hidden in Window menu;
+ Changing MDI mode now requires application restart (the lesser of two evils)
+- 'Create' menu renamed to more typical 'Insert'; 'Data' menuu moved after 'Format'
+- project navigator: usability fixes
+Widgets
+- KexiSmallToolButton helper class added
+- fixes for KexiFlowLayout
+Forms
+- "other widgets" toolbar popup added for clarity
+
+2005-08-12
+Form Designer
+- possible crashes fixed
+
+2005-08-10, 11
+Form Designer
+- added possibility of inserting autofields using 'Insert fields' button
+ from Data Source tab
+- CommandGroup usability improved: it's now possible to addCommand()
+ with flag telling that it will not be executed on CommandGroup::execute()
+- above CommandGroup's feature is used to make a single command
+ out of 'inserting multiple autoFields' task
+- top-level form surface is resized when inserted autoField(s) couldn't fit
+- inserting autofields is atomic command supporting undo/redo
+General
+- applications/x-kexi-connectiondata MIME type defined
+
+2005-08-09
+Form Designer
+- data source tab: usability improved for field list
+Widgets
+- KexiFieldListView is now displaying unique fields list for queries
+KexiDB
+- 'bool unique' arg supported for QuerySchema::fieldsExpanded()
+ and TableOrQuerySchema::columns()
+Query Designer
+- fixes for recent API change of KexiFieldListView
+
+2005-08-08
+Main
+- better readibility: new project type selector uses listview, not icon view
+
+2005-08-05
+Property Editor
+- '-flat' arg added for test app
+- painting improved for both flat and groupped mode
+
+2005-07-29..2005-08-02
+Form Designer
+- field editor's types list reuses KexiDB::fieldName() for i18n
+- autocaption is set properly for field editor on design time
+- field editor's labelCaption is no longer needed
+- propeditor: preserve "changed" flags (marked with BOLD) between property set switching
+- KAcceleratorManager::setNoAccel(QWidget*) is used for widgets in design mode
+ to avoid adding accelerators (for FormIO and for new widgets inserting)
+- factories: isPropertyVisibleInternal() is now called for superclass when needed
+ (thus, properties like "iconText" are properly hidden)
+- when a widget is deleted or cut, its parent container become selected
+KoProperty library
+- fix for spin box editor's width
+- better display for composed editors for types like Rect
+- fixed undo button's size in Editor
+- font editor fixed
+- Editor: expand/collapse boxes painted using styles; whitespace added on the left hand
+- Editor: editor widget's focusing fixed
+Table Designer
+- a fix for 'subType' combo
+Main Window
+- Navigator: when items renaming, treat "*" ("changed" flag) as a special character,
+ not a part of the item's name
+
+2005-07-28
+Form Designer
+- field editor's default focus policy is now inherited from internal editor
+
+2005-07-25..27
+Form Designer
+- better support for Auto Fields, including drag&drop
+- DesignTimeDynamicChildWidgetHandler added - interface for adding
+ dynamically created (at design time) widget to event eater
+- editor inside KexiDBFieldEdit is visible at design time
+- KexiDataItemInterface::field() changed to columnInfo()
+ -> more information can be now reused for visual field (i.e. query alias)
+- dbwidgets moved to libformutils as we're reusing many
+ of them outside of dbwidgets factory
+- version information is loaded/saved within form's UI string
+
+2005-07-23
+Core
+- management related to Part's ID improved; fixed related bug
+
+2005-07-22
+KexiDB
+- support for PostgreSQL on win32
+- PostgreSQL driver's transactions handling improved
+
+2005-07-21
+Form Designer
+- checkbox inline editing enabled
+- width of inserted widgets' size hint increased a bit to for better usability
+
+2005-07-13..20
+General
+- @internal added for private classes to make api docs more readable
+Startup / Server Support
+- connection data editor improved
+- connection data is now storable
+- added support for adding/deleting/editing connection data
+KexiDB
+- BIC CHANGE (witin v1.7): bool ConnectionData::savePassword flag added
+- KexiDB::connectionTestDialog() added for performing test connections
+
+TODO:::::::
+-fix shared actions availability for ideal mode! (reported by ccpasteur)
+-fix "edit tab order" action: not working when prj browser is focused...
+
+TODO: koproperty: us ListData-like struct instead of QMap, because data combo boxes should be sorted
+
+2005-07-10
+KexiDB
+- BIC CHANGE TO v1.7: (portability fix) pkeyfieldsorder and fieldsorder mapping
+ now use int instead of uint
+
+2005-07-08
+KoProperty
+- 'list data' introduced to clearly define 'keys' and 'strings' for combobox-like editors
+- cursor editor fixed
+- other minor fixes
+
+2005-07-05..07
+Form Designer
+- Data source fields can be dragged from Data Source Pane
+ and dropped on the form's surface to create data-aware line edit widgets
+- on new form empty creation, undo/redo history is cleared
+ and undo/redo actions are not available
+- on design time, 'dataSource' properties for forms and dataaware widgets
+ are synced with data source combo boxes
+
+2005-07-04
+Property Editor
+- properties now have 'sortingKey', so the order of properies is maintained
+- fixed bug in Set::clear()
+Form Designer's Property Editor
+- better handling multiple widget selection
+- fixes for widget info label
+
+2005-07-02
+Form Designer
+- fixed i18n for property captions
+- fixed filling enum values
+
+2005-06-20..30
+- * KexiProperty library made more generic as KoProperty *
+- Kexi now uses KoProperty
+
+Form Designer
+- Data source tab in the Property Pane implemented
+- 'dataSourceMimeType' property added for data-aware widgets, so it can be explicity
+ defined whether an object pointed by 'dataSource' property is a table of query.
+ This change is backward compatible.
+- fixed switching property buffers when switching between child windows
+- record navigator is now hidden if there's no data source assigned for a form
+Query Designer
+- object combo box now also contain queries (supporting queries is not implemented yet though)
+ the list within the combo is property sorted and grouped; it's reused in 'Data source' tab
+ of Prop. Pane; the combo now reacts on deleting/creating/renaming of table or query
+KexiDB
+- TableOrQuerySchema helper class added
+Main Window
+- crash fixed in 'detach window' action
+Core
+- Field draging: API improved, now both query and table fields/columns can be dragged;
+ added support for multiple fields dragging
+- added 'higlight object' action for KexiProject (available from property pane)
+- improved API for reaction on object creation/deletion/renaming
+General
+- small icons cleaned up; added better query/form/report/script icons
+- 'X-Kexi-GroupIcon' property no longer used; such icons are too hard to design and recognize
+- KexiFieldListView and KexiFieldComboBox added
+- "kfd:customHeader" element added for inserting custom information like kexiformdesigner's version
+- Number of currently selected tab of the Property Pane is preserved when switching back
+ to dialog for a particular part (in design mode). So if a user have looked at "data source" tab,
+ it's no longer necessary to click this tab again.
+
+2005-06-10..16
+General
+- more utility functions and clases moved to kexiutils library
+Form Designer
+- data aware 'text editor' widget added
+- inline editor pointer is now being kept only within parent factory
+- we're not creating additional (blue) resize handle set, but just changes color
+ of existing set thus resizing works well even if we're during inline editing
+- inconsistences with storing pointers to currently edited widget removed
+ (m_widget, m_editor members are now private)
+- we're copying common properties from an editing widget to an inline editor
+ (e.g. "margin")
+- fixed resetting cursor after editing
+- on creating a new spring, splitter and line widget, orientation is set according
+ its to aspect ratio; if the ratio is undefined, popup menu with vertical/horizontal/cancel
+ options is displayed
+- fixed crash when pressing Tab key on form surface in design mode
+- added 2 custom tabs to property pane: Data Source (mockup for now), Widgets
+Property Pane
+- widget class is displayed above property editor
+- blinking reduced on property buffer switching
+
+2005-06-08
+KexiDB
+- fixed crash for SQLite2 driver
+CSV Import Dialog
+- deadlock removed when importing CSV text like ","
+Form Designer
+- (all branches) temp. fix: QTabWidget used instead of KTabWidget ('&' bug)
+
+2005-06-01
+General
+- (all branches) missing icons added
+KexiDB
+- (all branches) field names converted to lowercase in memory to avoid problems with comparisons
+
+2005-05-31
+Core
+- editor within property panel is accepted before buffer switching
+Form Designer
+- record is properly fetched on subsequent switching to Data View mode,
+ especially after data source changes
+
+2005-05-25
+2005-05-27
+Form Designer
+- fixed crash when there're missing widgets
+Query Designer
+- bug for relationships creation fixed
+
+2005-05-23
+CSV Import Dialog
+- "Start at line" parameter in now better updated
+
+2005-05-13, 14
+Form Designer
+- "vAlign" property is now invisible for lineedit widgets (it's noop there)
+- "(autonumber)" label is (identical to the one in tableview) is displayed
+ in lineedit and label widgets assigned to autonumbered fields
+- shadow in label widgets is properly set for altered indenting, margins and alignment
+- double focusing of text field fixed
+- "format_font" action added; it's now possible setting font for one or multiple widget's
+ standard using font dialog
+Core
+- fixed possible crash when an object has beed saved with a different name than proposed
+TODO:
+- in "kexi" mode, "font" property is not visible e.g. for top-level form widget,
+ and other widgets that doesn't display text
+- fix data src updating in the data view when it has been changed in design view
+CSV Import Dialog
+- support for setting primary key (including autodetection)
+- support for 'first row contains column names' flag (including autodetection)
+- column types are autodetected
+- import is performed within a single transaction
+- characted encoding fixes; different encoding can be selected
+- delimiting character is autodetected
+- clipboard support re-added
+- floating-point values are properly parsed
+- empty cells are supported
+- new table name is based on provided filename
+Main Window
+- 'close all' works well if there are more than one yes/no/cancel dialogs required to show
+
+~~~~~~~~~~~~~~~~ 0.9 ~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~ 0.8 (excluding updated i18n; with KOffice 1.4) ~~~~~~~~~~~~~~~~
+
+2005-05-10
+Project Migration
+- MS Access filename filter added for convenience (it's autoselected when needed)
+- SQLite3 type is selected as default for destination engine type (for convenience)
+
+2005-05-09
+Forms
+- fixed crash because main window didn't notice property buffer change
+ form's property buffer is shared (ObjPropertyBuffer instance)
+- setting and clearing 'formName' property value for subform fixed
+- fixed problems with data in subforms: data provider only sets data
+ for own items, not inside subforms (data-awarness is disabled in subforms for now)
+
+2005-05-06
+Core, Property Editor, Form Designer
+- powerfull "property options" added to kexi properties:
+ can change behaviour of property editor's items
+- properties: for integer types it's now possible to set "min", "max", and "minValueText"
+ options; "minValueText", if available, is properly displayed:
+ e.g. see:"indent" property of Kexi's label widget
+Form Designer
+- shadow doesn't blink; properly refreshed on 'indent' property change
+KexiDB
+- inserting rows with autoinc values > than 0x7fffffff supported
+
+2005-05-05
+Form Designer
+- basic data type validating added to lineedit widget
+- spring drawing improved
+
+TODO: fix KIntValidator::validate()?
+
+2005-05-04
+Form Designer
+- row deleting and inserting implemented
+- when started new row editing, 'next' btn is enabled (code shared with table view)
+- "+ button" code shared with table view
+
+2005-05-02
+KexiDB
+- we're handling application/x-sqlite2 and application/x-sqlite3
+(those mime types are defined within kexi but we'll also add them for another KDE
+release)
+
+2005-04-30
+KexiDB
+- magic data is now defined for application/x-sqliteX, not for
+ application/x-kexiproject-sqliteX (X == 2 or 3)
+- file-based drivers can now define a list of supported mimetypes, not just
+ one mime type (added all known mimetypes thee, including Knoda's mimes, for
+ compatibility)
+
+2005-04-29
+Form Designer
+- fixed many crashes appearing when a widget name was changed
+- setting duplicated or invalid widget name is now impossible
+
+2005-04-28
+Form Designer
+- all known property and enum names are translated; names are simplified
+- more properties turned to advanced set and thus made invisible in Kexi
+- kexiproperty object is created only once for on propbuffer creation
+ for every QWidget's property. This fixes problems like
+ 'toggleButton' property being added twice to propeditor
+- looking for i18n'd property description or enum value description:
+ on failure, we're also asking parent widget factory for this, if one exists
+
+~~~~~~~~~~~~~~~~ 0.8 beta 1 (with KOffice 1.4) ~~~~~~~~~~~~~~~~
+
+2005-04-27
+Form Designer
+- KexiGradientWidget ifdef'd out for 0.1 release
+ (using KEXI_USE_GRADIENT_WIDGET macro) due to problems with performance
+ and child widget's palettes
+- added possibility of forcing particular class to be hidden
+ (eg. because it's not ready to use yet or makes no sense in current context)
+- fixed creating KexiProperties for enum values
+- property buffer: sometimes enums are remembered as casted to ints,
+ sometimes as string representations...
+- a few properties are now hidded due to inmaturity or senseless
+
+2005-04-25
+Main Window
+- fixed possible crash of the property editor on detached window closing
+KexiDB/SQL Parser
+- SQL Parser now accepts unicode characters for identifiers and strings
+Project Migration
+- drop existing database before if user configmed overwriting
+- close connection on failures
+- fixes for mdb driver
+
+
+2005-04-22..23
+Table View
+- Ctrl+Delete (delete row) shortcut also works inside cell's lineedit editor
+ (default shortcut is overriden)
+Table Designer
+- on switching to design mode, row buffers are initialized after old buffers
+ are cleared, not before (this fixes bug with disappearing property buffer)
+Form Designer
+- fix for setting background (gradient widget cleared background color value)
+
+2005-04-21
+Main Window
+- 'edit > select all' global shared action added
+Core
+- Active window can be found even if it's detached (as in KMDI).
+ This fixes shared actions availability for detached windows.
+Form Designer, Report Designer
+- KexiFormManager reused for reporting
+- we can now assign any kexi action to a button, not only shared one..
+
+2005-04-20
+Form Designer
+- we're not plugging to data-related actions for design view mode
+
+2005-04-19
+Project Wizard
+- 'finish' button disabled on 'select project type' page
+Form Designer
+- 'delete' key removes selected widget(s); except for top level form widget
+- 'menu' key works
+- avoid redundancy in action-creation code
+- better context menu
+- actions can interoperate with shared actions
+TableView
+- 'menu' key code taken from global settings
+Main Window
+- on startup propeditor is hidden if no project is opened
+Startup
+- Connection Selector: added information how to create connections
+
+2005-04-18
+Core
+- KexiProperty::setValue(): QCString type is now compatible with QString type
+Form Designer
+- assigning predefined Kexi actions to buttons
+- WidgetLibrary::setAdvancedPropertiesVisible() added, so advanced properties
+ can be hidden in Kexi.
+- WidgetLibrary::isPropertyVisible() improved
+- 'edit pixmap collection' and 'edit connections'
+ disabled for current version of Kexi Forms
+- using QString changed to QCString in maaany places
+
+2005-04-15..16
+Main
+- Navigator: double clicking on item which has no data view mode available,
+ opens it in design mode; Unavailable actions for given object types
+ are hidden in popup menu.
+- main window: navigator's layout management fixes, again...
+Form Designer
+- initial version of 'assigning actions to push buttons'
+- resizing main form widget is undoable and considered in setting dirty flag
+- initial size of form surface fixed
+- various fixes for form surface resizing and "Outer Area" text
+Core
+- Dialogs: new objects are not dirty by default but this is changed for forms
+
+2005-04-13
+Form Designer
+- On loading, top level form widgets with negative X or Y are moved to 0
+ to avoid weird behaviour.
+
+2005-04-12
+KexiDB
+- Scale property added for floating-point field types.
+ Now types like NUMERIC(precision,scale) are possible to declare.
+Core
+- String "Design of object \"%1\" has been modified." made well translatable
+ at plugins level.
+
+2005-04-11
+Form Designer
+- it's now possible to inherit features of a widget class from a different factory
+
+2005-04-07
+Query Designer
+- properies for rows were not cleared on switching back to design view
+ when statement changed
+
+
+2005-04-01..04
+* Many improvements added for Kexi Forms,
+ including editable query results. *
+KexiDB
+- BIC CHANGE:
+-- FieldList: field(const QString&) added
+-- QuerySchema: field(const QString&) added;
+ insertField() and fieldsExpanded() improved;
+- documentation improved
+Forms
+- setting (predefined) queries as form's dataSource works
+- editing data for _editable_ queries works (but not rows adding or removing)
+- setting "tablename.fieldname" for data-aware widget's dataSource works
+- setting "queryname.fieldname" for data-aware widget's dataSource works
+- when a table is set as form's data source, all primary key's fields
+ are always fetched (so editing works well with autonumbers)
+- autoSync set to false for dataSource properties (using KexiDataAwareWidgetInfo)
+- improved support for invalid dataSource property
+- data-aware widgets with invalid dataSource property are now removed from tab-focus cycle
+ (setInvalidState() now implies setting ClickFocus flag)
+- on form opening: first data-aware widget with TabFocus is focused (if available)
+- void KexiFormDataProvider::fillDuplicatedDataItems() works well
+ also if there are data sources like "tablename.fieldname"
+Forms Library
+- autoSync flag can be overriden independently within every widget factory
+ for every single class, using WidgetInfo::setAutoSyncForProperty()
+- when deselecting a widget with autoSync set to false property value is now set properly
+Main
+- KexiBrowser: pressing Enter key to accept item renaming won't execute "open" action
+
+
+2005-03-30
+Forms
+- setting form's background color property fixed
+KexiDB
+- BIC CHANGE: a few dtors made virtual
+- Connection: data inserting/updating/deleting:
+-- more error messages and codes added
+-- we're checking if master table's primary key is available
+- QuerySchema: pkeyFieldsOrder() improved, pkeyFieldsCount() added.
+- FieldList::indexOf() added
+
+2005-03-29
+Main Window
+- dock windows' width is managed for TabPageMode in the same way as for ChildframeMode
+- hiding property editor's dock when not needed
+Table/Form Views
+- database cursor deleted when not needed
+Forms
+- writing data changes implemented
+
+2005-03-25
+Forms
+- "cancel cell editing" and "cancel row editing" implemented
+
+2005-03-10..24
+Forms, TableViews
+- APIs for Forms and TableViews more integrated:
+-- KexiTableEdit now inherits KexiDataItemInterface
+-- KexiDataItemInterface is now used in KexiDataAwareObjectInterface,
+ not KexiTableEdit
+-- KexiFormScrollView and KexiDBForm now uses KexiDataAwareObjectInterface API
+ for dealing with r/w data
+- data-aware widgets with duplicated data source are updated concurently
+ on editing one of them
+- within forms, "column" term is now related not to column within data model,
+ but to order of data-aware widget. This "column" value is translated to
+ column number within data model when needed. This allows to duplicate data
+ widgets, hide them, change their ordering, etc. For example, tab order
+ (and visual order) of data-aware widgets is independant of column ordering
+ within data model.
+KexiDB
+- BIC CHANGE: QuerySchema::parentTable() renamed to masterTable()
+- QuerySchema::masterTable() now tries to return a table
+ if there's only one defined
+- Connection::createTable() - on error, existing table schema object
+ is not destroyed (i.e. it is still available e.g.
+ using within Connection::tableSchema(const QString& ),
+ even if the table was physically dropped.
+ This fixes crash for "alter table dialog" when recreating table failed
+ (the dialog expects that old table schema won't be destroyed).
+- Connection::resultExists() avoid appending optimizing "LIMIT 1" suffix when
+ the query statement is not "SELECT".
+- Connection::createTableStatement(): length parameter is now only used
+ for text and not long text field types.
+Alter Table Dialog
+- changing subType to LongText hides "length" property;
+ changing subType to Text shows "length" property.
+
+2005-03-04
+KexiDataAwareObjectInterface (TableView)
+- fixed setting cursor position (and iterator) when changing selected
+ row after row editing
+
+2005-03-03
+- doc/dev/compile_time_options.txt document added
+KexiDB
+- API CHANGED - version 1.5
+- KexiMessageHandler moved to KexiDB::MessageHandler, KexiDB::Title added
+ as public available, KexiDB::Object now uses MessageHandler.
+KexiProject
+- KexiProject now uses recent KexiDB additions
+Migration (Import Project Wizard)
+- fixed accepting entered target db filename
+- user is asked for confirming overwrites
+Main
+- KEXI_CUSTOM_HARDCODED_CONNDATA temp. compile-time option added,
+ see doc/dev/compile_time_options.txt
+KexiStartupFileDialog
+- Esc key is filtered-out so it's not hidden and
+ dialogs using this widget can bahave properly
+
+2005-02-27
+KexiDB
+- mysql/sqlite drivers: fixed problem with xxxxConnection::value(int)
+ when we're using raw sql statements (so expanded field list is empty)
+- MySQL Driver: a hack to enable local connections without using
+ a local socket file: if user doesn't want to use local socket file,
+ host name is automatically set (internally) to "127.0.0.1"
+ before connecting. This is especially usable when we're using SSH tunneling.
+
+2005-02-26
+Widgets
+- KexiTableRM moved to KexiRecordMarker in libguiutils
+Forms
+- "pen" icon is displayed on editing within a standard form, see:
+ www.kexi-project.org/wiki/wikiview/index.php?Forms%20embedded%20inside%20Kexi#3.5._
+- fixed Tab/BackTab ordering when there're widgets without TabFocus focus policy set
+- TabOrder dialog: "dirty" flag is always set to the curent form when the dialog is accepted
+KexiDB
+- Connection::dropTable(): be sure that we handle the correct TableSchema object
+- Connection::storeObjectSchemaData() if we already have stored a schema data
+ with the same name and type - just update it's properties as it would be existing object
+
+KexiRecordMarker
+2005-02-23..25
+Forms
+- fixed Tab/BackTab ordering within form's fields, including cycles
+ and problems after detaching/attaching windows for Child Frame Mode.
+ QApplication::sendEvent() used instead setFocus() for more realistic
+ (especially for QLineEdits) focusing effects when tabulator is pressed.
+- QLabel and KexiPixmapLabel available again in KFD
+Main
+- useful Statusbar debug info about focused widget added
+ (enable this using KEXI_STATUSBAR_DEBUG compile-time macro)
+
+2005-02-21
+Kexi Startup
+- "useLocalSocketFile" option for .kexis file implemented
+Forms
+- "autoTabStops" property added.
+ It is synced with changed made by TabStopDialog's checkbox.
+
+2005-02-11...2005-02-20
+Kexi Startup
+- Support for ".kexis" shortcut files added. You can now easily create
+ a shortcut file once, and connect to your remote database server with
+ just one click :)
+- "-type" command line option added
+ (specifies a type of a file provided as argument)
+- opened file types are additionally checked by name extension
+ (if mimetype was not detected) -- this is useful for shortcut files
+- KexiDBShortcutFile class added
+- database/connection dialog reimplemented
+- KexiDBDriverComboBox added: a combo box for selecting a database driver
+
+2005-02-17
+Forms
+- Avoid errors and handle invalid dataSource property values within forms
+- show errors as "#NAME?" in line edits and other data-aware widgets
+
+2005-02-04
+KexiDB
+- DateTime values: back to ISO 8601 datetime format:
+ http://www.w3.org/TR/NOTE-datetime
+ (ie. "1994-11-05T13:15:30" not "1994-11-05 13:15:30")
+ --backward compatible
+
+2005-02-02
+Tables/Forms
+- navigating within rows optimized by using iterators
+Forms
+- navigating readded (still read-only)
+
+
+2005-01-29..31
+Forms
+- KexiGradientWidget introduced:
+ new special effects within form surface;
+ Shadow Labels introduced
+ (by Christian Nitschkowski; segfault_ii at web.de)
+- "show ui xml code" debug function added to forms
+- 1st attempt to port Forms to updated data-awarness API
+
+2005-01-27..29
+Widgets
+- fixed several blind slot-signal connections
+Forms
+- fixed slots/signals signatures with namespaces
+- better resizing new forms contents
+
+2005-01-26
+Widgets
+- fixed focusing-out record navigator's line edit
+
+2005-01-20
+Core
+- On failed plugins loading, proper error message is associated with KexiPart::Info
+ and available later; instead of only assigning it globally to KexiPart::Manager.
+
+2005-01-17
+SQL Editor
+- focus is correctly to editor widget
+API, Utils
+- KexiDataAwareView, KexiDataAwareObjectInterface introduced
+- API and GUI Consistency highly improved:
+ KexiTableView and subclasses use the same api
+ as KexiFormView and all friends
+
+2005-01-14
+General
+- KEXI_REPORTS_SUPPORT compile-time define added
+Core
+- new Part Item added after saving to KexiProject object's structure
+ and removed from temporary list of unsaved items.
+
+2005-01-10
+Forms
+- KexiDataItemChangesListener added to allow signaling changes
+ from widgets implementing KexiDataItemInterface
+
+2005-01-08
+KexiDB
+- TableSchema: table names should be lower case to ease comparisons
+- we're using "lower(o_name)='givenobjname'" comparison in kexi__objects table
+ to avoid problems even if somebody hacked this table with upper case letters;
+ thanks to Martin Ellis for mentioning the problem
+Kexi Data-aware Widgets
+- KexiRecordNavigatorHandler interface introduced -handles requests
+ generated by KexiRecordNavigator
+Forms
+- Database-aware forms finally introduced
+Core
+- KexiDataItemInterface: An interface for declaring
+ widgets to be data-aware
+
+2005-01-06
+MainWindow
+- maximized child window (dialog) state is preserved
+ (a fix after recent KMDI behaviour changed)
+
+2005-01-04
+PropertyEditor
+- prevent scrolling to previously selected item on mouse click
+
+2004-12-22
+Reports
+- data view mode: record navigator is now "page navigator"
+ (it has also [+] button hidden)
+
+2004-12-21
+Core
+- KexiProperty - added 'fixed' flag for 'list' types
+ (the flag is used in Property Editor in most cases)
+- KexiPart::ObjectTypes introduced
+- KexiProperty:
+ property is unchanged if we're moving from null value to a null pixmap, etc.
+Form Designer
+- some fixes for efficiency
+- WidgetFactory can now define custom i18n'd:
+ -property descriptions
+ -property value descriptions
+
+2004-12-20
+SQLite Drivers (2 and 3)
+*DATABSE FORMAT CHANGED to 1.4*
+- date-time data type is now stored as "YYYY-MM-DD HH:MM:SS", not as
+ "YYYY-MM-DDTHH:MM:SS". This is compatible with SQLite format
+ http://www.sqlite.org/cvstrac/wiki?p=DateAndTimeFunctions
+ The change is backward compatible.
+ (thanks for reporting: Richard Groult)
+KexiDB
+- Connection: queryStringList() convenience function added
+- information about "possible problems" is displayed in details sections
+ of error message on failed loading db driver
+Migration
+- wizard fixed a bit
+- kexi__* tables are not copied on high-level
+- MySQL Migration driver, contributed by Martin Ellis <m.a.ellis@ncl.ac.uk>
+KexiDB Drivers
+- MySQL driver better splitted to internal and public part,
+ so migration driver can easier use it
+
+
+2004-12-18
+Forms
+- WidgetLibrary: check for duplicated factories added
+- factory groups introduced
+- factories loading splitted for lookup/loading stages
+- added 'override' flag for alternate widget class names
+- minor optimizations
+Form Plugin
+- fixed showing form's property right after form's window loading
+- record navigator added in data view
+
+2004-12-17
+KPropertyEditor
+- fixed editor positioning
+- bool editor height fixed
+- test: more property types
+
+2004-12-14
+Form Plugin
+- handling form's boundaries improved
+- after switching to/from form in data view mode, its contents position is preserved
+- fixed form's size setting in data view mode
+- two form's resize modes introduced, see
+ http://www.kexi-project.org/wiki/wikiview/index.php?Forms#Form_Resize_Modes_for_form_windows_inside_Kexi
+- in data view mode, scrollview is properly resized to avoid showing
+ outer area and without scrollbars flickering
+General
+- version info moved to kexi_version.h; kexi_global.h is auto-included
+
+2004-12-13
+Widgets
+- libkexiguiutils added (KexiDB-independent widgets)
+- KexiRecordNavigator widget added (the old one was unused)
+Core
+- Part::openInstance(): dlg->registerDialog() moved after successful dialog opening
+ to avoid focusing non-exisitng dialog and/or flickering
+ in a case of obj. loading errors
+
+2004-12-02
+KexiDB
+- version field added to kexidb driver's service files
+
+2004-11-29
+Plugins
+- Migration plugin added (this removed main<->migration circular dependency)
+Kexi Internal Parts
+- API improved
+
+~~~~ 2004 LT ~~~~
+
+2004-11-24
+Main
+- unix: fixed problem with SIGPIPE on running another Kexi process
+ (stdin/err/out is closed now)
+
+2004-11-22
+I18N
+- pl translation updated
+Main
+- disabling forms support fixed
+KexiDB
+- fixed setting relationships within query
+Query Designer
+- criteria handling fixed
+- information syncing fixed a bit more
+
+2004-11-20
+KexiDB
+- TableSchemaChangeListenerInterface added to signal changes
+ in tableschema set
+Query Designer
+- registers usage of table schema
+- fixes in GUI editor
+- more checks for schema changes
+Main
+- on table schema altering or removing, user if now asked
+ for closing windows using this table
+win32 File Dialog
+- make 'My Documents' folder as default
+Query Designer, SQL Parser
+- fixed invalid parsing string constants
+
+2004-11-19
+Query Designer
+- clearing "PK" flag is not needed in few cases
+- it's now possible to enter "alias_name: simple_expression"
+ in the column #1
+Table View
+- fixed repainting visible rows below a deleted row
+ (required if no confirmation box is displayed)
+- record navigator's rec # indicator field:
+-- fixed value validation on number entering
+-- fixed focus out
+- added support for editable comboboxes
+Main
+- always ask for removing objects
+Property Editor
+- do not steal Key_Home and Key_End key presses from editor item
+- Key_Tab and Key_BackTab work like down/up keys
+
+2004-11-17
+Startup dialogs
+- win32: fixed problem with changing directory for a new db file
+Kexi Utils
+- string2Identifier() : non-latin1 characters converted to latin1
+ eg. 'oacute' to 'o'
+
+2004-11-16
+Relations Widget
+- fixed updating tables combo box on table adding
+- on new table creation, combo box is updated with a new item
+Main Window
+- fixed setting non-latin1 characters (e.g. db name) for the title
+
+2004-11-15
+Main Window
+- close_project action's shortcut removed
+ (to avoid conflict with KMDI's 'close current child window')
+- CTRL+F4 key closes child window on win32
+Table View
+- KexiComboBoxPopup: selection is changed on mouse release event,
+ not on press, to avoid weird effect when selection is dragged
+- dropdown button placement fixed after clicking for KexiComboBoxTableEdit
+- fixed race condition in GUI on fast row inserting
+ (due to QApplication::precessEvents())
+- fixed vheader offset on row inserting
+Query Designer
+- user is warned about empty 'column' item while 'table' item is not empty
+- simple criterias are loaded to GUI
+KexiDB
+- expression classes optimized for space and speed (NArgExpr is not used)
+
+2004-11-13
+Property Editor
+- fixed moving selection (esp. using Home key)
+Query Designer
+- never ending story: fixed actions availability os switching back to design more
+- setting column aliases available using GUI
+- "caption" property hidden for KEXI_NO_UNFINISHED
+- wait cursor enabled for query executing
+Kexi Browser
+- CTRL+Enter shortcut handling fixed
+Main Window
+- property editor is shown before opening dialog (not after)
+ to allow initialize proper size inside the dialog.
+ E.g. this fixes column widths in query designer's view.
+Table View
+- sorting settings are cleared on setData()
+
+2004-11-12
+Kexi
+- 2 icons fixed
+KexiDB
+- Support for Generic SQL Keywords Escaping
+ contributed by Martin Ellis <m.a.ellis@ncl.ac.uk>
+
+2004-11-11, 12
+Property Editor
+- 'undo changes' button improved (icon, tooltip)
+- Esc key now properly undoes changes using KexiProperty::resetValue()
+- setWidget() usage fixed for spin box editors, and similar: this fixes problems with
+ key press event filtering for these editors
+- Home and End keys now moves to first/last row, also for nested properties
+- moving selection using keyboard now ensures an item is visible
+Properties
+- KexiProperty::resetValue() does nothing if property is unchanged
+
+2004-11-10
+Property Editor
+- on focus
+--previously focused editor is activated;
+--first visible item is activated if no item was active
+- fixed buffer switching (focus is not set)
+- focusing for combo box (list) editor fixed
+- focus is set again if was set before
+Table View Prop. Buffer
+- fixed memory leak and possible crash
+Table Designer
+- switching from design view to data view: it's now posible to discard changes
+- typo in prop name fixed
+Main
+- Key_F6/F7/F8 added for data/design/text modes
+
+2004-11-09
+Main
+- fix conflict of CTRL+Tab with tabbed/ideal mode on win32
+Query Designer
+- 'check query' action is now avaialble under Key_F9 (was CTRL+Key_Enter)
+ to avoid conflict with editor
+
+2004-11-08
+Main
+- a fix for saving main windows settings (now mdimode is saved to proper
+ section)
+- window_next, window_previous actions added
+KexiDB
+- DriverBehaviour::AUTO_INCREMENT_TYPE added
+
+2004-11-05
+Main
+- fixes for actions
+
+2004-11-04
+Migration
+- fixed compilation (problems due to curcular libs dependencies)
+KexiDB
+- alterTableName()
+--works better with already started transactions
+--finally works even for SQLite!
+- lowlevel Connection: drv_containsTable(), drv_getTablesList() added;
+ impelemnted for all 3 drivers
+- Connection::dropTable(): No error is raised if the table does not exist physically
+ -its schema is removed even in this case.
+Core
+- Kexi::setWaitCursor(), Kexi::removeWaitCursor() do nothing for non-GUI-aware apps.
+- KexiGUIMessageHandler now calls Kexi::removeWaitCursor()
+- If an object could not be opened in design/data view,
+ user is asked to let it to be opened in text mode (if available)
+- KexiDialogBase: temp data handling improved
+Main
+- "Open in Text View" added to the Browser
+Query Designer
+- SQL Editor is now able to open also invalid SQL statements,
+ so user can now open (and probably fix) statements.
+
+2004-11-03
+KexiSQLite 2, 3
+- crashes fixed for architectures where sizeof(char*) != 4
+Core
+- Property, PropertyBuffer: some methods made explicity inline
+- warnings added when trying to set a value to a null property
+ or when property not found. Setting value to null property disabled)
+TableView
+- in KexiTableView::acceptEditor():
+ there might be called cancelEditor() in updateRowEditBuffer() handler,
+ if this is true, d->pEditor is NULL. So we're checking
+ d->pEditor and calling startEditCurrentCell() only if it's present
+- Empty row is appended on row deletion when 'spreadsheet mode' is on.
+- fixed setting row numbers in record navigator for empty data sets
+- fixed editing large FP numbers (removed scientific mode)
+SQL Parser
+- added Date/DateTime/Time types support to ConstExpr
+ (parser does not operate on these constants yet)
+Query Designer
+- added support for Date/DateTime/Time criterias
+Table Designer
+- some properties are hidden for KEXI_NO_UNFINISHED
+Main
+- BUG: 89381 proposed fix, please test
+- Browser: added override for Key_Delete (items removing)
+KexiDB
+- DriverBehaviour: QString AUTO_INCREMENT_PK_FIELD_OPTION and
+ bool AUTO_INCREMENT_REQUIRES_PK added. This fixes problem with
+ pgsql sequences.
+- Connection:
+--fixed generationg 'create table' statements.
+--added drv_dropTable(), drv_alterTableName().
+
+2004-11-02
+Main
+- fixes for kexi__parts
+Query Designer
+- fixed adding fields by entering column names by hand
+- entering (simple) criteria now is working
+KexiDB
+- DriverBehaviour: SELECT_1_SUBQUERY_SUPPORTED added,
+ used in Connection::resultExists()
+Forms
+- on failed saving new form's data, all saving is rolled back
+- fixed saving form data for mysql (and probably pgsql)
+
+2004-10-29
+Query Designer
+- fixed focusing
+Table View
+- fixes for combo box
+
+===================== Kexi 0.1 Beta 5 "Halloween" =====================
+
+2004-10-27
+Kexi
+- a little speedup: QValueList::Iterator changed to QValueList::ConstIterator
+TableView
+- never-ending-story: fixed repaint of "insert row" for empty tables;
+ fixed setting contents size based on tableSize() on table view creation
+ (delayed slotUpdate() is used)
+- setBottomMarginInternal() added e.g. for popups
+- Appearance class introduced: defines table view's detailed appearance settings
+- "row highlighting" option added, comboboxpopup uses it
+- fixed doubled empty row inserting (edit_insert_empty_row action)
+- ensureCellVisible() fixed when record navigator is hidden
+- many improvements in combo box editor and popup
+Query Designer
+- "*" are better handled on schema building
+
+2004-10-25..26
+- defines moved from Makefile.global
+- minor compile fixes and some warnings removed
+I18N
+- pl translation updated
+Forms
+- fixed crash in connection editor
+TableView
+- datatimeedit hack disabled for !win32
+- "edit_clear_table" action added
+- KexiTableViewDataBase made protected in KexiTableViewData for safety
+- on row inserting, enable "next" button
+- record navigator
+-- now takes full width, painting fixed
+-- update width when number of digits in rows counter changes
+Main
+- preferences and configureToolbars actions ifdefed using KEXI_SHOW_UNIMPLEMENTED
+Plugins
+- Default implementation of Part::remove() added again.
+ This is enough eg. for Form plugin.
+- table and query plugin: Connection::eemoveObject() is called even
+ if object's could not be loaded, what allows to remove invalid objects.
+
+2004-10-22
+TableView
+- KexiDateTimeTableEdit, KexiTimeTableEdit implemented
+Table Designer
+- BLOB types ifdefed using KEXI_SHOW_UNIMPLEMENTED
+KexiDB
+- DriverManager does not list derver-based projects
+ when KEXI_SERVER_SUPPORT is undefined
+Startup
+- added error message when explicity provided db driver name not found
+
+2004-10-21
+Core
+- shared action's "enabler" introduced
+Startup
+- KexiStartupFileDialog: filename handling fixed for win32; fixed focusing
+- avoid asking for conversion when '--dbdriver sqlite2'
+ or '--createdb' CL option is present
+TableView
+- highlighting for int and float types fixed
+- fixed initializing for date picker
+- added 'datetime' and 'time' types support
+- adding empty rows when possible also for db-aware table views
+ (ie. RowEditoBuffer can be empty on insert)
+- it's now allowed to click "+" button in record navigator to insert rows
+ (current editing is accepted)
+- on failed row insert/update, user is informed
+ about "data_cancel_row_changes" action availability
+- on invalid value entered in a cell (after a msg box is closed),
+ the cell's editor is focused again
+Compile-time switches
+- KEXI_STARTUP_SHOW_TEMPLATES, KEXI_STARTUP_SHOW_RECENT,
+ KEXI_SERVER_SUPPORT, KEXI_FORMS_SUPPORT added (currently all enabled by default)
+Main
+- copy/paste/cut on navigator items enabled only for KEXI_SHOW_UNIMPLEMENTED
+- toolbars redesigned: now "design" toolbar is for kexipart's design view
+ and "data" is for data view. This avoids toolbars duplication.
+- propeditor dock window is hidden when a window in data view mode is active
+ (or no window is present). This feature is partially disabled
+ (using PROPEDITOR_VISIBILITY_CHANGES), though,
+ --REENABLE when blinking and dock width changes will be removed in KMDI
+- "data_cancel_row_changes" action added
+KexiDB
+- FORMAT CHANGED: SQLite Driver: 'date', 'datetime' and 'time'
+ types support fixed: Qt::ISODate format is used
+- FieldList: simple methods made inline; fixed bug in hasField()
+- findRef() instead of find() used in few places for speedup
+- a hack needed because QVariant(QTime) has broken isNull():
+ Internally: null time value is now null QDateTime(), not-null time value
+ is now QDateTime(QDate(0,1,2),time); This allows to store 00:00:00 times.
+ See also stringToHackedQTime(const QString& s).
+Table Designer
+- "tablepart_toggle_pkey" action temporary removed from toolbar
+ (will be re-added after shared toggle actions fix);
+- "tablepart_toggle_pkey" action added to popup menu and to Edit menu
+
+
+2004-10-20
+Main
+- "file_open" and "file_new" actions now start a new process
+ for creating/opening database, if needed.
+- KEXI_SHOW_UNIMPLEMENTED macro added
+KexiDB Parser
+- fixed handling large integer constants
+Table Designer
+- primary-key-fields: BigInteger type is enforced; subtypes are hidden
+Kexi Properties
+- KexiPropertyEditor::setBuffer() can be now safely called even from
+ KexiPropertyBuffer::propertyChanged() signal handlers (refreshing is delayed).
+Core
+- KexiViewBase::propertyBufferReloaded(): bool preservePrevSelection added
+
+2004-10-19
+Main
+- "delete_row" action shortcut is now CTRL+Delete, to avoid conflict with edit_cut (Shift Delete)
+- fixes for KexiBrowser
+TableView
+- KexiComboBoxTableEdit improved
+- KexiTableViewColumn: try harder to get a column name
+Startup
+- --password option removed
+- --create-opendb CL option added
+
+2004-10-18
+KexiDB Parser, KexiDB
+- Field::Type type() introduced for expression classes
+- type(): integer subtypes (Integer, BigInteger, ..)
+ are detected for int constants; the same for strings
+- Field::type(): If there's expression assigned,
+ type of the expression is returned instead.
+- Field::Type::Null added
+Query View
+- Expressions are now displayed, with header names
+Core
+- KexiDialogBase::activate() -another focus-related fix
+
+2004-10-16
+Core
+- KexiSharedActionConnector introduced
+SQL Editor
+- double clicking on a history item selects it for editing
+
+2004-10-15
+- small speedup for Kexi global objects on startup
+- fixes for KexiStartupFileDialog
+- tests/newapi is compield by default. Other tests disabled.
+Core, Plugins
+- SPEEDUP OF STARTUP:
+ Part::initActions() splitted to Part::initPartActions() (default one)
+ and Part::initInstanceActions() (lazy one)
+Kexi
+- name argument is now passed to KGenericFactory to avoid runtime warnings
+KexiDB Parser, KexiDB
+- Expression:
+-- former type renamed to token
+-- 'type' member added; typechecks added to validate()
+- more SQL statements are supported
+
+2004-10-14
+Kexi
+- more debug areas added
+- KexiStartupFileDialog wrapped for win32 again
+TODO: move some things to kdeui/win32
+KexiDB Parser, KexiDB
+- WHERE expression is supported
+- operators like "IS SIMILAR TO" and "IS NOT NULL" better supported
+- brackets better supported
+- both <> and != supported
+
+2004-10-11
+Parser:
+- ER004 supported
+
+2004-10-09
+Kexi Startup
+- ksqlite2to3 script added for unices. It's used instead of two QProcesses.
+- ksqlite2: -verbose-dump added for ksqlite2to3
+
+2004-10-06
+3rd Party
+- kexisql migrated to SQLite 3.0.7 (new kexisql3/ directory added);
+ former libkexisql is now libkexisql2 (updated to 2.8.15, by the way)
+KexiDB Drivers
+- "SQLite3" driver introduced. "SQLite" driver is now removed;
+ use "SQLite2" driver for comaptibility.
+KexiDB
+- static QString Driver::defaultFileBasedDriverMimeType()
+ and static QString Driver::defaultFileBasedDriverName() added,
+ so let's don't compare driver names against "sqlite" string when looking for default.
+Startup
+- sqlite version is now autodetected on startup
+- sqlite2 to sqlite3 format autoconversion added (with progress dialog)
+
+2004-10-04
+- command line options added: --user (-u), --port,
+ --host (-h), --password, --local-socket
+- password can be now entered using dialog, if it's a server-based conn.
+
+2004-10-01..02
+Kexi Startup
+- server-based (mysql, postgresql) projects are now available from command line
+ (usable since gui is not yet ready):
+ --createdb, --dropdb, --dbdriver command line options added
+- db creation and dropping from command line works without starting entire gui
+ see http://www.kexi-project.org/wiki/wikiview/index.php?CommandLineOptions
+Core
+- KexiMessageHandler introduced: receives messages e.g. from KexiProject
+- KexiGUIMessageHandler added
+KexiDB
+- improved error handling
+MySQL Driver
+- MySqlConnectionInternal added (like for SQLite)
+General
+- KMessageBox::warning**() used instead of question**() for dangerous messages.
+- 'tristate' helper class introduced. This reduces a need for adding
+ bool& cancelled parameters.
+- tristate class used where possible instead of 'bool& cancel' params
+
+2004-09-29
+2004-09-30
+SQL Parser highly improved
+- reduce/reduce conflicts solved: operators in expressions have now 9 valid precedence levels
+- expressions are now parsed quite well: e.g. identifier checking is recursive
+- better debugging
+- ParseInfo introduced: Data used on parsing
+- invalid use of reserver keywords are now detected:
+ '"KEYWORD" is reserved keyword: identifier was expected' message is now returned
+ instead of dumb "syntax error"
+KexiDB
+- Field: expressions are better supported
+- Connection: expressions are visible in selectStatement()
+
+2004-09-28
+KexiDB
+- Field: documentation added
+Kexi
+- ConstIterators used where possible
+
+2004-09-27
+SQL Parser
+- ER102, ER103, OK104 SQL Issues resolved (see statements.txt)
+- several bugfixes
+
+2004-09-17
+KexiDB
+- Connection::selectStatement(): alias is used insetead of table name,
+ if available
+- QuerySchema: 'index' term replaced by 'position' ; insertField(), addField()
+ overloads added allowing tables binding by position; addTable() now allows
+ to specify table alias; tablePositionForAlias(), tablePositions() added;
+ many docs and examples added
+SQL Parser
+- many improvements and checks:
+ -- ERR: there's no "persons" table in this query (alias "p" covers it)
+ select persons.id from persons p;
+ -- alias "p" for table "persons" is used
+ select p.id from persons p;
+ -- multiple aliases for the same table
+ select persons.id from persons, persons p, persons p2
+
+
+2004-09-15..16
+SQL Parser
+- select statements are now parser in more functional way, instead
+ of using global pointers. This enables nested statements in the future.
+- field alias support fixed
+- table alias support added
+KexiDB
+- (API CHANGED) QuerySchema:
+--"Field" in isFieldVisible(), setFieldVisible() renamed to "Column"
+--alias() renamed to columnAlias(), and so on
+--Table aliases are now supported: tableAlias() added, and so on
+--QueryFieldInfo class renamed to QueryColumnInfo
+
+2004-09-14
+TableView
+- hack added for KDownArrowPushButton for drawing with thinkeramik style.
+- convenient ctor added for KexiTableViewData
+KexiDB
+- Driver::escapeIdentifier() : "" are only added if there is a space inside the name.
+ This keeps SQL statements more readable, especially in SQL View.
+- DriverBehaviour::QUOTATION_MARKS_FOR_IDENTIFIER added
+- Driver::drv_escapeIdentifier() added:
+ This method is used by escapeIdentifier().
+ Implement escaping for any character like " or ' as your
+ database engine requires. Do not append or prepend any quotation
+ marks characters - it is automatically done by escapeIdentifier() using
+ DriverBehaviour::QUOTATION_MARKS_FOR_IDENTIFIER.
+Parser
+- tokens like '123abc' are now reported as invalid by lexer to parser
+
+
+2004-08-25
+Core
+- KexiSharedActionHost::invalidateSharedActions(QObject *o)
+ if o is not KexiDialogBase or its child,
+ only invalidate actions if these come from mainwindow's KActionCollection
+ (thus part-actions are untouched when the focus is e.g. in the Property Editor.
+- KexiViewBase::setDirty() -do not signal 'dirty' flag's changes if no change
+- ObjectStatus now also stores related KexiDB::Obeject's, for more verbose errors
+KexiDB
+- Field::isAutoIncrementEnabled() added + some checks for 'autoincrement' flag
+- fixed saving SQL statements for possible later error messages.
+ Object class now also better handles previous server error messages and numbers.
+- KexiDB::getHTMLErrorMesage() improved, it's more verbose in certain cases
+- Driver::escapeIdentifier() added; used in Connection in every place
+ where SQL statements are generated. Thanks to Martin Ellis.
+CC_MAIL: m.a.ellis@ncl.ac.uk
+TableView
+- "yes" button for "do you want to delete row?" dlg has "Delete row" text
+ (as KDE HIG requires)
+- cell-cursor's is position not reset after initDataContents() on show()
+ if it was set before KexiTableView widget showing.
+Table Designer
+- many validations added related to primary key and autonumber properties
+- On table creation (or altering), if there is no primary key, user is asked
+ if it should be added (automatically).
+ Useful setting "dont show this again" added for this as well.
+
+
+2004-08-24
+Table View
+- redraw fixed -very special case: If you're during editing a new row, and click
+ on last (empty) row, and your newly entered row has no data in its cells,
+ (what ends up with cancelRowEdit()), cursor is now moved one line up.
+- unknown, non-printable key events are not ignored now, but passed to superclass;
+ this e.g. fixes blocking ESC key in dialogs
+- fixed setting cursor on row removing
+Kexi Properties, Property Editor
+- icon added as optional property member
+Form Designer
+- Connection editor implementation cleaned a bit (bug #44)
+Table Designer
+- "autonumber" property is now visible
+- icons added to propertybuffer
+Form Module
+- kexidbwidgets.so ported to win32
+
+2004-08-23
+Table Designer
+- fixed code because of new column has been prepended
+KexiDB
+- KexiDB::Field::Type defaultTypeForGroup(KexiDB::Field::TypeGroup typeGroup)
+ added and used in Table Designer.
+- KexiDB::idForObjectName() convenience function added
+Table View
+- fixed methods for 'visible' flag in KexiTableViewColumn
+
+2004-08-21
+Table View
+- Key_Left, Key_Right now by default behaves like Key_BackTab, Key_Tab
+- KexiTableViewData::deleteRows(): refresh is requested only
+ if at least one row has been removed. This also magically fixed
+ a bug with missing vheaders in the Query Designer :)
+Main Window
+- opening object: if object is already opened but in other view,
+ its activated and switchToViewMode() is simply called.
+ This fixes switching GUIClient for "design" action called from the Navigator.
+- Kexi::ViewMode is repaced with int type in most places
+ to ease future extensibility.
+
+2004-08-20
+KexiDB, Table View
+- Kexi now supports and shows autonumber (sequences) column, see
+ http://www.iidea.pl/~js/kexi/shots/beta5/unicode_chars_and_autonumber.png
+Table View
+- autoincremented field can be omitted (left as null or empty)
+ if we're inserting a new row
+- fixed displaying "autonumber" cell on row inserting
+- row editing is accepted on sorting
+- on failed sorting, sort indicator is reverted
+- Key_Tab, Key_BackTab work again;
+ skipping to next/previous row added
+- when the user doubleclicks on the edge (handle)
+ of horizontal header's section, column's width
+ is adjusted to widest cell size
+Table Designer
+- before switching to design view, row editing is accepted
+Main
+- PropertyEditor/FontSize setting is now stored
+
+2004-08-19
+KexiDB
+- DriverPrivate added to avoid binary incompatibilities
+- driver (QCString keys, QVariant values) properties added
+ (some prdefined properties added as well)
+Table View
+- optimizations in KexiTableViewData::saveRow()
+New API Tests
+- "dr_prop" test added
+SQLITE Driver
+- Kexi file driver now supports UTF8 storage for text fields
+Shared Actions
+- data_sort_az, data_sort_za, data_filter actions are now shared & volatile,
+ KexiTableView and KexiDataTable now use these
+
+2004-08-17..18
+Core
+- added KexiViewBase::setAvailable() overload. This one and sharedAction()
+ now call parent's method, when needed.
+- Shared actions: some fixes for plugging/unplugging
+KexiBrowser
+- "rename" action is now shared as "edit_edititem" action
+KexiTableView
+- "start edit" action (for F2/Enter key) is now shared
+ as "edit_edititem" action
+- KexiKIconTableEdit introduced: Cell editor for displaying kde icon
+ (using icon name provided as string).
+- TableViewHeader (inherits QHeader) introduced: supports tooltips
+ when needed (for column description of if column is not wide enough)
+KexiTableView ComboBox
+- QStyle primitives are better used for drawing combo button
+- button is depressed after popup hidding
+Table Designer
+- "tablepart_toggle_pkey" shared action added for design view
+- "Not empty" field's property is now called "Allow Zero Size"
+ (and has opposite meaning)
+- 1st column with "key" icon (for pkey) added (using KexiKIconTableEdit)
+KexiDB
+- QString Field::subType() added
+- QuerySchema::autoIncrementFields() fixed (caused crash sometimes)
+
+2004-08-06
+Table Designer
+- some rules added: e.g. setting 'indexed' property to false clears 'primaryKey';
+ setting 'primaryKey' to true sets 'indexed', 'unique', etc. to true
+Core
+- KexiProperty, KexiPropertyBuffer: some operators added, operator [QCString] now
+ returns KexiProperty, convenient operator = (QVariant) added; KexiProperty::null added
+TODO: KexiProperty: add support for positive integers, "Auto" values e.g. for integer ==0
+ like defined in QSpinBox. Reuse this in PropIntSpinBox.
+
+2004-08-05
+PropertyEditor
+- Bool Editor: toggle button has now without ugly focus (space key still works)
+- KexiPropertySubEditor:setWidget() focus proxy is set only if the widget has
+ non-zero focus policy.
+- [+][-] branch box is better positioned; treeStepSize is now narrower
+- font size is decreased to a reasonable minimum
+Core
+- generateIconSetWithStar() not used, static icons added insted (icon cache was confused)
+MainWindow
+- right dock width is now stored in config file
+
+2004-08-04
+MySQL Driver
+- introduced again! thanks to Martin Ellis
+CC_MAIL: m.a.ellis@ncl.ac.uk
+KexiDB
+- alterTable() added (now only drops and recreates table)
+- createTable() 'zombie' rows from 'kexi__fields' table is removed for safety
+- Connection::storeObjectSchemaData() identifier is not obtained if
+ it's already provided
+- DriverManagerInternal::aboutDelete() added: called from Driver dtor
+ (because sometimes KLibrary is destroyed before DriverManagerInternal)
+- DriverBehaviour::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE added
+ True if the value (fetched from field (or function)
+ defined by ROW_ID_FIELD_NAME member) is EXACTLY the value if autoincremented field,
+ not just an implicit (internal) row number. Default value is false.
+ --used in Connection::lastInsertedAutoIncValue()
+Table Designer
+- saving changes to tables works now (data is just dropped, though);
+ user is warned about this, when needed
+- DataTable view is updated after saving table's design
+- temporary data added; KexiAlterTable_DataView implemented
+ (this fixes lack of updating datatable view after schema changes)
+
+2004-07-28..2004-08-03
+KexiDB
+- ProjectDB FORMAT CHANGED (remains backward compatible):
+- kexi__query* tables removed (to complicated, not needed since we
+ are primarily using kexi__objectdata and xml)
+- Driver::IgnoreTransactions flag added: transactions are silently bypassed,
+ (temporary, useful for current mysql driver implementation)
+- Query Schema: QueryFieldInfo introduced:
+ "select a as one, a as two from table" is now possible
+ (two different aliases for the same field);
+ this also maintains field visibility.
+- Most needed expression classes added (BaseExpr-derived)
+- most debugs removed
+- more debugString() methods added for few classes
+- FieldList: removeField(), insertField() (at position) implemented
+ addField() now uses insertField()
+KexiDB/parser
+- 'bison -dv' used instead of yacc
+- single-char tokens are now used as chars: symbols are dropped for better
+ code readability
+- more safe and readable code: $$ is now used instead of changing global variables
+- realType added
+- loadDataBlock(), storeDataBlock(), removeDataBlock() moved to Connection class
+- Connection::querySchema() methods now use parser
+- Connection::createTable() now also allows table replacing
+- Connection::storeObjectSchemaData() now reuses schema's id if > 0
+NEWAPI Test
+- now KCmdLineOptions are used, see README
+- "parser" test added
+KexiTableView
+- KexiTableViewPropertyBuffer: a fix for maintaining `dirty' flag
+- updates for QueryFieldInfo
+- compiler warnings removed
+Core, Widget, Plugins
+- updates for QueryFieldInfo
+- compiler warnings removed
+
+2004-07-24
+Form Designer
+- added icons for ajust/align icons
+
+2004-07-23
+Core
+- KexiViewBase::removeDataBlock() and KexiDialogBase::removeDataBlock() added.
+- more error messages added
+Main
+- Startup dialog re-enabled
+KexiDB
+- Utils: most smaller functions are now inline
+- optimizations:
+ * Connection::querySingleRecord(), querySingleString():
+ " LIMIT 1" is added to the statement
+ * Connection::resultExists(): "SELECT 1 FROM (<statement>) LIMIT 1"
+ statement is executed
+ * Connection::resultCount() added: "SELECT COUNT() FROM (<statement>)"
+ statement is executed
+- Cursor::open(): sets error message when needed
+Query Designer
+- KexiQueryView: shows error on failed execution
+
+2004-07-22
+Main
+- Kexi Browser: "create new" action added to browser's mini-toolbar;
+ icons for particular "create" actions are generated in a funny way
+- "project_new" and "project_open" actions removed from the main toolbar
+
+2004-07-20..21
+Kexi Browser
+- "rename object" action added (inline editor uses IdentifierValidator)
+- popup menu for group nodes now have a title
+Core
+- KexiProject, KexiPart supports object renaming
+- KexiProject::removeObject() and KexiProject::renameObject()
+ now operate within transaction.
+- KexiProject: more error mesages added.
+KexiDB
+- Connection::drv_executeSQL() is now protected. Connection::executeSQL() public
+ method added which remembers last executed sql statement for better error handling.
+Main
+- KexiBrowser: After item's successful rename, it's resorted,
+ and if it's dialog is opened, it's caption is updated as well.
+
+2004-07-19
+Kexi
+- We've now --final-mode and --design-mode CL-options for completness.
+KexiCore, KexiMain
+- KexiStartupHandler introduced: handles startup actions, that
+ are new performed independently from Main Window's stuff.
+ We're now prepared to having 'startup mode'
+ database's property, which can be overriden by --final-* CL-options.
+Form Designer
+- A small fix to reduce widget flickering on resizing:
+ when we're dragging left, top-left or top
+ handle, both size and position of widget changes. We're now hiding
+ the widget and showing it again after transformation.
+
+2004-07-16
+Form Designer
+- some strings fixed
+
+===================== Kexi 0.1 Beta 4 "FireDuck" =====================
+
+2004-07-15
+Forms
+- form's initial sizeHint is now hardcoded to (400,300), will be fixed later
+Query Designer
+- saving existing queries work in SQL View
+Core
+- after successful storeData() or storeNewData(): 'dirty' flag is cleared
+ for _every_ dialog's view
+
+2004-07-14
+Kexi
+- polish translation updated (also for KFormDesigner)
+Core
+- "current mode" member is now reverted if afterSwitchFrom() failed
+- removed one (I hope unnecessary?) shared-action invalidation
+- storeNewData() and storeData() new have 'bool &cancel' parameter
+Query Designer
+- storing and mode switching fixed
+- KexiQueryDesignerSQLView::storeNewData():
+ we won't store query layout: it is recreated 'by hand' in GUI Query Editor
+Table Designer
+- temporary behaviour: altering data is cancelled
+- fixed switching between Data and Design view
+
+2004-07-12..13
+KexiDB
+- Connection: selectStatement(): "SELECT FROM ..." queries (ie. without columns)
+ are allowed (needed for desiger); omitting "FROM" allowed
+KexiDB/Parser
+- "SELECT FROM " rule added
+Core
+- KexiPart::Part::openInstance(): now virtual KexiDialogBase::loadSchemaData() is called
+ what allows to reimplement stored schema data loading (reimpelemnted for queries)
+- KexiDialogBase::dirty() now is true if any view is dirty
+KexiTableView
+- If ensureCellVisible() is called before table view is visible, it's (col,row) arguments are
+ stored and ensureCellVisible() is called just after show event. This fixes invalid scrolling.
+Query editor, Table Editor
+- storage/ loading/ view switching/ dirty flag updating - improved
+
+2004-07-10
+TableView
+- "edit_delete" action is disabled for read-only tables
+
+2004-07-09
+KexiDB
+- It's possible to define relationships for QuerySchema
+ (with Relationship objects)
+- Connection: optimization: QString::reserve() used
+ before generationg SQL statements
+- Connection::selectStatement() now generates joining rules
+ (i.e. relationships using WHERE)
+Query Designer
+- joins defined by GUI are now visible in SQL View
+
+2004-07-08
+Query Designer
+- switching between design modes improved
+- on generating sql from GUI: "*" added if no fields are picked
+KexiMainWindow
+- neverending-story: actions availability updating fixed
+TableView
+- KexiTableViewPropertyBuffer: all buffers are cleared when view's
+ data is cleared (using clear())
+Relation Widget
+- view clearing supported
+Core
+- KexiViewBase: 'dirty' flag is now better signaled to a dialog
+KexiDB/SQL Parser
+- lexer: USER_DEFINED_NAME now accepts also [0-9] (after 1st char)
+- optional ';' is allowed at the end of top level statement
+- dummy table is removed from query after parsing
+
+2004-07-07
+Core
+- KexiDialogBase: Never Ending Story: Shared actions are invalidated on activate()
+- ObjectStatus general purpose class introduced. KexiDialogBase now uses this
+ to indicate errors.
+- KexiPart::Part: if newly opened dialog (KexiDialogBase) cannot display 1st view, it's closed
+ (via close()) and error message is set up
+- KexiPart, KexiProject:
+ const Kexi::ObjectStatus& lastOperationStatus() added for not-kexidb errors
+KexiMainWindow
+- formpart is now available by default; menu for switching it off is hidden
+- more errors taken from KexiProject are friendly displayed :)
+Query GUI Editor
+- saved layout (etc.) is loaded in afterSwitchFrom() for mode==Kexi::NoViewMode,
+ instead of doing it in the ctor
+SQL View
+- switching to other views improved: msgbox is displayed for invalid sql texts
+- Intelligence Built In: we're doing our best to avoid regenerate the same
+query from almost unchanged sql text
+
+2004-07-02..05
+XMLGUI
+- Tools menu added
+- undo/redo global actions added
+- tolbar setup consistent with ui_standards.rc and incoming katepart
+Main Window
+- hack: `window' menu moved right before settings menu
+Widgets
+- KexiSectionHeader: adding buttons connected to actions allowed on the right hand
+- Relation View: saved table widgets' geometries are better setup on loading
+ (fix for differences between font sizes on differens computers)
+- SQL Editor: better integration with katepart's actions using new shared action's API
+Core
+- KexiDialogBase now has activate(), deactivate() handlers,
+ KexiViewBase::updateActions(bool activated) is called for a current view
+- Workaround found for integrating katepart's actions with Kexi's shared actions:
+ - katepart KXMLGUIClient's KActions are now pluggable to KexiActionProxy
+ - KXMLGUIClient's KAction::enabled(bool) signals are now automatically mapped
+ to KexiActionProxy::setAvailable(bool)
+<TODO>: introduce KexiSharedXMLGUIClient class:
+ ctor: KexiSharedXMLGUIClient(KexiSharedActionHost *host, KXMLGUIClient *client).
+ A single instance is created for particular xml file (eg. katepartui.rc) and inserted
+ int KexiMainWindow's internal dictionary. This GUI client will be attached when needed
+ (ie. when given KexiViewBase object that is activated asks for this) and detached
+ when not needed.
+ showOnlyActions(QCStringList) - declares custom action list that will only be available
+ In any time, no KActions will be created if these already are available globally
+ within KexiMainWindow.
+</TODO>
+SQL View
+- history behaviour reverted to previous
+- added "clear history" and "select this query text" buttons in KexiSectionHeader
+- added popup menu with above actions
+
+2004-07-01
+SQL View
+- kate part now better integrates its actions with kexi menus/toolbars
+TODO fix it: actions, shortcuts
+- 'Tools' menu added, 'Window' menu is now repositioned before 'settings' menu
+
+2004-06-30
+SQL View
+- fully implemented history/status mode switching
+- both modes work with "check SQL" action
+Core
+- KexiViewBase::updateActions() virt. method added for convenience
+
+2004-06-28
+KexiCore
+- KexiViewBase now knows own view mode
+SQL Parser, SQL View
+- fixed error handling (syntax error is only set when no detailed error is known)
+- on error: fixed jumping to proper character (even where are more lines and tabulators)
+- status label's height is properly increased, when needed
+- jumping to proper character impelmented also for QT_ONLY_SQL_EDITOR
+- fixed crash for parsing empty SQL string
+Main Window
+- removed redundant MDI Mode option from View menu
+Core
+- some docs added
+- KexiSharedActionHost::createSharedAction() : subclassName arg added
+
+2004-06-25
+KexiCore
+- fixed switching for other mode
+ (formpart behaviour was really weird because of that)
+
+2004-06-24
+KexiCore
+- Some doxygen docs added.
+- Fixed activeView() when we're just switched to other view in KexiDialogBase
+Widgets
+- SQL Editor: now properly emits signal about text changes.
+- SQL Editor: now has QT_ONLY_SQL_EDITOR compile-time option (both modes works on win32)
+Utils
+- Kexi::setWaitCursor(), Kexi::removeWaitCursor() -for delayed cursors added (eg. user on view opening)
+KexiMain
+- --edittext [<object_type>:]<object_name> C.L. option added:
+ like --open, but the object will\nbe opened in Text Mode
+KexiDB
+- Parser::query() public function added; select() is now internal
+SQL Editor
+- CTRL+Return shortcut; added information what to do to validate query text
+
+2004-06-23
+KexiCore
+- Fixed focusing previously selected dialog after Kexi main window's activation
+- KatePart is working on win32 as well
+
+2004-06-21
+KexiActionProxy
+- activateSharedAction(), isAvailable() now have alsoCheckInChildren arg.
+KexiDialogBase
+- actions availability is invalidated on view switching
+KexiPart::Part
+- proper integration with SharedActionHost
+- actions creating simplified with Part::createSharedAction()
+- finally: initPartActions() and initInstanceActions()
+ replaced with single call of initActions().
+ createSharedPartAction() and createSharedAction() added
+Parts
+- all parts code updated for API changes
+
+2004-06-19
+KexiTableView
+- added checks if column is >=0 (this avoid crashes)
+FormDesigner
+- TabStopDialog:
+ * was badly implemented: exec() in ctor. exec() moved from ctor to
+ TabStopDialog::exec(Form*) method
+ * fixed crash: QVBoxLayout was added twice! 1st via ctor, 2nd view QLayout::addLayout()
+ --removed 1st
+Query Designer
+- kexiquerypartinstui.rc added and some actions
+Core
+- sql parser is now allocated in KexiProject
+KexiDB
+- docs greatly improved, thx Martin!
+- UNSIGNED INT -> INT UNSIGNED
+
+2004-06-18
+Query Designer
+- SQL Editor: added KexiSectionHeader
+- KexiQueryDesignerSQLView -> KexiQueryDesignerSQL
+- KexiQueryDesignerSQLEditor: now inherits KexiViewBase
+- Double clicking on table's field in Relation View allows to auto-add this field
+- KexiQueryDocument no longer used, similar KexiQueryPart::TempData is used
+- switching to SQL View working again
+- added warning when switching to dataview with empty design
+KexiTableView
+- Editor's focus position is updated (moved down)
+ when new row is inserted above current row
+KexiDialogBase
+- returning without failure when KexiViewBase::beforeSwitchTo()
+ returned cancel==true and success==false
+
+2004-06-17
+KexiTableView
+- Combobox's button is now in "toggle" mode; popping up is now better hidden/shown
+KexiViewBase
+- storeNewData(), storeDataBlock() simplified; parts code updated for this change
+Query Designer
+- relations widget: geometries for tables and connection lines are now stored
+- 'dirty' flag better updated
+
+
+2004-06-15
+2004-06-16
+TableView, Alter Table
+- Table Cell Editor's Focus is cleared on slotRefreshRequested()
+- fixed columns recreating on data resetting for a table
+TableView
+- fixed inserting row (empty or dropped)
+- dropping indicator line is now painted with XOR mode
+- more intuitive dragging and dropping rows: dropping on bottom 1/3 row's area means
+ dropping below this row
+- dropping after the last row is now possible
+- KexiTableViewData and KexiTableViewPropertyBuffer now supports multiple
+ rows deletion with cost O(n). KexiTableView is updated properly for this action.
+Alter Table
+- afted hiding table, rows (and prop. buffers) that are connected with it are deleted
+
+2004-06-14
+Core/Parts
+- For all parts: Instance GUI Clients are now splitted for per-view
+ GUI clients (switched on view switching) and common GUI client
+ (not switched within given dialog).
+
+2004-06-10
+2004-06-12
+KFormDesigner
+- rectangle on widget inserting, resize handles and grid's dots:
+ are now painted in white XORed color on top of all other widgets
+- fixed widget raising to top on clicking: also container members
+ are raised (eg. for tab widget)
+- more guarded ptrs added; fixed crash on ObjectTree destruction
+- selection rectangle is drawn unclipped now, on top of all widgets
+
+2004-06-09
+KexiTableViewPropertyBuffer:
+- slots' connections fixed
+KFormDesigner, Kexi Forms
+- "pointer" action: better icon added (from Karbon); action moved to "widgets" toolbar/menu
+- all "widgets" actions and "pointer" action are mutually exclusive
+
+
+2004-05-15
+KexiDB:
+- fixed bug: Connection::resultExists() now properly closes temp. cursor
+
+===================== Kexi 0.1 Beta 3 "United Europe" =====================
+
+2004-05-08
+Form Designer: Extra widgets
+- fixes for kde3.1 compatibility
+- implicit deleting is cleaner than explicit
+
+2004-05-06
+Main Window
+- Messages are displayed on failed switching to othe view.
+Lucijan's stuff
+- Compile fixes, will be backported
+
+2004-05-03
+Kexi Main Window
+- Dialog dictionary is properly cleared on project close (fixed possible crash)
+- Not stored dialogs are properly removed from a dictionary
+Kexi Startup
+- fixed possible crash due to deleting not owned connection data, deep copy is now performed
+
+2004-05-01
+KexiDB
+- QuerySchema: 'visible' field's property is now assigned to possition, not to field pointer,
+ because the same field can be added more than one time to a query
+- Connection::selectStatement(): only visible fields are shown
+Query Designer
+- executing (switching to data view mode) is working again!
+
+2004-04-30
+TableView:
+- dropdown button position properly updated when scrollview's x-offset is non-0
+Kexi Forms
+- toolbar moved from 'part-wide' area to 'part-instance' area
+Reationships/Query Editor
+- master/detail sides of the realtionship are swapped when correction is needed
+KexiViewBase
+- added possibility of disable data storing on view switching; for some cases
+
+[INFOSYSTEM 2004]
+
+~~~~~~~~~~~~~~~~ beta3 pre2 ~~~~~~~~~~~~~~~~
+
+2004-04-16
+Kexi Core
+- introduced KexiDialogTempData
+TODO: make KexiQueryDocument inherit KexiDialogTempData
+- added 'Settings->Other->Enable Forms' menu switch (off by default)
+Global
+- version: beta3 pre2
+- updated readme_en
+- Kexi backported to KDE3.1 and Qt3.1.1
+KexiDB
+- setting length is only available for Text fields
+Kexi Alter Table
+- 'length' property is set to 0 on changing type from Text to any other, and set to default (200)
+ otherwise
+
+2004-04-15
+Kexi Main
+- KexiNameWidget, KexiNameDialog: caption is now not required; geometry fixed
+- Ugly bug fixed: active window sometimes changed to
+ other on menu clicking or leaving dialog's focus (because KexiTableView had NoFocus policy)
+
+2004-04-14..15
+Table Views
+- insertEmptyRow() improved
+- KexiTableViewPropertyBuffer introduced
+- cell editors that have no editor widgets: current internal value is now displayed
+- KexiDataTableView: fixed columns double initializing
+Cell Editors
+- Boolean cells are now treated as other types: validation and edit buffering works
+- Boolean cell editor moved to separate KexiBoolTableEdit class
+- Enter key also toggles boolean editor's value
+- clickedOnContents() added -allows to toggle value without using
+ editor widget (used in boolean cells)
+- hasFocusableWidget() added -allows cell editors that have no editor widgets (e.g. boolean type)
+Query Designer
+- many functional improvements
+- property editor is now used, like in Alter Table Dialog (with KexiTableViewPropertyBuffer)
+KexiDB
+- Connection::useDatabase(): fix for non-sqlite dbases:
+ we're not checking for database inconsistencies when using temporary database.
+- SQLite cursor: added decoding for boolean values
+TODO: move this decoding somewhere to generic functions
+- Connection::useDatabase() : kexiCompatible param. added to allow using native dbases
+
+2004-04-13
+Forms
+- linkage fixes for win32
+- some null-checks added
+- widget deletion is now delayed (because of problems with qt/win32 events)
+Table Views
+- KexiTableView::maximizeColumnsWidth() added
+Relations Editor
+- Combo box is updated on table hidding
+- popup menu titles added
+Query Designer
+- columns' data is better updated
+
+2004-04-06..08
+KexiDB
+ - Reference class renamed to Relationship, documentation improved
+ - QuerySchema: added 'field visibility' flag
+Table Views
+ - added 'column visibility' flag, inherited from QuerySchema's 'field visibility'
+ - many sanity checks added usable when data is destroyed
+ - added possibility of disabling vertical scrollbar's tooltip
+ - table can be refreshed if data model changed outside of this table
+Combobox Table Columns
+ - key-based related data in columns added as an option (still of Field::Enum type)
+ - flickering removed when popping up for the first time
+Query Designer
+ - table of fields ('field' and 'table' columns) is updated a bit on table adding;
+ not finished though
+
+2004-04-05
+Kexi Alter Table
+ - fixed possible crash when the view is initialized with no table schema specified
+ (ie. for designing a new table)
+ - a pointer to table schema is updated after creating a new table
+ - compilter warnings removed
+KexiDB
+ - names and docs in Reference class improved
+ - Connection: major versions of database and kexidb is are compared on useDatabase():
+ errors are presented if needed
+
+2004-04-02
+KexiDialogBase
+ - beforeSwitchTo(int mode, bool &cancelled) is called also on newly created view
+
+2004-03-29..2004-04-01
+Kexi Query Designer Editor
+ - KexiSectionHeader class introduced
+ - section's size hind fixed
+Relations widget
+ - asterisks added to table boxes
+ - asterisks are not drag&drop enabled
+ - hiding tables implemented (connections are removable as well)
+Table View
+ - Temporary FIX: BLOB editors are disabled
+ (in KexiBlobEditorFactoryItem::createEditor()) because of unstability.
+TODO: reenable this
+ - setData(): when the same data object is passed, tableview is just refreshed
+ - setDropsAtRowEnabled() added; row-droping-indicator implemented
+ - KexiTableView::editor(): only if ignoreMissingEditor is false (the default),
+ and editor cannot be instantiated, current row editing (if present) is cancelled.
+ Thus: Now it's possible to edit data for tables where there are cells
+ that have no valid editors (yet?), such as BLOB now.
+ - paintCell(): if there is no editor defined, just a focus box is displayed,
+ filled with additional grayed diagonal pattern
+Table View Editors
+ - clicking on date table popup selects current date
+ - QScrollView is passed on editors creation instead of QWidget
+ - QScrollView::moveChild() is used for moving external widgets, instead of QWidget::move()
+ - fixed uninitialized leftMargin value for FP-Number editor (on win32)
+Alter Table Dialog
+ - Temporary: 'defaultValue' property hidden- we'll show this after we
+ get properly working editor for QVariant
+Kexi
+ - Welcome screen now uses html 'welcome_<locale>' fiels.
+ These are Temporary! Do Not Translate!
+Kexi Core
+ - KexiPartManager is now KexiDB::Object-derived; error texts are set now
+ - Action Proxies: child focus is better handled
+ - KexiViewBase: focus change tracking and actions updating improved
+
+2004-03-27
+Form Editor
+ - buggy deleting fixed: ResizeHandleSet::Dict defined, we're using that instead
+ of not-fully-defined QDict<ResizeHandleSet>
+Kexi
+ - added 'Welcome' screen with hot informations for our users
+
+~~~~~~~~~~~~~~~~ beta3 pre1 ~~~~~~~~~~~~~~~~
+
+2004-03-26
+ - pl translate updated
+ - some I18N_NOOP turned to i18n
+ - fixed crash on Relationships window closing due to lack of Part Item
+KexiTableView
+ - "dontAskBeforeDeleteRow" config key is better
+ - after removing a row, all rows below are updated
+ - repaint of bottom few pixels (empty) area fixed when scrolling and resizing
+Kexi
+ - "-new <object_type>" CLI option added: "Start new object design of type <object_type>"
+ (also convenient for everyday tests)
+Main Window
+ - MDI mode is now stored and restored
+ - fixed problems with property dock window when using IDEAl mode
+KMDI
+ - Fixed memory leak & crash:
+ Tab/IDEAl mode: mdiview was never destroyed but just moved out of the tab widget
+ - KMdiChildFrm: Mouse button press filtering is checked against every client's child,
+ not just client
+TODO: backport this to qextmdi???
+
+
+2004-03-25
+KexiTableView
+ - adjustColumnWidthToContents(-1) now mean that all columns' width is adjusted
+ - after calling adjustColumnWidthToContents(), editor width is updated, if needed
+ - setSpreadsheetMode() added
+KexiQueryDesignerGuiEditor
+ - spreadsheet-like mode set
+ - more columns added: totals & sum
+
+2004-03-24
+Main Window
+ - Wrong behaviour fixed:
+ after switching to other MDI mode, pointer to current dialog needs to be updated
+ (property editor was empty)
+ - previous 'view mode' toggle action is checked back properly after cancelled switch
+Table View
+ - fixed editing initializing with 'removeOld' mode
+Alter Table Dialog
+ - Temporary workaround:
+ if data is dirty and table schema was saved before --user is asked
+ for discarding changes.
+ - switching to Data Mode is cancelled if current design is empty (never saved and !dirty)
+KexiDialogBase
+ - switchToViewMode() has now option for cancelling switching
+ (if user is able to cancel in any case)
+ - switchToViewMode() calls saveObject() on main window before switching, if needed
+ - KexiViewBase::afterSwitchFrom() and beforeSwitchTo() have now bool &cancelled
+ parameter that allows to gently cancel switching
+ (probably after showing some info messages)
+
+2004-03-23
+2004-03-24
+Core, Main Window
+ - Volatile Actions introduced
+ - Menu updating and focusing fixed
+Kexi TableView, Kexi Table Data
+ - Empty row inserting implemented
+Kexi Alter Table
+ - Empty row inserting action added
+
+2004-03-22
+Main Window
+ - when toggling to other view mode (using radio action) failed, action for
+ previous mode is toggled back.
+
+2004-03-19
+2004-03-20
+KEXIDB:
+ <KEXIDB FORMAT CHANGED TO 1.2>
+ - 'kexi__objectdata' table introduced: contains object's data, such as form's xml def
+ SCHEMA: table kexi__objectdata
+ o_id integer REFERENCES kexi__objects(o_id),
+ o_data BLOB,
+ o_sub_id varchar(200)
+ </KEXIDB FORMAT CHANGED TO 1.2>
+
+ - Field::typeForString(typeString) Field::typeGroupForString(typeGroupString) added
+ - Connection::querySingleString() and Connection::resultExists() added
+ - KexiDB::sqlWhere() utility function added
+Alter Table Dialog
+ - Saving field types FIXED
+CORE:
+ - KexiDialogBase::storeDataBlock() added
+Table View
+ - fixed row's painting for empty table that have only row "for inserting"
+ - vertical record marker: fixed painting during inserting 1st row
+ - function for inserting an empty row above the cursor added (not finished yet)
+CORE:
+ - Shared Actions: unplugSharedAction(action_name, widget) added
+Main window:
+ - "Insert empty row" shared action added
+ - Shared actions are updated on switching between dialog view modes
+
+2004-03-17
+ -KexiProperty: fix for fix :) changes from null string to "" string (or vice-versa) are ignored
+ -Kexi Property editor receives signal KexiPropertyBuffer::propertyReset()
+ (called from KexiProperty::resetValue()) so property is property visually reseted
+ even on calling resetValue() either by clicking "reset" button or by hand.
+
+2004-03-13
+ -KexiProperty: changes from null string to "" string (or vice-versa) are ignored
+
+2004-03-12
+Table View
+ -Combo box editor: cursorAtEnd() and cursorAtStart() are just used
+ from KexiInputTableEdit
+ -Some debug disabled
+ -setColumnWidth() added
+Alter Table Dialog
+ -now KexiDataTable is inherited instead of using KexiTableView internally.
+ Thus we have actions like 'row deleting' available.
+ -on table schema saving: checking for duplicated field names is now case insensitive
+ -convenient width set for 'type' column
+Kexi Data Table widget
+ -Added ctor for not-db-aware version; docs updated
+Kexi Core
+ -string2identifier(): fixed bug for strings started from not alphanumeric char
+ -New validator added: KexiDBObjectNameValidator for checking
+ for 'kexi__' name violations
+ -KexiMultiValidator added
+Main Window
+ -KexiNameWidget now uses multivalidator, so it's easy to add additional validations
+ -above is used with KexiDBObjectNameValidator to check for violating 'kexidb__' names
+KexiDB
+ -Driver::isKexiDBSystemObjectName() static function added; isSystemObjectName()
+ uses this now by default
+
+2004-03-11
+Main Window
+ -fix: object's name and caption entered by user is used to create this object
+ -brand new objects have changed part item id --this is updated in main window's
+ structs as well
+ -in removeObject(): if object's dialog is opened -it's quietly closed before removing
+ -added check/hack to avoid inf. recursion between removeObject() and closeDialog()
+ -crash fixed: on dialog closing, it's properly removed from dictionary.
+Kexi Dialogs
+ -'neverSaved' flag is cleared on first successful saving
+ -active view is 0 on its parent dialog destruction
+ -Dialog's docID is not just id, and inherits part item's (KexiPart::Item) identifier
+Alter Table Dialog
+ -'name' and 'description' properties are updated on change
+ -on table schema saving, user is warned about: 1) duplicated field names
+ 2) not added fields at all; 3) fields without names
+Table View:
+ -'removeOld' flag is passed from TV to initializer, so given editor's implementation
+ can decide what to do
+
+2004-03-10
+Table View
+ -Combo box editor: after new row selecting (not hightlighting) editing is accepted
+ -setFilteringEnabled() added
+KexiDB::Field
+ -forgotten: Byte is numeric type, of course
+Alter Table Dialog
+ -anyway, subtype property is always created (may be needed later)
+ -subtype is properly opdated on type (group) changing
+Properties
+ -fixed possible crash
+ -KexiProperty: now it's possible to change a list of values (in case
+ of property of type 'list')
+KexiViewBase
+ -propertyBufferReloaded() added -called whenever current
+ property buffer is changed that few properties are now visible
+ and/or few other are invisible
+
+2004-03-09
+Main Window : some top level polishing
+ -most tooltips/what's this added for actions
+ -'save' action now works int the current dialog context
+ -message about more not impelmented features added
+ -proper quitting and project closing
+ -KMainWindow::queryExit() and queryClose() impelmented instad of closeEvent()
+ -"Create" menu is disabled when no project is loaded
+ -On last dialog closing, guiclient is removed (so dead toolbar are hidden now)
+ -Main Window: dock windows are disabled on project closing, for sanity
+ -some fixes for mime data (QString -> QCString)
+ -part manager's part lookup can be called only once now
+ (refreshing will be impl. one day)
+ -dock windows (navigator and propeditor) are properly handled on closing/reopening projects
+ -opening project after closing fixed
+ -caption is updated on dialog data saving; after saving brand new data,
+ item is added to the navigator
+Property Editor
+ -fixed crash on changing invisible properties
+Alter Table Dialog
+ -creating new tables works partially
+
+2004-03-08
+Table View
+ -'acceptsRowEditAfterCellAccepting' flag added
+Main Window
+ -closeDialog(), saveObject(), closeProject() methods have now 'cancelled'
+ parameter, so they can tell you if user just cancelled operation or there was
+ rather a real error.
+ -Duplicate names are checked on new object saving; user is prompted for unique name.
+ -On dialog closing: 'Dirty' flag is cleared when user discards dialog saving
+KexiDB::Field
+ -Connection::findObjectSchemaData() added for looking for objects by type and name
+ -INDEXED flag added to KexiDB::Field (now you can declare field to be indexed
+ even without any constraint)
+ -More implied rules added for methods like setPrimaryKey()
+ -More docs added
+Alter Table Dialog
+ -most missing field properties are now visible in property editor
+ -Creating new table: table schema is (almost) created using current buffers' state
+
+2004-03-06
+2004-03-05
+KexiDB
+ -'helpText' for SchemaData and Field members renamed to 'description'
+ -KEXIDB METADATA FORMAT CHANGED (to 1.1):
+ helpText renamed to description in kexi__objects table
+ -Connection::storeObjectSchemaData() added -can be reused mostly for object storage
+Core
+ -string2Identifier(): now created identifier is forced to lowercase
+Kexi Parts
+ -part item's and part info's 'mime' is now of QCString type
+ -structures like items dicts are now case insensitive QAsciiDict
+Kexi Dialogs
+ -storeData() and storeNewData() methods added
+ -currently used KexiDB::SchemaData object is pointed by KexiDialogBase
+ (useful for data saving)
+Kexi Views
+ -storeData() and storeNewData() methods added; called by parent dialog
+Kexi Project
+ -now keeps a list of never-stored part items (on 1st saving, a given
+ item is moved to a list of regular part items)
+ -createObject() -generic method for initiating object creation on the backend;
+ updates item's data when necessary, etc.
+Kexi Properties
+ -QCString is used for property name instead of QString
+ -structure like properties dict (in the buffer) is now case insensitive QAsciiDict
+KexiMainWindow
+ -saveObject() added, KexiNameDialog is used for prompting for object name and caption
+ -user is asked for object saving on dialog closing (and thus on app closing as well)
+Kexi Alter Table
+ -storeNewObject() and storeObject() implementation started
+Main
+ -KexiNameWidget and KexiNameDialog utility classes
+ (for entering both names and caption names) implemented
+
+2004-03-04
+Main Window
+ -Both on window close and application close: User is prompted for saving
+ unsaved data for each "dirty" dialog. Pressing "Cancel"
+ -cancels application closing.
+ -only stored objects are added to the Navigator
+ -few methods move to protected
+ -graphical notification about 'dirty'==unsaved data ("*" sign at the right hand)
+ added to the names on captions, taskbar and navigator
+ -API for saving kexi dialog's data added, plugged to the main window
+Kexi Dialogs
+ -currently not only dialog is activated when focus come to any of its children,
+ but also when any children is clicked.
+ -KexiViews have now 'dirty' flag and parent KexiDialog reuses that
+
+2004-03-03
+2004-03-02
+Kexi Dialog / View
+ -fixed internal view's focusing: on click on a view, dialog is activated
+ -focus cell is now grayed for disabled table views, not for unfocused
+KexiDB
+ -"kexi__parts" table is now filled with default (required) parts info
+ automatically in Connection::createDatabase(), not somewhere outside
+TODO: add version info?
+Main Window
+ -Part type name removed MDI Taskbar buttons' captions, to save some space
+ -User is now asked for saving object data on dialog closing (if it's dirty)
+ -Not stored objects are removed from memory and gui completely,
+ if user won't let to save them
+ -Not stored objects (part instances) have now identifiers < than -1
+Kexi Views/Dialogs
+ -"dirty" and "neverSaved" flags added
+Parts / Kexi Project
+ -so-called "unstoredItems" introduced, so they can initially exist in memory only
+ and user may want to give up, not saving them
+Table View
+ -Fixed focus-stealing from editor problem
+
+2004-02-27
+Kexi Property Editor, Kexi Property Framework
+ -propertyChanged() signal is received from assigned property buffer,
+ and values are visually updated (including children)
+ -changeProperty() code (for checking for changes)
+ moved from buffer to property setValue to avoid mistakes in class usage
+ -property editor class now has a dict of all items, based on its names
+ -each KexiProperty now has a pointer to its buffer, ans uses it to emit
+ signals on value changes
+ -fixed crash on moveEditor() when called from ctor
+ -KexiPropertyEditor is safely updated right before PropertyBuffer destroying
+Kexi Validator
+ -allows to define whether empty values are ok or not
+Alter Table Dialog:
+ -'name' and 'type' columns can be empty now (that just means the row is empty)
+ -buffer and 'type' column is cleared when 'name' column is cleared
+ -row deleting works (buffer is destroyed as well)
+Table View's Data
+ -aboutToDeleteRow() signal is emited before row deleting and rowDeleted() -after
+Table View
+ -contents size is updated after row deleting
+
+
+2004-02-26
+Kexi Property Editor
+ -leaveTheSpaceForRevertButton introduced for subeditor class.
+ This removes confucion when user clicks on a spinbox arrow and it suddenly
+ moves left.
+ -parent item is updated on children updates
+ -editor position is updated when user collapses or expands a property node
+ -resetting composed property value also resets all children
+Kexi Property Framework
+ -testiong for property change: for date and datetime types we're now comparing
+ values using strings, because there can be miliseconds difference in a time;
+ strings comparison fixed for null strings
+ -resetValue() added to KexiProperty for convenience
+ -for composed properties (like Rect):
+ * changing property child updates parent value
+ * setting property unchanged (eg. on resetting) updates parent's 'changed' flag
+ -QSizePolicy-type property helper improved
+ -setValue() renamed to setChildValue() to remove messy overload
+
+2004-02-24
+Table View:
+ -KexiTableViewData is now QObject-derived
+ -signals like aboutToChangeCell() or rowChanged() moved from KexiTableView
+ to KexiTableViewData, so it'll be easier to reuse KexiTableViewData
+ with a forms, and easier to share common data object between views.
+ -It's now allowed to call acceptRowEdit() from inside of cell-accept handlers
+ e.g.: KexiTableViewData::aboutToChangeCell() signal
+ (no infinite recursion is preformed but instead: the row is saved after
+ cell's acception).
+KexiDB:
+ -ResultInfo introduced
+ -getHTMLErrorMesage() utility method introduced
+ -Cursor: now pointer to connection is guarded
+ -Connection, Cursor: error status is cleared before performing update/delete/insert
+ -Connection::recentSQLString() added (useful for debugging and on-screen
+ error messages
+ -Connection: further error message fixes
+Alter Table Dialog:
+ -after 'name' field is entered, 'type' column as automatically filled
+ -"newrow" property is added for each new buffer to indicate for later processing
+ that these buffers are for newly added fields
+Properties are now indexed using case insensitive names.
+
+2004-02-22
+KexiDB:
+-copy constructors added for classes: table schema, field list, index schema, field
+Alter Table Dialog:
+-all changes are made to the deep copy of the table, not to the original
+
+TODO: KEXIDB: copy constructors: also copy references of the index schema
+
+2004-02-21
+Main Window:
+ -buffer in the property editor is properly cleared when current dialog is changed
+ or there is no dialog
+Table View:
+ -clearSelection() and field(int) methods added
+ -on row deleting: cursor is moved up only if we're deleted the last row
+ and row-inserting is not enabled
+ -fixed selecting row after no row was selected
+ -messagees are displayed on failed row updating/inserting
+ -CONSTRAINTS: illegal null/empty fields are checked on updating/inserting
+ (msg boxes are used if required)
+ -the cursor is moved to faulty cell's value on record update/insert error
+Combo box editor:
+ -selection on the popup table view is cleared then no test is entered
+ -up/down/left/right key presing leaves the lineedit if there is no
+ line selected on the popup table
+ -improved editing convenience
+KexiDB:
+ -isEmptyValue() generic function added for checking if a value of QVariant
+ is empty but not null
+Alter Table Dialog:
+ -"data type" column declared as NOT EMPTY
+
+
+2004-02-20
+KexiDB::Field:
+-setNonEmpty() fixed
+-NOT EMPTY constraint is now implied by PRIMARY KEY constraint
+Table View:
+-on a table cell edit accepting: violating of the following:
+--NOT NULL or NOT EMPTY constraints
+--validation rules (using attached KexiValidator)
+ so LATER this may be also used for forms
+--from-editor value getting errors (d->pEditor->value())
+..is now signaled using message box
+Alter Table Dialog:
+-"field name" field acts like a primary key
+
+2004-02-19
+KFormEditor lib:
+-ported to win32
+-some sanity checks added
+
+2004-02-18
+KexiPropertyEditor:
+-font editor item: description text moved to kexiproperty::format(); added weight & italic info
+-new method of column sorting: instead ascending - default order,
+ instead descending - alpha order
+- "revert to original value" button is automatically shown when property value is changed
+ and becames hidden when the value is reverted to original value
+KexiTableView: added new signals:
+-for checking validity of curent cell, before changing
+-for checking validity of curent row, before updating
+-for checking validity of a new row, before inserting
+In a case when checking for validity failed, row edititing/inserting
+(or cell updating) is cancelled --> editor is not removed
+- KexiPropertyBuffer: clear() reimplemented for proper clearing the data
+- KexiValidator class introduced
+KexiTableView:
+-acceptEditor() returns bool, so now it's checked if we can proceed with cursor moving, etc.
+-KexiValidator is used to check whether cell editing should be accepted or not.
+Alter Table Dialog:
+-hidden "name" property added, ident. validator is now used
+-added KexiDB::RowEditBuffer* argument to aboutToUpdate and aboutToInsert signals
+
+2004-02-17
+- KexiDB::typeStringsForGroup() ustility function added
+- KexiDB::Field: array of field type names fixed
+Kexi Property Framework improved:
+- properties can be nested, e.g. for Rect type there are automatically
+ created two properties: x and y
+- pointer to a parent property is kept, if present
+- all data is stored in KexiProperty and KexiPropertyBuffer, not just in GUI objects
+- editoritem text for composed values (like Rectangle) now displayed properly
+- reverting to default works properly for properties of type 'list'
+- By default, sorting is disabled (properties are in order of insert time)
+- KexiPropertyEditor: fixed value changes for prop. children
+
+2004-02-12
+- typeName() added to KexiPropertyBuffer
+- typeString(), typeGroupString() added to KexiDB::Field
+- KexiDB Utils: typeNamesForGroup() and typesForGroup() utility functions added
+- KexiPropertyEditorList crash fixed for StringList type
+
+2004-02-10
+- Property editor is now used globally in Kexi (property buffer's change comes from active KexiView)
+- Alter Table Dialog connected to global property editor
+- Kexi Main Window: removed unused code
+- Action for Focusing Property editor (alt+2) added
+- KexiProperty and KexiPropertyBuffer classes moved to core/
+ from widget/propertyeditor
+- KexiProperty has now oldValue() and changed() methods -- usable
+ since we've global property editor
+
+2004-02-09
+--design command-line option added, especially useful for developers
+main window:
+- dock/layout/geometries settings are stored/restored (a bit better, not complete though)
+- table view: update width of a combobox popup on editor's resizing
+
+2004-02-08
+- libkeximain introduced (by splitting libkexicore)
+Main Window settings:
+- main window size stored and restored before showing
+- "maximized childframes" setting stored/restored
+
+2004-02-04
+Table view:
+ -Generic Cell Editor Framework introduced
+ -selection background painting moved to editor's implementation
+ -for row-selection mode: Home/End keys always move to the 1st/last row
+ -page up/page down actions added as methods
+
+ComboBox Editor improoved:
+
+-we're completely dropping use of regular combobox
+-new layout: a lineedit + dropdown button + popup tableview, when needed
+-methods used (and few added) for tableview to enable adjusting
+ it for use it as dropdown tv:
+--hidden vtoolbar
+--hidden header (on user demand, and automatically, when only one
+ column is visible in the dropdown tv
+--hidden the context menu
+--hidden the record navigator
+--added and used a method for selecting entire row, not just a cell
+--tv's recordset made readonly, disable inserting
+--sorting disabled
+
+
+Property Editor:
+ pointer to current editor is now guarded, fixed crash (on sorting)
+ due to uninitialized member
+
+2004-01-29
+-KexiDB: crash fixed for records buffering, when a value is NULL
+(for some targets strdup crashes on NULLs)
+-KexiDB: i18n'd names for datatype groups added, just like datatype names.
+ This will be used eg. in Alter Table Dialog
+
+2004-01-28
+- Main Window: toggle actions are checked off before disabling
+
+- Table View:
+ -fixed size of Vertical header's item (record marker) used in Table View
+ (now also works ok with Windows style)
+ -don't paint contents of edited cell - painted editor is enough
+ -methods added for showing/hiding vertical and horizontal header
+ -framework for showing cell's focus depending on editor type added to cell
+ editor (eg. dropdown btn for combobox)
+
+- Main Window:
+ Fixed problem with autoincremented identifier in kexi__object
+ table on ObjectCreation for some backends
+ lucijan: what about using kexiDB API, not hardcoding?
+
+2004-01-27
+- Combo box editor
+ -key events fixed
+
+2004-01-26
+- Table View:
+ -KexiCellEditorFactory and KexiCellEditorFactoryItem introduced for generalized cell
+ editor creation and cell painting
+ -now, we don't try to recreate cell editors but store one editor instance for every
+ column and just show/hide/move it when needed
+ -column width is now adjustable with adjustColumnWidthToContents() and stretchable with
+ setColumnStretchEnabled()
+
+
+===================== Kexi 0.1 Beta 2 "Warsaw By Night" =====================
+
+2004-01-20
+- kmdi captions fix backported to kexi_compat
+- kexistartupfiledialog: we're using paths unstead urls now for better compat.
+
+2004-01-19
+RESIZING HELL RESOLVED
+- KexiDialogBase resizing fixed for maximize state: only resize a dialog if it is in normal state
+- KexiMainWindow: for dialogs in normal state: decrease dialog's height if it exceeds area contents
+- KexiViewBase:
+ -preferred size hint introduced;
+ -parentDialog() convenience method added for getting view's dialog parent.
+
+- KexiDB::Connection:
+ -tableNames() return only tables that have valid names
+ -the same for any other objects
+- actions in 'Create' menu work
+- PropertyEditor has cell borders' color like this used in table view
+- closing() signal added to KexiViewBase - it works like in KexiDialogBase.
+ Effect: table view's data is saved on closing if editing is in progress
+
+- table view, kexidb: after row inserting, autoincremented fields have displayed values
+- query removing works
+- KexiMainWindow: shared actions are disabled when no proxy is available
+
+2004-01-17
+- KexiDialogBase: minimum height fixed: added height of its caption
+- KexiTableView: repainting of bottom contents FINALLY FIXED
+- ALTER TABLE Dialog impl. started
+- query part: crashes removed when no sql string defined for query; by default "visible" column is true
+- relation view:
+ -for global db relations -all tables are shown (will be customized later)
+ -open table/design table actions fixed
+- bool Kexi::isIdentifier(QString) added for convenience
+- KexiDB: Tables and queries with invalid names (that are not valid identifiers) are skipped on lookup
+- KexiProject: The same for any objects and KexiProject level
+
+2004-01-14
+CORE API CHANGED:
+- Kexi Dialogs : view modes functionality added -- KexiViewBase, and integrated with KexiMainWindow
+ and KexiDialogBase
+- KexiDockBase removed because if KexiViewBase existence
+- If a dialog is already opened in given mode and user have dbl-clicked on the Navigator,
+ the dialog is not switched to other mode
+
+- InternalParts and dialogs without multiple modes FIXED
+- KexiTableView: when cursor is moved down and navigator covers the cursor's area,
+ area is scrolled up.
+
+2004-01-13
+KexiActionProxy:
+ - isSupported(const char* action_name) added -returns true, if action is supported by the proxy
+ - isAvailable() now lookups also in the childrens actions, if actual action proxy does
+ not support a given action
+- Relation View:
+ some action availability updates
+
+- more icon sizes added: state_sql, table; state_text added for future "text view" mode
+
+- KexiMainWindow: actions are invalidated more accurate on dialogs switching/closing
+- KexiDialogBase: added flags t ocheck if given dialog's implementation accepts given view mode
+ (data/design/sql modes)
+- Kexi::ViewMode introduced - view modes for kexi dialogs (i.e. data/design/sql)
+
+2004-01-12
+- Relation View:
+ - tables focusing fixed
+ - actions 'remove table', 'remove relation' work (not impl)
+ - "relation view" widget extracted and moved from relations/ plugin to widget/relations/
+ (because it's used also in queries)
+ - KexiRelationDialog renamed to KexiRelationWidget to avoid mistakes
+ - actions moved from KexiRelationView up to KexiRelationDialog (simplicity)
+ - added new actions: "open table", "design table"
+- KexiActionProxy:
+ new plugSharedAction() overload added for easy creating alternative action names.
+
+2004-01-10
+- KexiDB::Field::isIntegerType() added. Now It's easier to detect if a field is integer.
+- Fixed loading and saving values of type Float or Double in database tables.
+- Row updating/inserting: Fixed converting decimal symbol from locale back to db backend format
+
+2004-01-09
+- PartManager: Parts are sorted using definition stored in kexirc
+ (code taken from the old API)
+- .ui files can be now translated
+
+2004-01-08
+- KexiInternalPart introduced (as generalized KexiRelationPart):
+ produces internal Kexi dialogs and widgets ON DEMAND
+ (so: KexiRelationPart is removed)
+- Kexi Relations (widget and dialog) is now produced by KexiInternalPart
+- Tables in relation view: sizes fixed
+
+2004-01-07
+- Build fixes for gcc2.95 on kde3.1
+- Relations View: connection and viewtable focusing fixed and some actions added
+
+2004-01-06
+- KexiTableView:
+ -rowEditStarted(int) signal is emitted when row editing is started
+ (for updating or inserting)
+ -rowEditTerminated(int) emmited when row editing is terminated
+ (no matter if accepted or not)
+ -proper availability updates for "edit_delete_row" (disabled when tv is
+ readonly) and "data_save_row" (enabled only on row editing)
+- KexiDialogBase/KexiPart : fixed caption icons setting for dialogs created by
+ parts
+- Shared Actions:
+ - KexiSharedActionHost introduced - it's "action sharing" functionality extracted from KexiMainWindow.
+ - global default KexiSharedActionHost added for convenience
+ - KexiActionProxy now uses KexiSharedActionHost as it's host instead of explicity pointed KexiMainWindow
+ - on destruction, KexiSharedAction object is taken out of its KexiSharedActionHost
+ - int KexiMainWindow::generatePrivateDocID() added, so we can use this when we want to get unique doc id
+e.g. for KexiDialogBase.
+ - generatePrivateDocID() is used in Relations dialog
+
+- KexiMainWindow: fixed bug in virtual function
+- KexiSharedActionHost: fixed behaviour on destruction
+
+-KMDI:
+ -caption's icon is resized on setIcon() when needed
+ -default icon changed to SmallIcon("filenew"),
+ if not found - filenew.xpm is used
+
+2004-01-05
+- QueryAsterisk:
+ -setTable() added - a must for parser to work
+ -debug improoved
+- Connection: dropTable() added
+- Query Part ported to win32
+- KexiBrowser: nice title added for popup under part item
+- KexiTableView:
+ -navigator showing/hiding implemented
+ -cursor setting a bit fixed for empty view
+ -cell's text has now proper color for read-only column
+- KMDI: fixed bad QWidget filter lookup and unsafe identifier hiding in childview's eventfilter
+- kexipropertyeditor ported to win32, kexiproperty and kexipropertybuffer moved from core
+
+2004-01-04
+- KexiActionProxy: "action" term in methods substituted by more readable: "sharedAction"
+- some actions (in browser) marked as KEXI_UNFINISHED
+- KexiMainWindow: KStdActions are now also acceptable as shared actions
+- copy/cut/paste actions are shared now; added to edit menu
+- "relations" action reintroduced
+- closing() convenient signal added to KexiDialogBase
+- on Table closing - any edits that are in progress are accepted (thx to lucijan for report)
+- KexiTableView:
+ -fixed crash while counting minimum size for columnless tableview
+ -preventing from crash when no data is set before constructing is finished
+- KexiProject: openObject() and removeObject() added so we will be able to catch kexidb errors
+
+- KexiPart items are now removable! (both from GUI and db backend)
+- Kexi Browser is items are removed if required
+- KexiDialog: now has itemIcon() possible for reimpl. and Kexi Relation View reimpl this, since
+ it has no KexiPart
+- Kexi Relation View: small crash fixed
+
+2004-01-03
+- KexiDB, core:
+ -ConnectionData and KexiProjectData now inherit QObject
+ (useful for QGuardedPtr, sharing and for notifications about changes)
+ -project_caption, project_desc properties are now created and stored in projects
+ -some utility functions added in kexidb/utils.cpp
+- KexiMainWindow: application's caption now also contain current project's caption (or name)
+ (this also works ok for maximized windows)
+- mysql driver reenabled (may not work correctly)
+
+- ConnectionData
+ -conflict with QObject fixed
+ -"name" member is now: "connName"
+
+2004-01-02
+- table view:
+ -in key event - we give up with executing actions that are shared with main window, because
+ these actions are executed at main window's level. These actions are declared using
+plugSharedAction().
+ -row deleting works
+ -rows number is updated in tv navigator after row deletion
+- actions: MSA-like "Delete Record" substituted by "Delete Row"
+
+TODO: show posible errors (as well as for INSERTs and UPDATEs)!!!
+
+
+2003-12-30
+** Many cleanups and behavioral fixes
+- KexiMainWindow:
+ -focusing fixed again (now menubar doesn't grab move focus from navigator)
+ -shared action set is now simplified, generalized
+ -getlogin() used at least on win32, cause lack of KUser :)
+- KexiDockBase added as a base for docked widgets hat offer shared actions.
+ Now it is a pair with KexiDialogBase class (both inherit KexiActionProxy).
+- "Data" menu entry introduced
+- KexiTableView:
+ -added action for current cell removing
+ -added popup menu
+ -cell focus-marker is not grayed when popup menu is executed
+ -Key_Space pressing fixed
+ -added possibility for plugging shared actions to table view (using plugSharedAction()),
+ e.g. "data_save_row" action, so shortcuts are properly handled
+ -deleting row while new row is edited just cancels editing
+ -vscrollbar tooltip is not visible when corliing is dont using keyboard instead of a mouse
+ -stranger's key events (e.g. pressing up arrow in navigator's line edit)
+ aren't processed by table view (in keyPressEvent())
+
+2003-12-22
+- KexiTableView most keyboard events are now only accesible without key modifiers (eg. CTRL+Key_Up now
+doesn't work)
+
+2003-12-21
+- KexiActionProxy introduced
+- New terms introduced: part's gui client and part instance's gui client.
+ The latter is removed from main window's guifactory when part instance is deactivated
+ e.g. "Filter" action of tablepart is hidden when needed),
+ while the former is premanent in main window (e.g. "New Table" action is always visible).
+
+- KexiMainWindow:
+ -dockwidgets' (like e.g. browser) focusing on main window's activate/deactivate
+ and on menu bar popups - fixed.
+ -removed toolbar item's fileckering when we're closing one kexidialog and another kexidialog
+ of the same type (guiclient) has to be activated (solution: guiclients are removed not
+ on kexidialog closing but before activating another, it new activated dialog has different client.
+
+- Global Action availability updates: 1) KexiMainWindow updates its actions on dialog switching,
+ or even on focusing dockwidget, like e.g. browser; 2) from dialog point of view - when required,
+ action availiability (true/false) is signalled from currently focused dialog to main window
+- Browser: popup menu re-added, (there is also example how to plug
+ both custom and global actions to the popup)
+- Opening part instances in design mode prepared (openInstance())
+
+2003-12-20
+- KexiDialogBase remembers its creator (a KexiPart::Part object)
+- KMdiChildFrm's icon is scaled when needed (e.g. when detached/attached)
+
+- KexiPart's classes: some methods are now protected
+- Toolbar buttons are now flicker-free, because gui clients are now attached
+ to parts instead of part instances
+
+
+2003-12-19
+- KexiDB::Field:
+ -many properties of type int are now of type uint
+ -WIDTH property added
+ -NOTEMPTY property added
+ -canBeEmpty(), typeGroup(), isDateTimeType() added
+
+- KexiDBTableViewColumn merged with KexiTableViewColumn, now column properties are always reused
+ from KexiDB::Field's properties
+
+- KexiTableView: full handling of NULL or EMPTY values on editor accepting, e.g.:
+ * cells are displayed as empty EVEN for numeric types, if null values are allowed
+ * cell value is forced to NULL if empty values are not allowed (e.g.: for numeric types)
+
+- Updating with NULL values fixed, and it's done only when needed.
+- KexiTableView editors: pressing "left arrow" key at begginning of cell's text moves
+ to cell on the left hand; the same for end of text+right arrow key
+
+- Finally: new row inserting works (no data integrity checking or warnings added though).
+
+- KexiTableView: cells: fixed displaying of text (and text selection) longer than cell's width
+- KexiDB:
+ -escapeString() and valueToSQL() moved from Connection to Driver
+ -escaping ' and " chars fixed
+
+2003-12-18
+2003-12-17
+2003-12-16
+2003-12-15
+- tableview: changes in keys behaviour:
+ -Ctrl+home moves to 1st row, Ctrl+end - to last row,
+ Home -to 1st col., End -to last col.
+ Ctrl+Shift+home moves to 1st row and col, Ctrl+Shift+end - to last row and col,
+ -Key_BackTab is also used to navigate cells
+ -row editing improoved: buffer containing changes for current
+ edited row introduced (KexiDB::RowEditBuffer)
+- tableview cell editors / items:
+ -KexiTableItem simply inherits from KexiDB::RowData
+ -Tab and Shift+tab, Up, Down, Home, End, etc. keys work also when cell editor is visible
+- FINALLY: row editing fully working: data is updated @ the backend using KexiDB
+
+- keximainwindow:
+ -Tab key pressed on navigator activates current child window
+ -Mainwindow's caption now contains (for Childframe Mode) child window's caption
+ as the prefix if the child is attached and maximized. This works well with attaching/detaching
+ and deactivating child windows.
+
+- KMdiChildArea: 2x crash fix for childframe mode
+ -when KMdiChildArea::setTopChild(0) is called and m_pZ is empty, pMaximizedChild is 0
+ -in KMdiChildArea::manageChild(), first - we need to take old references to childwindow
+ that is added to m_pZ (crash reproduction: detach one childwindow, attach it, detach again)
+
+-KexiDB:
+ -IndexSchema's pkey retrieving for loaded project -fixed
+ -RowEditBuffer class introduced
+ -Object::debugError() shows more error info
+ -Connection::updateRow() and Cursor::updateRow() inttroduced for row updating
+ -some metods goes inline
+ -expanded list (obtained from QuerySchema::fieldsExpanded())
+ of Cursor's all query fields is cached within cursor
+ -FieldList - fields lookup is now case insensitive
+ -results of QuerySchema::fieldsExpanded() is cached inside QuerySchema
+ -QuerySchema::pkeyFieldsOrder(), QuerySchema::fieldsOrder() methods introduced (results are cached)
+
+-Drivers:
+ -SQLite driver updated for current API
+ -MySQL driver just compiles
+
+
+2003-12-13
+- browser: focusing fixed: doesn't lose focus when clicked
+
+2003-12-12
+- added caching to KexiProject::items() and KexiPartInfo
+- objects lookup-and-opening functionality moved from navigator (KexiBrowser) to keximainwindow
+- "-open" CLO reintroduced
+- KexiMainWindow: error messaging generalized a bit
+- KexiTableView - editors API and behaviour improoved
+
+- KexiPart::Part objects are cached
+- pointers to KexiPart::Item are used instead of values
+- KexiStartupFileDialog's file selection works on win32
+
+
+2003-12-11
+- Cursor::movePrev() impl. updated
+- DriverManager: result names are available for KParts::ComponentFactory::ComponentLoadingError errors
+
+- Detached KexiDialogBase windows now have proper icons
+- KexiTableView:
+ - size hint and minimum size hint fixed
+ - better widths for text fields in navigator
+ - cell redrawing & colors fixed
+- KexiDataTable: size hint and minimum size hint is reused from KexiTableView
+- Some fixes for KMDI taskbar
+- KexiInputTableEdit:
+ - fixed adding first char on starting edit
+ - numeric field == 0 is cleared on edit
+- KexiDialogBase, KexiPart::Part: creating part instances and registering generalized
+- KexiMainWindow: foxusing improoved, added action for focusing navigator
+- KexiMainWindow: members moved to d-pointer
+- KexiPart::GUIClient is created by KexiPart on demand, if GUI window available
+
+2003-12-10
+- KexiMainWindow:
+ * "show navigator" action is auto-handled by kmdi
+ * fixed : pointer to current xmlgui is now cleared before closing kexidialog
+- KexiTableView::paintRow() fixed crash when col number was ==-1
+
+2003-12-08
+- Kexi MainWindows' settings are now stored and restored:
+ - toolbars, docks positions
+ - MDI mode
+ - added workaround for TaskBar positioning problem
+
+2003-12-06
+- sources synced with win32 target, again
+- Kexi:: singletons are now accessed via functions, eg. Kexi::connset()
+
+2003-12-03
+- KMDI modes enabled
+- KexiStatusBar introduced
+
+2003-11-30
+- KexiDB::Field::isFPNumericType() added
+- KexiTableView now entirely uses KexiDB::Field::Type for types checking, not QVariant
+- KexiTableView::paintCell() simplified
+
+2003-11-28
+- KexiStartupDialog: existing file opening fixed (problem with autocompletion);
+ "accept" aborting when no filename entered
+- some minor fixes and TODOs
+
+2003-11-25
+Finally commited again
+General:
+- PostgreSQL-based projects: tables are now visible in Kexi
+- KexiProjectSelectorWidget for "projectopen" action reused with KexiProjectSelectorDialog
+- most KexiDB errors are connected to message boxes, even driver loading failures
+
+- after unsuccessfull Cursor::moveFirst() eof() and bof() now return true
+- '..' substituted with ".." for message strings
+
+- pqxx driver:
+ - cursor's internal transaction is now destroyed on error in drv_open()
+ - cursor's internal transaction has an unique name for easy debugging
+
+- Kexi::detectProjectData() added for detecting file type both on startup and after
+ filename is selected in Kexi's file dialog
+- KexiPart::Manager is now shared between project sessions
+- KexiPart:: classes' implementation a bit refreshed
+- QValidator and helper functions for identifiers added
+
+- Main Window:
+ - "Open Existing Project" action works both for file- and server-based projects
+ - "New Project" action works both for file- and server-based projects
+ - above actions now work also after startup, from toolbar
+
+- KexiPart::dbAvailable signal not needed since openProject() function returns boolean
+
+- KexiStartupDialog:
+ - KexiConnSelectorWidget reused
+ - if single page is configured, no tabs are visible,
+ so KexiStartupDialog is reused for projectopen and projectnew actions
+
+- KexiStartupFileDialog introduced as embeddable widget,
+ reusable for different file types (e.g. .kexi and .kexis)
+- KexiNewProjectWizard introduced for creating empty projects (both file and server based)
+ it will be also reused for "project templates wizards"
+
+2003-11-24
+2003-11-23
+- KexiDB: Connection::databaseExists() now works correctly with file-based drivers
+ - just file existence is checked
+
+- KexiDB::Connection
+ - creating or dropping system database is not permitted
+ - databaseExists(): full file paths are compared for file-based drivers
+- KexiDB::Driver i18n fix, thanks for Malcolm Hunter
+CCMAIL: malcolm.hunter@gmx.co.uk
+
+- KexiPart::Manager is now shared
+- KexiPart:: classes' implementation a bit refreshed
+
+TODO: keep ownership properly in KexiPart namespace!!!!!!
+
+
+2003-11-22
+2003-11-21
+- drivermanager cleanup hack
+- some improovements in kexidb clases
+- KexiDB:
+ - Driver::isSystemDatabaseName() added
+ - some methods converted to const
+ - Connection::databaseNames() now has parameter for skipping system database names
+ - Driver::isValid()
+ - now checks driver's major version and compares against kexidb library major version
+ - now is called on createConnection() instead of Connection::connect() so errors
+ can be catch earlier
+ - KEXIDB_DRIVER (like Q_OBJECT) in driver.h and KEXIDB_DRIVER_INFO in driver_p.h macros
+ introduced for ease driver development and decrease error count
+
+
+2003-11-19
+- KexiNewProjectWizard added
+- KexiStartupFileDialog convenient KFileDialog subclass added/reused
+
+2003-11-17
+- KexiDB: buffered cursors: the first record was shown 2 times
+- KexiDB::Connection: some internal query fixes
+- newapi tests: -buffered-cursors switch added, non-internal cursor for all tests
+ are now unbuffered by default; fixed buggy behaviour on nonsuccessful t.v. test
+
+2003-11-15
+- small compiler issues fixed in pqxxconnection
+
+2003-11-14
+- Field: inlines fixed for newer compilers
+- DriverManagerInternal::slotAppQuits() used to destroy all drivers
+ on QApplication quit, so even if there are DriverManager's static
+ instances that are destroyed on program "static destruction",
+ drivers are not kept after QApplication death.
+
+2003-11-12
+2003-11-13
+- Kexi startup procedure for both files and db connections introduced
+- startup dialog integrated
+- projectdata redesigned
+- menu items improoved, polished; e.g. Changed "File" menu entry to "Database"
+- ConnectionData:
+ -added id for connectiondata
+ -some const added
+
+2003-11-10
+- added global kexi version info, like in koffice
+- configure.in.in: added checking for qextmdi if required
+
+2003-11-07
+2003-11-06
+2003-11-05
+- KexiTableView:
+ - Navigation bar added
+ - cell's focus indicator frame is now also visible after focusout
+ - before entering to edit mode, we ensure that current cell is visible
+ - vscrollbar tooltip added
+- Cursor: some members made private or protected
+- Cursor: m_beforeFirst is probably not needed anymore; m_at==0 is enough
+- Field: isNumericType(), isTextType() added, etc.;
+ type names are now i18n'd in an array;
+ some methods inlined
+
+TODO: void KexiTableView::cancelRowEdit() still dont repaint properly!!
+TODO: update on lostfocus
+
+- SQLiteConnection:: sqlite_freemem() used to free error message when needed;
+ do the same for SQLiteCursor
+- serverResult(), etc. methods aded also to Cursor class, so finer grained error
+ info is available
+
+
+2003-11-04
+- Documentation added for few newer and older methods.
+
+2003-10-31
+- KexiDB::Cursor: many code like drv_getNextRecord() moved from SQLiteCursor down
+ to Cursor class
+
+2003-10-29
+2003-10-30
+- KexiStartupDialog added. Now, although it is project-and-connection-oriented,
+ it is designed to be still consistent with conventional document-driver startup
+ dialogs used for other KOffice apps.
+- KexiProjectSelectorWidget introduced
+- KexiProjectData structure added
+- KexiProjectSet structure added
+- Test for new KexiStartupDialog. Code written here will be reused for
+ "fileopen" and "filenew" Kexi actions.
+
+2003-10-28
+- KexiStartupDialog introduced
+- KexiDB::ConnectionData: now offers (optional) driverName info
+- KexiDB::Driver::Info (short usefull structure) is now offered by DriverManager
+- ConnectionData::localSocketFileName optional attribute added
+
+2003-10-23
+- new icons for few kexi-specific contexts;
+- most icons are installed into Kexi app dir
+
+2003-10-22
+- KexiTableView:
+ - sorting works again
+ - before sorting, row editing is cancelled
+ - better "ensure visible" code when scrolling on small area
+ - inserting rows code completed
+ - vheader repaint fixed
+
+2003-10-21
+- tests/newapi : Now all test types accept <db_name>
+- KexiTableView:
+ - page down key also move cursor to "insert row" if one is present
+ - double clicking on cell (opening cell editor) fixed
+ - fixed cell's repaint when autoscroll is performed after clicking cell at different row
+ - sorting by column enabled, full set of methods added for this
+ - new feature: empty row is appended right after start of editing new row (like in MSA),
+ to allow user to move to a next row
+- KexiDB driver services' properties updated:
+ - X-Kexi-DriverType=[File|Network]
+ - and X-Kexi-FileDBDriverMime (for file-based drivers only;
+ for sqlite it is: application/x-sqlite)
+ -MimeType property removed
+- startup speed improoved: drivers lookup in KexiDB::DriverManager is now on demand.
+- Driver name now reuses QObject::name()
+- Connection::lastInsertedAutoIncValue() introduced
+
+2003-10-19
+- Connection::useTemporaryDatabaseIfNeeded() now is also reused
+ for databaseExists() and databaseNames(), so these methods work when engine
+ needs used any database before asking for info
+- ConnectionPrivate::m_skip_databaseExists_check_in_useDatabase used
+ to avoid endless recursion between useDatabase() and databaseExists()
+ when useTemporaryDatabaseIfNeeded() is working
+- Connection::setupKexiDBSystemSchema() added check to prevent creating
+ system tables schema more that once per connection
+
+- Connection: reomved default values from useDatabase() and createDatabase()
+ because this might be confusing to use first-found database name here; (thanks piggz!)
+- FieldList: cached string == comma-separated list of fields added
+- Connection: valueToSQL() overload added, insertRecord() overload added so we can omit
+ some fields from inserting; this is also internally used when kexi__* tables are
+ filled what improoves backward compatibility
+- FieldList now offers lookup by field name, list of field names, it can create
+ subLists, usable to define list of fields for data inserting
+- fixed compiler-dependent bug in insertRecord()
+
+2003-10-18
+- Connection:
+ - useTemporaryDatabaseIfNeeded() added - Because some engines need to have
+ opened any database before executing administrative sql statements
+ like "create database" or "drop database",
+ this method is used to use appropriate, existing database for this connection.
+ - above method is now used in createDatabase() and dropDatabase() if needed.
+ - QString anyAvailableDatabaseName() - returns name of any (e.g. first found)
+ database for this connection, eg. "template1" for PostgreSQL.
+ - DriverBehaviour::ALWAYS_AVAILABLE_DATABASE_NAME added when hardcoded value
+ for above is enough.
+ - setAvailableDatabaseName(const QString& dbName) - This is option that e.g.
+ application that make use of KexiDB library can set to tune connection
+ behaviour when it need to temporary connect to any database
+ in the server to do some work (e.g. DROP DATABASE).
+ - Connection cleaning up bug fixed a bit.
+ - Naming Conventions document added
+ - drv_isDatabaseUsed() introduced for additional state-checks
+
+2003-10-17
+- Driver developers should not change values of several important Connection
+ class members, so these are now private.
+
+2003-10-16
+- KexiTableView:
+ - maany repainting, updating, resizing improoved, code simplifications
+ - editing row session introduced: edits for the same row is not accepted until move to another row
+ This allows multiple fields editing before row editing accept.
+
+2003-10-15
+- missing QuerySchema::addAsterisk(QueryAsterisk *asterisk) added
+- KexiTableView headers and cells repainting improoved
+- for mysql driver: beh->ROW_ID_FIELD_NAME="_ROWID", is this ok?
+
+2003-10-14
+- DriverBehaviour::ROW_ID_FIELD_NAME added
+- bool Driver::isValid() invented, it is so clever that obsolete or bad driver
+ can not be easily harmfull for application.
+- KexiTableView and KexiDataTableView reimplemented for new KexiDB API.
+- few classes renamed, e.g. KexiTableViewData is here now instead KexiTableList
+- KexiTableViewData is now mostly data structure, special rows like "inserter" will be handled
+ by KexiTableView code.
+- KexiDataTableView now uses Cursor (a bit not efficient yet).
+
+2003-10-13
+- Cursor has now two ways for definition: 1) use raw statement 2) use QuerySchema
+- Conenction: prepareQuery() accepting QuerySchema overload added
+- TableSchema::query() convenience function added
+- tableview: QuerySchema is now used a bit
+- QuerySchema(TableSchema* tableSchema) is created by defining
+ "all-tables query asterisk" (see QueryAsterisk) item.
+- Connection: implemented and selectStatement() replaced queryStatement() because
+ not only "select" queries will be implemented.
+- QuerySchema::fieldsExpanded() convenient function added to expand query asterisks
+- KexiDataTableView uses QuerySchema even smarter
+- kexi/kexidb/tests moved to kexi/ to avoid curcular deps
+
+2003-10-12
+- Connection::isDatabaseUsed() fixed
+- Connection: isConnected() is substituted with isDatabaseUsed() in some cases
+ and checkIsDatabaseUsed() is substituted with checkConnected() in many cases.
+- kexidb/tests/newapi tableview now uses KexiDataTableView
+- Connection:: many overloads like queryStatement( KexiDB::QuerySchema& querySchema ) or
+ executeQuery( QuerySchema& query, uint cursor_options = 0 ) added
+- KexiDataTableView: simplified: setDataSet() -> setData(); record -> cursor
+
+2003-10-11
+- Transaction::isNull() fixed
+- now tables creation seems to be ok ('tests/newapi sqlite tables' works)
+- sources synced again with win32 target
+- kexidb/parser and kexidb/tests/parser ported to win32
+- KexiDB::Expression introduced
+- isSystemObjectName() now checks for "kexi__" prefixes
+- checks on create table: if name is not system, if table is not empty, if fields are not system
+- KexiDB::Reference introduced
+- KexiDB::IndexSchema now contains informations about multiple related Reference objects
+- kexidb/tests/newapi : now tests are functions, so can be called as subtests if needed;
+ gui tests group added; tableview gui test introduced
+
+2003-10-10
+- FIX: creating kexidb system tables schema objects on useDatabase()
+- KexiDB::Object::debugError() added for convenience
+- Object: serverErrorMsg(), serverResult(), serverResultName(),
+ drv_clearServerResult() added
+- virtual Q_ULLONG Connection::drv_lastInsertRowID() added - returns unique
+ identifier of last inserted row.
+- DriverBehaviour::AUTO_INCREMENT_FIELD_OPTION added; autoinc field's option is now
+ used in "CREATE TABLE" statement building; bool SPECIAL_AUTO_INCREMENT_DEF added
+- fixed crash on mass transactions closing during closeDatabase()
+- added tables creation subtest to kexidb/tests/newapi
+- SQLite* classes: last operation's result is now stored for later checking
+
+2003-10-09
+KexiDB::Connection:
+- "select * from ..." substituted by "select <field name(s)> from ..."
+ where possible, so after future kexi_* tables change it will still work.
+- list of internally used kexi__* tables available with new static method
+- bool TableSchema::isKexiDBSystem() added for tables that are internal for KexiDB
+- bool SchemaData::isNative() added for objects that are native, ie. not contain additional
+ metadata information.
+
+- Connection::destroy() instead of Connection::disconnect() should be called
+ from xxxxConnection subclasses.
+- ~TableSchema calls Connection::removeMe() so it is removed from tables list if needed
+- ordering in Field object added
+- quite hacky and thus effective insertRecord() overloads added
+- some schema is added to kexi__* tables on createTable()
+
+2003-10-07
+- KexiDB::QueryAsterisk class introduced to define select queries with asterisks, like
+ "staff.*" in "SELECT staff.*, cars.model from staff, cars WHERE staff.car = cars.number"
+ (1st ttype) or "*" in "SELECT * from staff, cars WHERE staff.car = cars.number" (2nd type)
+- Query schemas storage added
+- using include path returned by 'mysql_config --mysql_config' as
+ is instead of eating mysql suffix
+- Transaction::null and isNull() added
+- Connection: Simulated AutoCommit feature implemented,
+ createTable() added - it uses autocommit if enabled.
+- Driver: DriverBehaviour member added - Detailed definition
+ of driver's default behaviour.
+
+2003-10-06
+-KexiDB::QuerySchema:
+ - field aliases added and checking if there is alias
+ - list of tables used in query added
+ - debug() updated
+
+2003-10-05
+-KexiDB::
+ - fields() now offered by fieldlist
+ - Field::Field( ) args order fixed (compiler didn't complain..)
+ - TableSchema: primary idx setting added
+ - TableSchema: autogenerated indices are added implicity on addField()
+ - IndexSchema: unique flag added, primary flag is now dependend on this
+
+2003-10-04
+-KexiDB:: addedd better access to creating schemas
+
+2003-10-03
+-KexiDB:: API extended for db transactions
+ - Transaction is now implicity shared container for storing transaction handle,
+ TransactionData is used as internal driver-dependent storage
+ - former Transaction class is now TransactionGuard
+ - "default transaction" added to Conenction
+ - SingleTransactions, MultipleTransactions and NestedTransactions added to KexiDB::Driver::Features
+ - autoCommit option added to Connection (can use both driver-specific feature ans simulate),
+ (not fully implemented yet)
+ - active transactions are rolled back on database close
+ - heavily commented :)
+ - index.* moved to indexschema.*
+
+2003-10-02
+-KexiDB::
+ - IndexSchema now inherits also from SchemaData (its additional
+ properties will be used at least in gui)
+ - IndexSchema now points to a table that contains it, not connection
+ - fixed owning rules for FieldList: not only TableSchema owns its fileds,
+ while QuerySchema, IndexSchema not
+ - more methods moved to const
+ - QueryData offers now information about its parent table
+
+ - some record inserting introduced in Connection
+ - yet more methods moved to const
+ - KexiDB version info added (both as functions and defines)
+ - global definitions file added
+
+2003-10-01
+-KexiDB::
+ - Table and query schemas now can be looked up using its id or name using Connection::tableSchema()
+ and Connection::querySchema()
+ - Connection::querySingleRecord() added for easy retrieval single (first) record
+ from query's result set
+ - Cursor::storeCurrentRecord(RecordData &data) added: Puts current record's data into data
+ (makes a deep copy).
+ - update after moving query.*, table.* file to queryschema.*, tableschema.*
+ - Cursor buffering-related members moved from SQLiteCursor to Cursor
+ - Cursor::moveFirst() fixed for buffered cursors - reopen() is now not needed
+ - SQLiteCursor::drv_getNextRecord() we don't try to fetch records when
+ we know that buffer is already fully loaded.
+- Uff, It is first time since ~3 moths I can see kexi main window after kexiDB change
+
+2003-09-28
+- KexiDB:
+ - Table, Query and Index classes renamed to TableSchema, QuerySchema and IndexSchema
+ - query schemas storage introduced
+ - kexi__table 'system table' renamed to kexi__objects and for it now will be used for
+ storing also other types of objects, eg. queries. Some properties added to this table.
+ - kexi__querydata added for query schemas
+ - kexi__db added for storing database properties, e.g. kexidb version
+
+2003-09-26
+- new kexidb/newapi test added: dbcreation
+- KexiDB::Connection::databaseExists() has now arg. that allows ignoring error messages (deflt)
+- params for createDatabase() and useDatabase() are now optional for more convenient
+ use file-based drivers
+
+2003-09-24
+bool buffering_completed added to SQLiteCursor: true if we have already all
+ records stored in the buffer
+
+2003-09-18
+- Buffered KexiDB::Cursor type introduced, for this type KexiDB::Cursor::isBuferred() is true;
+ this is "test" implementation for SQLiteConnection only [INCOMPLETE].
+ Cursor options introduced (Cursor::options()). Cursor's buffered flag if one these options.
+ Options are now optional parameters both for protected Coursor's constructor and for
+ Conenction::executeQuery() and Conenction::prepareQuery()
+- virtual bool Connection::drv_databaseExists( const QString &dbName ) added for optional
+ reimplementation. It is a pair with drv_getDatabasesList(), both are used in databaseExists() now.
+ See comments for details.
+- Connection::useDatabase(dbName) now do not allows dbName that do not exists,
+ while for single-db-per-connection-engines (eg. file-based engines) Connection::databaseExists()
+ returns true only for single db name (eventually URL).
+- for file drivers: file existence checks moved to databaseExists()
+- in Conenction::useDatabase(dbName): closeDatabase() is called before other database should be used,
+ if there already was opened database.
+- Connection::drv_getDatabasesList() has now default implementation that returns empty list.
+- sources in drivers/mySQL/ updated just to work with kexidb API changes
+- Connection::drv_databaseExists(dbName) is now by default just checking if dbName
+ is on the db names list
+- Some code for bufering added - forward moving with buffered cursors looks
+ better.
+
+2003-09-17
+- KexiDB::Query introduced: Table::name() and Query::name() moved to FieldList.
+- Field::ListIterator FieldList::fieldsIterator(), Field::FieldList::debug() and clear() added
+- ERR_CURSOR_RECORD_FETCHING added
+
+TODO: add
+-static cursor types
+-scrollable cursor types
+
+2003-09-16
+- KexiDB::DriverManager: driver names accepted by KexiDB::DriverManager::driver(name)
+ and KexiDB::DriverManager::serviceInfo(name) methods are now case insensitive.
+- 3rdparty/kexisql: added files needed by win32 target
+- removed kexidb/drivers/sqlite/driver directory: now we include sqlite.h from 3rdparty/kexisql/src/
+- MySqlDriver's service name is "kexidb_mysqldriver", export macro is KEXIDB_MYSQL_DRIVER_EXPORT;
+ win32 compilation fixed.
+
+2003-09-15
+- KexiDB::Cursor:
+ - movePrev(), bof() added;
+ - drv_getRecord() splitted to drv_getNextRecord(), drv_getPrevRecord()
+ - fieldCount() added, not fully works yet
+ - on open() we have always bof()==true and eof()==false
+ - simplification: now bof() is computed as m_at==0
+ - we are internally counting records from 1 and externally from 0
+ (what is visible using at()).
+ - m_at and thus at() has now Q_LLONG type
+
+2003-09-14
+- KexiDB::Object::setErrorMsg renamed to setError
+- KexiDB::DriverManager is now just a container for one reference
+ to Internal Library's Driver Manager, so all you are expected to do is to remember
+ about deleting KexiDB::DriverManager instance in your app.
+ You can create many KexiDB::DriverManager objects.
+- KexiDB lib now handles automatic deletion of drivers after last
+ KexiDB::DriverManager object deletion (thus refcount==0).
+- tests/newapi now looks more simply at the end of main.cpp file :)
+TODO: do the same with DriverManager as Driver
+- KexiDB::Connection::tableSchema() updated for new KexiDB::Connection::executeQuery() semantic.
+- DriverManager::self() no longer needed
+TODO: add fieldCount() to Cursor
+
+2003-09-13
+KexiDB:
+- Connection::prepareQuery() added for creating not opened cursors.
+- Connection::executeQuery() now automatically opens the query with created cursor.
+- Connection::destroy() convenient method added for obligatory use
+ in Conenction subclasses' dctors. destroy() just disconnets() and takes the Connection
+ object out of parent (Driver).
+- SQLiteConnection updated for above requirements
+- cursors and tabledefs are now deleted not in ~Connection but
+ on every Connection::disconnect().
+- kexidb/tests/newapi also compiles with qmake and runs on unix
+TODO: add auto removing Drivers on DriverManager deletion.
+
+2003-09-12
+Great day for KexiDB in cvs:
+- Old KexiDB moved with actual Kexi version to non-default old_db_api branch.
+- Current (HEAD) now contains new kexidb/ dir for with API
+- kexidb/tests/ added to cvs with one 'newapi' test
+
+2003-09-11
+- Index::List and Field::List are now QPrrList<Field>, so Field objects are now stored as a pointers,
+ no as a values. These are owned by Table objects.
+
+2003-09-10
+- Html Doxygen-generated docs for KexiDB module configured
+- KexiDB::Index introduced - definition of single table index (1 or multi-field)
+- KexiDB::FieldList introduced - list of fields; base class for KexiDB::Index, KexiDB::Table
+TODO: add KexiDB::Query and use this object eg. as argument for Cursor* Connection::executeQuery()
+
+2003-09-09
+kexidb:
+- Every Connection object stores cursors opened with it. On Connection destruction,
+ cursors are deleted automatically. The same with table schemas within Connection object.
+ QPtrDict is used for storing cursor objects.
+- Every Driver object stores connection using QPtrDict, not QPtrList.
+- Driver::isSystemObjectName(), Driver::isSystemFieldName() are case insensitive.
+
+TODO: PROPOSAL: add compile-time option for kexidb to be kde-independent.
+ This could be quite easy.
+
+2003-09-08
+kexidb:
+- QStringList Driver::systemNames() is now bool Driver isSystemObjectName()
+ -- Checks system object names,
+ eg. build-in system tables that cannot be used by user,
+ and in most cases user even shouldn't see these. Specific for
+ a given driver implementation.
+ For SQLITE driver system object names are these with prefix "sqlite_"
+- QStringList isSystemFieldName( n ) added: return true if n is a system field names,
+ build-in system fields that cannot be used by user,
+ and in most cases user even shouldn't see this. Specific for
+ a given driver implementation.
+ For SQLITE driver this has one system field name: "_ROWID_" (see CHANGELOG-Kexi-SQLITE).
+- QString Connection::valueToSQL( const Field::Type ftype, QVariant& v )
+ added for encoding values for sql queries
+- Connection::createTableStatement(): unique, default and not null flags are now added.
+
+TODO: also fields with UNIQUE flags should cause KexiDB::Table to have appropriate KexiDB::Index
+
+2003-09-06
+- new KexiDB is stored in "kexidb" subdir, old api in "kexiDB", on win32 in "kexidb_old"
+- virtual QString escapeString(const QString& str)
+ virtual QCString escapeString(const QCString& str) added for Connection
+- Field::setDefaultValue(const QCString& d) added. Default values are stored in 'f_default Text'
+ field of kexi__fields table and are string-encoded there.
+
+2003-09-04
+- kexidb CHANGES FROM JOWENN: No ; after namespac, some d Pointers,
+ littlebit different includes, littlebit modified destructors
+
+2003-09-01
+ KexiDB::
+ - Cursor:: moveFirst(), moveLast(), moveNext(), eof(), at(), QVariant value(int i)
+TODO: Cursor::value(int i) should use schema information to convert types of values
+ (now all values are strings)
+ - Connection::tableSchema() - returns schema of given table retrieved
+ using connection's system kexi__* tables
+ - Connection::deleteCursor()
+ - Driver::sqlTypeName(int id_t) - returns sql type name for given driver
+ - static QString Driver::defaultSQLTypeName(int id_t) - returns sql type name for given
+driver
+ (usable when we do not have Driver instance yet)
+ - Table::debug()
+TODO: add default value storage
+
+2003-08-29
+- KexiDB::Cursor class, Cursor* KexiDB::Connection::executeQuery(statement) creates cursor
+- KexiDB::Connection::drv_executeSQL(): executes query \a statement, but without returning resulting
+ rows (used mostly for functional queries).
+
+2003-08-28
+- KexiDB::Transaction helper class introduced, transaction-related methods added for KexiDB::Connection.
+- drv_createTable() and createTableStatement() moved up to KexiDB::Connection
+
+2003-08-27
+- KexiDB::Table, KexiDB::Field classes introduced.
+ New DB-storage design started: kexi__table, kexi__fields system tables
+ (for db schema storage).
+
+2003-07-28
+- KexiDataTableView::tableSize() removed (wasn't this unnecessary?)
+
+2003-07-26
+- KexiRelationViewTableContainer: width is now based on maximum width of
+the field name or header name.
+- KexiDataTable do not stores members like KexiDB or KexiDBRecordSet bu
+uses these from KexiDataTableView.
+
+2003-07-25
+- KexiDialogBase now also inherits from KXMLGUIClient for easy actions management.
+ Do not make KXMLGUIClient subclass but just call setXMLFile() in KexiDialogBase:: subclasses' ctor
+ and add actions to KexiDialogBase::actionColection().
+- KexiRelationDialog:
+ - now uses KXMLGUIClient to better utilize its actions
+ - right-click context menus fully works
+ - 'hide selected table', 'open selected table' actions added (unimpl.), kexirelationsview.rc
+added.
+- Main Kexi menu bar little more standarized: 'Edit' menu added, 'View' menu moved just after Edit menu.
+TODO: implement selected table hiding
+TODO: implement selected table opening
+TODO: add drag from browser
+
+2003-07-24
+- --open command line option added for automatic opening objects at startup, this will help in our
+ testing (see 'kexi --help' or main.cpp for details).
+- KexiDialogBase got sizeHint() now that make it fit to qworkspace if it is a in-workspace window.
+- virtual QString KexiProjectHandler::groupName() added that offers store plurar form of part name,
+ while name() offers singular form.
+- KexiRelationViewTable:
+ - list items (fields) without icons are aligned using transparent icons;
+ - moving TableViews back from outside of scroll view area automatically shrinks scroll view's
+ area to smallest possible size
+ - field that we drag over now is highlighted
+TODO: fix KListView::viewportPaintEvent() like in KexiRelationViewTable::drawItemHighlighter()
+ and commit to cvs (kdeui)
+- KexiRelationViewTableContainer got now focus/unfocus/z-order functionality. Colors of table headers
+ are consistent with focusing and OS settings
+TODO: fix connection lines painting
+
+2003-07-23
+- KexiTableView:
+ - KexiTableViewPrivate d-pointer added with "kexitableview_p.h", "kexitableview_p.cpp"
+ - Fixed repainting empty areas (background) in KexiTableView: QColorGroup::Base color is used.
+ - editableOnDoubleClick(), setEmptyAreaColor() added
+ - For Qt::Key_Menu key, context menu is show below the current cell, if available.
+- KexiDialogBase reorganized. Now it offers more generic properties and functionality,
+ so subclasses can be more cleanly written and consistent with API and behaviour.
+- Relations window is registered with 'kexi/relations' id,
+ while relations window embedded in query designer has id 'kexi/query/<query_name>/relations'
+TODO: implement sizeHint() for KexiDialogBase subclasses
+TODO: make open, create, delete and edit functions in KexiProjectHandlerProxy subclasses more generic
+
+2003-07-22
+- scrollbars enabled in KexiWorkspaceMDI
+
+2003-07-21
+- Kexi project 'kexi_doc' icon added, on win32 it is added to kexi.exe resources (id=1) and registered for
+ .kexi extension on installation.
+TODO: add "New document" (empty or from template) command-line option, so it can be registered
+ as "shell/new" in HKCR registry key on win32.
+TODO: like above: "Print" and "Print to"
+
+2003-07-18
+- " " prepended to Project Browsers' list items' texts for better look (maybe add this feature
+ to KListView or make its generic subclass?)
+- Some fixes with text positioning in tableview cells: float and date type y-offset,
+ date type editor frames removed
+TODO: maybe optionally use KDateWidget (this needs to extend and improve this class)?
+ QDateEdit is good enough (but this need to follow date format for locale or settings
+ using QDateEdit::Order, etc.).
+TODO: fix KDatePicker (in kdeui): accepting with Return key or dblclick, and setting focus.
+- KexiTableEdit has now QColorGroup::Base (usually: white) background
+- KexiInputTableEdit has now some space on the left, so strings look better.
+TODO: add default hint for columns widths, based on type of field
+
+2003-07-17
+- actions for Form, Kugar, Relations, Script Parts updated using KexiPartItemAction,
+ KexiProjectHandlerProxy::groupContextMenu() and itemContextMenu()
+ like in Tables and Queries' Parts
+- kexikugarhandlerui.rc moved to kexi/data/
+- 'Other Licenses' submenu made in 'Help' menu, 'Report Generator Licensing'
+ entry moved here from Kugar Part's popup menu
+TODO: move this submenu after 'Error report' Entry (requires changes to koffice_shell.rc).
+- kexirc added
+- added option for KexiBrowserItem to be sorted in FIFO order (top level items - by default,
+ others are sorted in alpha order). Visible Parts' order is now specified (by library name)
+ in the Kexi's main config file "kexirc" ([Parts]/Order), because KTrader sorts parts
+ by name in aplha order. Unspecified Parts are appended at the end of list.
+ This order setting is loaded and used on parts loading (KexiProject::loadHandlers()).
+
+2003-07-16
+- fixed registering (registerAs) for query designer's windows,
+ query from project browser removing fixed
+- project browser's groups items (tables, queries,..) are now non-selectable
+ as these are rather special constant containers
+- added convenient hidden(), shown() signals to KexiDialogBase
+- added KexiDialogBase::plugToggleAction(KToggleAction *toggle_action) for syncing window
+ visibility with toggle action's state
+
+2003-07-15
+- Kexi/win32 synced with cvs again after plugins/ rearrangement.
+- New Kexi icon updated for win32
+- menubar and popupmenu entries in Query Part merged using KexiPartItemAction (new KAction subclass);
+ The same for Tables Part. KexiPartPopupMenu now collaborates with KexiPartItemAction to reuse its
+ action's information (avoid redundancy).
+TODO: add delete_item action icon (equal "button_cancel" in crystalsvg) to the koffice/actions icons
+TODO: like above, add "script" icon to kexi/actions ("moc_src" is used)
+
+2003-07-01
+- Some table view text positions painting fixes for x11, for dbl and int type, editor's position fixed.
+TODO: add left margin for cells' text (at least for x11)
+
+2003-06-28
+- Project Wizard: fixed align for big picture, some widgets availability fixes,
+big picture moved to KexiCreateProjectPage superclass.
+- KEXI_ADD_EXAMPLE_ENGINES compile-time option added: if set, example engines will be added
+ to the drivers list in the Project wizard
+
+2003-06-22
+- Alter Table Window: info about not implemented rows removal added for KEXI_NO_UNFINISHED option
+ Redundant label with table name removed from top of the window.
+ Double click in KexiTableView reenabled.
+ PropertyEditor and KexiTableView size hints little fixed.
+- KexiTableView: now clicking outside of a grid do not activates any cell;
+ (crash fixed) sorting disabled while in-cell editor is visible
+- "Project properties" function disabled for KEXI_NO_UNFINISHED option
+
+2003-06-21
+- automatically set focus inside Alter Table Window and show cursor
+- fixed crash on entering data in 2nd, 3rd, etc. columns in table added during current session
+ (table def struct wasn't created for new table)
+
+2003-06-17
+([JK] - fix proposed by Jakub Kubica)
+[JK]- (in kofficecore): KoMainWindow::KoMainWindow: KStdAction::configureToolbars disabled:
+TODO: enable this when configure window will work
+[JK]- "Settings>Configure Kexi" action temporary disabled for KEXI_NO_UNFINISHED option
+TODO: reenable this!
+[JK]- "Save password" in authorization page of the Project wizard removed for KEXI_NO_UNFINISHED option
+TODO: reenable this!
+- <ActionList name="toolbarlist"> line removed from koffice_shell.rc (this removed redundant actions)
+[JK]- now, after failed db connection Project wizard do not allow moving to DB selection page
+- now it's not allowed to close alter table window for a table that do not contain fields
+ (message box is displayed then). The same is when user try to delete table (without fields)
+ that is currently opened
+TODO: allow the user to give up and delete this table
+
+
+2003-06-15
+- when KEXI_NO_UNFINISHED option is set, msgbox information is presented for unimplemented actions
+- kexidb: added information about if database (given by name) is considered as system db
+- projectwizard: only non-system databases are visible on the db list; db icon added
+- propertyeditor in altertable window has narrower 2nd column
+
+2003-06-14
+- inserting queries into list fixed
+- added KEXI_NO_FILTER_DLG compile-time option for incomplete filter dialog in query editor
+- added KEXI_NO_UNFINISHED compile-time option for disabling unfinished features
+- KexiTableView::DeletionPolicy is now (mostly) used for choose KexiTableView deletion method
+TODO: KexiTableView::AskDelete flag is not implemented
+TODO: do the same with KexiTableView::AdditionPolicy
+- auto popup menu is added for KexiTableView when KexiTableView::DeletionPolicy != NoDelete
+ or the same is for KexiTableView::AdditionPolicy, so no more external (e.g. in KexiDataTable)
+ popup menu creation is required for just addition and deletion actions.
+- fixed row deletion in query designer's table view
+- fixed subwindows sizes: now these are resized to the workspace size
+- tableview's cells text positioning fixed, synced with editors, cells size fixed,
+ numeric cells' editors are now right-aligned
+
+2003-06-13
+- Fixed crash on altering newly created table
+- (temporary fix) MySqlRecord is now always set to r-w mode
+TODO: MySqlRecord: get readonly flag from the database
+TODO: IMPORTANT: Kaxi must automatically add primary key it is not added by user,
+ or at least propose this for user before saving altered table. If given table does not conatin
+ a PK, then it will be readonly.(or at least removing given row wont be possible if this row
+ is duplicated)
+ CURRENT STATE: removing rows works only for table having PK
+- data table window's caption fixed
+- Control+Delete shortcut added for current record removing
+- fixed removing vertical header item after current record removing
+- KexiTableEdit now uses layout for proper displaying its Editor
+TODO: KexiTableEdit shouldnt have subwidget like KLineEdit but its subclasses should
+ inherit also from given editor widget (eg. from KLineEdit)
+TODO: KexiTableView's vertical header isn't updated after several actions
+- windows now have icons == KexiProjectHandler::itemPixmap()
+- normal size for relations dialog's combobox
+TODO: change method for adding tables to relations
+- KexiDialogBase subclasses now registers to view with
+ identifier == KexiProjectHandlerItem::fullIdentifier(), not caption or title
+- KexiRelationDialog is now registered to view as "kexi/relations",
+ so relations window is unique per view
+TODO: disable altering when the same table is opened for data viewing
+- avoid opening both alter view and data view for the same table
+- avoid creating multiple tables with the same name
+TODO: other objects too: move this to common method
+TODO: avoid creating/altering table with no fields
+
+2003-06-12
+- Table View Editor keyboard actions fixed, double type validating fixed,
+ fixed Enter, Delete keys, added F2 key, disabled non-printable unused keys
+TODO: use QValidator everywhere here!
+- after opening KexiDataTable focus is set inside automatically
+
+2003-06-11
+- Kexi objects have now titles - user-visible text instead low-level names
+
+2003-06-09
+- calling KexiView::finalizeInit() moved to KexiProject, actions for toggling
+dock windows visiblity fixed, simplified code
+- some icons, actions fixed
+- KexiProjectHandlerItem redesign
+
+2003-06-05
+- relation view's fixes and redesign
+
+2003-06-04
+- projectWizard: double click on dbases list opens selected db
+- navigator: KEXI_NO_MULTI_TABS option added for disable showing redundant tabs
+
+2003-05-28
+- fixed problem with project window's increased width while clicking on the
+ tabs; multitabbar fonts improvements; code cleanups
+
+2003-05-27
+- i18n-pl finished for ui and servicenames
+- kexi can load and save documents
+
+2003-05-17
+- kexi has icon on win32
+- kexi installer
+
+2003-05-08
+- kexi cvs merge:
+ KPath removed (qt/win32 will be patched instead)
+- libkexifilters added
+- core/kexisettings, core/kexiworkspaceSDI kexiDB/kexidbupdaterecord ported
+
+2003-04-28
+- fresh cvs and win32 version merge
+- kexifilter added
+
+2003-04-22
+- partial (example) polish l18n for GUI
+
+2003-04-15
+- keximysqlinterface lib. ported
+- updated servicetypes/kexidbdriver.desktop installed
+- mysqlinterface.desktop removed: valid is keximysqlinterface.desktop
+
+2003-04-12
+- libraries ported: kexipart kexihandler_form kexihandler_query kexihandler_relation
+ kexihandler_table kexiprojectwizard
+
+- Kexi running with projectwizard (no db avaliable although)
+
+2003-04-07
+- now Kexi has visible menu, toolbar and 'welcome' dialog
+
+2003-04-02
+- application's start process is simply hacked inside main() instead of inside KoApplication::start()
+ so, Kexi application running on screen for the first time on win32
+
+2003-04-01
+- kexi runs but is invisible
+
+2003-03-31
+- KEXI_NO_PRINT=1 (temporary) disables printing functionality
+- kexiDB/kexidb.h: KexiDBDriver *m_currentDriver moved from public slots section to private
+- kexicore and kexidb libs ported
+- kexi at least now compiles (but crashes)
+
+2003-03-28
+- first touch on Kexi sources for porting
+
+2002-06-23, Sunday, 00:20:36 (Lucijan)
+- First Kexi commit, SVN revision #162554
+ details: http://websvn.kde.org/trunk/koffice/kexi/?rev=162554
diff --git a/kexi/doc/dev/INTERESTING b/kexi/doc/dev/INTERESTING
new file mode 100644
index 000000000..cbcba162e
--- /dev/null
+++ b/kexi/doc/dev/INTERESTING
@@ -0,0 +1,29 @@
+----------------------------------------------------------
+ Interesting sources of information
+ - projects, docs, etc. that can help in Kexi development
+----------------------------------------------------------
+
+2003-10-06 js
+
+[SQL Translator and Text RecordParser]
+
+from README:
+ SQL::Translator is a group of Perl modules that converts
+ vendor-specific SQL table definitions into other formats, such as
+ other vendor-specific SQL, ER diagrams, documentation (POD and HTML),
+ XML, and Class::DBI classes. The main focus of SQL::Translator is
+ SQL, but parsers exist for other structured data formats, including
+ Excel spreadsheets and arbitrarily delimited text files. Through the
+ separation of the code into parsers and producers with an object model
+ in between, it's possible to combine any parser with any producer, to
+ plug in custom parsers or producers, or to manipulate the parsed data
+ via the built-in object model. Presently only the definition parts of
+ SQL are handled (CREATE, ALTER), not the manipulation of data (INSERT,
+ UPDATE, DELETE).
+
+http://search.cpan.org/src/KCLARK/
+
+Note: can be usefull for KexiDB develoment
+to see their parsers and converters
+
+----------------------------------------------------------
diff --git a/kexi/doc/dev/TODO-Kexi-js b/kexi/doc/dev/TODO-Kexi-js
new file mode 100644
index 000000000..5974845c0
--- /dev/null
+++ b/kexi/doc/dev/TODO-Kexi-js
@@ -0,0 +1,1098 @@
+-----------------------------------------------------------------------------
+ Kexi Development TODO Document
+ From jstaniek's Point of View
+
+ Also includes: win32 porting and multiplatform
+ features, planning, improving ideas, details
+
+ Copyright (C) 2003-2007 Jaroslaw Staniek js at iidea.pl / OpenOffice Polska
+ Kexi home page: http://www.kexi-project.org/
+-----------------------------------------------------------------------------
+
+TODO: rename KexiTabBrowser to KexiProjectWindow
+TODO: rename KexiBrowser to KexiProjectListView
+TODO: rename KexiBrowserItem to KexiProjectListViewItem
+TODO: rename KexiDialogBase to KexiWindowBase
+
+TODO: Kexi can use database as only medium for storing _all_ project's data
+ and then can treat .kexi files as:
+ -just export/import format for Kexi projects
+ -medium for storing projects that use sqlite driver
+ -simply: shortcut to project stored in database
+
+HINT: DO NOT USE signals/slots for lowlevel data manipulation (e.g. loading data for tableviews)!
+
+TODO: add kexiinclude_HEADERS and kexiincludedir to Makefile.ams to install headers, see:
+http://developer.kde.org/documentation/other/makefile_am_howto.html
+
+TODO: after kexi main window is activated again (from minimized state),
+ always 1st opened subwindow is activated
+TODO: when editor in table cell is active: after click outside of it, editor should be closed (accepted)
+
+TODO: FIX: QMetaObject::findSignal:KexiQueryPart: Conflict
+ with KexiProjectHandler::itemListChanged(KexiProjectHandler*) in KexiView
+
+TODO: FIX vertical alignment in table view's cells based on corrent QFontMetrics (make tests for
+ different font sizes and names)
+
+TODO: change texts in projectwizard to more descriptive:
+"Authentication" --> "Database server's user authentication"
+"Database Location" --> "Database Server Location"
+
+TODO: [complexity=big] add command line kexi tools
+
+TODO: install kexi-specific icons in apps/kexi/icons not as global
+
+TODO(GUI):
+-add "Change data source" button in project wizard
+-after pressing this button, show "connections selection dialog"
+-create "connections selection dialog" instead of project wizard:
+--add "Always show me this advanced dialog" checkbox to connections selection dialog
+--add "Set selected connection as default" checkbox to connections selection dialog
+-old project wizard is now "connection wizard" (for creating new connection);
+ "connection" created by user stores full info needed to get databases list
+-connection data is a mime type of local xml file, so it can be used to create shortcuts
+-kexi project is just connection extended with providing database name and user (local)
+ settings for this given database
+-on startup: by default offer:
+--creating empty temporary db on startup with name "New database"
+--creating new database using database wizard
+--opening existing Kexi projects
+--importing existing non-kexi databases
+
+FIX: corrupted database can be created sometimes (eg. for tests/newapi sqlite dbcreation)
+ --reason: maybe file was not flushed and app too early exited?
+
+<SQLITE>
+from: http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html
+
+TODO:
+"Although SQLite parses named transactions, the names are ignored.
+SQLite does not support nested transactions. If you split the SQL statements in a transaction
+over several sqlite_exec() calls, you must be prepared to handle failure in each of these calls.
+If a failure occurs SQLite will return a non-zero value. After that, SQLite will revert to the
+default behavior of giving each SQL statement its own transaction until a new transaction is started."
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+---so: check if any sqlite_exec() failed and if so - update connection state as if rollback has been done.
+
+TODO: use callbacks for fetching data (to avoid data copying)
+TODO: "The SQLite library includes functions sqlite_encode_binary() and sqlite_decode_binary()
+in "encode.c". They can be used to safely encode binary data in a string form suitable for storing
+in SQLite, and safe for use in SQL queries. These functions are an "addition" to the SQLite library.
+They are not compiled into the library by default, and are not present in the precompiled binaries
+distributed on the download page."
+
+TODO: add possibility in cursor: "Operating in-place on data returned by sqlite_get_table()"
+
+TODO: add custom collation using sqlite3_create_collation() for:
+ - non-latin1 characters
+ - non-latin1 characters with NOCASE option
+TODO: add option for use ":memory:" special file.
+ - Allow to use it also e.g. for migration, etc.
+ - Allow to attach it as special portion of db
+
+TODO: Use hints coming from "Understanding The Architecture Of SQLite" slides
+ (optimization, etc.), http://www.sqlite.org/php2004/slides-all.html
+
+TODO: table merge automaton:
+ Imagine you have a simple database with two tables, and a third to join them as a
+ many-to-many relationship. Someone sends me an SQLite database as a file which has the
+ same structure, but the data is different. I want to merge these two databases
+ together keeping all of the relationships intact, without duplicating data.
+
+ For example, if the tables were "customers" and "products" and the one in between
+ "orders", there is the possibility that some customers and/or products might be the
+ same, but with different primary keys. The problem is that since the databases were
+ independently created, a simple union will break the relationships since the primary
+ keys will overlap.
+
+ I can think of brute force ways to do this, but I was wondering if anyone might have a
+ good algorithm or technique to accomplish this efficiently.
+
+TODO: add support for PRAGMA page_size on new db creation,
+ and e.g. propose default values for win32
+ http://www.sqlite.org/cvstrac/wiki?p=PerformanceTuningWindows
+ (see also other optimization mentioned here)
+
+TODO: In Memory database/tables: how to load an sqlite file to be entirely fit in the memory?
+ clever solution: "You could set the cache size as big as your database file (via pragma).
+ This should load all (used) data into memory. This is probably not a good solution
+ if your application does not run for a longer time. "
+TODO: add autovacuum support on SQLite db creation?
+
+TODO: use sqlite3_set_authorizer() to get fine-grained access control support
+TODO: use SQLite's > 3.3.6 ability to load new SQL functions and collating sequences from shared libraries and DLLs
+ http://www.sqlite.org/cvstrac/wiki?p=LoadableExtensions
+TODO: use Virtual Tables to get external virtual data like CSV live queries
+ http://www.sqlite.org/cvstrac/wiki?p=VirtualTables
+TODO: use triggers to get undo/redo: http://www.sqlite.org/cvstrac/wiki?p=UndoRedo
+
+TODO: implement autoincrement for any field (also multiple fields) using the triggers; example:
+ CREATE TABLE test (a integer, b integer, txt text);
+ CREATE TRIGGER test_a_seq_trigger AFTER INSERT ON test FOR EACH ROW BEGIN update test set a=(ifnull((select max(a)+1 from test), 1)) where rowid=new.rowid and new.a isnull; END;
+ CREATE TRIGGER test_b_seq_trigger AFTER INSERT ON test FOR EACH ROW BEGIN update test set b=(ifnull((select max(b)+1 from test), 1)) where rowid=new.rowid and new.b isnull; END;
+ insert into test values (null, null, 'foo');
+ insert into test values (null, 7, 'bar');
+ insert into test values (null, null, 'text');
+ a|b|txt
+ 1|1|foo
+ 2|7|bar
+ 3|8|text
+ Then, support the following grammar in KEXISQL:
+ CREATE TABLE test (a integer autoincrement, b integer autoincrement, txt text);
+ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^
+
+</SQLITE>
+
+<RENAME_PROPOSALS>
+ALSO TODO: move all simple widget classes (e.g. KexiSettings, KexiTabBrowser)
+from core, to more detailed dirs,
+and projectWizard/ to wizards/ (there will be more wizards soon)
+
+</RENAME_PROPOSALS>
+
+TODO:
+- add kexi__parts (p_id integer, p_type) table and appropriate part objects in kexi__objects
+
+
+
+MOST RECENT TODO:
+- reuse recent project tab as separate window for "More projects..." menu action [difficulty: 1]
+- implement contents update for "recentprojects" menu action [difficulty: 2]
+- implement: drag-and-drop of the project file to the outside of Kexi, [difficulty: 2]
+- saveas== saving another shortcut to a project if it is server-based [difficulty: 2]
+
+TODO: improve security for password information in .kexis files
+TODO: for .kexis files' conenction data - if password attr. is not present - ask for password
+IDEA: integrate password information in .kexis files with KWallet
+
+
+TODO: Put Connection::dropTable() and Conenction::dropQuery() INSIDE TRANSACTION!!!!!!!
+
+TOP REQUESTED FEATURES LIST @ PL EXPO 2004:
+- ODBC driver (hello mattr!)
+- Access -> Kexi migration tool (done)
+- scripting
+
+=== Kexi Parts TODO ===
+- kexipart.h: add KexiPart::AboutData : public KAboutData
+ and
+ #define KEXIPART_EXPORT_COMPONENT_FACTORY( libname, partClass, aboutData ) \
+ K_EXPORT_COMPONENT_FACTORY( libname, KGenericFactory<partClass>(aboutData) )
+- add gui for viewing full part information
+
+=== General TODO ===
+- avoid using veryvery long names for objects
+- IDEA: Learning module. A special optional pane which shows what commands are sent to server
+ for learning purposes.
+- IDEA: Server-side wrapper for db connections (very simple) that allows anonymous [1]
+ (or any predefined group of users [2])
+ -- wrapper handles connections by validating *virtual* usernames and passwords for [1] case,
+ or providing unique virtual user names and passwords for [2] case
+ -- on the server side, wrapper connects to database server (it can be available locally or remotely)
+ using it's private user name and password. Thus, we only need one *physical* user name and password
+ for multiple users. Virtual names/passwords are mapped to physical one.
+ This is a solution for hosted database servers where it's hard to create as many users
+ as actually needed.
+ -- Priviledges are mapped as well, and translated. By default, user A could be able to create his/her
+ own database and drop it. He/she couldn't be able to do the same with user's B databases,
+ until user B allow this. The same for creating tables, and writing data.
+ -- wrapper secures server from too large data sets being sent to server
+ (parameters need to be configurable)
+ -- it's all dedicated for Kexi; so it's NOT a generic wrapper for any database client.
+- add "ADVANCED" find dialog: like in Mozilla Advanced Address Book Search window
+
+=== USABILITY TODO ===
+-add "Simple/Advanced Usage" global option
+--for simple usage:
+--use captions everywhere possible, instead of names (tables, fiels, etc.); names should be autogenerated
+
+=== i18n ===
+- plugins/forms/kexiformpart.cpp "Best Fit" instead of "To Fit" (pl=Najlepsze dopasowanie)
+- KexiQueryDesignerGuiEditor::initTableColumns(): i18n("Visible") --> i18n("Column visible", "Visible")
+- add comment for translators: i18n("'empty' is an adjective here", "Empty database")
+-"Could rename table \"%1\" using the same name."
+ --> "Could NOT rename table \"%1\" using the same name."
+
+I am using update_kexi_po to recreate kexi.po file. THis will run cd koffice/ && make -f admin/Makefile.common package-messages
+
+
+=== Import/Export Framework TODO ===
+-KEXIDB: add lower level functions like: "getting table names list", "getting table schema",
+ "getting index schema"
+
+=== Combo box editor TODO ====
+-new layout: a lineedit + dropdown button + popup tableview, when needed
+-use (and add few) methods for tableview to enable adjusting it for use it as dropdown tv
+--add scrolling for mouse dragging
+-dropdown tv has to contain:
+--QSizeGrip at the bottom corner
+-in kexidb: implement combobox settings for the foreign field:
+--ordered set of fields that are visible in popup tableview (ie. just query that is bound with the column)
+-use multi-key relations from kexidb
+- copy should handle "displayed" value, not the real one
+ (maybe let's use a custom clipboard mimetype? (containing a bound value) -- otherwise
+ it will be not posible to paste the copied value)
+- fix support for query as the row source
+- (forms combo) repaint (remove focus rectangle) when other widget is focused using a mouse click
+
+=== Table View TODO ====
+- do not accept when enterind "-" for unsigned numeric types
+- add a functions for moving/cutting/copying/pasting rows
+- extended support for enums (combo boxes)
+- add top-left button that allow select all rows
+- enable clicked vheader button to select given row
+- enable column dragging
+- add action "restore original column order"
+- add possibility of renaming column by 1) dbl-clicking header 2) "rename column" action
+- more actions (as in Format and Insert menus of MSA2k3)
+- add own impl. (based on Qt) for date/time editor because:
+-- no focusSection(), etc. is available in current impl.
+-- frames cannot be easily removed
+-store last sorting column (and type) and load it on KexiTableView::setData()
+ (not it's only cleared)
+- replace KexiDB::RowData usage in KexiTableItem with something faster
+- TODO RELATED TO CELL EDITORS:
+ - signal on situation when user e.g. pressed alpha key when only numeric chars is allowed
+ - signal when null/empty value forcell isn't permitted
+ - signal when repeated value isn't permitted
+ - add support for cut, copy & paste of cell's contents (get shortcuts from KStdAccel)
+HINT: removing Qt::WStaticContents from KexiTableView ctor fixed repaint problem
+ the problem was with repainting when hscrollbar disapperars....
+- KexiTableView loads all data from the table: very slow and consumes a lot of memory:
+ (e.g. for each record with 3 fields: 1 integer, 1 text and 1 float consumed ~350B)
+ - ctors and dtors are WAY TOO SLOW!
+ - add an option: for bigger tables load only part of the table data
+ - optimize MySQL record size
+- add focus rectangles for cells:
+ http://mfc.dundas.com/mfc/grid/index.aspx?section=Grid&body=focusrectangles.htm
+- add more cell types & options:
+ http://mfc.dundas.com/mfc/grid/index.aspx?section=Grid&body=celltypes.htm
+ --including: red corner with a note; sliders; radio buttons
+- highlight sorted column's cells slightly darker, like in current KListView's impl.
+- clicking on a header section (to sort), it ensures current cell is visible, but
+ only y-position should be changed, not x
+- add support for crossed out rows - that can be used for results of DELETE queries
+ (see http://www.gnome-db.org/images/screenshots/mergeant_table_data.png)
+- add combo boxes for fields filtering in column headers,
+ as in http://www.sqlmanager.net/i/scr/mysql/manager/Main%20100.gif
+- display autonumber indicator in autonumbered column(s)
+- reimplement header widget to better show sorting indicators
+- display tooltip over cells that are to small to display entire contents
+- Add icons for conditional formatting: http://blogs.msdn.com/excel/archive/2006/05/09/594200.aspx
+ (displaying icons is already possible using custom properties, BTW)
+- display [null] values in boolean fields, e.g. as [?]
+- allow to select entire rows, columns and grids as in a spreadsheet;
+ offer copy/cut/paste/copy-to-file/clear for such selections
+- tooltips:
+-- implement tooltip manager (this will allow to create custom tooltips)
+-- use if ( QApplication::isEffectEnabled( UI_AnimateTooltip ) == FALSE ||....
+ as in qtooltip.cpp:564
+-- implement custom tooltip for large texts and BLOB, add "zoom" option at the bottom of the tooltip
+ (zooming should not show a modal dialog but rather a widget inside the TV)
+-- add the same zoom option to the context menu
+-- also show tooltips when the content is only partially visible
+- fix horizontal scrollbar's width updating: large tables like 'tabkodypocztowe1' hide the 'last' and 'new' buttons
+- display default values for BLOB types
+
+- add "None" option for editors supporting 3rdState, e.g. int, string (needed for "default"
+ property of the Table Designer)
+- fix repainting cells when moving down/up arrow at the window boundaries
+ (weird, win32 is not affected)
+
+- (e.g. in table designer): in an empty row 1. drop down a type combo. 2. do not select enything an click outside
+ of it. 3.
+- do not autoscroll vertically the contents if the current column is wider than the view
+ (my example: 'microsoft_terminology' table's 1st column) because the text us unreadable
+ valid even when we use arow keys only
+
+- KexiComboBoxTableEdit::createInternalEditor():
+ set d->internalEditor visible and use it to enable data entering by hand
+ (for now only types based that use KexiInputTableEdit (Text, Integer..) allows data entry using keyboard
+
+- display error message on top of table view if data fetching failed
+
+==== ALTER TABLE TODO: =============
+-add a function for editing indices (including multi-field indices)
+ 2 methods: 1) in additional dock 2) in property editor
+-add all missing properties (even when most are not working)
+-add "index" information icon @ the left hand of line with key(s) that is/are indexed
+-in propeditor: auto-sync pkey, unique, required & indexed properties on changing;
+ add message boxes if needed
+-for 'column width' property: add 'default' value at the beginning
+-propose index key (ask for name) on saving tables without index (allow to bypass this message)
+- re-add "tablepart_toggle_pkey" action after shared toggle actions fix:
+--when propeditor is focused, there's a problem with syncing on/off state of toolbar button
+ of "tablepart_toggle_pkey" action
+ --> altertable view doesn't receive the signal about toggling (because it's not focused)
+
+==== Forms TODO ====
+-add "datasheet view" to db-aware forms
+-add [NewFormDefaults] option group and:
+--add "autoTabStop" (bool)
+^^^ implement above so Form::autoAssignTabStops() will be called before form saving
+ and before switching to data mode (this is already performed in FormIO::saveFormToDom().
+- add setting expressions for data-aware widget's dataSource
+- add setting anonymous (not stored) queries as form's dataSource
+- display "Autonumber" string for data-aware widgets
+- implement shared action categories to filter out them in 'assigning actions to push buttons'
+- implement "restore default properties" action for widgets
+- allow form's surface resizing when there's global layout defined
+- fix repainting form's surface boundaries repainting when its size is changed by entering
+ value in the propeditor
+- fix spring behaviour: it cannot be shrinked if its inside a layout
+- KTextEdit doesn't allow to set custom cursor (also in Qt Designer)
+- support for multiple widgets selection in the property editor (requires changes to kexiproperty)
+- handle older and newer formats in FormIO::loadFormFromDom()
+- KAcceleratorManager::setNoAccel(QWidget*) - use it in data mode,
+ define "auto accels" form's property and if it's false
+- find a way to create more meaningfull default names for widgets than button1, button2.
+ Maybe convert widget text (if available) using string2Identifier()?
+ Maybe ask user to enter meaningfull name (+"do not ask this question again" checkbox)?
+ See also http://charlespetzold.com/etc/DoesVisualStudioRotTheMind.html
+- For newer widget types, add information for compatible widgets that
+ can be used instead of them. For best flexibility save this information in a form XML.
+ Example use: We have Kexi version N and N+1. In N+1 there's KexiFrame widget introduced that
+ extends QFrame with more features. Kexi N has no KexiFrame implemented but only QFrame.
+ When KexiFrame witget type has been encountered in the form's XML string,
+ Kexi N it is able to insert QFrame instead of KexiFrame (unsupported properties will be just ignored).
+ The result of substitution may be not accurate, so thing twice before adding such information
+ for a given widget type.
+- add "buttonStyle" property for pushbutton widget with following values:
+-- Button - the deafult
+-- Hiperlink - clickable hiperlink will be displayed instead of button itself
+-- Image - an clickable image will be displayed instead of button itself
+ For this value, aditional properties will make sense:
+--- image, mouseOverImage, mouseDownImage
+-- Thus, we'll have more consistent solution than MSA, which allows to
+ mess the form by setting hiperlinks directly also in Image box and Label widgets
+- add "icon" property for pushbutton
+- For "OnCLick" property:
+-- allow to set hiperlink (and anchor) as value (i.e. so called "target location")
+ (see MSA's hiperlinkaddress, hiperlinksubaddress properties)
+- frame widget:
+-- add such fancy frame: http://www.themaninblue.com/writing/perspective/2004/08/05/
+- tab widget: add color property for setting particular tab
+- add a Scratchpad as in QtDesigner4, allows to drag&drop widgets between forms...
+- Use RESET clause for Q_PROPERTY, eg. RESET unsetPalette for paletteBackgroundColor.
+ Then, use bool QMetaProperty::reset ( QObject * o ) const to reset property value in the propeditor.
+- Autoforms: allow to define groups of fields so the fileds can be displayed with nice groupo boxes.
+ (e.g. http://www.glom.org/screenshots/glom_data_details.png)
+ This definition shgould be made during table design, so we can reuse groupping information also in
+ autoreports and auto web forms. Especially usable for tables with lots of fields.
+ We could even define the groupping levels so top-level groups can be splitted using Tab Wigget.
+- fix support for blob visible values for the combo box widget
+
+==== Property Editor TODO: =========
+- text does not fit well inside combobox editor
+- double editor should be klineedit with doublevalidator, not spinbox
+- add a hint for propertybuffer to allow properties to be displayed side-by-side
+ e.g. width, height properties ca be displayed this way. See also property editor
+ at http://freshmeat.net/screenshots/41728/
+-KexiProperty: make it really shared (using QShared?)
+-for each row (property) allow to define double-click-action dependent on property type,
+ * e.g. bool editor: toggle true/false,
+ * enum editor: select next value (circularly),
+ * fire selection dialog for filename editor, color editor, pixmap editor, etc.
+ * expand children for cells of type like "Rect"
+ * other types- by default: just move cursor to editor
+- add support for "set" types (e.g. AlignTop|AlignBottom) and port form's objpropertybuffer to this
+- add "description" label on the bottom of the property editor;
+ it should reuse Property::description() text
+- support Variant type (needs update in Editor) so Property::setValue() won't show a "INCOMPATIBLE TYPES!" warning
+ (needed e.g. by KexiTableDesignerView for "defaultValue" property)
+
+==== Query Designer TODO: ======
+- add tables dropping from the browser
+- add a dialog for inserting multiple tables at a time
+- fix connections drawing
+- make "1" and "inf" signs a bit larger (scalable)
+- 'totals' are unused yet
+- update query after change in Connection cache
+- for queries like 'select * from a, b, c' allow to enter * in 'table' column
+--this should also work when we're recreating field rows on switching from SQL view to design view
+- query part: react on KexiProject::aboutItemDelete() and KexiProject::aboutItemRename() signals
+- query parameters:
+-- use a special dialog with validators compatible with the field type for getting query parameters
+ (currently KInputDialog is used)
+-- use dialog like KInputDialog::getItemList() for supporting multiple values as query parameters
+- support add "PARAMETERS [prompt1] datatype1, [prompt2] datatype2;" syntax to the parser,
+ and add query "Query parameters" dialog/pane, allowing to order parameters that user should
+ provide (Ks. eksp., p. 73)
+- ADDINs: a tree dialog showing query dependencies, and query templates like these http://www.4tops.com/query_tree.htm
+
+==== SQL Editor TODO: =====
+- intergrate editor's (KPart) actions with KexiMainWindow's Actions
+- win32: katepart on win32: void KateSyntaxDocument::setupModeList (bool force)
+ Works slowly for the 1st time because there is no cache.
+ Workaround: removed most of apps/katepart/syntax/*.xml files
+- win32: fix freeze on 1st char entering
+- win32: fix mmap for win9x (InterlockedCompareExchange() replacement is needed
+ -> see kdelibs/win/mmap.c)
+- call qApp->processEvents() on highlighting schema loading,
+ so 'wait' cursor can be visible on 1st loading.
+- allow saving invalid queries (KexiQueryDesignerSQLView::storeNewData())
+ (for invelid queries, after opening SQL view should be shown automatically)
+- allow to copy error message (e.g. using a copy button)
+ + allow to select message text (use active label?)
+
+==== Main window TODO ====
+- propeditor dock window is hidden when a window in data view mode is active
+ (or no window is present). This feature is partially disabled
+ (using PROPEDITOR_VISIBILITY_CHANGES), though,
+ --REENABLE when blinking and dock width changes will be removed in KMDI
+- add sorting projects information using KexiProjectData::lastOpened
+- also add this info as "Database>Open Recent" menu subentry
+- display errors when plugin library could not be instantiated due to a broken lib
+- Project Navigator: display multiline item names is needed
+- display progress bar (in the statusbar?) to indicate there're pending jobs
+- enlarge the default main window size, currently it's ~50% ofthe desktop size
+ - not convenient as user is forced to enlarge the window by hand (settings are stored though)
+- add "File->Save All" action
+- add settings dialog, KConfigXT based simple version
+ + about:config-like using kconfigeditor http://extragear.kde.org/apps/kconfigeditor/
+ (possible reuse of koproperty)
+
+TODO: add SharedAction::setVisible() and use QMenuData::setItemVisible() where needed
+
+CCPASTEUR:
+ -perhaps we'd reuse KDE Menu editor GUI?
+
+=== KexiDB TODO ===
+- add to field's properties list: input mask, validation rule, column width
+- improve Expression class, add code for generating expression
+ strings in Connection::queryStatement()
+- add dynamic resize for dict members like Connection::m_tables
+- make KexiDB really ASYNC, multithreaded!
+- implement relationships (foreign key information) between tables + storage
+-- Support tree standard types of integrity rules as described at http://allenbrowne.com/ser-64.html
+ plus: Cascade to Null Relations
+- implement GROUP BY clause + storage
+- add schema to kexi__* tables on createTable()
+- add support for creation temporary databases
+- look at Connection::isReadOnly() to see if we can perform certain operations
+ requiring write access (create db, update db props, etc.)
+- reuse ConnectionData::useLocalSocketFile for connections using socket file
+- Connection::alterTableName():
+--alter table name for server DB backends!
+--what about objects (queries/forms) that use old name?
+- PGSQL: use setval(sequence) to allow setting autonumber values by hand
+ http://www.postgresql.org/docs/7.4/interactive/functions-sequence.html
+- optionally, introduce EDITABLE_AUTONUMBER_FIELDS driver flag
+PiggZ_ js: and to ensure you always have id 1 and 2, well.....they would be anyway at db creation time as sequence = 0
+ js PiggZ_: but users ofter want to enter it anyway
+ js eg. sqlite increments its autonumber to 1001 if you enter 1000
+ js we want nothing more that that
+ js s/than that
+ PiggZ_ personally, i never want to enter a value into autonumber.....autonumber usually = pkey, so its best to let the db handle it
+ js If use want strict sequences to be used, he can:
+ js 1) hide autonumber column at all
+ js 2) set it not-editable
+ js 3) or just do not touch this column
+ PiggZ_ well then, its probably best to set the value of the sequence if a value is manually entered...if possible
+
+- add an QAsciiDict of (reserved) SQL keywords and use it in Driver::escapeIdentifier()
+- add FieldType and move things like typeGroupString() there
+- CRASH: removing tables when a query still uses it.
+ HOW TO FIX: Add TableSchema::Ptr, QuerySchema::Ptr, etc.
+-- related to above: add a possibility to return all objects (and optionally all opened objects)
+ thad depend on a given table
+-- we could also add a GUI tree to show these dependencies
+- add permissions information to connection : and - for sqlite - check if file is RO
+ and then set RO flag
+- add kexi__toremove table, and use it to remove objects on begin connection
+- [Kexi 0.2?] sometimes a user has got only one database availaliable on a server
+ (eg. when server is commercially hosted). Somethime user isn't just able
+ to create database because of insuffficient privileges.
+ Solutions --> add a possibility of sharing multiple projects within the same database
+ * kexi__* tables will be shared
+ * prefixes can be needed for table names (add option to set or unset this)
+ * prefixes will be hidden (stripped out) by KexiDB from real names, eg. 'mydb_mytable' -> 'mytable'
+ * let's set default prefix equal to database name
+ * within a group of "projects sharing the same databse" it will be easier to:
+ ** mount (attach) external projects to an opened Kexi project
+ ** run queries using tables from many projects
+ * disadvantages:
+ ** decreased modularity and data encapsulation
+ ** possible problems with transactions and user access rights
+ ** custom table names can look weird outside Kexi
+ ** the feature adds another level of complexity to KexiDB
+-define "login timeout" setting (in seconds) so connecting can rely on this value.
+ The setting could be assigned to a given connection data,
+ but could be also defined globally for the application.
+ (note: db:login-timeout is defined by opendocument spec.)
+- take a look at agrep: http://www.tgries.de/agrep/ -- looks loke it manages to convert
+ non latin1 characters to latin1, eg. '' to 'a':
+ "* option -ia searches case-insensitive
+ ISO characters are mapped to the nearest lower ASCII equivalent."
+- support for COMPOUND KEYS, ie. keys with 2+ fields; http://en.wikipedia.org/wiki/Compound_key
+- OPTIMIZATION: use PREPARE and EXECUTE statements for some backends, eg. for pgsql:
+ (excerpt from http://www.postgresql.org/docs/8.0/interactive/sql-prepare.html)
+ 1. Create a prepared query for an INSERT statement, and then execute it:
+ PREPARE fooplan (int, text, bool, numeric) AS
+ INSERT INTO foo VALUES($1, $2, $3, $4);
+ EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00);
+ 2. Create a prepared query for a SELECT statement, and then execute it:
+ PREPARE usrrptplan (int, date) AS
+ SELECT * FROM users u, logs l WHERE u.usrid=$1 AND u.usrid=l.usrid
+ AND l.date = $2;
+ EXECUTE usrrptplan(1, current_date);
+ 3. For completnedd, provide KexiDB API for "DEALLOCATE" as well
+- Implement "copy table" feature.
+ To copy table t1 as t2 with data use: "create table t2 as select * from t1"
+ To copy table t1 as t2 without data use: "create table t2 as select * from t1 limit 0"
+ NOTE: pkeys, triggers, default values are not copied?
+
+- BLOBs in MySQL: max_allowed_packet is 1MiB; read http://dev.mysql.com/doc/mysql/en/blob.html
+ and http://dev.mysql.com/doc/mysql/en/server-parameters.html
+-- http://bugs.mysql.com/bug.php?id=1605 "MySQL protocol (it does not send 'chunked streams'
+for blobs...The client has to read the _entire_ blob into memory."; use:
+ "select substring(document, 1, 10240) from documents where docid='3'" this to get chunks:
+ "select substring(document, 10241, 10240) from documents where docid='3'"
+ ....
+-- Other approach: store binary data using two tables 1st for metainfo about the data,
+ 2nd for data chunks (e.g. 64KiB each): http://php.dreamwerx.net/forums/viewtopic.php?t=6
+
+????? is it possible to just use a database and store the kexi stuff in a .kexi file?
+ jstaniek ruurd: you just dont want to create kexi__* tables on your database?
+ ruurd yes, and use an existing database.
+ jstaniek ruurd: that's planned but note:
+ jstaniek this option doesn't offer you atomic changes to database schema, by definition, because two database conenctions are started concurrently.
+ jstaniek So we'll need to behave with care...
+ jstaniek ruurd: of course this is easier to do with read only databases or even databases where you don't plan to change schema (but just process db records)
+- add Connection::ping() or so, using http://dev.mysql.com/doc/mysql/en/mysql-ping.html
+ and use it after being idle for a while (eg. call it every minute; also add an option for the delay)
+- Move ObjectStatus to kexidb. Merge with Object.
+- Support for opening .sql files: just import it on opening to in-memory database and dump it back into
+ .sql text on exisitng. Add optional compressing.
+- Add support for compressed kexi files (better keep the same .kexi extension instead of .kexiz)
+- Implement tree structures using idea #5 described here http://www.depesz.com/various-sqltrees.php
+ (does pgsql require this hack?)
+- double values are still rounded: consider storing them in memory as a decimal type
+ (e.g. using a special Q_LLONG+decimalplace class); needed e.g. in KexiQueryParameters::getParameters()
+- handle input mask using a special KexiDB::FieldInputMask class
+ --needed in forms (KexiDBLineEdit::setColumnInfo()) and table views
+- add setValue() to cursors: this will REQUIRE a buffered cursor, since we cannot run sql
+ before fetching is done... See http://kexi-project.org/cgi-bin/irclogger_log/kexi?date=2006-08-03,Thu&sel=229#l225
+- use ThreadWeaver library for threaded KexiDB version
+- tables can use queries as a row source for lookup fields, what can lead to infinite recursion;
+ FIX this by either: 1) not allowing to use such table in a query (see kexi/to_fix/Ksiazka_adresowa2_recursive_query_deadlock.kexi)
+ or 2) delayed loading of query column
+
+=== KexiDB Parser TODO ===
+- add a method for replacing a single given table name in the statement (useful on table renames)
+- add clever query relatins parsing
+ (needed for switching back to GUI editor from text mode)
+- add flexible support for date/time constants
+- store text position information (line, column) for every token so it can be used
+ in SQL editor to place cursor in case of errors
+- add translation method for SQL operators to driver,
+ so SQLite can return "CONCAT" and mysql return "||" for concatenation oeprator, and so on
+- improvement in terms of data recovery:
+ assume you're entering a long record and db connection is dead before saving...
+ the record could be buffered locally before closing Kexi application... and on next startup, re-sent.
+ This can work in simple cases (when complex transactions are not involved).
+- report "ambiguous field name 'id'" error for ambiguous queries like "SELECT a.id, b.id FROM a, b ORDER BY id"
+- add types checking to **Expr::validate()
+- consider switching from bison/flex to http://www.antlr.org/
+- make parser reentrant (for now we've used a workaround)
+
+=== KexiDB MySQL Driver TODO ===
+- use InnoDB instead of MyISAM tables because of transactions support
+ http://dev.mysql.com/doc/refman/5.0/en/ansi-diff-transactions.html
+ Kexi's MySQL driver uses MyISAM engine, but transaction support needs InnoDB
+ (there should be also an option available to set on CREATE TABLE, and on an default for CREATE DATABASE)
+
+=== KexiDB PostgreSQL Driver TODO ===
+- port to libpq
+- set version information in drv_useDatabase()
+ There's connection_base::server_version() in libpqxx trunk -
+ http://thaiopensource.org/development/libpqxx/file/trunk/include/pqxx/connection_base.hxx
+
+=== KEXIDB TESTS===
+- add tests for DatabaseProperties
+- add tests for PreparedStatement
+- add tests for migration
+
+=== Startup TODO ===
+- reuse KexiNameWidget in KexiNewProjectWizard!
+- use KPasswordDialog::disableCoreDumps() for security reasons
+- IDEA for connection shortcuts:
+ Mysql Query Browser has a simple xml file defined:
+ http://dev.mysql.com/doc/query-browser/en/mysql-gui-appendix-store-connections.html
+- allow to choose if the file should be locked (opened in EXCLUSIVE mode) or not
+ (THIS SHOULD BE DEFAULT BEHAVIOUR)
+- (linux) remove stupid message 'Please select the file to open'
+ when opening exisiting server project and clicking 'OK'
+- database files accessed remotely: make it work without copying a file to temp. dir.
+ (maybe even without copying entire file? - patching sqlite required)
+- add "Open read only" checkbox to the file browser
+ (Driver::ReadOnlyConnection option already allows to open in read only mode)
+
+=== CLI TODO ===
+- add kexicmd as a symlink to kexi binary, for totally non-gui usage
+- just drop all tables when database already exist
+ (so recreating database will be available even
+ if you have only access to one database).
+- GRANT privileges for non-root (current?) user for newly created db
+
+=== Mimetypes/icons TODO ===
+- new mimetypes:
+ application/x-vnd.kexi.project.sqlite2
+ application/x-vnd.kexi.project.sqlite3
+ application/x-vnd.kexi.project.shortcut
+ other:
+ application/x-vnd.kexi.connections.shortcut
+ application/x-vnd.kexi.table.shortcut
+ application/x-vnd.kexi.query.shortcut
+- remove x-vnd.kexi.desktop in kdelibs
+- copy mimetypes and magic def. to kdelibs (keep it within Kexi for compat.)
+- remake kexi mime type svg icons
+- copy kexi mime icons to kdelibs, remove old one
+** Ask SQLite devs for a possibility for adding a field to indicate kexi-sqlite
+ format without opening database
+
+
+=== Docs TODO ===
+
+-translate the current content from polish to english
+-?? migrate english content to wiki format
+-move polish content to .po translation file
+-Add this to the preface http://encyklopedia.helion.pl/index.php/Kartotekowa_baza_danych
+-www: mention what features are already available and what is planned in terms of:
+-- general features, example: http://www.e-cen.pl/index.php?co=wiecej&id=26&dzial=8
+-- scripting, example: http://www.e-cen.pl/index.php?co=wiecej&id=22&dzial=6
+- add chapters for:
+-- simple printouts
+-- "assign action" to a button
+-- CSV import/export/copy/paste (csv import is explained on kexi@kde.org ML; 2007-03-23)
+
+=== 2005 PRO TODO ===
+-greatly improve sql parser
+-greatly improve sql gui designer
+--totals (sum, avg)
+-more docs
+-more widgets for db-aware forms
+-add simple reporting
+-finish server support
+--connection creator
+--fix permission problems
+
+
+=== after-0.9 TODO ===
+TODO: add time zones support: 1994-11-05T08:15:30-05:00
+TODO: update to sqlite3.1 and use it's features http://www.sqlite.org/releasenotes310.html
+TODO: 0.2: reuse http://segfault.is-a-geek.net:8001/feedback-kinitiator/
+ gfx can be found here: http://cvs.sourceforge.net/viewcvs.py/kinitiator/
+
+TODO: look at recent Martin's stuff: http://homepages.cs.ncl.ac.uk/m.a.ellis/kexi/
+
+
+Core TODO:
+- update shared actons availability ONLY AFTER we've switched to other kexidialog base,
+ not just on LOST FOCUS
+- for now: clicking on empty menu bar's item (eg. "Format") causes to losing shared actions state
+- make core/ non-gui lib. Move data creation code out of KexiDialogBase.
+
+Reports TODO
+- reuse most of KexiFormPart code in KexiReportPart (eg. creating shared actions)
+- line widget should use special resize handle set -- with only two rectangles (Forms too)
+
+KMDI TODO
+- do not execute KMdiMainFrm::switchOffMaximizeModeForMenu() when true==QApplication::closingDown()
+
+QUICK TODO:
+Forms:
+-change form's object caption to "caption" property, when changed and conversely!
+
+TODO:::::::
+1. test dipesh.kexi - ID column is not used on row updating!!!
+
+2. dipesh np, I also found a crasher witch is maybe related.
+ dipesh just change a value, go to design mode and switch back to dataview...
+ dipesh [KCrash handler]
+ dipesh #3 0xb59b156f in KexiFormScrollView::createEditor ()
+ dipesh from /usr/lib/kde3/kexihandler_form.so
+ dipesh #4 0xb7e5f502 in KexiDataAwareObjectInterface::startEditCurrentCell ()
+ dipesh from /usr/lib/libkexidatatable.so.0
+ dipesh #5 0xb59b276a in KexiFormScrollView::valueChanged ()
+ dipesh from /usr/lib/kde3/kexihandler_form.so
+ dipesh I look where the crasher is coming from.
+ dipesh oh, in KexiFormScrollView::createEditor
+ dipesh column( col ) returns NULL
+ jstaniek dipesh: it's only within your example project...
+ dipesh therefore column( col )->readOnly() crashes...
+
+reuse KWallet http://events.kde.org/info/kastle/presentations/kwallet-kastle-2003.ps
+
+=== Interesting features ===
+- Add random names/surnames generator: http://www.ruf.rice.edu/~pound/
+ Good for generating good-looking random data for a given table.
+-- Autodetect 'name' columns or ask user to describe what he wants.
+ Comply with data validation rules.
+-- use famous 'Lorem ipsum dolor sit amet...' test text for longtext types
+
+
+=== QUICK TODO ===
+
+TODO: in KexiMainWindowImpl::slotProjectOpen() add support for starting new kexi instance for selected conn data
+ (we need to remember from what filename has been a conndata loaded)
+TODO: move ConnectionData::savePassword to QMap<ConnectionData*,bool> KexiDBConnectionSet::savePasswordMap or so
+TODO: Forms: fix label width resizing when entering text within inline editor
+TODO: Forms copy more properties (like palette) to inline editor from e.g. text label
+TODO: Forms: fix: double clicking unselected widget doesn't switch us to inline editing mode
+TODO: Forms: object tree: enable sorting by name after clicking "name" header;
+ enable sorting by class,name by clicking "class" header
+
+Startup TODO:
+- when using a startup item, touch the file for better sorting:
+http://www.koders.com/c/fid97791600CA5D0A2EA559490BD9444B0775B2370A.aspx
+
+- add "Save Special->Shortcut file for this project" action
+- add "Save Shortcut file for this connection" action
+- FOR SLOW CONNECTIONS: show progress bar on:
+-- retrieving a list of projects (in KexiProjectSelectorDialog)
+-- opening a project (in status bar?)
+- Also, add "cancel" button near the progress bar (reuse the one from KMail?)
+
+
+TODO: If a form has no focus, FormManager::windowChanged() and KexiFormView::setFocusInternal()
+ don't work properly (setFocus() is not available...)
+
+FORMS:
+-Image Box:
+--pasting large images makes chooser button invisible; maybe place it on top of the widget?
+--set better default for focusPolicy, implement setFocus()
+--add filter property (using KImageEffect)
+--make image-related actions undoable/redoable in design time
+--add 'print' action
+--add for movies/animations support using QMovie to KexiDBImageBox
+ or (most probably) inherit KexiDBImageBox as KexiDBMovieBox
+---KexiBLOBBuffer::pixmap() shouldn't be used but data() should be used to create QMovie object
+ in each movie widget itself because even if the same QMovie was used in many movie widgets:
+ a) speeds can vary between movie widgets, b) different forms can be opended in different moments
+ so playing starts in different moments, c) one or more movies can be stopped while others not
+---add following related properties: int playbackSpeed (in frames per second, 0==default),
+ bool autoStart (true==default), playbackControls (false==default), bool loopPlayback (true==default)
+ bool pauseAfterEnd (0==nothing==default)
+-property pane: shrink widget name displayed at the top by adding "..." to avoid problems
+ with pane's size when selected widget has very long name
+- resize handles are always black: this is bad on black background!
+- use KURLLabel as an option for push button widget
+
+
+Combo Box Editor
+- ADD "restrict to list" OPTION: don't copy 1st row's value if there's no value in the combo
+ --> needed for 1st column of Query Designer
+
+KEXIDB:
+- ConnectionData: make local sockets default for unix, instead of tcp/ip
+
+GENERAL:
+- removed conflict between standalone and KOffice version by:
+-- inserting "standalone" word into module names,
+ eg. kexidb_mysqldriver.so -> kexidb_mysqldriver_standalone.so
+-- renaming kexi to kexi-standalone
+- rename "data view mode" to "browse view mode" (as in Filemaker)
+- add "File->Mail..." action (requires closing and copying of the sqlite file, if locked)
+
+MARKETING:
+- add "Created With Kexi" page and a link to it from k-p.org and kexi.pl
+- when releasing new Kexi version publish: A first look at Kexi x.y" page, like this:
+ http://software.newsforge.com/article.pl?sid=05/09/28/1345228
+- short reasoning for using db abstraction layers:
+ http://developers.slashdot.org/comments.pl?sid=164824&cid=13755151
+
+
+ACCESIBILITY - Mouseless Operation Problems (KOffice 1.4.1):
+- MORE: http://accessibility.kde.org/reports/koffice-1.4.1-accessibility-assessment.pdf
+- It is not possible to select the data type of a
+field in the table editor. Alt+DownArrow does not work.
+- When editing the fields of a table, it is not
+obvious how to flag a field as a primary key.
+WORKAROUND: Use Popup Context Menu (Menu key) and select Primary Key, OR use Edit | Primary
+Key on the main menu, OR use Property Editor.
+- In the Property Editor, is not obvious how to
+change a boolean property. Alt+DownArrow does not work.
+- It is not possible to change the value of a
+property in the property editor that has a picklist of possible values, for example,
+SubType. Alt+DownArrow does not work.
+- When creating a query, it is not possible to add a table to the query.
+- Not possible to select a column, table,
+totals in Query Columns panel. Alt+DownArrow does not work.
+- It is not possible to add a widget to a form.
+- Pressing Tab key while focus is in the Form
+Editor window, but not on any widgets on the form, crashes Kexi.
+- It is not possible to change the size of a
+widget in the form editor without the mouse.
+
+TODO:
+- update conn dialog gui
+example: http://www.ntpb.co.uk/kexi/msn.png
+
+
+Migration (Import Project Wizard) TODO:
+- change drivers comboboxes to KexiDBDriverList
+ (impl. this one with drv name and description columns)
+- close currently opened file-based project if it's the same as imported one
+ (do not save anything)
+- 'select source db type' page: change "sqlite/msa/mysql/pg" to "sqlite/msa/server database"
+ because type of server db is in fact selected on the next page (source conn selection)
+- set the same path for "destination" file browser as for "source" one
+- add "do not create system tables" option to the wizard
+- reuse the migration framework to support "offline mode" - for instance usable while moving with
+ out of network with a laptop, by copying the relevant remote tables to a local temp db;
+ this is already implemnted e.g. in MSA 2k7:
+ http://blogs.msdn.com/access/archive/2006/10/13/sharepoint-apps-offline-and-intro-to-sharepoint-designer.aspx
+ coming back online requeres data merges what is not easy as resolving potential conflict may be needed
+ (example: http://clintc.officeisp.net/Blogs/2006/40%20-%20SharePoint%20Offline/10%20-%20Conflict%20Resolution%20UI.JPG)
+
+MDB Migration:
+- add checks for "no space left on device" error (otherwise Kexi will crash)
+- fix currency converting
+- use MSysRelationdhips table to read MSA db relationships (easy, it's not encoded)
+
+=== Kexi Web Site TODO ===
+- move to mediawiki (JJ?)
+- Add "Features" page like this http://www.agata.org.br/us/index.php?file=features.php
+- Extend "Credits" page with Gold Users/Translators, etc. like here:
+ http://www.agata.org.br/us/index.php?file=thanks.php
+
+AUTOFIELD:
+- copy/paste doesn't preserve caption text (maybe caption should be copied
+ to from QueryColumnInfo to a property)
+
+KexiFrame:
+- fix highlight for data mode! (very old bug)
+
+Migration:
+- fix: server as destination is broken
+
+CSV Import TODO:
+- switch from dialog to wizard; add filedlg widget as the 1st page
+- add better detection for CSV data with \t delimiters (needed for pasting from spreadsheets)
+- add option for storing import settings
+- always test using e.g. this file: http://cvs.sourceforge.net/viewcvs.py/*checkout*/wcuniverse/priv/units/units.csv
+- change boolean "First row contains column names" to 3-value combo:
+ "Get column names:" ["None", "From the first row", "From the first imported row"]
+- add "skip empty columns" and "skip empty rows" option
+- (advanced) there can be a column that is a result of exporting combo box visible values;
+ in this case add option for normalizing the table using existing (or new) lookup table
+- add clear message when :
+-- primary key cannot be set because of non-unique values
+-- number cannot be set because non-number values
+-- etc.
+- add "fix non-unique valuesID /add missing ID values" option for PK column
+- add option for skipping particular columns
+- add option for filtering import results afterwards:
+-- to limit number of rows stored in db and
+-- to process columns using expressions
+
+CSV Export TODO:
+- add progress bar and wizard
+- add "Include row count as first column - include row number in the first
+ column of each row" option
+- add "Include column types on top" option
+- add "Fixed columns width" option (can be extended to support "fixed width text" format)
+- add "Set text to (null) on NULL value - use the string "(null)" to represent
+ a NULL value in the document" option
+- add "Replace empty/Null fields with..." option
+- add "Lines terminated with.." option (\n or \r\n or \r)
+- add option for compression (gzip, zip, bzip2...)
+- MORE TODOs here: http://www.aquafold.com/docs-qw-save-results.html
+- by default, use delimiter=\t and quote=none for clipboard (needed for copying to spreadsheets)
+- add "Strip leading and trailing blanks off of text values" option
+- deal with lookup fields: for every such field
+-- add option for exporting lookup tables as separate files
+-- add option for exporting a) both visible values and indices b) indices only c) visible values only
+ -for now c) is supported
+
+XML Export TODO:
+- consider export to at least these three formats: http://csv2xml.sourceforge.net/xmlmodes.html
+ (templates/XSLT could be also supported)
+
+General TODO:
+- handle KApplication::shutDown()
+
+TODO:
+- on opening detect whether a table exists (empty table view should not be displayed)
+- Another Kexi instance started from KexiMainWindow using QProcess freezes after opening
+ 3rd table or so. Move to KProcess on Linux.
+
+TODO:
+- if you place e.g. a label in a container like a tabwidget and doubleclick it to edit the content,
+ the label jumps around. ccpasteur said he fixed it last year ;)
+- only change bg color of tab widget, not its outer area; fix changing tabs bg color
+
+Import
+- error "BLOB/TEXT column 'isbn' used in key specification without a key length"
+ when importing books.mdb into a mysql db
+
+Forms:
+- after setting "auto" tab order, we need to close and open the form to get tab stops to work properly
+
+TODO:
+- use KLineEdit::displayText() to get partially filled date or time values like 20__-__-__;
+ then use this for checking data validity (?)
+
+TODOs from Jeff Denman:
+- add command line option for performing a complete database import, especially for MSA files
+- instead of always displaying "object names" in the project navigator:
+ display "captions" but offer an option to display "names" instead;
+ AND, optionally: offer a full-width window with the list of database objects
+- import bug for MSA: no values for this field after import:
+ Field Size: Decimal
+ Precision: 10
+ Scale: 2
+ Decimal Places: Auto
+
+TODO Data Types
+-Currency
+ MS Access: Currency values and numeric data used in mathematical calculations involving data
+ with one to four decimal places. Accurate to 15 digits on the left side of the decimal separator
+ and to 4 digits on the right side.
+-Decimal Number (NUMERIC?)
+ MS Access: Decimal Stores numbers from -10^381 through 10^381 (.adp),
+ from -10^281 through 10^281 (.mdb); decimal precision: 28 B, storage size: 12 B
+
+
+TODO Migration from Native SQLite3
+- use sqlite3_column_decltype() http://sqlite.org/capi3ref.html#sqlite3_column_decltype
+ and sqlite3_column_name() to know column names and types.
+ Add reasonable case-insensitive mappings like "INTEGER|integer|INT|int" -> [integer]
+ - or: for import data from native-sqlite databases: use "pragma table_info(tablename)"
+ -- maybe also use "pragma table_info" for sanity checking, or more in kexidb driver??
+- use "pragma user_version={32bit int}" (http://www.sqlite.org/pragma.html) to set, say, kexidb version.
+ This information is stored in a fixed place in the sqlite3 header, so it's possible to read it using
+ KDE Mime Type system (a magic data defined in share/mimelnk/magic).
+
+MDB Import
+- FIXME sometimes order of imported fields is invalid: PKEY field jumps to end.. (books2.mdb)
+
+TODO: table view
+- for FP numbers: allow to start entering value from "." or ","
+
+Forms TODO
+- add "navigationCaption" property to table and form, so "Records:" Label in the record nav. becomes e.g. "Bananas:"
+ see: http://blogs.msdn.com/thirdoffive/archive/2006/04/06/560454.aspx
+
+Forms TODO
+- Checkbox widget with focus policy set to Tab has problems with KexiDataAwareObjectInterface::acceptEditor()
+ because m_editor is set to _currently focused_ widget (problem where there are e.g. >1 checkboxes)
+ Partially fixed by setting StrongFocus policy as default;
+ FIX THIS for other focus policies too...
+
+
+KDElibs4 TODO:
+- wizard
+- do not require .la in libltdl
+
+Kexi 2.0 TODOs:
+- decrease # of shared libs, see Clarification at the bottom
+ of http://people.redhat.com/drepper/no_static_linking.html
+
+== April 1 2007 TODO ==
+- show the screenshot of kexi displaying yes/no/maybe-dialogs, as for the "Woman edition"
+
+TODO:
+Kexi >2.0 (probably late 2007): Add SQLite >3.3 driver. Offer exporting similar to the one between 2.8->3.0.
+ Think about backporting the >3.3 driver to Kexi 1.1.
+
+
+
+- for table t1(a,b,c) delete column t1.c:
+
+CREATE TABLE t1_new(a,b);
+INSERT INTO t1_new SELECT a,b FROM t1;
+DROP TABLE t1;
+ALTER TABLE t1_new RENAME TO t1;
+
+- for table t1(a,b,c) rename column t1.c to t1.d:
+
+CREATE TABLE t1_new(a,b,d);
+INSERT INTO t1_new SELECT a,b,c FROM t1;
+DROP TABLE t1;
+ALTER TABLE t1_new RENAME TO t1;
+
+
+== Forms TODO ==
+- enable edit->copy, actions, etc. for Data View
+- ImageBox: show a tootip with large image and its name if the image was cropped
+ or only its name if the image is not cropped
+- AutoField: highlighting the label when the buddy is focused
+- AutoField: handle label editing after double clicking
+- AutoField: draw required field in bold (or optionally add (*))
+- display default values in other widgets showing text
+- new action for button widgets: open table/query
+ +with query parameters taken from a line edit
+- provide "Edit->Clear Table Contents" action in forms too
+- autofield: after setting form's data source to a valid value,
+ "unbound" mark should disappear in design time
+- rename "Source field" to "Widget's data source" in the data source pane
+- Designer bug: "Click a container widget (or a form surface itself),
+ where widgets are inserted and select one of the layout
+ types from the context menu item Layout Widgets. "
+ "I tried this with the Frame, the Group box and the Tab
+ widget, the menu item Layout Widgets is not available. You
+ have to select the widgets inserted in the contrainer widget
+ to make the menu item available. "
+- unhide "Editor type" property for auto fields (it's hidden in 1.1 due to a crash)
+
+TODO:
+- inform Raphael about koffice.org/1.5/ url, give him the php code
+
+Table Designer TODO
+- i18n("Lookup") == Odnosnik (Ks. eksperta, s. 53)
+- add setting for default length of the text type
+- allow to set default values for BLOB types (requires a change in KoProperty as well)
+- 2.0: Add drag-and-drop-to-create-lookup-column to the "lookup column" tab of the property pane.
+ This should be implemented by displaying a list of fields and allowing to drop one into the
+ table view. Then, lookup column properties should be autoconfigured.
+- lookup column: support these properties of LookupFieldSchema:
+ columnWidths(), columnHeadersVisible(), maximumListRows(), limitToList(), displayWidget()
+- show warning if there are no bound and visible columns defined (for now, this is just ignored
+ and user cannot select a value from the list in the data view)
+
+=== Simple Printouts TODO ===
+- cell contents can be too large for a single page - split it to man pages if needed (true for large texts, blobs..)
+- add support for BLOBs
+- fix printing and previewing for horizontal arragement
+
+
+Form/TV Shortcuts:
+
++To move to the record number box (record number box: A small box that displays the current record number
+ in the lower-left corner in Datasheet view and Form view. To move to a specific record, you can type
+ the record number in the box, and press ENTER.); then type the record number and press ENTER
+ F5
++ Add moving to next record when Tab is pressed at the last field
++ Add moving to prev record when BackTab is pressed at the 1st field
+
+
+== TODOs for 1.1.2 ==
+- support combo box within the autofield widget
+(done?): notify and update data source after schema changes
+- todo: fix things like SELECT *, cars.owner AS ab, 1.3 AS wyr1, persons.surname FROM cars LEFT OUTER JOIN persons ON cars.owner=persons.id WHERE cars.owner > 3
+ (crash - see Simple_Database.kexi - persons_and_cars query has lookup fields and OUTER JOIN is NOT PARSED
+ -- we should generate the SQL in a different way for the Query Designer)
+- fix horizontal scrollbar hiding in the "Available fields" list box (a problem with layouts?)
+- display a message when connection is lost...
+
+
+TODO:
+- fix crashes when table schema referenced by a combo box changes
+- replace QDate{Time}Edit in koproperty with KLineEdit, similar to the one in KexiTableView
+
+TODO: "Database "z3" created but could not be closed after creation." err. on win32 after creating a pgsql db
+TODO: PqxxMigrate::drv_copyTable(): we've switched from BLOBs to LongText
+ ==KexiDB::Field::LongText part shoud be removed; add other backward-copatibility code (how?)
+ if (fieldsExpanded.at(index)->field->type()==KexiDB::Field::BLOB || fieldsExpanded.at(index)->field->type()==KexiDB::Field::LongText)
+
+
+Startup templates
+- TODO: : use main/startup/TemplateItem.ui and main/startup/TemplateWidget.h
+ http://lxr.kde.org/source/KDE/kdebase/kicker/kicker/ui/addapplet.cpp?v=3.5-branch#188
+- TODO look at schemas at http://www.databaseanswers.org/data_models/index.htm
+
+Find/Replace REPLACE
+- use this for replace: virtual bool columnEditable(int col);
diff --git a/kexi/doc/dev/advantages.txt b/kexi/doc/dev/advantages.txt
new file mode 100644
index 000000000..31e156076
--- /dev/null
+++ b/kexi/doc/dev/advantages.txt
@@ -0,0 +1,17 @@
+---------------
+Kexi Advantages
+---------------
+
+I. Over MS Access
+
+1. Data Entry
+1.1. Autonumber fields
+Autonumber fields are editable. On a new record inserting, you can skip entering a value in autonumber field but you can also enter any (unique) value on your own (unavailable in MSA). For existing records, you can also change the number in the field (unavailable in MSA).
+
+Automatically generated values for autonumber fields are only generated on new row inserting. If you cancel row inserting, no value will be generated. This allows you to keep counting consistent. In MSA autonumber's counter (sequence) is increased just when you start entering data of a new record.
+
+
+2. Data presentation
+2.1. Tabular data view
+Sorting by selected column is also available by simply clicking on the column.
+
diff --git a/kexi/doc/dev/alter_table_type_conversions.ods b/kexi/doc/dev/alter_table_type_conversions.ods
new file mode 100644
index 000000000..5719d5b13
--- /dev/null
+++ b/kexi/doc/dev/alter_table_type_conversions.ods
Binary files differ
diff --git a/kexi/doc/dev/auto_update_service.txt b/kexi/doc/dev/auto_update_service.txt
new file mode 100644
index 000000000..eb9d67348
--- /dev/null
+++ b/kexi/doc/dev/auto_update_service.txt
@@ -0,0 +1,29 @@
+Installation / Update Service for QKW / KDE and apps
+
+Jaroslaw Staniek
+started: 2003-07-22
+
+OOPL-only: Create Update Service using kdecore/KMD5 and bzip2 lib. This would be used to automatic
+updates for QKW and Kexi packages at the end-user side. Two options:
+- offline update from local archives
+- online update from the company ftp resources
+Benefits:
+- Updating would not longer require uninstal+install scheme.
+- Settings could be stored safer between updates.
+- User could be notified about new features and fixes installed.
+- User could add/remove components.
+- Everything using consistent gui on all platforms.
+- 'Batch/net installation' options for enterprises.
+- Smart 'Fix installation' option.
+- Possibility of collecting user settings and infos about installations. These can optionally transferred
+to developers with a bug report to help find a bug context.
+- This is a step for making KDE apps packages distribution-neutral on Linux.
+Notes:
+- The Service guis and backend should be build from two parts:
+ - First: qt-only that is used to initialize the process and check the system, independently
+ from existing KDE sybsystem.
+ - Second: used when we already know we already have a working KDE subsystem. This part can use
+ KConfig, KLibLoader, etc. to change the KDE settings.
+- To be able to do this, KDE needs get more guidelines for distributors of its binaries.
+- To be usable for commercial use, there should be one packaging system for KDE
+ (maybe on top of tgz/rpm/deb/win32/mac?), "Meta Packaging".
diff --git a/kexi/doc/dev/compile_time_options.txt b/kexi/doc/dev/compile_time_options.txt
new file mode 100644
index 000000000..220cbca14
--- /dev/null
+++ b/kexi/doc/dev/compile_time_options.txt
@@ -0,0 +1,158 @@
+Compile-Time Options for Kexi
+-----------------------------
+
+(c) 2005-2007, Jaroslaw Staniek, <js @ iidea . pl>
+
+
+See http://www.kexi-project.org/wiki/wikiview/index.php?AdvancedBuildNotes
+for explanation how to conveniently set compile-time options.
+
+List of available options
+-------------------------
+
+* KEXI_STANDALONE
+Type: defined/undefined
+Default: -
+Description: Can be defined globally in kexi_global.h.
+Defined for standalone Kexi releases (without KOffice, e.g. 1.0) and undefined
+for releases bundled with KOffice,. e.g. 1.1.
+
+* KEXI_NO_UNFINISHED
+Type: defined/undefined
+Default: defined in kexi_export.h if KEXI_OPTIONS is undefined
+Description: If defined, unfinished Kexi features (for example a few features
+within Table Designer) will be hidded for Kexi, to avoid user confusion.
+
+* KEXI_SHOW_UNIMPLEMENTED
+Type: defined/undefined
+Default: undefined
+Description: If defined, forces to show menu entries and dialogs just to give
+impression about development plans of Kexi Team.
+Only recommended for test/development versions.
+
+* KEXI_FORMS_SUPPORT
+Type: defined/undefined
+Default: defined in kexi_export.h if KEXI_OPTIONS is undefined
+Description: If defined, form plugin will be loaded; else: it will be
+not loaded even if is compiled.
+
+* KEXI_REPORTS_SUPPORT
+Type: defined/undefined
+Like KEXI_FORMS_SUPPORT macro, but for enabling report plugin.
+Default: undefined
+
+* KEXI_SCRIPTS_SUPPORT
+Type: defined/undefined
+Like KEXI_FORMS_SUPPORT macro, but for enabling scripts plugin.
+Default: undefined
+
+* KEXI_KROSS_SUPPORT
+Type: defined/undefined
+If KEXI_SCRIPTS_SUPPORT is defined, this option defines that
+Kross, located at kexi/scripting{core|plugins}, should be
+compiled and used for scripting.
+Default: same as KEXI_SCRIPTS_SUPPORT
+
+* KEXI_NO_MIGRATION
+Type: defined/undefined
+Default: undefined
+Description: If defined, data/project migration support for Kexi
+(available in Tools>Migration menu) will be disabled; else: support will
+be enabled.
+
+* KEXI_CUSTOM_HARDCODED_CONNDATA
+Type: defined/undefined
+Default: undefined
+Description: If defined, you are forcing to add custom hardcoded connections
+to the KexiMigration::importWizard constructor and KexiStartupHandler::init().
+All you need is to create your own private "custom_connectiondata.h" file
+in kexi/ directory, which will be included into Kexi source code and fill
+it with code providing one or more hardcoded connections.
+Example custom_connectiondata.h file for adding a single connection data:
+---- custom_connectiondata.h, cut here -----
+ conndata = new KexiDB::ConnectionData();
+ conndata->connName = "My Custom Pgsql Connection";
+ conndata->driverName = "postgresql"; //can be also "sqlite3", "sqlite2", "mysql"
+ conndata->hostName = "myhost.example.com";
+ conndata->userName = "john";
+ conndata->password = "secret";
+ Kexi::connset().addConnectionData(conndata); //always call this to add
+----- cut here ------------------------------
+Notes:
+- security warning: if you provide your passwords in your code, it will
+ be available in compiled binaries, so think twice before providine the binaries
+ for somebody else. The custom_connectiondata.h file will not be available
+ in CVS, so don't worry.
+- this hardcoding will be removed when full handling of connectiondata arrives.
+- conndata is predefined as: KexiDB::ConnectionData* conndata;
+- See KexiDB::ConnectionData deocumentation for more information.
+- you can assign KUser().loginName() for username if you want to get current
+ login.
+
+* KEXI_USE_GRADIENT_WIDGET
+Type: defined/undefined
+Default: undefined
+Description: If defined, KexiGradientWidget is used as form's background.
+Currently disabled due to problems with performance and child widget's palettes.
+
+
+* KEXI_NO_SUBFORM
+Type: defined/undefined, temporary
+Default: undefined
+Description: If defined, subform widget will not be available
+
+* KEXI_NO_AUTOFIELD_WIDGET
+Type: defined/undefined, temporary
+Default: undefined
+Description: If defined, atofield (KexiDBFieldEdit) widget will not be available,
+nor dragging fields from "data source" will be available
+
+* KEXI_NO_IMAGEBOX_WIDGET
+Type: defined/undefined, temporary
+Default: undefined
+Description: If defined, image box (KexiImageBox) widget will not be available
+
+* CUSTOM_VERSION
+Type: defined/undefined
+Default: undefined
+Description: Defined means Kexi version compiled for standalone distribution
+with some minor vendor-specific tweaks. Used for OOPL version.
+
+* KEXI_DEBUG_GUI
+Type: defined/undefined
+Default: defined (but should be undefined for final releases)
+Description: If defined, showInternalDebugger=true (in [General] group)
+can be specified to display KexiDB Debugger.
+In the future the window will be probably used for debugging other features
+like scripts or macros. This option is useful for Kexi Developers.
+
+Also shows additional "Show Form UI Code" action in form's design mode.
+This is useful for debugging: after executing the action,
+a dialog with current (after changes) and original form's
+GUI XML code will be presented in a tabbed dialog, so you can compare what changed
+and look for possible problems. The "current" XML code will be saved when "save"
+action is executed. TODO: it will be merged with Internal Debuger window.
+
+* KEXI_DB_COMBOBOX_WIDGET
+Type: defined/undefined
+Default: undefined
+Description: Defined means Kexi offers combo box form widget.
+
+* DB_TEMPLATES
+Type: defined/undefined
+Default: undefined
+Description: Defined means Kexi offers "Templates" in the startup dialog.
+
+==== Obsolete, don't use ====
+
+* KDOCKWIDGET_P
+Type: defined/undefined
+Default: undefined
+Description: If defined, enables a hack for better handling property editor's
+panel within KexiMainWindowImpl object.
+
+* KEXI_OPTIONS
+Type: defined/undefined
+Default: undefined
+Description: Define this if you want override default compile time options
+to use any of the options described below.
diff --git a/kexi/doc/dev/kexi_alter_table.txt b/kexi/doc/dev/kexi_alter_table.txt
new file mode 100644
index 000000000..4a6c851e1
--- /dev/null
+++ b/kexi/doc/dev/kexi_alter_table.txt
@@ -0,0 +1,110 @@
+-----------------------
+KEXI ALTER TABLE ISSUES
+-----------------------
+
+1. PROBLEMS
+- different databases have different set of altering capabilities
+ (SQLite has no table altering capabilities at all; re-creation is needed:
+ this has high cost in time and disk space for large tables)
+- we need to collect every detailed change made in Table Designer and apply
+ these changes to existing table schema
+- in the case when a table is already filled with data, we need to perform
+ tasks to move that data to a new structure,
+ especially if database engine can't do it for us or if the engine
+ wants simply drop the data because convertion cannot be done automatically
+- because of above issues, some work needs to be done at the client side
+
+2. What can be altered using ALTER TABLE:
+2.1 Inserting columns
+ (into a specified position)
+ ADD COLUMN <column_definition> [FIRST | AFTER <col_name> ]
+
+ (adding many colums at the end)
+ ADD COLUMN <column_definition>,...
+
+2.2 Adding constraints, indices
+ ADD INDEX [<index_name>] [<index_type>] (<index_col_name>,...)
+
+ ADD CONSTRAINT [<symbol>] PRIMARY KEY [<index_type>] (<index_col_name>,...)
+
+ ADD CONSTRAINT [<symbol>] UNIQUE [<index_name>] [<index_type>] (<index_col_name>,...)
+
+ ADD CONSTRAINT [<symbol>] FOREIGN KEY [<index_name>] (<index_col_name>,...)
+ [<reference_definition>]
+
+2.3 Altering column properties
+ ALTER COLUMN <col_name> {SET DEFAULT <literal> | DROP DEFAULT}
+
+ CHANGE COLUMN <old_col_name> column_definition [FIRST|AFTER <col_name>]
+
+ (rename column)
+ CHANGE <old_column> <new_column>
+
+ MODIFY COLUMN <column_definition> [FIRST | AFTER <col_name>]
+
+2.4 Dropping
+ DROP COLUMN <col_name>
+
+ DROP PRIMARY KEY
+
+ DROP INDEX <index_name>
+
+ DROP FOREIGN KEY <fk_symbol>
+
+2.5 Renaming a table
+ RENAME TO <new_tbl_name>
+
+
+3. How to perform table altering be re-creation:
+SQLite: (taken from Ticket 236: Add RENAME TABLE
+ - will be easier to work around missing ALTER TABLE
+ http://www.sqlite.org/cvstrac/tktview?tn=236,8)
+ Currently the recommended method to ALTER TABLE is:
+
+ 1. Create a temporary table.
+ 2. Copy all data from original table to temporary table.
+ 3. Drop the original table.
+ 4. Create a new table.
+ 5. Copy all data from the temporary table to the new table.
+
+For example, suppose you have a table named "t1" with columns
+names "a", "b", and "c" and that you want to delete column "c"
+from this table. The following steps illustrate how this could be done:
+
+BEGIN TRANSACTION;
+CREATE TEMPORARY TABLE t1_backup(a,b);
+INSERT INTO t1_backup SELECT a,b FROM t1;
+DROP TABLE t1;
+CREATE TABLE t1(a,b);
+INSERT INTO t1 SELECT a,b FROM t1_backup;
+DROP TABLE t1_backup;
+COMMIT;
+
+If the table name also changes on altering, it's easier to do the
+altering (no temp. table needed):
+
+BEGIN TRANSACTION;
+CREATE TABLE t2(c,d);
+INSERT INTO t2 (c,d) SELECT a,b FROM t1;
+DROP TABLE t1;
+COMMIT;
+
+ If we had RENAME TABLE, we'd get easier alter table here:
+ 1. Rename original table to temp name.
+ 2. Create new table with ORIGINAL name.
+ 3. Move data from previous to new table.
+ 4. Drop previous table.
+
+4. PROPOSED ALGORITHM
+
+
+
+5. Useful documentation
+ http://dev.mysql.com/doc/mysql/en/ALTER_TABLE.html
+
+
+6. Document History
+2004-07-20 js
+ Started
+ Asked Richard Hipp about RENAME TABLE
+
diff --git a/kexi/doc/dev/kexi_final_mode.txt b/kexi/doc/dev/kexi_final_mode.txt
new file mode 100644
index 000000000..7435eff35
--- /dev/null
+++ b/kexi/doc/dev/kexi_final_mode.txt
@@ -0,0 +1,21 @@
+----------------------
+Kexi Final Mode Design
+----------------------
+
+1. Questions / Proposals
+
+a) Do we need to store information about needed plugins?
+ The proposal: it's enough as is: needed plugin is checked on object loading.
+
+b) Instead having redundant kexi__final, we can easily
+ use kexi__db with for the same purpose?
+
+c) More than one startup item would be useful.
+ The proposal: introduce property "startup_items" with a value of ordered
+ int-list of IDs: "32,543,324,532". This is also probably better than a list of names?
+
+d) startup-related property (for kexi__db):
+ - "startup_mode", value: "0"==Design Mode, "1"==Final Mode
+ (default satartup mode; can be overriden by --final-mode and --design-mode)
+ "0" is the default
+
diff --git a/kexi/doc/dev/kexi_general_import_export.txt b/kexi/doc/dev/kexi_general_import_export.txt
new file mode 100644
index 000000000..493349f73
--- /dev/null
+++ b/kexi/doc/dev/kexi_general_import_export.txt
@@ -0,0 +1,53 @@
+Main goals:
+ *) easy extendability
+ *) common look and feel for common tasks
+
+**************************
+
+Workflow importing :
+Step 1:
+ a)
+ Project->Import
+ -> File
+ -> Connection
+ ==> All types
+
+ b)
+ RMB item group in navigator->Import
+ -> File
+ -> Connection
+ ==> Only type of item group
+
+------
+File based:
+Step 2:
+ *) Lookup of any ImportSource plugin capable handling any of the possible types
+ *) Filling of mime filter list on file dialog page
+ *) Loading of plugin previously looked up for handing specified file
+Step 3:
+ *) Showing user all possible imports, eg all tables from a database
+ *) Allowing source specific configuration, eg range selection in a kspread document
+Step 4:
+ *) loading import GUI plugin for destination kexi mimetype
+ *) Allow the user to choose how to import
+ eg for tables: import new table,
+ append data to an existing table
+ column remapping, renaming, ...
+Step 5:
+ *) doing import
+ tables : handing the chosen configuration over to PiggZ's kexidb-import architecture
+ * : GUI plugin is responsible for importing directly or loading an additional, NON visual plugin
+Step 6:
+ *) DONE
+
+
+**************************
+
+Needed facilities:
+ Page Interface: Wizard<-> Source plugin
+ Wizard<-> Destination plugin
+ Source interfaces: Depending on destination type, eg table import interface, report import interface, ....
+ Needed for communication between Source and Destination GUI
+
+
+JoWenn
diff --git a/kexi/doc/dev/kexi_guidelines.txt b/kexi/doc/dev/kexi_guidelines.txt
new file mode 100644
index 000000000..7c2dfaba3
--- /dev/null
+++ b/kexi/doc/dev/kexi_guidelines.txt
@@ -0,0 +1,35 @@
+PROPOSAL: Generic Guidlines For Kexi Development
+
+Jaroslaw Staniek
+started: 2003-06-25
+
+Idea 1
+Add tests for command line-only operations using kexiDB library.
+These tests can be later turned to command line options for Kexi what will allow non
+GUI operations o databases/projects.
+
+Idea 2
+Look at QSQL module from QT. E.g. there is efficient cursor feature (it works quite good with mysql).
+We have KexiDBRecordSet instead, but this is just the same thing.
+In fact many KexiDB features are compatible with QSQL.
+Focus on:
+- avoid loading all result data to client memory (fetch some records instead)
+- avoid blocking operations (do asynchronously as many things as possible)
+- avoid memory copying (at low level try to store original char* data instead of QString,
+ convert to QString only on data displaying)
+
+Idea 3
+Directories layout:
+dialogs/
++-altertable/
++-datatable/
++-queryeditor/
++-sqleditor
+
+widgets/
++-tableview/
++-datatableview/
+
+Idea 4
+Classes naming guidelines:
+'Dialog' suffix for all classes
diff --git a/kexi/doc/dev/kexi_i18n_guidelines.txt b/kexi/doc/dev/kexi_i18n_guidelines.txt
new file mode 100644
index 000000000..2be1490dc
--- /dev/null
+++ b/kexi/doc/dev/kexi_i18n_guidelines.txt
@@ -0,0 +1,39 @@
+Kexi Translation Issues
+-----------------------
+
+Many things can be found here: http://i18n.kde.org/translation-howto/index.html
+
+
+Additional guidelines:
+
+#1. Let's not use "Do you really want" question. Use "Do you want" question instead.
+
+
+
+#2. Try do not overuse html formatting tags like <qt> in translated strings is you
+do not need to. Eg. rather use
+
+ "<qt>" + i18n("Connection error: <b>%</b>") + "</qt>"
+
+instead of
+
+ i18n("<qt>Connection error: <b>%</b></qt>");
+
+
+
+#3. Use " instead of ' , ie.:
+
+ i18n("Object \"%1\" not found.") --GOOD
+
+instead of :
+
+ i18n("Object '%1' not found.") --BAD
+
+Also, do not use <b> tags around "" ie.
+
+ i18n("Object <b>\"%1\"</b> not found.") --BAD
+
+
+--
+js@iidea.pl, july 2004
+
diff --git a/kexi/doc/dev/kexi_import.txt b/kexi/doc/dev/kexi_import.txt
new file mode 100644
index 000000000..be640ba1c
--- /dev/null
+++ b/kexi/doc/dev/kexi_import.txt
@@ -0,0 +1,61 @@
+kexiImport proposal - piggz (www.piggz.co.uk)
+=============================================
+
+This is a proposal for some import functionality for kexi.
+As of this moment i have no idea how it will work, but ive
+been thinking and figured i should get something down in
+writing. Hopefully you will all improve on this and we can
+get something working.
+
+The way i see it we need to do a few things:
+
+1) A common api to different import systems
+ This would be similar in concept to kexidb drivers, but would
+ only provide limited, specific functionality
+
+2) DB specific classes that provide import functionality
+ for postgres, mysql firebird etc...
+
+
+For 1) we need to to:
+
+i) Connect to backend -|
+ |
+ii) Get list of databases | All this is already catered for in
+ | kexidb
+iii) Get list of tables -|
+
+iv) For a given table, get list of columns as stringlist
+
+v) For a given table/column combination, get extended information for
+ that column:
+
+ type
+ +--If a known kexi type then ok
+ +--If unknown then driver may be able to automagically map to suitable type
+ +--Ask the user what to do (map to type, convert column, drop column)
+
+ index (yes/no)
+
+ pkex (yes/no)
+
+ default value
+
+ Alot of this is already catered for in old kexi api, so maybe some
+ chunks fo code could be reused, though a bit of a rewrite is inevitable
+ to limit the functionality to the above
+
+Once the table/column information is available then a tableschema object would be
+created and standard kexidb api could create nescessary structure. For this there
+would be 2 modes of operation
+
+A) Structure will be created under new db name, and kexidb api would be used as
+ normal
+
+B) Structure will be created in existing db. Kexi api would need a mode where
+ kexi__* structure is created, but tables are not created as they already exist
+
+
+Ideas, suggestions and designs welcome :o)
+
+PiggZ \ No newline at end of file
diff --git a/kexi/doc/dev/kexi_issues.txt b/kexi/doc/dev/kexi_issues.txt
new file mode 100644
index 000000000..62292f2d7
--- /dev/null
+++ b/kexi/doc/dev/kexi_issues.txt
@@ -0,0 +1,30 @@
+---------------------------------------------------------
+ Kexi IDEAS, ISSUES, PROPOSALS
+ Copyright (C) 2003 Jaroslaw Staniek js at iidea dot pl
+ Started: 2003-08-01
+ Kexi home page: http://www.koffice.org/kexi/
+---------------------------------------------------
+
+|
+| This document is expected to be independent of given KexiDB driver implementation,
+| but proposals are described here after certain decisions:
+| -drivers are (by current importance): SQLite (embedded),
+| ODBC (universality reasons), Mysql (historical reasons), PostgreSQL
+|
+
+1. EXTENSION: Inheriting properties
+ Kexi objects like tables, forms, reports can be put in sort of relation diagram:
+ Easy example: form and report depends on table/query (or more tables and queries).
+
+ This dependency can be called inheriting when we wnat to reuse as many
+ as possible properties of parent 'object' in child 'object'.
+
+ Examples of these properties: table colors, columns widths, etc. can be
+ reused in forms/reports that use this its data.
+ Inherited values can be of course changed for 'child' objects if needed.
+
+ Other inherited properties example are custom error/warning messages
+ (see kexidb_issuses doc, #1).
+
+ Properties inheritance makes database schema more rich and reduces required work
+ for later stages of the database project.
diff --git a/kexi/doc/dev/kexidb_api_changes.txt b/kexi/doc/dev/kexidb_api_changes.txt
new file mode 100644
index 000000000..9f40e7e49
--- /dev/null
+++ b/kexi/doc/dev/kexidb_api_changes.txt
@@ -0,0 +1,125 @@
+--------------------------------------------
+ KexiDB Module API Changes
+ Doc started: 2003-07-08 by js@iidea.pl
+--------------------------------------------
+
+These changes are propsals for further disscussions.
+
+
+1. *** RENAMING ***
+
+Some classes can be betted described with other names:
+
+class KexiDBInterfaceManager -> KexiDBDriverManager
+
+Motivation: 'Interface' is a more generic term. Thus, the class name is not a description.
+'Driver Manager' term is also used in the iODBC package.
+
+
+2. Now, KexiDB is just another ODBC-like layer. Probably there woudn't be too much drivers
+for KexiDB if KexiDB will be still like it is.
+
+What to change?
+KexiDB should be merged with non-visual (but db-related) parts of KexiProject.
+KexiProject then should be renamed to KexiDBProject and be moved to KexiDB.
+Visual (document-like) project container (formerly KexiProject) that inherits KoDocument
+could be then named to something else and still exist since Kexi, as KOffice application,
+needs to implement KoDocument. (KexiProjectHandler is also visual-related)
+
+What we got then?
+After that move, KexiProject becames a non-visual general-purpose entity. SO it can be instantiated
+not only as Kexi Koffice document window, but also:
+- for batch (non-visual) data processing (equivalents of given features availabe from gui, e.g. data export)
+- for easy use in Kexi plugins and/or other apps that would know about enchanced database properties
+provided by Kexi projects' metadata
+- to enable linking current Kexi project with external Kexi projects' data, in the future (these external
+projects do not have to be visible as regular projects, but its metadata is loaded)
+
+Other motivation:
+It's not necessary for KexiProject to be dependent on KOfficelibs.
+
+
+3. KexiDBConnection should be moved to KexiDB module and it should be only connection data container.
+
+
+4. Kexi uses KoStore as medium for storing project's meta data.
+
+What if we want to store even this data in the database?
+
+We need more generic class KexiDBProjectStore for this of extend KoStore. Note that extending
+KoStore may not be possible (or nice) because of hierarchical (fs-like) structure of this medium,
+while db store is relational.
+
+Why to store Kexi projects in the db?
+
+- We can utilize transactional, multiuser features of databases "for free"
+- Using sqlite engine we can still store projects in local files.
+So, maybe drop KoStore completely? Oh, maybe not, but we can treat this format as xml-exported,
+mainly for external processing, migration and backups.
+
+Note that there can be one database location for storing project's user data and completely other location
+for the database that has stored project's meta-data (see example below).
+
+
+5. Example usage of db engine, to get both project metadata and raw db connection
+
+{
+ kdDebug() << "DRIVERS:" << KexiDBEnginesManager::enginesNames() << endl;
+ KexiDBEngine *engine = KexiDBEngineManager::engine("mysql");
+ if (!engine) {
+ kdDebug() << "no engine" << endl;
+ return 1;
+ }
+
+ //we can also get the manager obj. with: KexiDBEnginesManager *manager = KexiDBEnginesManager::self();
+
+ //connection data that can be later reused
+ KexiDBConnectionData conn_data;
+ conn_data.host = "myhost";
+ conn_data.password = "mypwd";
+
+ KexiDBConnection *conn = engine->createConnection(conn_data);
+ if (!conn->connect()) {
+ kdDebug() << "err. connect" << endl;
+ return 1;
+ }
+ kdDebug() << "DATABASES:" << conn->dbNames() << endl;
+
+//we want use this connection to load given Kexi Project:
+
+ KexiDBProject *prj = new KexiDBProject( conn, "test" );
+ /* connection 'conn' has been opened for project meta-data source:
+ KexiDBProjectStore *store = new KexiDBProjectStore( conn )
+ Kexi then opens data base with KexiDBConnection::openDatabase( name )
+ where 'name' comes from meta-data.
+ This new connection is for projects' data.
+ */
+ /* we could also load project from a file:
+ KexiDBProject *prj = new KexiDBProject( "~/myprj.kexi" );
+ Then, KexiProject will be loaded from the file:
+ KexiDBProjectStore *store = new KexiDBProjectStore( "~/myprj.kexi" );
+ ..and data connection will be established as before.
+ */
+ if (!prj)
+ kdDebug() << "err. open db" << endl;
+ return 1;
+ }
+ kdDebug() << "TABLE DEFS:" << prj->tableDefsNames() << endl;
+ kdDebug() << "QUERY DEFS:" << prj->queryDefsNames() << endl;
+
+ KexiDBTableDef *tab_def = prj->tableDef("people") << endl;
+ if (!tab_def)
+ kdDebug() << "no table def" << endl;
+ return 1;
+ }
+ kdDebug() << "TABLE DEF FIELDS:" << tab_def->fieldsNames() << endl;
+
+ KexiDBFieldDef *field = tab_def->field("age");
+ if (!field)
+ kdDebug() << "no such field" << endl;
+ return 1;
+ }
+ kdDebug() << "FIELD TYPE:" << field->typeName() << endl;
+
+ return 0;
+} \ No newline at end of file
diff --git a/kexi/doc/dev/kexidb_issues.txt b/kexi/doc/dev/kexidb_issues.txt
new file mode 100644
index 000000000..0dd945c69
--- /dev/null
+++ b/kexi/doc/dev/kexidb_issues.txt
@@ -0,0 +1,211 @@
+---------------------------------------------------------
+ KexiDB module IDEAS, ISSUES, PROPOSALS
+ Copyright (C) 2003 Jaroslaw Staniek js at iidea dot pl
+ Started: 2003-08-01
+ Kexi home page: Users: http://www.koffice.org/kexi/ Developers: http://kexi-project.org
+
+ Changes:
+ 2005-09-16 Global BLOBs Storage
+---------------------------------------------------------
+
+|
+| This document is expected to be independent of given KexiDB dirver implementation,
+| but proposals are described here are after certain decisions has been made:
+| -drivers are (by current importance): SQLite (embedded),
+| ODBC (universality reasons ), Mysql (historical reasons), PostgreSQL
+|
+
+-----------------------------------------------------------------
+
+1. EXTENSION: Option for setting user-defined error messages on selected error types.
+
+Since new KexiDB has fully i18n'd user-visible messages, it outperforms other products as MSA
+(that often shows confusing message coming directly from the engine). There is possibility
+to provide users with table of error messages that they can personalize. These settings can
+be properties of table (query, form, etc.) and thus can be inherited (eg. by form from a table).
+By default, i18n'd messages from KexiDB can be presented.
+
+<EXAMPLE>
+Before we set our message for UNIQUE KEY conflict for table Cars (name varchar UNIQUE, price integer),
+we have got this message when try to violate 'unique' restionction:
+"There is already added one record with field 'name' set to 'Dodge' in table 'Cars'.
+This field must be unique."
+Now, we want more descriptive message: "There should be only one car named 'Dodge'!".
+We write this not as above, but rather: "There should be only one car named %field%!",
+since %field% is automatically substituted by given field value. After this we get message
+that is easier to understand for everyday user. Of course Kexi (and competitors too) never will
+be so clever as a human in building such messages automatically.
+</EXAMPLE>
+
+<NOTE>Inheriting messages: it will be more conventient to set up custom messages (if required)
+for table definition rather than just in form. If we set up given message in for a table, every
+form that uses this table as data source (end every query that do so) can use this message when needed.
+This note is also useful for other types of properties that can be inherited by extended objects from.
+</NOTE>
+
+-----------------------------------------------------------------
+
+2. KexiDB classes inheritance diagram
+
+TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
+
+Object
+ DriverManager
+ Driver
+ [DRIVER]Driver
+ Connection
+ [DRIVER]Connection
+
+[DRIVER] should be substituted by SQLite, MySQL, etc.
+These are classess to reimplement in the drivers.
+
+ConnectionData
+Field
+Cursor
+ [DRIVER]Cursor
+FieldList
+ TableSchema
+ QuerySchema
+ IndexSchema
+Transaction
+TransactionGuard
+BaseExpr
+ NArgExpr
+ UnaryExpr
+ BinaryExpr
+ ConstExpr
+ VariableExpr
+
+-----------------------------------------------------------------
+
+3. Table schema, query schema, etc. storage
+
+Table or query schemas are extended with additional properties,
+usually not available from most db engines or even related to gui.
+
+//This table contains list of kexi object stored within project (tables, queries, etc.)
+kexi__objects
+ o_id integer unsigned primary key auto_increment,
+ o_type byte, //type id taken from KexiDB::ObjectTypes
+ o_name varchar(200),
+ o_caption varchar(200), //optional, enriched name, can contain non-latin1 characters
+ o_help varchar
+
+//This table contains object's data, such as form's xml def.
+kexi__objectdata
+ o_id integer REFERENCES kexi__objects(o_id),
+ o_data varchar,
+ o_sub_id varchar(200) //sometimes single object can have more than one data
+ //-these can be identified by o_sub_id
+ //if not needed, leave it as NULL
+
+
+//This table contains fields that are members of given kexi object.
+//Not all object types should be allowed, now allowed are tables and queries
+kexi__fields
+ t_id integer REFERENCES kexi__objects(o_id),
+ f_type byte,
+ f_name varchar(200),
+ f_length integer,
+ f_precision integer,
+ f_constraints integer,
+ f_options integer,
+ f_default varchar,
+//these are additional properties:
+ f_order integer,
+ f_caption varchar(200),
+ f_help varchar
+
+//This table contains a list of parts defined for this database
+kexi__parts
+ p_id integer unsigned primary key auto_increment,
+ p_name varchar(200),
+ p_mime varchar(200),
+ p_url varchar
+TODO: add version?
+
+//This table contains global (predefined or not) properties for this database
+kexi__db
+ db_property varchar(32),
+ db_value varchar
+
+//Predefined properties stored in kexi__db:
+property="kexidb_major_ver", value=<major_version>
+ Where: <version> is a string containing integer in textual form that denotes
+ KexiDB implementation major version used to create or update this database.
+ This property is useful for KexiDB library and applications to denote
+ what KexiDB features could be available. This ensures backward compatibility
+ and (at least in some cases) -- forward compatibility.
+property="kexidb_minor_ver", value=<minor_version>
+ like above -- minor KexiDB version
+property="project_caption", value=<string>
+property="project_desc", value=<string>
+
+TODO: put more props. todo - creator, created date, etc. (also to KexiProjectData)
+
+// Global BLOBs Storage (BLOB == BINARY LARGE OBJECT)
+// Designed to store any kind of static images, sound and other binary data
+//Table:
+kexi__blobs
+ o_id integer unsigned primary key auto_increment, //unique project-wide identifier of a BLOB
+ o_data BLOB,
+ o_name varchar(200), //optional, name of the blob,
+ //can be e.g. a name of original file the data comes from,
+ //can contain non-latin1 characters
+ o_caption varchar(200), //optional, enriched name, can contain non-latin1 characters
+ o_mime varchar(200) NOT NULL, //MIME type of a BLOB, e.g. image/jpg
+ o_folder_id integer unsigned //references kexi__gallery_folders.f_id
+ //If null, the BLOB only points to virtual "All" folder
+ //WILL BE USED in Kexi >=1.1
+
+//(not yet implemented, planned for 1.1)
+//User-defined folders for better BLOBs categorizing.
+//For simplification, folders have no lowlevel name but only caption.
+//By default there are no physical folders, but only following virtual:
+//-"All" folder
+//-"Used" folder (computation of this takes some time because design of forms
+// and reports needs to be parsed)
+//-"Unused" folder (the same note as for "Used)
+//-"By type" folder with "jpeg", "png" subfolders, etc. - available by grepping using BLOB's o_mime
+kexi__gallery_folders
+ f_id integer unsigned primary key auto_increment, //unique id of a folder
+ f_parent_id integer unsigned //references f_id, can be null for top level folders
+ f_caption varchar(200), //optional, enriched name, can contain non-latin1 characters
+ f_notes varchar, //optional notes
+
+-----------------------------------------------------------------
+
+4. Extended table schema information:
+
+Docs available online:
+- http://kexi-project.org/wiki/wikiview/index.php?ExtendedTableSchemaInformation
+- Table Column Lookup: http://kexi-project.org/wiki/wikiview/index.php?KexiDBLookupColumns
+
+-----------------------------------------------------------------
+
+5. DEPRECATED TABLES
+
+//This table contains additional information for query schemas
+kexi__querydata
+ q_id integer REFERENCES kexi__objects(o_id),
+ q_sql varchar, //raw sql text - stored in the unchanged form entered by a user (or generated)
+ q_valid boolean, //query schema may be invalid (syntactically or logically,
+ //but still can be stored)
+ q_parent_tab integer REFERENCES kexi__objects(o_id) //parent table of this query
+
+//This table contains information about fields defined in select queries
+kexi__queryfields
+ q_id integer REFERENCES kexi__objects(o_id),
+ f_order integer, //order of this field in query schema
+ f_id integer REFERENCES kexi__objects(f_order), //may be NULL if a field is asterisk
+ f_tab_asterisk integer REFERENCES kexi__objects(o_id), //if this is "single-table" asterisk
+ //this member references to valid tables id, otherwise is NULL
+ f_alltab_asterisk boolean //true if this is "all-tables" asterisk, otherwise false
+
+//This table contains information about tables used by select queries
+kexi__querytables
+ q_id integer REFERENCES kexi__objects(o_id),
+ t_id integer REFERENCES kexi__objects(o_id), //references to table used by given q_id
+ t_order integer //order of t_id table in query schema's tables list
+
+
diff --git a/kexi/doc/dev/kexidb_sql.txt b/kexi/doc/dev/kexidb_sql.txt
new file mode 100644
index 000000000..128bbae4b
--- /dev/null
+++ b/kexi/doc/dev/kexidb_sql.txt
@@ -0,0 +1,36 @@
+---------------------------------------------------------
+ KexiDB Common SQL definition
+ Copyright (C) 2003 Jaroslaw Staniek js at iidea dot pl
+ Started: 2003-09-10
+ Kexi home page: http://www.koffice.org/kexi/
+---------------------------------------------------------
+
+0. Preface
+----------
+Below is definition (formal grammar and comments) for SQL used externally by KexiDB.
+"Externally" means that this language is visible to the user and can be used
+by her/him to define queries.
+
+Many attempts has been done to create common part of many SQL versions that
+are currently in everyday use, mainly:
+- SQLite
+- MySQL
+- PostgreSQL
+
+
+1.1 CREATE TABLE
+----------------
+
+<CREATE_TABLE> ::= CREATE TABLE <table_name> (
+ <field_def>[,<field_def>]*
+ [,<table_constraint>]*
+ )
+<field_def> ::= <field_name> <field_type> <field_constraint>
+
+Note
+----
+CREATE TABLE statement could be added to KexiDBSQL for advanced users convenience,
+although tables can be easier created with gui. CREATE TABLE for KexiDB have
+non-standard options KexiDB-specific.
+
+TODO.......
diff --git a/kexi/doc/dev/kexisql_grammar_notes.txt b/kexi/doc/dev/kexisql_grammar_notes.txt
new file mode 100644
index 000000000..9f80ed693
--- /dev/null
+++ b/kexi/doc/dev/kexisql_grammar_notes.txt
@@ -0,0 +1,62 @@
+/* Jaroslaw Staniek:
+ TAKEN FROM his compiler's parser
+ AS EXAMPLE FOR LUCIJAN'S KEXISQL PARSER */
+
+
+
+exp: exp3 { $$->prn(); }
+ | exp OR exp3 { $$ = new NOpLog($1,OR,$3); $$->prn(); }
+ ;
+exp3: exp3n {/*default*/}
+ | exp3 AND exp3n { $$ = new NOpLog($1,AND,$3); }
+ ;
+exp3n: exp2 {/*default*/}
+ | NOT exp3n { $$ = new NOp1arg(NOT,$2); }
+ ;
+exp2: exp1 {/*default*/}
+ | exp1 op_rel exp1 { $$ = new NOpRel($1,$2,$3); }
+ ;
+exp1: exp0 {/*default*/}
+ | exp1 op_ar1 exp0 { $$ = new NOpAr($1,$2,$3); }
+ ;
+exp0: exp_poj {/*default*/}
+ | exp0 op_ar0 exp_poj { $$ = new NOpAr($1,$2,$3); }
+ ;
+
+
+
+
+* prior.2 - relational oper. */
+op_rel: '=' { $$='='; }
+ | '<' { $$='<'; }
+ | '>' { $$='>'; }
+ | "<=" { $$=REL_LESS_EQ; }
+ | ">=" { $$=REL_GREAT_EQ; }
+ | "<>" { $$=REL_NOT_EQ; }
+ ;
+/* prior.1 - arytmetic oper. +,- */
+op_ar1: '+' { $$='+'; }
+ | '-' { $$='-'; }
+ ;
+/* prior.0 - arytmetic oper. *,/ */
+op_ar0: '*' { $$='*'; }
+ | '/' { $$='/'; }
+ ;
+exp_single: single {/*default*/}
+ | '(' exp ')' { /*$$=new NOp1arg(0, $2);*/ $$=$2; }
+ | '-' exp_single %prec OP_MINUS { $$=new NOp1arg('-', $2); }
+ | '+' exp_single %prec OP_PLUS { $$=new NOp1arg('+', $2); }
+ ;
+single: const {/*default*/}
+ | variable {/*default*/ }
+ | function_call {/*default*/}
+
+const: CONST_BOOL { $$ = new NConstBool(yylval.bval); }
+ | CONST_INT { $$ = new NConstInt(yylval.ival); }
+ | CONST_STR { $$ = new NConstStr(yylval.sval); }
+ ;
+
+
+
+
+
diff --git a/kexi/doc/dev/lib_dependency.odg b/kexi/doc/dev/lib_dependency.odg
new file mode 100644
index 000000000..d3a720951
--- /dev/null
+++ b/kexi/doc/dev/lib_dependency.odg
Binary files differ
diff --git a/kexi/doc/dev/lib_dependency.png b/kexi/doc/dev/lib_dependency.png
new file mode 100644
index 000000000..29ed24a71
--- /dev/null
+++ b/kexi/doc/dev/lib_dependency.png
Binary files differ
diff --git a/kexi/doc/dev/mysql_bugs.txt b/kexi/doc/dev/mysql_bugs.txt
new file mode 100644
index 000000000..9e3a76f26
--- /dev/null
+++ b/kexi/doc/dev/mysql_bugs.txt
@@ -0,0 +1,6 @@
+2003-10-06 lucijan, mysql Ver 12.21 Distrib 4.0.14, for pc-linux-gnu (i686):
+
+parser doesn't allow "SELECT 1, 2, 3, * FROM fish"
+but allows "SELECT *, 1, 2, 3 FROM fish" and "SELECT 1, 2, 3, fish.* FROM fish"
+
+sqlite and postgress support "SELECT 1, 2, 3, * FROM fish" thouugh
diff --git a/kexi/doc/dev/naming_conventions.txt b/kexi/doc/dev/naming_conventions.txt
new file mode 100644
index 000000000..8d34f97a5
--- /dev/null
+++ b/kexi/doc/dev/naming_conventions.txt
@@ -0,0 +1,137 @@
+---------------------------------------------------------
+ Kexi Naming Conventions
+
+ These rules apply to Kexi Team developers
+ There are also guidelines for future naming decissions.
+
+ Started: 2003-10-17
+ Copyright (C) 2003 Kexi Team
+ Kexi home page: http://www.kexi-project.org/
+---------------------------------------------------
+
+1. Namespaces
+To simplify class names and make these names shorter, we use namespaces.
+
+1.1. KexiDB namespace
+
+This denotes KexiDB module. All classes from kexidb/ directory are in KexiDB
+namespace.KexiDB-compatible database drivers (from kexidb/drivers/
+subdirectories are not in this KexiDB namespace.
+
+
+1.2. KexiWindow ???
+
+#TODO
+
+2. Directories
+
+plugins/<subdirs>
+ any plugins like Kexi dialogs for table altering, query desinger, etc.;
+ one subdirectory per plugin; each subdirectory is a KDE module
+
+widgets/<subdirs>
+ any widgets that are not dependent on core nor kexidb, thus reusable
+ in other projects; one subdirectory per widget; all subdirectories are linked
+ together with static compilation to single common library
+
+3rdparty/<subdirs>
+ any modules that can be considered as external (not necessarily optional)
+
+
+3. Creating documentation
+
+- Doxygen (www.doxygen.org) is used for generating Kexi developer documentation.
+- default target directory of these docs in html format is: doc/html for all sources
+ and doc/kexidb/html for kexidb-only docs
+- one step (..) up from mentioned dirs are places for .doxygen project files used
+ for docs generating
+- Single-line comments are created begginning with: //!
+- Multi-line comments are created begginning with /*! or /**
+- Style of comments (/*! of /**) for given file should be preserved
+
+Example 3.1: Comments for non-void functions, adding information about parameters:
+
+/*! Displays value of \a x.
+ \return true if displaying is successfull */
+bool display(int x);
+
+Notes for example:
+-\return special Doxygen's tag is used to describe that we return
+something in the method (\returns will not work for Doxygen!).
+-Clause should be started from capital letter and terminated with a dot.
+-"\a" special tag should be used before inserting argument names (names are
+equal to these from the method's definition) - the names will be therefore
+highlighted by Doxygen.
+
+-For more sophisticated methods (with more arguments), you can as well
+use \param tag, e.g.:
+
+/*!
+\param x horizontal position
+\param y vertical position
+\param col color of the box
+*/
+
+-Methods/functions should be documented in header files (.h), not in implementation
+ files. This allows easier inspection for users of the source code.
+-Comments from implementation files can be turned into
+ additional documentation, if really needed (when we use "/*!")
+ and this also will be included to generated docs if Doxygen project has enabled appropriate
+ option for doing this.
+-Classes should be also documented -comments put just as the lines
+ before "class keyword.
+
+-to add reference to similar or related function, use \sa tag, e.g.:
+/*! blablabla
+ \sa bleble
+*/
+
+-to mark a code fragment that someting is TO DO, you can use \todo tag, e.g.:
+
+/*! \todo (js) it is so hard to implement!
+*/
+
+-example above shows that adding e.g. own nick inside the brackets what can help
+to recognise who marked given fragment.
+
+4. Indentation
+4.1 We can use the rule as in the rest of KDE code.
+example:
+ QString objectName(); //wrong
+ QString objectName(); //ok
+rule: dont use tabs between words.
+
+4.2 To avoid many indentation levels, we can use:
+
+void mymethod()
+{
+ if (!something)
+ return;
+ if(!something_else && .....)
+ return
+ do_something();
+}
+
+ instead of:
+
+void mymethod()
+{
+ if (something) {
+ if(something_else && .....) {
+ do_something;
+ }
+ }
+}
+
+This is good, because made qt and kde sources readable.
+
+4.3 Indentation within classes declaration
+
+class MyClass {
+ public:
+ MyClass();
+ void method();
+ protected:
+};
+
+ANYWAY: we use indentation.
diff --git a/kexi/doc/dev/pgsql_issues.txt b/kexi/doc/dev/pgsql_issues.txt
new file mode 100644
index 000000000..9e21e4ac2
--- /dev/null
+++ b/kexi/doc/dev/pgsql_issues.txt
@@ -0,0 +1,7 @@
+
+1. Dynamic (non-buffered cursors) in PostgreSQL
+To avoid big memory consuming, we will need to use backend-side (native) cursors
+with "DECLARE CURSOR" statement. Otherwise KexiDB::Cursor needs to be buffered.
+
+http://archives.postgresql.org/pgsql-interfaces/2000-09/msg00082.php
+
diff --git a/kexi/doc/dev/settings.txt b/kexi/doc/dev/settings.txt
new file mode 100644
index 000000000..ef203cef1
--- /dev/null
+++ b/kexi/doc/dev/settings.txt
@@ -0,0 +1,168 @@
+f-----------------------------------------------------------
+Settings stored in 'kexirc' config file
+
+This is official list.
+All other settings are unofficial and are subject to change.
+
+Started: 2004-08-20, js
+-----------------------------------------------------------
+
+Group: MainWindow
+# percentage width of the Project Navigator pane
+-LeftDockPosition [integer: 0..100]
+
+# True if the Project Navigator pane is visible after startup.
+-ShowProjectNavigator [boolean] (default: true)
+
+# True if single click should open item in the Project Navigator.
+# Otherwise double click should be used to open item.
+# Meaningful when "single click to open files" option is set
+# in the Control Center.
+- SingleClickOpensItem [boolean] (default: false)
+
+# Percentage property editor's width
+-RightDockPosition [integer: 0..100]
+
+# True if "hover close button" should be available for tabs in the main window.
+# Only affects IDEAl user unterface mode.
+-HoverCloseButtonForTabs [boolean] (default: false)
+
+Group: PropertyEditor
+-FontSize [integer]
+
+Group: Notification Messages
+-dontAskBeforeDeleteRow [boolean] (default: false)
+
+-askBeforeOpeningFileReadOnly [boolean] (default: true)
+
+# If true, warning messages related to plugins, e.g.
+# "Errors encountered during loading plugins"
+# will not be displayed on the application's startup.
+-dontShowWarningsRelatedToPluginsLoading [boolean] (default: false)
+
+Group: General
+# Especially useful for SQL-related messages
+# default=true for advanced Kexi mode
+NOT YET IMPLEMENTED: -alwaysShowDetailsInMsgBoxes [boolean]
+
+# If true, Kexi Debugger internal window will be displayed.
+# In the future the window will be probably used for debugging other features
+# like scripts or macros. This option is useful for Kexi Developers.
+# Only available when KEXI_DEBUG_GUI compile time option is defined.
+-showKexiDBDebugger [boolean] (default: false)
+
+Group: Recent Dirs
+-lastVisitedImagePath [stringlist] # used for open/save dialogs for image files
+ # see KexiImageBox::slotInsertFromFile() and slotSaveAs();
+
+Group: Startup
+-OpenExistingType [File|Server]
+-ShowStartupDialog [boolean]
+#default file db driver name, used on file db creation:
+TODO: -DefaultFileDBDriverName [string default="sqlite3", can be also "sqlite2"]
+
+Group: TableView
+TODO: -add default values for KexiTableView::Appearance
+
+Group: TableDesigner
+-autogeneratePrimaryKeysOnTableDesignSaving [boolean]
+TODO: -defaultFieldType [the list of types], default=Text
+TODO: -autoPrimaryKeyForFieldNames [stringlist]
+TODO: -defaultTextFieldLength [uint, <255] default=50
+TODO: -defaultIntegerFieldSubtype [the list of types (byte, short, etc.)] default=long
+
+Group: QueryDesigner
+TODO: -autoJoinOnTableInserting [boolean]
+
+Group: KeyboardNavigation
+-cursorPlacementAfterLastOrFirstFormField [stringlist: nextOrPrevRecord|firstOrLastField(default)]
+
+Group: Forms
+TODO:-overrideStyleName [string] (empty if do not override)
+TODO:-doNotFocusAutonumberFields [boolean] (true by default; when this and autoTabStop for a form is true,
+ autonumber text fields are skipped)
+ implement this in KexiFormView::afterSwitchFrom()
+TODO:-appendColonToAutoLabels [boolean] (true by default; when true, colon character is appended
+ to autolabel text)
+TODO:-makeFirstCharacterUpperCaseInCaptions [boolean] (true by default; when true, first character
+ in autolabel or popup menu caption text is converted to upper case.
+ Usable especially when no field's caption is provided)
+TODO:-labelPositionInAutoLabels [enum: Left, Top] (Left by default)
+
+TODO:-gridSize [uint] (10 by default)
+
+Group: NewFormDefaults
+TODO:-styleName [string]
+TODO:-autoTabStop [boolean]
+
+Group: ImportExport
+# Default character encoding for MS Access MDB/MDE files (older than 2000).
+# Currently used by in Advanced Options of Importing Wizard.
+# Useful if you are performing many imports of MS Access databases.
+# Valid values can be "cp 1250", "cp 1251", etc. Case insensitive.
+# If not provided, system default will be is assumed.
+-DefaultEncodingForMSAccessFiles [string]
+
+# Default character encoding for importing CSV (Comma-Separated Value) files.
+# If not provided, system default will be is assumed.
+-DefaultEncodingForImportingCSVFiles [string]
+
+# True if options should be visible in the "CSV Export dialog".
+-ShowOptionsInCSVExportDialog [boolean] (false by default)
+
+# If provided, appropriate options for CSV Export Dialog will be loaded
+-StoreOptionsForCSVExportDialog [boolean] (default: false)
+
+# Default delimiter used for exporting CSV (Comma-Separated Value) files.
+-DefaultDelimiterForExportingCSVFiles [string] (default is ",")
+
+# Default text quote character used for exporting CSV (Comma-Separated Value) files.
+-DefaultTextQuoteForExportingCSVFiles [string] (default is ")
+
+# Default character encoding for exporting CSV (Comma-Separated Value) files.
+# If not provided, system default will be is assumed.
+# Only used when StoreOptionsForCSVExportDialog option is true.
+-DefaultEncodingForExportingCSVFiles [string]
+
+# Strip leading and trailing blanks off of text values when importing
+# CSV (Comma-Separated Value) files.
+-StripBlanksOffOfTextValuesWhenImportingCSVFiles [boolean] (default: true)
+
+# Default setting used to specify whether column names should be added as the first row
+# for exporting CSV (Comma-Separated Value) files.
+# Only used when StoreOptionsForCSVExportDialog option is true.
+-AddColumnNamesForExportingCSVFiles [string] (default: true)
+
+# Maximum number of rows that can be displayed in the CSV import dialog.
+# Used to decrease memory consumption.
+-MaximumRowsForPreviewInImportDialog [int] (default: 100)
+
+# Maximum number of bytes that can be loaded to preview the data in the CSV
+# import dialog. Used to decrease memory consumption and speed up the GUI.
+-MaximumBytesForPreviewInImportDialog [int] (default: 10240)
+
+Group: Recent Dirs
+# A list of recently displayed directories in file dialogs related to CSV
+ import/export.
+-CSVImportExport [string list]
+
+# A list of recently displayed directories in "Source database" file dialog
+ of Project Migration
+-ProjectMigrationSourceDir [string list]
+
+# A list of recently displayed directories in "Destination database" file
+ dialog of Project Migration
+-ProjectMigrationDestinationDir [string list]
+
+# A list of recently displayed directories in "Open existing project"
+ and "Create new project" file dialog of Startup Dialog
+-OpenExistingOrCreateNewProject [string list]
+
+# A list of recently displayed directories in file dialogs related to images
+ (e.g. images within forms).
+-LastVisitedImagePath [string list]
+
+# A list of recent displayed directories in a file dialogs used for dowloading
+ example databases (Get Hot New Stuff)
+-DownloadExampleDatabases [string list]
+
diff --git a/kexi/doc/dev/sql_engine_specifics.txt b/kexi/doc/dev/sql_engine_specifics.txt
new file mode 100644
index 000000000..3db65fdf9
--- /dev/null
+++ b/kexi/doc/dev/sql_engine_specifics.txt
@@ -0,0 +1,22 @@
+SQL Engines - Specifics
+-----------------------
+
+This document lists specifics among SQL engines. We need to remember about them when implementing features of KexiDB and designing KEXISQL.
+
+Legend: + == works, - == doesn't work, ? == not tested yet
+
+== 1. Ordering by computed column ==
+Examples: select rand() from T order by 1;
+ select 1 from T order by 1;
+MySQL: +
+SQLite: -
+PostgreSQL: ?
+
+== 2. Using more than just "*" in the column list ==
+Example: select *, 1 from T;
+MySQL: -
+SQLite: +
+PostgreSQL: ?
+
+Notes: select T.*, 1 from T; works everywhere
+
diff --git a/kexi/doc/dev/sqlite_issues.txt b/kexi/doc/dev/sqlite_issues.txt
new file mode 100644
index 000000000..9e9ebf56d
--- /dev/null
+++ b/kexi/doc/dev/sqlite_issues.txt
@@ -0,0 +1,200 @@
+---------------------------------------------------------
+ SQLITE DRIVER IDEAS, ISSUES, PROPOSALS
+ Copyright (C) 2003 Jaroslaw Staniek js at iidea dot pl
+ Started: 2003-07-09
+ Kexi home page: http://www.koffice.org/kexi/
+---------------------------------------------------
+
+
+1. In most situations (especially on massive data operations) we do not want get types of the columns,
+so:
+
+PRAGMA show_datatypes = OFF;
+
+
+2. SQLite automatically adds primary key to the table if there is no such key.
+Such pkey column is not visible for statemets like 'select * from table',
+'select oid,* from table' need to be executed to also get this special column.
+
+See section '3.1 The ROWID of the most recent insert' of c_interface.html file.
+
+
+3. For smaller tables (how small? -- add configuration for this) sqlite_get_table() 'in memory'
+function could be used to speed up rows retrieving.
+
+
+4. Queries entered by user in the Query Designer should be checked for syntactically or logically validity and transformed to SQLite-compatible form befor execution. It is nonsense to ask SQLite engine if the given sql statement is valid, because then we wouldn't show too detailed error message to the user.
+
+
+5. SQLite not only doesn't handles column types but also doesn't checks value sizes, eg. it is possible to insert string of length 100 to the column of size 20.
+These checks should be made in KexiDB SQLite engine driver. In fact for each driver these checks could be made because user wants get a descriptive, localized, friendly message what's wrong. No single engine provides this of course. We need to store such a parameters like field size in project meta-data as sqlite doesn't stores that in any convenient way. It stores only 'CREATE TABLE' statement, as is.
+
+
+6. Possible storage methods for SQLite database embedded in Kexi project:
+ A. Single SQLite-compatible database file (let's name it: .sqlite file)
+ - Advantages: Best deal for bigger databases - no need for rewriting data form SQLite file to another,
+ fastest open and save times. DB data consumes disk space only once. Other applications that uses SQLite library could also make use of standard format of .sqlite file's contents. Kexi project and data would be easily, defacto, separated, what is considered as good method in DB programming.
+ - Disadvantages: User (who may want to transfer a database) need to know that .kexi file doesn't stores his data but .sqlite is for that.
+
+ B. Single SQLite-compatible database file embedded inside Kexi project .kexi file.
+ SQLite requires an access to a file in its own (raw) format to be available somewhere in the path. If SQLite storing layer could be patched to adding an option for seek to given file position, sqlite data can be stored after Kexi project data. When sqlite raw data file could be saved after a Kexi project's data, rewriting the project contents should be performed (and this is done quite frequently). So, finally storing both files concatenated during normal operations is risky, costly and difficult to implement cleanly.
+ - Advantages: User do not need to know that there is sqlite used in Kexi as embedded DB engine (and even if there is any sql engine). Transferring just one file between machines means successfully transferring data and project.
+ - Disadvantages: lack of everything described as advantages of A. method: difficult and costly open and save operations (unless SQLite storing layer could be patched).
+
+ Extensions and compilations of the both above methods:
+ - .sqlite files are really good compressable, so compress option can be added (if not for regular saving, then at least for "Email project & data" or 'Save As' actions. For these actions concatenating the sqlite data with Kexi project's data would be another option convenient from user's point of view.
+
+ CURRENT IMPLEMENTATION: B way is selected with above extensions added to the TODO list.
+
+
+7. SQLite-builtin views are read-only. So the proposal is not to use them. Here is why:
+ We want have rw queries in Kexi if main table in a query is rw.
+ <DEFINITION>: Main table T in a query Q is a table that is not at 'many' side of query relations.
+ </DEFINITION>
+ <Example>:
+ table persons (name varchar, city integer);
+ table cities (id integer primary key, name varchar);
+
+ DATA: [Jarek, 1]-------[1, Warsaw]
+ /
+ [Jakub, 1]-----/
+
+ query: select * from persons, cities
+ Now: 'cities' table is the main table (in other words it is MASTER table in this query).
+ 'cities' table is rw table in this query, while 'persons' table is read-only because it is at 'many' side
+ in persons-cities relation. Modifying cities.id field, appropriate persons.city values in related
+ records will be updated if there is cascade update enabled.
+ </Example>
+ IDEAS:
+ A) Query result view (table view, forms, etc.) should allow editing fields from
+ main (master) table of this query, so every field object KexiDB::Field should have a method:
+ bool KexiDB::Field::isWritable() to allow GUI check if editing is allowed. Look that given field object
+ should be allocated for given query independently from the same field allocated for table schema.
+ The first field object can disallow editing while the latter can allow editing (because it is
+ component of regular table).
+ B) Also add method for QString KexiDB::Field that returns i18n'd message about the reasons
+ of disallowing for editing given field in a context of given query.
+
+
+----------------------------------------------------------------
+8. ERRORS Found
+8.1 select * from (select name from persons limit 1) limit 2
+ -should return 1 row; returns 2
+
+----------------------------------------------------------------
+
+HINTS:
+
+PRAGMA table_info(table-name);
+For each column in the named table, invoke the callback function
+once with information about that column, including the
+column name, data type, whether or not the column can be NULL,
+and the default value for the column.
+
+
+---------------------------------------------------------------
+OPTIMIZATION:
+
+Re: [sqlite] Questions about sqlite's join translation
+Od:
+D. Richard Hipp <drh-X1OJI8nnyKUAvxtiuMwx3w@public.gmane.org>
+Odpowiedz do:
+sqlite-users-CzDROfG0BjIdnm+yROfE0A@public.gmane.org
+Data:
+sobota 9 padziernika 2004 02:59:06
+Grupy:
+gmane.comp.db.sqlite.general
+Nawizania: 1
+
+Keith Herold wrote:
+> The swiki says that making JOINs into a where clause is more efficient,
+> since sqlite translates the join condition into a where clause.
+
+When SQLite sees this:
+
+SELECT*FROMaJOINbONa.x=b.y;
+
+It translate it into the following before compiling it:
+
+SELECT*FROMa,bWHEREa.x=b.y;
+
+Neither form is more efficient that the other.Bothwillgenerate
+identical code.(TherearesubtledifferencesonanLEFTOUTER
+JOIN, but those details can be ignored when you are looking at
+things at a high level, as we are.)
+
+>Italso
+> says that you make queries more effiecient by minimizing the number of
+> rows returned in the FROM clause as far to the left as possible in the
+> join.Doesthelattermatterifyouaretranslatingeverythingintoa
+> whereclauseanyway?
+>
+
+SQLite implements joins using nested loops with the outer
+loop formed by the first table in the join and the inner loop
+formed by the last table in the join.Sofortheexample
+above you would have:
+
+Foreachrowina:
+Foreachrowinbsuchthatb.y=a.x:
+Returntherow
+
+If you reverse the order of the tables in the FROM clause like
+this:
+
+SELECT*FROMb,aWHEREa.x=b.y;
+
+You should get an equivalent result on output, but SQLite will
+implement the query differently.Specificallyitdoesthis:
+
+Foreachrowinb:
+Foreachrowinasuchthata.x=b.y:
+Returntherow
+
+The trick is that you want to arrange the order of tables so that
+the "such that" clause on the inner loop is able to use an index
+to jump right to the appropriate row instead of having to do a
+full table scan.Suppose,forexample,thatyouhaveanindex
+on a(x) but not on b(y).Thenifyoudothis:
+
+SELECT*FROMa,bWHEREa.x=b.y;
+
+Foreachrowina:
+Foreachrowinbsuchthatb.y=a.x:
+Returntherow
+
+For each row in a, you have to do a full scan of table b.So
+the time complexity will be O(N^2).Butifyoureversetheorder
+of the tables in the FROM clause, like this:
+
+SELECT*FROMb,aWHEREb.y=a.x;
+
+Foreachrowinb:
+Foreachrowinasuchthata.x=b.y
+Returntherow
+
+No the inner loop is able to use an index to jump directly to the
+rows in a that it needs and does not need to do a full scan of the
+table.ThetimecomplexitydropstoO(NlogN).
+
+So the rule should be:Foreverytableotherthanthefirst,make
+sure there is a term in the WHERE clause (or the ON or USING clause
+if that is your preference) that lets the search jump directly to
+the relavant rows in that table based on the results from tables to
+the left.
+
+Other database engines with more complex query optimizers will
+typically attempt to reorder the tables in the FROM clause in order
+to give you the best result.SQLiteismoresimple-minded-it
+codes whatever you tell it to code.
+
+Before you ask, I'll point out that it makes no different whether
+you say "a.x=b.y" or "b.y=a.x".Theyareequivalent.Allofthe
+following generate the same code:
+
+ONa.x=b.y
+ONb.y=a.x
+WHEREa.x=b.y
+WHEREb.y=a.x
+---------------------------------------------------------------
+
diff --git a/kexi/doc/dev/tableview_issues.txt b/kexi/doc/dev/tableview_issues.txt
new file mode 100644
index 000000000..568fc28e2
--- /dev/null
+++ b/kexi/doc/dev/tableview_issues.txt
@@ -0,0 +1,27 @@
+---------------------------------------------------------
+ KexiTableView (and KexiDataTableView) Issues Document
+
+ Started 2003-10-13
+
+ Copyright (C) 2003 Jaroslaw Staniek js at iidea dot pl
+ (C) OpenOffice Polska 2003
+ Kexi home page: http://www.kexi-project.org/
+---------------------------------------------------
+
+2003-10-13
+Assumptions
+1. Now, I dont think about yet about features like data editing, sorting etc.
+ so, I try to forget about KexiTableList, until sorting will be readded.
+
+2. I will try to reimpl. KexiTableView to accept (reuse) raw data offered internally by KexiDB::Cursor:
+ const char ** recordData() const;
+ This is easy at least for buffered read-only data. So, we also consider that cursor used as datasource
+ is buffered.
+ It would be way more offective than using soooo many allocations and classes with KexiTableItem ;
+ btw, KexiTableItem is oversized: KexiTableView pointer is stored with each KexiTableItem :)
+
+result: for 1st step I keep KexiTableItem with some changes, code polishing
+- KexiTableItem is internal class so accessors will be removed;
+ shortly speaking - KexiTableItem is just a data structure
+
+
diff --git a/kexi/doc/handbook/docbook-status.txt b/kexi/doc/handbook/docbook-status.txt
new file mode 100644
index 000000000..a4c16d2f2
--- /dev/null
+++ b/kexi/doc/handbook/docbook-status.txt
@@ -0,0 +1,163 @@
+-----------------------------------------------------
+Contents:
+ Source HTML view of the manual (as per translation from Polish)
+ Docbook view of the manual as per SVN.
+-----------------------------------------------------
+
+Changelog:
+2005-12-05 mart
+ initial checkin
+2006-01-11 jstaniek
+ update for chapter numbers, more TODOs added
+
+HTML view of the Manual:
+
+The chapter numbers in section headings here refer to
+the Chapters in the HTML manual at
+ http://iidea.pl/~js/kexi/manual/en/
+
+The original Polish version can be found here:
+http://iidea.pl/~js/kexi/manual/pl/html/contents.html
+
+Icons and polish screenshots can be found here:
+http://www.iidea.pl/~js/kexi/manual/en/kexi-docs-03.zip
+
+Chapter numbers in each descriptions refers to docbook version.
+
+HTML Chapter 1:
+Has moved to Appendix A.
+Needs writing.
+
+HTML Chapter 2:
+Largely irrelevant marketing.
+
+Useful parts are now in Chapter 1
+(some bits missing - I'll sort them).
+
+Last Section (Different datatypes) is now moved to Appendix B.
+(There are some discrepancies between HTML and reality, take
+care when editting)
+
+HTML Chapter 4:
+Has become Chapter 2.
+
+HTML Chapter 5:
+Has become Chapter 3.
+
+HTML Chapter 6:
+Will become Chapter 4.
+
+HTML Chapter 7:
+Will become Chapter 5.
+
+HTML Chapter 8:
+Has become Chapter 6.
+
+HTML Appendix A. Menu items + B. Keyboard Shortcuts
+Will become Chapter 7.
+
+HTML Appendix C, D, E:
+Will become Appendix C, D, E.
+
+
+-----------------------------------------------------
+
+Docbook version:
+
+The chapter numbers here refer to the Docbooked manual.
+
+The names refer to the person working on that part of the manual.
+Please contact them if you'd like to help.
+
+
+Chapter 1: Introduction (Martin)
+New text based on
+02_00_00_idx_intro_to_kexi.html Done/Removed
+02_01_00_what_is_kexi.html Done/Removed
+02_02_00_features_of_kexi.html Done/Removed
+02_03_00_is_kexi_for_me.html Done/Removed
+Some work still required:
+ Use <email> tag for mailing lists (they currently won't show in a printed doc)
+ Check source text once more for any relevant missing info.
+
+Chapter 2: Kexi Basics (Raphael)
+The Kexi main window stuff is text I've written can be mered with 4.6 in HTML
+
+04_00_00_idx_basics_kexi.html Removed. (It's empty)
+04_01_00_project_files.html Done.
+04_02_00_running_kexi.html Ignoring for now.
+04_03_00_creating_database.html Done. TODO: polish source changed - UPDATE!
+04_04_00_project_opening.html Done. TODO: polish source changed - UPDATE!
+04_05_00_help_on_help.html Done.
+04_06_00_main_application_elements.html
+ Merge with "Kexi Main Window" section in docbook.
+ Done.
+04_06_01_project_navigator.html Done.
+04_06_02_object_windows.html Done.
+04_06_03_property_editor.html Done.
+
+Chapter 3: Building Simple Databases (Raphael)
+05_00_00_idx_building_simple_database.html Done.
+05_01_00_table_designing.html Done.
+05_02_00_entering_data_into_tables.html Done.
+05_03_00_query_designing.html Done.
+05_04_00_form_designing.html Done.
+05_05_00_data_entering_into_forms.html Done.
+
+Chapter 4. Advanced database topics
+06_01_00_kexi_project_sharing.html NOT READY FOR CONVERSION YET
+06_02_00_idx_designing_advanced_objects.html NOT READY FOR CONVERSION YET
+06_04_00_using_CLI.html NOT READY FOR CONVERSION YET
+
+Chapter 5. Working with external data
+07_01_00_data_formats.html NOT READY FOR CONVERSION YET
+07_02_00_data_import.html NOT READY FOR CONVERSION YET
+07_02_01_data_import_csv.html NOT READY FOR CONVERSION YET
+07_02_02_data_pasting_csv.html NOT READY FOR CONVERSION YET
+07_03_00_db_project_import.html NOT READY FOR CONVERSION YET
+07_04_00_data_export.html NOT READY FOR CONVERSION YET
+07_04_01_data_export_csv.html NOT READY FOR CONVERSION YET
+07_04_02_data_copying_csv.html NOT READY FOR CONVERSION YET
+07_05_00_project_export.html NOT READY FOR CONVERSION YET
+07_06_00_working_with_multiple_projects.html NOT READY FOR CONVERSION YET
+
+
+Chapter 6: Configuring Kexi (Martin):
+08_00_00_kexi_tuning.html Removed. (It's basically a chapter TOC)
+08_01_00_mdi.html Done.
+08_02_00_dock_undock.html Done.
+08_03_00_conf_keys.html TODO
+
+Chapter 7: Command Reference
+aa_00_00_menu.html NOT READY FOR CONVERSION YET (MERGE DOCS WITH docbook version)
+ab_00_00_shortcuts.html NOT READY FOR CONVERSION YET (MERGE DOCS WITH docbook version)
+
+Anne-Marie wrote this text (updated by jstaniek),
+since the Polish version has not been translated into English yet.
+WILL BE REPALCED BY HTML Appendix A. and B.
+
+Appendix A (Raphael):
+01_01_00_what_is_db.html Done.
+01_02_00_db_spreadsheet.html Done.
+01_03_00_design.html Done.
+01_04_00_who_needs.html Done.
+01_05_00_db_software.html Done.
+
+Appendix B:
+02_04_00_differences.html 1/2 Done. Another table to go.
+
+Appendix C: Widgets Reference
+ac_00_00_widgets.html NOT READY FOR CONVERSION YET
+TODO
+
+Appendix D: Specifications
+ad_01_01_csv_format.html NOT READY FOR CONVERSION YET
+TODO
+
+Appendix E: Example database projects
+ae_00_00_example_projects.html NOT READY FOR CONVERSION YET
+TODO
+
+Appendix E: Support
+ah_00_00_web_pages.html NOT READY FOR CONVERSION YET
+ag_00_00_technical_support.html NOT READY FOR CONVERSION YET
diff --git a/kexi/doc/handbook/html.tmp/01_01_00_what_is_db.html b/kexi/doc/handbook/html.tmp/01_01_00_what_is_db.html
new file mode 100644
index 000000000..dd4bff429
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/01_01_00_what_is_db.html
@@ -0,0 +1,34 @@
+<H2>1.1. What is a database?</H2>
+<p>
+You can define a database as a collection of data on one topic. It is organised in a way allowing to easily browse the information, make changes or add new items.
+</p>
+<p>
+Look at this diagram for one of the above examples: a simple phone book.
+
+<br><img src="img/01_02_example.png">
+<br>A diagram of a phone number database<br><br>
+
+The above picture shows a set of two contacts each of which is presented on a separate card. It appears that such a card can constitute a single line in a table:
+</p>
+
+<p>
+<table border="1" cellspacing="0" cellpadding="2">
+<tr><td colspan="2"><b>Contacts</b></td></tr>
+<tr><td><b>Name</b></td><td><b>Tel No.</b></td></tr>
+<tr><td>Joan</td><td>699 23 43 12</td></tr>
+<tr><td>Adam</td><td>711 19 77 21</td></tr>
+</table>
+</p>
+
+<p>
+<b>Terms and definitions</b>: A single data which constitutes a part of a greater collection can be called a <b>line</b> or more professionally a <b>record</b>. The collection is narmally called a <b>table</b>. Moreover, the most natural name for the table is one describing the data it offers/stores which is <b>Contacts</b>. Furthermore, each line in the table consists of <b>columns</b> often also called <b>fields</b>. In the table <em>Contacts</em> there are two columns (fields): <em>Name</em> and <em>Tel No.</em>.
+
+</p>
+<p>
+For simple uses a single table can make up a <b>database</b>. Many people consider these two equivalent. As you will see, for real databases we usually need more than one table.
+</p>
+<p>
+To sum up, you have already got a simple database with one table <em>Contacts</em>.
+
+<!-- -- > msa_whitepaper.pdf -->
+</p> \ No newline at end of file
diff --git a/kexi/doc/handbook/html.tmp/01_02_00_db_spreadsheet.html b/kexi/doc/handbook/html.tmp/01_02_00_db_spreadsheet.html
new file mode 100644
index 000000000..e7959df74
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/01_02_00_db_spreadsheet.html
@@ -0,0 +1,206 @@
+<H2>1.2. A database and a spreadsheet</H2>
+<p>
+It is very likely that you have already used spreadsheet applications like KSpread, OpenOffice.org Calc or Microsoft Excel. If so, you will probably wonder: since both spreadsheets and databases have tables, why should I use the latter?
+</p>
+<p>
+While comparing spreadsheets and databases you may encounter the following issues which you will later see in greater detail:
+</p>
+<ul>
+<li><a href="#ref_integrity">Referential data integrity</a></li>
+<li><a href="#data_redundancy">Data redundancy</a></li>
+<li><a href="#data_integrity">Data integrity and validity</a></li>
+<li><a href="#data_limiting">Data limiting</a></li>
+<li><a href="#performance">Performance and capacity</a></li>
+<li><a href="#data_entry">Convenient data entry</a></li>
+<li><a href="#reports">Reports</a></li>
+<li><a href="#programming">Programming</a></li>
+<li><a href="#multiuse">Multiuse</a></li>
+<li><a href="#security">Security</a></li>
+</ul>
+
+
+<H3>How is a database different from a spreadsheet?</H3>
+<p>
+Gradually exceeding the capacity of a mobile phone, expand your table <em>Contacts</em> adding a column (field) <em>Address</em>. Add more telephone numbers (office, home) for each person and add surnames to names. To make it simpler we assume the following:
+</p>
+<ul>
+<li>the table is limited to two people (obviously, there could be hundreds and thousands of them in a real database)</li>
+<li>there are no two persons with the same name and surname</li>
+</ul>
+
+<p>
+<table border="1" cellspacing="0" cellpadding="2">
+<tr><td colspan="3"><b>Contacts</b></td></tr>
+<tr><td><b>Name and surname</b></td><td><b>Tel</b></td><td><b>Address</b></td></tr>
+<tr><td>Joan Smith</td><td>699 23 43 12</td><td>Western Gate 1, Warsaw</td></tr>
+<tr><td>Adam Willson</td><td>711 19 77 21</td><td>London, Frogs Drive 5</td></tr>
+<tr><td>Joan Smith</td><td>110 98 98 00</td><td>Western Gate 1</td></tr>
+<tr><td>Smith Joan</td><td>312 43 42 22</td><td>Warsaw, Western Gate 1</td></tr>
+<tr><td>ADAM Willson</td><td>231 83 02 04</td><td>Frogs Drive 5, London</td></tr>
+</table>
+</p>
+
+<p>
+Such a table can be made both in a spreadsheet and in a database.
+Using a spreadsheet is very easy, of couse. What problems do we encounter at this stage?
+
+
+<a name="ref_integrity"></a>
+<h4>Referential data integrity</h4>
+<p>
+Suppose you are using a spreadsheet and you need to change the address of at least one person. You have a small problem: you often have to change the address in many lines. For example, Joan takes three lines. A real problem will arise if you forget to change one of the lines - the address asigned to this person will be <b>ambiguous</b>, hence <b>your data loses integrity</b>.
+</p>
+<p>
+Moreover there is no simple way of deleting a chosen person from the table since you have to remember about deleting all the lines releted to him or her.
+</p>
+
+<a name="data_redundancy"></a>
+<h4>Data redundancy</h4>
+<p>
+This is directly connected to the previous problem. In fields <em>Name and surname</em> and <em>Address</em> the same data is entered many times. This is typical of spreadsheets, ineffective way of storing data because the database grows unnecessarily, thus requiring more computer resources (larger size of data and slower access).
+</p>
+<p>How can you solve these problems with a database? You can split information into smaller chunks by creating additional table <em>Persons</em> with only two columns: <em>Name and suname</em> and <em>Address</em>:
+</p>
+<p>
+<p>
+<table border="1" cellspacing="0" cellpadding="2">
+<tr><td colspan="2"><b>Persons</b></td></tr>
+<tr><td><b>Name and surname</b></td><td><b>Address</b></td></tr>
+<tr><td>Joan Smith</td><td>Western Gate 1, Warsaw</td></tr>
+<tr><td>Adam Willson</td><td>Frogs Drive 5, London</td></tr>
+</table>
+</p>
+<p>
+Each line in the table <em>Persons</em> corresponds to a <b>single person</b>.
+Table <em>Contacts</em> is from now <b>in a relation</b> to the table <em>Persons</em> (see next paragraph).
+</p>
+
+<a name="data_integrity"></a>
+<h4>Data integrity and validity</h4>
+<p>
+Note the way data is entered in fields <em>Name and surname</em> and <em>Address</em>. People entering data can be fallible, sometimes even negligent. In our sample data we have both different sequence of entering name and surname (Joan Smith and Smith Joan; Adam and ADAM) and many more ways of entering the same address. Surely you can think of many other ways.
+</p>
+<p>The above problem shows that e.g. when searching the telephone number of a person whose address is "Western Gate 1, Warsaw" you will not get a full result. You will get only one line instead of three. Moreover You will also not find all the telephone numbers searching for the value "Joan Smith" in the field <em>Name and surname</em>, because "Smith Joan" will not fit to "Joan Smith".
+</p>
+<p>How can you solve these problems using a database? You can do this by changing the design of the table <em>Persons</em> by:
+</p>
+<p>
+<ol>
+<li><p><b>Dividing data</b> in the field <em>Name and surname</em> into two separate fields: <em>Name</em> and <em>Surname</em>.
+</p></li>
+<li><p><b>Dividing data</b> in the field <em>Address</em> into three separate fields <em>Street</em>, <em>House number</em> and <em>Town</em>.
+</p></li>
+<li><p><b>Guaranteeing data correctness:</b> by ensuring that no fields are empty, e.g. you must always enter house number.
+</p>
+</ol>
+</p>
+<p>
+A modified table looks something like this:
+</p>
+<p>
+<table border="1" cellspacing="0" cellpadding="2">
+<tr><td colspan="5"><b>Persons</b></td></tr>
+<tr><td><b>Name</b></td><td><b>Surname</b></td><td><b>Street</b></td><td><b>House number</b></td><td><b>City</b></td></tr>
+<tr><td>Joan</td><td>Smith</td><td>Western Gate</td><td>1</td><td>Warsaw</td></tr>
+<tr><td>Adam</td><td>Willson</td><td>Frogs Drive</td><td>5</td><td>London</td></tr>
+<tr><td colspan="5"><b>Conditions</b></td></tr>
+<tr><td>required<br>field</td><td>required<br>field</td><td>required<br>field</td><td>required<br>field</td><td>required<br>field</td></tr>
+</table>
+</p>
+<p>
+Thanks to introducing conditions <em>required field</em> we can be sure that the entered data is complete. In case of other tables you may of course allow omitting certain fields while entering data.
+</p>
+
+<a name="data_limiting"></a>
+<h4>Limiting data view</h4>
+<p>
+Spreadsheet displays all lines and columns of the table which is bothersome in case of very large data sheets. You may of course filter and sort lines in spreadsheets, however you must be extra careful while doing so. Spreadsheet users are in risk of forgetting that their data view has been filtered what can lead to mistakes. For example, while calculating sums you may think you have 100 rows of data while in fact there are 20 rows more hidden.
+</p>
+<p>If you want to work on a small subset of data, e.g. to send it for others to edit, you can copy and paste it to another spreadsheet and after editing paste the changed data back to the main spreadsheet. Such "manual" editing may cause data loss or incorect calculations.
+</p>
+<p>To limit the <b>data view</b> database applications offer <em>queries</em>, <em>forms</em> and <em>reports</em>.
+</p>
+<p>A very practical way of limitting is the following extended version of the previously described table <em>Persons</em>:
+</p>
+<p>
+<table border="1" cellspacing="0" cellpadding="2">
+<tr><td colspan="6"><b>Persons</b></td></tr>
+<tr><td><b>Name</b></td><td><b>Surname</b></td><td><b>Street</b></td><td><b>House number</b></td><td><b>City</b></td><td><b>Income</b></td></tr>
+<tr><td>Joan</td><td>Smith</td><td>Western Gate</td><td>1</td><td>Warsaw</td><td>2300</td></tr>
+<tr><td>Adam</td><td>Willson</td><td>Frogs Drive</td><td>5</td><td>London</td><td>1900</td></tr>
+</table>
+</p>
+
+<p>
+Let's assume that the newly introduced column <em>Income</em> contains confidential data. How can you share e.g. contact details of the persons with your coworkers but without <b>revealing their income</b>? It is possible if <b>you share only a query and not the whole table</b>. The query could select all columns except for the column <em>Income</em>. In database world such a query is often known as a <em>view</em>
+</p>
+
+<a name="performance"></a>
+<h4>Performance and capacity</h4>
+<p>
+Your computer is probably quite fast, however you will easily see that it doesn't help with slow, large spreadsheets. Their low efficiency is first of all due to lack of indexes accelertaing the process of data search (databases do offer them). Moreover if you use things like system clipboard, even copying data may become troublesome with time.
+</p>
+<p>Spreadsheets containing large data sets may take ages to open. Spreadsheet loads lots of data to the computer's memory while opening. Most of the data loaded are probably useless/unneccessary for you at the moment. Databases unlike spreadsheets load data from computer storage only when needed.
+</p>
+<p>
+In most cases you will not have to worry how the database stores its data. This means that unlike spreadsheets, databases do not care about:
+</p>
+<ul>
+<li>
+The sequence of lines since you can order the lines according to your needs. Moreover, you can view the same data in many views with different orders. </li>
+<li>The same goes for columns (fields) of the table.</li>
+</ul>
+</p>
+<p>
+Together with <em>Limiting data view</em> described in the previous paragraph these qualities constitute the advantage of databases.
+</p>
+
+<a name="data_entry"></a>
+<h4>Data entry
+</h4>
+<p>
+The latest editions of applications for creating spreadsheets enable you to design data-entry forms. Such forms are most useful if your data cannot be conveniently displayed in tabular view, e.g. if the text occupies too many lines or if all the columns do not fit on the screen.
+</p>
+<p>
+In this case the very way the spreadsheet works is problematic. Fields for data entry are placed loosely within the spreadsheet and very often are not secure against the user's (intentional or accidental) intervention.
+</p>
+
+<a name="reports"></a>
+<h4>Reports</h4>
+<p>
+Databases enable grouping, limiting and summing up data in a form of a <em>report</em>. Spreadsheets are usually printed in a form of small tables without fully automatic control over page divisions and the layout of fields.
+</p>
+
+<a name="programming"></a>
+<h4>Programming</h4>
+<p>
+Applications for creating databases often contain full programming languages. Newer spreadsheets have this capability too, however calculations come down to modifying the spreadsheet's fields and simple data copying, regardless of the relevance an integrity rules mentioned in previous paragraphs.
+</p>
+<p>
+Data processing within a spreadsheet is usually done via a graphical user's interface which may slow down the data processing speed. Databases are capable of working in background, outside of graphical interfaces.
+</p>
+
+<a name="multiuse"></a>
+<h4>Multiuse</h4>
+<p>It is hard to imagine a multiuse of one spreadsheet. Even if it is technically possible in the case of the latest applications, it requires a lot of discipline, attention and knowledge from the users, and these cannot be guaranteed.
+</p>
+<p>A classical way to sharing data saved in a spreadsheet with other person is to send a file as a whole (usually using e-mail) or providing a spreadsheet file in a computer network. This way of work is ineffective for larger groups of people - data that could be needed in a particular time may be currently locked by another person.
+</p>
+<p>
+On the other hand, databases have been designed mainly with multiuser access in mind. Even for simplest version locking at particular table row's level is possible, what enables easy sharing of table data.
+</p>
+
+<a name="security"></a>
+<h4>Security</h4>
+<p>
+Securing a spreadsheet or its particular sections with a password is only symbolic activity.
+After providing a spreadsheet file in computer network, every person being able to copy the file can try to break the password. It is sometimes not so hard as the password is stored in the same file as the spreadsheet.
+</p>
+<p>Features for edit locking or copy locking of a spreadsheet (or its part) is equally easy to break.
+</p>
+<p>
+Databases (except these saved in a file instead of a server) do not need to be available in a single file. You're accessing them using a computer network, usually by providing a user name and a password. You are gaining access only to these areas (tables, forms or even selected rows and columns) whose were assigned to you by setting appropriate access rights.
+</p>
+<p>
+Access rights can affect ability of data editing or only data reading. If any data is not avaliable to you, it will not be even sent to your computer, so there is no possibility of making a copy of the data in such easy way as in case of spreadsheet files.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/01_03_00_design.html b/kexi/doc/handbook/html.tmp/01_03_00_design.html
new file mode 100644
index 000000000..636501914
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/01_03_00_design.html
@@ -0,0 +1,8 @@
+<H2>1.3. Database design</H2>
+
+<p>
+Database design needs careful planning. Note that <em>Contacts</em> table redesign proposed in this section 1.2 can generate problems when the table is filled with data. For exampe, renaming a field is a simple task, but splitting <em>Address</em> field into two separate fields requires careful and tedious work.
+</p>
+<p>
+To avoid such situations, <b>rethink your database project</b> before you create it in your computer, and before you and others will start to use it. Thus, by investing some time initially, you will most probably save your time on everyday use.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/01_04_00_who_needs.html b/kexi/doc/handbook/html.tmp/01_04_00_who_needs.html
new file mode 100644
index 000000000..33a5f379c
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/01_04_00_who_needs.html
@@ -0,0 +1,24 @@
+<h2>1.4. Who needs databases?</h2>
+
+<p>
+Stick to spreadsheets if:
+</p>
+<p>
+</p><ul>
+<li>Your needs are limited and your data will never grow to large volumes (can you actually forecast that now?)</li>
+<li>You are unable to acquire the methodology of database construction.
+You may however consider either outsorcing this task to someone else or using simpler tools.</li>
+<li>You use complicated spreadsheets and you lack time or money to switch to databases.
+Think or ask someone whether this does not lead down a blind alley.
+Don't count on magical tools that would change your spreadsheet (regardless how well made) into a database.</li>
+</ul>
+
+<p>
+Consider using databases if:
+</p>
+<ul>
+<li>Your data collection expands every week.</li>
+<li>You often create new spreadsheets, copy within these and you feel that this work
+is getting more and more tedious. In this case the effort of switching to databases easily pays off.</li>
+<li>You create reports and statemets for which the table view of a spreadsheet is not suitable. You can then consider switch to using a database with form views.</li>
+</ul>
diff --git a/kexi/doc/handbook/html.tmp/01_05_00_db_software.html b/kexi/doc/handbook/html.tmp/01_05_00_db_software.html
new file mode 100644
index 000000000..0f1c08cc8
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/01_05_00_db_software.html
@@ -0,0 +1,54 @@
+<br><h2>1.5. Software for database construction</h2>
+<p>
+So far you have learnt the general characteristics of databases without going into much
+detail about specific applications for designing them.
+</p>
+<p>
+The first databases were built together with large mainframe computers in the 60s, e.g. IBM System/360.
+Those were not the days of PCs, therefore these databases required a highly specialized personnel.
+Although the old computers' hardware was unreliable, they were immeasurably slower and had less
+storage capacity, one feature of databases still remains most attractive: the data access by many
+users through network.
+</p>
+
+<p>
+In the 70s scientists formed the theory of relational databases
+(terms like: <em>table</em>, <em>record</em>, <em>column (field)</em> and <em>relationality</em> and many others).
+On the basis of this theory IBM DB2 and Oracle databases were created,
+which have been developed and used till today. In the late 70s the first PCs
+were constructed. Their users could (gradually) utilize many types of applications,
+including those for database construction.
+</p>
+<p>When it comes to large databases in companies, the situation hasn't changed:
+they still require powerful computers or computer complexes called <em>clusters</em>.
+This goes, however, beyond the topic of this manual.
+</p>
+<p>
+In the area of "accessible" databases with graphic user interface
+for PCs you can choose from the following:
+</p>
+<p>
+</p><ul>
+<li><p><a href="http://www.dbase.com/">DBase</a>
+- a tool for databases operation for DOS popular in the 80s. Files in DBase format
+are still used in some specific cases due to their simplicity.
+</p></li>
+<li><p><a href="http://msdn.microsoft.com/vfoxpro/productinfo/overview/">FoxPro</a>
+- an application similar to DBase (early 90s). After being taken over by
+Microsoft, graphic user interfaces were introduced and therefore it is
+used for creating databases on PCs. This product is still offered, though seems a bit obsolete.
+</p></li>
+<li><p><a href="http://office.microsoft.com/access/">Microsoft Access</a>
+- an application for databases (data and graphic interface design) with many simplifications,
+therefore suitable for beginners, designed in the late 80s,
+based on 16-Bit Architecture. Product offered and widely used till today, especially by small companies,
+where efficiency and multiuser requirements are not very demanding.
+</p></li>
+<li><p><a href="http://www.filemaker.com/">FileMaker</a> - popular application similar to MS Access in simplicity, operating on Windows and Macintosh platforms, offered sice 1985.
+</p></li>
+<li><p><a href="http://www.kexi.pl/">Kexi</a>
+- a multiplatform application (Unix/Linux, Windows, Mac OS X) designed in 2003,
+developed according to OpenSource principles, part of the global <a href="http://www.kde.org/">K Desktop Environment</a> project, i.e. graphic environment for Unix/Linux systems. A significant contributor to Kexi's development is OpenOffice Poland company.
+</p></li>
+</ul>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/02_00_00_idx_intro_to_kexi.html b/kexi/doc/handbook/html.tmp/02_00_00_idx_intro_to_kexi.html
new file mode 100644
index 000000000..7b9fc6591
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/02_00_00_idx_intro_to_kexi.html
@@ -0,0 +1,7 @@
+<H1>2. Introduction to Kexi</H1>
+<p>
+In this chapter you will learn what Kexi application is and how it can be of use to you. You will be able to decide which areas of your work can be simplified or automatized using Kexi.
+</p>
+<p>
+Perhaps you already know some tools for creating databases or at least spreadsheets. If so, here you can learn about the basic differences between Kexi and other popular applications.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/02_01_00_what_is_kexi.html b/kexi/doc/handbook/html.tmp/02_01_00_what_is_kexi.html
new file mode 100644
index 000000000..9cfbe28de
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/02_01_00_what_is_kexi.html
@@ -0,0 +1,25 @@
+<H2>2.1. What is Kexi?</H2>
+<p>
+Briefly speaking, Kexi is the application for creating databases and for data management. It enables you:
+</p>
+<p>
+<ul>
+<li>to build databases from scratch using the existing templates or your own database design
+<li>to develop databases and adjust them to your changing needs
+<li>to enter data conveniently using advanced data sheets and forms
+<li>to process data - to sort, filter, group and sum up and many other operations
+<li>share your data with other users <!-- TODO: also using computer networks -->
+</ul>
+</p>
+<p>
+Kexi is a member of a PlusOfficePL family of products offered by OpenOffice Polska together with technical support. Kexi nalecy do rodziny programw PlusOfficePL, oferowanej wraz ze wsparciem technicznym przez firm OpenOffice Polska.
+</p>
+<p>
+The motto of Kexi application is "Database creation for everyone". Introduction of this product was motivated by the lack of software Rapid Application Development tools similiar to Microsoft Access, FoxPro, Oracle Forms or FileMaker, that would be available for all contemporary hardware and system platforms. Kexi was made to fill this gap.
+</p>
+<p>
+Kexi is the first large KDE application available for Microsoft Windows, which makes it easier for the user to transfer data between platforms, integrate and migrate for more cost-efficient systems like Linux.
+</p>
+<p>
+Kexi is also one of the products of an international <a href="http://www.kde.org/">K Desktop Environment</a> project, a graphic environment for Unix/Linux systems. The KDE project involves many companies (including the largest ones such as Novell and IBM), organisations and independent authors.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/02_02_00_features_of_kexi.html b/kexi/doc/handbook/html.tmp/02_02_00_features_of_kexi.html
new file mode 100644
index 000000000..9666e8d1e
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/02_02_00_features_of_kexi.html
@@ -0,0 +1,15 @@
+<H2>2.2. Kexi Features</H2>
+<p>
+Kexi has many features that distinguish it from the competition:
+</p>
+<p>
+<ul>
+<li>It works under different operating systems: Linux, Windows, Macintosh.</li>
+<li>Data and design is highly portable between these platforms.</li>
+<li>Different database management systems supported: MySQL, PostgreSQL (others to come).</li>
+<li>Convinient configuration and adjusting of the application.</li>
+<li>Fully translated user interface, application is available in many language versions, without the need to install them separately. Currently about 30 languages are supported, with a target of 60.</li>
+<li>Perfect integration both in the popular KDE graphical enviroment for Linux and in Microsoft Windows.</li>
+</li>
+</ul>
+</p> \ No newline at end of file
diff --git a/kexi/doc/handbook/html.tmp/02_03_00_is_kexi_for_me.html b/kexi/doc/handbook/html.tmp/02_03_00_is_kexi_for_me.html
new file mode 100644
index 000000000..4cb22f175
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/02_03_00_is_kexi_for_me.html
@@ -0,0 +1,12 @@
+<H2>2.3. Is Kexi for me?</H2>
+<p>
+Kexi is designed to serve both beginners (when it comes to databases) as well as more advanced users who know a great deal about databases. If you belong to the latter group, you will probably want to skip the sections of this documentation you are familiar with. However you will surely benefit from reading <a href="02_04_00_differences.html">chapter 2.4</a> intended especially for experts.
+</p>
+<p>
+If you have only used spreadsheets for data processing so far, you should read chapter <a href="01_02_00_db_spreadsheet.html">1.2. Database and spreadsheet</a>.
+If you feel that while using spreadsheets some activities, like entering the data, are too tedious and time-consuming, and final result contains hard-to-find errors, then Kexi could be a good solution for you.
+Even if you use only a small part of functions provided by Kexi, your data stored in a form of database will probably be more legible and easier to comprehend for your coworkers.
+</p>
+<p>
+If you have previously used applications with a graphic user interface (which is highly probable), using Kexi should be easy since it is in many ways similar.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/02_04_00_differences.html b/kexi/doc/handbook/html.tmp/02_04_00_differences.html
new file mode 100644
index 000000000..6e0c3ccd4
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/02_04_00_differences.html
@@ -0,0 +1,103 @@
+<H2>2.4. Differences between Kexi and other applications</H2>
+
+<H3>2.4.1. Terminology used in Kexi and in other database applications</H3>
+<p>
+English terms are bracketed.
+</p>
+<p>
+<table border="1" cellspacing="0" cellpadding="5">
+<tr align="left">
+<th>Kexi</th> <th>MS Access</th> <th>dBase i FoxPro</th> <th>Paradox</th>
+</tr>
+<tr>
+<td>Database (<i>Database</i>)</td> <td>Database</td> <td>Catalog (<i>Catalog</i>)</td> <td>Directory of Related files (<i>Directory of Related files</i>)</td>
+</tr>
+<tr>
+<td>Table (<i>Table</i>)</td> <td>Table</td> <td>Database file<br>(<i>Database file</i>)</td> <td>Table</td>
+</tr>
+<tr>
+<td>Datasheet (<i>Datasheet</i>)</td> <td>Datasheet</td> <td><i>BROWSE</i> Command</td> <td><i>View</i> Command</td>
+</tr>
+<tr>
+<td>Table design (<i>Table design</i>)</td> <td>Table design</td> <td><i>MODIFY STRUCTURE</i> Command</td> <td><i>Modify Restructure</i> Command</td>
+</tr>
+<tr>
+<td>Primary key<br>(<i>Primary key</i>)</td> <td>Primary key</td> <td>Unique Index</td> <td>Key field</td>
+</tr>
+<tr>
+<td>Index (<i>Index</i>)</td> <td>Index</td> <td><i>Index</i></td> <td><i>Tools QuerySpeed</i></td>
+</tr>
+<tr>
+<td>Validation rule</td> <td>Validation rule</td> <td><i>PICTURE/VALID</i> Clause</td> <td><i>ValChecks</i></td>
+</tr>
+<tr>
+<td>Query (<i>Query</i>)</td> <td>Query</td> <td><i>Query, QBE, View</i></td> <td><i>Query</i></td>
+</tr>
+<tr>
+<td>Form (<i>Form</i>)</td> <td>Form</td> <td>Screen</td> <td><i>Forms</i></td>
+</tr>
+<tr>
+<td>Subform (<i>Subform</i>)</td> <td>Subform</td> <td><i>Multiple File Screen</i></td> <td><i>Multiple-record selection</i></td>
+</tr>
+<tr>
+<td>"Open a form" Command<br>(<i>Open a form</i></td> <td><i>Open a form</i> Command</td> <td><i>SET FORMAT TO, EDIT</i> Command</td> <td><i>Image PickForm</i></td>
+</tr>
+<tr>
+<td>Find command<br>(<i>Find command</i>)</td> <td>Find command</td> <td><i>LOCATE AND SEEK</i> Command</td> <td><i>Zoom</i></td>
+</tr>
+<tr>
+<td>List box, combo box</td> <td>List box, combo box</td> <td>Pick list</td> <td>Lookup</td>
+</tr>
+<tr>
+<td>Macro (<I>Macro</I>)</td> <td>Macro</td> <td>-</td> <td>-</td>
+</tr>
+<tr>
+<td>Script (<I>Script</I>)</td> <td>Script</td> <td>Program file</td> <td>Script</td>
+</tr>
+</table>
+</p>
+
+<p>
+As you can see from the table above the terminology used in Kexi is close to the one used in MS Access application.
+</p>
+
+<H3>2.4.2. Basic data types used in Kexi and in other database applications</H3>
+<p>
+<table border="1" cellspacing="0" cellpadding="5">
+<tr align="left">
+<th>Kexi</th> <th>MS Access</th> <th>dBase i FoxPro</th> <th>Paradox</th>
+</tr>
+<tr>
+<td>Text (<I>Text</I>)</td> <td>Text</td> <td>Character</td> <td><I>Alphanumeric</I></td>
+</tr>
+<tr>
+<td>Long text (<I>Long text</I>)</td> <td>Memo</td> <td><I>Memo</I></td> <td><I>Memo</I></td>
+</tr>
+<tr>
+<td>Date, Time (<i>Date</i>, <i>Time</i>)</td> <td>Date, Time</td> <td><I>Date</I></td> <td><I>DateTime</I></td>
+</tr>
+<tr>
+<td>Object (<I>Object</I>)</td> <td>OLE Object (<I>OLE Object</I>)</td> <td><I>General</I></td> <td><I>OLE, Graphical, Binary</I></td>
+</tr>
+<tr>
+<td>Yes/No (<I>Yes/No</I>)</td> <td>Yes/No</td> <td>Logical</td> <td><I>Logical</I></td>
+</tr>
+<tr>
+<td>Integer number (<I>Integer number</I>)</td> <td>Number (<I>Integer</I>)</td> <td><I>Numeric</I></td> <td><I>Integer</I></td>
+</tr>
+<tr>
+<tr>
+<td>Big Integer number (<I>Big integer number</I>)</td> <td>Liczba cakowita duga (<I>Long integer</I>)</td> <td><I>Numeric</I></td> <td><I>Long Integer</I></td>
+</tr>
+<td>Floating-point number:<br>Single/Double precision<br>(<I>Floating-point number</I>)</td> <td>Single/Double precision number<br>(<I>Single/Double precision number</I>)</td> <td><I>Float</I></td> <td><I>Number</I></td>
+</tr>
+</table>
+</p>
+
+<!--
+Szczegowy wykaz typw danych obsugiwanych przez Kexi, wraz z zakresami, znajduje si w dodatku ###.
+
+ --terminologia (tabelka roznic Kexi/MSA/dBase,FoxPro/Paradox) [str.18]
+ --rnice w sposobie uywania
+ rodowisko pracy z Kexi (GUI, CLI)
+--> \ No newline at end of file
diff --git a/kexi/doc/handbook/html.tmp/04_00_00_idx_basics_kexi.html b/kexi/doc/handbook/html.tmp/04_00_00_idx_basics_kexi.html
new file mode 100644
index 000000000..9b6c31969
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_00_00_idx_basics_kexi.html
@@ -0,0 +1,3 @@
+<H1>4. Kexi usage basics</H1>
+
+<!-- todo - index -->
diff --git a/kexi/doc/handbook/html.tmp/04_01_00_project_files.html b/kexi/doc/handbook/html.tmp/04_01_00_project_files.html
new file mode 100644
index 000000000..4ecab5b89
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_01_00_project_files.html
@@ -0,0 +1,13 @@
+<H2>4.1. Project files</H2>
+<p>Many applications such as OpenOffice.org or Microsoft Excel create files which are called <em>documents</em>. Kexi also creates files but we will call them <em>Kexi projects</em> or </em>Kexi database files</em>.
+</p>
+
+<H4>Notes</H4>
+<ul>
+<li>Kexi database can also be stored on database servers. THis is why, to avoid ambiguity, we talked above about <em>Kexi database files</em> and not <em>Kexi database</em>. The latter will be used however if the way you store the database is irrelevant.</li>
+</ul>
+<p>
+<img src="img/04_01_00_kexi_project_file.png">
+<br>Kexi project file on the desktop<br><br>
+The name of the Kexi database file has extention <em>.kexi</em> both on MS Windows and Linux.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/04_02_00_running_kexi.html b/kexi/doc/handbook/html.tmp/04_02_00_running_kexi.html
new file mode 100644
index 000000000..fdf051300
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_02_00_running_kexi.html
@@ -0,0 +1,25 @@
+<H2>4.2. Running Kexi</H2>
+
+<H3>4.2.1. Linux or Unix operating systems</H3>
+<p>
+In the <em>K</em> menu of the K Desktop Environment click <em>Office</em> folder and then click <em>Kexi</em> entry.
+</p>
+<p>
+You may also run Kexi from command line (e.g. using <em>Konsole</em> application or hotkey Alt+F2) and entering:
+</p>
+<pre>
+kexi
+</pre>
+<p>
+See chapter <a href="06_04_00_using_CLI.html">6.4. Using the command line</a>.
+</p>
+
+<H3>4.2.2. Microsoft Windows operating systems</H3>
+<p>
+Choose <em>Applications</em> from menu Start and <em>Kexi</em> folder. Click <em>Kexi</em> shortcut.
+</p>
+
+<H3>4.2.3. Mac OS X operating systems</H3>
+<p>
+In folder <em>Applications</em> click icon <em>Kexi</em>.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/04_03_00_creating_database.html b/kexi/doc/handbook/html.tmp/04_03_00_creating_database.html
new file mode 100644
index 000000000..aa27e210c
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_03_00_creating_database.html
@@ -0,0 +1,18 @@
+<H2>4.3. Creating a new database project</H2>
+<ol>
+<li><p>
+Run Kexi (see <a href="04_02_00_running_kexi.html">Running Kexi</a>). You will see the following window:
+
+<br><img src="img/04_03_00_startup_window.png">
+<br>Kexi startup window<br><br>
+
+</p></li>
+<li><p>Click <em>Ok</em> button to run the creation of a new project. <p></li>
+<li><p>Click <em>Cancel</em> button or press <kbd>Escape</kbd> to close window. You will see an empty application window (only menu).
+</p></li>
+</ol>
+
+<p>
+</p>
+<!-- TODO: info about using 'new project wizard' -->
+
diff --git a/kexi/doc/handbook/html.tmp/04_04_00_project_opening.html b/kexi/doc/handbook/html.tmp/04_04_00_project_opening.html
new file mode 100644
index 000000000..65da08546
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_04_00_project_opening.html
@@ -0,0 +1,59 @@
+<H2>4.4. Opening an existing Kexi database file</H2>
+<p>
+To open an existing Kexi database file:
+</p>
+<p>
+<ul>
+<li><a href="#window_open_existing">select it in <em>Open Existing Project</em></a> window; or</li>
+<li><a href="#open_icon">open it by clicking on .kexi file's icon</a></li>
+</ul>
+</p>
+
+<a name="window_open_existing"></a>
+<H3>4.4.1. Opening a database file in <em>Open Existing Project</em> window</H3>
+<p>
+<ol>
+<li><p>
+Run Kexi application (see <a href="04_02_00_running_kexi.html">Running Kexi</a>). You should see &quot;Choose Project&quot; startup dialog window. Choose <em>Open Existing Project</em> tab. You will see following dialog:
+ <br><img src="img/04_04_01_startup_open_existing.png">
+ <br>Startup window: <em>Open Existing Project</em> tab<br><br>
+</p></li>
+<li><p>From <em>Look in</em> drop down, pick a folder containing a file you are looking for.
+</p></li>
+<li><p>Pick a file, you want to open ir enter its name using the <em>File name</em> box.
+</p></li>
+<li><p>Click <em>OK</em> button.
+</p></li>
+</ol>
+<H4>Notes</H4>
+<p>
+<ul>
+<li>By default <em>File type</em> drop down list has <em>All Supported Files</em> setting chosen.
+In case the file you are looking for has other extension, you can change selection of <em>File type</em> drop down list to <em>All files</em> to display all available files (regardless of an extension).
+</li>
+<li>If you have selected a file of external type like MS Access' .mdb file, Kexi will provide you with option to import the file.
+<!-- todo an advice to read "importing" chapter will be placed here -->
+</li>
+<li>If you have selected a <em>connection data</em> file (with .kexic extension) or a <em>shortcut to a project on database server</em> file (with .kexis extension), Kexi will display appropriate windows.
+</li>
+</ul>
+</p>
+
+<a name="open_icon"></a>
+<H3>4.4.2. Opening an existing Kexi database file by clicking on .kexi file's icon</H3>
+<p>
+Click file's icon using your file manager or desktop:
+</p>
+<img src="icons/mime-kexiproject_sqlite.png" class="icon"/>
+<p>
+Kexi will open this database project automatically.
+</p>
+
+<H4>Notes</H4>
+<p>
+<ul>
+<li>
+<p><b>Note about databse files accessed remotely.</b> You may want to open a database file that is located on a remote source (e.g. an web or FTP server or MS Windows share). K Desktop Environment allows you to open files from remote sources directly in applications and saving changes back to the source, but this is not the case with database files. By clicking on a database file located on a remote source, you will cause a download the file to a temporary directory on your computer and all eventual your changes will be made to this local file. The remote original of the file will remain unchanged after you close Kexi, so it's recommended to copy (download) the file to your computer first, then open the file and copy it back to remove source if you want to make it up to date.</p>
+</li>
+</ul>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/04_05_00_help_on_help.html b/kexi/doc/handbook/html.tmp/04_05_00_help_on_help.html
new file mode 100644
index 000000000..cd1d13392
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_05_00_help_on_help.html
@@ -0,0 +1,10 @@
+<H2>4.5. Using built-in help</H2>
+<p>Following ways to get built-in help in Kexi are available:
+<ul>
+<li><b>The Handbook in form of electronic document.</b>
+THe Handbook is available by presing <kbd>F1</kbd> key or selecting <em>Help&gt;Kexi Handbook</em> from the menubar.
+</li>
+<li><b><em>What's This?</em> hints.</b> Select <em>Help&gt;What's This?</em> from the menu bar and click on an area of the application to get hints about it.
+</li>
+</ul>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/04_06_00_main_application_elements.html b/kexi/doc/handbook/html.tmp/04_06_00_main_application_elements.html
new file mode 100644
index 000000000..7411b12be
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_06_00_main_application_elements.html
@@ -0,0 +1,21 @@
+<H2>4.6. Main application elements</H2>
+<p>
+ <br><img src="img/04_06_00_main_window.png">
+ <br>Kexi's main window<br><br>
+
+Main elements of Kexi application's window are:
+<ul>
+<li><p><b>Menubar</b> - contains available commands for the application. You will find detailed description of any of the commands in <a href="aa_00_00_menu.html">Appendix ??</a>.
+</p></li>
+<li><p><b>Toolbar</b> - contains most frequently used commands.
+</p></li>
+<li><b><a href="04_06_01_project_navigator.html">Project Navigator's pane</a></b> - contains a list of any object (tables, queries, forms, ...) created within the currently opened database project. The navigator also contains small toolbar with most usable commands related to the database objects.
+</li>
+<li><p><b><a href="04_06_02_object_windows.html">Opened database objects area</a></b> - a central area of the application taking most of the screen space. For IDEAl user interface mode it contains a switchable tabs with windows that are always maximized. For Childframe user interface mode it contains floating windows.
+</li>
+<li><p><b><a href="04_06_03_property_editor.html">Properties pane</a></b> - contains a list of properties of currently activated database object. For certain objects (e.g. form's widgets) it can be consisted of several tabs.
+</p></li>
+<li><p><b>Taskbar</b> - contains a list of currently opened windows with database objects. For IDEAl user interface mode, it is available as a number of tabs. For Childframe user interface mode, it is available as a number of buttons, behaving just like your operating system's taskbar.
+</p></li>
+</ul>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/04_06_01_project_navigator.html b/kexi/doc/handbook/html.tmp/04_06_01_project_navigator.html
new file mode 100644
index 000000000..c0df2e29b
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_06_01_project_navigator.html
@@ -0,0 +1,33 @@
+<H3>4.6.1. Project Navigator pane</H3>
+<p>
+Project Navigator pane is one of the most frequently used elements of the Kexi main window.
+The pane contains a list of all objects created within the currently opened Kexi database project. The objects are splitted into groups: tables, queries, forms.
+<br>
+</p>
+<a name="mini_toolbar"></a>
+<p>
+Project Navigator pane also contains a <b>small toolbar for most frequently used commands</b> (from left to right): <em>Open selected object</em>, <em>Design selected object</em>, <em>Create a new object</em>, and <em>Delete selected object</em>.
+</p>
+<p>
+<img src="img/04_06_01_nav_mini_toolbar.png">
+<br>A toolbar in the Project Navigator pane<br><br>
+</p>
+<p>
+</p>
+<p>
+For each object on the list context menu is available using the &RMB;. For example, this is context menu for <em>persons</em> table:
+ <br><img src="img/04_06_01_context_menu.png">
+ <br>Project Navigator pane's context menu<br><br>
+
+Commands of this menu is documented in <a href="aa_05_00_menu.html#nav_panel_menu">Appendix A.10<!-- TODO (js) APPENDIX number --></a>.
+<p>
+See also a list of available shortcuts in <a href="ab_00_00_shortcuts.html#nav_panel">Appendix B.2. Project Navigator pane<!-- TODO (js) APPENDIX number --></a> .
+</p>
+
+<p>
+Double clicking with &LMB; on the object's name on the list allows to open the object in Data View. If the object's window was alread opened, the action just activates the window without switching it's view mode.
+</p>
+<p>
+Note that your operating system can be set up to handle single clicks instead of double clicks. In this case it is enough to single click on the object name to open it's window.
+<!-- TODO (js) but then how to select an object without opening it? -->
+</p>
diff --git a/kexi/doc/handbook/html.tmp/04_06_02_object_windows.html b/kexi/doc/handbook/html.tmp/04_06_02_object_windows.html
new file mode 100644
index 000000000..42bd0a08a
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_06_02_object_windows.html
@@ -0,0 +1,35 @@
+<H3>4.6.2. Database object windows</H3>
+
+<H4>Opening object's window</H4>
+<p>
+To open a database object in a window:
+<ol>
+<li><p>Select the object in <a href="04_06_01_project_navigator.html">Project Navigator pane</a></p></li>
+<li><p>Click <img src="icons/edit.png" class="icon"> <em>Open</em> button on the <a href="04_06_01_project_navigator.html#mini_toolbar">Project Navigator pane's toolbar</a>.</p></li>
+</ol>
+
+<H4>Commands related to object windows</H4>
+
+<H5>Closing an object window</H5>
+<p>
+When the <a href="08_01_00_mdi.html#mode_ideal">IDEAl</a> user interface mode (the default) is used, each window has it's own tab. Move the mouse pointer to the tab. You will see <img src="fileclose.png" class="icon"> close button. Click it to close the tab.
+</p>
+<p>
+In the <a href="08_01_00_mdi.html#mode_childframe">Childframe</a> on the right hand of each opened window there are buttons you can use to control the window. Click the first one on the right hand to close the window.
+</p>
+<p>
+Alternatively, regardless of the user interface mode you are using, you can select <em>Window</em> -&gt;<em>Close</em> from the Menubar.
+</p>
+
+<H5>Window's buttons for Childframe user interface mode</H5>
+<p>
+ <br><img src="img/04_06_02_window_buttons.png">
+ <br>Window's buttons<br><br>
+Other button's (from right to left) can be used to: maximize, minimize and undock the window.
+</p>
+<p>
+There's a small icon on the left hand of the window which can be clicked to show a context menu with commands related to the window.
+</p>
+<p>
+See also <a href="08_02_00_dock_undock.html">8.2. Docking and undocking of the windows<!-- TODO (js) chapter# --></a>.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/04_06_03_property_editor.html b/kexi/doc/handbook/html.tmp/04_06_03_property_editor.html
new file mode 100644
index 000000000..d823c0565
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/04_06_03_property_editor.html
@@ -0,0 +1,44 @@
+<H3>4.6.3. Property Editor pane</H3>
+<p>
+The Property Editor pane allows to change properties of object displayed in the active window. Depending on the context, the pane is consisted of one or more tabs. The first, always visible, <em>Properties</em>tab contains the list of available properties.
+</p>
+<p>
+ <img src="img/04_06_03_prop_panel.png">
+ <br>Property Editor<br><br>
+</p>
+<p>
+Rules for using the Property Editor:
+</p>
+<p>
+<ul>
+<li>One row means a single property.</li>
+<li>You can use mouse or keyboard to change values of particular properties.</li>
+<li>Most frequently used types of property values are:
+<ul>
+<li>a number; you can enter the value directly or increate or decrease it's value clicking with the &LMB; on the arrows: <img src="img/04_06_03_prop_arrows.png" class="icon"></li>
+<li>text</li>
+<li>drop down list of values</li>
+<li>Yes/No value; in form of a button you can toggle; button toggled on means <em>yes</em> (<em>true</em>) value, button toggled off means <em>no</em> (<em>false</em>) value; see the above figure</li>
+</ul>
+</li>
+<li>There is no need for accepting changed value: changes are visible immediately after moving to a different row of the Property Editor's list or by pressing <em>Enter</em> key.</li>
+<li>Names of the recently changed properties that not yet were stored in the database are marked with bold text.</li>
+<li>After changing a value of property, a special <em>Undo changes</em> button appears on the right hand of the Property Editor's list:<br>
+ <img src="img/04_06_03_prop_undo.png" class="icon"><br>
+By clicking it you can revert the value of the property to the original value that was loaded from the database upon opening the database object. The button is only visible when the property is actually highlighted.
+</li>
+</ul>
+</p>
+<p>
+Property Editor pane is empty if:
+<ul>
+<li>no single database object's window is opened, or</li>
+<li>active database object's window does not offer properties; it is usually the case when it is opened in Data View instead of Design View</li>
+</ul>
+</p>
+
+<!-- TODO (js) describe custom form designer's tabs -->
+
+<p>
+See also the list of keyboard shortcuts available for the Property Editor pane in appendix <a href="ab_00_00_shortcuts.html#prop_panel">B.3. Property Editor pane<!-- TODO (js) chapter# --></a>.
+</p>
diff --git a/kexi/doc/handbook/html.tmp/05_00_00_idx_building_simple_database.html b/kexi/doc/handbook/html.tmp/05_00_00_idx_building_simple_database.html
new file mode 100644
index 000000000..77be8d8c0
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/05_00_00_idx_building_simple_database.html
@@ -0,0 +1,18 @@
+
+<H1>5. Building Simple Databases</H1>
+<p>
+ To learn basics of Kexi usage, first you could build a simple database utilizing most elementary Kexi's features. To make things simpler, advanced database design topics will not be covered here.
+</p>
+<p>
+Start by creating a new empty <em>Phone Book</em>. See chapter <a href="04_03_00_creating_database.html">4.3. <!-- TODO number -->Creating a new database project</a> for information how to do this.
+</p>
+<p>
+Having a new empty database project, perform the following steps:
+<ol>
+<li>Design database tables. Read section <a href="05_01_00_table_designing.html">5.1</a>.</li>
+<li>Enter data to tables. Read section <a href="05_02_00_data_entering_into_tables.html">5.2</a></li>
+<li>Design database queries. Read section <a href="05_03_00_query_designing.html">5.3</a></li>
+<li>Design forms. Read section <a href="05_04_00_form_designing.html">5.4</a></li>
+</ol>
+
+</p>
diff --git a/kexi/doc/handbook/html.tmp/05_01_00_table_designing.html b/kexi/doc/handbook/html.tmp/05_01_00_table_designing.html
new file mode 100644
index 000000000..14c0b5113
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/05_01_00_table_designing.html
@@ -0,0 +1,109 @@
+
+<H2>5.1. Designing Database Tables</H2>
+
+<p>
+First, there will be two tables added to your database: <em>persons</em> and <em>phone_numbers</em>. These are exactly the same tables as described in chapter <a href="01_02_00_db_spreadsheet.html">1.2. A database and a spreadsheet</a>. A layout for <em>Persons</em> can be found in section <a href="01_02_00_db_spreadsheet.html#data_integrity">Data integrity and validity</a> in that chapter.
+</p>
+
+<p>
+<ol>
+<li><p>
+Select <em>Insert-&gt;Table</em> from the Menubar. You can also use button
+<img src="icons/table_newobj.png" class="icon"> <em>Create object: table</em> on the Project Navigator's toolbar (see section <a href="04_06_01_project_navigator.html">4.6.1. Project Navigator pane</a>).
+</p></li>
+<li><p>
+Table Designer's window will appear. Looking at the top of designer's window you will notice that Kexi proposed you a generic name like <em>template</em> for the new table. The table design is not saved yet so you will be able to assign more proper name later. Moreover, because of the same reason, the table name is not yet visible in the <a href="04_06_01_project_navigator.html">Project navigator</a>.
+</p></li>
+</ol>
+
+<a name="design_window"></a>
+<H3>5.1.1. Table Designer window</H3>
+
+<p>
+Table Designer window consists of following columns:
+<ul>
+<li><b>PK</b> - Primary key. It will be discussed this topic in <a href="06_00_00_idx_building_advanced_database.html">chapter 6</a>.</li>
+<li><b>Filed Name</b> - field name (in other words: column name) which will be visible during data entering.</li>
+<li><b>Data Type</b> - a combo box containing a list of data types allowing to set a main rule for entered data for a given field. For example, when integer number data type is set for a field, database user will not able to enter a letter characters into this field.
+<!-- More information about data types is available in section <a href="??_??_??_datatypes.html">???. Data types</a>. -->
+</li>
+<li><b>Comments</b> - you can enter here any informations useful for understanding what the given field is provided for. This additional text will be saved within the table design and only visible when it is designed.</li>
+</ul>
+</p>
+<p>
+In <em>Table designer</em> window, every row corresponds to a single table field. You can recognize you are in <em>design mode</em> because the <nobr><img src="icons/state_edit.png" class="icon"> <em>Switch to Design View mode</em></nobr> button is toggled on within the main Kexi toolbar (see the figure below).
+</p>
+<p>
+To start entering the <em>Persons</em> table design:
+<ol>
+<li>
+<p>In the first row click on the cell in <em>Field name</em> column and enter <kbd>name</kbd> field name.
+ <br><img src="img/05_01_00_defining_columns.png">
+ <br>Entering names for table fields<br><br>
+</p>
+<a name="note_field_names"></a>
+<H4>Notes about field names</H4>
+<p>
+ <ul>
+ <li>You must not left name for any of your fields empty.</li>
+ <li>Field names could not contain natonal character (like , , ), special characters or space characters. The names must only contain roman letters, numbers and underscore sign &quot;_&quot;. Use the latter instead of spaces or dashes.</li>
+ <li>Field names must be started with a roman letter or underscore sign &quot;_&quot;, never with a number.</li>
+ <li>It does not matter whether you are using small or capital letters. For Kexi databases, &quot;Persons&quot; is the same ad &quot;persons&quot; name.</li>
+ </ul>
+</p>
+</li>
+<li><p>
+Use down arrow key to move to next row. In the <em>Data Type</em> column, <em>Text</em> type appeared automatically. This is what you actually expected since a person's name should be in fact of type text.
+</p></li>
+<li><p>
+In a similar way, enter the following fields into the table design:
+ <ul>
+ <li><kbd>surname</kbd></li>
+ <li><kbd>street</kbd></li>
+ <li><kbd>house_number</kbd></li>
+ <li><kbd>city</kbd></li>
+ </ul>
+</p></li>
+<li><p>
+All above fields except <kbd>house_number</kbd> are of type text. Change <kbd>house_number</kbd> field's type to <em>integer number</em>. To do this, click on a cell in the <em>Data Type</em> column, <kbd>house_number</kbd> row and then click on drop down list's button <img src="icons/dropdown_button.png" class="icon"> (you can also press F4 or Alt+Down arrow keys). The list of data types will appear. Select <em>Integer number</em> type.
+ <br><img src="img/05_01_01_changing_datatype.png">
+ <br>Changing data type of a filed to integer number<br><br>
+Since now, <kbd>house_number</kbd> field only acepts numbers.
+</p></li>
+<!-- TODO setting additional properties: e.g. caption -->
+<li><p>
+<em>Persons</em> table desgin is ready. Click <nobr><img src="icons/state_data.png" class="icon"> <em>Switch to Data View</em></nobr> button on the toolbar to finish designing and switch to Data View for the table. This allows you entering data into the table.
+</p></li>
+<li><p>
+As the design is not yet saved in the database, &quot;Save Object As&quot; dialog window appears. You need to specify the name for the new table.
+ <br><img src="img/05_01_01_entering_table_name.png">
+ <br>Entering table name before saving its design<br><br>
+
+Kexi offers a generic name like <em>Table1</em>. To change the name, enter <kbd>Persons</kbd> into the <em>Caption</em> field and press <kbd>Enter</kbd> key or click <em>OK</em> button. <em>Caption</em> field will be used to displaying the table to database end-users, e.g. as a form. Unlike the name, the caption can contain any characters including spaces a special characters.
+<br/>
+<br/>
+Note that filling <em>Caption</em> field automatically fills <em>Name</em> field. For your convenience the rule for using only latin letters, digits and the &quot;_&quot; character is kept. You can alter contents of the <em>Name</em> field if you want to.
+
+ <br><img src="img/05_01_01_automatic_names.png">
+ <br>Example of automatically filled Name field<br><br>
+</p></li>
+<li><p>
+You are asked about an agreement for automatic adding of primary key to the table. The idea of primary keys is described in <a href=""><!-- TODO chapter # -->chapter 6</a>. Click <em>Add primary key</em> button to continue.
+ <br><img src="img/05_01_01_pkey_recommended.png">
+ <br>A question about automatic adding a primary key<br><br>
+</p></li>
+<li><p>
+<em>Persons</em> table has been created and opened in Data View. Its name appears in the Project Navigator pane.
+ <br><img src="img/05_01_01_table_created.png">
+ <br><em>Persons</em> table in the Project Navigator pane<br><br>
+</p></li>
+<li><p>
+Create <em>phone_numbers</em> table, in a similar way as <em>persons</em> table.
+</p></li>
+<li><p>
+Create <kbd>person</kbd> field of type <em>Integer number</em> and <kbd>phone</kbd> of type <em>Text</em>. Do not use a number type here because phone number can have many different forms and prefixes.
+</p></li>
+<li><p>
+Click <nobr><img src="icons/state_data.png" class="icon"> <em>Switch to Data View</em></nobr> button on the toolbar and enter <em>Phones</em> caption for the table. As for your previous table, allow Kexi to automatically create primary key.
+</p></li>
+</ol>
diff --git a/kexi/doc/handbook/html.tmp/05_02_00_entering_data_into_tables.html b/kexi/doc/handbook/html.tmp/05_02_00_entering_data_into_tables.html
new file mode 100644
index 000000000..2a4edfa25
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/05_02_00_entering_data_into_tables.html
@@ -0,0 +1,38 @@
+
+<H2>5.2. Entering Data Into Tables</H2>
+<p>
+You have designed two <em>Persons</em> and <em>phone_numbers</em> tables. Both contain no data yet. You can enter some. In this chapter you will learn how to do this fast and effectively.
+</p>
+<p>
+<ol>
+<li><p>
+Start with <em>persons</em> table. Open it in Data View using <a href="04_06_01_project_navigator.html">Project Navigtor</a>.
+Current cell is marked with (usually black) rectangle, a <em>cursor</em>. Contents of the cell, if exists, is highlighted with a diferent color. Current row, i.e. the one you have placed your rectangular cursor in, is marked on the left hand using with an arrow symbol <img src="icons/button_tableview_currentrow.png" class="icon">.
+</p>
+<p>You can navigate through table cells using mouse or arrow keys, Page Down, Page Down, Home, End keys. To learn more about available of the key bindings for the data table view, see the section <!-- TODO chapter moved --><a href="ab_00_00_shortcuts.html#data_table">B.4. Data Table</a> in the Appendix B. Key Bindings.
+</p>
+<p>
+Initially, after opening table <em>Persons</em>, the cursor is placed in the <em>id</em> column. The column has autonumber property defined, marked with blue <em>(autonumber)</em> text in the last row. That means you do not have to enter values there by hand when entering data for a new row because the cell will be filled automatically with successive numbers.
+ <br><img src="img/05_02_00_data_editing.png">
+ <br>Data entry<br><br>
+</p></li>
+<li><p>
+Inserting new rows and entering data for them in &kexi; is different from the way of doing this in spreadsheets.
+To enter data for a new row, you need to use the arrow keys or mouse, to move your cursor to the special empty last row marked with <nobr>,,plus'' <img src="icons/button_tableview_newrow.png" class="icon"> sign. Place your cursor in (second) <em>name</em> column and enter a person's name. </nobr> Also enter surname, street, house number and city. When done, move down arrow or mouse button to the last empty row to accept new row inserting. You can insert rows you can see in the figure and add more your own.
+<h4>Details About Actions Available While Entering Data Into Tables</h4>
+<ul>
+<li>When you started entering data by entering the first character, editing of the current row is started. On the left hand of the data table, <nobr>pencil <img src="icons/button_tableview_editrow.png" class="icon"> symbol appears.</nobr></li>
+<li>
+Double clicking a cell with &RMB; or pressing <em>Enter</em> or <em>F2</em> key also starts editing of the current row.</li>
+<li>Pressing <em>Esc</em> key when, a contents of a cell is edited, <b>cancels changes made to this cell</b>. However <nobr>pencil <img src="icons/button_tableview_editrow.png" class="icon"></nobr> symbol will not disappear because you can still move to different cell of the edited row to change its contents. To <b>cancel changes made to entire edited row</b>, press <em>Esc</em> key again.</li>
+<li>Instead of pressing <em>Esc</em> key, you can click <img src="icons/button_cancel.png" class="icon"> toolbar button or select <em>Data-&gt;Cancel Row Changes</em> from the menubar.</li>
+<li>Click <em>Shift+Enter</em> keys to accept changes made to all cells in the currently edited row. You can also click <img src="icons/button_ok.png" class="icon"> toolbar button or select <em>Data-&gt;Save Row</em> from the menubar.</li>
+</ul>
+</p></li>
+<li><p>
+Fill the <em>phone_numbers</em> table with data, e.g. similar to provided in the figure below. In the <em>persons</em> column you need to provide a number of the person existing in the <em>persons</em> table.
+ <br><img src="img/05_01_01_table2_contents.png">
+ <br>Example contents of the <em>phone_numbers</em> table<br><br>
+</p></li>
+</ol>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/05_03_00_query_designing.html b/kexi/doc/handbook/html.tmp/05_03_00_query_designing.html
new file mode 100755
index 000000000..5a483c761
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/05_03_00_query_designing.html
@@ -0,0 +1,37 @@
+
+<H2>5.3. Designing Database Queries</H2>
+<p>
+A database's primary purpose is to store and help extracting information you are looking for. Unlike databases written on a paper sheets, Kexi database allows you to specify for much more search criterias. Results are returend faster without much dependency of. This all is a power of databases, however to be able to perform efffective <em>queries</em> in your database, you need to learn how to tell the database what are you looking for.
+</p>
+<p>
+With database queries you can limit data coming from a table to a predefined set of rows and columns as well as dynamically <em>join</em> data coming from multiple tables.
+</p>
+<p>
+To see how queries work in practice you will create <em>contacts</em> query joining data from two tables <em>persons</em> and <em>phone_numbers</em> designed in <a href="05_01_00_table_designing.html">chapter 5.1</a> and filled with data in <a href="05_02_00_entering_data_into_tables.html">chapter 5.2</a>.
+</p>
+
+<p>
+<ol>
+<li><p>
+Create a new empty query by selecting <nobr><img src="icons/query_newobj.png" class="icon"> <em>Insert-&gt;Query</em></nobr> from the menubar. Design window will appear similar to the one presented oi the figure below. The window is horizontally splitted into two areas: query relationships on the top and query columns below.
+<!-- TODO update screenshot with names of window's areas -->
+</p></li>
+<li><p>
+Select table <em>persons</em> in the drop down list <em>Table:</em> located at the top of the window and click <em>Add</em> button. A graphical representation of the table will appear in the the relations area. Do the same for <em>phone_numbers</em> table to insert it too, as in the figure below.
+ <br><img src="img/05_03_00_query_design.png">
+ <br><em>contacts</em> query design<br><br>
+</p></li>
+<li><p>
+Add query relationship using mouse drag &amp; drop technique: click the field <em>id</em> in the table <em>persons</em> table, drag it and drop onto the <em>person</em> field of the <em>phone_numbers</em> table. This will <b>join both fields by creating a new relationship</b>.
+</p></li>
+<li><p>
+Doube-clik the <em>name</em> field in the <em>persons</em> table, to add the field as a <b>query column</b>. In a similar way, add <em>surname</em>, <em>street</em>, <em>house_number</em>, <em>city</em> fields from the <em>persons</em> table and <em>phone</em> from the <em>phone_numbers</em> table.
+</p></li>
+<li><p>
+Query design is now ready to test it. Click <nobr><img src="icons/state_data.png" class="icon"> <em>Switch to data view</em></nobr> button on the toolbar, to switch from design to viewing the data provided as query results.
+ <br><img src="img/05_03_00_query_results.png">
+ <br><em>Contacts</em> query results<br><br>
+</p></li>
+<li>Save the query design for later use by clicking <nobr><img src="icons/filesave.png" class="icon"> <em>Save</em></nobr> button on the toolbar. You can also use <em>File-&gt;Save</em> from the menubar or press <em>Ctrl+S</em> keys. Because the query design has not been saved yet, you will be asked to specify a name for it. Enter <em>Contacts</em> text in the <em>caption</em> field.
+</ol>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/05_04_00_form_designing.html b/kexi/doc/handbook/html.tmp/05_04_00_form_designing.html
new file mode 100644
index 000000000..9cb671d13
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/05_04_00_form_designing.html
@@ -0,0 +1,510 @@
+<H2>5.4. Designing Forms</H2>
+<p>
+</p>
+
+<a name="basics"></a>
+<H3>5.4.1. Most important terms</H2>
+<p>
+<ul>
+<li><b>Form</b> - a window provided for easy data entry and presentation on the computer screen.
+</li>
+<li><b>Form's data source</b> - database table or query providing data displayed in the form. The data source is needed because forms itself are only <b>tools</b> for displaying and entering data, while tables and queries are the source of data. New, empty forms have no data source assigned, so they are not displaying any data from your database unless you assign a data source to them.</li>
+<li><b>Form field</b> - direct equivalent of column in a table or query. Most frequently used are fields for displaying text and numbers. Entering a new value or changing the existing value of such a field causes a change in <em>bound</em> table or query column (after accepting the change).</li>
+<li><b>Form design</b> - tasks you are performing to define appearance and functions of the form. To do this, you need to provide data source, insert form fields of various types and place them at the appropriate location.</li>
+ <li><b>Form widget</b> - form's element. Main widget types are:
+ <ul>
+ <li>Widgets displaying information, e.g. text box or image box. Each widget of this type can be <em>bound</em> to a data source field (a table or a query column). Therefore, such widgets are called in short <em>form fields</em>.</li>
+ <li>Widgets able to perform a specified action, e.g. a push button that can close the current form. Within other applications this widget type is sometimes called <em>form control</em> because it can perform previously defined action of <b>controlling</b> your database application's behavior.</li>
+ <li>Other widgets allowing to enrich form's appearance, e.g. &quot;line widget&quot; can visually separate two form areas.</li>
+ </ul>
+</li>
+<li><p>
+<a name="widget_containers"></a>
+<b>Container widget</b> - a widget that can <b>contain</b> other widgets within its area. For example, frame widget or tab widget are containers. Form's surface itself is a container as well. Command button cannot be called as container because it is not possible to insert a widget inside it. In more complex cases, container widget can be inserted inside a container, so nesting is possible.
+
+ <br><img src="img/05_04_01_widget_containers.png">
+ <br>Example container widgets<br><br>
+</p>
+</li>
+</ul>
+</p>
+
+
+<a name="forms_and_tables"></a>
+<H3>5.4.2. Forms versus tables</H3>
+<p>
+In chapter <a href="05_02_00_entering_data_into_tables.html">5.2</a> you learned about how to enter data directly into tables using their data sheet view. However, in many cases forms are better suited for data entry:
+<ul>
+<li>Table can contain too many columns to display them on your screen. Form can display such a data using multiple rows.</li>
+<li>Form allows to visually split data fields into logical groups, thus increasing readability.
+Labels with additional information can be inserted to give users more hints on how to use the form or what given data fields mean.</li>
+<li>Command buttons can be used within forms for commonly used commands so users can use forms in a similar way as a standalone applications they know.</li>
+<li>In data sheet view displaying multi-row data text fields or images is as easy as within forms.</li>
+</ul>
+</p>
+
+<a name="design"></a>
+<H3>5.4.3. Working with form design</H3>
+<p>
+As with table or query design, you are able to use <em>Data View</em> and <em>Design View</em>. Form designing is performed in <em>Design View</em>. We will ofter refer to form design window as to <em>Form Designer</em>.
+</p>
+<ol>
+<li>
+To create a new empty form, select <em>Insert</em> -&gt; <nobr><img src="icons/form_newobj.png" class="icon"> <em>Form</em></nobr> from the Menubar. Optionally, you can use <nobr><img src="icons/form_newobj.png" class="icon"> <em>New Form</em></nobr> command from drop-down button on the Project Navigator's toolbar.
+</li>
+<li>
+A new window with empty rectangular from surface will appear. Beyond the form surface outer area is placed, separated from the surface using borders. You can move the borders to resize the form surface.
+The surface is covered with a grid painted using dots which simplifies accurate widgets positioning.<br><br>
+ <br><img src="img/05_04_03_new_empty_form.png">
+ <br>A window with design of a new form<br><br>
+</p>
+</li>
+</ol>
+
+<p>
+As with table design, Form Designer provides <b>Property pane</b>. To save some space on the screen, the pane has been splitted with three tabs related to the currently selected form:
+<ol>
+<li><em>Property tab</em> containing a list of properties for the currently selected widgets</li>
+<li><em><nobr><img src="icons/property_pane_datasource_tab.png" class="icon"> Data source</nobr></em> tab containing properties related specifically to <b>data source</b> of the currently selected widget or the form itself.</li>
+<li><em><nobr><img src="icons/property_pane_widget_tab.png" class="icon"> Widgets</nobr></em> tab containing a hierarchy of all widgets of the form. The list simplifies widgets lookup by name and navigation between them.</li>
+</ol>
+There is information about currently selected widget's name and type displayed on the first and second tab.
+</p>
+<p>
+Additional toolbars are also available:
+ <ul>
+ <li><em>Widgets</em> toolbar used for inserting new widgets into the form</li>
+ <li><em>Format</em> toolbar used to format form's elements (e.g. adjusting widget's size, grouping). Formatting commands are also available in the <em>Format</em> menu. More about these commands can be found in appendix <a href="aa_05_00_menu.html#menu_format">A.6. Format Menu</a>.</li>
+ </ul>
+</li>
+</ol>
+
+<a name="widgets_tab"></a>
+<H3>5.4.4. Using the &quot;Widgets&quot; tab</H3>
+<p>
+The "Widgets" tab in the Property pane provides a list of form widgets and their hierarchy. Each widget is presented within the hierarchy beside other widgets being on the same level (the same parent container). Child widgets (inside containers) are presented using indented names.
+</p>
+
+<p>
+On the picture below, the form (a container) contains two widgets: &quot;groupBox2&quot; and &quot;options&quot; command button. In turn, &quot;groupBox2&quot; (being a container itself) contains two check box widgets.<br><br>
+
+ <img src="img/05_04_04_widgets_tab.png">
+ <br>Using the &quot;Widgets&quot; tab<br><br>
+
+Each widget has displayed its name and type. The type has also an icon displayed - the same as the one displayed on the toolbar used while form designing is performed.
+</p>
+
+<H4>Notes</H4>
+<ul>
+<li>Changing the current selection on the list causes appropriate selection on the designed form. This allows for easier widget lookup by name and easier navigation. For example, it is possible to select a widget by name, and then switch to the &quot;Properties&quot; tab to change the widget's properties.</li>
+<li>Keeping the <kbd>Ctrl</kbd> key pressed while an item on the widgets list is being selected allows to select multiple widgets at a time. Keeping the <kbd>Shift</kbd> key allows to select entire lists of widgets</li>
+<li>When widget is inserted, it is recommended to give it a reasonable name. For example, &quot;green&quot; check box widget has been named specifically for its meaning, using the &quot;Properties&quot; tab (<em>Name</em> property has been used to do that). Such change can make it easier to find a widget within the list.<br><br>
+ <img src="img/05_04_04_renaming_widgets.png">
+ <br>Naming the widget as &quot;green&quot;<br><br>
+Giving widgets reasonable names can be useful but is not mandatory. Note that widget's name is a property that is not visible to the user of your form. Users will can only see a widget text, provided by <em>Text</em> property or similar.
+</li>
+</ul>
+
+<a name="fields_inserting"></a>
+<H3>5.4.5. Inserting widgets - text fields</H3>
+<p>Let's create a form providing information about persons, i.e. a form connected it with <em>Persons</em> table.
+</p>
+<p>
+If the form being designed should present data obtained from the database, you need to place appropriate <em>fields</em> on it. To do this, use <em>Widgets</em> toolbar containing a set of togglable buttons. Each button corresponds with a single widget type.
+</p>
+
+<p>
+<ol>
+<li>
+Click <nobr><img src="icons/lineedit.png" class="icon"> <em>Text Box</em></nobr> button on the <em>Widgets</em> toolbar.</li>
+<li>Click on the form surface with &LMB;. New text box widget will be placed in the point where you clicked. Before you release &LMB; you can drag your mouse to specify a desired size for the widget.</li>
+<li>If needed, move the inserted widget using drag &amp; drop to a desired position. You can resize the widget afterwards by dragging one of the small boxes appearing near its corners. Note that the boxes are only visible when the widget is selected. If you select another widget or the form surface, the boxes disappear.</li>
+<li>Click <em>Text Box</em> toolbar button again and click on the form surface to insert another widget. Repeat this action once again until you get three text boxes inserted in your form. For sake of simplicity we will limit ourselves to three data fields.</li>
+</ol>
+</p>
+
+<H4>Notes</H4>
+<p>
+<ul>
+<li>There is context menu available in form's design mode, activated by &RMB; click on selected widget or form's surface. The menu offers commands like <nobr><img src="icons/editcut.png" class="icon"> <em>Cut</em></nobr>, <nobr><img src="icons/editcopy.png" class="icon"> <em>Copy</em></nobr>, <nobr><img src="icons/editpaste.png" class="icon"> <em>Paste</em></nobr>, <nobr><img src="icons/editdelete.png" class="icon"> <em>Delete</em></nobr> and other, more complex. Many of the commands are also provided in the Menubar, usually <em>Edit</em>. Keyboard shortcuts are also available for these commands. Some of the commands are only available for certain types of widgets.</li>
+<li>Commands <nobr><img src="icons/editcut.png" class="icon"> <em>Cut</em></nobr>, <nobr><img src="icons/editcopy.png" class="icon"> <em>Copy</em></nobr> and <nobr><img src="icons/editpaste.png" class="icon"> <em>Paste</em></nobr> makes it possible to move or copy widgets between forms, even between separate database projects.</li>
+<li>Holding the <kbd>Ctrl</kbd> key down while clicking a widget allows to select multiple widgets.</li>
+<li>Instead of using <nobr><img src="icons/editcopy.png" class="icon"> <em>Copy</em></nobr> and <nobr><img src="icons/editpaste.png" class="icon"> <em>Paste</em></nobr> commands, to duplicate a widget within the same form you can hold down the <kbd>Ctrl</kbd> key while moving the widget. After the <kbd>Ctrl</kbd> key is released, the dragged widget will not be moved but copied in the new location.</li>
+</ul>
+</p>
+
+<a name="data_sources"></a>
+<H3>5.4.6. Assigning data sources</H3>
+<p>
+The fields you inserted have no <em>data source</em> assigned yet, so these are not able to display information from the database. To assign data source, <nobr><img src="icons/database.png" class="icon"> <em>Data Source</em></nobr> tab of the <em>Property pane</em>.
+</p>
+
+<p>
+The very first step is to specify the <em>form's data source</em>, i.e. a place the displayed data will be fetched from. As mentioned above, you will use table <em>persons</em> as a data source for your new form.
+</p>
+
+<p>
+<ol>
+<li>Click on the form's surface, as you will alter its properties.</li>
+<li>Switch to the <nobr><img src="icons/database.png" class="icon"> <em>Data Source</em></nobr> tab and enter <em>persons</em> table name in the <em>Form's data source</em> drop down list. Alternatively, you can select this name from the drop down list.<br>
+ <br><img src="img/05_04_05_entering_form_data_source.png">
+ <br>Entering form's data source name<br><br>
+</li>
+</ol>
+</p>
+
+<p>
+You have assigned form's data source. Now you need to do specify field widget's data source.
+</p>
+
+<p>
+<ol>
+<li>Click the first text field widget at the top of the form.</li>
+<li>In the <nobr><img src="icons/database.png" class="icon"> <em>Data Source</em></nobr> tab of the property pane enter field name <em>name</em> in the <em>data source</em> drop down list. Alternatively, you can select this name from the drop down list.<br>
+ <br><img src="img/05_04_05_entering_text_field_data_source.png">
+ <br>Entering field's data source &quot;name&quot;<br><br>
+</li>
+<li>Click next text field widget and enter <em>surname</em> as the data source.</li>
+<li>Enter data sources for <em>street</em>, <em>house_number</em> and <em>city</em> text fields in a similar way.</li>
+</ol>
+</p>
+<p>
+You can now save the form's design (this is not mandatory to test the form in action). To save, click the <nobr><img src="icons/filesave.png" class="icon"> <em>Save object changes</em></nobr> toolbar button or use the <nobr><img src="icons/filesave.png" class="icon"> <em>File -&gt; Save</em></nobr> menu command. Upon saving you will be asked for entering the form's name. Enter <em>Persons</em> as caption and click <em>OK</em> button. Form's name will be filled automatically.
+</p>
+<p>
+It is right moment for testing your form. Click the <nobr><img src="icons/state_data.png" class="icon"> <em>Switch to data view</em></nobr> toolbar button. Unless you made a mistake when while entering data sources, you should see form's fields filled with data from the <em>persons</em> table.<br>
+ <br><img src="img/05_04_06_form_with_text_fields.png">
+ <br>The <em>Persons</em> form in data view after inserting text fields and assigning data sources<br><br>
+</p>
+
+<H4>Notes</H4>
+<p>
+<ul>
+<li>If you want to remove data source assignment for a form widget, you can use <em><img src="icons/clear_left.png" class="icon"> Clear widget's data source</em> button near the <em>Source field</em> drop down list. Similarly, you can use <em><img src="icons/clear_left.png" class="icon"> Clear data source</em> button near the <em>Form's data source</em> drop down list.<br>
+ <br><img src="img/05_04_05_data_source_actions.png">
+ <br>
+</li>
+<li>Use <em><nobr><img src="icons/goto.png" class="icon"> Go to selected data source</nobr></em> button to select appropriate table or query in the <em>Project Navigator</em>, so you can quickly open a table or query being the data source of the form.</li>
+<!-- TODO: mention about creating Auto Fields by using drag & drop -->
+</ul>
+</p>
+
+
+<a name="text_labels"></a>
+<H3>5.4.7. Inserting text labels</H3>
+<p>
+To make it easier for the form's user to identify meaning of every field widget, these should have added text labels with appropriate titles. To create text labels <nobr><img src="icons/label.png" class="icon"> <em>Label</em></nobr> widget is used.
+</p>
+<p>
+Insert three text label widgets onto the form, placing them on the left hand of the text fields (or on the right hand if your operating system uses right-to-left layout). On inserting every new label, a text cursor appears inside where you can enter desired title. Enter consecutively: <em>Name</em>, <em>Surname</em> and <em>Street</em>. Additionally, on the top of the form insert another label displaying name of the form, i.e. &quot;Persons&quot;. Enlarge this label's size and set larger font using <a href="aa_00_00_menu.html#menu_format_font">Format -&gt; Font</a> menu command.
+</p>
+<p>
+ <br><img src="img/05_04_06_form_with_labels.png">
+ <br>Ready to use form after adding text labels<br><br>
+</p>
+<!-- TODO: update this topic then we've got auto-labels -->
+
+
+<a name="actions"></a>
+<H3>5.4.8. Actions</H3>
+
+<p>
+<b>Action</b> is a single activity isolates in the application, available for user to execute. It can be also executed automatically as an reaction for a given event (e.g. after opening a form).
+</p>
+<!-- TODO: co to jest zdarzenie -->
+
+<a name="assigning_actions_to_buttons"></a>
+<H4>Assigning actions to form buttons</H4>
+<p>
+Most actions can be assigned to form button. Assigned action is executed after button is clicked.
+</p>
+<p>
+To assign action:
+<ol>
+<li>Switch to form's design view if you have not done yet.</li>
+<li>Select the existing button widget by clicking on it or put a new button widget onto the form. If you inserted a new button, enter its title and press <kbd>Enter</kbd> key.</li>
+<li>Click the button widget with the &RMB; to display context menu.</li>
+<li>From the context menu select <nobr><img src="icons/form_action.png" class="icon"> <em>Assign action...</em> command.</li>
+<li>An <em>Assigning Action to Command Button</em> dialog window will appear presenting a list of available actions. One of the actions is selected if the widget already has action assigned. Otherwise the <em>Action type</em> drop down list has <em>No type</em> item selected.</li>
+<li>From the <em>Action type</em> drop down list select <em>Application</em> item. Available application-wide actions will be listed.</li>
+<li>Select one of the actions on the list (e.g. &quot;Delete Row&quot;).</li>
+<li>Click <em>OK</em> button or press <kbd>Enter</kbd> key to accept your selection.</li>
+</ol>
+</p>
+<p>
+ <img src="img/05_04_07_assigning_action_to_button.png">
+ <br>Assigning &quot;Delete Row&quot; action to a form's button<br><br>
+</p>
+
+<p>
+After switching to the form's <em>data view</em> you can try whether the action works. For example, if you assigned &quot;Delete Row&quot; action, clicking the button, the current database row will be deleted, similarly to executing <em>Edit &gt; Delete Row</em> menu command (depending on your settings you may be asked to confirm the removal).
+</p>
+<H4>Notes</H4>
+<ul>
+<li>To remove action assignment, select <em>No type</em> item from the <em>Action type</em> drop down list of the <em>Assigning Action to Command Button</em> dialog window.</li>
+<li>Action only work in the form's <em>data view</em>. Not every action's assignment is reasonable. For example, <em>Font...</em> actions is not available in the form's data view, so having it assigned to the button has no effect.</li>
+</ul>
+
+<a name="form_layouts"></a>
+<H3>5.4.9. Widget layouts</H3>
+<p>
+In most cases form widgets should be reasonable placed and aligned. Positioning, aligning and resizing widgets by hand is not easy and these parameters are not adjusted when the user resizes the form. In fact the situation is even worse because you cannot assume a given form requires a given space because users have different font sizes and display resolutions.
+</p>
+<p>
+The following example presents a form where text fields and labels were placed by hand. Some of them cannot fit in the form's window.
+ <br><img src="img/05_04_08_form_no_fit.png">
+ <br>An example form with widgets that cannot not fit in the window<br><br>
+</p>
+<p>
+Using special tool called <em>widget layouts</em> can help to automatically lay out the form widgets. Widget layout is an action of grouping two or more widgets so these are well positioned and have appropriate sizes.
+</p>
+<p>
+Using layout in this form improves alignment. Moreover, its space is better developed. Text fields are closer each other, spacing is constant.
+ <br>
+ <br><img src="img/05_04_08_form_well_fit.png">
+ <br>Example form with layout used<br><br>
+</p>
+<p>
+There are two methods to create widget layout.
+<ul>
+<li>Select two or more widgets that should be placed in a common layout, and select one of the layout types from the context menu item <a href="aa_00_00_menu.html#menu_format_layout">Layout Widgets</a>.</li>
+<li>Click a <a hrf="widget_containers">container widget</a> (or a form surface itself), where widgets are inserted and select one of the layout types from the context menu item <a href="aa_00_00_menu.html#menu_format_layout">Layout Widgets</a>. All widgets existing within the container or within the form, being on the same level will be put into a single common layout.</li>
+</ul>
+In each of these cases you can also use <em>Format -&gt; Layout Widgets</em> menu.
+</p>
+<p>
+ <img src="img/05_04_08_form_layout_selecting.png">
+ <br>Selecting widgets that will be put into a layout<br>
+ <br><img src="img/05_04_08_form_layout_selected.png">
+ <br>Four widgets are selected<br><br>
+ <br><img src="img/05_04_08_form_layout_popup.png"><br>
+ <br>Using the context menu for putting the widgets into a grid layout<br><br>
+</p>
+<p>
+Widget layout is presented in the design view using a blue, green or red box drawn with broken line. This line is displayed only in the form's design view.
+</p>
+<p>
+ <br>
+ <br><img src="img/05_04_08_form_layout_grid.png">
+ <br>Widgets within a grid layout<br><br>
+</p>
+<p>
+Besides the grid type, there are other widget layout types.
+<ul>
+<li>vertical
+ <br>
+ <br><img src="img/05_04_08_form_layout_vertical.png">
+ <br>Vertical widget layout<br><br>
+</li>
+<li>horizontal
+ <br>
+ <br><img src="img/05_04_08_form_layout_horizontal.png">
+ <br>Horizontal widget layout<br><br>
+</li>
+<!-- TODO podzia poziomy / pionowy
+ <br><img src="img/05_04_08_form_layout_vertical_splitter.png">
+ <br><br>
+ <br><img src="img/05_04_08_form_layout_horizontal_splitter.png">
+ <br><br>
+</li> -->
+</ul>
+</p>
+
+<a name="form_springs"></a>
+<H4>Springs in widget layouts</H4>
+<p>
+A <em>spring</em> in widget layouts is a special, invisible element allowing to adjust widget's position and size within layouts. Such a spring stretches or squeezes a widget on the right, top, bottom or left hand, so it can have desired size and position.
+</p>
+<p>To use a spring:
+<ol>
+<li>Select <img src="icons/spring.png" class="icon"> spring icon on the <em>Widgets</em> toolbar.</li>
+<li>Click on a selected point of the form to insert the spring.</li>
+</ol>
+</p>
+<p>
+For the following example, the spring has been inserted on the left hand of the text label &quot;Persons&quot;. The label is thus displayed on the right hand of the form. To make the spring work, it has been put into a common horizontal layout with the label.
+ <br><img src="img/05_04_08_form_spring.png">
+ <br>Horizontal layout containing a spring and a text label<br><br>
+</p>
+<p>
+To make springs work you need to create a global widget layout i.e. a layout for the form itself. Then, springs can use edges of the form as a boundary for expanding.
+</p>
+
+<a name="advanced_form_layouts"></a>
+<H4>Advanced widget layouts</H4>
+<p>
+Widget layouts can be combined (or nested). On the following example you can identify two nested layouts:
+</p>
+<p>
+<ol>
+<li>horizontal layout with a spring, aligning the &quot;Persons&quot; text label to the right</li>
+<li>grid layout grouping widgets on the whole form</li>
+</ol>
+</p>
+<p>
+ <img src="img/05_04_08_form_advanced_layout.png">
+ <br>Two widget layouts combined: horizontal layout inside of a grid layout<br><br>
+</p>
+<p>
+The horizontal layout is treat in the example as a single widget by the grid layout - it takes exactly one &quot;cell&quot; of the grid.
+After opening a form designed this way in the data view, you can notice (by resizing the form) that:
+</p>
+<ul>
+<li>&quot;Persons&quot; text label thanks to the spring used is constantly aligned to the to the right side of the form</li>
+<li>text fields take all of the available width thanks to putting them into the grid layout</li>
+<li>all the form's widgets are pushed to the top thanks to the spring used at the bottom of the form</li>
+</ul>
+<p>
+ <img src="img/05_04_08_form_advanced_layout_view.png">
+ <br>The form using the two layouts displayed in data view<br><br>
+</p>
+
+<a name="layout_breaking"></a>
+<H4>Removing widget layouts</H4>
+<p>
+To remove widget layout without removing widgets, perform one of these actions:
+<ul>
+<li>click with the &RMB; on the layout's border and select <em>Break Layout</em> command from the context menu</li>
+<li>click with the &LMB; on the layout's border and select <em>Format -&gt; Break Layout</em> menu command</li>
+</ul>
+</p>
+
+<H4>Notes</H4>
+<p>Removing widget layout using the <em>Break Layout</em> command will not remove widgets contained in the layout. If you want to remove the widgets as well, just select the layout by clicking on its border and press <kbd>Delete</kbd> key or use <nobr><em>Edit -&gt <img src="icons/editdelete.png" class="icon"> Delete</em></nobr> menu command or context menu command.
+</p>
+
+<a name="layout_size_types"></a>
+<H4>Size policies for widgets within a layout</H4>
+<p>
+Instead of setting a fixed size for your widgets, in &kexi; you can choose between various widget's <em>size policies</em>. A size policy is a flexible strategy for controlling how a widget is stretched (or shrunk) depending on other neighbouring widgets and space available within the form.
+</p>
+<p>
+After putting widgets into a <em>layout</em>, typically each widget gets a proportional (<em>Preferred</em>) size policy. These widgets will be automatically resized with preferred settings, depending on their type and size of the entire layout itself. For example, three buttons put into the horizontal layout will be resized to fit their visible text.
+</p>
+<p>
+For each widget inserted into the form, there are settings for size policy available in the <em>Property Editor</em>. The settings are presented as a group of properties called <em>Size Policy</em>.
+
+ <br><img src="img/05_04_09_size_policy_properties.png">
+ <br>A group of properties for defining a widget's size policy<br><br>
+
+This group of properties contains:
+<ul>
+<li><b>Horizontal Size Policy</b> defining horizontal size of the widget,</li>
+<li><b>Vertical Size Policy</b> defining vertical size of the widget,</li>
+<li><b>Horizontal Stretch</b> defining strength of activity of the <em>Horizontal Size Policy</em>,</li>
+<li><b>Vertical Stretch</b> defining strength of activity of the <em>Vertical Size Policy</em></li>
+</ul>
+
+<H5>Values of size policies</H5>
+<p>
+There are following values available on the drop down list for <em>Horizontal Size Policy</em> and <em>Vertical Size Policy</em> properties visible in the <em>Property Editor</em>:
+</p>
+<ul>
+<li><b>Fixed</b> value means that the widget cannot be automatically resized; it should maintain the constant size defined on design time (width or height),</li>
+<li><p><b>Minimum</b> value means that the original size of the widget is set as minimal allowed, it is sufficient and there is no need for expanding the widget, but the widget will be expanded if needed. This type of policy can be used to force widget to be expanded to the whole width or height, especially if you set a <em>stretch</em> value greater than 0.
+ <br><img src="img/05_04_09_size_policy_minimum.png">
+ <br>Text field and two buttons within a grid layout (<em>Minimum</em> horizontal size policy is set for both buttons, so these are slightly wider than needed)<br><br>
+</p></li>
+<li><b>Maximum</b> value means that the original size of the widget is set as maximum allowed and can be decreased without breaking the widget's usability and readability if other widgets need more space,</li>
+<li><p><b>Preferred</b> value means that the original size of the widget is the best and preferred; the widget can be shrunk or expanded however and it will stay readable,
+ <br><img src="img/05_04_09_size_policy_preferred.png">
+ <br>Text field and two buttons within a grid layout (<em>Preferred</em> horizontal size policy is set for both buttons)<br><br>
+</p></li>
+<li><b>Expanding</b> value means that the original size of the widget is reasonable but the widget can be also shrunk; it can be expanded as well to take as much space as possible,</li>
+<li><b>Minimum Expanding</b> value means that the original size of the widget is allowed; it can be expanded to take as much space as possible,</li>
+<li><b>Ignored</b> value means that the original size of the widget is ignored; the widget can be expanded to take as much space as possible but other widgets usually will not allow for that</li>
+</ul>
+Different widget types have various default size policies; for example, button widgets have default size policy set to <em>Minimum</em> (in both directions), while text field widgets have vertical size policy set to <em>Fixed</em>.
+<p>
+<p>
+The most frequently used size policies are <em>Preferred</em>, <em>Minimum</em> and <em>Maximum</em>.
+</p>
+
+<H5>Vertical and horizontal stretch</H5>
+<p>
+<em>Vertical Stretch</em> and <em>Horizontal Stretch</em> properties accept integer values greater or equal to 0. These properties allow to fine-tune the behavior of size policies. Default value for the properties is 0. Greater value of the stretch means that the widget will be expanded more than other widgets having smaller stretch value set. For example, the following image presents two buttons where the first button has <em>Vertical Stretch</em> set to 0 and the second button has <em>Vertical Stretch</em> set to 1.
+
+ <br><img src="img/05_04_09_size_policy_vertical_stretch.png">
+ <br>Size of button widgets affected by setting <em>Vertical Stretch</em> property of the second button to 1<br><br>
+
+</p>
+
+<a name="widget_adjusting"></a>
+<H3>5.4.10. Setting widgets size and position by hand</H3>
+<p>
+In case when your form has no main layout set for auto-positioning and auto-resizing its widgets, you will probably want to align widget's position and size so the form can look cleaner and be easier to use. The &kexi; form designer simplifies this task by offering the following groups of commands:
+</p>
+<ul>
+<li><p>Adjusting sizes of selected widgets. The commands are available in the <em>Format -&gt; Adjust Widgets Size</em> submenu of the Menubar and in the <em>Adjust Widgets Size</em> submenu of the context menu. Toolbar's drop down button <nobr><img src="icons/aogrid.png" class="icon"> <em>Adjust Widgets Size</em></nobr> is also available.
+ <ul>
+ <li><nobr><img src="icons/aofit.png" class="icon"> <em>To Fit</em></nobr> - sizes of the selected widgets will be altered so each widget will be resized to its preferred size and its contents; for example, text label's size will be changed to fit its text. Position of the widgets will not be changed.</li>
+ <li><nobr><img src="icons/aogrid.png" class="icon"> <em>To Grid</em></nobr> - sizes of the selected widgets will be altered so each widget's corner will be placed in the form's (or other container's) grid point. Widget's position can be slightly altered.</li>
+ <li><nobr><img src="icons/aoshortest.png" class="icon"> <em>To Shortest</em></nobr> - height of the selected widgets will be altered so that each of them will have the same height as the shortest one. Position of the widgets will not be changed.</li>
+ <li><nobr><img src="icons/aotallest.png" class="icon"> <em>To Tallest</em></nobr> - height of the selected widgets will be altered so that each of them will have the same height as the tallest one. Position of the widgets will not be changed.</li>
+ <li><nobr><img src="icons/aonarrowest.png" class="icon"> <em>To Narrowest</em></nobr> - width of the selected widgets will be altered so that each of them will have the same height as the narrowest one. Position of the widgets will not be changed.</li>
+ <li><nobr><img src="icons/aowidest.png" class="icon"> <em>To Widest</em></nobr> - width of the selected widgets will be altered so that each of them will have the same height as the widest one. Position of the widgets will not be changed.</li>
+ </ul>
+</li>
+<li><p>Aligning positions of the selected widgets. The commands are available in the <em>Format -&gt; Align Widgets Position</em> submenu of the Menubar and in the <em>Align Widgets Position</em> submenu of the context menu. Toolbar's drop down button <nobr><img src="icons/aoleft.png" class="icon"> <em>Align Widgets Position</em></nobr> is also available.
+ <ul>
+ <li><nobr><img src="icons/aoleft.png" class="icon"> <em>To Left</em></nobr> - all the selected widgets' left positions will be moved to the position of the leftmost widget's left edge.</li>
+ <li><nobr><img src="icons/aoright.png" class="icon"> <em>To Right</em></nobr> - all the selected widgets' right positions will be moved to the position of the rightmost widget's right edge.</li>
+ <li><nobr><img src="icons/aotop.png" class="icon"> <em>To Top</em></nobr> - all the selected widgets' top positions will be moved to the position of the uppermost widget's upper edge.</li>
+ <li><nobr><img src="icons/aobottom.png" class="icon"> <em>To Bottom</em></nobr> - all the selected widgets' bottom positions will be moved to the position of the bottommost widget's bottom edge.</li>
+ <li><nobr><img src="icons/aopos2grid.png" class="icon"> <em>To Grid</em></nobr> - all the selected widgets' top-left corners will be moved so that it is positioned in the nearest grid point.</li>
+ </ul>
+ </p>
+ <p>
+ None of the above commands resizes the widgets.
+ </p>
+</li>
+</ul>
+</p>
+
+<p>
+There are also additional commands available:
+<nobr><img src="icons/raise.png" class="icon"> <em>Bring Widget to Front</em></nobr> (i.e. above all other widgets) and <nobr><img src="icons/lower.png" class="icon"> <em>Send Widget to Back</em></nobr> (i.e. below all other widgets). These two commands are rarely used, as it is not common to place one widget on top of other (with an exception when a container widget contains other widget inside). Also note that clicking a widget with mouse button is enough to bring the widget to front.
+</p>
+
+<a name="tab_order_editing"></a>
+<H3>5.4.11. Setting the tab order</H3>
+<!-- TODO move this introduction to a special section about properties related to focusing -->
+<p>
+<em>Widget's focus</em> determines widget's activity available using keyboard. Focus is related to widgets displayed in form's data view. Exactly one form widget can have focus at the same time. Most frequent use of focus is text entry (when a given text field is active, i.e. it is <em>focused</em>). Other example is a button widget - when focused, it is possible to &quot;press&quot; it using the <kbd>Enter</kbd> or <em>Space</em> key instead of a mouse button.
+</p>
+<p>There are a few methods of making the widgets active (moving the focus to the widget): clicking with a mouse button, rotating the mouse wheel over the widget, or using the <em>Tab</em> key. The latter method is often used because of it's speed and convenience for users. Availability of the focusing methods is controlled by <em>Focus Policy</em> property of a given widget.
+</p>
+<p>
+There is relationship between focusing (activating) widgets using <em>Tab</em> key and <b>tab order</b> setting of a form. After pressing the <em>Tab</em> key, the <b>next</b> widget should be focused, so the form should know about the <em>tab order</em>.</p>
+<p>
+To alter table order for a form's widget:
+<ol>
+<li>switch to design view of the form</li>
+<li>execute <em>Edit -&gt; Edit Tab Order</em> menu command.
+<em>Edit Tab Order</em> will appear with settings for this form.
+
+ <br><img src="img/05_04_11_tab_stop_dialog.png">
+ <br>A window for editing tab order for a form<br><br>
+
+<p>
+The window contains a list with two columns: the first column displays widget names, the second - types of the widgets. To make it easier to recognize meaning of the names and types for the user, icons related to the types are also displayed. The list contains only widgets having focus policy allowing to use the <kbd>Tab</kbd> key.
+The window allows you to change tab order or set the automatic tab order.
+</p>
+</li>
+<li><p>
+To change tab order, either:
+<ul>
+<li>Click a selected widget name on the widgets list and drag it to a desired position (up or down) using the mouse.
+</li>
+<li>Click a selected widget name on the widgets list and use <em>Move Up</em> or <em>Move Down</em> buttons, to move the widgets to a desired position.</li>
+<li>Click the <em>Handle tab stops automatically</em> check box to set the automatic tab order for the form. If this option has been switched on, any changes made to the list of widgets by hand are not taken into account - &kexi; will be handling the tab orders on its own. The automatic ordering means that the top-left widget will be focused first (or the top-right if your operating system uses right-to-left layout), and the order comes from the left to right (from the right to left, appropriately) and from the top to bottom.
+ <br><img src="img/05_04_11_auto_tab_stop.png">
+ <br>Automatic tab order for a form<br><br>
+</li>
+</ul>
+</p>
+</li>
+<li>
+Click the <em>OK</em> button to accept the changes or <em>Cancel</em> button to dismiss the changes.
+</li>
+</ol>
diff --git a/kexi/doc/handbook/html.tmp/05_05_00_data_entering_into_forms.html b/kexi/doc/handbook/html.tmp/05_05_00_data_entering_into_forms.html
new file mode 100644
index 000000000..2b324ab09
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/05_05_00_data_entering_into_forms.html
@@ -0,0 +1,12 @@
+<H3>5.5. Entering data using forms</H3>
+<p>
+Data entering and editing is usually database application user's task, not developer's. In practice it is desirable to check the form in terms of valid data entry, and see whether the form works as expected.
+</p>
+<p>
+To test your form, switch to its data view. A single database row (record) of data will be displayed and a text cursor will be set inside the first data field. You can move between fields using &RMB; or <kbd>Tab</kbd> and <kbd>Shift+Tab</kbd> keys. While editing, there will be <img src="icons/button_tableview_editrow.png" class="icon"> pencil icon visible near the record navigator. After entering row's (record) data you can press <kbd>Shift+Enter</kbd> keys or <img src="icons/button_ok.png" class="icon"> toolbar button to accept changes made to the current row. Clicking <img src="icons/button_cancel.png" class="icon"> toolbar button discards changes made to the current row and restores contents of the data fields. You can use <img src="icons/navigator_next.png" class="icon"> record navigator's button to move to a new row. All the navigator's functions are also available in similar way as in the data table view<!-- TODO link -->.
+<!-- TODO shortcuts! -->
+</p>
+<p>
+</p>
+
+<!-- TODO -->
diff --git a/kexi/doc/handbook/html.tmp/08_00_00_kexi_tuning.html b/kexi/doc/handbook/html.tmp/08_00_00_kexi_tuning.html
new file mode 100644
index 000000000..ad577cbb7
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/08_00_00_kexi_tuning.html
@@ -0,0 +1,11 @@
+<H2>8. Adjusting Kexi application to your needs</H2>
+<p>
+Kexi application has a customizable user interface allowing the user to adjust it to his preferences.
+</p>
+<p>
+<ul>
+<li><a href="08_01_00_mdi.html">8.1. Changing the graphic interface mode</a></li>
+<li><a href="08_02_00_dock_undock.html">8.2. Docking and undocking the windows</a></li>
+<li><a href="08_03_00_conf_keys.html">8.3. Hotkeys configuration</a></li>
+</ul>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/08_01_00_mdi.html b/kexi/doc/handbook/html.tmp/08_01_00_mdi.html
new file mode 100644
index 000000000..65b36b811
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/08_01_00_mdi.html
@@ -0,0 +1,20 @@
+<H3>8.1. Changing the graphical interface mode</H3>
+<p>
+MDI (<em>Multiple Document Interface</em>) modes enable the user to choose the way to manage the aplication's widows. You can choose one of two modes according to your preferences - it will be also used next time you run the application.
+</p>
+<p>
+The change of MDI mode is possible using the <em>MDI mode</em> command from the <em>Window</em> menu bar.
+Following modes are available:
+<ul>
+<a name="mode_ideal"></a>
+<li><b>IDEAL mode:</b> standardm modern mode known from KDE or the latest Microsoft applications, displaying child windows inside the application in form of <b>tabs</b>. Tabs are always maximized while panes with changeable size are located at the sides of the screen. They are also automatically hidden when not used, which provides you with more space.
+<br><img src="img/08_01_00_mdi_mode_ideal.png">
+<br>IDEAL mode<br><br>
+</li>
+<a name="mode_childframe"></a>
+<li><b>Childframe mode:</b> displaying child windows inside the application. You can minimize and maximize them. A taskbar corresponding to each window is also available as well as panes with changeable size located at the sides of the screen.
+<br><img src="img/08_01_00_mdi_mode_childframe.png">
+<br>Child mode<br><br>
+</li>
+</ul>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/08_02_00_dock_undock.html b/kexi/doc/handbook/html.tmp/08_02_00_dock_undock.html
new file mode 100644
index 000000000..934d1f49f
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/08_02_00_dock_undock.html
@@ -0,0 +1,32 @@
+<H3>8.2. Docking and undocking of the windows</H3>
+
+<p>
+To <em>undock</em> a window you must drag it from the inside of the Kexi application and drop it so that you can e.g. maximize or move it freely. This is particularly useful when working on a small screen or when working with large datasheets. Undocking can also be used when working on two display monitors connected to one computer: you can move selected windows to the other display.
+</p>
+<p>
+<em>Docking</em> is a command opposite to <em>undocking</em>: the window is placed back inside the application.
+</p>
+<p>
+Docking in windows is available only in <a href="08_01_00_mdi.html#mode_childframe">childframe mode</a> of the graphic interface. The command is always available in case of panes.
+</p>
+<p>
+The <em>Dock/undock</em> command is also available:
+<ul>
+<li>through clicking the button with the arrow in the top right corner of the main window of the application (if the window of the object is maximized):
+<br><img src="img/08_02_00_dock_undock_button_max.png">
+<br>Dock/undock key for a maximized window<br><br>
+</li>
+<li>through clicking the button with the arrow in the top right corner of the selected object (if it is not maximized):
+<br><img src="img/08_02_00_dock_undock_button.png">
+<br>Dock/undock key for a non-maximized window<br><br>
+</li>
+<li>through the right-click on the window titlebar and selecting the <em>undock</em> command from the context menu:
+<br><img src="img/08_02_00_dock_undock_popup.png">
+<br><em>Undock</em> command in the context menu on the winow titlebar<br><br>
+</li>
+<li>through the right-click on the application taskbar and selecting the <em>dock</em> command from the context menu:
+<br><img src="img/08_02_00_dock_undock_taskbar_popup.png">
+<br><em>Dock</em> command in the context menu of the application's taskbar<br><br>
+</li>
+</ul>
+</p>
diff --git a/kexi/doc/handbook/html.tmp/08_03_00_conf_keys.html b/kexi/doc/handbook/html.tmp/08_03_00_conf_keys.html
new file mode 100644
index 000000000..1622a154d
--- /dev/null
+++ b/kexi/doc/handbook/html.tmp/08_03_00_conf_keys.html
@@ -0,0 +1,32 @@
+<H3>8.3. Keyboard shortcuts (key bindings) configuration</H4>
+<p>
+This option allows to change the shortcuts in Kexi application. To do this, use the <em>Configure Shortcuts</em> command from the <em>Settings</em> menu.
+
+<br><img src="img/08_04_00_configure_shortcuts.png">
+<br>Shortcuts configuration dialog box<br><br>
+To confirm the introduced changes click <em>OK</em> button. Changes are permanent which means that they will also be available next time you run Kexi application.
+</p>
+<p>
+Available actions:
+<ul>
+<li><p>To quickly change a command type its name in the <em>Search</em> radio button.</p></li>
+<li><p>Click the <em>Defaults</em> button at the bottom of the dialog box to restore standard shortcut settings for Kexi application.</p></li>
+<li><p>Select the requested command from the list and select the <em>None</em> radio button to remove a shortcut for this command.</p></li>
+<li><p>Select the requested command from the list and select the <em>Default</em> radio button to restore the standard shortcut for this command. Shortcut description will appear at the bottom of the dialog box.</p></li>
+<li><p>Select the requested command from the list and tick the <em>Custom</em> radio button to change the shortcut for this command. <em>Shortcut configuration</em> dialog box will pop up:
+<br><img src="img/08_04_00_configure_shortcut.png">
+<br><em>Shortcut configuration</em> dialog box<br><br>
+ You can provide one or two shortcuts.
+ </p>
+ <p>
+ Available actions:
+ <ul>
+ <li>Click the <em>Advanced</em> button to show the advanced settings if you want to provide more than one shortcut.</li>
+ <li>Click <img src="icons/locationbar_erase.png" class="icon"> button to remove the selected shortcut.</em>
+ <li>Select the <em>Primary shortcut</em> radio button (or <em>Alternate shortcut</em>) and then press the key combination you want to set, e.g. Ctrl+Alt+R.</li>
+ <li>Click the <em>OK</em> button or press <em>Enter</em> to confirm shortcut settings. Click <em>Cancel</em> to abandon the change.</em></li>
+ </ul>
+ </p>
+</li>
+</ul>
+</p>
diff --git a/kexi/doc/handbook/translation-status.txt b/kexi/doc/handbook/translation-status.txt
new file mode 100644
index 000000000..acc00d000
--- /dev/null
+++ b/kexi/doc/handbook/translation-status.txt
@@ -0,0 +1,111 @@
+Kexi Handbook: README
+---------------------
+
+0. Notes
+- For more info contact Jaroslaw Staniek, js at iidea.pl (project maintainer)
+- Original html content is strored temporary in html.tmp/ subdirectory.
+- For now let's LEAVE <img> and <a href..> tags untouched - enclose them with
+ <!-- --> comments. We will prepare english screenshots later; for now only polish version exists.
+ Do not remove any existing <!-- --> comments.
+- Development version of this handbook generated out of the docbook sources:
+ http://docs.kde.org/development/en/koffice/kexi/index.html
+- For steps (e.g. in tutorial) rather use <procedure> tag instead of <itemizedlist>.
+ <title> subtag is available as well for <procedure>. Details:
+ http://www.docbook.org/tdg/en/html/procedure.html
+
+1. States:
+-DONE
+-MOVED
+-empty means "translation not ready"
+
+2. TODOs
+-add a documentation for form's Auto Fields
+-add "File types supported by Kexi" (describe .kexis, .kexic, .kexi -- appendix?)
+
+3. Translation status:
+Note: the numbering is taken from original Polish HTML version:
+ http://kexi.pl/doc/pl/contents.html
+
+DONE 1. Introduction to databases
+DONE 1.1. What is a database?
+DONE 1.2. A database and a spreadsheet
+DONE 1.3. Database design
+DONE 1.4. Who needs a database?
+DONE 1.5. Database creation software
+DONE 2. Introduction to Kexi
+DONE 2.1. What is Kexi?
+DONE 2.2. Kexi features
+DONE 2.3. Is Kexi for me?
+DONE 2.4. Differences between Kexi and other applications
+WILL BE MOVED to Appendix: 3. Kexi Installation
+DONE 4. Kexi usage basics
+DONE 4.1. Project files
+DONE 4.2. Running Kexi
+DONE 4.3. Creating a new database project
+DONE 4.4. Opening an existing Kexi database file
+DONE 4.4.1. Using "Open Existing Project" window
+DONE 4.4.2. By clicking on .kexi file's icon
+DONE 4.5. Using built-in help
+DONE 4.6. Main application elements
+DONE 4.6.1. Project Navigator pane
+DONE 4.6.2. Database object windows
+DONE 4.6.3. Property pane
+DONE 5. Building simple databases
+DONE 5.1. Designing database tables
+DONE 5.1.1. Table Designer window
+ TODO: update recent changes made to 05_01_00_table_designing.html
+DONE 5.2. Entering data into tables
+ TODO: update recent changes made to 05_02_00_data_entering_into_tables.html
+DONE 5.3. Designing database queries
+ TODO: update recent changes made to 05_03_00_query_designing.html
+DONE 5.4. Designing forms
+DONE 5.4.1. Most important terms
+DONE 5.4.2. Forms versus tables
+DONE 5.4.3. Working with form design
+DONE 5.4.4. Using the "Widgets" tab
+DONE 5.4.5. Inserting widgets - text fields
+DONE 5.4.6. Assigning data sources
+DONE 5.4.7. Inserting text labels
+DONE 5.4.8. Actions
+DONE * Assigning actions to form buttons
+DONE 5.4.9. Widget layouts
+DONE * Springs in widget layouts
+DONE * Advanced widget layouts
+DONE * Removing widget layouts
+DONE * Size policies for widgets within a layout
+DONE 5.4.10. Setting widgets size and position by hand
+DONE 5.4.11. Setting the tab order
+DONE 5.5. Entering data using forms
+6. Advanced database topics
+ 6.1. Sharing a Kexi database project with others
+ 6.1.1. File-based database
+ 6.1.2. Server database
+ 6.2. Designing advanced objects
+ 6.3. Database administration
+ 6.3.1. Security in file-based databases
+ 6.3.2. Security in server databases
+ 6.3.3. Database file compacting
+ 6.4. Using the command line
+7. Work with external data
+ 7.1. Data importing
+ 7.1.1. Importing CSV files
+ 7.1.2. Pasting CSV data from the clipboard
+ 7.2. Importing external database projects
+ 7.3. Data exporting
+ 7.3.1. Exporting data to CSV files
+ 7.3.2. copying CSV data to the clipboard
+ 7.4. Exporting database projects
+ 7.5. Working with multiple database projects
+DONE 8. Adjusting Kexi application to your needs
+DONE 8.1. Changing the graphical interface mode
+DONE 8.2. Docking and undocking of the windows
+DONE 8.3. Keyboard shortcuts (key bindings) configuration
+ Table of Contents
+ Index
+ Appendices
+ A. Menu items
+ B. Keyboard Shortcuts
+ C. Form and Report widgets
+ D. Specifications
+ E. Example database projects
+ F. Web pages about Kexi
diff --git a/kexi/doc/kexidb/kexidb.doxygen b/kexi/doc/kexidb/kexidb.doxygen
new file mode 100644
index 000000000..ebfa7741a
--- /dev/null
+++ b/kexi/doc/kexidb/kexidb.doxygen
@@ -0,0 +1,220 @@
+# Doxyfile 0.1
+
+#---------------------------------------------------------------------------
+# General configuration options
+#---------------------------------------------------------------------------
+PROJECT_NAME = "KexiDB Library"
+PROJECT_NUMBER = "Version 2"
+OUTPUT_DIRECTORY = .
+OUTPUT_LANGUAGE = English
+USE_WINDOWS_ENCODING = YES
+EXTRACT_ALL = YES
+EXTRACT_PRIVATE = NO
+EXTRACT_STATIC = YES
+EXTRACT_LOCAL_CLASSES = YES
+HIDE_UNDOC_MEMBERS = NO
+HIDE_UNDOC_CLASSES = NO
+HIDE_FRIEND_COMPOUNDS = NO
+HIDE_IN_BODY_DOCS = NO
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ALWAYS_DETAILED_SEC = YES
+INLINE_INHERITED_MEMB = NO
+FULL_PATH_NAMES = NO
+STRIP_FROM_PATH =
+INTERNAL_DOCS = NO
+CASE_SENSE_NAMES = YES
+SHORT_NAMES = NO
+HIDE_SCOPE_NAMES = NO
+SHOW_INCLUDE_FILES = YES
+JAVADOC_AUTOBRIEF = YES
+MULTILINE_CPP_IS_BRIEF = NO
+DETAILS_AT_TOP = YES
+INHERIT_DOCS = YES
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = YES
+DISTRIBUTE_GROUP_DOC = NO
+TAB_SIZE = 4
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+GENERATE_DEPRECATEDLIST= YES
+ALIASES = libdoc=@mainpage \
+ sect=<p><b> \
+ reimplemented= \
+ "deprecated=<b>This class or method is obsolete, it is provided for compatibility only.</b>" \
+ obsolete=@deprecated \
+ ref=
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+OPTIMIZE_OUTPUT_FOR_C = NO
+OPTIMIZE_OUTPUT_JAVA = NO
+SHOW_USED_FILES = YES
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET = YES
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = NO
+WARN_IF_DOC_ERROR = YES
+WARN_FORMAT =
+WARN_LOGFILE =
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT = ../../kexidb
+FILE_PATTERNS = *.h \
+ *.cpp \
+ *.cc \
+ *.hpp
+RECURSIVE = YES
+EXCLUDE = ../../kexidb/parser/sqlparser.h \
+ ../../kexidb/parser/sqlparser.cpp \
+ ../../kexidb/parser/sqlscanner.cpp
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS = *.moc.* \
+ moc* \
+ *.all_cpp.* \
+ *unload.* \
+ */test/* \
+ */tests/*
+EXAMPLE_PATH =
+EXAMPLE_PATTERNS =
+EXAMPLE_RECURSIVE = NO
+IMAGE_PATH = ../common
+INPUT_FILTER =
+FILTER_SOURCE_FILES = NO
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER = YES
+INLINE_SOURCES = NO
+STRIP_CODE_COMMENTS = YES
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+VERBATIM_HEADERS = YES
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX = YES
+COLS_IN_ALPHA_INDEX = 3
+IGNORE_PREFIX = K
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_HEADER = ../common/header.html
+HTML_FOOTER = ../common/footer.html
+HTML_STYLESHEET = ../common/kde-web.css
+HTML_ALIGN_MEMBERS = YES
+GENERATE_HTMLHELP = NO
+CHM_FILE =
+HHC_LOCATION =
+GENERATE_CHI = NO
+BINARY_TOC = NO
+TOC_EXPAND = NO
+DISABLE_INDEX = YES
+ENUM_VALUES_PER_LINE = 4
+GENERATE_TREEVIEW = NO
+TREEVIEW_WIDTH = 250
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX = NO
+LATEX_OUTPUT = latex
+LATEX_CMD_NAME = latex
+MAKEINDEX_CMD_NAME = makeindex
+COMPACT_LATEX = NO
+PAPER_TYPE = a4wide
+EXTRA_PACKAGES =
+LATEX_HEADER =
+PDF_HYPERLINKS = NO
+USE_PDFLATEX = NO
+LATEX_BATCHMODE = NO
+LATEX_HIDE_INDICES = NO
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .kde3
+MAN_LINKS = YES
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML = NO
+XML_OUTPUT = xml
+XML_SCHEMA =
+XML_DTD =
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF = NO
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD = NO
+PERLMOD_LATEX = NO
+PERLMOD_PRETTY = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = YES
+SEARCH_INCLUDES = YES
+INCLUDE_PATH = ../../kexidb
+INCLUDE_FILE_PATTERNS =
+PREDEFINED = QT_VERSION=311
+EXPAND_AS_DEFINED =
+SKIP_FUNCTION_MACROS = YES
+#---------------------------------------------------------------------------
+# Configuration::addtions related to external references
+#---------------------------------------------------------------------------
+TAGFILES =
+GENERATE_TAGFILE = kexidb.tag
+ALLEXTERNALS = NO
+EXTERNAL_GROUPS = NO
+PERL_PATH =
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS = YES
+HIDE_UNDOC_RELATIONS = NO
+HAVE_DOT = YES
+CLASS_GRAPH = YES
+COLLABORATION_GRAPH = YES
+TEMPLATE_RELATIONS = YES
+INCLUDE_GRAPH = YES
+INCLUDED_BY_GRAPH = YES
+GRAPHICAL_HIERARCHY = NO
+DOT_IMAGE_FORMAT = gif
+DOT_PATH =
+DOTFILE_DIRS =
+MAX_DOT_GRAPH_WIDTH = 800
+MAX_DOT_GRAPH_HEIGHT = 1024
+MAX_DOT_GRAPH_DEPTH = 0
+GENERATE_LEGEND = YES
+DOT_CLEANUP = YES
+#---------------------------------------------------------------------------
+# Configuration::addtions related to the search engine
+#---------------------------------------------------------------------------
+SEARCHENGINE = NO
+CGI_NAME =
+CGI_URL =
+DOC_URL =
+DOC_ABSPATH =
+BIN_ABSPATH =
+EXT_DOC_PATHS =
diff --git a/kexi/doc/plan/kexi-announce.txt b/kexi/doc/plan/kexi-announce.txt
new file mode 100644
index 000000000..4975fff77
--- /dev/null
+++ b/kexi/doc/plan/kexi-announce.txt
@@ -0,0 +1,42 @@
+ js, lucijan, 2003-10-19
+
+!Why Kexi does not behave as well as KOffice application using kofficelibs compared to others apps from the family?
+
+
+* Kexi, unlike (all?) others KOffice application is not document-driven
+;: Kexi is project-driven insted. Thus it is conceptually more like KDevelop.
+
+* Application's environment.
+;:Typical KOffice application is built upon the fact that there is a main document (that also can act as KPart) for the application view, and most application logic is built around this single native format/mime type and the KPart class that uses data in this single format.
+
+;:Kexi has more distributed nature, there is more than just one mode (or two) for editing a document or viewing it as read-only (both modes for e.g. KWord uses mostly the same KPart widget(s), only with certain actions disabled/enabled). Some Kexi KParts have editors that are built as strictly different components that are used for viewing (as an example, "table altering" dialog versus table/query data view).
+
+* Storage differences
+;: KOffice depends on storing its native format in single file (in some cases, it can be non-xml but still it is self-contained file)
+;:Kexi has many storage options, since one of its goals is integration of external database systems. Kexi allows you to save a short XML informational file (something like a shortcut to a database connection) that specifies how to connect to the database (where your project is stored) again.
+;:The other project storage method for Kexi uses file-based databases (currently, binary SQLite-compatible, with Kexi system objects inside). For end-user convenience, such a SQLite database file can be self contained and no additional information (in other XML files) is required (although it is still possible to have one).
+
+* KOffice behaves differently at opening projects than Kexi.
+;: When a non-native format is encountered, KOffice framework automatically invokes filters to convert the foreign format into that KOffice applications native format.
+
+;:Kexi is not expected to do so: there is generalisation inside KexiDB module; this module handles all differences "on-line". Otherwise it would be impossible to use Kexi: you don't need to convert e.g. MySQL-embedded-file to a SQLite file, since if there is a driver for MySQL-embedded it is still neutral to SQLite one.
+
+;:Similarly, Kexi does not download all data from SQL server and converts that format to "common" internal data representation and then operates on this representation. The "filter chains" from KOffice framework could force Kexi to do so (otherwise wrapping should be added), while in most cases the only thing that is converted (on demand) in Kexi projects are the database schemas.
+
+;: Above notes do not mean that Kexi is designed to lack filters functionality. Kexi filters functionality is designed with a knowledge that importing database schemas is not fully automatic, because there is not always enough database schema information (e.g. when migrating database from raw MySQL format to Firebird-driven Kexi project). The same applies for export: after exporting, much of project's information can disappear. You can say it is quite like exporting e.g. KWord document to, say, RTF. Yes, but Kexi often has to offer additional dialogs to ask user for additional hints -- summing up, KO filter chains are not so usable in Kexi, since we are not performing just "convert-file-to-file" task.
+
+
+!Why kexi is still a member of KOffice suite?
+
+* Kexi is still part of KOffice suite, what mean that from end-user perspective, strategy is the same as before. This implies that we will do our best to honour KOffice release
+
+* Kexi is a missing application for Free platforms like Linux, *BSD
+
+* It (will be) still properly integrated with other KOffice compontents (KWord mailmerge, KChart graphs, Kugar reports, KSpread tables ...) and/or reuse them
+
+* Work on moving Kexi-as-KoMainWindow/KoDocument-driven application to Kexi-as-KMainWindow/KexiDB-driven application is stared by Kexi Team
+
+
+%%%
+
+Any comments and ideas and further proposals are heavily welcome :)
diff --git a/kexi/examples/Makefile.am b/kexi/examples/Makefile.am
new file mode 100644
index 000000000..2363c37de
--- /dev/null
+++ b/kexi/examples/Makefile.am
@@ -0,0 +1,5 @@
+Simple_Database.kexi: Simple_Database.kexi.sql
+ $(srcdir)/build_kexi_file.sh $(srcdir)/Simple_Database.kexi.sql $(top_builddir)/kexi/examples/Simple_Database.kexi
+
+examplesdir = $(kde_datadir)/kexi/examples
+examples_DATA = Simple_Database.kexi
diff --git a/kexi/examples/README b/kexi/examples/README
new file mode 100644
index 000000000..f167f0ecf
--- /dev/null
+++ b/kexi/examples/README
@@ -0,0 +1,29 @@
+Example Kexi database files
+---------------------------
+
+1. Introduction
+
+This directory contains example Kexi database files in SQLite 3.1 format,
+distributed with Kexi source package.
+
+
+2. Files
+
+Simple_Database.kexi - simple database described in the Kexi Handbook
+
+
+3. Note for developers: generated files
+
+If you are using Kexi source code obtained from KDE Subversion repository,
+you can see .kexi.sql files here instead of .kexi files. This make it easier
+to track changes in the Subversion repository.
+
+Each .kexi file is generated out of the original .kexi.sql file by running
+build_kexi_files.sh script.
+Makefile is also provided, so you can just run make (or unsermake) to create
+.kexi files.
+
+You can run update_sql_files.sh script if you have changed .kexi file using
+Kexi program and want to update the related .kexi.sql file before checking it
+into the Subversion repository.
+
diff --git a/kexi/examples/Simple_Database.kexi.sql b/kexi/examples/Simple_Database.kexi.sql
new file mode 100644
index 000000000..f8a243e3d
--- /dev/null
+++ b/kexi/examples/Simple_Database.kexi.sql
@@ -0,0 +1,684 @@
+BEGIN TRANSACTION;
+CREATE TABLE kexi__db (db_property Text(32), db_value CLOB);
+INSERT INTO "kexi__db" VALUES('kexidb_major_ver', '1');
+INSERT INTO "kexi__db" VALUES('kexidb_minor_ver', '2');
+INSERT INTO "kexi__db" VALUES('kexiproject_major_ver', '1');
+INSERT INTO "kexi__db" VALUES('kexiproject_minor_ver', '0');
+CREATE TABLE kexi__fields (t_id UNSIGNED Integer, f_type UNSIGNED Byte, f_name Text(200), f_length Integer, f_precision Integer, f_constraints Integer, f_options Integer, f_default Text(200), f_order Integer, f_caption Text(200), f_help CLOB);
+INSERT INTO "kexi__fields" VALUES(1, 3, 'id', 0, 0, 119, 1, NULL, 1, 'ID', NULL);
+INSERT INTO "kexi__fields" VALUES(1, 3, 'age', 0, 0, 0, 1, NULL, 2, 'Age', NULL);
+INSERT INTO "kexi__fields" VALUES(1, 11, 'name', 200, 0, 0, 0, NULL, 3, 'Name', NULL);
+INSERT INTO "kexi__fields" VALUES(1, 11, 'surname', 200, 0, 0, 0, NULL, 4, 'Surname', NULL);
+INSERT INTO "kexi__fields" VALUES(53, 11, 'a', 200, 0, 0, 200, '1', 0, NULL, NULL);
+INSERT INTO "kexi__fields" VALUES(53, 11, 'b', 200, 0, 0, 200, '2', 1, NULL, NULL);
+INSERT INTO "kexi__fields" VALUES(2, 3, 'id', 0, 0, 119, 1, NULL, 0, 'ID', NULL);
+INSERT INTO "kexi__fields" VALUES(2, 11, 'model', 200, 0, 0, 0, NULL, 1, 'Car model', NULL);
+INSERT INTO "kexi__fields" VALUES(103, 4, 'id', 0, 0, 119, 1, NULL, 0, 'Id', NULL);
+INSERT INTO "kexi__fields" VALUES(103, 3, 'owner', 0, 0, 0, 0, NULL, 1, 'Owner', NULL);
+INSERT INTO "kexi__fields" VALUES(103, 3, 'car', 0, 0, 0, 0, NULL, 2, 'Car', NULL);
+INSERT INTO "kexi__fields" VALUES(103, 3, 'since', 0, 0, 0, 0, NULL, 3, 'Since', NULL);
+CREATE TABLE kexi__objectdata (o_id UNSIGNED Integer NOT NULL, o_data CLOB, o_sub_id Text(200));
+INSERT INTO "kexi__objectdata" VALUES(4, '<!DOCTYPE UI>
+<UI stdsetdef="1" version="3.1" >
+<kfd:customHeader version="2" />
+<pixmapinproject/>
+<class>QWidget</class>
+<widget class="QWidget" >
+<property name="name" >
+<cstring>formularz1</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>0</x>
+<y>0</y>
+<width>500</width>
+<height>200</height>
+</rect>
+</property>
+<property name="caption" >
+<string>formularz1</string>
+</property>
+<property name="dataSource" >
+<string>persons</string>
+</property>
+<property name="dataSourceMimeType" >
+<cstring>kexi/table</cstring>
+</property>
+<property name="paletteBackgroundColor" >
+<color>
+<red>255</red>
+<green>220</green>
+<blue>168</blue>
+</color>
+</property>
+<widget class="KexiDBLabel" >
+<property name="name" >
+<cstring>TextLabel1</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>20</x>
+<y>40</y>
+<width>152</width>
+<height>36</height>
+</rect>
+</property>
+<property name="dataSource" >
+<string>name</string>
+</property>
+<property name="font" >
+<font>
+<family>Verdana</family>
+<pointsize>12</pointsize>
+<weight>50</weight>
+<bold>0</bold>
+<italic>1</italic>
+<underline>0</underline>
+<strikeout>0</strikeout>
+</font>
+</property>
+<property name="shadowEnabled" >
+<bool>true</bool>
+</property>
+<property name="text" >
+<string>Name</string>
+</property>
+<property name="alignment" >
+<set>WordBreak|AlignBottom</set>
+</property>
+</widget>
+<widget class="Line" >
+<property name="name" >
+<cstring>Line1</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>20</x>
+<y>80</y>
+<width>450</width>
+<height>11</height>
+</rect>
+</property>
+<property name="frameShadow" >
+<enum>Plain</enum>
+</property>
+<property name="lineWidth" >
+<number>3</number>
+</property>
+<property name="orientation" >
+<enum>Horizontal</enum>
+</property>
+</widget>
+<widget class="KexiDBLabel" >
+<property name="name" >
+<cstring>TextLabel2</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>180</x>
+<y>40</y>
+<width>182</width>
+<height>36</height>
+</rect>
+</property>
+<property name="dataSource" >
+<string>surname</string>
+</property>
+<property name="font" >
+<font>
+<family>Verdana</family>
+<pointsize>12</pointsize>
+<weight>50</weight>
+<bold>0</bold>
+<italic>1</italic>
+<underline>0</underline>
+<strikeout>0</strikeout>
+</font>
+</property>
+<property name="shadowEnabled" >
+<bool>true</bool>
+</property>
+<property name="text" >
+<string>Surname</string>
+</property>
+<property name="alignment" >
+<set>WordBreak|AlignBottom</set>
+</property>
+</widget>
+<widget class="KexiDBImageBox" >
+<property name="name" >
+<cstring>obrazek2</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>380</x>
+<y>10</y>
+<width>90</width>
+<height>70</height>
+</rect>
+</property>
+<property name="alignment" >
+<set>AlignTop|AlignRight</set>
+</property>
+<property name="storedPixmapId" >
+<number>3</number>
+</property>
+</widget>
+<widget class="KexiDBAutoField" >
+<property name="name" >
+<cstring>age</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>20</x>
+<y>150</y>
+<width>148</width>
+<height>21</height>
+</rect>
+</property>
+<property name="dataSource" >
+<string>age</string>
+</property>
+</widget>
+<widget class="KexiDBAutoField" >
+<property name="name" >
+<cstring>name</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>10</x>
+<y>100</y>
+<width>159</width>
+<height>21</height>
+</rect>
+</property>
+<property name="autoCaption" >
+<bool>false</bool>
+</property>
+<property name="dataSource" >
+<string>name</string>
+</property>
+</widget>
+<widget class="KexiDBAutoField" >
+<property name="name" >
+<cstring>surname</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>170</x>
+<y>100</y>
+<width>200</width>
+<height>21</height>
+</rect>
+</property>
+<property name="autoCaption" >
+<bool>false</bool>
+</property>
+<property name="dataSource" >
+<string>surname</string>
+</property>
+</widget>
+</widget>
+<layoutDefaults spacing="6" margin="11" />
+<tabstops>
+<tabstop>TextLabel1</tabstop>
+<tabstop>TextLabel2</tabstop>
+<tabstop>obrazek2</tabstop>
+<tabstop>age</tabstop>
+<tabstop>name</tabstop>
+<tabstop>surname</tabstop>
+</tabstops>
+</UI>
+', NULL);
+INSERT INTO "kexi__objectdata" VALUES(65, X'3C21444F43545950452055493E0A3C5549207374647365746465663D2231222076657273696F6E3D22332E3122203E0A3C6B66643A637573746F6D4865616465722076657273696F6E3D223222202F3E0A3C7069786D6170696E70726F6A6563742F3E0A3C636C6173733E515769646765743C2F636C6173733E0A3C77696467657420636C6173733D225157696467657422203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E666F726D313C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E303C2F783E0A3C793E303C2F793E0A3C77696474683E3533303C2F77696474683E0A3C6865696768743E3232303C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D226175746F54616253746F707322203E0A3C626F6F6C3E747275653C2F626F6F6C3E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2263617074696F6E22203E0A3C737472696E673E666F726D313C2F737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2264617461536F7572636522203E0A3C737472696E673E636172733C2F737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2264617461536F757263654D696D655479706522203E0A3C63737472696E673E6B6578692F7461626C653C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D22666F637573506F6C69637922203E0A3C656E756D3E4E6F466F6375733C2F656E756D3E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2269636F6E22202F3E0A3C70726F7065727479206E616D653D2270616C657474654261636B67726F756E64436F6C6F7222203E0A3C636F6C6F723E0A3C7265643E3230333C2F7265643E0A3C677265656E3E3233383C2F677265656E3E0A3C626C75653E3138353C2F626C75653E0A3C2F636F6C6F723E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2270616C657474654261636B67726F756E645069786D617022202F3E0A3C77696467657420636C6173733D224B65786944424C6162656C22203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E546578744C6162656C313C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E33303C2F783E0A3C793E32303C2F793E0A3C77696474683E3236363C2F77696474683E0A3C6865696768743E33343C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2264617461536F7572636522203E0A3C737472696E673E6D6F64656C3C2F737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D22666F6E7422203E0A3C666F6E743E0A3C66616D696C793E56657264616E613C2F66616D696C793E0A3C706F696E7473697A653E31363C2F706F696E7473697A653E0A3C7765696768743E37353C2F7765696768743E0A3C626F6C643E313C2F626F6C643E0A3C6974616C69633E303C2F6974616C69633E0A3C756E6465726C696E653E303C2F756E6465726C696E653E0A3C737472696B656F75743E303C2F737472696B656F75743E0A3C2F666F6E743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2270616C657474654261636B67726F756E645069786D617022202F3E0A3C70726F7065727479206E616D653D22736861646F77456E61626C656422203E0A3C626F6F6C3E747275653C2F626F6F6C3E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D227465787422203E0A3C737472696E673E4D6F64656C3C2F737472696E673E0A3C2F70726F70657274793E0A3C2F7769646765743E0A3C77696467657420636C6173733D224B65786944424C6162656C22203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E546578744C6162656C323C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E33303C2F783E0A3C793E3131303C2F793E0A3C77696474683E37363C2F77696474683E0A3C6865696768743E32343C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D227465787422203E0A3C737472696E673E49443C2F737472696E673E0A3C2F70726F70657274793E0A3C2F7769646765743E0A3C77696467657420636C6173733D224B65786950757368427574746F6E22203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E50757368427574746F6E313C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E3331303C2F783E0A3C793E3130303C2F793E0A3C77696474683E3133303C2F77696474683E0A3C6865696768743E34303C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D226F6E436C69636B416374696F6E22203E0A3C737472696E673E6B616374696F6E3A77696E646F775F636C6F73653C2F737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D227465787422203E0A3C737472696E673E436C6F73652057696E646F773C2F737472696E673E0A3C2F70726F70657274793E0A3C2F7769646765743E0A3C77696467657420636C6173733D224B65786950757368427574746F6E22203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E50757368427574746F6E323C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E3331303C2F783E0A3C793E3135303C2F793E0A3C77696474683E3133333C2F77696474683E0A3C6865696768743E33373C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D22666F637573506F6C69637922203E0A3C656E756D3E4E6F466F6375733C2F656E756D3E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D226F6E436C69636B416374696F6E22203E0A3C737472696E673E6B616374696F6E3A646174615F736176655F726F773C2F737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D227465787422203E0A3C737472696E673E53617665204368616E6765733C2F737472696E673E0A3C2F70726F70657274793E0A3C2F7769646765743E0A3C77696467657420636C6173733D224B65786944424C6162656C22203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E546578744C6162656C343C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E33303C2F783E0A3C793E3134303C2F793E0A3C77696474683E37363C2F77696474683E0A3C6865696768743E32343C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D227465787422203E0A3C737472696E673E4D6F64656C3C2F737472696E673E0A3C2F70726F70657274793E0A3C2F7769646765743E0A3C77696467657420636C6173733D224B6578694442496D616765426F7822203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E6F6272617A656B323C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E3331303C2F783E0A3C793E31303C2F793E0A3C77696474683E3131303C2F77696474683E0A3C6865696768743E37303C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2273746F7265645069786D6170496422203E0A3C6E756D6265723E313C2F6E756D6265723E0A3C2F70726F70657274793E0A3C2F7769646765743E0A3C77696467657420636C6173733D224B65786944424C696E654564697422203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E6964456469743C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E3131303C2F783E0A3C793E3131303C2F793E0A3C77696474683E3132303C2F77696474683E0A3C6865696768743E32303C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2264617461536F7572636522203E0A3C737472696E673E69643C2F737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D22666F637573506F6C69637922203E0A3C656E756D3E5374726F6E67466F6375733C2F656E756D3E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D227465787422203E0A3C737472696E673E3C2F737472696E673E0A3C2F70726F70657274793E0A3C2F7769646765743E0A3C77696467657420636C6173733D224B65786944424C696E654564697422203E0A3C70726F7065727479206E616D653D226E616D6522203E0A3C63737472696E673E6D6F64656C456469743C2F63737472696E673E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2267656F6D6574727922203E0A3C726563743E0A3C783E3131303C2F783E0A3C793E3134303C2F793E0A3C77696474683E3132303C2F77696474683E0A3C6865696768743E32303C2F6865696768743E0A3C2F726563743E0A3C2F70726F70657274793E0A3C70726F7065727479206E616D653D2264617461536F7572636522203E0A3C737472696E673E6D6F64656C3C2F737472696E673E0A3C2F70726F70657274793E0A3C2F7769646765743E0A3C2F7769646765743E0A3C696E636C75646568696E74733E0A3C696E636C75646568696E743E6B70757368627574746F6E2E683C2F696E636C75646568696E743E0A3C696E636C75646568696E743E6B6C696E65656469742E683C2F696E636C75646568696E743E0A3C2F696E636C75646568696E74733E0A3C6C61796F757444656661756C74732073706163696E673D223622206D617267696E3D22313122202F3E0A3C74616273746F70733E0A3C74616273746F703E546578744C6162656C313C2F74616273746F703E0A3C74616273746F703E6F6272617A656B323C2F74616273746F703E0A3C74616273746F703E546578744C6162656C323C2F74616273746F703E0A3C74616273746F703E6964456469743C2F74616273746F703E0A3C74616273746F703E50757368427574746F6E313C2F74616273746F703E0A3C74616273746F703E546578744C6162656C343C2F74616273746F703E0A3C74616273746F703E6D6F64656C456469743C2F74616273746F703E0A3C74616273746F703E50757368427574746F6E323C2F74616273746F703E0A3C2F74616273746F70733E0A3C2F55493E0A00', NULL);
+INSERT INTO "kexi__objectdata" VALUES(96, '<!DOCTYPE UI>
+<UI stdsetdef="1" version="3.1" >
+ <pixmapinproject/>
+ <class>QWidget</class>
+ <widget class="QWidget" >
+ <property name="name" >
+ <cstring>report2</cstring>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>590</width>
+ <height>590</height>
+ </rect>
+ </property>
+ <property name="caption" >
+ <string></string>
+ </property>
+ <widget class="Label" >
+ <property name="name" >
+ <cstring>Label1</cstring>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>10</x>
+ <y>20</y>
+ <width>566</width>
+ <height>134</height>
+ </rect>
+ </property>
+ <property name="backgroundOrigin" >
+ <enum>ParentOrigin</enum>
+ </property>
+ <property name="font" >
+ <font>
+ <family>Trebuchet MS</family>
+ <pointsize>48</pointsize>
+ <weight>75</weight>
+ <bold>1</bold>
+ <italic>1</italic>
+ <underline>0</underline>
+ <strikeout>0</strikeout>
+ </font>
+ </property>
+ <property name="text" >
+ <string>Sales Report</string>
+ </property>
+ </widget>
+ <widget class="PicLabel" >
+ <property name="name" >
+ <cstring>PicLabel1</cstring>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>420</x>
+ <y>170</y>
+ <width>1</width>
+ <height>1</height>
+ </rect>
+ </property>
+ <property name="backgroundMode" >
+ <enum>PaletteBackground</enum>
+ </property>
+ <property name="backgroundOrigin" >
+ <enum>ParentOrigin</enum>
+ </property>
+ <property name="cursor" >
+ <cursor>0</cursor>
+ </property>
+ <property name="frameShape" >
+ <enum>NoFrame</enum>
+ </property>
+ <property name="pixmap" >
+ <pixmap>image0</pixmap>
+ </property>
+ <property name="scaledContents" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="KexiSubReport" >
+ <property name="name" >
+ <cstring>SubReport1</cstring>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>20</x>
+ <y>170</y>
+ <width>330</width>
+ <height>290</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ <images>
+ <image name="image0" >
+ <data format="XBM.GZ" length="79" >789c534e494dcbcc4b554829cdcdad8c2fcf4c29c95030e0524611cd48cd4ccf28010a1797249664262b2467241641a592324b8aa363156c15aab914146aadb90067111b1f</data>
+ </image>
+ </images>
+ <layoutDefaults spacing="6" margin="11" />
+ <tabstops>
+ <tabstop>SubReport1</tabstop>
+ </tabstops>
+</UI>
+', NULL);
+INSERT INTO "kexi__objectdata" VALUES(101, '<!DOCTYPE script>
+<script language="python" ># This is Technology Preview (BETA) version of scripting
+# support in Kexi. The scripting API may change in details
+# in the next Kexi version.
+# For more information and documentation see
+# http://www.kexi-project.org/scripting/
+
+# Saves the contents of &quot;persons&quot; table, without the &quot;id&quot; column
+# to a &quot;SimpleReport54321.txt&quot; text file in the current user''s home
+# directory. Totals about average, minimum, maximum age are appended.
+
+import krosskexidb, krosskexiapp, os, codecs
+
+keximainwindow = krosskexiapp.get(&quot;KexiAppMainWindow&quot;)
+connection = keximainwindow.getConnection()
+table = connection.tableSchema(&quot;persons&quot;)
+if not table:
+ raise(&quot;No table ''persons''&quot;)
+query = table.query()
+
+cursor = connection.executeQuerySchema(query)
+if not cursor:
+ raise(&quot;Query failed&quot;)
+
+sum = 0
+max = 0
+min = 1000
+count = 0
+
+# Walk through all items in the table.
+filename = os.path.expanduser(&quot;~&quot;)+os.sep+&quot;SimpleReport54321.txt&quot;
+f = codecs.open(filename, &quot;wt&quot;, &quot;utf-8&quot;)
+if not f:
+ raise(&quot;Opening file failed&quot;)
+f.write(&quot;%s\t%s\t%s\n-------------------------\n&quot;
+ % (query.fieldlist().field(1).caption(),
+ query.fieldlist().field(2).caption(),
+ query.fieldlist().field(3).caption()))
+while cursor.moveNext():
+ count += 1
+ sum += cursor.value(1)
+ if max &lt; cursor.value(1):
+ max = cursor.value(1)
+ if min > cursor.value(1):
+ min = cursor.value(1)
+ f.write( &quot;%s\t%s\t%s\n&quot; % (cursor.value(1), cursor.value(2),cursor.value(3)))
+
+f.write( &quot;\nAverage age:\t%.2f\n&quot; % (sum / count) )
+f.write( &quot;Minimum age:\t%.2f\n&quot; % min)
+f.write( &quot;Maximum age:\t%.2f\n&quot; % max)
+f.close()
+</script>
+', NULL);
+INSERT INTO "kexi__objectdata" VALUES(104, 'SELECT persons.name, persons.surname, persons.age, cars.model, ownership.since FROM persons, ownership, cars WHERE cars.id = ownership.car AND persons.id = ownership.owner', 'sql');
+INSERT INTO "kexi__objectdata" VALUES(104, '<query_layout><table name="persons" x="380" y="54" width="110" height="132"/><table name="ownership" x="180" y="49" width="110" height="132"/><table name="cars" x="9" y="57" width="110" height="92"/><conn mtable="cars" mfield="id" dtable="ownership" dfield="car"/><conn mtable="persons" mfield="id" dtable="ownership" dfield="owner"/></query_layout>', 'query_layout');
+INSERT INTO "kexi__objectdata" VALUES(103, '<!DOCTYPE EXTENDED_TABLE_SCHEMA>
+<EXTENDED_TABLE_SCHEMA version="1" >
+ <field name="owner" >
+ <lookup-column>
+ <row-source>
+ <type>table</type>
+ <name>persons</name>
+ </row-source>
+ <bound-column>
+ <number>0</number>
+ </bound-column>
+ <visible-column>
+ <number>3</number>
+ </visible-column>
+ </lookup-column>
+ </field>
+ <field name="car" >
+ <lookup-column>
+ <row-source>
+ <type>table</type>
+ <name>cars</name>
+ </row-source>
+ <bound-column>
+ <number>0</number>
+ </bound-column>
+ <visible-column>
+ <number>1</number>
+ </visible-column>
+ </lookup-column>
+ </field>
+</EXTENDED_TABLE_SCHEMA>
+', 'extended_schema');
+INSERT INTO "kexi__objectdata" VALUES(105, '<!DOCTYPE UI>
+<UI stdsetdef="1" version="3.1" >
+<kfd:customHeader version="2" />
+<pixmapinproject/>
+<class>QWidget</class>
+<widget class="QWidget" >
+<property name="name" >
+<cstring>form1</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>0</x>
+<y>0</y>
+<width>514</width>
+<height>611</height>
+</rect>
+</property>
+<property name="autoTabStops" >
+<bool>true</bool>
+</property>
+<property name="dataSource" >
+<string>ownership</string>
+</property>
+<property name="dataSourceMimeType" >
+<cstring>kexi/table</cstring>
+</property>
+<property name="paletteBackgroundColor" >
+<color>
+<red>154</red>
+<green>168</green>
+<blue>198</blue>
+</color>
+</property>
+<widget class="KexiDBComboBox" >
+<property name="name" >
+<cstring>comboBox</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>100</x>
+<y>90</y>
+<width>121</width>
+<height>26</height>
+</rect>
+</property>
+<property name="dataSource" >
+<string>owner</string>
+</property>
+</widget>
+<widget class="KexiDBComboBox" >
+<property name="name" >
+<cstring>comboBox2</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>100</x>
+<y>130</y>
+<width>121</width>
+<height>26</height>
+</rect>
+</property>
+<property name="dataSource" >
+<string>car</string>
+</property>
+</widget>
+<widget class="KexiDBLabel" >
+<property name="name" >
+<cstring>label</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>20</x>
+<y>90</y>
+<width>55</width>
+<height>29</height>
+</rect>
+</property>
+<property name="text" >
+<string>Owner:</string>
+</property>
+</widget>
+<widget class="KexiDBLabel" >
+<property name="name" >
+<cstring>label2</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>20</x>
+<y>130</y>
+<width>69</width>
+<height>29</height>
+</rect>
+</property>
+<property name="text" >
+<string>Car:</string>
+</property>
+</widget>
+<widget class="KexiDBLabel" >
+<property name="name" >
+<cstring>label3</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>20</x>
+<y>170</y>
+<width>59</width>
+<height>29</height>
+</rect>
+</property>
+<property name="text" >
+<string>Since:</string>
+</property>
+</widget>
+<widget class="KexiDBLineEdit" >
+<property name="name" >
+<cstring>textBox</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>100</x>
+<y>170</y>
+<width>172</width>
+<height>26</height>
+</rect>
+</property>
+<property name="dataSource" >
+<string>since</string>
+</property>
+</widget>
+<widget class="KexiDBLabel" >
+<property name="name" >
+<cstring>TextLabel1</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>30</x>
+<y>20</y>
+<width>266</width>
+<height>44</height>
+</rect>
+</property>
+<property name="dataSource" >
+<string></string>
+</property>
+<property name="font" >
+<font>
+<family>Verdana</family>
+<pointsize>16</pointsize>
+<weight>75</weight>
+<bold>1</bold>
+<italic>0</italic>
+<underline>0</underline>
+<strikeout>0</strikeout>
+</font>
+</property>
+<property name="paletteBackgroundPixmap" />
+<property name="shadowEnabled" >
+<bool>true</bool>
+</property>
+<property name="text" >
+<string>Ownership</string>
+</property>
+</widget>
+<widget class="KexiDBImageBox" >
+<property name="name" >
+<cstring>image</cstring>
+</property>
+<property name="geometry" >
+<rect>
+<x>320</x>
+<y>20</y>
+<width>175</width>
+<height>149</height>
+</rect>
+</property>
+<property name="storedPixmapId" >
+<number>5</number>
+</property>
+</widget>
+</widget>
+<includehints>
+<includehint>kcombobox.h</includehint>
+<includehint>klineedit.h</includehint>
+</includehints>
+<layoutDefaults spacing="6" margin="11" />
+<tabstops>
+<tabstop>TextLabel1</tabstop>
+<tabstop>image</tabstop>
+<tabstop>label</tabstop>
+<tabstop>comboBox</tabstop>
+<tabstop>label2</tabstop>
+<tabstop>comboBox2</tabstop>
+<tabstop>label3</tabstop>
+<tabstop>textBox</tabstop>
+</tabstops>
+</UI>
+', NULL);
+INSERT INTO "kexi__objectdata" VALUES(106, '<!DOCTYPE macros>
+<macro xmlversion="1" >
+ <item action="open" >
+ <variable name="name" >cars</variable>
+ <variable name="object" >form</variable>
+ </item>
+ <item action="navigate" >
+ <variable name="record" >last</variable>
+ </item>
+</macro>
+', NULL);
+INSERT INTO "kexi__objectdata" VALUES(107, 'SELECT name, surname, age FROM persons WHERE persons.age > [Enter minimum person''s age] ORDER BY age', 'sql');
+INSERT INTO "kexi__objectdata" VALUES(107, NULL, 'query_layout');
+CREATE TABLE kexi__objects (o_id INTEGER PRIMARY KEY, o_type UNSIGNED Byte, o_name Text(200), o_caption Text(200), o_desc CLOB);
+INSERT INTO "kexi__objects" VALUES(1, 1, 'persons', 'Persons in our company', NULL);
+INSERT INTO "kexi__objects" VALUES(2, 1, 'cars', 'Cars', NULL);
+INSERT INTO "kexi__objects" VALUES(4, 3, 'persons', 'Persons in our company', NULL);
+INSERT INTO "kexi__objects" VALUES(65, 3, 'cars', 'Cars', NULL);
+INSERT INTO "kexi__objects" VALUES(96, 4, 'report', 'Report', NULL);
+INSERT INTO "kexi__objects" VALUES(101, 5, 'write_simple_text_report', 'Write simple text report', NULL);
+INSERT INTO "kexi__objects" VALUES(103, 1, 'ownership', 'Ownership', NULL);
+INSERT INTO "kexi__objects" VALUES(104, 2, 'persons_and_cars', 'Persons and cars', NULL);
+INSERT INTO "kexi__objects" VALUES(105, 3, 'ownership', 'Ownership', NULL);
+INSERT INTO "kexi__objects" VALUES(106, 7, 'open_cars_and_go_to_last', 'Open cars and go to the last row', NULL);
+INSERT INTO "kexi__objects" VALUES(107, 2, 'persons_by_age', 'Persons by age', NULL);
+CREATE TABLE kexi__parts (p_id INTEGER PRIMARY KEY, p_name Text(200), p_mime Text(200), p_url Text(200));
+INSERT INTO "kexi__parts" VALUES(1, 'Tables', 'kexi/table', 'http://koffice.org/kexi/');
+INSERT INTO "kexi__parts" VALUES(2, 'Queries', 'kexi/query', 'http://koffice.org/kexi/');
+INSERT INTO "kexi__parts" VALUES(3, 'Formularze', 'kexi/form', 'http://koffice.org/kexi/');
+INSERT INTO "kexi__parts" VALUES(4, 'Reports', 'kexi/report', 'http://www.koffice.org/kexi/');
+INSERT INTO "kexi__parts" VALUES(5, 'Scripts', 'kexi/script', 'http://www.koffice.org/kexi/');
+INSERT INTO "kexi__parts" VALUES(7, 'Macros', 'kexi/macro', 'http://www.koffice.org/kexi/');
+CREATE TABLE kexi__querydata (q_id UNSIGNED Integer, q_sql CLOB, q_valid Boolean);
+CREATE TABLE kexi__queryfields (q_id UNSIGNED Integer, f_order Integer, f_id Integer, f_tab_asterisk UNSIGNED Integer, f_alltab_asterisk Boolean);
+CREATE TABLE kexi__querytables (q_id UNSIGNED Integer, t_id UNSIGNED Integer, t_order UNSIGNED Integer);
+CREATE TABLE persons (id INTEGER PRIMARY KEY, age UNSIGNED Integer, name Text(200), surname Text(200));
+INSERT INTO "persons" VALUES(1, 32, 'Georege', 'Foster');
+INSERT INTO "persons" VALUES(2, 62, 'Joan', 'Shelley');
+INSERT INTO "persons" VALUES(3, 51, 'William', 'Gates ™');
+INSERT INTO "persons" VALUES(4, 45, 'John', 'Smith');
+INSERT INTO "persons" VALUES(10, 58, 'Александр', 'Пушкин');
+INSERT INTO "persons" VALUES(12, 79, 'Μιχαήλ', 'Στεφανόπουλος');
+CREATE TABLE kexi__blobs (o_id INTEGER PRIMARY KEY, o_data BLOB, o_name Text(200), o_caption Text(200), o_mime Text(200) NOT NULL, o_folder_id Integer UNSIGNED);
+INSERT INTO "kexi__blobs" VALUES(1, X'89504E470D0A1A0A0000000D4948445200000040000000400806000000AA6971DE0000000467414D410000AFC837058AE90000142349444154789CD59B79745CD57DC73FF7BD37FB48335A2DCBB2E47DB7B18D6D9684B0248436EC04DA104228344B2124E4A42181344DD2B43D49D3939ED393363D25699AD07242A0610D3B0683C166B321DE6D61C992656BD748B3BC99B7DEFEF1DE482369644B6008F99D73A5D168E6CEEFF7BDDFDF727FF70E7C10E49ACE395CDB75165CA3BEDF1F2DDEEF0F1C2797EDAF209EF85C5583F6351908360DB7F5FC2BF72FBB13C8FF41F57ACFE5DCEF6A5CDBF5A9D02D7DBBAEFE55413E724CCA1F1E93B2EE1F32924B77FD9AC4B9C9F74B95F79D727CB6F7E3346EBCEB9C0D89AF7FF3D3C959977F442312833931A8591EA42757B16AD03A731D849F67E895CC7BADCEFBE702D71D5B4F2C70C78A7981AB3FF5F1A4D8B81250C1B241FA8A0455D89E81FB7E5360FFEF0E6FE3D8F337B2EBCB87DE4BB5DE7B006EE89E4748BD7DEE6CEDC6CBCEAF8A9CBD160211306D9072B2329A0AAD06DCF75B83BDBF6DDBE71E7DE57A76DFB4F3BD52EFBD03E0FA911A22C6ADD5B5815B3E7E76B2FE431B2156094619C3278AAA408F84C71E36D8F9EB235D76D79BD7F3FB6BB7BC176A9E7A00AED916A176E9F5D10AE5AF3FBC3EB1E4C39B04893A283820DDE94FA32AD007BCBAD9E08D5F1C19D28FECFB3C7BAF7AE054AB7B0A01F8AEC22DB75E1A8A697FB366497CE339676954378221C175DED98C9A80B4023B5FB178E3A76DF95CFBE1DBD877F1CF4E9DCEA70A809B3BAA948AC47FCD69085EB96A5384F50B4117E0BC43C34B4501F21AB4EFB679ED27879DE1D623DF66DF9FFCF0DDCFECC9A949836B6F59BBA439FEC3CBAF8A2BDB02D06D422560BB60BCCB517041DA10A857A85A92540A9DDAC732E6656186EEDE0ABC6B884F0D00F3AF9BDFD05C7BDDAE55616D7D183AD3B033078E0B48705DB05C305D301C3067308AEF9336384905774925A1FED087470A97CEC2D5B752D867BC1BD5B55302803484702569171E52E0925A08F7C1AB83100E4052856A0DE22A8415080A10BEF395F341E9FFB0A4C7809C0BC3360CDAA08755E6DFD8C82235F085B7B77E2647EAFE6F02D63B55FDD4006003AE44051C090F031FAD83B380EDC3D0A3400F9ED1017F04050495F20098D25B75CB07C19578A848581A83458B15DACFAF446CD137496802DADFA9EAA70600189FDC256C16F0A17A384FC2964140783698FEC84D7B5E6FCC8BC2790DB0AE0E1E57A0DB71114E2128BD7023FC57CE584E210326A820E16560CD2CF8A880177ABC974D3BEFF8863786E1CC0658DD006A10065D68B340481033A82BA69253C4001BE996590009BB242CACF741E8F27CFA8420F886D784E1ECD9B0B8014418522E2836645DE8B2212E25722695D514720A5D00A44359321E7640AF867305BC7204462C2683200117AAC3B06E362C68041981011784098A800070C084BC0B150E27AFA9A721A706000B2F52D98CAEE044E9060A95B0711EEC6A83BE025E9503E04222042B664353232831E893200C0F27050F0017D89BC74BDE6E313ABE3B39B541B058964CA157CA816D51D8B4102287A1230BD1002C9D05CD4D6046A11FDF70E1192EFC11003A6C48994010EFB33E5031C0F10198820145D16D7851830B17C3653A1C8C40360E9D1230BCC5157800148D5785173B0E15FCB915FCCFFAA030C00CBAB84C0B0000D7869D1AACAA870379D04A0D2F198ACF0217E830BDDA00FCCF71E50787010BC4A184EA2E51B1F1349EC6C25812DED461D8828818BFEAA3D417604B18B2FDEC5114C50FB87FE82C20CFA68204373F1BF98FDBBE252E0A38D3640078795CBA9035C0564A0C2F66073FA6E6DD32D339FC616380BC06D5CCF36927C2EDEAB296D595F3AEC46E0F7B85C9C482682A5101075C0BF2627C563C29860A5ECDED6581E97E6259993100D6C55CE43ADC115CDC701E675D081B3F49A3BB0C6B8F456CAF4E7E7594BC39BDB964316E88192EA6031812B121BB4C7C71DE77E4D6CC6DFCFBE0F199DA023300207B11EBC351BE251AAAAE52CE3C47B0EE32A85B03224013697E7EB5CEE7EE31091AF5F4AE89932DBAC31422A56F88CDCCDB320A485B125BE85444CE8A5E3D100EB73851AEE79F070FCE70A6930390BF98059AC6EDA23A7A837AFAE911D67D02E66C00350A863E1A88CE6891DCFF992C9FFD1F93A03E9BFE4D0906EC13CF2D7D5F96330140F5F60066AF45405188348FD02C6A3776BA358F3A7FC7757C77F0F519CC3635F6E94BA80D2B7C494D046F5656AD98C5BA8F41F3260824BC5E9774C636EE45A7D55C8E0F06B8F6574D74D7CEA1707E0D474DCA3221A9C169517861E4445A9488026A00021D79E403C7A8E9E9E76BDF53A8B97837DFE3FB886309FA5E71BAF483BD37F13743CFBC6300E49944AC3AAE57E2EA37D4250B16B2F623D0B209C2B55E6BA76878B10891723C108ACB4856E3867BE7B3479B8FB8A88EB79DC94E9ED4604D1C5E4C9D040001220091010BE5896E22FB8F73D1E539AEB819EAEB0482206FF3363FE01FC9764B72DBC3A991D6DEBFE28E81FB6604C07DD7A05E99E31211E7DBEABCB91B587B06CCDB08D1599EF2AE3366A42CF93DF139BC7DAA9E57B9F5E1C56C359613FA443D7B853AAE83370AC010637B82899A0520967551B7F41178E3281BCE1EE2D25B0BCC5F244777E000010274D1C54FF937527D79B22F878C546BDF57F8E6C05DD306C0B892BF0FCE9FF36D56AE869675106FF434739DF1068F33BCF4799F11EE1808AE2DF8EA63CB782AB396C4A58DBC2134CFEF8164C00760B00C0001885A92F0EB43385BBA58BABC9B3FBD35C5D20D96577016638BAFBDF441181443FC4CDE457A4027BB2D28070FF7DEC9ED83FF342D00B297F17FB19B6FFC24C90630FC268BEB8E1927271A4C090872743BEB3DE7BF4F78407D67F32AFEF7F899345D3D975702412C139241585D015B074A0050210244F6A491CF77515FD5C9855FEA61C5793A8A2AB12D513EB1F85668A8E832CFDDEEAF184A8F60BE1AA5B7B5F7C77C7DE04EA6E81B8E7685BFB1943F0BAF5AB4027BC83B91181E06DBF118601963C32C3E2E788F4B875500B3E479C304D3E0FCA60ED4EC08BF7BAE9AB58BE2F4453C26CC0E4167C60320A840CD119DC0031DC4BAF6F0919B7673D11D1D542FCEA09B26BA6562B8FE90258F4BFECEBB79A4942C13CB38AE1E47AF1BA2C2AA3E3BB3D66DE62DFD5974265528E3D3A004D2235030BC68DF75000843AC06B410081F2FD7EF75E3576393D8519219A404D7E6D6459BA919DECF9DFF7D0367FEF94A8ECE8D61B9DE12CC1E30B19FEF414DB5B1E68A839C765537B16A8BACA14076428414131E4B3929880A21B8800BD8127A81CED38ED2A235DED071BB93E0B9D4177892FE130020BD667E3603410B1A6AA1F5101CDF0FC13804A21E106AC8CB49421B7B9F2C8201D82658A6C788820E852CE486B956D549CC3DC657EFBF939557AD409B1B61D65303E43BF7B3FCA3075870452BB39A24A609853493B3C344E34FF03F05C12636E106A07DE91196A80BAE38E4B62548E837F11BE34879001CD73BB0772558398FC2F317415B1BF4F700837EB0131356B88409AE3B361C076C7FBE781C16AFE713AB9BA8E87C832FFE32CD801925BE690FA9BB7E40C5C20692FD6B681FCA12D762A842C1C59DDAE7CBA54E31FEB700568A950855E1C8FC232C162DE7B72B471FB063EE67F985B5671C008EC46F6BB963AD26DB00AB079AE77AE5DAF1A3DE764D326664E9CA8FFEED7A603A2EC4E2D0DC024BD6C29C5510ABE71CB6F3E0854FF09F0F77B0EB2F9A18681AE2E96C07B998CBDC420BDB065FA32E54436DA09A8008A0A0E0FA0155965BFD09C60B84BFB516D8D226E12691A682B640B2CC5DB86ECFB15D5F05EBCB78C78E9E580E9EF19633BED7663B60F4C2EC066FF6F6C3E34198C800C75FF94814E635C1C255D0B80CA2358002D961E8ED666965378B236DECB09AA1100057E5655E677542678DB390C78F6F0301D5812A2AB50AE26A8CB01246555494220526D05EE2E24A898989EEE6483B19B24E0E4D53A8488471933643D161488BD9403DD031C60077820B4C94542FD424C15D08ADFE9EA31484A2E1A1303435C3826550BF70CC703DED05512303A96E1C53C1B0553F660870144065B7B5976C85CE658D1FE1C98E57E8CE77D1AD68201414A110101A01258022FCDC29E42800B6B4B1A485252D9050118A5193ACA43A996075640DA8F088F5045868789DC53117B065110077EA5EDBF020242A61D132D8BFD7DF13C8F18637CD87BA1688547906E7FD6826145055E83D0CF91CD80A8E8B779E60014ED1B143B45B9D64C3052E6C398397DA76D397EF0355E00A8981898139667829FDFDAAB42A52456D7525B16484399139ACD096532B6A79B6B0D9EB5D964496320C704EDC6C1C19F2FC7AD94A3878C06340C31C98D30CD58D104E805441CFF88617870A8E05BDED1EDA9683E34AA494608A12000002F433C033DA8B9C37EF43EC6A8BD091EB04B524668FFA7CB12A1524C395D4D524085629D4466A5912584215491CDBA59F7E0EE55B277592C662C028034E0200C0481A422138EF42C8668100842BC1D520972D31BAE8AB3E00831D904D7BF75F6CE9798F2B3D17B02786768D8C95E569F545CE99B789507B9043A9C31E08A52B0E5404135457C788D50589472A68D15AA822816B4B8618464365C84971BCD043A59318D74B1CCF00DBF582DE74DACDD91C5419A0443DD07259AF8D4BA9D1C5A178AE3078CC83DF9560491C47FA2E500E004F3DC332D8AC6CE3ACE6F584DC15ECEE3FE07D8E84682846635D0DA15A052DA251A7D55345126C4891F63BCB0A9AD0D8973F80659A93DAE96331C0990103C043D130A16041A1E0193969D57DE3CD3CA4FBBCB2BAD8FAB5188D01D252BCCE68D9FCA62071D92676B0B669151BC5691C49759348C6D0EA244ED426AA5690A40A610B46C8A078891021042A2ABA9BA735D70EA8632DF5890038D25346D8F6F45A8CD2674BAE00791D1465B2E1527AFB06233B5640E1FD92165E0C2806C1B20C288A0A48DE32DEE2632D1770CBF26BF8D9D0BD546A09926E358A5448DBBEE1C2371EC5FF5BE160FE1086950745F58260897DA300B82E9A342D845DAC034ED2A691AE57E5E5B2A0E72630C0FFBF6397F85BC97C1318802D3C373859674406783BD7414CC4B04C17E10A3222E79B3BB6EA45F315A130600DD063F479A62AF8E79765624057961D55AFEEBDBC6941030147F71B202750487A911CD304C328317CF4C764C34BC5612C085AD30100900235A061981679C3206D67C7567CD4706FD50582B49D65C0EA67342E150F54CA6581CBDFE297DF1F4EB55C9277FE72D9B23944ECACB782620AA5A45FEADA7EED20989EEBF8A2966C28A5C9495C600C00D702C771D10B79145529A1BC182D8EA494E86E9E825318331EC6CE13CA0541A0E73B47F8D1889D362FD7EDCFAF3DAD45AB70D2DEA5DE7220146380E54CFD9A72220045A1ADCF2693B328601293517266EEE4734805C3B418C967700D9B9C920784977C1020252E2E8E2C9E954C98AF78A85A2E08E285A2B61F77F1A394AD67AECAB77DE5CCD3E7876BB42CE40B93952B05C0724EAEBC9793E8D7054F1FCCB0B94DCFBE259D5D7BF6B5352D3CA3A51955D097EFF762C9940008A4267102124C7015AF08F2BA6C13DDAE8C3E45004A64E2B9800D74FEA2879FF4168C61DD3C7CC7D9EBE755CE8928904EFB91BE1400777A00A882B4A9F0424781275A75F38D82BD673FBC94851DFC4B4FA0F536E74BCB362E5A1792418E668F7B9A96651D485B41DAC24B9B8A52DC1F963778E29B1510528C7B71B98B9212C8B61668EDCEBABDF19191D3137535B1EA880ABAEE7788FDCD4F651286731E438A3BC2D2011852E1E5A30E3FDF99717E7B347FE839DB7DF4303C62C266E03572720F2FE5F60E34E717D736D6CFAD0F5633984B81EB97C7E306C4D518712D464FBA0F5CA5CC6BCABF4F488DA819C0EECF62B5190769E5496068AA9BA212C8755A1C3E9E955DC1C1E1B5F1BADA647D2C80D0F5B13D7F2209A91CE83E008E1C0D328ED078734870F7EE1CF71ECE1D79DA741FDF030FE5E159603BF03690010A38F4F082BE2B353B3737DE9858D81C9F4D7F26E51F802A1E18AE67484C8B8F0120D5B1FF951D8014446588E09089B577D8305ED6DFE4359EC0E0F7C0C8C9AECAEAC71DDA0FEAB485FA526B42D555B58DD5519474C607A00A86B2630C901284C2415DE3DE4305EE39943BFE58D67EF67578300B4F03DB8003C030E38F4A2CA09FEDC6EE9164A65A6B8CAC589C6C61203B826B3B63203810F701E81D3E1100000A511126AA837968D02E6CCBEC779F741E651F8F60B00DE804ACE9DC152E0CBB1CFD7D8E7DE1FE91E54462B39BE7245147867D066441CF83AA71D40DF160A7C33D07B3430FA7EC175F903C340C4F002F017B8001A6BEE06C0383ECB0F66442C361D91058BB72D6623190C9605B964F778805623E00FDE501900A61254CDC56718EA664EED5C136E749FB3176F2083ACF02AF02470003A67F59DA2840F76B05F6460733F36C119CD7D2524B30A4C1708E7E53F0D490C2DD0773D987FB8C579FB179B0171E075E04DE027A99DE7D5E071862B7B327AB8D885C95B97E5DF34A3595C962160C7005B1408C58204A5F6A3200212544A508C0E008D91DFDC7CCA78CA7799987C8F02C63EC1BA1847D33B92D6E49E8DD61B2571DC8CD2A5862496DCB6C76B467B8E740B6F0C0B1FC5B8F1BF2A10EF89D0B5B801DC0317CA467202E9066BFBBB760A4F30355231B4E5FB02A90CB9BE8D91CB16005F14094BED400B82A3882A012A22A1C45C9E5C8ECEB19349ED6B7C867E4830CF1241EFBF602839461DF4CAFCBDB40FF1E873DCA702179686FCF82C7BBB2871ECEB98FEE933C62C173C0EB4007DE971F67501B4E0221433BFBADE15CAA3739B471F5A2E561DBD4304D8B782846FF403FC14094443842188BE1D663E9FCE691EDF271F7218E8FB26F171EFBA63CA89F66F9364982C0E2597041AF77F7793FD08A77CDEF24B702662402A863259FD4AEA8F8DB0DEB37CD165614DD3068EF6F2712721939DA9B2FECCCEE661B5BD179132FD61C01D24C6301DE2900E0155155782C4A3173AA4F570450C5422EE192C0F7CE39FFDCF905C366F7EE37EDC2AEF47E5E925B19E20D3CC3DBF032CCB4BF49F26E0078BF2541031772B1F8163A515E96DBE9E4753C9AB7E2F9F88CD9F7C7040078DF0D580334E37D07A315CFC7A7792D6BB2FCB10120F04ED0C37841F65D7FCBFCFF016C6165351D8EB4D80000000049454E44AE426082', 'blockdevice.png', 'blockdevice', 'image/png', 0);
+INSERT INTO "kexi__blobs" VALUES(3, X'89504E470D0A1A0A0000000D4948445200000040000000400806000000AA6971DE0000000467414D410000D904DCB2DA020000135949444154789CD59B7B9025557DC73FE774F7EDFB98E7CE30B3B33B3BFB6477D9058145107CA06CB994C19060225652BE922ACBB24CA2FFA42A9A5425B18C1563122B188BC20A5634C45891604011214A11C00701161784957DB13BFB9CF7EBCE7DF5E39CFC714EF7ED7B7756B0E66E343DD5D3EFEEDFEF7B7EBFEFF9FD7EE75CC1C55B04E0FCC57B0AAF1FECD21FF05DF6F99EDEE638E038E04881230582783156FA450D4F2E54DC073E7C576D3F1003EA22CAD622E4C578A7F8BD9BF3EB770F7247D117EF92A9D220A5D97A9E4BCECFE3E6F2B8AE44EA081D2E134495A341C05DF73E1FDD7DEFF729739181B81800381F7E7BE1EA2D97E86F79AE1871A4696DA3B86E82E0402E972797CF93F30B787E012F57C0118AB03A4DA33A796E7E517FE2035F0CBF0E4480BE08B2E274F87DF2A6CBF21B2EDF20BE2B8533AAB5406B81D2A035986373A306B4D6081C341210201CA4EB93EB1AC6EF5EDBED8BE577DDF6FAE8D28A8C1F3D7292808B004227011040FED63DB94FE73DF97652A5CD252DECB1106884BDA6ED7589100E0803821002E9E629F46D4452BF7CF760E52619AB6FBD78861A1D06A193004860E02D977A770A210A68AB3456629D553CB10681D6CA3C2A84114708732C1D100E85DE31745C5FBF6970F9DA2367D57F4E2E76D6123A058000DCB75CEA5E75CD36E7A3911684B1304AEBA6D26414CF5A88D6809008213160242E61F6F3BDA3C4D5994DBB36349C6F3FA39EC0704247964E002000E76F3F90DB77F3D5E26BC3FDAA6B7448B37640B35C835A3D51B85DE9042050060152E5535730FB4238E4BA86508BE3D70EF5F0C833C7D4393A64059D00407E649F77D9EE51F9B074C41A8D00348EA311B2A8A7E795D02AAB745BCB6B61B8009DB67CD3129A6EE17A45A2B0EE94E4E29A07F7AB0781A003B2AF1A000114DF7FA3FBD9822FAE4DFC3D69DD2D9B2F17575F7F0B9540333335795ECB5BFD536E40E9D4EC110291708205C4F37B6894C7B7D72AF1378E4E314307AC60B5004860E0D66BDCBF914274D1E6EF97BDF9838C5EF10E46378C3170C908A74E1C230802B400682545B2AED0AEBCB50C2757A4BA3821BBFC68F2B197E2A7E80017B8AB7C5EEE19F3D649C4DA300295097AB4D4745DB29352CF20859E014A7D43AC1BDBCC03FFFECF4C9D1D27B64151F6FE5869948E40446811A0A503C205E1185165809B1FA4AB50DE0B7C11A8FDD201E82AA9E130767100854661CC4AB83EBD831B414548295933BC897CB187F77D7880EFDCF72F1C7CFE1994046581D009102AC2B8B7551A172D1C10010817E1F59077D9037401F3AC32545EAD0BE446FAE4B69DEB9DDF6D129A21BAB51BAF64C7D5EFC0B8A902ADF0FC02F9522F5BB6ED402379E5C8CB2DDD641A1F288D161264B33710492F01541626720B95E89BA7E638FBCB06C09D5DD23D375E96FB5012F1986E4D70C5F5BFC9F0E8762B5FD3D11DC7C52F76B361E3468647C678F9A73F218AE2D61E024B96A9D2D2B881ED12CB7367A9D4C5932F9E560759250FACD605542DA25EAEA9E9922F2F518EC6D120356CD9F906505186C464BAEF484977DF30575DF7662E5937CAD7FFE90EA6274EA10CF51957D291C91184E501E998D5770915F89E1EEA80FCD6A6560100505BA8EA639182303611E0F0E84EBA4ABD1087CD55256B042A44A0C8977AD8B869071FFBB3CF71FD5BDF49AC24610451240823683422EA8D887A23A0518F68D443824640180984145D1D90BF23815061C31A6FEB6097BC36F1E3EBDEF66ED6AF1F83AC5FA459814A8F85D638AE4BCEF7D97DD5B55CBEE78DCC4C4D3179EE8CED224DB224B440A188823ACB4BB3444183A945F5C39F9E523F02EAAB11BE13003871A40ADB47BCDB0442680DEF78F7C729BA3168437E2B847F49860468840029047D7D035C77E3DBB9E686BD14BB7B4138848D06E5A579824695B8510555A711A9897B7F147CA11A32CE2A23C2D5FA9006EA87A7D4E199B27A79B05BEEEA2EF53130B209E60F59FF952063EBFF4E8613EC3969CF27C98F920CAF5DC72DBFF53E732E5862FAF063DC79E71D6955E9F9F1E8C1E92AA780C62AE55F3D89605A60FAD844F4706F31B76B78F452D0B159556C14D489E22B00A125089509812D61266B58C6753D8250E068982BC707EFDF1FDD074C02E16A855F3589600A980B3F3A1A7E6FB9AA678BA55E8802438F71B0C21A36B7093126D754DB39154063917313D384114CCEAB135F7DB2FE79E008B0480772814E00A0814A23E6E4CB93D17D7D5DDD467001C40D03C64A4028BB8D32BD440B3091390EAB1C3E32CED9B2F7ECDD4FD43EBDDCE06960820ED5043A01001861A61E7F39B85F214F138526B34B156E5865DB95CF1E8719EB0820AAC1C23150217B6F7A2BCF4F16BEAA144F03C7812A1DAA07748203C0187C1938592A75ED4745A3088C82D2109B09662C1FA80C0F48C7F280D3E40715406DCA58815B40B872E1C523E70E0027810A1D2C89750A00306438373BB77812151A85750C718C61784B88B6D68770DA08D2A6BF61158265105647E932313B770C98A3832D9F2C9D0440038DA327675E21AA81F4005B0A161A7404B1B0E1B10D6B556205D2F418519D34B7B1754284C7E989B943C072F362E7964E7140B2444F1E383E4E6883B3D20820C80C0C8052C6D7A3865138AC417D09828A01210D98B0204A9E3F7AF61026E2EBF8B840A70108BFF3D491B341BDDA4029A340692D48D72AA55A8150B1B1886CA89C02A0D224E86B0FFCF800FF4F008880A5277EBCFFFB04CB360C16501C04A7D812FE1A65B380647361C0C943AE9BE9B9CAD9670F9E384B878AA0ED4BA787C6009C7FFBDE81A9DDC3955D3BC6360C4927672A1ED2332417B5056FEDA39302C8F582DFC7FCE252ED4FFFE16B5FDCFFB3D34F6148F0577A682C5922AD099E3E383EA52BE3C3BB37AD1BF6F305C7B0BA04C7B3BE1E9BBBEDD09991C687C20078459E7BE1B9D9CFDCF185EF7EE5C123F72A38C12AB3BE0B2D17038018A82F54F4E2B3476627860A4B1B370F75AD2BE68B1845ED5018A299294A17FC3EF07BA8D52A9C38FC0C0F3FFEF40FFEFE1BAFDC15C3CF8005FBDE8E2F9DEC06934563AAB527E7CACA5970471EAFAAE09A85233F6174FD16FC421769E6E795C0F5417A34EA5526C69FA3B234CDD88EDD8C6C0D0FC7EC3F0CCCD081A4E742CBC500004C3357817327CE2D1CDCB0ED661666A63872F420DD5DFDF40FAEC3F34BC4518D5A638685D9B354971718DEB091B11D6F44484957D1AF62129E8B427EC972B100001B188561B400D0373844576F3F93278FF3CAD1036865F82C972FB066782D9B77EFC2F5F2245DE1406F3ECFFFC134998B098000744D5E32787C2A715F09DD5BE9E9DA4CBD161009075C977A4E706EC9A330B483DAEC51A2A0C1B972616C74D7F503A70F3EB5C84504A2D3536492F7799FBDE39E3DC3A5C27B7A064A1FDCFBB61BD6B47F496B2897EB144B795C071CC7C1910E5114A24C2D507FE14BDF7C48C4F2B94654FDE183F7DDF583175E78219920D1B1EE70B50024CFCBF7BEF78F4AD7BEE9C61B8BF9E22D7E2E775BB15418291472144B79AE7ADD16FA7B4A2D0F8641C8F4C123B8BD3D0C6D1E6DB9A6B566A95C637A6691B9B932274E4D71F6CC7465A9B2FC6810D6FF6BE2DC9947BE7CE7A74E602C6355D6F18B02902AFCB14F7E6670F3E88E1B0ABE7FA3D2FA0DA542F11A3FEFFAC5629E829F434A81941221254858373CC0964DC3388E248E22169F7C8A358D654204B54B77D0B3751300956A83F1535368A5F0FD1C39CFB503EE9AE9D9454E9F9961727A81E9E9B9438D5AF050A55179E4F147BEF2E4FEFDFB9350F917B28ED70240728FFB579FBDE79AA1C1DEDF108E7B8B97CB5D2924E45C8762318F94D2AE2255DEB100180C247E21C7E68DC32C3FFB13D63FF303BC0DA320254B533304B7FF0E35E9313DB3603E28EC609894B8AEC4731D5CCF414A81528A4610333935CFB989594E9D99AECC2D2C3C1406F5470EBCF4CCFD8FDE7FCFFC6B05E3D50010FBF6ED2BFEFA6F7FF40FBB0AA58F7839779356B199D7E74A5CE920A563847424D249402003481380041CBF5266E7F71FC01FDB808E2296AA0D5ED97B2BC291A95422114D348514809012CF73C8792ED2DE1FC7310B0BCB1C3B7196574E9CABCCCCCE7F737A72FCAFEFF9F2E70EF32A01D4CF8B04E5EDB77F70F8D6777DE8B1357D7DEF735CFA82B081B48396D28863AADA429A995D088414F6D8D4FA85B0C7909E8FFD02B9A041D7C9E388A525C6AFDF4B58EA4208F34EB33D7F3163868A288A69042171AC701C737FA1E033B27680CD1B4772C542FECA724DBF7FCBA5AF7BFA85E79E1CE7E758C285001040E10F3EFEA9FFE8E9E9B9C171054BE56524C69F93D614422030AD2C84300ADBF332B31A1068D90E1C3F42F78E6D0821582CF610F4F5A79F5E59FFF375504A1145068424A9743D879E9E12BEE7F94BE5F0E65884F79E193F56BE1008174A87BD3FFFCB3B6F2F95BAF7160B39E6E717D1CA8CFD279CABDAB857A4FF928D68B92645F22963056EA50CAE0B9E47AEBC64EFFAF91E99CD9C9506A534511453AB05C4B1326BA4F05C97AD9BD6B1767860ED9EABF77E02C85DE89D2B012080D2BAF5639FCCFB1EE5E52A61A49AA93CED50AE2CB4485FDFFC849432BDEA2D2D9A5298E7E155CAAFF2B6B62BC617921D8230260C23945276D5388E43775791EE9EFEDF7FD34DEFDCCE05AC7D2500DC3FFE93CFBD335F28ECF03C97C5B2B59EA476814DE21241561053640E8C5B6741301772F3B3E684E3905B9833B78BF45F9304B34A277AD3DC57CAEC348290288A8922451CC5CCCE2D3235BD88EBBAB9DD97DFF07E2E60052B0190EFEBE9BFDA71244A1B7495D2166BDD543E559716E185CDF48494D6FFCD440F292552984F4A09EED222781E380EDEE282E51469794258F02EE4163A33CDD68E39C79A308C514A515EAE71F0D029FC9C872325F9427E0F507C2D0008C0F70BF92D524882204C21D770C1982BF17991CCF012591EC8B881B0E4A9344EA30EF5BAB180B959B24F8896B7B6029D42902AAFD3C42A568AE5E53A2F1D3C4114C50821701D875CAE78A905E0BC065F09805CA150DA220484616890BE60C099689A51DCF6E1494B4A5BF54E822481C02F2F98C72D00EEC25C1A33886CAF91FA9095CCCCAF6B758384943554AB0D0E1E1AA7D1884CCD55291CC7A158288E0E0DADEF7FAD00E40BC5E218421004514B9D32D96F7D40B4FC17649548CC5A368191D2B07E2E074100F53A6E18E04491A913B53C275A2D6285840A3B33AD5CAE72E8C81982466C7B084386C6052557EE79EB0E5620C27600E4CE9D57F4FBB97C9FB0C4A2B3933ADAB14A1A27D35AA915644030F140332CCE95CBE68575EB0652E22D2F810D9D130B485ED60C8CEC3643885AC3CCF412274FCFD8D0B9B54934C6FA4646C776B142FA7F1E00D7DDB077BB90E6C341985880A68571B23088260A690C9F448632131C25798290B8E525F34C06007F6ED65EA7053891809006484DB788A2883367E7989B5FB6BF4449C26D43C0666B7CB0B777601B2BF404ED88B8EB464637258AC4516CBE95718304F96C57959A6A4BE46781B04298D508E94F4F9820A85A4D08026F61CE84D742A00408AD5198A9D76678CDCC1902130095CB35E6172B8461842B2511D91CC46EB5365BA5F0F3851D806FCD286DC67600BCDEBE81312125688DD21A37496844AAAE152AB501D2F83D13CBB784C26986685AC55D9C3783A6F5BA79464A72733348298DC96AD3ED4AFB192584B542A8D7429696AB046164585E4A22A96D786E15962A553C21D762A1B40DC8B70390750101785DA5EEED00511CA7AD98155ECA24DE6F3E95F8BCCC9A7DA278F6BC15C69D9B37114CB50AB51A08813337DB34E14C4E9180DA68842C2DD5A8D6CCB42029256E46A6D6ADB10221A5E984A52097F3FB77EFBE611D6D44D80E80EF170A631288C23855C2DAEE792D2B853419600B3D5B9F4DACA6252DB600CC4E9B5BAB55B36A8D3735D97A9F90680D611053A90684A1196697D2A4DD6EFB7B316C9F286E08151B7A1AC2DEBEFB8A5DAF0640BE902FAC431A0B9036C595584553563EBFF8916687ED00655AC5B1AB776A1CA2C8B47EB50A4AE14E4F993800815226AA0BC39858693BA33C03604B6B93569E921F5A98F02BF92305746878C34EC0BB1000F2A69B6E59E779B922C83492123209669A7EDCB48A0CBB5BE564B6EB92E75B80E3489C99290340A5024B4BB0BC8CFBC28134BCD50978494B3B59E5B3AED82CB909DBE2ADC0D87B6CEB168BDD5B69EB09B224E86CDDBE6B9330EF238EA34C5C9E35FF4CCBDBF3D9FE3A2B9CB48593340E1012D108101313A6E51312540AE7CC696BE2021741E4821B692207241A2935AE84484A8452B6875119802DE10965C531D3EFD298444AFC9CBFD1029012614B1C70F797FEEEC59FBDF4C2A348181CE837A52FD1EC9B9320DFC67B6D7C90B4481B21A5AD6713A3E949F07DB37677434F0F944A88FE3EE4C2BC2538D1F471A7F92E6BCFD6DC699AB875D3A42B6E56A4486392A05E2F3FFC9DAFDF4D5B24D31E1ACAFF7EECA1D3F9BC2FB76CD9BEA9AFAF3717C5315A83E3485CC73126EC3A4847E2380EAE63CD3AB996EEB7AED2A482E48E1FA5FBA92760DB36E8EF3720E4F350ABB1FCE6BD285B191222F3BBA2C435B4466BD5B2AFECBED2CAE62DC9BEB916C7913A71FCE583FFFA95CFFFE3E143077E089C2333DCD646DFF8C008B0BDD4DD7DD9BE7DB7BD7EE3C6AD234A930BC30021AC82D2413A8E9DBA6A8190B24559C711489900D0FC8C77E2387DDFBE17B66E8562D1B07418C299332CECFD35C2D1B1F45E93D0C4C4B1B25B5300554A11C73161AC88558C8A35B132152165AEEB58C59C39736AEAF903FFF3F2E993475F020E63A6D82D922994B6E79902C3923DC0303084F969CA054B4ABFA28BCD60A8607E563305CC6246AD5BF2DAFF055B3FD5E723B21B8E0000000049454E44AE426082', 'kuser.png', 'kuser', 'image/png', 0);
+INSERT INTO "kexi__blobs" VALUES(4, X'89504E470D0A1A0A0000000D4948445200000080000000800806000000C33E61CB0000000467414D410000AFC837058AE90000001974455874536F6674776172650041646F626520496D616765526561647971C9653C0000380F4944415478DA62FCFFFF3F03232323C328A00B60026266208605F83F244C77008A7B800062198D13FA447CAF1D83A88938433E3323831D28CF7DF9C570DC7323431B50EE0710FF02E23F03E1308000621C2D01689EE3590E84321472B1305402D9FCC0E06680E1B73F1836F86C62C8018A7F04E2EF40FC97DE25004000318DC6116D321628E2770733849D8A64B8C3CDC2D0018A7C0660A4F36A3A30B08B2A80980C42EC0C01B9FA0C3640260F10B322550D74030001345A05503FE299370730188B7032F4B2323258832B7A606CB38B28302866CC67E0D57260F8F3F503C3E944417022D01761F00252E7A125C06F2CA50023B4246182B2FF23B51BFE53EA6080001A4D00D403CCABBC195464F918EAD89818A220452C30D6380518E4E3FA1944EC121081CE2DC0C0074C081FAE1E6010E36430020A7103313B107F434B00CC6BFD8066F232D401A35AFED73F862757DE30CCCCD8C37016DA76F843690312208046130015EAF9E9CE0C22BAA20C859CCC0C59403E1F2CF225831A18C43DF31998B904A04A1F43E3589D814F1B920004D8197480025C40CC068D0F5029C034CB8D41444F14DC76A80099052A4558806580B1188357963E83CFB48B0C5780EABE40D5935D120004D06802A0B04B77388221898795A1131805FCFFA1112F649BC0201554CFC006ACEB21E00D10CF03E29B50BE2CB0043003AB05455DB5098373EB198665D076C0DF43110C41FCEC0C5D400B64FF411B8C3C1A0E0C9FAE1D00D501BC9E0A0C65C00490072D01FE52D2780408A0D10440663DBF3794C119987B7B80DD3A1D683C8223491218F13CC0861E048072FB2620DE8D66C463067E6D6B06983E453E7035B0B1C69241335815D8766062B086E57A6EA09952D1FD0C9C72060C77FB0319DE9FDEC000949703AAE705E2CF94F61E00026834019016F14C2B7D1854550519660323DE06D6C0631356609000463C28E723C06E68E47FC361DC4D067E603BE0FD95030C125C0CFA5B8319AA15F919426111CF04AC36A4A3FA19046D2066FEF9FAECE77FE62FBF401D89BFFFC0A50F27B4C460865603640180001A4D004436F02603EB794B29863E766003EF1FB4C60535F0C4DC0B182402EB9194821AF42BA0C53E3E004C00C076C08FDBC086A02003A8AE50808D0F08BB16308805D483DB0EFF7E7FFEF3FEF2DC976F4EB73EFFFEE08D04B0C8E015E660D086463E0BA55D4780001A4D0004EAF9D9C0C698BE18432107B081070C693E586B4BC4AD80413CB01EA98107AADF3722D5F3F8C1F79BF7804A7731F07343221D94A8B8D41D18A493E733B08A40DA0E5F1EED7DFFE240CE833F1F6E7C676464F8CFF99781F927349140733E33B42D42360008A0D10480A78177229A21898B95A11AD4188335F0780D03C075329B0872030F54D41F25CAE03F6FBF30BCDD7C9EE1D3D1DB0CB0061E0BB00A118FEC079B0D023FDFDDFCF6F270C983AF0FB67C646262F8C70CC4A004C0F58F91E903B00800B9255683416DF10D86279496000001349A00B034F080AD7067010E48030F56CF73C81A304802EB6450A30CD1C0DB03ADEBBF1165F8FB2DE719DEEFB9CAF0F7DB2F702482EA7941970206113F4815022AEEDF9CE97DFAF654E33368C4FF872500903CC72F060E5835C1CF06EE6EB2209502648D070004D06802406AE06D0C645051E067E8656362F086D5F3A0DC29E65FCF206083DCC03B0AADE7898BF86F171F32BC597592E1F79B2FF05CCF6795C020EC5B0F2FEEBF9D9DC0F0F454CB8D3FBFDE7E0646F85F78CE47EAE3733322E6115404185491AA01B24B0180001A4D00A0811C5706510B29867A4E16864C7000834215983B85808D3121D77CB47A7E1E110D3C4471FF66D161866F379FC3238E4DC6804134BC1F5CDF83078CDE5F6478B73981E1F7AB0B0CC048E7646265F8881CF9A0A21FA48EED2364808909C8072622466022E545EA05909D00000268242700F04CDDC5448606606066C31A7820CC0FCC9DA03A1975046F39D10DBC7FDF7F317CDC7A9EE1E3BEABB0061B0323B0C7201AD60FCEF9908A1E587D3F6A61607C7F9DE1DFA7DB608BD9FE33F0013BF4CFC1118F14F9E016DF2FF04821030730617C063606A5B81874D1AA00B20040008DC404009EA93B13C790C4CD0669E0C1EA792E350706C924442B1C52C4AF20BA8107029FF75F0547FE1F503D0F2BEE9D8025894F3DB8CE67F8FB8981E1C502209E0F610333361BBF18C3F7EFF71998FE831321D6DCCCFC93113457001A04FA07349319584280E60FD8284D000001C432C2229EF9580C83333F3B432D30D75BC3EA7950B12C16812896C969E0FDBCFD82E1C3DA130C3F1FBF830FF172A83A3088C6CF07B723201D86B50C0C4F2741723F1C7C02260071866F2FC009809DE92F03CF7F26F0FA0038002DD960FF068E70067666705A65E0656550A446090010402C2324E299B68630A82809808759BD61F53C2B306284812D7078B10C6FE06D22BA9EFFFBEE0B30E24F327C0736F4600D3C6621050691B8F90C1C6AD004F5ED3AB0B86F06C6F549AC66708870C16B17E6BF0CFC7F59183E814A02E40620F30FF08411030F0BE35FD0420E905D91AA0C1ACB6F83BB8264270080001AEE098069960783A895346A030F5414F33B1730083AE7438A6578036F39B4BE27AE9EFF7AE02AC3E7EDE721110F1D19E4712C6010F0868E0C828AF8C76D0C0CAF8139FF3FEE5E1A23CB0F06561E4186DF5FDE83DA0142DFFE333CF9F70F980098C08BB5FE33FF646063FE0F2A20C03382FF60258C003BB861C84A4957102080580622374269585D87BCC081AA0DBCAB290C0DC02213DCC08359C46B99006E8C21221E7DA68E8851BC53B7C1110F6AE5C3EA792EF30406C1907E70220083D78B18189E4D0676053E126122B01A101063F8FDF93DBC1D80DC1660FFCCC00BAA06C05D4116480200614D410643A0D00E4A7A02000144AF04008E90BB790C4E3C6C0C0EA0C90C2066FCFA9BE1D1E9670C8763D733DC65804C68C0A637FF5390C058CE2430040B7030743143A753C18108AC8F458011CF2E6B407603EFF79D170C9F779C67F875FB39BCB8675371601008EE676095819AFBE534B0B8AF82D4F3FF89F5C637060E6129862F8F218990F90F83C87F568657E04400B2E30723274A95016C077CFBC3C0C4C3C220CE805847C0C440C6AC204000D13A0130AF0B6710B15760280016C1D1C03EAC2CF2A24841A0B76480BDD97BB90CEBD6DD60E82AD90DCE863F18B02F8D22D8C03B9D009EA2AD854DA7FE870EE4801A629C6AE437F0FE038BFBCF1B4E82733E7C9C0058CFF37AD683733E24753C0316F735C004700A5896FD432ADC18884ACF6CC014CBC4C2C6F0EFCF2F06D67F0C423FFF33BC86C9B17E01AF196480950AA00400CC3C4C5C9004C04A494310208068950098D783225E91A100E8C82C60C4F3FD83D65BA00064145460F8F7EE01840F1406B68182520D19825C1519A6E8CF62686580AC74F9416469C0BC2B023C8257072CEEA3601104EA770B87F6838B7C721B78E0E4B2F33CC3B7435781590ED2AD63E41060E0B62F60E076C88714F77F3F3330BC5BCAC0F0723AF61C4F7459F61ED8189466F80AEC0D001B828240A35881A5E43FB65F0C2CACBF1838918DE16265F8FBE63B038B201B78A89A15A9042019000410B51300D38608065150C403EB2A94E5514C0A0E0C4C46F10C4C9A01E0400437A49E5F60F8756422C3AF330BC0E124C7CF90F32097C1A2740F43D2EAEBF0F553B8D6BD31CDF36610B59406DAC50A5936058E2060A4F003FBDD7C4EF988FA98C4061E08FCBAF290E1EBC6930C7FDF22866FD9750218F882FAC1B91F0C3E6E06F6E77BA0FD792C114E5245F609980064C00900D8006061F9C320FC8F89E13972FD0F1F126661FCF71F3A29E428C520B7FF19C333682940320008206A2500701DFFB98AA181830569540D1421C0886776AC67605274C0D42429CCC0111AC5C06AADC8F07DD54486DF4FDF31F0B133984CF160386625C31053B89BE118B434F8C58058050BB6EB7A1A4321371B432530B0F861F53CB74502B8050EEF7783733AA89E3F4FB447FE3E7BC7F00D18F1BFEE3C874FD3B2481930F004F683EB7B482BF01C03C3AB5E207D8301DE05C088F0FF24A682BFC004C0CDC0C4CCC6F017540DFC6510FBF19FE105DB57465E7495E0B10068A234106150032680B3E4F604000288D204006E74BDAB6048E2058DAA3122A64D198011CF028C785002406FF04022045114334B01232FDD8BE1DBA6930C3F4FDF66606664E089D165D8004C04D3279C6298B2FC2AC373502DDBE1C8C0EFA9CCE027C8C150099EA285863D68C04500D8026793416EE0615B8A85BF9EFFB1FB3CC38FC357E1F53C03B004E1F3EF67E0308356237F80CE783D0198240F20D5F3E44436EE6A80534291E1CB939B0C4CFF80A5E75F064E8ECFF01E0CBC140086F55F580290E1064F0AB1919B00000288DC9D41E0EEDCFD420613491E865E3666C4FA770671030626CF7E1C114FB8F1054A00A0A2F71F74CA14E4C95F7F189E8298CC4C0C32F04624C8C7C0B68450EC7C700B1F013691D4C003DB79E42AC38F5DE7C17D7B98F91CB6050C5CEEF5E02A85E11FB010FAB88A81E1C30A48710FCEF5FF2018CEFE0F4914B8D8C4359D18FEFED064787E7C1384F78DE19DE04B46AC99F4DCCBFFDC1F7E3230BFFCC67022E920787711A87EFB002D2D894BF440B7010410392500D3CE3806111B39867A6063240BD6EEF9CFAFC0C0E4000C3083042C5A888F14765355608920CCF06503A2186661669046EE3D805BE05EF50CDCE69435F0FEDE7BC1F06DD52160831451CFB3283930F044CC676082D5F35FB60333E63C482BFFFF3F3C39FD3F15DA017F199839BE4106853E034B83AF8C02D02A1073F410341EF083819997954109AD2B48120008201612733DF3AB328660214E866E606E9485CBD837303059E4035D2580A685F448013B4A5A884120DB93E10FB04D004A04FFBE6B0323470EE2715D7F449F1BDEC0237E2916384EDE7F61F8BEFA30C39FBB88695A266069C21D3E9F8145D901D6E907B6EEA700FB22E7A0451B214391E9FF1455033C32EA0CEFAE9F6000A60526681C616C1CE56565FCFBECFF7F5676260631684F80959C04001040C42600A68D510CA2EECA0C73D95918BCE17E96776060F49FCFC020A080A69CF448C1951040180240E90D54D30843CD7D034D602444FC8F5F0C7F8E5E65F8B9E73CA29E07265A4ED77A067660910FE99A808AFB69909CFFFF3FFE98A656D58FDE0E10D566E03ACFC6C0F8EF37C868566C0900D80086F5AC1992D4184CE7DD82F70418497111400011930098DF563004031B5EB3804D057E0668A03100239E513D004B3D4FDAE81AF1E02845E6FE3D779BE1E7969328F53C9B710203876F3FA49E0717F78B19183EAF0526824F0472387210935DDEE3CE6D2C1F817D3F01606B0E3C16C40A34F507239AE1C80D41791E784390E411418000229400983F543224F37330CC848B80221D94EB318A7B42EBE00706FCBBFF82E1F7D613E0EE1DBC0D01ACE7D97D80FD79296855F2F33830E2A7038BFD17689336FF518B74A2E2F73FE53D836F2F1858BFFD65F8893450087225B212562686FFA0EEE0F7DFC0E60A3B830AB909002080F025002694C887E67A068C5C4FFA640A3DC0FF0F5F18FE6C3DC9F0F7FA4344710FACE7398111CFA205F5C3BF970C0C9FFA81EDE68B8888C799A1FF138EE4FFE48E02A281EB6F1918817D612666604BEFEF1F5099CE8A9E0020ED006067099800F8D8C00D4176721A820001843301805AFA7CEC0C5DF0C88FDBCFC0206180A575BF9161B081BFFBCF33FC3D761551DCB30B30B05A1730B03943A769FF7F6560F8BE1C98D33640231E5B4E273107A32716721B83BF817A1E7F07CD113330B3730163F813C81826602AC0680CF2B233FC7DF19581859B19BC38848D9C86204000E14A004CD6B20C05F03A3F7C3D96C89F47A3BA9E02006CE4FD5D7F98E1EFB58788E2DE3081811518F1A0F9074871BF1912F9A0317C92732A5A02818F7A5111DCFD0A65FC616066E366F8F39D11DC5F07DAC50A4C042809408813B23804241DAFC260BAF00E78C08CA486204000E14A00CC2C4C0C76601628E2E5D10775560CBEC807E57CA4C807B99BDDBB1F3104FDF72A30C74F02D22F91BA75FF891BCD23AA9D47858400ACF719EE7D4374BA81F1CDC40ACCE63F7F606D0C223704E5B818D4C86907000410CE0400ECEE59C31B7D185DBCDD832EF2FF1D0716F9A0FA1E147692060CACC9FB21934EA0E2FEE702603DBF8FC0400E5ACBFE3F7A51FE9F8841A0FF484AC928216E01DDFA1BB911FA9B8185830B960040E601FB8688B6001B33C37FD080D0B75F0C4C829086203B52354054020008209C5500EE52F008C3009D6A86BFE8DF0FE9DB330A2830B001231FDE4BF9D100CC48F7918A6FA47969184689706C91CF801499E8FA70241652DB009F80A5FB931FA8EBA418FF116C0C0A7380371A3109B1819789B393DA0E000820D2E790FFBF460DBC4180FF5D7F049ECC010FE5062175517F2D04E68307C883DF588A74B4C8C7DAFDFB8F96BB195073F77F1C098881047F5CFA8C39EE0A2D059839B8E0F1051D1882033E0EC6BFE02DEA4C0C622ABC0C420C88052244018000C299007EFE61B806E94E3D409F4180D4A18308C38A7ED08824BCCEFF0774F7EFADD8732C8A1803163603167D68918A5EC463338FD802E02130E77FC6714C20E32F606390153161F71F3501F0B3038B63A87516220C9A0C889941A266F80002085702F80FAC8A1E83FDF3E000660280CD840D12FCF7C663C878BE26527BE5F736B4DC88946BB115E5C8399DE13FE1DC8D2D4130A0D7F944E47C50D17FF73B6A5B8211AD1460FA0B6C947122AAEDFF88225E9087E117CC59CABCE045A224750701020897A27F6FBF311C874C9C0073D28F0F48529C83ABF87FF0021ECECC5AFE485D82D3B8733C4A44FD47ADDF19D07235D6D202475DFF1F8F7DD8C01FA0FCF56F101ABDE847C9BFBFC0D500928BD9E0AD756062E0E563F80EB24A849D411F346A434A350010403813C0EDB70C87607EFD8F5C0A306A0EAA04F0FFF93B78D58CE8F2016BAF7F5FB1D7D7B8EAFAFFC825040396A29F014711FF1FB3C8FF8FD61BC085EF008BFE2F7F117C462CBD0BB018A831082C05D8D86169831559B508B0A30052CAC7025E23C84E4A020008205C09E0AFEB2286F370FF3DBF88A443735015FFFF5EBC87B4FE9107AA407D7E0652EA7A6CC5393671B49202AB389612021B7EF29B81E1C56F6CAD3E2426234697109E2CFE33C07B70A2DC8C7F605E8B526030872602A2DA010001C4846754E3CFD75F0C27310A3146F9C15505C01200F29434A80188B5B186ABE186DE68C355EFA399F11F5BC980A337808C5F0123FEDE4FB49C8FA51460440E7DD0A010330323133C63B3C318E23C0C7F61A62B71331893D21D040820BCB3811C2C0C9A581BB3A06A009CCB0678C2E7E76F86BF2F3E401A809206C85380F873E67F022D79949201AD9D801EC1FFB1951C58D60CC047FB8025D79D5F840711C183B98C88440136FA17032B1737C3AF2F9F6099173C3FC0F28781950FD80E78FF818153880DDC0EE044EA0DE01D100208205C2984F94D39430D6C4B15A3843E969EC0606800BE82E72B66257BC444CFBF57F8FBE90C045AF2B85AEFD8DA10B8E606FEA3250A100645FEF55F98B1CEF81FC718C07F8C2E2113B01D00EB12028D87970262DC906161503B40998741042901E0050001C48425DDB1BC2D672806F62FCB61730128DD2B5000FFBD372812C09F9B4F2161CC21009EE38717FFFFB1B4EEFF13E8DA61D0D86607B1F5F5B1941AFFB1D4FF6F80917FE337A4C54FCAB4315A2DCEC8F81BD125FC0F8C606897504A90F127CC287B51703B8083986A0020805890AC613A95C6A0AA2DCA3007B4CA17EC0776010696C0F968C3AEF320CBA606BAF8FFF88DE1EFAD67E0B066D5424AA07F4EE16ECCE1EA0DA017DFC4B41D70F62EB0F41EDE01E9877F1193508CC834721B00691E8F11C90EE4E161869F0C2C9C3C0C7F7E7C8309B103A5BF8B7080561231FCF9FB8B8145818BC11628B51E2901E01CBB0708207802F859CBB09F9989C116EE7F60A38A25723D786205517F4D023646F70F8AE1FF9F5BCE32FCFB016945B318C7232580D398DD316C7534B6563DA1BEFE7F227B03C891FF0CD4E8FB87A38E67C01C00FA4F6832F73FB021081918FAFBF33B786410D854F8016A078809317C7FF29C81979F95410FAD1D80F336128000822500E6DFFF183E33434FA162B22C00EFE6818FA9FFB9028CFC39C0A2FFFEC0CFFABDFAC4F073FB7986BF2F3F4272BF7501782937BCFB07AAFF7135C4FE136A08624B280CD88B7E9CBD01280635BD1E02E92FB89688FD276E6A19393130C2ECF8C1C0024C007F800900DC1CF8078C6826869F62C0DE00E8B408D0BC80970483CEB6170CAFA071FC0B579202082078FDF0EA0BC30EB83D0AF6A891FFA90A480F7CBDFFE7D84D861F0B0F30FC83463EA8E5CFEE82B4CA0734ED8B51AFFFC7D3DD63C0BEE60FDF183F038E993F6473416B3A6E3320964732E2A8F41919708C0032A29EA0C088DE16F80B6C0C323130B3B2C19CC406AA48980C53B960CED5E6637064409C278CB31D0010403089FF4A131996FD01960220CD7FCF2F442A237480DA450776B4EFD507869F8B0F32FC3A7A13DEA662736960E0CE3F0F59D10B8A7CD0B42FACFB876B79177ABF1CD7F42EBEE1619CED0BE8E4D44B20F7112362CF20FAF80203B6D149061C2381781A85FFBF33B0707083F99FF854194FD82CE079A291C0C622AE0A3656940DBC9E830B6950082B00082058020055523F5F7F6158033EAEE3DA06D4F17F56F38119E9FBF193E1EFF19B0C3F971C0617FDE0EA11D82BE1CA3B8FC8F9A031FFEF399069DFFF84266970B4F8B10D0A616B24FEC7D56BF80F39B41D14F1EF1971AC0740AB0218518A1B3C0B507195127FC0AB76EEABA7309CB49BCFF0994F052CCA69E005D6CDC9CCA06821085E2B085B2C8A35790104107202F87DF605C332985BFE9C5B803422E44FFF5CFFE42DC3AF6547187E9FB80D1766756E00473E64D00758BEFEEA0526DB1E480980739106AE451FD8FAF50CD847F718F00C1B8342EE1530189F32211DDA8E1EF178DA0128918B76380C7A3580A4FE13B70AC341DD1E86BB2A092811CAA9610BF79EB1008333B414C0792115400021D70D7F0257309CF9F687E10648F39FA313918685C42155013D221FB4BA67CF2586DF6B4F02BB7ADF2175BDA2030347EE79C4AADE7F6780EAF2A0337EF8867919708CFB33E068BDE3E80DFCC7313CFC0918A68F81A5EB674622D616FEC7E4626CCA256E93EE6DD9048643FAB3C1890063044F4092815502520D487330B813AA06000208390180D2F2AFFBEF19E68039EF1F30FCBD87340BC8E10AD9E54A43FCFFFA13863F8B0E32FCBBFE1412EEC08628AB773F037BCA7E48AE07AD46FAD50CC9F90C5F71CCDAFDC733AF8F6D3918038EAA02D7D231E8D2CCA7C052F51D1303628316B6F57F78169F3262197D64F88F3D2D40C70B3EF1A8301C359CC3705B2E019C76201833D17083AA81FF906AC04C00BC67800357290010404C682EFBBDE002C3465863F0CF39A4C6202730013072D126D77FFAC6F06FD36986BFFBAE30FCFFF9077263864600037BF67906162BE89EBD3FDB81C57D0564AA1763848FC0824C9C8D39061CF53A03F67AFC1730FC5E0323FE3533969E35B646220391437EFF71E77E46D0603F2FC35DB944866306F380B95E956089C1A5690BF78E313FB81AE0C49500000208BD7BF077F2298697EFBE33EC0619003EBA05B931082A05A8DCD0FB7FE62EC3BF352718FE3F7D0F1F80628D5ACFC01ABD1EB296FFDF4348C4FF5E08A9EB19080CDCFCC7B142075BC30D6B5B00CB481E28B2DF0323FD1530F27F32E218246220AEE8C7EC6F227239236663EF3DBF21C309C3790C77E59350723D0433C031CAE08EA024031BB41A9062075703DCB8AA018000424F00E0C6E0C9270C3360E1F2FB0C52639027887AB91EB4576F1DB09E3F7B0F9CEB610350AC59E721730FA0C8FEBD0618E0E5D0E95D06CC0929065C43BBF85AF5F856F2A0252650687C0286D96B60E6F9C68C656A17476412B5841C7FB5FF878587E1B6723EC339FDC90CDF39249122195BBB01D3101E234835C0C1C4A068C28FBB1A0008206C03047FC2D7329CF90E6D0CFE3C82DC18940076C0F5286CE4019BCA276E31306C3BCFC0003A68113AE1C49CB49F81D9B31F3200F50F58CCFF0046FCEFD5B8CDC157D7631CD68063AE1FD77A3E501DF41518E16FD88034139A1A2C918975E3087AC2F88F7DB5CF7FCCF8FC2060C470DA7821C363E930E80410440235D7E32F05B8B56CE1B69A0B30F83320EE264489738000C296004083983F1F7C6098031E1378F780E1CF5DA4C620973BF90D3D60AE67D8086CB95F7D02DFB3C7E8D0C0C09C799E017CA40C78340FD8ADFBD10869F0E19D0DC253D7FF475EBC8AAF7B88C58C1FC020F9C0064900E8CBC7D123F23F9EE55E589B01F8CF14F8C30AACEB550A182E1A4C65F8C929098D7C440E27A514600556039C8A8660278AB332B8E1AA060002880947D0FE5E789161E35F506310941ACE203506B93D8076719396EB7F02733DB081C7B0E32203C3971F907093776060CA0016F70ED0AEDD9F0390011DD06C1E3166E25BBB8F2D0270CD02C2F06F60507C0446FC17603DFF97117BA4FFC7D5B7C73198C388A3AEC7D220FD2868C470DE6431C3539908A4A95F44C493530AF01943AA01564606313F51F0C820466310208070AE099C7606DA1804C5DFE905E06E214A2220B6A1F7E23D03C35A60A43E7A0B6D480A804F15614CD80F39590494D34139FEE73468238FD8F9E0FF78E6E4FF6319F2C5D1D70745F63760987C06E23F8CB8733B7AA4FF67C0DDF523663F00B4D1F7879587E1BE6A11C315C319E05C8F1EB1949402BCDA767097AB7031F8602B05000208E7AA60502970EA19C30CF83280D348A5005F2871B9F405B007B1F332F8982F3000ED33CCBBCFC0003B480AB471E35B2964C289E4F604039E717A06EC43B7E863FC3F81E1F005181EBF98317B02D88688B14D15E31BE1C3D6F340029F8075FD2593A50CCF652371462225A50013270FBC14E06766B092E76090803606E14B01010208DF6A913FC09ED8994F3F194E834B815348BD0116A0399C368473FFF567F05C0F3E5C02B4CD1CDCC87B008978D0EC1D29B99E01CB281E313B7DD05BFEA05C0F8AF81F2CD0E327B1ADE3C7359E8FEB34301CD5022352830F1A397F812DFC87CA850CD70D6630FC42C9F50C18118B9A10088D1CA2AAF9FCF92BC36B016D480F0B98FB6D04C06D012EE419428000C29700C08DC1A79F185682BCF217D818FC79650352F9E24538973E7E07516B5EC0C0A00FCBF5C0BAFE5B3D64E60EDF9A797C18EB942E03EE153CC891F90B18E95F8191FF97094B4EC6B6A297017B1BE0FF7FEC2DFCFFFFF10E067EE63762B86ABC84E1A50CAE5C8FCA471EEDC39DEB51F97FFFFE65B87DFB21C3F5EBF7187E092933FC67856C2A0136066DD1978A010410138176F66FEB850C0B618DC16F0791BA84DC76906E21AE3D7B9F7F20D42AD8232DE5990F9DBC213FFEB12FDFFA8F7B34103669F31D18F13F59B177CFFEE3199AFD8FAD5D80A5AB89AB7108CDF54F940A196EE94F67F805EED733E2C8F50C28753FA9A5C0EBD71F182E5EBCC5F0FEFD2706C6DFDF19584F02ABEE5FDFC0CE7FF58BE12A03E2FC00B046800022B46E1C540AFC78F685613EF8C4CEDB07C025011CF087E32EFEBFFCC0CC05A0153BFFBF305016FBB8A674F1CC078072FB770E08CD80A781C7C880A37EC76237AE36079659BE2FC0BAFEA6D1128657D21128F53ABE061C6AC4132E057EFDFA0DCCF1F719EEDF7F022E01985FDF6260DFD1C2C07CEF38A423F697E1F5DEF7E0CB257E3320CE5D660008204209003C41B4FF01C31298B7BE6C6F444A00DEB88B7F1626DCF3239462062C4534B6153FE0EE1D30C7FF60C76C40FE67C03F29C480A5E1F69FD0C81F6A2F0094EB9F291632DCD545CDF59811C98025E7632F05B0E5FA172FDE325CB972075CE783723DFB91E90CEC7BFB8055DD5BB0533EFE65B8BEFE0D43D5F35FE02377910FDF6600082062CE09FC5BBC87E1969B22C37A114E86C0EF973730F07E875E8DC2C40BEC110013C1C72D98BA0410975C80E61328BAE016E72010B66A0029E27EB341FAF738876D719CFD82B33AC0362E80A5FD00ECE27DE5336278A25CCBF08B1D5A4D825BEFFFD13204FA823FE84D515045E00B83E06C06F4E5C10C3F7FFE66B87BF731C3C78F5FC0EA989F5E60603D3E9FE13FA8C807B5E2FF317CBBFD8D61CDB2970C2B416B93413504283D30400E99002700800022260180570BDD7DCFB04C189800FE7FFBC0F0FDE402066E07E82C9D20B031F37E13DEAE3AC38B8B0C8C1A01543C5409CBA24FF4C59B3F39A0D74DFCC7B249034BBF1D47570D6BA39301C72110E05CCFCBF05A2685E1AD4438F47027D861DC90C847CDCDFFB12D11465287D0F31F2DB1BD7CF996E1F1E3970C7FFEFC01E77A96637319981E9F879F79FCF617C35960C4F7BFFC053E41F43D34017C6240DCC100060001C442647EFB13B08661DF832C86A7ACCC0CD29FF74F4424000E7560C7C20874F92DA6467E4E86FFEFBF03AD87B61B18B9A85402E05BBE0D2AE0D8B137CC709DDC81AF01F79F0175EF1EC6FC012262BE0173FD732548AE874DF73342F5429C8C63F09F885200966E20B9FE09C3A74F905C0F8A746660E4FFFF0969E88172FDE94F0CD3B6BC61D8CB00393DFC1D34D77F65C0720B0B4000117B5630B84B78FF2343BFAA2043CF9FB70F18BE5FDAC0C0A907DD9021E007B92C0963629A1592009E5F804E26293250ED605DACEBF4404D1C58E4FFC31EA9FF896898603BE401CFBABD7FC0BAFEAD5432C33B7158AE87C83332228C82E568CC3A1C7F29809C104075FD93272F197EFF06E6FA5FDF19988ECE62607C740EEEFD37BF19CE2E7D0ECEF54FA1110FCAF9A0B367BE2335FE50004000319150EBFE725CCAB0E8CF7F4897F0F33EA42E212801B04A623604F939203D30580200FB448C0A0D416C7301A0720A18F9FF18712FDCC43979836D860FCBC20E46CCA2FF3B9F21C323DDC50C1F2423B0B4D6195146EE70CFFF628E03209B036AE1DFB8F10058E4BF00B6F0FF0173FD3906E67545F0C807E5FAE31F187AFA1E32D40023FF0E501BA8D87F01C46FD11B7DE800208048392E1EDC257CF19961BE240F43DE0F6097105412C0AF67118E015ADB89AA039A00C089E0FE01C8010EA025E6A0235AA9D5108445D61F3648E433FCC3334FCF80E52008061C43CCF80787FE31F332BC974962F820168694EB91733E2345A5004CCFB3676F8039FF0D38D7337C79C3C074623103C3C333F0BAFE0DB0AE5F02CCF52F7E129FEB9101400091724A18B83178E031B44B08B4FCC356A42EA1A03F3072B951C6029880AD465850C24B01D0E2528AC600B00CFFFE0546FE7F263C753B039EC51C84267D304B8E1FBC860CCFB417327C0236F4700DE8602B05F0AF024195FFFEFD27C3B56BF78109E03538D7335EDBC1C0B8B1121CF9602FFF67F8760C98EB7B1E30D400239FA45C8F0C000288D463E2FE56EC67B8F5E61BC37AF098C0F1050CFFBE43978C3183BA844EA811C00A349E9315E2E07B0711ED008AC70090EAE97FCC10FC1FDBB66D6C1331384A068C133D3067F5FE3103EB7A851A8697EA5381B58D24DEB178E46E1BA24F8F7BCC1F99FFFCF96BF030EEF7EF3F1818BF007B6EDB9A18184E2C8234F480E63CFCCEB063F24386944DAF18B642231E845F411B7B3FA0AD7CA21A5B0001446A02009702675E30CC8085D5C7BD486D01896C8C45208C225C90121AB6C298E21200B9EB06DA81C386B9AEFF3FAE7E3B9EB580FFF18D02024395DF96E185EE5A866F225E04C7E2D14B0158E22034A083C8F56F207257B631FC5F57CEF0FFD935D868DE9B3D6F181AA63E62E87FFE137CDB2A682BE07352733D3200082026326ADE3FE93B184E7FFE059925FCB07702C3BF6FD052804D9A814128002590994438214CA01A7035005A4C42494310B995FE978DC0916F04A66F314A06CCE21F94EBDF29B533BC55EE00B3499991C33EB2872BD7BF050FE5FEF8F1131895AF19FE6DAE67F8770C58C2FEFC0A76E20360AE9FFA90216FD71B86FD48B9FE3539B91E1900041039B74D82BB84773F30CC06738011FBE502D22CA170204A026016E680072B7C6919AB2E858D3F70DF0BFBD02CB67DFC8C783678E0593BF053C096E18DF61A304D6CAE471DD685D5FFB84B0150AEBF7EFD01B8A10792FF7B6933C3DFD5C50CFF9E5D053BE5E73F8637BB81B97ECA0370AE07D5F54FA175FD3B68AEFFCD40C1D9BD0001C4446614FC0E5CC7B0FEC71F602A048D3A6D466A0CF29A3330F098C21B828C5CCC0C8C9C2C906AE0EE412A5503A0A95C6606AC9B3C701EFA8863DF1F321B366803CCE91F15DA193E28B633FC67E1255874E39AD2C53DBE8FE8D783BA77A05CFFFFF32B863F1B6B18FE1E9987C8F5DF8075FD0386BC9DAF3172FD2706E2AFD6C50B000288894C7D208BBF3FF8C8D00F4E0D6F1E307C3A86B460442A0F6B35F01B5602B05950D610FCC78ABBA58FF5806706069C9741A025845FC0BAFE9DD66A865FD05C8F3E23474ADD8FAB14F8FEFD1738E241C53E78E5CD854D0CBF561430FC7D72055ED7EF7ACDD030E93E30D7FF40C9F5EFA1237A14E57A64001040E42600F09231EF350C0B7FFE01A74A860F4791978C014B0136297824B14842BA83A01E03B81A00B503E0A382A4E67E661CFBFB712C17C738D10BDBC20D48AEFFA2D0C6F045B10DCCC6B5268FF8BA1F7B2900CAF5376F3E04E67A607B0D98EB7FAEAB62F87D6836C3FF1F885C0F8CF8BC1DAF6897EB910140003151A017D4E8F8FEE63BC31A90C3BFDD38C0F0EDE60184AC743EBC27C022CC8E58207C6523A21D4072FC3342EA7E5CC53ACE5543FF19F09D0BF09BCF86E193C62A86DFFC7618F534A9AB71709502A05C0F8A78D0240E88FFFBFC0686EFCB7280B9FE32D8553F8075FDCE570C0D13EE31F43FA371AE4706000144490200AF15987389612A6C78F8F546A4B6806808A457006A0700E38C450492087EDF41AA06C8C9FD384FEEC673B2378ECB1FFE3373337C576805E236A00379B1CECB93BB260FB91478F9F21DC3AD5B8FC0B9FEFFA7570CDF579732FC3C30139EEBEF7F65D831E12E43DE763AE57A640010404C14EAFFB3F41AB054FBC2B000E491AFC0520084E140B6001E31AC12906AE0F7D30B905545E09E003709F18FDEF0C333CDCF8067A93854EC2F30D77F535FC5F087CF96E0583C29A500B21E50E3EED6ADC7E00400E2FF3ABB9EE1EBA24C863F8F2F41364901EBFA1D2F191AFAEFD237D723038000A23401800786E65E6698F2F73F6447F1AB0D48A5805828B414F8CFC02681540D5C86761BD974C9C8FD045AF788D54E58CD01D5EFBFE45B197ECAC3723D23CE3579E494023035A048BF7DFB31A485FFE925C39715450CDFF74F03B6F02147ECDD07D6F5FD7718F2B6BDA47FAE47060001C4440533FE2CBF0E2D05401D53F45240AE08DC0E60E2640262664802B803ED0EB259129FFB1998F09CDA85A5958FBE440C3421C56BC3F04B7D2530F7DB12188B67C0B27C8B502980C8F5B76F3F6178F5EA3DD8CC9F67D6327C9A9FCAF0FBD145C84932C05CBFED054343EF6D86FEA70394EB91014000917B7D3C3A608FD46090AF306338031A09E0D6706050AE443A4FF0A419304D3F66F87CF913C3D73B90850BE21DEF19983880DDB9D72144180F3DFD1C360506BBB6FD1F321BE9AAF67F4834F8A2286E86BF52150C7F79ADA1A5D07F248C9BFFEFDF7FF87C3C8C8D5D0F440C94EB5FBC7807E6FF79FF8CE1D3A636865F0F2FC04BBEBB5F18762C7FC2B0E8E9778C993BD8481E5D2F6302B913208098A864D69FE537185E3CFFCA001E0CF872FD0018C3817C313804386511D3C33F2E6D807407599408A55148E4639CC58FE73838A4AAE13F30D7FF05E6FA7F7CB6440EE8E02B0518B0D6FD3F7E4056E980723D48FCFBA9550CEFE724C1231F94EBB702737D0F28D77F1FF85C8F0C0002885A09003C3CBCE00AB02D00DD43F0623D525B40229C8181DF9281858F19D82380AC6FFB711B5A0D800F9DC0B7C18409CB26CDFF0CF8177682B6C2F030FC976D65F82FD70A9EA92434168F7D4D3EA1D13EC83AFCBB779F825BF8FF3EBE6078BF2887E1F3AE490CFF7E40EAFA7BC0167ECF2D86BC2DCF07B6AEC705000288898A66FD59711358027E039602A0535FAEA195028A2590E3AD25D8C0BEFD7611DA1064B724C28978CEF1C5B6F50B98EB1954573230405BF8A8AB71089702E80D3F6C75FDCF9FBF18EEDD7B064E0020FEB793AB18DECC8C63F8F500727DDDF73F0C6FB63C6368E8BA39F8723D320008206A26007029B0F02AA447001278303311212B600D2C05AC18382421A75B826610C189007C0299128ED61F138E85190CD8176DB002CD926E66609069019ACB833547133BA083DE03404E40A04807453E2811FCFDF09CE1ED824C868FDBFB19FE7DFF02AFEBBB6F32E46D1EA4B91E190004101395CDFBB312580A3CFAC43009DCDA7FFD80E1CD21A43902B516064E09167835F0EDE24644298075EA9709AD94C7B24207C6160C06B635663130F0D8608D34724B01E4C4018AF0070F9E33BC7903C9F55F8E2D6778352D9AE1E7FDB360D780EAFACDC05CDF7983A1FFC920CEF5C8002080A8D50B40E91180F2FBB1088673AC8C0C526C220A0CDA1DE71998B9A0670F5FC96278BB652EC3E787BFC0C7BCCAF7BD074D133230BCC9C4E23A76D47C026FD9FF47B4F499C51818444B191838F5F1B67609B5F8B1F1FFFD43F40040B91ED2B50366E1A7D719DE6DEE61F87EEF0C3C3D5EFFC8B076C5238635C0887F36185AF8C4F6020002888906E68252F9B7071F192680E7B381A5C08B6D48AB8634DA19386584C1F10A5A4BF015B49680451998D9C5B18CFCE1B8CD0B96EBF90381C5FD0CBC918F6FBA969852E0DBB79F0CF7EF3F6378FBF61398FF61CF0C86E79322187E00231FE484CFBF191E2D79C050D2739361E650C9F5C8002080689100C0A38351DB19167CF9C570061455CFB74D60F8FB15BA6A88859F81CB22175E0D7CB900AD0638AC50CB7FF80A5A2C4BB440B95EBC9B81412813DCDA2758CCE159B481AB2DF00F58BA80723D681DFE9F3F7F197E3DBFC9F06C6218C3FBDDD311B9FE13C3DAFC730CD9FB5F319C8446FCA0ADEB7101800062A291B9A022EFDBB5770C3DE08520C0C8BFBFB01021AB52C9C0A5A8044900E7A1BD014E374CA7619BD4E101E67AC969C004A3476A6D477429005AA5F3E8D14B86F7EFBF80C5DEED9AC6F0A43F98E1E7B31B60F9CF7F181E2D06E6FAAEEBE013549E4323FF2552AEFF3598733D320008205A2500F04C61CE7E863D1F7F32EC05CF141E58C0F0F12AA25BC86D5B0BAF06C0898015560DFC874CFBA2CFF2318B02EBFA0E60632F9DA85C4F4E2900CAF56FDE7C6478FAF43530D7FF03E6FA1B0C8F7B8318DEED9C0A77C635605D9F7B86217BDF4B70AE7F064D006FD072FD90010001C44443B3C1EB05B63D60A88575D11FAF460C0E719BC6831B81E085A5B0C5245C6ED85BFFDC01C0C89F0A6C5EEA51E8247CB9FE17B0B87FCDF0E1C357706278BB7332C3C3EE006083EF06E4345B605DBFE83E4349E735945C0F5B9BF70D5AD7FF671862002080689900C0DBC9265E60B8FEF42BC35470445F39C0F0F200A25BC863100009DC731BC02501380120D7F7A0BA5E1898EBF9D3209B4E288D7E2CB9FEFFFF7FE006DEB3676F197EFF06E6FA67D719EE77FA31BCD936199E0641B93EE73443F6DE1718B91ED4CAFF39D4723D32000820261A9B0FDE4EB6EC26C36460D882AE4F62B837AF10DC26000141977C78747F3EB70172E40C2BF40874DE6860436F0115723DEE5200347CFBF4E91B864F9FBE8213C59BED1319EE76F8307C7F721D721AFC2F86470BEF3294B45F1D5EB91E19000410AD1300780DC8FABB0CCF810DC26610EF3730F29F6E81740BD9650DC07B0B41A5C0FB23D06A802F1E58DC031B79BC31B419F8806EBD7EF7EE13C3F3E7EFC0C7A9FC78728DE16E9B17C3ABAD13E0CD8EAB1F18D6669D62C8DE330C733D320008205A0C046103A0336AF9B7F9326CE36261300105B0C5CCFB0C1C620A0CAF363632BC5ADF001ED7D1E8BDCF001A38A22500E5FAD7AF3F32FCFAF5075CFC3FDFD407C6B046DE4760AE5FFB8861D2EEE70C171910072B7C44CAF1C322E26103410001C44427BBC0DDC2532F810D4268B1707D12649E40D03A1E5E86BE3BBC90660E008DE6BD7FFF99E1E5CBF7D05C7F95E166B33BC38BCD7DF0A20A94EB334E326403237F58E77A64001040F44A00E0C1A186530CA79E7D61980A0AEDF7C006E1F37D0B185881399E4BDD015C02BC459E37A022009DAA015AA8F1F9F37730FFF9A65E86EB8DAE0CDF1E5D85344281B97EDE6D869296CBC3B7AEC705000288898E768172CFB765B719A6FC0496BCA080BF3507D22014B489879CDCFAFA01C367E429642AE4FA0F1FBE30BC7AF5019CEBBF3DBAC270ADC185E1D9C61E78917FE53DC3DAD4E3232BD723038000A26702003708B73C607876EB3D431348E0D7970F0C77973532F01B07303041278BDE1E5A48B55C0F8AF8AF5F7F801B7EA048BFD6E00C4E04FFA163F87381B9BEF9D2C8CBF5C8002080E8D508C468106EF0803608811C93B6FD0C9F8F2D044F1D838E9FD39B741F317B4846AE071D9B061AD801F90D14E1F7E6E4327C7B78053E9108CAF58D17C0CBD76087287D60409DB91BF6110F6B04020410D300D80B6E106E7FCC90FFE73FC3175084DC9855C8206417CF009B37787F7A0399B9FE17780207D4D2FFFBED23C3E3E5350C57EB1D21B99E0192EBE7DC62280146FE88CEF5C8002080062201801B8433AF325C7FF98D015CDE7FBA7781E1E5B983E02E2028113C479E3E26322583EA7AD0302E88FDF9C631866BC0887FB96B26643712B0FABFFC8E616DE26186EC9D4F47665D8F0B0004D0405401B0E138F0C291F5EE0C07D89919D44111A51956C0F06AC7047051ADD7799E815BC180A8BAFE13E8DAB97FFFC0B9FEC9FA4E86973B67200E4CFCC1707DE60D8649A7DF80AF737E0FC5C3B25F4F4E150010404C0365370364CAF4CBD1170CC5B0D1B7A7670EC027019F6D9D48D0F1A088076110FBCBCDA30C576BEDE1910FCAF5A75E332C4A39C250028CFC4B0CA8F3F5233AD7230380001AA8120006403B3EF866DB31344972316481229F9B135834B030807B0526D3EE33B0700B60CDF55FBF7E0737F8FE7CFDC8F0747D07C38B9DD31147A78172FD758689C004709B01F598D4EF0C4368AE9E1E25004000310DB01BC00DC2B5F71926FFFCCBF01CBC4DFA17B4BF086C0CBE3DB501AD85FF8FE1F3E76FE0C80777E56E1C01E67A5B70E4C3EAFA93AF1816251F62280146FE65A4BAFE2D52AE1F8D7C24001040035D02800068D727779D11838F8908C352502E16E0814CDA80DA00C6BDE7E1B91ED2B5FB07CEF577676530BC3FB31525D7CF00E67A600218CDF52494000001341812006C6C806F8E1D439F083B432C3BB062E0841EF1AFDBB097815DD912BC2E0F04DE9DD9C2700F18F9BF81890096EB0F3D6758D87799610B52230FD4AFFFC23044D6E50D64020008209641E2167055B0F22E437BAA3A830B30B62439A12BC29FEC99CFA0A060CEF0EBCD2386874BCAC1090096EB9F7D6538DB7A9EA1FFC167F891E8EF47733D69002080064B0900AF0A6A0C187C8C8419967271005B882C9088960CAC6278B17D2A4AAEDFF69861DA8C6B2847A283E8AF48ADFBD15C4F440900104083290130C27A0533AD19FAC438196279916EAB87E5FAFB9F190E755D609809CDF5241F8E3C0A50130040000DA60400024CD00122C129160CDD0A020C516CD013E13EFE6478B8E521C3DC45B7C023791F9006746047A48EE67A32120040000DB60400AB0A40170E81060084402502B491F80F9ACB3F43EB7958AEFF339AEBC94F0000013418130008B0404B025042805D75FA1F5AC4FF402AEE47733D8509002080066B028055072CD01201E6C0BF50FC6F34D7532701000410E3FFFFA319682403800003004042FE192CC3EC400000000049454E44AE426082', 'folder_yellow.png', 'folder yellow', 'image/png', 0);
+INSERT INTO "kexi__blobs" VALUES(5, X'89504E470D0A1A0A0000000D4948445200000080000000800806000000C33E61CB0000000467414D410000AFC837058AE90000001974455874536F6674776172650041646F626520496D616765526561647971C9653C0000380F4944415478DA62FCFFFF3F03232323C328A00B60026266208605F83F244C77008A7B800062198D13FA447CAF1D83A88938433E3323831D28CF7DF9C570DC7323431B50EE0710FF02E23F03E1308000621C2D01689EE3590E84321472B1305402D9FCC0E06680E1B73F1836F86C62C8018A7F04E2EF40FC97DE25004000318DC6116D321628E2770733849D8A64B8C3CDC2D0018A7C0660A4F36A3A30B08B2A80980C42EC0C01B9FA0C3640260F10B322550D74030001345A05503FE299370730188B7032F4B2323258832B7A606CB38B28302866CC67E0D57260F8F3F503C3E944417022D01761F00252E7A125C06F2CA50023B4246182B2FF23B51BFE53EA6080001A4D00D403CCABBC195464F918EAD89818A220452C30D6380518E4E3FA1944EC121081CE2DC0C0074C081FAE1E6010E36430020A7103313B107F434B00CC6BFD8066F232D401A35AFED73F862757DE30CCCCD8C37016DA76F843690312208046130015EAF9E9CE0C22BAA20C859CCC0C59403E1F2CF225831A18C43DF31998B904A04A1F43E3589D814F1B920004D8197480025C40CC068D0F5029C034CB8D41444F14DC76A80099052A4558806580B1188357963E83CFB48B0C5780EABE40D5935D120004D06802A0B04B77388221898795A1131805FCFFA1112F649BC0201554CFC006ACEB21E00D10CF03E29B50BE2CB0043003AB05455DB5098373EB198665D076C0DF43110C41FCEC0C5D400B64FF411B8C3C1A0E0C9FAE1D00D501BC9E0A0C65C00490072D01FE52D2780408A0D10440663DBF3794C119987B7B80DD3A1D683C8223491218F13CC0861E048072FB2620DE8D66C463067E6D6B06983E453E7035B0B1C69241335815D8766062B086E57A6EA09952D1FD0C9C72060C77FB0319DE9FDEC000949703AAE705E2CF94F61E00026834019016F14C2B7D1854550519660323DE06D6C0631356609000463C28E723C06E68E47FC361DC4D067E603BE0FD95030C125C0CFA5B8319AA15F919426111CF04AC36A4A3FA19046D2066FEF9FAECE77FE62FBF401D89BFFFC0A50F27B4C460865603640180001A4D004436F02603EB794B29863E766003EF1FB4C60535F0C4DC0B182402EB9194821AF42BA0C53E3E004C00C076C08FDBC086A02003A8AE50808D0F08BB16308805D483DB0EFF7E7FFEF3FEF2DC976F4EB73EFFFEE08D04B0C8E015E660D086463E0BA55D4780001A4D0004EAF9D9C0C698BE18432107B081070C693E586B4BC4AD80413CB01EA98107AADF3722D5F3F8C1F79BF7804A7731F07343221D94A8B8D41D18A493E733B08A40DA0E5F1EED7DFFE240CE833F1F6E7C676464F8CFF99781F927349140733E33B42D42360008A0D10480A78177229A21898B95A11AD4188335F0780D03C075329B0872030F54D41F25CAE03F6FBF30BCDD7C9EE1D3D1DB0CB0061E0BB00A118FEC079B0D023FDFDDFCF6F270C983AF0FB67C646262F8C70CC4A004C0F58F91E903B00800B9255683416DF10D86279496000001349A00B034F080AD7067010E48030F56CF73C81A304802EB6450A30CD1C0DB03ADEBBF1165F8FB2DE719DEEFB9CAF0F7DB2F702482EA7941970206113F4815022AEEDF9CE97DFAF654E33368C4FF872500903CC72F060E5835C1CF06EE6EB2209502648D070004D06802406AE06D0C645051E067E8656362F086D5F3A0DC29E65FCF206083DCC03B0AADE7898BF86F171F32BC597592E1F79B2FF05CCF6795C020EC5B0F2FEEBF9D9DC0F0F454CB8D3FBFDE7E0646F85F78CE47EAE3733322E6115404185491AA01B24B0180001A4D00A0811C5706510B29867A4E16864C7000834215983B85808D3121D77CB47A7E1E110D3C4471FF66D161866F379FC3238E4DC6804134BC1F5CDF83078CDE5F6478B73981E1F7AB0B0CC048E7646265F8881CF9A0A21FA48EED2364808909C8072622466022E545EA05909D00000268242700F04CDDC5448606606066C31A7820CC0FCC9DA03A1975046F39D10DBC7FDF7F317CDC7A9EE1E3BEABB0061B0323B0C7201AD60FCEF9908A1E587D3F6A61607C7F9DE1DFA7DB608BD9FE33F0013BF4CFC1118F14F9E016DF2FF04821030730617C063606A5B81874D1AA00B20040008DC404009EA93B13C790C4CD0669E0C1EA792E350706C924442B1C52C4AF20BA8107029FF75F0547FE1F503D0F2BEE9D8025894F3DB8CE67F8FB8981E1C502209E0F610333361BBF18C3F7EFF71998FE831321D6DCCCFC93113457001A04FA07349319584280E60FD8284D000001C432C2229EF9580C83333F3B432D30D75BC3EA7950B12C16812896C969E0FDBCFD82E1C3DA130C3F1FBF830FF172A83A3088C6CF07B723201D86B50C0C4F2741723F1C7C02260071866F2FC009809DE92F03CF7F26F0FA0038002DD960FF068E70067666705A65E0656550A446090010402C2324E299B68630A82809808759BD61F53C2B306284812D7078B10C6FE06D22BA9EFFFBEE0B30E24F327C0736F4600D3C6621050691B8F90C1C6AD004F5ED3AB0B86F06C6F549AC66708870C16B17E6BF0CFC7F59183E814A02E40620F30FF08411030F0BE35FD0420E905D91AA0C1ACB6F83BB8264270080001AEE098069960783A895346A030F5414F33B1730083AE7438A6578036F39B4BE27AE9EFF7AE02AC3E7EDE721110F1D19E4712C6010F0868E0C828AF8C76D0C0CAF8139FF3FEE5E1A23CB0F06561E4186DF5FDE83DA0142DFFE333CF9F70F980098C08BB5FE33FF646063FE0F2A20C03382FF60258C003BB861C84A4957102080580622374269585D87BCC081AA0DBCAB290C0DC02213DCC08359C46B99006E8C21221E7DA68E8851BC53B7C1110F6AE5C3EA792EF30406C1907E70220083D78B18189E4D0676053E126122B01A101063F8FDF93DBC1D80DC1660FFCCC00BAA06C05D4116480200614D410643A0D00E4A7A02000144AF04008E90BB790C4E3C6C0C0EA0C90C2066FCFA9BE1D1E9670C8763D733DC65804C68C0A637FF5390C058CE2430040B7030743143A753C18108AC8F458011CF2E6B407603EFF79D170C9F779C67F875FB39BCB8675371601008EE676095819AFBE534B0B8AF82D4F3FF89F5C637060E6129862F8F218990F90F83C87F568657E04400B2E30723274A95016C077CFBC3C0C4C3C220CE805847C0C440C6AC204000D13A0130AF0B6710B15760280016C1D1C03EAC2CF2A24841A0B76480BDD97BB90CEBD6DD60E82AD90DCE863F18B02F8D22D8C03B9D009EA2AD854DA7FE870EE4801A629C6AE437F0FE038BFBCF1B4E82733E7C9C0058CFF37AD683733E24753C0316F735C004700A5896FD432ADC18884ACF6CC014CBC4C2C6F0EFCF2F06D67F0C423FFF33BC86C9B17E01AF196480950AA00400CC3C4C5C9004C04A494310208068950098D783225E91A100E8C82C60C4F3FD83D65BA00064145460F8F7EE01840F1406B68182520D19825C1519A6E8CF62686580AC74F9416469C0BC2B023C8257072CEEA3601104EA770B87F6838B7C721B78E0E4B2F33CC3B7435781590ED2AD63E41060E0B62F60E076C88714F77F3F3330BC5BCAC0F0723AF61C4F7459F61ED8189466F80AEC0D001B828240A35881A5E43FB65F0C2CACBF1838918DE16265F8FBE63B038B201B78A89A15A9042019000410B51300D38608065150C403EB2A94E5514C0A0E0C4C46F10C4C9A01E0400437A49E5F60F8756422C3AF330BC0E124C7CF90F32097C1A2740F43D2EAEBF0F553B8D6BD31CDF36610B59406DAC50A5936058E2060A4F003FBDD7C4EF988FA98C4061E08FCBAF290E1EBC6930C7FDF22866FD9750218F882FAC1B91F0C3E6E06F6E77BA0FD792C114E5245F609980064C00900D8006061F9C320FC8F89E13972FD0F1F126661FCF71F3A29E428C520B7FF19C333682940320008206A2500701DFFB98AA181830569540D1421C0886776AC67605274C0D42429CCC0111AC5C06AADC8F07DD54486DF4FDF31F0B133984CF160386625C31053B89BE118B434F8C58058050BB6EB7A1A4321371B432530B0F861F53CB74502B8050EEF7783733AA89E3F4FB447FE3E7BC7F00D18F1BFEE3C874FD3B2481930F004F683EB7B482BF01C03C3AB5E207D8301DE05C088F0FF24A682BFC004C0CDC0C4CCC6F017540DFC6510FBF19FE105DB57465E7495E0B10068A234106150032680B3E4F604000288D204006E74BDAB6048E2058DAA3122A64D198011CF028C785002406FF04022045114334B01232FDD8BE1DBA6930C3F4FDF66606664E089D165D8004C04D3279C6298B2FC2AC373502DDBE1C8C0EFA9CCE027C8C150099EA285863D68C04500D8026793416EE0615B8A85BF9EFFB1FB3CC38FC357E1F53C03B004E1F3EF67E0308356237F80CE783D0198240F20D5F3E44436EE6A80534291E1CB939B0C4CFF80A5E75F064E8ECFF01E0CBC140086F55F580290E1064F0AB1919B00000288DC9D41E0EEDCFD420613491E865E3666C4FA770671030626CF7E1C114FB8F1054A00A0A2F71F74CA14E4C95F7F189E8298CC4C0C32F04624C8C7C0B68450EC7C700B1F013691D4C003DB79E42AC38F5DE7C17D7B98F91CB6050C5CEEF5E02A85E11FB010FAB88A81E1C30A48710FCEF5FF2018CEFE0F4914B8D8C4359D18FEFED064787E7C1384F78DE19DE04B46AC99F4DCCBFFDC1F7E3230BFFCC67022E920787711A87EFB002D2D894BF440B7010410392500D3CE3806111B39867A6063240BD6EEF9CFAFC0C0E4000C3083042C5A888F14765355608920CCF06503A2186661669046EE3D805BE05EF50CDCE69435F0FEDE7BC1F06DD52160831451CFB3283930F044CC676082D5F35FB60333E63C482BFFFF3F3C39FD3F15DA017F199839BE4106853E034B83AF8C02D02A1073F410341EF083819997954109AD2B48120008201612733DF3AB328660214E866E606E9485CBD837303059E4035D2580A685F448013B4A5A884120DB93E10FB04D004A04FFBE6B0323470EE2715D7F449F1BDEC0237E2916384EDE7F61F8BEFA30C39FBB88695A266069C21D3E9F8145D901D6E907B6EEA700FB22E7A0451B214391E9FF1455033C32EA0CEFAE9F6000A60526681C616C1CE56565FCFBECFF7F5676260631684F80959C04001040C42600A68D510CA2EECA0C73D95918BCE17E96776060F49FCFC020A080A69CF448C1951040180240E90D54D30843CD7D034D602444FC8F5F0C7F8E5E65F8B9E73CA29E07265A4ED77A067660910FE99A808AFB69909CFFFF3FFE98A656D58FDE0E10D566E03ACFC6C0F8EF37C868566C0900D80086F5AC1992D4184CE7DD82F70418497111400011930098DF563004031B5EB3804D057E0668A03100239E513D004B3D4FDAE81AF1E02845E6FE3D779BE1E7969328F53C9B710203876F3FA49E0717F78B19183EAF0526824F0472387210935DDEE3CE6D2C1F817D3F01606B0E3C16C40A34F507239AE1C80D41791E784390E411418000229400983F543224F37330CC848B80221D94EB318A7B42EBE00706FCBBFF82E1F7D613E0EE1DBC0D01ACE7D97D80FD79296855F2F33830E2A7038BFD17689336FF518B74A2E2F73FE53D836F2F1858BFFD65F8893450087225B212562686FFA0EEE0F7DFC0E60A3B830AB909002080F025002694C887E67A068C5C4FFA640A3DC0FF0F5F18FE6C3DC9F0F7FA4344710FACE7398111CFA205F5C3BF970C0C9FFA81EDE68B8888C799A1FF138EE4FFE48E02A281EB6F1918817D612666604BEFEF1F5099CE8A9E0020ED006067099800F8D8C00D4176721A820001843301805AFA7CEC0C5DF0C88FDBCFC0206180A575BF9161B081BFFBCF33FC3D761551DCB30B30B05A1730B03943A769FF7F6560F8BE1C98D33640231E5B4E273107A32716721B83BF817A1E7F07CD113330B3730163F813C81826602AC0680CF2B233FC7DF19581859B19BC38848D9C86204000E14A004CD6B20C05F03A3F7C3D96C89F47A3BA9E02006CE4FD5D7F98E1EFB58788E2DE3081811518F1A0F9074871BF1912F9A0317C92732A5A02818F7A5111DCFD0A65FC616066E366F8F39D11DC5F07DAC50A4C042809408813B23804241DAFC260BAF00E78C08CA486204000E14A00CC2C4C0C76601628E2E5D10775560CBEC807E57CA4C807B99BDDBB1F3104FDF72A30C74F02D22F91BA75FF891BCD23AA9D47858400ACF719EE7D4374BA81F1CDC40ACCE63F7F606D0C223704E5B818D4C86907000410CE0400ECEE59C31B7D185DBCDD832EF2FF1D0716F9A0FA1E147692060CACC9FB21934EA0E2FEE702603DBF8FC0400E5ACBFE3F7A51FE9F8841A0FF484AC928216E01DDFA1BB911FA9B8185830B960040E601FB8688B6001B33C37FD080D0B75F0C4C829086203B52354054020008209C5500EE52F008C3009D6A86BFE8DF0FE9DB330A2830B001231FDE4BF9D100CC48F7918A6FA47969184689706C91CF801499E8FA70241652DB009F80A5FB931FA8EBA418FF116C0C0A7380371A3109B1819789B393DA0E000820D2E790FFBF460DBC4180FF5D7F049ECC010FE5062175517F2D04E68307C883DF588A74B4C8C7DAFDFB8F96BB195073F77F1C098881047F5CFA8C39EE0A2D059839B8E0F1051D1882033E0EC6BFE02DEA4C0C622ABC0C420C88052244018000C299007EFE61B806E94E3D409F4180D4A18308C38A7ED08824BCCEFF0774F7EFADD8732C8A1803163603167D68918A5EC463338FD802E02130E77FC6714C20E32F606390153161F71F3501F0B3038B63A87516220C9A0C889941A266F80002085702F80FAC8A1E83FDF3E000660280CD840D12FCF7C663C878BE26527BE5F736B4DC88946BB115E5C8399DE13FE1DC8D2D4130A0D7F944E47C50D17FF73B6A5B8211AD1460FA0B6C947122AAEDFF88225E9087E117CC59CABCE045A224750701020897A27F6FBF311C874C9C0073D28F0F48529C83ABF87FF0021ECECC5AFE485D82D3B8733C4A44FD47ADDF19D07235D6D202475DFF1F8F7DD8C01FA0FCF56F101ABDE847C9BFBFC0D500928BD9E0AD756062E0E563F80EB24A849D411F346A434A350010403813C0EDB70C87607EFD8F5C0A306A0EAA04F0FFF93B78D58CE8F2016BAF7F5FB1D7D7B8EAFAFFC825040396A29F014711FF1FB3C8FF8FD61BC085EF008BFE2F7F117C462CBD0BB018A831082C05D8D86169831559B508B0A30052CAC7025E23C84E4A020008205C09E0AFEB2286F370FF3DBF88A443735015FFFF5EBC87B4FE9107AA407D7E0652EA7A6CC5393671B49202AB389612021B7EF29B81E1C56F6CAD3E2426234697109E2CFE33C07B70A2DC8C7F605E8B526030872602A2DA010001C4846754E3CFD75F0C27310A3146F9C15505C01200F29434A80188B5B186ABE186DE68C355EFA399F11F5BC980A337808C5F0123FEDE4FB49C8FA51460440E7DD0A010330323133C63B3C318E23C0C7F61A62B71331893D21D040820BCB3811C2C0C9A581BB3A06A009CCB0678C2E7E76F86BF2F3E401A809206C85380F873E67F022D79949201AD9D801EC1FFB1951C58D60CC047FB8025D79D5F840711C183B98C88440136FA17032B1737C3AF2F9F6099173C3FC0F28781950FD80E78FF818153880DDC0EE044EA0DE01D100208205C2984F94D39430D6C4B15A3843E969EC0606800BE82E72B66257BC444CFBF57F8FBE90C045AF2B85AEFD8DA10B8E606FEA3250A100645FEF55F98B1CEF81FC718C07F8C2E2113B01D00EB12028D87970262DC906161503B40998741042901E0050001C48425DDB1BC2D672806F62FCB61730128DD2B5000FFBD372812C09F9B4F2161CC21009EE38717FFFFB1B4EEFF13E8DA61D0D86607B1F5F5B1941AFFB1D4FF6F80917FE337A4C54FCAB4315A2DCEC8F81BD125FC0F8C606897504A90F127CC287B51703B8083986A0020805890AC613A95C6A0AA2DCA3007B4CA17EC0776010696C0F968C3AEF320CBA606BAF8FFF88DE1EFAD67E0B066D5424AA07F4EE16ECCE1EA0DA017DFC4B41D70F62EB0F41EDE01E9877F1193508CC834721B00691E8F11C90EE4E161869F0C2C9C3C0C7F7E7C8309B103A5BF8B7080561231FCF9FB8B8145818BC11628B51E2901E01CBB0708207802F859CBB09F9989C116EE7F60A38A25723D786205517F4D023646F70F8AE1FF9F5BCE32FCFB016945B318C7232580D398DD316C7534B6563DA1BEFE7F227B03C891FF0CD4E8FB87A38E67C01C00FA4F6832F73FB021081918FAFBF33B786410D854F8016A078809317C7FF29C81979F95410FAD1D80F336128000822500E6DFFF183E33434FA162B22C00EFE6818FA9FFB9028CFC39C0A2FFFEC0CFFABDFAC4F073FB7986BF2F3F4272BF7501782937BCFB07AAFF7135C4FE136A08624B280CD88B7E9CBD01280635BD1E02E92FB89688FD276E6A19393130C2ECF8C1C0024C007F800900DC1CF8078C6826869F62C0DE00E8B408D0BC80970483CEB6170CAFA071FC0B579202082078FDF0EA0BC30EB83D0AF6A891FFA90A480F7CBDFFE7D84D861F0B0F30FC83463EA8E5CFEE82B4CA0734ED8B51AFFFC7D3DD63C0BEE60FDF183F038E993F6473416B3A6E3320964732E2A8F41919708C0032A29EA0C088DE16F80B6C0C323130B3B2C19CC406AA48980C53B960CED5E6637064409C278CB31D0010403089FF4A131996FD01960220CD7FCF2F442A237480DA450776B4EFD507869F8B0F32FC3A7A13DEA662736960E0CE3F0F59D10B8A7CD0B42FACFB876B79177ABF1CD7F42EBEE1619CED0BE8E4D44B20F7112362CF20FAF80203B6D149061C2381781A85FFBF33B0707083F99FF854194FD82CE079A291C0C622AE0A3656940DBC9E830B6950082B00082058020055523F5F7F6158033EAEE3DA06D4F17F56F38119E9FBF193E1EFF19B0C3F971C0617FDE0EA11D82BE1CA3B8FC8F9A031FFEF399069DFFF84266970B4F8B10D0A616B24FEC7D56BF80F39B41D14F1EF1971AC0740AB0218518A1B3C0B507195127FC0AB76EEABA7309CB49BCFF0994F052CCA69E005D6CDC9CCA06821085E2B085B2C8A35790104107202F87DF605C332985BFE9C5B803422E44FFF5CFFE42DC3AF6547187E9FB80D1766756E00473E64D00758BEFEEA0526DB1E480980739106AE451FD8FAF50CD847F718F00C1B8342EE1530189F32211DDA8E1EF178DA0128918B76380C7A3580A4FE13B70AC341DD1E86BB2A092811CAA9610BF79EB1008333B414C0792115400021D70D7F0257309CF9F687E10648F39FA313918685C42155013D221FB4BA67CF2586DF6B4F02BB7ADF2175BDA2030347EE79C4AADE7F6780EAF2A0337EF8867919708CFB33E068BDE3E80DFCC7313CFC0918A68F81A5EB674622D616FEC7E4626CCA256E93EE6DD9048643FAB3C1890063044F4092815502520D487330B813AA06000208390180D2F2AFFBEF19E68039EF1F30FCBD87340BC8E10AD9E54A43FCFFFA13863F8B0E32FCBBFE1412EEC08628AB773F037BCA7E48AE07AD46FAD50CC9F90C5F71CCDAFDC733AF8F6D3918038EAA02D7D231E8D2CCA7C052F51D1303628316B6F57F78169F3262197D64F88F3D2D40C70B3EF1A8301C359CC3705B2E019C76201833D17083AA81FF906AC04C00BC67800357290010404C682EFBBDE002C3465863F0CF39A4C6202730013072D126D77FFAC6F06FD36986BFFBAE30FCFFF9077263864600037BF67906162BE89EBD3FDB81C57D0564AA1763848FC0824C9C8D39061CF53A03F67AFC1730FC5E0323FE3533969E35B646220391437EFF71E77E46D0603F2FC35DB944866306F380B95E956089C1A5690BF78E313FB81AE0C49500000208BD7BF077F2298697EFBE33EC0619003EBA05B931082A05A8DCD0FB7FE62EC3BF352718FE3F7D0F1F80628D5ACFC01ABD1EB296FFDF4348C4FF5E08A9EB19080CDCFCC7B142075BC30D6B5B00CB481E28B2DF0323FD1530F27F32E218246220AEE8C7EC6F227239236663EF3DBF21C309C3790C77E59350723D0433C031CAE08EA024031BB41A9062075703DCB8AA018000424F00E0C6E0C9270C3360E1F2FB0C52639027887AB91EB4576F1DB09E3F7B0F9CEB610350AC59E721730FA0C8FEBD0618E0E5D0E95D06CC0929065C43BBF85AF5F856F2A0252650687C0286D96B60E6F9C68C656A17476412B5841C7FB5FF878587E1B6723EC339FDC90CDF39249122195BBB01D3101E234835C0C1C4A068C28FBB1A0008206C03047FC2D7329CF90E6D0CFE3C82DC18940076C0F5286CE4019BCA276E31306C3BCFC0003A68113AE1C49CB49F81D9B31F3200F50F58CCFF0046FCEFD5B8CDC157D7631CD68063AE1FD77A3E501DF41518E16FD88034139A1A2C918975E3087AC2F88F7DB5CF7FCCF8FC2060C470DA7821C363E930E80410440235D7E32F05B8B56CE1B69A0B30F83320EE264489738000C296004083983F1F7C6098031E1378F780E1CF5DA4C620973BF90D3D60AE67D8086CB95F7D02DFB3C7E8D0C0C09C799E017CA40C78340FD8ADFBD10869F0E19D0DC253D7FF475EBC8AAF7B88C58C1FC020F9C0064900E8CBC7D123F23F9EE55E589B01F8CF14F8C30AACEB550A182E1A4C65F8C929098D7C440E27A514600556039C8A8660278AB332B8E1AA060002880947D0FE5E789161E35F506310941ACE203506B93D8076719396EB7F02733DB081C7B0E32203C3971F907093776060CA0016F70ED0AEDD9F0390011DD06C1E3166E25BBB8F2D0270CD02C2F06F60507C0446FC17603DFF97117BA4FFC7D5B7C73198C388A3AEC7D220FD2868C470DE6431C3539908A4A95F44C493530AF01943AA01564606313F51F0C820466310208070AE099C7606DA1804C5DFE905E06E214A2220B6A1F7E23D03C35A60A43E7A0B6D480A804F15614CD80F39590494D34139FEE73468238FD8F9E0FF78E6E4FF6319F2C5D1D70745F63760987C06E23F8CB8733B7AA4FF67C0DDF523663F00B4D1F7879587E1BE6A11C315C319E05C8F1EB1949402BCDA767097AB7031F8602B05000208E7AA60502970EA19C30CF83280D348A5005F2871B9F405B007B1F332F8982F3000ED33CCBBCFC0003B480AB471E35B2964C289E4F604039E717A06EC43B7E863FC3F81E1F005181EBF98317B02D88688B14D15E31BE1C3D6F340029F8075FD2593A50CCF652371462225A50013270FBC14E06766B092E76090803606E14B01010208DF6A913FC09ED8994F3F194E834B815348BD0116A0399C368473FFF567F05C0F3E5C02B4CD1CDCC87B008978D0EC1D29B99E01CB281E313B7DD05BFEA05C0F8AF81F2CD0E327B1ADE3C7359E8FEB34301CD5022352830F1A397F812DFC87CA850CD70D6630FC42C9F50C18118B9A10088D1CA2AAF9FCF92BC36B016D480F0B98FB6D04C06D012EE419428000C29700C08DC1A79F185682BCF217D818FC79650352F9E24538973E7E07516B5EC0C0A00FCBF5C0BAFE5B3D64E60EDF9A797C18EB942E03EE153CC891F90B18E95F8191FF97094B4EC6B6A297017B1BE0FF7FEC2DFCFFFFF10E067EE63762B86ABC84E1A50CAE5C8FCA471EEDC39DEB51F97FFFFE65B87DFB21C3F5EBF7187E092933FC67856C2A0136066DD1978A010410138176F66FEB850C0B618DC16F0791BA84DC76906E21AE3D7B9F7F20D42AD8232DE5990F9DBC213FFEB12FDFFA8F7B34103669F31D18F13F59B177CFFEE3199AFD8FAD5D80A5AB89AB7108CDF54F940A196EE94F67F805EED733E2C8F50C28753FA9A5C0EBD71F182E5EBCC5F0FEFD2706C6DFDF19584F02ABEE5FDFC0CE7FF58BE12A03E2FC00B046800022B46E1C540AFC78F685613EF8C4CEDB07C025011CF087E32EFEBFFCC0CC05A0153BFFBF305016FBB8A674F1CC078072FB770E08CD80A781C7C880A37EC76237AE36079659BE2FC0BAFEA6D1128657D21128F53ABE061C6AC4132E057EFDFA0DCCF1F719EEDF7F022E01985FDF6260DFD1C2C07CEF38A423F697E1F5DEF7E0CB257E3320CE5D660008204209003C41B4FF01C31298B7BE6C6F444A00DEB88B7F1626DCF3239462062C4534B6153FE0EE1D30C7FF60C76C40FE67C03F29C480A5E1F69FD0C81F6A2F0094EB9F291632DCD545CDF59811C98025E7632F05B0E5FA172FDE325CB972075CE783723DFB91E90CEC7BFB8055DD5BB0533EFE65B8BEFE0D43D5F35FE02377910FDF6600082062CE09FC5BBC87E1969B22C37A114E86C0EF973730F07E875E8DC2C40BEC110013C1C72D98BA0410975C80E61328BAE016E72010B66A0029E27EB341FAF738876D719CFD82B33AC0362E80A5FD00ECE27DE5336278A25CCBF08B1D5A4D825BEFFFD13204FA823FE84D515045E00B83E06C06F4E5C10C3F7FFE66B87BF731C3C78F5FC0EA989F5E60603D3E9FE13FA8C807B5E2FF317CBBFD8D61CDB2970C2B416B93413504283D30400E99002700800022260180570BDD7DCFB04C189800FE7FFBC0F0FDE402066E07E82C9D20B031F37E13DEAE3AC38B8B0C8C1A01543C5409CBA24FF4C59B3F39A0D74DFCC7B249034BBF1D47570D6BA39301C72110E05CCFCBF05A2685E1AD4438F47027D861DC90C847CDCDFFB12D11465287D0F31F2DB1BD7CF996E1F1E3970C7FFEFC01E77A96637319981E9F879F79FCF617C35960C4F7BFFC053E41F43D34017C6240DCC100060001C442647EFB13B08661DF832C86A7ACCC0CD29FF74F4424000E7560C7C20874F92DA6467E4E86FFEFBF03AD87B61B18B9A85402E05BBE0D2AE0D8B137CC709DDC81AF01F79F0175EF1EC6FC012262BE0173FD732548AE874DF73342F5429C8C63F09F885200966E20B9FE09C3A74F905C0F8A746660E4FFFF0969E88172FDE94F0CD3B6BC61D8CB00393DFC1D34D77F65C0720B0B4000117B5630B84B78FF2343BFAA2043CF9FB70F18BE5FDAC0C0A907DD9021E007B92C0963629A1592009E5F804E26293250ED605DACEBF4404D1C58E4FFC31EA9FF896898603BE401CFBABD7FC0BAFEAD5432C33B7158AE87C83332228C82E568CC3A1C7F29809C104075FD93272F197EFF06E6FA5FDF19988ECE62607C740EEEFD37BF19CE2E7D0ECEF54FA1110FCAF9A0B367BE2335FE50004000319150EBFE725CCAB0E8CF7F4897F0F33EA42E212801B04A623604F939203D30580200FB448C0A0D416C7301A0720A18F9FF18712FDCC43979836D860FCBC20E46CCA2FF3B9F21C323DDC50C1F2423B0B4D6195146EE70CFFF628E03209B036AE1DFB8F10058E4BF00B6F0FF0173FD3906E67545F0C807E5FAE31F187AFA1E32D40023FF0E501BA8D87F01C46FD11B7DE800208048392E1EDC257CF19961BE240F43DE0F6097105412C0AF67118E015ADB89AA039A00C089E0FE01C8010EA025E6A0235AA9D5108445D61F3648E433FCC3334FCF80E52008061C43CCF80787FE31F332BC974962F820168694EB91733E2345A5004CCFB3676F8039FF0D38D7337C79C3C074623103C3C333F0BAFE0DB0AE5F02CCF52F7E129FEB9101400091724A18B83178E031B44B08B4FCC356A42EA1A03F3072B951C6029880AD465850C24B01D0E2528AC600B00CFFFE0546FE7F263C753B039EC51C84267D304B8E1FBC860CCFB417327C0236F4700DE8602B05F0AF024195FFFEFD27C3B56BF78109E03538D7335EDBC1C0B8B1121CF9602FFF67F8760C98EB7B1E30D400239FA45C8F0C000288D463E2FE56EC67B8F5E61BC37AF098C0F1050CFFBE43978C3183BA844EA811C00A349E9315E2E07B0711ED008AC70090EAE97FCC10FC1FDBB66D6C1331384A068C133D3067F5FE3103EB7A851A8697EA5381B58D24DEB178E46E1BA24F8F7BCC1F99FFFCF96BF030EEF7EF3F1818BF007B6EDB9A18184E2C8234F480E63CFCCEB063F24386944DAF18B642231E845F411B7B3FA0AD7CA21A5B0001446A02009702675E30CC8085D5C7BD486D01896C8C45208C225C90121AB6C298E21200B9EB06DA81C386B9AEFF3FAE7E3B9EB580FFF18D02024395DF96E185EE5A866F225E04C7E2D14B0158E22034A083C8F56F207257B631FC5F57CEF0FFD935D868DE9B3D6F181AA63E62E87FFE137CDB2A682BE07352733D3200082026326ADE3FE93B184E7FFE059925FCB07702C3BF6FD052804D9A814128002590994438214CA01A7035005A4C42494310B995FE978DC0916F04A66F314A06CCE21F94EBDF29B533BC55EE00B3499991C33EB2872BD7BF050FE5FEF8F1131895AF19FE6DAE67F8770C58C2FEFC0A76E20360AE9FFA90216FD71B86FD48B9FE3539B91E1900041039B74D82BB84773F30CC06738011FBE502D22CA170204A026016E680072B7C6919AB2E858D3F70DF0BFBD02CB67DFC8C783678E0593BF053C096E18DF61A304D6CAE471DD685D5FFB84B0150AEBF7EFD01B8A10792FF7B6933C3DFD5C50CFF9E5D053BE5E73F8637BB81B97ECA0370AE07D5F54FA175FD3B68AEFFCD40C1D9BD0001C4446614FC0E5CC7B0FEC71F602A048D3A6D466A0CF29A3330F098C21B828C5CCC0C8C9C2C906AE0EE412A5503A0A95C6606AC9B3C701EFA8863DF1F321B366803CCE91F15DA193E28B633FC67E1255874E39AD2C53DBE8FE8D783BA77A05CFFFFF32B863F1B6B18FE1E9987C8F5DF8075FD0386BC9DAF3172FD2706E2AFD6C50B000288894C7D208BBF3FF8C8D00F4E0D6F1E307C3A86B460442A0F6B35F01B5602B05950D610FCC78ABBA58FF5806706069C9741A025845FC0BAFE9DD66A865FD05C8F3E23474ADD8FAB14F8FEFD1738E241C53E78E5CD854D0CBF561430FC7D72055ED7EF7ACDD030E93E30D7FF40C9F5EFA1237A14E57A64001040E42600F09231EF350C0B7FFE01A74A860F4791978C014B0136297824B14842BA83A01E03B81A00B503E0A382A4E67E661CFBFB712C17C738D10BDBC20D48AEFFA2D0C6F045B10DCCC6B5268FF8BA1F7B2900CAF5376F3E04E67A607B0D98EB7FAEAB62F87D6836C3FF1F885C0F8CF8BC1DAF6897EB910140003151A017D4E8F8FEE63BC31A90C3BFDD38C0F0EDE60184AC743EBC27C022CC8E58207C6523A21D4072FC3342EA7E5CC53ACE5543FF19F09D0BF09BCF86E193C62A86DFFC7618F534A9AB71709502A05C0F8A78D0240E88FFFBFC0686EFCB7280B9FE32D8553F8075FDCE570C0D13EE31F43FA371AE4706000144490200AF15987389612A6C78F8F546A4B6806808A457006A0700E38C450492087EDF41AA06C8C9FD384FEEC673B2378ECB1FFE3373337C576805E236A00379B1CECB93BB260FB91478F9F21DC3AD5B8FC0B9FEFFA7570CDF579732FC3C30139EEBEF7F65D831E12E43DE763AE57A640010404C14EAFFB3F41AB054FBC2B000E491AFC0520084E140B6001E31AC12906AE0F7D30B905545E09E003709F18FDEF0C333CDCF8067A93854EC2F30D77F535FC5F087CF96E0583C29A500B21E50E3EED6ADC7E00400E2FF3ABB9EE1EBA24C863F8F2F41364901EBFA1D2F191AFAEFD237D723038000A23401800786E65E6698F2F73F6447F1AB0D48A5805828B414F8CFC02681540D5C86761BD974C9C8FD045AF788D54E58CD01D5EFBFE45B197ECAC3723D23CE3579E494023035A048BF7DFB31A485FFE925C39715450CDFF74F03B6F02147ECDD07D6F5FD7718F2B6BDA47FAE47060001C4440533FE2CBF0E2D05401D53F45240AE08DC0E60E2640262664802B803ED0EB259129FFB1998F09CDA85A5958FBE440C3421C56BC3F04B7D2530F7DB12188B67C0B27C8B502980C8F5B76F3F6178F5EA3DD8CC9F67D6327C9A9FCAF0FBD145C84932C05CBFED054343EF6D86FEA70394EB91014000917B7D3C3A608FD46090AF306338031A09E0D6706050AE443A4FF0A419304D3F66F87CF913C3D73B90850BE21DEF19983880DDB9D72144180F3DFD1C360506BBB6FD1F321BE9AAF67F4834F8A2286E86BF52150C7F79ADA1A5D07F248C9BFFEFDF7FF87C3C8C8D5D0F440C94EB5FBC7807E6FF79FF8CE1D3A636865F0F2FC04BBEBB5F18762C7FC2B0E8E9778C993BD8481E5D2F6302B913208098A864D69FE537185E3CFFCA001E0CF872FD0018C3817C313804386511D3C33F2E6D807407599408A55148E4639CC58FE73838A4AAE13F30D7FF05E6FA7F7CB6440EE8E02B0518B0D6FD3F7E4056E980723D48FCFBA9550CEFE724C1231F94EBB702737D0F28D77F1FF85C8F0C0002885A09003C3CBCE00AB02D00DD43F0623D525B40229C8181DF9281858F19D82380AC6FFB711B5A0D800F9DC0B7C18409CB26CDFF0CF8177682B6C2F030FC976D65F82FD70A9EA92434168F7D4D3EA1D13EC83AFCBB779F825BF8FF3EBE6078BF2887E1F3AE490CFF7E40EAFA7BC0167ECF2D86BC2DCF07B6AEC705000288898A66FD59711358027E039602A0535FAEA195028A2590E3AD25D8C0BEFD7611DA1064B724C28978CEF1C5B6F50B98EB1954573230405BF8A8AB71089702E80D3F6C75FDCF9FBF18EEDD7B064E0020FEB793AB18DECC8C63F8F500727DDDF73F0C6FB63C6368E8BA39F8723D320008206A26007029B0F02AA447001278303311212B600D2C05AC18382421A75B826610C189007C0299128ED61F138E85190CD8176DB002CD926E66609069019ACB833547133BA083DE03404E40A04807453E2811FCFDF09CE1ED824C868FDBFB19FE7DFF02AFEBBB6F32E46D1EA4B91E190004101395CDFBB312580A3CFAC43009DCDA7FFD80E1CD21A43902B516064E09167835F0EDE24644298075EA9709AD94C7B24207C6160C06B635663130F0D8608D34724B01E4C4018AF0070F9E33BC7903C9F55F8E2D6778352D9AE1E7FDB360D780EAFACDC05CDF7983A1FFC920CEF5C8002080A8D50B40E91180F2FBB1088673AC8C0C526C220A0CDA1DE71998B9A0670F5FC96278BB652EC3E787BFC0C7BCCAF7BD074D133230BCC9C4E23A76D47C026FD9FF47B4F499C51818444B191838F5F1B67609B5F8B1F1FFFD43F40040B91ED2B50366E1A7D719DE6DEE61F87EEF0C3C3D5EFFC8B076C5238635C0887F36185AF8C4F6020002888906E68252F9B7071F192680E7B381A5C08B6D48AB8634DA19386584C1F10A5A4BF015B49680451998D9C5B18CFCE1B8CD0B96EBF90381C5FD0CBC918F6FBA969852E0DBB79F0CF7EF3F6378FBF61398FF61CF0C86E79322187E00231FE484CFBF191E2D79C050D2739361E650C9F5C8002080689100C0A38351DB19167CF9C570061455CFB74D60F8FB15BA6A88859F81CB22175E0D7CB900AD0638AC50CB7FF80A5A2C4BB440B95EBC9B81412813DCDA2758CCE159B481AB2DF00F58BA80723D681DFE9F3F7F197E3DBFC9F06C6218C3FBDDD311B9FE13C3DAFC730CD9FB5F319C8446FCA0ADEB7101800062A291B9A022EFDBB5770C3DE08520C0C8BFBFB01021AB52C9C0A5A8044900E7A1BD014E374CA7619BD4E101E67AC969C004A3476A6D477429005AA5F3E8D14B86F7EFBF80C5DEED9AC6F0A43F98E1E7B31B60F9CF7F181E2D06E6FAAEEBE013549E4323FF2552AEFF3598733D320008205A2500F04C61CE7E863D1F7F32EC05CF141E58C0F0F12AA25BC86D5B0BAF06C0898015560DFC874CFBA2CFF2318B02EBFA0E60632F9DA85C4F4E2900CAF56FDE7C6478FAF43530D7FF03E6FA1B0C8F7B8318DEED9C0A77C635605D9F7B86217BDF4B70AE7F064D006FD072FD90010001C44443B3C1EB05B63D60A88575D11FAF460C0E719BC6831B81E085A5B0C5245C6ED85BFFDC01C0C89F0A6C5EEA51E8247CB9FE17B0B87FCDF0E1C357706278BB7332C3C3EE006083EF06E4345B605DBFE83E4349E735945C0F5B9BF70D5AD7FF671862002080689900C0DBC9265E60B8FEF42BC35470445F39C0F0F200A25BC863100009DC731BC02501380120D7F7A0BA5E1898EBF9D3209B4E288D7E2CB9FEFFFF7FE006DEB3676F197EFF06E6FA67D719EE77FA31BCD936199E0641B93EE73443F6DE1718B91ED4CAFF39D4723D32000820261A9B0FDE4EB6EC26C36460D882AE4F62B837AF10DC26000141977C78747F3EB70172E40C2BF40874DE6860436F0115723DEE5200347CFBF4E91B864F9FBE8213C59BED1319EE76F8307C7F721D721AFC2F86470BEF3294B45F1D5EB91E19000410AD1300780DC8FABB0CCF810DC26610EF3730F29F6E81740BD9650DC07B0B41A5C0FB23D06A802F1E58DC031B79BC31B419F8806EBD7EF7EE13C3F3E7EFC0C7A9FC78728DE16E9B17C3ABAD13E0CD8EAB1F18D6669D62C8DE330C733D320008205A0C046103A0336AF9B7F9326CE36261300105B0C5CCFB0C1C620A0CAF363632BC5ADF001ED7D1E8BDCF001A38A22500E5FAD7AF3F32FCFAF5075CFC3FDFD407C6B046DE4760AE5FFB8861D2EEE70C171910072B7C44CAF1C322E26103410001C44427BBC0DDC2532F810D4268B1707D12649E40D03A1E5E86BE3BBC90660E008DE6BD7FFF99E1E5CBF7D05C7F95E166B33BC38BCD7DF0A20A94EB334E326403237F58E77A64001040F44A00E0C1A186530CA79E7D61980A0AEDF7C006E1F37D0B185881399E4BDD015C02BC459E37A022009DAA015AA8F1F9F37730FFF9A65E86EB8DAE0CDF1E5D85344281B97EDE6D869296CBC3B7AEC705000288898E768172CFB765B719A6FC0496BCA080BF3507D22014B489879CDCFAFA01C367E429642AE4FA0F1FBE30BC7AF5019CEBBF3DBAC270ADC185E1D9C61E78917FE53DC3DAD4E3232BD723038000A26702003708B73C607876EB3D431348E0D7970F0C77973532F01B07303041278BDE1E5A48B55C0F8AF8AF5F7F801B7EA048BFD6E00C4E04FFA163F87381B9BEF9D2C8CBF5C8002080E8D508C468106EF0803608811C93B6FD0C9F8F2D044F1D838E9FD39B741F317B4846AE071D9B061AD801F90D14E1F7E6E4327C7B78053E9108CAF58D17C0CBD76087287D60409DB91BF6110F6B04020410D300D80B6E106E7FCC90FFE73FC3175084DC9855C8206417CF009B37787F7A0399B9FE17780207D4D2FFFBED23C3E3E5350C57EB1D21B99E0192EBE7DC62280146FE88CEF5C8002080062201801B8433AF325C7FF98D015CDE7FBA7781E1E5B983E02E2028113C479E3E26322583EA7AD0302E88FDF9C631866BC0887FB96B26643712B0FABFFC8E616DE26186EC9D4F47665D8F0B0004D0405401B0E138F0C291F5EE0C07D89919D44111A51956C0F06AC7047051ADD7799E815BC180A8BAFE13E8DAB97FFFC0B9FEC9FA4E86973B67200E4CFCC1707DE60D8649A7DF80AF737E0FC5C3B25F4F4E150010404C0365370364CAF4CBD1170CC5B0D1B7A7670EC027019F6D9D48D0F1A088076110FBCBCDA30C576BEDE1910FCAF5A75E332C4A39C250028CFC4B0CA8F3F5233AD7230380001AA8120006403B3EF866DB31344972316481229F9B135834B030807B0526D3EE33B0700B60CDF55FBF7E0737F8FE7CFDC8F0747D07C38B9DD31147A78172FD758689C004709B01F598D4EF0C4368AE9E1E25004000310DB01BC00DC2B5F71926FFFCCBF01CBC4DFA17B4BF086C0CBE3DB501AD85FF8FE1F3E76FE0C80777E56E1C01E67A5B70E4C3EAFA93AF1816251F62280146FE65A4BAFE2D52AE1F8D7C24001040035D02800068D727779D11838F8908C352502E16E0814CDA80DA00C6BDE7E1B91ED2B5FB07CEF577676530BC3FB31525D7CF00E67A600218CDF52494000001341812006C6C806F8E1D439F083B432C3BB062E0841EF1AFDBB097815DD912BC2E0F04DE9DD9C2700F18F9BF81890096EB0F3D6758D87799610B52230FD4AFFFC23044D6E50D64020008209641E2167055B0F22E437BAA3A830B30B62439A12BC29FEC99CFA0A060CEF0EBCD2386874BCAC1090096EB9F7D6538DB7A9EA1FFC167F891E8EF47733D69002080064B0900AF0A6A0C187C8C8419967271005B882C9088960CAC6278B17D2A4AAEDFF69861DA8C6B2847A283E8AF48ADFBD15C4F440900104083290130C27A0533AD19FAC438196279916EAB87E5FAFB9F190E755D609809CDF5241F8E3C0A50130040000DA60400024CD00122C129160CDD0A020C516CD013E13EFE6478B8E521C3DC45B7C023791F9006746047A48EE67A32120040000DB60400AB0A40170E81060084402502B491F80F9ACB3F43EB7958AEFF339AEBC94F0000013418130008B0404B025042805D75FA1F5AC4FF402AEE47733D8509002080066B028055072CD01201E6C0BF50FC6F34D7532701000410E3FFFFA319682403800003004042FE192CC3EC400000000049454E44AE426082', 'folder_yellow.png', 'folder yellow', 'image/png', 0);
+CREATE TABLE cars (id INTEGER PRIMARY KEY, model Text(200));
+INSERT INTO "cars" VALUES(1, 'Fiat');
+INSERT INTO "cars" VALUES(2, 'Syrena');
+INSERT INTO "cars" VALUES(3, 'Chrysler');
+INSERT INTO "cars" VALUES(4, 'Volvo');
+INSERT INTO "cars" VALUES(5, 'BMW');
+CREATE TABLE ownership (id INTEGER PRIMARY KEY, owner Integer, car Integer, since Integer);
+INSERT INTO "ownership" VALUES(1, 1, 1, 2004);
+INSERT INTO "ownership" VALUES(2, 2, 2, 1982);
+INSERT INTO "ownership" VALUES(3, 3, 3, 2002);
+INSERT INTO "ownership" VALUES(4, 4, 4, 2005);
+INSERT INTO "ownership" VALUES(5, 10, 4, 2006);
+INSERT INTO "ownership" VALUES(6, 4, 1, 2003);
+INSERT INTO "ownership" VALUES(7, 12, 3, 1999);
+COMMIT;
diff --git a/kexi/examples/build_kexi_file.sh b/kexi/examples/build_kexi_file.sh
new file mode 100755
index 000000000..a3d73cd91
--- /dev/null
+++ b/kexi/examples/build_kexi_file.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# Builds a single .kexi file from a .sql file specified as $1.
+# The destination .kexi file is saved with name specified as $2.
+# $2 can be omitted if $1 is of a form "name.kexi.sql" - then
+# the destination file will be "name.kexi".
+# Only .kexi file that is older than .sql file is recreated.
+# ksqlite is needed on the $PATH.
+
+KEXISQL3PATH=../3rdparty/kexisql3/src/.libs/
+PATH=$KEXISQL3PATH:$PATH
+export PATH
+LD_LIBRARY_PATH=$KEXISQL3PATH:$LD_LIBRARY_PATH
+export LD_LIBRARY_PATH
+which ksqlite > /dev/null || exit 1
+
+[ $# -lt 1 ] && echo "Missing .sql filename." && exit 1
+
+if [ $# -lt 2 ] ; then
+ kexi_file=`echo $1 | sed -e "s/\.kexi\.sql/\.kexi/"`
+else
+ kexi_file=$2
+fi
+
+if test -f "$kexi_file" -a ! "$kexi_file" -ot "$1" ; then
+ echo "Local $kexi_file is newer than $1 - skipping it"
+ exit 0
+fi
+
+rm -f "$kexi_file"
+echo "Creating \"$kexi_file\" ... "
+ksqlite "$kexi_file" < "$1" || exit 1
+echo "OK"
diff --git a/kexi/examples/build_kexi_files.sh b/kexi/examples/build_kexi_files.sh
new file mode 100755
index 000000000..8f59c9efe
--- /dev/null
+++ b/kexi/examples/build_kexi_files.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# Builds .kexi files from .sql files from the current directory.
+# Only .kexi file that is older than .sql file is recreated.
+# ksqlite is needed on the PATH
+
+for f in `ls -1 *.kexi.sql` ; do
+ ./build_kexi_file.sh $f
+done
diff --git a/kexi/examples/update_sql_files.sh b/kexi/examples/update_sql_files.sh
new file mode 100755
index 000000000..e7cfe98bd
--- /dev/null
+++ b/kexi/examples/update_sql_files.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# Updates .sql files using .kexi files
+# Only .sql file that is older than .kexi file is recreated.
+# ksqlite is needed on the PATH
+
+KEXISQL3PATH=../3rdparty/kexisql3/src/.libs/
+PATH=$PATH:$KEXISQL3PATH
+LD_LIBRARY_PATH=$KEXISQL3PATH:$KEXISQL3PATH
+which ksqlite > /dev/null || exit 1
+
+for f in `ls -1 *.kexi` ; do
+ if test -f $f.sql -a ! $f.sql -ot $f ; then
+ echo "Local $f.sql is newer than $f - skipping it"
+ continue
+ fi
+ echo -n "Creating $f.sql ... "
+ echo "vacuum;" | ksqlite $f
+ echo .dump | ksqlite $f > $f.sql || exit 1
+ echo "OK"
+done
diff --git a/kexi/formeditor/Makefile.am b/kexi/formeditor/Makefile.am
new file mode 100644
index 000000000..8f8aacbe6
--- /dev/null
+++ b/kexi/formeditor/Makefile.am
@@ -0,0 +1,34 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+INCLUDES = -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/widget/utils \
+ -I$(top_srcdir)/kexi/widget/tableview \
+ -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/lib/kofficecore \
+ -I$(top_srcdir)/lib -I$(top_srcdir)/lib/koproperty $(all_includes)
+
+lib_LTLIBRARIES = libkformdesigner.la
+libkformdesigner_la_LDFLAGS = -no-undefined $(all_libraries) $(VER_INFO)
+libkformdesigner_la_SOURCES = container.cpp resizehandle.cpp widgetfactory.cpp \
+ widgetlibrary.cpp libactionwidget.cpp form.cpp \
+ objecttree.cpp formIO.cpp formmanager.cpp objecttreeview.cpp spring.cpp \
+ commands.cpp events.cpp connectiondialog.cpp richtextdialog.cpp \
+ tabstopdialog.cpp editlistviewdialog.cpp utils.cpp widgetpropertyset.cpp \
+ kfdpixmapedit.cpp widgetwithsubpropertiesinterface.cpp
+
+METASOURCES = AUTO
+libkformdesigner_la_LIBADD = \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/kexi/widget/utils/libkexiguiutils.la \
+ $(top_builddir)/kexi/widget/tableview/libkexidatatable.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la \
+ $(LIB_KPARTS)
+
+servicetypesdir = $(kde_servicetypesdir)
+servicetypes_DATA=widgetfactory.desktop
+
+SUBDIRS = . factories #test #scripting #kdevelop_plugin
+
+messages: rc.cpp
+ $(EXTRACTRC) `find . -name "*.rc" -o -name "*.ui"` >> rc.cpp
+ $(XGETTEXT) `find . -name "*.cpp" -o -name "*.h"` -o $(podir)/kformdesigner.pot
diff --git a/kexi/formeditor/TODO b/kexi/formeditor/TODO
new file mode 100644
index 000000000..611d5c0cd
--- /dev/null
+++ b/kexi/formeditor/TODO
@@ -0,0 +1,66 @@
+^^^^^^^^^^^^^^^ IMPORTANT ^^^^^^^^^^^^^^^^^^^^^^
+**** New Features : ******
+* KMainWindow support : Make an action editor + a toolbar editor (as in MozillaFirebird ?) + a menu editor
+ (see Gambas and askk other devs if they already have done this)
+* Automatically set label name to the text
+* Create an event editor and manage signals/slots (EventBuffer ?)
+ HOWEVER, SIGNAL/SLOT ISSUES (js):
+ 1. signals/slots mechanism is hard to use for average folks
+ 2. it may be easily simplified to avoid confusing them
+ 3. TrollTech would try to get influence on the Kexi Project
+ because of violating their licenses, if we'd like to expose
+ all their technologies so plainly
+ (at least commercial) license mentions that there cannot be
+ a project that is a replacement of the Qt developed out of the Qt)
+ 4. It wouldn't hurt to ask them, as well.
+* Add many other widgets :-)
+* Create db-aware widget factory
+
+****** Bugfixes/Changes : *****
+* Add a grouping ability to Widget
+* Switch WifdgetInfo::className() to QCString
+* Make subform open design when double-clickng
+
+^^^^^^^^^^^^^^^^^^ OTHER ^^^^^^^^^^^^^^^^^^^^^^^^
+* Move tab between two QTabWidgets by drag'n drop
+* Better editor for iconview
+* during inline editing: CTRL+Enter could insert \n
+
+^^^^^^^^^^^^^^^^ DONE ^^^^^^^^^^^^^^
+* Finish sync between Form and ObjectTreeView (2/21/04)
+* Only QTabWidget should be selected, not pages (2/21/04)
+* Add a minimum size for created widgets (using size hint) (2/21/04)
+* Move the pasted widget a little to avoid to have two widgets at the same pos (2/21/04)
+* Make FormManager independant of the parent container (ie no longer depends on QWorkspace) (2/23/04)
+* Fix pixmaps in Property Editor (2/24/04)
+* Properties names and values are now i18n'ed (2/24/04)
+* Add a classname and filename member into Form (2/24/04)
+* Add support for layouts and special container (HBox, VBox ...) and spacers (partially 2/27/04)
+* Allow Multiple selection : edit common properties (colors, font..),copy/cut them at one time, ... (2/28/04)
+* Move multiple selected widgets at one time (2/29/04)
+* Added real support for old values (2/29/04)
+* Form previewing stuff (3/01/04)
+* QByteArray methods in FormIO (3/01/04)
+* Undo support(3/05/04)
+* Alignment and layout properties also support old values (3/06/04)
+* Better handling of multiple widgets in ObjectPropertyBuffer : each widget has its own old value, so reset will act as expected (3/07/04)
+* Remove all these " Q*** " names (3/08/04)
+* When double-clicking on a QLabel or a QLabel (others ?), the widget should be inline-editable (also on creation) (3/11/04)
+* Add many other widgets :-) (part I : all basic widgets) (3/20/04)
+* Add grid layout (3/27/04)
+* Add many other widgets :-) (part II : all basic containers + more widgets) (4/13/04)
+* Change tab position by drag'n drop (4/14/04)
+* Icons in ObjectTreeView(in branch) + context menu (4/14/04)
+* Modify Spacer for better compatibility (save orientation and expanding) (4/19/04)
+* Rich text editor (see BasKet source) (4/20/04)
+* Allow to select multiple widgets by drawing a rectangle on form (4/22/04)
+* Add a "Lay out Vertcally / horizontally " menu item when multiple widgets are selected (4/22/04)
+* Select all widgets when pasted (8/05/04)
+* Handle tab stops (8/05/04)
+* Make ObjectTreeView handle multiple widgets (11/05/04)
+* Manage label buddies using a popup menu (24/05/04)
+* dragging with CTRL key can cause object's copying (25/05/04)
+* Create a PixmapCollection with an editor which allow to choose pixmaps by path or from KDE icons, no inline saving of pixmaps (9/06/04)
+* Make actions toggable (with KToggleAction) (js 11/06/04)
+* Make FormWidget pure virtual (17/06/04)
+* Resize label to fit the text (22/06/04)
diff --git a/kexi/formeditor/commands.cpp b/kexi/formeditor/commands.cpp
new file mode 100644
index 000000000..ca4f0f20d
--- /dev/null
+++ b/kexi/formeditor/commands.cpp
@@ -0,0 +1,1601 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <qdom.h>
+#include <qwidget.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qsplitter.h>
+#include <qmetaobject.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kmessagebox.h>
+#include <kaccelmanager.h>
+
+#include "formIO.h"
+#include "container.h"
+#include "objecttree.h"
+#include "formmanager.h"
+#include "form.h"
+#include "widgetlibrary.h"
+#include "events.h"
+#include "utils.h"
+#include "widgetpropertyset.h"
+#include "widgetwithsubpropertiesinterface.h"
+#include <koproperty/property.h>
+
+#include "commands.h"
+
+using namespace KFormDesigner;
+
+// Command
+
+Command::Command()
+ : KCommand()
+{
+}
+
+Command::~Command()
+{
+}
+
+// PropertyCommand
+
+PropertyCommand::PropertyCommand(WidgetPropertySet *set, const QCString &wname,
+ const QVariant &oldValue, const QVariant &value, const QCString &property)
+ : Command(), m_propSet(set), m_value(value), m_property(property)
+{
+ m_oldvalues.insert(wname, oldValue);
+}
+
+PropertyCommand::PropertyCommand(WidgetPropertySet *set, const QMap<QCString, QVariant> &oldvalues,
+ const QVariant &value, const QCString &property)
+ : Command(), m_propSet(set), m_value(value), m_oldvalues(oldvalues), m_property(property)
+{
+}
+
+/*
+MultiCommand::MultiCommand()
+{
+}
+
+MultiCommandGroup::addSubCommand(PropertyCommand* subCommand)
+ : Command(), m_propSet(set), m_value(value), m_oldvalues(oldvalues), m_property(property)
+{
+}
+*/
+
+void
+PropertyCommand::setValue(const QVariant &value)
+{
+ m_value = value;
+ emit FormManager::self()->dirty(FormManager::self()->activeForm());
+}
+
+void
+PropertyCommand::execute()
+{
+ FormManager::self()->activeForm()->selectFormWidget();
+ m_propSet->setUndoing(true);
+
+ QMap<QCString, QVariant>::ConstIterator endIt = m_oldvalues.constEnd();
+ for(QMap<QCString, QVariant>::ConstIterator it = m_oldvalues.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem* item = FormManager::self()->activeForm()->objectTree()->lookup(it.key());
+ if (item) {//we're checking for item!=0 because the name could be of a form widget
+ FormManager::self()->activeForm()->setSelectedWidget(item->widget(), true);
+ }
+ }
+
+ (*m_propSet)[m_property] = m_value;
+ m_propSet->setUndoing(false);
+}
+
+void
+PropertyCommand::unexecute()
+{
+ FormManager::self()->activeForm()->selectFormWidget();
+ m_propSet->setUndoing(true);
+
+ QMap<QCString, QVariant>::ConstIterator endIt = m_oldvalues.constEnd();
+ for(QMap<QCString, QVariant>::ConstIterator it = m_oldvalues.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem* item = FormManager::self()->activeForm()->objectTree()->lookup(it.key());
+ if (!item)
+ continue; //better this than a crash
+ QWidget *widg = item->widget();
+ FormManager::self()->activeForm()->setSelectedWidget(widg, true);
+ //m_propSet->setSelectedWidget(widg, true);
+
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(widg);
+ QWidget *subWidget = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : widg;
+ if (-1!=subWidget->metaObject()->findProperty( m_property, true ))
+ subWidget->setProperty(m_property, it.data());
+ }
+
+ (*m_propSet)[m_property] = m_oldvalues.begin().data();
+ m_propSet->setUndoing(false);
+}
+
+QString
+PropertyCommand::name() const
+{
+ if(m_oldvalues.count() >= 2)
+ return i18n("Change \"%1\" property for multiple widgets" ).arg(m_property);
+ else
+ return i18n("Change \"%1\" property for widget \"%2\"" ).arg(m_property).arg(m_oldvalues.begin().key());
+}
+
+void
+PropertyCommand::debug()
+{
+ kdDebug() << "PropertyCommand: name=\"" << name() << "\" widgets=" << m_oldvalues.keys()
+ << " value=" << m_value << " oldValues=" << m_oldvalues.values() << endl;
+}
+
+// GeometryPropertyCommand (for multiples widgets)
+
+GeometryPropertyCommand::GeometryPropertyCommand(WidgetPropertySet *set,
+ const QStringList &names, const QPoint& oldPos)
+ : Command(), m_propSet(set), m_names(names), m_oldPos(oldPos)
+{
+}
+
+void
+GeometryPropertyCommand::execute()
+{
+ m_propSet->setUndoing(true);
+ int dx = m_pos.x() - m_oldPos.x();
+ int dy = m_pos.y() - m_oldPos.y();
+
+ QStringList::ConstIterator endIt = m_names.constEnd();
+ // We move every widget in our list by (dx, dy)
+ for(QStringList::ConstIterator it = m_names.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem* item = FormManager::self()->activeForm()->objectTree()->lookup(*it);
+ if (!item)
+ continue; //better this than a crash
+ QWidget *w = item->widget();
+ w->move(w->x() + dx, w->y() + dy);
+ }
+ m_propSet->setUndoing(false);
+}
+
+void
+GeometryPropertyCommand::unexecute()
+{
+ m_propSet->setUndoing(true);
+ int dx = m_pos.x() - m_oldPos.x();
+ int dy = m_pos.y() - m_oldPos.y();
+
+ QStringList::ConstIterator endIt = m_names.constEnd();
+ // We move every widget in our list by (-dx, -dy) to undo the move
+ for(QStringList::ConstIterator it = m_names.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem* item = FormManager::self()->activeForm()->objectTree()->lookup(*it);
+ if (!item)
+ continue; //better this than a crash
+ QWidget *w = item->widget();
+ w->move(w->x() - dx, w->y() - dy);
+ }
+ m_propSet->setUndoing(false);
+}
+
+void
+GeometryPropertyCommand::setPos(const QPoint& pos)
+{
+ m_pos = pos;
+ emit FormManager::self()->dirty(FormManager::self()->activeForm());
+}
+
+QString
+GeometryPropertyCommand::name() const
+{
+ return i18n("Move multiple widgets");
+}
+
+void
+GeometryPropertyCommand::debug()
+{
+ kdDebug() << "GeometryPropertyCommand: pos=" << m_pos << " oldPos=" << m_oldPos
+ << " widgets=" << m_names << endl;
+}
+
+///////////////// AlignWidgetsCommand ////////
+
+AlignWidgetsCommand::AlignWidgetsCommand(int type, WidgetList &list, Form *form)
+: Command(), m_form(form), m_type(type)
+{
+ for(QWidget *w = list.first(); w; w = list.next())
+ m_pos.insert(w->name(), w->pos());
+}
+
+void
+AlignWidgetsCommand::execute()
+{
+ // To avoid creation of GeometryPropertyCommand
+ m_form->selectFormWidget();
+
+ int gridX = m_form->gridSize();
+ int gridY = m_form->gridSize();
+ QWidget *parentWidget = m_form->selectedWidgets()->first()->parentWidget();
+ int tmpx, tmpy;
+
+ WidgetList list;
+ QMap<QCString, QPoint>::ConstIterator endIt = m_pos.constEnd();
+ for(QMap<QCString, QPoint>::ConstIterator it = m_pos.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(it.key());
+ if(item && item->widget())
+ list.append(item->widget());
+ }
+
+ switch(m_type)
+ {
+ case AlignToGrid:
+ {
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ tmpx = int( (float)w->x() / ((float)gridX) + 0.5 ) * gridX;
+ tmpy = int( (float)w->y() / ((float)gridY) + 0.5 ) * gridY;
+
+ if((tmpx != w->x()) || (tmpy != w->y()))
+ w->move(tmpx, tmpy);
+ }
+ break;
+ }
+
+ case AlignToLeft:
+ {
+ tmpx = parentWidget->width();
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(w->x() < tmpx)
+ tmpx = w->x();
+ }
+
+ for(QWidget *w = list.first(); w; w = list.next())
+ w->move(tmpx, w->y());
+ break;
+ }
+
+ case AlignToRight:
+ {
+ tmpx = 0;
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(w->x() + w->width() > tmpx)
+ tmpx = w->x() + w->width();
+ }
+
+ for(QWidget *w = list.first(); w; w = list.next())
+ w->move(tmpx - w->width(), w->y());
+ break;
+ }
+
+ case AlignToTop:
+ {
+ tmpy = parentWidget->height();
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(w->y() < tmpy)
+ tmpy = w->y();
+ }
+
+ for(QWidget *w = list.first(); w; w = list.next())
+ w->move(w->x(), tmpy);
+ break;
+ }
+
+ case AlignToBottom:
+ {
+ tmpy = 0;
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(w->y() + w->height() > tmpy)
+ tmpy = w->y() + w->height();
+ }
+
+ for(QWidget *w = list.first(); w; w = list.next())
+ w->move(w->x(), tmpy - w->height());
+ break;
+ }
+
+ default:
+ return;
+ }
+
+ // We restore selection
+ for(QWidget *w = list.first(); w; w = list.next())
+ m_form->setSelectedWidget(w, true);
+}
+
+void
+AlignWidgetsCommand::unexecute()
+{
+ // To avoid creation of GeometryPropertyCommand
+ m_form->selectFormWidget();
+ // We move widgets to their original pos
+ QMap<QCString, QPoint>::ConstIterator endIt = m_pos.constEnd();
+ for(QMap<QCString, QPoint>::ConstIterator it = m_pos.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(it.key());
+ if(item && item->widget())
+ item->widget()->move( m_pos[item->widget()->name()] );
+ m_form->setSelectedWidget(item->widget(), true); // We restore selection
+ }
+}
+
+QString
+AlignWidgetsCommand::name() const
+{
+ switch(m_type)
+ {
+ case AlignToGrid:
+ return i18n("Align Widgets to Grid");
+ case AlignToLeft:
+ return i18n("Align Widgets to Left");
+ case AlignToRight:
+ return i18n("Align Widgets to Right");
+ case AlignToTop:
+ return i18n("Align Widgets to Top");
+ case AlignToBottom:
+ return i18n("Align Widgets to Bottom");
+ default:
+ return QString::null;
+ }
+}
+
+void
+AlignWidgetsCommand::debug()
+{
+ kdDebug() << "AlignWidgetsCommand: name=\"" << name() << "\" form=" << m_form->widget()->name()
+ << " widgets=" << m_pos.keys() << endl;
+}
+
+///// AdjustSizeCommand ///////////
+
+AdjustSizeCommand::AdjustSizeCommand(int type, WidgetList &list, Form *form)
+: Command(), m_form(form), m_type(type)
+{
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(w->parentWidget() && w->parentWidget()->isA("QWidgetStack"))
+ {
+ w = w->parentWidget(); // widget is WidgetStack page
+ if(w->parentWidget() && w->parentWidget()->inherits("QTabWidget")) // widget is tabwidget page
+ w = w->parentWidget();
+ }
+
+ m_sizes.insert(w->name(), w->size());
+ if(m_type == SizeToGrid) // SizeToGrid also move widgets
+ m_pos.insert(w->name(), w->pos());
+ }
+}
+
+void
+AdjustSizeCommand::execute()
+{
+ // To avoid creation of GeometryPropertyCommand
+ m_form->selectFormWidget();
+
+ int gridX = m_form->gridSize();
+ int gridY = m_form->gridSize();
+ int tmpw=0, tmph=0;
+
+ WidgetList list;
+ QMap<QCString, QSize>::ConstIterator endIt = m_sizes.constEnd();
+ for(QMap<QCString, QSize>::ConstIterator it = m_sizes.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(it.key());
+ if(item && item->widget())
+ list.append(item->widget());
+ }
+
+ switch(m_type)
+ {
+ case SizeToGrid:
+ {
+ int tmpx=0, tmpy=0;
+ // same as in 'Align to Grid' + for the size
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ tmpx = int( (float)w->x() / ((float)gridX) + 0.5 ) * gridX;
+ tmpy = int( (float)w->y() / ((float)gridY) + 0.5 ) * gridY;
+ tmpw = int( (float)w->width() / ((float)gridX) + 0.5 ) * gridX;
+ tmph = int( (float)w->height() / ((float)gridY) + 0.5 ) * gridY;
+
+ if((tmpx != w->x()) || (tmpy != w->y()))
+ w->move(tmpx, tmpy);
+ if((tmpw != w->width()) || (tmph != w->height()))
+ w->resize(tmpw, tmph);
+ }
+ break;
+ }
+
+ case SizeToFit:
+ {
+ for(QWidget *w = list.first(); w; w = list.next()) {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(w->name());
+ if(item && !item->children()->isEmpty()) { // container
+ QSize s;
+ if(item->container() && item->container()->layout())
+ s = w->sizeHint();
+ else
+ s = getSizeFromChildren(item);
+ // minimum size for containers
+ if(s.width() < 30)
+ s.setWidth(30);
+ if(s.height() < 30)
+ s.setHeight(30);
+ // small hack for flow layouts
+ int type = item->container() ? item->container()->layoutType() : Container::NoLayout;
+ if(type == Container::HFlow)
+ s.setWidth(s.width() + 5);
+ else if(type == Container::VFlow)
+ s.setHeight(s.height() + 5);
+ w->resize(s);
+ }
+ else if(item && item->container()) // empty container
+ w->resize(item->container()->form()->gridSize() * 5, item->container()->form()->gridSize() * 5); // basic size
+ else {
+ QSize sizeHint(w->sizeHint());
+ if (sizeHint.isValid())
+ w->resize(sizeHint);
+ }
+ }
+ break;
+ }
+
+ case SizeToSmallWidth:
+ {
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if((tmpw == 0) || (w->width() < tmpw))
+ tmpw = w->width();
+ }
+
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(tmpw != w->width())
+ w->resize(tmpw, w->height());
+ }
+ break;
+ }
+
+ case SizeToBigWidth:
+ {
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(w->width() > tmpw)
+ tmpw = w->width();
+ }
+
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(tmpw != w->width())
+ w->resize(tmpw, w->height());
+ }
+ break;
+ }
+
+ case SizeToSmallHeight:
+ {
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if((tmph == 0) || (w->height() < tmph))
+ tmph = w->height();
+ }
+
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(tmph != w->height())
+ w->resize(w->width(), tmph);
+ }
+ break;
+ }
+
+ case SizeToBigHeight:
+ {
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(w->height() > tmph)
+ tmph = w->height();
+ }
+
+ for(QWidget *w = list.first(); w; w = list.next())
+ {
+ if(tmph != w->height())
+ w->resize(w->width(), tmph);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ // We restore selection
+ for(QWidget *w = list.first(); w; w = list.next())
+ m_form->setSelectedWidget(w, true);
+}
+
+QSize
+AdjustSizeCommand::getSizeFromChildren(ObjectTreeItem *item)
+{
+ if(!item->container()) // multi pages containers (eg tabwidget)
+ {
+ QSize s;
+ // get size for each container, and keep the biggest one
+ for(ObjectTreeItem *tree = item->children()->first(); tree; tree = item->children()->next())
+ s = s.expandedTo(getSizeFromChildren(tree));
+ return s;
+ }
+
+ int tmpw = 0, tmph = 0;
+ for(ObjectTreeItem *tree = item->children()->first(); tree; tree = item->children()->next()) {
+ if(!tree->widget())
+ continue;
+ tmpw = QMAX(tmpw, tree->widget()->geometry().right());
+ tmph = QMAX(tmph, tree->widget()->geometry().bottom());
+ }
+
+ return QSize(tmpw, tmph) + QSize(10, 10);
+}
+
+void
+AdjustSizeCommand::unexecute()
+{
+ // To avoid creation of GeometryPropertyCommand
+ m_form->selectFormWidget();
+ // We resize widgets to their original size
+ QMap<QCString, QSize>::ConstIterator endIt = m_sizes.constEnd();
+ for(QMap<QCString, QSize>::ConstIterator it = m_sizes.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(it.key());
+ if(item && item->widget())
+ {
+ item->widget()->resize( m_sizes[item->widget()->name()] );
+ if(m_type == SizeToGrid)
+ item->widget()->move( m_pos[item->widget()->name()] );
+ m_form->setSelectedWidget(item->widget(), true); // We restore selection
+ }
+ }
+}
+
+QString
+AdjustSizeCommand::name() const
+{
+ switch(m_type)
+ {
+ case SizeToGrid:
+ return i18n("Resize Widgets to Grid");
+ case SizeToFit:
+ return i18n("Resize Widgets to Fit Contents");
+ case SizeToSmallWidth:
+ return i18n("Resize Widgets to Narrowest");
+ case SizeToBigWidth:
+ return i18n("Resize Widgets to Widest");
+ case SizeToSmallHeight:
+ return i18n("Resize Widgets to Shortest");
+ case SizeToBigHeight:
+ return i18n("Resize Widgets to Tallest");
+ default:
+ return QString::null;
+ }
+}
+
+void
+AdjustSizeCommand::debug()
+{
+ kdDebug() << "AdjustSizeCommand: name=\"" << name() << "\" form=" << m_form->widget()->name()
+ << " widgets=" << m_sizes.keys() << endl;
+}
+
+// LayoutPropertyCommand
+
+LayoutPropertyCommand::LayoutPropertyCommand(WidgetPropertySet *buf, const QCString &wname,
+ const QVariant &oldValue, const QVariant &value)
+ : PropertyCommand(buf, wname, oldValue, value, "layout")
+{
+ m_form = FormManager::self()->activeForm();
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(wname);
+ if (!titem)
+ return; //better this than a crash
+ Container *m_container = titem->container();
+ // We save the geometry of each wigdet
+ for(ObjectTreeItem *it = m_container->objectTree()->children()->first(); it; it = m_container->objectTree()->children()->next())
+ m_geometries.insert(it->name().latin1(), it->widget()->geometry());
+}
+
+void
+LayoutPropertyCommand::execute()
+{
+ PropertyCommand::execute();
+}
+
+void
+LayoutPropertyCommand::unexecute()
+{
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(m_oldvalues.begin().key());
+ if (!titem)
+ return; //better this than a crash
+ Container *m_container = titem->container();
+ m_container->setLayout(Container::NoLayout);
+ // We put every widget back in its old location
+ QMap<QCString,QRect>::ConstIterator endIt = m_geometries.constEnd();
+ for(QMap<QCString,QRect>::ConstIterator it = m_geometries.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem *tree = m_container->form()->objectTree()->lookup(it.key());
+ if(tree)
+ tree->widget()->setGeometry(it.data());
+ }
+
+ PropertyCommand::unexecute();
+}
+
+QString
+LayoutPropertyCommand::name() const
+{
+ return i18n("Change layout of widget \"%1\"").arg(m_oldvalues.begin().key());
+}
+
+void
+LayoutPropertyCommand::debug()
+{
+ kdDebug() << "LayoutPropertyCommand: name=\"" << name() << "\" oldValue=" << m_oldvalues.keys()
+ << " value=" << m_value << endl;
+}
+
+// InsertWidgetCommand
+
+InsertWidgetCommand::InsertWidgetCommand(Container *container)
+ : Command()
+{
+ m_containername = container->widget()->name();
+ m_form = container->form();
+ m_class = FormManager::self()->selectedClass();
+ m_insertRect = container->m_insertRect;
+ m_point = container->m_insertBegin;
+ m_name = container->form()->objectTree()->generateUniqueName(
+ container->form()->library()->namePrefix(m_class).latin1(),
+ /*!numberSuffixRequired*/false);
+}
+
+InsertWidgetCommand::InsertWidgetCommand(Container *container,
+ const QCString& className, const QPoint& pos, const QCString& namePrefix)
+ : Command()
+{
+ m_containername = container->widget()->name();
+ m_form = container->form();
+ m_class = className;
+ //m_insertRect is null (default)
+ m_point = pos;
+ if (namePrefix.isEmpty()) {
+ m_name = container->form()->objectTree()->generateUniqueName(
+ container->form()->library()->namePrefix(m_class).latin1() );
+ }
+ else {
+ m_name = container->form()->objectTree()->generateUniqueName(
+ namePrefix, false /*!numberSuffixRequired*/ );
+ }
+}
+
+void
+InsertWidgetCommand::execute()
+{
+ if (!m_form->objectTree())
+ return;
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(m_containername);
+ if (!titem)
+ return; //better this than a crash
+ Container *m_container = titem->container();
+ int options = WidgetFactory::DesignViewMode | WidgetFactory::AnyOrientation;
+ if (m_container->form()->library()->internalProperty(m_class, "orientationSelectionPopup")=="1") {
+ if(m_insertRect.isValid()) {
+ if (m_insertRect.width() < m_insertRect.height()) {
+ options |= WidgetFactory::VerticalOrientation;
+ options ^= WidgetFactory::AnyOrientation;
+ }
+ else if (m_insertRect.width() > m_insertRect.height()) {
+ options |= WidgetFactory::HorizontalOrientation;
+ options ^= WidgetFactory::AnyOrientation;
+ }
+ }
+ if (options & WidgetFactory::AnyOrientation) {
+ options ^= WidgetFactory::AnyOrientation;
+ options |= m_container->form()->library()->showOrientationSelectionPopup(
+ m_class, m_container->m_container,
+ m_container->form()->widget()->mapToGlobal(m_point));
+ if (options & WidgetFactory::AnyOrientation)
+ return; //cancelled
+ }
+ }
+ else
+ options |= WidgetFactory::AnyOrientation;
+
+ QWidget *w = m_container->form()->library()->createWidget(m_class, m_container->m_container, m_name,
+ m_container, options);
+
+ if(!w) {
+ FormManager::self()->stopInsert();
+ WidgetInfo *winfo = m_container->form()->library()->widgetInfoForClassName(m_class);
+ KMessageBox::sorry(FormManager::self()->activeForm() ? FormManager::self()->activeForm()->widget() : 0,
+ i18n("Could not insert widget of type \"%1\". A problem with widget's creation encountered.")
+ .arg(winfo ? winfo->name() : QString::null));
+ kdWarning() << "InsertWidgetCommand::execute() ERROR: widget creation failed" << endl;
+ return;
+ }
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0)
+//! @todo allow setting this for data view mode as well
+ if (m_form->designMode()) {
+ //don't generate accelerators for widgets in design mode
+ KAcceleratorManager::setNoAccel(w);
+ }
+#endif
+
+ // if the insertRect is invalid (ie only one point), we use widget' size hint
+ if(( (m_insertRect.width() < 21) && (m_insertRect.height() < 21)))
+ {
+ QSize s = w->sizeHint();
+
+ if(s.isEmpty())
+ s = QSize(20, 20); // Minimum size to avoid creating a (0,0) widget
+ int x, y;
+ if(m_insertRect.isValid())
+ {
+ x = m_insertRect.x();
+ y = m_insertRect.y();
+ }
+ else
+ {
+ x = m_point.x();
+ y = m_point.y();
+ }
+ m_insertRect = QRect(x, y, s.width() + 16/* add some space so more text can be entered*/,
+ s.height());
+ }
+ w->move(m_insertRect.x(), m_insertRect.y());
+ w->resize(m_insertRect.width()-1, m_insertRect.height()-1); // -1 is not to hide dots
+ w->setStyle(&(m_container->widget()->style()));
+ w->setBackgroundOrigin(QWidget::ParentOrigin);
+ w->show();
+
+ FormManager::self()->stopInsert();
+
+ // ObjectTreeItem object already exists for widgets which corresponds to a Container
+ // it's already created in Container's constructor
+ ObjectTreeItem *item = m_container->form()->objectTree()->lookup(m_name);
+ if (!item) { //not yet created...
+ m_container->form()->objectTree()->addItem(m_container->m_tree,
+ item = new ObjectTreeItem(m_container->form()->library()->displayName(m_class), m_name, w, m_container)
+ );
+ }
+ //assign item for its widget if it supports DesignTimeDynamicChildWidgetHandler interface
+ //(e.g. KexiDBAutoField)
+ if (m_form->designMode() && dynamic_cast<DesignTimeDynamicChildWidgetHandler*>(w)) {
+ dynamic_cast<DesignTimeDynamicChildWidgetHandler*>(w)->assignItem(item);
+ }
+
+ // We add the autoSaveProperties in the modifProp list of the ObjectTreeItem, so that they are saved later
+ QValueList<QCString> list(m_container->form()->library()->autoSaveProperties(w->className()));
+
+ QValueList<QCString>::ConstIterator endIt = list.constEnd();
+ for(QValueList<QCString>::ConstIterator it = list.constBegin(); it != endIt; ++it)
+ item->addModifiedProperty(*it, w->property(*it));
+
+ m_container->reloadLayout(); // reload the layout to take the new wigdet into account
+
+ m_container->setSelectedWidget(w, false);
+ if (m_container->form()->library()->internalProperty(w->className(),
+ "dontStartEditingOnInserting").isEmpty())
+ {
+ m_container->form()->library()->startEditing(
+ w->className(), w, item->container() ? item->container() : m_container); // we edit the widget on creation
+ }
+//! @todo update widget's width for entered text's metrics
+ kdDebug() << "Container::eventFilter(): widget added " << this << endl;
+}
+
+void
+InsertWidgetCommand::unexecute()
+{
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(m_name);
+ if (!titem)
+ return; //better this than a crash
+ QWidget *m_widget = titem->widget();
+ Container *m_container = m_form->objectTree()->lookup(m_containername)->container();
+ m_container->deleteWidget(m_widget);
+}
+
+QString
+InsertWidgetCommand::name() const
+{
+ if(!m_name.isEmpty())
+ return i18n("Insert widget \"%1\"").arg(m_name);
+ else
+ return i18n("Insert widget");
+}
+
+void
+InsertWidgetCommand::debug()
+{
+ kdDebug() << "InsertWidgetCommand: name=\"" << name() << "\" generatedName=" << m_name
+ << " container=" << m_containername
+ << " form=" << m_form->widget()->name() << " class=" << m_class
+ << " rect=" << m_insertRect << " pos=" << m_point << endl;
+}
+
+/// CreateLayoutCommand ///////////////
+
+CreateLayoutCommand::CreateLayoutCommand(int layoutType, WidgetList &list, Form *form)
+ : m_form(form), m_type(layoutType)
+{
+ WidgetList *m_list=0;
+ switch(layoutType)
+ {
+ case Container::HBox:
+ case Container::Grid:
+ case Container::HSplitter:
+ case Container::HFlow:
+ m_list = new HorWidgetList(form->toplevelContainer()->widget()); break;
+ case Container::VBox:
+ case Container::VSplitter:
+ case Container::VFlow:
+ m_list = new VerWidgetList(form->toplevelContainer()->widget()); break;
+ }
+ for(QWidget *w = list.first(); w; w = list.next())
+ m_list->append(w);
+ m_list->sort(); // we sort them now, before creating the layout
+
+ for(QWidget *w = m_list->first(); w; w = m_list->next())
+ m_pos.insert(w->name(), w->geometry());
+ ObjectTreeItem *item = form->objectTree()->lookup(m_list->first()->name());
+ if(item && item->parent()->container())
+ m_containername = item->parent()->name();
+ delete m_list;
+}
+
+void
+CreateLayoutCommand::execute()
+{
+ WidgetLibrary *lib = m_form->library();
+ if(!lib)
+ return;
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(m_containername);
+ Container *container = titem ? titem->container() : 0;
+ if(!container)
+ container = m_form->toplevelContainer(); // use toplevelContainer by default
+
+ QCString classname;
+ switch(m_type) {
+ case Container::HSplitter: case Container::VSplitter:
+ classname = "QSplitter"; break;
+ default:
+ classname = Container::layoutTypeToString(m_type).latin1();
+ }
+
+ if(m_name.isEmpty())// the name must be generated only once
+ m_name = m_form->objectTree()->generateUniqueName(classname);
+
+ QWidget *w = lib->createWidget(classname, container->widget(), m_name.latin1(), container);
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0)
+//! @todo allow setting this for data view mode as well
+ if (w) {
+ if (m_form->designMode()) {
+ //don't generate accelerators for widgets in design mode
+ KAcceleratorManager::setNoAccel(w);
+ }
+ }
+#endif
+ ObjectTreeItem *tree = w ? m_form->objectTree()->lookup(w->name()) : 0;
+ if(!tree)
+ return;
+
+ container->setSelectedWidget(0, false);
+ w->move(m_pos.begin().data().topLeft()); // we move the layout at the position of the topleft widget
+ // sizeHint of these widgets depends on geometry, so give them appropriate geometry
+ if(m_type == Container::HFlow)
+ w->resize( QSize(700, 20) );
+ else if(m_type == Container::VFlow)
+ w->resize( QSize(20, 700));
+ w->show();
+
+ // We reparent every widget to the Layout and insert them into it
+ QMap<QCString,QRect>::ConstIterator endIt = m_pos.constEnd();
+ for(QMap<QCString,QRect>::ConstIterator it = m_pos.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(it.key());
+ if(item && item->widget())
+ {
+ item->widget()->reparent(w, item->widget()->pos(), true);
+ item->eventEater()->setContainer(tree->container());
+ m_form->objectTree()->reparent(item->name(), m_name);
+ }
+ }
+
+ if(m_type == Container::HSplitter)
+ ((QSplitter*)w)->setOrientation(QSplitter::Horizontal);
+ else if(m_type == Container::VSplitter)
+ ((QSplitter*)w)->setOrientation(QSplitter::Vertical);
+ else if(tree->container()) {
+ tree->container()->setLayout((Container::LayoutType)m_type);
+ w->resize(tree->container()->layout()->sizeHint()); // the layout doesn't have its own size
+ }
+
+ container->setSelectedWidget(w, false);
+ FormManager::self()->windowChanged(m_form->widget()); // to reload the ObjectTreeView
+}
+
+void
+CreateLayoutCommand::unexecute()
+{
+ ObjectTreeItem *parent = m_form->objectTree()->lookup(m_containername);
+ if(!parent)
+ parent = m_form->objectTree();
+
+ // We reparent every widget to the Container and take them out of the layout
+ QMap<QCString,QRect>::ConstIterator endIt = m_pos.constEnd();
+ for(QMap<QCString,QRect>::ConstIterator it = m_pos.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(it.key());
+ if(item && item->widget())
+ {
+ item->widget()->reparent(parent->widget(), QPoint(0,0), true);
+ item->eventEater()->setContainer(parent->container());
+ if(m_pos[it.key()].isValid())
+ item->widget()->setGeometry(m_pos[it.key()]);
+ m_form->objectTree()->reparent(item->name(), m_containername);
+ }
+ }
+
+ if(!parent->container())
+ return;
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(m_name);
+ if (!titem)
+ return; //better this than a crash
+ QWidget *w = titem->widget();
+ parent->container()->deleteWidget(w); // delete the layout widget
+ FormManager::self()->windowChanged(m_form->widget()); // to reload ObjectTreeView
+}
+
+QString
+CreateLayoutCommand::name() const
+{
+ switch(m_type)
+ {
+ case Container::HBox:
+ return i18n("Group Widgets Horizontally");
+ case Container::VBox:
+ return i18n("Group Widgets Vertically");
+ case Container::Grid:
+ return i18n("Group Widgets in a Grid");
+ case Container::HSplitter:
+ return i18n("Group Widgets Horizontally in a Splitter");
+ case Container::VSplitter:
+ return i18n("Group Widgets Vertically in a Splitter");
+ case Container::HFlow:
+ return i18n("Group Widgets By Rows");
+ case Container::VFlow:
+ return i18n("Group Widgets Vertically By Columns");
+ default:
+ return i18n("Group widgets");
+ }
+}
+
+void
+CreateLayoutCommand::debug()
+{
+ kdDebug() << "CreateLayoutCommand: name=\"" << name() << "\" generatedName=" << m_name
+ << " widgets=" << m_pos.keys() << " container=" << m_containername
+ << " form=" << m_form->widget()->name() << endl;
+}
+
+/// BreakLayoutCommand ///////////////
+
+BreakLayoutCommand::BreakLayoutCommand(Container *container)
+ : CreateLayoutCommand()
+{
+ m_containername = container->toplevel()->widget()->name();
+ m_name = container->widget()->name();
+ m_form = container->form();
+ m_type = container->layoutType();
+
+ for(ObjectTreeItem *tree = container->objectTree()->children()->first(); tree; tree = container->objectTree()->children()->next())
+ {
+ QRect r(container->widget()->mapTo(container->widget()->parentWidget(), tree->widget()->pos()), tree->widget()->size());
+ m_pos.insert(tree->widget()->name(), r);
+ }
+}
+
+void
+BreakLayoutCommand::execute()
+{
+ CreateLayoutCommand::unexecute();
+}
+
+void
+BreakLayoutCommand::unexecute()
+{
+ CreateLayoutCommand::execute();
+}
+
+QString
+BreakLayoutCommand::name() const
+{
+ return i18n("Break Layout: \"%1\"").arg(m_name);
+}
+
+void
+BreakLayoutCommand::debug()
+{
+ kdDebug() << "BreakLayoutCommand: name=\"" << name()
+ << " widgets=" << m_pos.keys() << " container=" << m_containername
+ << " form=" << m_form->widget()->name() << endl;
+}
+
+// PasteWidgetCommand
+
+PasteWidgetCommand::PasteWidgetCommand(QDomDocument &domDoc, Container *container, const QPoint& p)
+ : m_point(p)
+{
+ m_data = domDoc.toCString();
+ m_containername = container->widget()->name();
+ m_form = container->form();
+
+ if(domDoc.namedItem("UI").firstChild().nextSibling().toElement().tagName() != "widget")
+ return;
+
+ QRect boundingRect;
+ for(QDomNode n = domDoc.namedItem("UI").firstChild(); !n.isNull(); n = n.nextSibling()) // more than one widget
+ {
+ if(n.toElement().tagName() != "widget")
+ continue;
+ QDomElement el = n.toElement();
+
+ QDomElement rect;
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if((n.toElement().tagName() == "property") && (n.toElement().attribute("name") == "geometry"))
+ rect = n.firstChild().toElement();
+ }
+
+ QDomElement x = rect.namedItem("x").toElement();
+ QDomElement y = rect.namedItem("y").toElement();
+ QDomElement wi = rect.namedItem("width").toElement();
+ QDomElement h = rect.namedItem("height").toElement();
+
+ int rx = x.text().toInt();
+ int ry = y.text().toInt();
+ int rw = wi.text().toInt();
+ int rh = h.text().toInt();
+ QRect r(rx, ry, rw, rh);
+ boundingRect = boundingRect.unite(r);
+ }
+
+ m_point = m_point - boundingRect.topLeft();
+}
+
+void
+PasteWidgetCommand::execute()
+{
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(m_containername);
+ if (!titem)
+ return; //better this than a crash
+ Container *container = titem->container();
+ QString errMsg;
+ int errLine;
+ int errCol;
+ QDomDocument domDoc("UI");
+ bool parsed = domDoc.setContent(m_data, false, &errMsg, &errLine, &errCol);
+
+ if(!parsed)
+ {
+ kdDebug() << "WidgetWatcher::load(): " << errMsg << endl;
+ kdDebug() << "WidgetWatcher::load(): line: " << errLine << " col: " << errCol << endl;
+ return;
+ }
+
+ //FormIO::setCurrentForm(m_container->form());
+
+ kdDebug() << domDoc.toString() << endl;
+ if(!domDoc.namedItem("UI").hasChildNodes()) // nothing in the doc
+ return;
+ if(domDoc.namedItem("UI").firstChild().nextSibling().toElement().tagName() != "widget") // only one widget, so we can paste it at cursor pos
+ {
+ QDomElement el = domDoc.namedItem("UI").firstChild().toElement();
+ fixNames(el);
+ if(m_point.isNull())
+ fixPos(el, container);
+ else
+ changePos(el, m_point);
+
+ m_form->setInteractiveMode(false);
+ FormIO::loadWidget(container, el);
+ m_form->setInteractiveMode(true);
+ }
+ else for(QDomNode n = domDoc.namedItem("UI").firstChild(); !n.isNull(); n = n.nextSibling()) // more than one widget
+ {
+ if(n.toElement().tagName() != "widget")
+ continue;
+ QDomElement el = n.toElement();
+ fixNames(el);
+ if(!m_point.isNull())
+ moveWidgetBy(el, container, m_point);
+ else {
+ fixPos(el, container);
+ kdDebug() << "jdkjfldfksmfkdfjmqdsklfjdkkfmsqfksdfsm" << endl;
+ }
+
+ m_form->setInteractiveMode(false);
+ FormIO::loadWidget(container, el);
+ m_form->setInteractiveMode(true);
+ }
+
+ //FormIO::setCurrentForm(0);
+ m_names.clear();
+ // We store the names of all the created widgets, to delete them later
+ for(QDomNode n = domDoc.namedItem("UI").firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if(n.toElement().tagName() != "widget")
+ continue;
+ for(QDomNode m = n.firstChild(); !m.isNull(); n = m.nextSibling())
+ {
+ if((m.toElement().tagName() == "property") && (m.toElement().attribute("name") == "name"))
+ {
+ m_names.append(m.toElement().text());
+ break;
+ }
+ }
+ }
+
+ container->form()->selectFormWidget();
+ QStringList::ConstIterator endIt = m_names.constEnd();
+ for(QStringList::ConstIterator it = m_names.constBegin(); it != endIt; ++it) // We select all the pasted widgets
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(*it);
+ if(item)
+ container->setSelectedWidget(item->widget(), true);
+ }
+}
+
+void
+PasteWidgetCommand::unexecute()
+{
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(m_containername);
+ if (!titem)
+ return; //better this than a crash
+ Container *container = titem->container();
+ // We just delete all the widgets we have created
+ QStringList::ConstIterator endIt = m_names.constEnd();
+ for(QStringList::ConstIterator it = m_names.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem* titem = container->form()->objectTree()->lookup(*it);
+ if (!titem)
+ continue; //better this than a crash
+ QWidget *w = titem->widget();
+ container->deleteWidget(w);
+ }
+}
+
+QString
+PasteWidgetCommand::name() const
+{
+ return i18n("Paste");
+}
+
+void
+//QDomElement
+PasteWidgetCommand::changePos(QDomElement &el, const QPoint &newpos)
+{
+ //QDomElement el = widg.cloneNode(true).toElement();
+ QDomElement rect;
+ // Find the widget geometry if there is one
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if((n.toElement().tagName() == "property") && (n.toElement().attribute("name") == "geometry"))
+ rect = n.firstChild().toElement();
+ }
+
+ QDomElement x = rect.namedItem("x").toElement();
+ x.removeChild(x.firstChild());
+ QDomText valueX = el.ownerDocument().createTextNode(QString::number(newpos.x()));
+ x.appendChild(valueX);
+
+ QDomElement y = rect.namedItem("y").toElement();
+ y.removeChild(y.firstChild());
+ QDomText valueY = el.ownerDocument().createTextNode(QString::number(newpos.y()));
+ y.appendChild(valueY);
+
+ //return el;
+}
+
+void
+PasteWidgetCommand::fixPos(QDomElement &el, Container *container)
+{
+/* QDomElement rect;
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if((n.toElement().tagName() == "property") && (n.toElement().attribute("name") == "geometry"))
+ rect = n.firstChild().toElement();
+ }
+
+ QDomElement x = rect.namedItem("x").toElement();
+ QDomElement y = rect.namedItem("y").toElement();
+ QDomElement wi = rect.namedItem("width").toElement();
+ QDomElement h = rect.namedItem("height").toElement();
+
+ int rx = x.text().toInt();
+ int ry = y.text().toInt();
+ int rw = wi.text().toInt();
+ int rh = h.text().toInt();
+ QRect r(rx, ry, rw, rh);
+
+ QWidget *w = m_form->widget()->childAt(r.x() + 6, r.y() + 6, false);
+ if(!w)
+ return;
+
+ while((w->geometry() == r) && (w != 0))// there is already a widget there, with the same size
+ {
+ w = m_form->widget()->childAt(w->x() + 16, w->y() + 16, false);
+ r.moveBy(10,10);
+ }
+
+ // the pasted wigdet should stay inside container's boundaries
+ if(r.x() < 0)
+ r.moveLeft(0);
+ else if(r.right() > container->widget()->width())
+ r.moveLeft(container->widget()->width() - r.width());
+
+ if(r.y() < 0)
+ r.moveTop(0);
+ else if(r.bottom() > container->widget()->height())
+ r.moveTop(container->widget()->height() - r.height());
+
+ if(r != QRect(rx, ry, rw, rh))
+ //return el;
+ //else
+ changePos(el, QPoint(r.x(), r.y()));
+*/
+ moveWidgetBy(el, container, QPoint(0, 0));
+}
+
+void
+PasteWidgetCommand::moveWidgetBy(QDomElement &el, Container *container, const QPoint &p)
+{
+ QDomElement rect;
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if((n.toElement().tagName() == "property") && (n.toElement().attribute("name") == "geometry"))
+ rect = n.firstChild().toElement();
+ }
+
+ QDomElement x = rect.namedItem("x").toElement();
+ QDomElement y = rect.namedItem("y").toElement();
+ QDomElement wi = rect.namedItem("width").toElement();
+ QDomElement h = rect.namedItem("height").toElement();
+
+ int rx = x.text().toInt();
+ int ry = y.text().toInt();
+ int rw = wi.text().toInt();
+ int rh = h.text().toInt();
+ QRect r(rx + p.x(), ry + p.y(), rw, rh);
+ kdDebug() << "Moving widget by " << p << " from " << rx << " " << ry << " to " << r.topLeft() << endl;
+
+ QWidget *w = m_form->widget()->childAt(r.x() + 6, r.y() + 6, false);
+
+ while(w && (w->geometry() == r))// there is already a widget there, with the same size
+ {
+ w = m_form->widget()->childAt(w->x() + 16, w->y() + 16, false);
+ r.moveBy(10,10);
+ }
+
+ // the pasted wigdet should stay inside container's boundaries
+ if(r.x() < 0)
+ r.moveLeft(0);
+ else if(r.right() > container->widget()->width())
+ r.moveLeft(container->widget()->width() - r.width());
+
+ if(r.y() < 0)
+ r.moveTop(0);
+ else if(r.bottom() > container->widget()->height())
+ r.moveTop(container->widget()->height() - r.height());
+
+ if(r != QRect(rx, ry, rw, rh))
+ //return el;
+ //else
+ changePos(el, QPoint(r.x(), r.y()));
+}
+
+void
+PasteWidgetCommand::fixNames(QDomElement &el)
+{
+ QString wname;
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if((n.toElement().tagName() == "property") && (n.toElement().attribute("name") == "name"))
+ {
+ wname = n.toElement().text();
+ while(m_form->objectTree()->lookup(wname)) // name already exists
+ {
+ bool ok;
+ int num = wname.right(1).toInt(&ok, 10);
+ if(ok)
+ wname = wname.left(wname.length()-1) + QString::number(num+1);
+ else
+ wname += "2";
+ }
+ if(wname != n.toElement().text()) // we change the name, so we recreate the element
+ {
+ n.removeChild(n.firstChild());
+ QDomElement type = el.ownerDocument().createElement("string");
+ QDomText valueE = el.ownerDocument().createTextNode(wname);
+ type.appendChild(valueE);
+ n.toElement().appendChild(type);
+ }
+
+ }
+ if(n.toElement().tagName() == "widget") // fix child widgets names
+ {
+ QDomElement child = n.toElement();
+ fixNames(child);
+ }
+ }
+}
+
+void
+PasteWidgetCommand::debug()
+{
+ kdDebug() << "PasteWidgetCommand: pos=" << m_point
+ << " widgets=" << m_names << " container=" << m_containername
+ << " form=" << m_form->widget()->name()
+ << " data=\"" << m_data.left(80) << "...\"" << endl;
+}
+
+// DeleteWidgetCommand
+
+DeleteWidgetCommand::DeleteWidgetCommand(WidgetList &list, Form *form)
+ : Command(), m_form(form)
+{
+ m_domDoc = QDomDocument("UI");
+ m_domDoc.appendChild(m_domDoc.createElement("UI"));
+
+ QDomElement parent = m_domDoc.namedItem("UI").toElement();
+
+ //for(QWidget *w = list.first(); w; w = list.next())
+ /*for(WidgetListIterator it(list); it.current() != 0; ++it)
+ {
+ QWidget *w = it.current();
+ // Don't delete tabwidget or widgetstack pages
+ if(w->parentWidget()->inherits("QWidgetStack"))
+ {
+ list.remove(w);
+ continue;
+ }
+ }*/
+ removeChildrenFromList(list);
+
+ for(WidgetListIterator it(list); it.current() != 0; ++it)
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(it.current()->name());
+ if (!item)
+ return;
+
+ // We need to store both parentContainer and parentWidget as they may be different (eg for TabWidget page)
+ m_containers.insert(item->name().latin1(), m_form->parentContainer(item->widget())->widget()->name());
+ m_parents.insert(item->name().latin1(), item->parent()->name().latin1());
+ FormIO::saveWidget(item, parent, m_domDoc);
+ form->connectionBuffer()->saveAllConnectionsForWidget(item->widget()->name(), m_domDoc);
+ }
+
+ FormIO::cleanClipboard(parent);
+}
+
+void
+DeleteWidgetCommand::execute()
+{
+ Container *containerToSelect = 0;
+
+ QMap<QCString,QCString>::ConstIterator endIt = m_containers.constEnd();
+ for(QMap<QCString,QCString>::ConstIterator it = m_containers.constBegin(); it != endIt; ++it)
+ {
+ ObjectTreeItem *item = m_form->objectTree()->lookup(it.key());
+ if (!item || !item->widget())
+ continue;
+
+ Container *cont = m_form->parentContainer(item->widget());
+ if (!containerToSelect)
+ containerToSelect = cont;
+ cont->deleteWidget(item->widget());
+ }
+ //now we've nothing selecte: select parent container
+ if (containerToSelect)
+ m_form->setSelectedWidget( containerToSelect->widget() );
+}
+
+void
+DeleteWidgetCommand::unexecute()
+{
+ QCString wname;
+ m_form->setInteractiveMode(false);
+ for(QDomNode n = m_domDoc.namedItem("UI").firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if(n.toElement().tagName() == "connections") // restore the widget connections
+ m_form->connectionBuffer()->load(n);
+ if(n.toElement().tagName() != "widget")
+ continue;
+ // We need first to know the name of the widget
+ for(QDomNode m = n.firstChild(); !m.isNull(); n = m.nextSibling())
+ {
+ if((m.toElement().tagName() == "property") && (m.toElement().attribute("name") == "name"))
+ {
+ wname = m.toElement().text().latin1();
+ break;
+ }
+ }
+
+ ObjectTreeItem* titem = m_form->objectTree()->lookup(m_containers[wname]);
+ if (!titem)
+ return; //better this than a crash
+ Container *cont = titem->container();
+ ObjectTreeItem *parent = m_form->objectTree()->lookup(m_parents[wname]);
+ QDomElement widg = n.toElement();
+ if(parent)
+ FormIO::loadWidget(cont, widg, parent->widget());
+ else
+ FormIO::loadWidget(cont, widg);
+ }
+ m_form->setInteractiveMode(true);
+}
+
+QString
+DeleteWidgetCommand::name() const
+{
+ return i18n("Delete widget");
+}
+
+void
+DeleteWidgetCommand::debug()
+{
+ kdDebug() << "DeleteWidgetCommand: containers=" << m_containers.keys()
+ << " parents=" << m_parents.keys() << " form=" << m_form->widget()->name() << endl;
+}
+
+// CutWidgetCommand
+
+CutWidgetCommand::CutWidgetCommand(WidgetList &list, Form *form)
+ : DeleteWidgetCommand(list, form)
+{}
+
+void
+CutWidgetCommand::execute()
+{
+ DeleteWidgetCommand::execute();
+ m_data = FormManager::self()->m_domDoc.toCString();
+ FormManager::self()->m_domDoc.setContent(m_domDoc.toCString());
+}
+
+void
+CutWidgetCommand::unexecute()
+{
+ DeleteWidgetCommand::unexecute();
+ FormManager::self()->m_domDoc.setContent(m_data);
+}
+
+QString
+CutWidgetCommand::name() const
+{
+ return i18n("Cut");
+}
+
+void
+CutWidgetCommand::debug()
+{
+ kdDebug() << "CutWidgetCommand: containers=" << m_containers.keys()
+ << " parents=" << m_parents.keys() << " form=" << m_form->widget()->name()
+ << " data=\"" << m_data.left(80) << "...\"" << endl;
+}
+
+// CommandGroup
+
+namespace KFormDesigner {
+class CommandGroup::SubCommands : public KMacroCommand
+{
+ public:
+ SubCommands( const QString & name )
+ : KMacroCommand(name)
+ {
+ }
+ const QPtrList<KCommand>& commands() const { return m_commands; }
+};
+}
+
+CommandGroup::CommandGroup( const QString & name, WidgetPropertySet *propSet )
+ : Command()
+ , m_subCommands(new SubCommands(name))
+ , m_propSet(propSet)
+{
+}
+
+CommandGroup::~CommandGroup()
+{
+ delete m_subCommands;
+}
+
+const QPtrList<KCommand>& CommandGroup::commands() const
+{
+ return m_subCommands->commands();
+}
+
+void CommandGroup::addCommand(KCommand *command, bool allowExecute)
+{
+ if (!command)
+ return;
+
+ m_subCommands->addCommand(command);
+ if (!allowExecute)
+ m_commandsShouldntBeExecuted.insert(command, (char*)1);
+}
+
+void CommandGroup::execute()
+{
+ FormManager::self()->blockPropertyEditorUpdating(this);
+ for (QPtrListIterator<KCommand> it(m_subCommands->commands()); it.current(); ++it) {
+ if (!m_commandsShouldntBeExecuted[it.current()])
+ it.current()->execute();
+ }
+ FormManager::self()->unblockPropertyEditorUpdating(this, m_propSet);
+}
+
+void CommandGroup::unexecute()
+{
+ FormManager::self()->blockPropertyEditorUpdating(this);
+ m_subCommands->unexecute();
+ FormManager::self()->unblockPropertyEditorUpdating(this, m_propSet);
+}
+
+QString CommandGroup::name() const
+{
+ return m_subCommands->name();
+}
+
+void CommandGroup::resetAllowExecuteFlags()
+{
+ m_commandsShouldntBeExecuted.clear();
+}
+
+void
+CommandGroup::debug()
+{
+ kdDebug() << "*CommandGroup: name=\"" << name() << "\" #=" << m_subCommands->commands().count() << endl;
+ uint i = 1;
+ for (QPtrListIterator<KCommand> it(m_subCommands->commands()); it.current(); ++it, i++) {
+ kdDebug() << "#" << i << ":"
+ << (m_commandsShouldntBeExecuted[it.current()] ? "!" : "") << "allowExecute:" << endl;
+ if (dynamic_cast<Command*>(it.current()))
+ dynamic_cast<Command*>(it.current())->debug();
+ else if (dynamic_cast<CommandGroup*>(it.current()))
+ dynamic_cast<CommandGroup*>(it.current())->debug();
+ else
+ kdDebug() << "(other KCommand)" << endl;
+ }
+ kdDebug() << "End of CommandGroup" << endl;
+}
diff --git a/kexi/formeditor/commands.h b/kexi/formeditor/commands.h
new file mode 100644
index 000000000..619ef791e
--- /dev/null
+++ b/kexi/formeditor/commands.h
@@ -0,0 +1,380 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMEDITOR_COMMANDS_H
+#define KFORMEDITOR_COMMANDS_H
+
+#include <qmap.h>
+#include <qdict.h>
+#include <qptrlist.h>
+#include <qptrdict.h>
+#include <qvariant.h>
+#include <qdom.h>
+
+#include <kcommand.h>
+#include "utils.h"
+
+class QWidget;
+class QRect;
+class QPoint;
+class QStringList;
+class QCString;
+
+namespace KFormDesigner {
+
+class WidgetPropertySet;
+class ObjectTreeItem;
+class Container;
+class Form;
+
+//! Base class for KFormDesigner's commands
+class KFORMEDITOR_EXPORT Command : public KCommand
+{
+ public:
+ Command();
+ virtual ~Command();
+
+ virtual void debug() = 0;
+};
+
+/*! This command is used when changing a property for one or more widgets. \a oldvalues is a QMap
+ of the old values of the property for every widget, to allow reverting the change. \a value is
+ the new value of the property. You can use the simpler constructor for a single widget.
+ */
+class KFORMEDITOR_EXPORT PropertyCommand : public Command
+{
+ public:
+ PropertyCommand(WidgetPropertySet *set, const QCString &wname, const QVariant &oldValue,
+ const QVariant &value, const QCString &property);
+ PropertyCommand(WidgetPropertySet *set, const QMap<QCString, QVariant> &oldvalues,
+ const QVariant &value, const QCString &property);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ QCString property() const { return m_property; }
+
+ void setValue(const QVariant &value);
+ const QMap<QCString, QVariant>& oldValues() const { return m_oldvalues; }
+ virtual void debug();
+
+ protected:
+ WidgetPropertySet *m_propSet;
+ QVariant m_value;
+ QMap<QCString, QVariant> m_oldvalues;
+ QCString m_property;
+};
+
+/*! This command is used when moving multiples widgets at the same time, while holding Ctrl or Shift.
+ You need to supply a list of widget names, and the position of the cursor before moving. Use setPos()
+ to tell the new cursor pos every time it changes.*/
+class KFORMEDITOR_EXPORT GeometryPropertyCommand : public Command
+{
+ public:
+ GeometryPropertyCommand(WidgetPropertySet *set, const QStringList &names, const QPoint& oldPos);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ void setPos(const QPoint& pos);
+ virtual void debug();
+
+ protected:
+ WidgetPropertySet *m_propSet;
+ QStringList m_names;
+ QPoint m_oldPos;
+ QPoint m_pos;
+};
+
+/*! This command is used when an item in 'Align Widgets position' is selected. You just need
+to give the list of widget names (the selected ones), and the
+ type of alignment (see the enum for possible values). */
+class KFORMEDITOR_EXPORT AlignWidgetsCommand : public Command
+{
+ public:
+ enum { AlignToGrid = 100, AlignToLeft, AlignToRight, AlignToTop, AlignToBottom };
+
+ AlignWidgetsCommand(int type, WidgetList &list, Form *form);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+
+ protected:
+ Form *m_form;
+ int m_type;
+ QMap<QCString, QPoint> m_pos;
+};
+
+/*! This command is used when an item in 'Adjust Widgets Size' is selected. You just need
+ to give the list of widget names (the selected ones), and the
+ type of size modification (see the enum for possible values). */
+class KFORMEDITOR_EXPORT AdjustSizeCommand : public Command
+{
+ public:
+ enum { SizeToGrid = 200, SizeToFit, SizeToSmallWidth, SizeToBigWidth,
+ SizeToSmallHeight, SizeToBigHeight };
+
+ AdjustSizeCommand(int type, WidgetList &list, Form *form);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+
+ protected:
+ QSize getSizeFromChildren(ObjectTreeItem *item);
+
+ protected:
+ Form *m_form;
+ int m_type;
+ QMap<QCString, QPoint> m_pos;
+ QMap<QCString, QSize> m_sizes;
+};
+
+/*! This command is used when switching the layout of a Container. It remembers the old pos
+ of every widget inside the Container. */
+class KFORMEDITOR_EXPORT LayoutPropertyCommand : public PropertyCommand
+{
+ public:
+ LayoutPropertyCommand(WidgetPropertySet *set, const QCString &wname,
+ const QVariant &oldValue, const QVariant &value);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+
+ protected:
+ Form *m_form;
+ QMap<QCString,QRect> m_geometries;
+};
+
+/*! This command is used when inserting a widger using toolbar or menu. You only need to give
+the parent Container and the widget pos.
+ The other information is taken from FormManager. */
+class KFORMEDITOR_EXPORT InsertWidgetCommand : public Command
+{
+ public:
+ InsertWidgetCommand(Container *container);
+
+ /*! This ctor allows to set explicit class name and position.
+ Used for dropping widgets on the form surface.
+ If \a namePrefix is empty, widget's unique name is constructed using
+ hint for \a className (WidgetLibrary::namePrefix()),
+ otherwise, \a namePrefix is used to generate widget's name.
+ This allows e.g. inserting a widgets having name constructed using
+ */
+ InsertWidgetCommand(Container *container, const QCString& className,
+ const QPoint& pos, const QCString& namePrefix = QCString());
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+
+ //! \return inserted widget's name
+ QCString widgetName() const { return m_name; }
+
+ protected:
+ Form *m_form;
+ QString m_containername;
+ QPoint m_point;
+ QCString m_name;
+ QCString m_class;
+ QRect m_insertRect;
+};
+
+/*! This command is used when creating a layout from some widgets using "Lay out in..." menu item.
+ It remembers the old pos of every widget, and takes care of updating ObjectTree too. You need
+ to supply a WidgetList of the selected widgets. */
+class KFORMEDITOR_EXPORT CreateLayoutCommand : public Command
+{
+ public:
+ CreateLayoutCommand(int layoutType, WidgetList &list, Form *form);
+ CreateLayoutCommand() {;} // for BreakLayoutCommand
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+
+ protected:
+ Form *m_form;
+ QString m_containername;
+ QString m_name;
+ QMap<QCString,QRect> m_pos;
+ int m_type;
+};
+
+/*! This command is used when the 'Break Layout' menu item is selected. It does exactly the
+ opposite of CreateLayoutCommand. */
+class KFORMEDITOR_EXPORT BreakLayoutCommand : public CreateLayoutCommand
+{
+ public:
+ BreakLayoutCommand(Container *container);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+};
+
+/*! This command is used when pasting widgets. You need to give the QDomDocument containing
+the widget(s) to paste, and optionnally the point where to paste widgets. */
+class KFORMEDITOR_EXPORT PasteWidgetCommand : public Command
+{
+ public:
+ PasteWidgetCommand(QDomDocument &domDoc, Container *container, const QPoint& p = QPoint());
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+
+ protected:
+ /*! Internal function used to change the coordinates of a widget to \a newpos
+ before pasting it (to paste it at the position of the contextual menu). It modifies
+ the "geometry" property of the QDomElement representing the widget. */
+ void changePos(QDomElement &widg, const QPoint &newpos);
+ /*! Internal function used to fix the coordinates of a widget before pasting it
+ (to avoid having two widgets at the same position). It moves the widget by
+ (10, 10) increment (several times if there are already pasted widgets at this position). */
+ void fixPos(QDomElement &el, Container *container);
+ void moveWidgetBy(QDomElement &el, Container *container, const QPoint &p);
+ /*! Internal function used to fix the names of the widgets before pasting them.
+ It prevents from pasting a widget with
+ the same name as an actual widget. The child widgets are also fixed recursively.\n
+ If the name of the widget ends with a number (eg "QLineEdit1"), the new name is
+ just incremented by one (eg becomes "QLineEdit2"). Otherwise, a "2" is just
+ appended at the end of the name (eg "myWidget" becomes "myWidget2"). */
+ void fixNames(QDomElement &el);
+
+ protected:
+ Form *m_form;
+ QCString m_data;
+ QString m_containername;
+ QPoint m_point;
+ QStringList m_names;
+};
+
+/*! This command is used when deleting a widget using the "Delete" menu item.
+You need to give a WidgetList of the selected widgets. */
+class KFORMEDITOR_EXPORT DeleteWidgetCommand : public Command
+{
+ public:
+ DeleteWidgetCommand(WidgetList &list, Form *form);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+
+ protected:
+ QDomDocument m_domDoc;
+ Form *m_form;
+ QMap<QCString, QCString> m_containers;
+ QMap<QCString, QCString> m_parents;
+};
+
+/*! This command is used when cutting widgets. It is basically a DeleteWidgetCommand
+which also updates the clipboard contents. */
+class KFORMEDITOR_EXPORT CutWidgetCommand : public DeleteWidgetCommand
+{
+ public:
+ CutWidgetCommand(WidgetList &list, Form *form);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+ virtual void debug();
+
+ protected:
+ QCString m_data;
+};
+
+/*! A Command Group is a command that holds several subcommands.
+ It will appear as one to the user and in the command history,
+ but it can use the implementation of multiple commands internally.
+ It extends KMacroCommand by providing the list of commands executed.
+ Selected subcommands can be marked as nonexecutable by adding them using
+ addCommand(KCommand *command, bool allowExecute) special method.
+*/
+class KFORMEDITOR_EXPORT CommandGroup : public Command
+{
+ public:
+ CommandGroup( const QString & name, WidgetPropertySet *propSet );
+ virtual ~CommandGroup();
+
+ /*! Like KmacroCommand::addCommand(KCommand*)
+ but if \a allowExecute is false, \a command will not be executed
+ as a subcommand when CommandGroup::execute() is called.
+
+ This is useful e.g. in KexiFormView::insertAutoFields(),
+ where a number of subcommands of InsertWidgetCommand type and subcommands
+ is groupped using CommandGroup but some of these subcommands are executed
+ before executing CommandGroup::execute().
+
+ If \a allowExecute is true, this method behaves exactly like
+ KmacroCommand::addCommand(KCommand*).
+
+ Note that unexecute() doesn't check \a allowExecute flag:
+ all subcommands will be unexecuted (in reverse order
+ to the one in which they were added).
+ */
+ void addCommand(KCommand *command, bool allowExecute);
+
+ /*! Executes all subcommands added to this group
+ in the same order as they were added. Subcommands added with
+ addCommand(KCommand *command, bool allowExecute) where allowExecute == false,
+ will not be executed. */
+ virtual void execute();
+
+ /*! Unexecutes all subcommands added to this group,
+ (in reversed order). */
+ virtual void unexecute();
+
+ virtual QString name() const;
+
+ /*! \return a list of all subcommands of this group.
+ Note that if a given subcommand is a group itself,
+ it will not be expanded to subcommands on this list. */
+ const QPtrList<KCommand>& commands() const;
+
+ /*! Resets all 'allowExecute' flags that was set in addCommand().
+ Call this after calling CommandGroup::execute() to ensure that
+ in the future, when REDO is be executed, all subcommands will be executed. */
+ void resetAllowExecuteFlags();
+
+ virtual void debug();
+
+ protected:
+ class SubCommands;
+ SubCommands *m_subCommands;
+ //! Used to store pointers to subcommands that shouldn't be executed
+ //! on CommandGroup::execute()
+ QPtrDict<char> m_commandsShouldntBeExecuted;
+ WidgetPropertySet *m_propSet;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/connectiondialog.cpp b/kexi/formeditor/connectiondialog.cpp
new file mode 100644
index 000000000..a40348e7a
--- /dev/null
+++ b/kexi/formeditor/connectiondialog.cpp
@@ -0,0 +1,420 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qregexp.h>
+#include <qmetaobject.h>
+#include <qstrlist.h>
+
+#include <kpushbutton.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include "kexitableview.h"
+#include "kexitableviewdata.h"
+#include "events.h"
+#include "form.h"
+#include "formmanager.h"
+#include "objecttree.h"
+
+#include "connectiondialog.h"
+
+namespace KFormDesigner {
+
+/////////////////////////////////////////////////////////////////////////////////
+///////////// The dialog to edit or add/remove connections //////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+ConnectionDialog::ConnectionDialog(QWidget *parent)
+: KDialogBase(parent, "connections_dialog", true, i18n("Edit Form Connections"),
+ Ok|Cancel|Details, Ok, false)
+, m_buffer(0)
+{
+ QFrame *frame = makeMainWidget();
+ QHBoxLayout *layout = new QHBoxLayout(frame, 0, 6);
+
+ // Setup the details widget /////////
+ QHBox *details = new QHBox(frame);
+ setDetailsWidget(details);
+ setDetails(true);
+
+ m_pixmapLabel = new QLabel(details);
+ m_pixmapLabel->setFixedWidth( int(IconSize(KIcon::Desktop) * 1.5) );
+ m_pixmapLabel->setAlignment(AlignHCenter | AlignTop);
+
+ m_textLabel = new QLabel(details);
+ m_textLabel->setAlignment(AlignLeft | AlignTop);
+ //setStatusOk();
+
+ // And the KexiTableView ////////
+ m_data = new KexiTableViewData();
+ m_table = new KexiTableView(0, frame, "connections_tableview");
+ m_table->setSpreadSheetMode();
+ m_table->setInsertingEnabled(true);
+ initTable();
+ m_table->setData(m_data, false);
+ m_table->adjustColumnWidthToContents(0);
+ layout->addWidget(m_table);
+
+ //// Setup the icon toolbar /////////////////
+ QVBoxLayout *vlayout = new QVBoxLayout(layout, 3);
+ KPushButton *newItem = new KPushButton(SmallIconSet("filenew"), i18n("&New Connection"), frame);
+ vlayout->addWidget(newItem);
+ m_buttons.insert(BAdd, newItem);
+ connect(newItem, SIGNAL(clicked()), this, SLOT(newItem()));
+
+ KPushButton *delItem = new KPushButton(SmallIconSet("editdelete"), i18n("&Remove Connection"), frame);
+ vlayout->addWidget(delItem);
+ m_buttons.insert(BRemove, delItem);
+ connect(delItem, SIGNAL(clicked()), this, SLOT(removeItem()));
+
+ vlayout->addStretch();
+
+ setInitialSize(QSize(600, 300));
+ //setWFlags(WDestructiveClose);
+
+ connect(m_table,SIGNAL(cellSelected(int, int)), this, SLOT(slotCellSelected(int, int)));
+ connect(m_table->data(), SIGNAL(rowInserted(KexiTableItem*,bool)), this, SLOT(slotRowInserted(KexiTableItem*,bool)));
+ this->newItem();
+}
+
+void
+ConnectionDialog::initTable()
+{
+ KexiTableViewColumn *col0 = new KexiTableViewColumn(i18n("OK?"), KexiDB::Field::Text);
+ col0->field()->setSubType("KIcon");
+ col0->setReadOnly(true);
+ col0->field()->setDescription(i18n("Connection correctness"));
+ m_data->addColumn(col0);
+
+ KexiTableViewColumn *col1 = new KexiTableViewColumn(i18n("Sender"), KexiDB::Field::Enum);
+ m_widgetsColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text);
+ col1->setRelatedData( m_widgetsColumnData );
+ m_data->addColumn(col1);
+
+ KexiTableViewColumn *col2 = new KexiTableViewColumn(i18n("Signal"), KexiDB::Field::Enum);
+ m_signalsColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text);
+ col2->setRelatedData( m_signalsColumnData );
+ m_data->addColumn(col2);
+
+ KexiTableViewColumn *col3 = new KexiTableViewColumn(i18n("Receiver"), KexiDB::Field::Enum);
+ col3->setRelatedData( m_widgetsColumnData );
+ m_data->addColumn(col3);
+
+ KexiTableViewColumn *col4 = new KexiTableViewColumn(i18n("Slot"), KexiDB::Field::Enum);
+ m_slotsColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text);
+ col4->setRelatedData( m_slotsColumnData );
+ m_data->addColumn(col4);
+
+ QValueList<int> c;
+ c << 2 << 4;
+ m_table->maximizeColumnsWidth(c);
+ m_table->setColumnStretchEnabled( true, 4 );
+
+ connect(m_data, SIGNAL(aboutToChangeCell(KexiTableItem*, int, QVariant&, KexiDB::ResultInfo*)),
+ this,SLOT(slotCellChanged(KexiTableItem*, int, QVariant, KexiDB::ResultInfo*)));
+ connect(m_data, SIGNAL(rowUpdated(KexiTableItem*)), this, SLOT(checkConnection(KexiTableItem *)));
+ connect(m_table, SIGNAL(itemSelected(KexiTableItem *)), this, SLOT(checkConnection(KexiTableItem *)));
+}
+
+void
+ConnectionDialog::exec(Form *form)
+{
+ m_form = form;
+ updateTableData();
+
+ KDialogBase::exec();
+}
+
+void ConnectionDialog::slotCellSelected(int col, int row)
+{
+ m_buttons[BRemove]->setEnabled( row < m_table->rows() );
+ KexiTableItem *item = m_table->itemAt(row);
+ if (!item)
+ return;
+ if(col == 2) // signal col
+ updateSignalList(item);
+ else if(col == 4) // slot col
+ updateSlotList(item);
+}
+
+void ConnectionDialog::slotRowInserted(KexiTableItem* item,bool)
+{
+ m_buffer->append(new Connection());
+ checkConnection( item );
+}
+
+void
+ConnectionDialog::slotOk()
+{
+ // First we update our buffer contents
+ for(int i=0; i < m_table->rows(); i++)
+ {
+ KexiTableItem *item = m_table->itemAt(i);
+ Connection *c = m_buffer->at(i);
+
+ c->setSender( (*item)[1].toString() );
+ c->setSignal( (*item)[2].toString() );
+ c->setReceiver( (*item)[3].toString() );
+ c->setSlot( (*item)[4].toString() );
+ }
+
+ // then me make it replace form's current one
+ delete m_form->connectionBuffer();
+ m_form->setConnectionBuffer(m_buffer);
+
+ QDialog::accept();
+}
+
+void
+ConnectionDialog::updateTableData()
+{
+ // First we update the columns data
+ ObjectTreeDict *dict = new ObjectTreeDict( *(m_form->objectTree()->dict()) );
+ ObjectTreeDictIterator it(*dict);
+ for(; it.current(); ++it)
+ {
+ KexiTableItem *item = m_widgetsColumnData->createItem(); //new KexiTableItem(2);
+ (*item)[0] = it.current()->name();
+ (*item)[1] = (*item)[0];
+ m_widgetsColumnData->append(item);
+ }
+ delete dict;
+
+ // Then we fill the columns with the form connections
+ for(Connection *c = m_form->connectionBuffer()->first(); c ; c = m_form->connectionBuffer()->next())
+ {
+ KexiTableItem *item = m_table->data()->createItem(); //new KexiTableItem(5);
+ (*item)[1] = c->sender();
+ (*item)[2] = c->signal();
+ (*item)[3] = c->receiver();
+ (*item)[4] = c->slot();
+ m_table->insertItem(item, m_table->rows());
+ }
+
+ m_buffer = new ConnectionBuffer(*(m_form->connectionBuffer()));
+}
+
+void
+ConnectionDialog::setStatusOk(KexiTableItem *item)
+{
+ m_pixmapLabel->setPixmap( DesktopIcon("button_ok") );
+ m_textLabel->setText("<qt><h2>The connection is OK.</h2></qt>");
+
+ if (!item)
+ item = m_table->selectedItem();
+ if (m_table->currentRow() >= m_table->rows())
+ item = 0;
+
+ if (item)
+ (*item)[0] = "button_ok";
+ else {
+ m_pixmapLabel->setPixmap( QPixmap() );
+ m_textLabel->setText(QString::null);
+ }
+}
+
+void
+ConnectionDialog::setStatusError(const QString &msg, KexiTableItem *item)
+{
+ m_pixmapLabel->setPixmap( DesktopIcon("button_cancel") );
+ m_textLabel->setText("<qt><h2>The connection is invalid.</h2></qt>" + msg);
+
+ if (!item)
+ item = m_table->selectedItem();
+ if (m_table->currentRow() >= m_table->rows())
+ item = 0;
+
+ if (item)
+ (*item)[0] = "button_cancel";
+ else {
+ m_pixmapLabel->setPixmap( QPixmap() );
+ m_textLabel->setText(QString::null);
+ }
+}
+
+void
+ConnectionDialog::slotCellChanged(KexiTableItem *item, int col, QVariant&, KexiDB::ResultInfo*)
+{
+ switch(col)
+ {
+ // sender changed, we clear siganl and slot
+ case 1:
+ (*item)[2] = QString("");
+ // signal or receiver changed, we clear the slot cell
+ case 2:
+ case 3:
+ {
+ (*item)[4] = QString("");
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void
+ConnectionDialog::updateSlotList(KexiTableItem *item)
+{
+ m_slotsColumnData->deleteAllRows();
+ QString widget = (*item)[1].toString();
+ QString signal = (*item)[2].toString();
+
+ if((widget.isEmpty()) || signal.isEmpty())
+ return;
+ ObjectTreeItem *tree = m_form->objectTree()->lookup(widget);
+ if(!tree || !tree->widget())
+ return;
+
+ QString signalArg(signal);
+ signalArg = signalArg.remove( QRegExp(".*[(]|[)]") );
+
+ QStrList slotList = tree->widget()->metaObject()->slotNames(true);
+ QStrListIterator it(slotList);
+ for(; it.current() != 0; ++it)
+ {
+ // we add the slot only if it is compatible with the signal
+ QString slotArg(*it);
+ slotArg = slotArg.remove( QRegExp(".*[(]|[)]") );
+
+ if(!signalArg.startsWith(slotArg, true) && (!signal.isEmpty())) // args not compatible
+ continue;
+
+ KexiTableItem *item = m_slotsColumnData->createItem(); //new KexiTableItem(2);
+ (*item)[0] = QString(*it);
+ (*item)[1] = (*item)[0];
+ m_slotsColumnData->append(item);
+ }
+}
+
+void
+ConnectionDialog::updateSignalList(KexiTableItem *item)
+{
+ ObjectTreeItem *tree = m_form->objectTree()->lookup((*item)[1].toString());
+ if(!tree || !tree->widget())
+ return;
+
+ m_signalsColumnData->deleteAllRows();
+ QStrList signalList = tree->widget()->metaObject()->signalNames(true);
+ QStrListIterator it(signalList);
+ for(; it.current() != 0; ++it)
+ {
+ KexiTableItem *item = m_signalsColumnData->createItem(); //new KexiTableItem(2);
+ (*item)[0] = QString(*it);
+ (*item)[1] = (*item)[0];
+ m_signalsColumnData->append(item);
+ }
+}
+
+void
+ConnectionDialog::checkConnection(KexiTableItem *item)
+{
+ // First we check if one column is empty
+ for(int i = 1; i < 5; i++)
+ {
+ if( !item || (*item)[i].toString().isEmpty())
+ {
+ setStatusError( i18n("<qt>You have not selected item: <b>%1</b>.</qt>").arg(m_data->column(i)->captionAliasOrName()),
+ item);
+ return;
+ }
+ }
+
+ // Then we check if signal/slot args are compatible
+ QString signal = (*item)[2].toString();
+ signal = signal.remove( QRegExp(".*[(]|[)]") ); // just keep the args list
+ QString slot = (*item)[4].toString();
+ slot = slot.remove( QRegExp(".*[(]|[)]") );
+
+ if(!signal.startsWith(slot, true))
+ {
+ setStatusError( i18n("The signal/slot arguments are not compatible."), item);
+ return;
+ }
+
+ setStatusOk(item);
+}
+
+void
+ConnectionDialog::newItem()
+{
+ m_table->acceptRowEdit();
+ m_table->setCursorPosition(m_table->rows(), 1);
+}
+
+void
+ConnectionDialog::newItemByDragnDrop()
+{
+ KFormDesigner::FormManager::self()->startCreatingConnection();
+ connect(KFormDesigner::FormManager::self(), SIGNAL(connectionAborted(KFormDesigner::Form*)), this, SLOT(slotConnectionAborted(KFormDesigner::Form*)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(connectionCreated(KFormDesigner::Form*, Connection&)), this, SLOT(slotConnectionCreated(KFormDesigner::Form*, Connection&)) );
+
+ hide();
+}
+
+void
+ConnectionDialog::slotConnectionCreated(KFormDesigner::Form *form, Connection &connection)
+{
+ show();
+ if(form != m_form)
+ return;
+
+ Connection *c = new Connection(connection);
+ KexiTableItem *item = m_table->data()->createItem(); //new KexiTableItem(5);
+ (*item)[1] = c->sender();
+ (*item)[2] = c->signal();
+ (*item)[3] = c->receiver();
+ (*item)[4] = c->slot();
+ m_table->insertItem(item, m_table->rows());
+ m_buffer->append(c);
+}
+
+void
+ConnectionDialog::slotConnectionAborted(KFormDesigner::Form *form)
+{
+ show();
+ if(form != m_form)
+ return;
+
+ newItem();
+}
+
+void
+ConnectionDialog::removeItem()
+{
+ if(m_table->currentRow() == -1 || m_table->currentRow()>=m_table->rows())
+ return;
+
+ int confirm = KMessageBox::warningContinueCancel(parentWidget(),
+ QString("<qt>")+i18n("Do you want to delete this connection ?")+"</qt>", QString::null, KGuiItem(i18n("&Delete Connection")),
+ "dontAskBeforeDeleteConnection"/*config entry*/);
+ if(confirm == KMessageBox::Cancel)
+ return;
+
+ m_buffer->remove(m_table->currentRow());
+ m_table->deleteItem(m_table->selectedItem());
+}
+
+}
+
+#include "connectiondialog.moc"
diff --git a/kexi/formeditor/connectiondialog.h b/kexi/formeditor/connectiondialog.h
new file mode 100644
index 000000000..3e6ac07b0
--- /dev/null
+++ b/kexi/formeditor/connectiondialog.h
@@ -0,0 +1,114 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef FORMCONNECTIONDIALOG_H
+#define FORMCONNECTIONDIALOG_H
+
+#include <qintdict.h>
+#include <kdialogbase.h>
+
+namespace KexiDB {
+ class ResultInfo;
+}
+
+class QLabel;
+class QButton;
+class KexiTableView;
+class KexiTableViewData;
+class KexiTableItem;
+
+namespace KFormDesigner {
+
+class Form;
+class ConnectionBuffer;
+class Connection;
+
+
+/*! This dialog is used to edit the connections of a form. It uses KexiTableView for this. There is also a details widget (icon + text)) that shows correctness
+ of current connection. */
+class KFORMEDITOR_EXPORT ConnectionDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ ConnectionDialog(QWidget *parent);
+ ~ConnectionDialog() {;}
+
+ /*! Displays as modal dialog, to edit connections in Form::connectionBuffer(). */
+ void exec(Form *form);
+
+ protected:
+ /*! Used when connection is ok. Displays a message in details widget and changes icon in 'OK?' column. */
+ void setStatusOk(KexiTableItem *item = 0);
+ /*! Used when connection is wrong. Displays a message in details widget and changes icon in 'OK?' column. \a msg is
+ the message explaining what's wrong. */
+ void setStatusError(const QString &msg, KexiTableItem *item = 0);
+ //! Inits table data, columns, etc.
+ void initTable();
+ /*! Updates the widget list (shown in receiver and sender columns). Then fill in the table with the connections in m_buffer. */
+ void updateTableData();
+ /*! Updates the slot list, according to the receiver name, and only shows those who are compatible with signal args. */
+ void updateSlotList(KexiTableItem *item);
+ //! Updates the signal list, according to the sender name.
+ void updateSignalList(KexiTableItem *item);
+
+ protected slots:
+ /*! Slot called when the user modifies a cell. Signal and/or slot cells are cleared if necessary (not valid anymore). */
+ void slotCellChanged(KexiTableItem*, int, QVariant&, KexiDB::ResultInfo*);
+ /*! This function checks if the connection represented by KexiTableItem \a item is valid. It checks if all args (sender, receiver, signal and slot)
+ are given, and then if signal/slot args are compatible (should be always true, as we don't show non-compatible slots). It calls \ref setStatusOk()
+ or \ref setStatusError() following the result of checks. */
+ void checkConnection(KexiTableItem *item);
+
+ /*! Hides the dialog and allow the user to create the Connection by drag-and-drop in the Form itself. Currently disabled in the GUI.
+ \sa FormManager::startCreatingConnection() */
+ void newItemByDragnDrop();
+ /*! Creates a new item. It just moves the cursor to the last empty row. */
+ void newItem();
+ void removeItem();
+
+ /*! This slot is called when the user ends connection creation (when in drag-and-drop mode). The dialog is restored,
+ and the created connection is added to the list. */
+ void slotConnectionCreated(KFormDesigner::Form *form, KFormDesigner::Connection &connection);
+ /*! This slot is called when the user aborts connection creation (when in drag-and-drop mode). The dialog is restored,
+ and an empty connection is created. */
+ void slotConnectionAborted(KFormDesigner::Form *form);
+
+ void slotCellSelected(int col, int row);
+ void slotRowInserted(KexiTableItem*,bool);
+
+ /*! Slot called when the user presses 'Ok' button. The Form::connectionBuffer() is deleted, created again and filled with Connection.
+ If the user presses 'Cancel', nothing happens. */
+ virtual void slotOk();
+
+ protected:
+ enum {BAdd = 10, BRemove};
+ Form *m_form;
+ ConnectionBuffer *m_buffer;
+ KexiTableView *m_table;
+ KexiTableViewData *m_data;
+ KexiTableViewData *m_widgetsColumnData, *m_slotsColumnData, *m_signalsColumnData;
+ QLabel *m_pixmapLabel, *m_textLabel;
+ QIntDict<QButton> m_buttons; //! dict of button (for disabling them)
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/container.cpp b/kexi/formeditor/container.cpp
new file mode 100644
index 000000000..75a6dea6c
--- /dev/null
+++ b/kexi/formeditor/container.cpp
@@ -0,0 +1,1182 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qrect.h>
+#include <qevent.h>
+#include <qvaluevector.h>
+#include <qlayout.h>
+#include <qcursor.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+
+#include <cstdlib> // for abs()
+
+#include "utils.h"
+#include "container.h"
+#include "widgetlibrary.h"
+#include "objecttree.h"
+#include "form.h"
+#include "formmanager.h"
+#include "commands.h"
+#include "events.h"
+#include "kexiflowlayout.h"
+
+using namespace KFormDesigner;
+
+EventEater::EventEater(QWidget *widget, QObject *container)
+ : QObject(container)
+{
+ m_widget = widget;
+ m_container = container;
+
+ installRecursiveEventFilter(m_widget, this);
+}
+
+bool
+EventEater::eventFilter(QObject *, QEvent *ev)
+{
+ if(!m_container)
+ return false;
+
+ // When the user click the empty part of tab bar, only MouseReleaseEvent is sent,
+ // we need to simulate the Press event
+ if(ev->type() == QEvent::MouseButtonRelease && m_widget->inherits("QTabWidget"))
+ {
+ QMouseEvent *mev = static_cast<QMouseEvent*>(ev);
+ if(mev->button() == LeftButton)
+ {
+ QMouseEvent *myev = new QMouseEvent(QEvent::MouseButtonPress, mev->pos(), mev->button(), mev->state());
+ m_container->eventFilter(m_widget, myev);
+ delete myev;
+ //return true;
+ }
+ }
+// else if(ev->type() == QEvent::ChildInserted) {
+ // widget's children have changed, we need to reinstall filter
+// installRecursiveEventFilter(m_widget, this);
+// }
+
+ return m_container->eventFilter(m_widget, ev);
+}
+
+EventEater::~EventEater()
+{
+ if(m_widget)
+ removeRecursiveEventFilter(m_widget, this);
+}
+
+// Container itself
+
+Container::Container(Container *toplevel, QWidget *container, QObject *parent, const char *name)
+: QObject(parent, name)
+, m_insertBegin(-1,-1)
+, m_mousePressEventReceived(false)
+, m_mouseReleaseEvent(QEvent::None, QPoint(), 0, 0)
+{
+ m_container = container;
+ m_toplevel = toplevel;
+
+ m_moving = 0;
+ m_tree = 0;
+ m_form = toplevel ? toplevel->form() : 0;
+ m_layout = 0;
+ m_layType = NoLayout;
+ m_state = DoingNothing;
+
+ QCString classname = container->className();
+ if((classname == "HBox") || (classname == "Grid") || (classname == "VBox") ||
+ (classname == "HFlow") || (classname == "VFlow"))
+ m_margin = 4; // those containers don't have frames, so little margin
+ else
+ m_margin = m_form ? m_form->defaultMargin() : 0;
+ m_spacing = m_form ? m_form->defaultSpacing() : 0;
+
+ if(toplevel)
+ {
+ ObjectTreeItem *it = new ObjectTreeItem(m_form->library()->displayName(classname),
+ widget()->name(), widget(), this, this);
+ setObjectTree(it);
+
+ if(parent->isWidgetType())
+ {
+ QString n = parent->name();
+ ObjectTreeItem *parent = m_form->objectTree()->lookup(n);
+ m_form->objectTree()->addItem(parent, it);
+ }
+ else
+ m_form->objectTree()->addItem(toplevel->objectTree(), it);
+
+ connect(toplevel, SIGNAL(destroyed()), this, SLOT(widgetDeleted()));
+ }
+
+ connect(container, SIGNAL(destroyed()), this, SLOT(widgetDeleted()));
+}
+
+Container::~Container()
+{
+ kdDebug() << " Container being deleted this == " << name() << endl;
+}
+
+void
+Container::setForm(Form *form)
+{
+ m_form = form;
+ m_margin = m_form ? m_form->defaultMargin() : 0;
+ m_spacing = m_form ? m_form->defaultSpacing() : 0;
+}
+
+bool
+Container::eventFilter(QObject *s, QEvent *e)
+{
+// kdDebug() << e->type() << endl;
+ switch(e->type())
+ {
+ case QEvent::MouseButtonPress:
+ {
+ m_insertBegin = QPoint(-1, -1);
+ m_mousePressEventReceived = true;
+
+ kdDebug() << "QEvent::MouseButtonPress sender object = " << s->name()
+ << "of type " << s->className() << endl;
+ kdDebug() << "QEvent::MouseButtonPress this = " << name() << endl;
+
+ m_moving = static_cast<QWidget*>(s);
+ QMouseEvent *mev = static_cast<QMouseEvent*>(e);
+ m_grab = QPoint(mev->x(), mev->y());
+
+ // we are drawing a connection
+ if(FormManager::self()->isCreatingConnection()) {
+ drawConnection(mev);
+ return true;
+ }
+
+ if(((mev->state() == ControlButton) || (mev->state() == ShiftButton))
+ && (!FormManager::self()->isInserting())) // multiple selection mode
+ {
+ if(m_form->selectedWidgets()->findRef(m_moving) != -1) // widget is already selected
+ {
+ if(m_form->selectedWidgets()->count() > 1) // we remove it from selection
+ unSelectWidget(m_moving);
+ else // the widget is the only selected, so it means we want to copy it
+ {
+ //m_copyRect = m_moving->geometry();
+ m_state = CopyingWidget;
+ if(m_form->formWidget())
+ m_form->formWidget()->initBuffer();
+ }
+ }
+ else // the widget is not yet selected, we add it
+ setSelectedWidget(m_moving, true, (mev->button() == RightButton));
+ }
+ else if((m_form->selectedWidgets()->count() > 1))//&& (!m_form->manager()->isInserting())) // more than one widget selected
+ {
+ if(m_form->selectedWidgets()->findRef(m_moving) == -1) // widget is not selected, it becomes the only selected widget
+ setSelectedWidget(m_moving, false, (mev->button() == RightButton));
+ // If the widget is already selected, we do nothing (to ease widget moving, etc.)
+ }
+ else// if(!m_form->manager()->isInserting())
+ setSelectedWidget(m_moving, false, (mev->button() == RightButton));
+
+ // we are inserting a widget or drawing a selection rect in the form
+ if((/*s == m_container &&*/ FormManager::self()->isInserting()) || ((s == m_container) && !m_toplevel))
+ {
+ int tmpx,tmpy;
+ if(!FormManager::self()->snapWidgetsToGrid() || (mev->state() == (LeftButton|ControlButton|AltButton)))
+ {
+ tmpx = mev->x();
+ tmpy = mev->y();
+ }
+ else
+ {
+ int gridX = m_form->gridSize();
+ int gridY = m_form->gridSize();
+ tmpx = int( (float)mev->x() / ((float)gridX) + 0.5 ); // snap to grid
+ tmpx *= gridX;
+ tmpy = int( (float)mev->y() / ((float)gridY) + 0.5 );
+ tmpy *= gridX;
+ }
+
+ m_insertBegin = (static_cast<QWidget*>(s))->mapTo(m_container, QPoint(tmpx, tmpy));
+ if(m_form->formWidget())
+ m_form->formWidget()->initBuffer();
+
+ if(!FormManager::self()->isInserting())
+ m_state = DrawingSelectionRect;
+ }
+ else {
+ if(s->inherits("QTabWidget")) // to allow changing page by clicking tab
+ return false;
+ }
+
+ if (m_objectForMouseReleaseEvent) {
+ const bool res = handleMouseReleaseEvent(m_objectForMouseReleaseEvent, &m_mouseReleaseEvent);
+ m_objectForMouseReleaseEvent = 0;
+ return res;
+ }
+ return true;
+ }
+
+ case QEvent::MouseButtonRelease:
+ {
+ QMouseEvent *mev = static_cast<QMouseEvent*>(e);
+ if (!m_mousePressEventReceived) {
+ m_mouseReleaseEvent = *mev;
+ m_objectForMouseReleaseEvent = s;
+ return true;
+ }
+ m_mousePressEventReceived = false;
+ m_objectForMouseReleaseEvent = 0;
+ return handleMouseReleaseEvent(s, mev);
+ }
+
+ case QEvent::MouseMove:
+ {
+ QMouseEvent *mev = static_cast<QMouseEvent*>(e);
+ if(m_insertBegin!=QPoint(-1,-1) && FormManager::self()->isInserting() && ((mev->state() == LeftButton) || (mev->state() == (LeftButton|ControlButton)) ||
+ (mev->state() == (LeftButton|ControlButton|AltButton)) || (mev->state() == (LeftButton|ShiftButton)) ) )
+ // draw the insert rect
+ {
+ drawInsertRect(mev, s);
+ return true;
+ }
+ // Creating a connection, we highlight sender and receiver, and we draw a link between them
+ else if(FormManager::self()->isCreatingConnection() && !FormManager::self()->createdConnection()->sender().isNull())
+ {
+ ObjectTreeItem *tree = m_form->objectTree()->lookup(FormManager::self()->createdConnection()->sender());
+ if(!tree || !tree->widget())
+ return true;
+
+ if(m_form->formWidget() && (tree->widget() != s))
+ m_form->formWidget()->highlightWidgets(tree->widget(), static_cast<QWidget*>(s));
+ }
+ else if(m_insertBegin!=QPoint(-1,-1) && s == m_container && !m_toplevel && (mev->state() != ControlButton) && !FormManager::self()->isCreatingConnection()) // draw the selection rect
+ {
+ if((mev->state() != LeftButton) || /*m_inlineEditing*/ m_state == InlineEditing)
+ return true;
+ int topx = (m_insertBegin.x() < mev->x()) ? m_insertBegin.x() : mev->x();
+ int topy = (m_insertBegin.y() < mev->y()) ? m_insertBegin.y() : mev->y();
+ int botx = (m_insertBegin.x() > mev->x()) ? m_insertBegin.x() : mev->x();
+ int boty = (m_insertBegin.y() > mev->y()) ? m_insertBegin.y() : mev->y();
+ QRect r = QRect(QPoint(topx, topy), QPoint(botx, boty));
+ m_insertRect = r;
+
+ if(m_form->formWidget())
+ m_form->formWidget()->drawRect(r, 1);
+
+ m_state = DoingNothing;
+ return true;
+ }
+ else if(mev->state() == (LeftButton|ControlButton)) // draw the insert rect for the copied widget
+ {
+ if(s == m_container)// || (m_form->selectedWidgets()->count() > 1))
+ return true;
+ drawCopiedWidgetRect(mev);
+ return true;
+ }
+ else if( ( (mev->state() == Qt::LeftButton) || (mev->state() == (LeftButton|ControlButton|AltButton)) )
+ && !FormManager::self()->isInserting() && (m_state != CopyingWidget)) // we are dragging the widget(s) to move it
+ {
+ if(!m_toplevel && m_moving == m_container) // no effect for form
+ return false;
+ if((!m_moving) || (!m_moving->parentWidget()))// || (m_moving->parentWidget()->inherits("QWidgetStack")))
+ return true;
+
+ moveSelectedWidgetsBy(mev->x() - m_grab.x(), mev->y() - m_grab.y());
+ m_state = MovingWidget;
+ }
+
+ return true; // eat
+ }
+
+ case QEvent::Paint: // Draw the dotted background
+ {
+ if(s != m_container)
+ return false;
+ int gridX = m_form->gridSize();
+ int gridY = m_form->gridSize();
+
+ QPainter p(m_container);
+ p.setPen(QPen(white, 2));
+ p.setRasterOp(XorROP);
+ int cols = m_container->width() / gridX;
+ int rows = m_container->height() / gridY;
+
+ for(int rowcursor = 1; rowcursor <= rows; ++rowcursor)
+ {
+ for(int colcursor = 1; colcursor <= cols; ++colcursor)
+ {
+ p.drawPoint(-1 + colcursor *gridX, -1 + rowcursor *gridY);
+ }
+ }
+
+ return false;
+ }
+
+ case QEvent::Resize: // we are resizing a widget, so we set m_move to true -> the layout will be reloaded when releasing mouse
+ {
+ if(m_form->interactiveMode())
+ m_state = MovingWidget;
+ break;
+ }
+
+ //case QEvent::AccelOverride:
+ case QEvent::KeyPress:
+ {
+ QKeyEvent *kev = static_cast<QKeyEvent*>(e);
+
+ if(kev->key() == Key_F2) // pressing F2 == double-clicking
+ {
+ m_state = InlineEditing;
+ QWidget *w;
+
+ // try to find the widget which was clicked last and should be edited
+ if(m_form->selectedWidgets()->count() == 1)
+ w = m_form->selectedWidgets()->first();
+ else if(m_form->selectedWidgets()->findRef(m_moving) != -1)
+ w = m_moving;
+ else
+ w = m_form->selectedWidgets()->last();
+ m_form->library()->startEditing(w->className(), w, this);
+ }
+ else if(kev->key() == Key_Escape)
+ {
+ if(FormManager::self()->isCreatingConnection())
+ FormManager::self()->stopCreatingConnection();
+ else if(FormManager::self()->isInserting())
+ FormManager::self()->stopInsert();
+ return true;
+ }
+ else if((kev->key() == Key_Control) && (m_state == MovingWidget))
+ {
+ if(!m_moving)
+ return true;
+ // we simulate a mouse move event to update screen
+ QMouseEvent *mev = new QMouseEvent(QEvent::MouseMove, m_moving->mapFromGlobal(QCursor::pos()), NoButton,
+ LeftButton|ControlButton );
+ eventFilter(m_moving, mev);
+ delete mev;
+ }
+ else if(kev->key() == FormManager::self()->contextMenuKey())
+ {
+ FormManager::self()->createContextMenu(static_cast<QWidget*>(s), this, false);
+ return true;
+ }
+ else if (kev->key() == Key_Delete)
+ {
+ FormManager::self()->deleteWidget();
+ return true;
+ }
+ // directional buttons move the widget
+ else if(kev->key() == Key_Left){ // move the widget of gridX to the left
+ moveSelectedWidgetsBy(-form()->gridSize(), 0);
+ return true;
+ }
+ else if(kev->key() == Key_Right){ // move the widget of gridX to the right
+ moveSelectedWidgetsBy(form()->gridSize(), 0);
+ return true;
+ }
+ else if(kev->key() == Key_Up){ // move the widget of gridY to the top
+ moveSelectedWidgetsBy(0, - form()->gridSize());
+ return true;
+ }
+ else if(kev->key() == Key_Down){ // move the widget of gridX to the bottom
+ moveSelectedWidgetsBy(0, form()->gridSize());
+ return true;
+ }
+ else if((kev->key() == Key_Tab) || (kev->key() == Key_BackTab)){
+ ObjectTreeItem *item = form()->objectTree()->lookup(form()->selectedWidgets()->first()->name());
+ if(!item || !item->parent())
+ return true;
+ ObjectTreeList *list = item->parent()->children();
+ if(list->count() == 1)
+ return true;
+ int index = list->findRef(item);
+
+ if(kev->key() == Key_BackTab){
+ if(index == 0) // go back to the last item
+ index = list->count() - 1;
+ else
+ index = index - 1;
+ }
+ else {
+ if(index == int(list->count() - 1)) // go back to the first item
+ index = 0;
+ else
+ index = index + 1;
+ }
+
+ ObjectTreeItem *nextItem = list->at(index);
+ if(nextItem && nextItem->widget())
+ form()->setSelectedWidget(nextItem->widget(), false);
+ }
+ return true;
+ }
+
+ case QEvent::KeyRelease:
+ {
+ QKeyEvent *kev = static_cast<QKeyEvent*>(e);
+ if((kev->key() == Key_Control) && (m_state == CopyingWidget)) {
+ // cancel copying
+ //m_copyRect = QRect();
+ if(m_form->formWidget())
+ m_form->formWidget()->clearForm();
+ }
+ return true;
+ }
+
+ case QEvent::MouseButtonDblClick: // editing
+ {
+ kdDebug() << "Container: Mouse dbl click for widget " << s->name() << endl;
+ QWidget *w = static_cast<QWidget*>(s);
+ if(!w)
+ return false;
+
+ //m_inlineEditing = true;
+ m_state = InlineEditing;
+ m_form->library()->startEditing(w->className(), w, this);
+ return true;
+ }
+
+ case QEvent::ContextMenu:
+ case QEvent::Enter:
+ case QEvent::Leave:
+ case QEvent::FocusIn:
+ case QEvent::FocusOut:
+// case QEvent::DragEnter:
+// case QEvent::DragMove:
+// case QEvent::DragLeave:
+ return true; // eat them
+
+ default:
+ return false; // let the widget do the rest ...
+ }
+ return false;
+}
+
+bool
+Container::handleMouseReleaseEvent(QObject *s, QMouseEvent *mev)
+{
+ if(FormManager::self()->isInserting()) // we insert the widget at cursor pos
+ {
+ if(m_form->formWidget())
+ m_form->formWidget()->clearForm();
+ KCommand *com = new InsertWidgetCommand(this/*, mev->pos()*/);
+ m_form->addCommand(com, true);
+ m_insertBegin = QPoint(-1,-1);
+ m_insertRect = QRect();
+ return true;
+ }
+ else if(s == m_container && !m_toplevel && (mev->button() != RightButton) && m_insertRect.isValid()) // we are drawing a rect to select widgets
+ {
+ drawSelectionRect(mev);
+ return true;
+ }
+ if(mev->button() == RightButton) // Right-click -> context menu
+ {
+ FormManager::self()->createContextMenu(static_cast<QWidget*>(s), this);
+ }
+ else if(mev->state() == (Qt::LeftButton|Qt::ControlButton))// && (m_copyRect.isValid()))
+ {
+ // copying a widget by Ctrl+dragging
+
+ if(m_form->formWidget())
+ m_form->formWidget()->clearForm();
+ if(s == m_container) // should have no effect on form
+ return true;
+
+ // prevent accidental copying of widget (when moving mouse a little while selecting)
+ if( ( (mev->pos().x() - m_grab.x()) < form()->gridSize() && (m_grab.x() - mev->pos().x()) < form()->gridSize() ) &&
+ ( (mev->pos().y() - m_grab.y()) < form()->gridSize() && (m_grab.y() - mev->pos().y()) < form()->gridSize() ) )
+ {
+ kdDebug() << "The widget has not been moved. No copying" << endl;
+ return true;
+ }
+
+ m_form->setInteractiveMode(false);
+ // We simulate copy and paste
+ FormManager::self()->copyWidget();
+ if(m_form->selectedWidgets()->count() > 1)
+ FormManager::self()->setInsertPoint( mev->pos() );
+ else
+ FormManager::self()->setInsertPoint( static_cast<QWidget*>(s)->mapTo(m_container, mev->pos() - m_grab) );
+ FormManager::self()->pasteWidget();
+ m_form->setInteractiveMode(true);
+
+ //m_initialPos = QPoint();
+ }
+ else if(m_state == MovingWidget) // one widget has been moved, so we need to update the layout
+ reloadLayout();
+
+ // cancel copying as user released Ctrl before releasing mouse button
+ m_insertBegin = QPoint(-1,-1);
+ m_insertRect = QRect();
+ m_state = DoingNothing;
+ return true; // eat
+}
+
+void
+Container::setSelectedWidget(QWidget *w, bool add, bool dontRaise, bool moreWillBeSelected)
+{
+ if (w)
+ kdDebug() << "slotSelectionChanged " << w->name()<< endl;
+
+ if(!w)
+ {
+ m_form->setSelectedWidget(m_container);
+ return;
+ }
+
+ m_form->setSelectedWidget(w, add, dontRaise, moreWillBeSelected);
+}
+
+void
+Container::unSelectWidget(QWidget *w)
+{
+ if(!w)
+ return;
+
+ m_form->unSelectWidget(w);
+}
+
+Container*
+Container::toplevel()
+{
+ if(m_toplevel)
+ return m_toplevel;
+ else
+ return this;
+}
+
+void
+Container::deleteWidget(QWidget *w)
+{
+ if(!w)
+ return;
+// kdDebug() << "Deleting a widget: " << w->name() << endl;
+ m_form->objectTree()->removeItem(w->name());
+ FormManager::self()->deleteWidgetLater( w );
+ m_form->setSelectedWidget(m_container);
+}
+
+void
+Container::widgetDeleted()
+{
+ m_container = 0;
+ deleteLater();
+}
+
+/// Layout functions
+
+void
+Container::setLayout(LayoutType type)
+{
+ if(m_layType == type)
+ return;
+
+ delete m_layout;
+ m_layout = 0;
+ m_layType = type;
+
+ switch(type)
+ {
+ case HBox:
+ {
+ m_layout = (QLayout*) new QHBoxLayout(m_container, m_margin, m_spacing);
+ createBoxLayout(new HorWidgetList(m_form->toplevelContainer()->widget()));
+ break;
+ }
+ case VBox:
+ {
+ m_layout = (QLayout*) new QVBoxLayout(m_container, m_margin, m_spacing);
+ createBoxLayout(new VerWidgetList(m_form->toplevelContainer()->widget()));
+ break;
+ }
+ case Grid:
+ {
+ createGridLayout();
+ break;
+ }
+ case HFlow:
+ {
+ KexiFlowLayout *flow = new KexiFlowLayout(m_container,m_margin, m_spacing);
+ flow->setOrientation(Horizontal);
+ m_layout = (QLayout*)flow;
+ createFlowLayout();
+ break;
+ }
+ case VFlow:
+ {
+ KexiFlowLayout *flow = new KexiFlowLayout(m_container,m_margin, m_spacing);
+ flow->setOrientation(Vertical);
+ m_layout = (QLayout*)flow;
+ createFlowLayout();
+ break;
+ }
+ default:
+ {
+ m_layType = NoLayout;
+ return;
+ }
+ }
+ m_container->setGeometry(m_container->geometry()); // just update layout
+ m_layout->activate();
+}
+
+void
+Container::reloadLayout()
+{
+ LayoutType type = m_layType;
+ setLayout(NoLayout);
+ setLayout(type);
+}
+
+void
+Container::createBoxLayout(WidgetList *list)
+{
+ QBoxLayout *layout = static_cast<QBoxLayout*>(m_layout);
+
+ for(ObjectTreeItem *tree = m_tree->children()->first(); tree; tree = m_tree->children()->next())
+ list->append( tree->widget());
+ list->sort();
+
+ for(QWidget *obj = list->first(); obj; obj = list->next())
+ layout->addWidget(obj);
+ delete list;
+}
+
+void
+Container::createFlowLayout()
+{
+ KexiFlowLayout *flow = dynamic_cast<KexiFlowLayout*>(m_layout);
+ if(!flow || m_tree->children()->isEmpty())
+ return;
+
+ const int offset = 15;
+ WidgetList *list=0, *list2=0;
+ if(flow->orientation() == Horizontal) {
+ list = new VerWidgetList(m_form->toplevelContainer()->widget());
+ list2 = new HorWidgetList(m_form->toplevelContainer()->widget());
+ }
+ else {
+ list = new HorWidgetList(m_form->toplevelContainer()->widget());
+ list2 = new VerWidgetList(m_form->toplevelContainer()->widget());
+ }
+
+ // fill the list
+ for(ObjectTreeItem *tree = m_tree->children()->first(); tree; tree = m_tree->children()->next())
+ list->append( tree->widget());
+ list->sort();
+
+ if(flow->orientation() == Horizontal) {
+ int y = list->first()->y();
+ for(QWidget *w = list->first(); w; w = list->next()) {
+ if( (w->y() > y +offset)) {
+ // start a new line
+ list2->sort();
+ for(QWidget *obj = list2->first(); obj; obj = list2->next())
+ flow->add(obj);
+ list2->clear();
+ y = w->y();
+ }
+ list2->append(w);
+ }
+
+ list2->sort(); // don't forget the last line
+ for(QWidget *obj = list2->first(); obj; obj = list2->next())
+ flow->add(obj);
+ }
+ else {
+ int x = list->first()->x();
+ for(QWidget *w = list->first(); w; w = list->next()) {
+ if( (w->x() > x +offset)) {
+ // start a new column
+ list2->sort();
+ for(QWidget *obj = list2->first(); obj; obj = list2->next())
+ flow->add(obj);
+ list2->clear();
+ x = w->x();
+ }
+ list2->append(w);
+ }
+
+ list2->sort(); // don't forget the last column
+ for(QWidget *obj = list2->first(); obj; obj = list2->next())
+ flow->add(obj);
+ }
+
+ delete list;
+ delete list2;
+}
+
+void
+Container::createGridLayout(bool testOnly)
+{
+ //Those lists sort widgets by y and x
+ VerWidgetList *vlist = new VerWidgetList(m_form->toplevelContainer()->widget());
+ HorWidgetList *hlist = new HorWidgetList(m_form->toplevelContainer()->widget());
+ // The vector are used to store the x (or y) beginning of each column (or row)
+ QValueVector<int> cols;
+ QValueVector<int> rows;
+ int end=-1000;
+ bool same = false;
+
+ for(ObjectTreeItem *tree = m_tree->children()->first(); tree; tree = m_tree->children()->next())
+ vlist->append( tree->widget());
+ vlist->sort();
+
+ for(ObjectTreeItem *tree = m_tree->children()->first(); tree; tree = m_tree->children()->next())
+ hlist->append( tree->widget());
+ hlist->sort();
+
+ // First we need to make sure that two widgets won't be in the same row,
+ // ie that no widget overlap another one
+ if(!testOnly) {
+ for(WidgetListIterator it(*vlist); it.current() != 0; ++it)
+ {
+ QWidget *w = it.current();
+ WidgetListIterator it2 = it;
+
+ for(; it2.current() != 0; ++it2) {
+ QWidget *nextw = it2.current();
+ if((w->y() >= nextw->y()) || (nextw->y() >= w->geometry().bottom()))
+ break;
+
+ if(!w->geometry().intersects(nextw->geometry()))
+ break;
+ // If the geometries of the two widgets intersect each other,
+ // we move one of the widget to the rght or bottom of the other
+ if((nextw->y() - w->y()) > abs(nextw->x() - w->x()))
+ nextw->move(nextw->x(), w->geometry().bottom()+1);
+ else if(nextw->x() >= w->x())
+ nextw->move(w->geometry().right()+1, nextw->y());
+ else
+ w->move(nextw->geometry().right()+1, nextw->y());
+ }
+ }
+ }
+
+ // Then we count the number of rows in the layout, and set their beginnings
+ for(WidgetListIterator it(*vlist); it.current() != 0; ++it)
+ {
+ QWidget *w = it.current();
+ WidgetListIterator it2 = it;
+ if(!same) { // this widget will make a new row
+ end = w->geometry().bottom();
+ rows.append(w->y());
+ }
+
+ // If same == true, it means we are in the same row as prev widget
+ // (so no need to create a new column)
+ ++it2;
+ if(!it2.current())
+ break;
+
+ QWidget *nextw = it2.current();
+ if(nextw->y() >= end)
+ same = false;
+ else {
+ same = !(same && (nextw->y() >= w->geometry().bottom()));
+ if(!same)
+ end = w->geometry().bottom();
+ }
+ }
+ kdDebug() << "the new grid will have n rows: n == " << rows.size() << endl;
+
+ end = -10000;
+ same = false;
+ // We do the same thing for the columns
+ for(WidgetListIterator it(*hlist); it.current() != 0; ++it)
+ {
+ QWidget *w = it.current();
+ WidgetListIterator it2 = it;
+ if(!same) {
+ end = w->geometry().right();
+ cols.append(w->x());
+ }
+
+ ++it2;
+ if(!it2.current())
+ break;
+
+ QWidget *nextw = it2.current();
+ if(nextw->x() >= end)
+ same = false;
+ else {
+ same = !(same && (nextw->x() >= w->geometry().right()));
+ if(!same)
+ end = w->geometry().right();
+ }
+ }
+ kdDebug() << "the new grid will have n columns: n == " << cols.size() << endl;
+
+ // We create the layout ..
+ QGridLayout *layout=0;
+ if(!testOnly) {
+ layout = new QGridLayout(m_container, rows.size(), cols.size(), m_margin, m_spacing, "grid");
+ m_layout = (QLayout*)layout;
+ }
+
+ // .. and we fill it with widgets
+ for(WidgetListIterator it(*vlist); it.current() != 0; ++it)
+ {
+ QWidget *w = it.current();
+ QRect r = w->geometry();
+ uint wcol=0, wrow=0, endrow=0, endcol=0;
+ uint i = 0;
+
+ // We look for widget row(s) ..
+ while(r.y() >= rows[i])
+ {
+ if(rows.size() <= i+1) // we are the last row
+ {
+ wrow = i;
+ break;
+ }
+ if(r.y() < rows[i+1])
+ {
+ wrow = i; // the widget will be in this row
+ uint j = i + 1;
+ // Then we check if the widget needs to span multiple rows
+ while(rows.size() >= j+1 && r.bottom() > rows[j])
+ {
+ endrow = j;
+ j++;
+ }
+
+ break;
+ }
+ i++;
+ }
+ //kdDebug() << "the widget " << w->name() << " wil be in the row " << wrow <<
+ //" and will go to the row " << endrow << endl;
+
+ // .. and column(s)
+ i = 0;
+ while(r.x() >= cols[i])
+ {
+ if(cols.size() <= i+1) // last column
+ {
+ wcol = i;
+ break;
+ }
+ if(r.x() < cols[i+1])
+ {
+ wcol = i;
+ uint j = i + 1;
+ // Then we check if the widget needs to span multiple columns
+ while(cols.size() >= j+1 && r.right() > cols[j])
+ {
+ endcol = j;
+ j++;
+ }
+
+ break;
+ }
+ i++;
+ }
+ //kdDebug() << "the widget " << w->name() << " wil be in the col " << wcol <<
+ // " and will go to the col " << endcol << endl;
+
+ ObjectTreeItem *item = m_form->objectTree()->lookup(w->name());
+ if(!endrow && !endcol) {
+ if(!testOnly)
+ layout->addWidget(w, wrow, wcol);
+ item->setGridPos(wrow, wcol, 0, 0);
+ }
+ else {
+ if(!endcol) endcol = wcol;
+ if(!endrow) endrow = wrow;
+ if(!testOnly)
+ layout->addMultiCellWidget(w, wrow, endrow, wcol, endcol);
+ item->setGridPos(wrow, wcol, endrow-wrow+1, endcol-wcol+1);
+ }
+ }
+}
+
+QString
+Container::layoutTypeToString(int type)
+{
+ switch(type)
+ {
+ case HBox: return "HBox";
+ case VBox: return "VBox";
+ case Grid: return "Grid";
+ case HFlow: return "HFlow";
+ case VFlow: return "VFlow";
+ default: return "NoLayout";
+ }
+}
+
+Container::LayoutType
+Container::stringToLayoutType(const QString &name)
+{
+ if(name == "HBox") return HBox;
+ if(name == "VBox") return VBox;
+ if(name == "Grid") return Grid;
+ if(name == "HFlow") return HFlow;
+ if(name == "VFlow") return VFlow;
+ return NoLayout;
+}
+
+/// Drawing functions used by eventFilter
+void
+Container::drawConnection(QMouseEvent *mev)
+{
+ if(mev->button() != LeftButton)
+ {
+ FormManager::self()->resetCreatedConnection();
+ return;
+ }
+ // First click, we select the sender and display menu to choose signal
+ if(FormManager::self()->createdConnection()->sender().isNull())
+ {
+ FormManager::self()->createdConnection()->setSender(m_moving->name());
+ if(m_form->formWidget())
+ {
+ m_form->formWidget()->initBuffer();
+ m_form->formWidget()->highlightWidgets(m_moving, 0/*, QPoint()*/);
+ }
+ FormManager::self()->createSignalMenu(m_moving);
+ return;
+ }
+ // the user clicked outside the menu, we cancel the connection
+ if(FormManager::self()->createdConnection()->signal().isNull())
+ {
+ FormManager::self()->stopCreatingConnection();
+ return;
+ }
+ // second click to choose the receiver
+ if(FormManager::self()->createdConnection()->receiver().isNull())
+ {
+ FormManager::self()->createdConnection()->setReceiver(m_moving->name());
+ FormManager::self()->createSlotMenu(m_moving);
+ m_container->repaint();
+ return;
+ }
+ // the user clicked outside the menu, we cancel the connection
+ if(FormManager::self()->createdConnection()->slot().isNull())
+ {
+ FormManager::self()->stopCreatingConnection();
+ return;
+ }
+}
+
+void
+Container::drawSelectionRect(QMouseEvent *mev)
+{
+ //finish drawing unclipped selection rectangle: clear the surface
+ if(m_form->formWidget())
+ m_form->formWidget()->clearForm();
+ int topx = (m_insertBegin.x() < mev->x()) ? m_insertBegin.x() : mev->x();
+ int topy = (m_insertBegin.y() < mev->y()) ? m_insertBegin.y() : mev->y();
+ int botx = (m_insertBegin.x() > mev->x()) ? m_insertBegin.x() : mev->x();
+ int boty = (m_insertBegin.y() > mev->y()) ? m_insertBegin.y() : mev->y();
+ QRect r = QRect(QPoint(topx, topy), QPoint(botx, boty));
+
+ setSelectedWidget(m_container, false);
+ QWidget *widgetToSelect = 0;
+ // We check which widgets are in the rect and select them
+ for(ObjectTreeItem *item = m_tree->children()->first(); item; item = m_tree->children()->next())
+ {
+ QWidget *w = item->widget();
+ if(!w)
+ continue;
+ if(w->geometry().intersects(r) && w != m_container) {
+ if (widgetToSelect)
+ setSelectedWidget(widgetToSelect, true/*add*/, false/*raise*/, true/*moreWillBeSelected*/);
+ widgetToSelect = w; //select later
+ }
+ }
+ if (widgetToSelect) //the last one left
+ setSelectedWidget(widgetToSelect, true/*add*/, false/*raise*/, false/*!moreWillBeSelected*/);
+
+ m_insertRect = QRect();
+ m_state = DoingNothing;
+ m_container->repaint();
+}
+
+void
+Container::drawInsertRect(QMouseEvent *mev, QObject *s)
+{
+ int tmpx, tmpy;
+ QPoint pos = static_cast<QWidget*>(s)->mapTo(m_container, mev->pos());
+ int gridX = m_form->gridSize();
+ int gridY = m_form->gridSize();
+ if(!FormManager::self()->snapWidgetsToGrid() || (mev->state() == (LeftButton|ControlButton|AltButton)) )
+ {
+ tmpx = pos.x();
+ tmpy = pos.y();
+ }
+ else
+ {
+ tmpx = int( (float) pos.x() / ((float)gridX) + 0.5);
+ tmpx *= gridX;
+ tmpy = int( (float)pos.y() / ((float)gridY) + 0.5);
+ tmpy *= gridX;
+ }
+
+ int topx = (m_insertBegin.x() < tmpx) ? m_insertBegin.x() : tmpx;
+ int topy = (m_insertBegin.y() < tmpy) ? m_insertBegin.y() : tmpy;
+ int botx = (m_insertBegin.x() > tmpx) ? m_insertBegin.x() : tmpx;
+ int boty = (m_insertBegin.y() > tmpy) ? m_insertBegin.y() : tmpy;
+ m_insertRect = QRect(QPoint(topx, topy), QPoint(botx, boty));
+
+ if(m_insertRect.x() < 0)
+ m_insertRect.setLeft(0);
+ if(m_insertRect.y() < 0)
+ m_insertRect.setTop(0);
+ if(m_insertRect.right() > m_container->width())
+ m_insertRect.setRight(m_container->width());
+ if(m_insertRect.bottom() > m_container->height())
+ m_insertRect.setBottom(m_container->height());
+
+ if(FormManager::self()->isInserting() && m_insertRect.isValid())
+ {
+ if(m_form->formWidget())
+ {
+ QRect drawRect = QRect(m_container->mapTo(m_form->widget(), m_insertRect.topLeft())
+ , m_insertRect.size());
+ m_form->formWidget()->drawRect(drawRect, 2);
+ }
+ }
+}
+
+void
+Container::drawCopiedWidgetRect(QMouseEvent *mev)
+{
+ // We've been dragging a widget, but Ctrl was hold, so we start copy
+ if(m_state == MovingWidget) {
+ //FormManager::self()->undo(); // undo last moving
+ //m_moving->move(m_initialPos);
+ if(m_form->formWidget()) {
+ m_container->repaint();
+ m_form->formWidget()->initBuffer();
+ }
+ m_state = CopyingWidget;
+ }
+
+ //m_copyRect.moveTopLeft(m_container->mapFromGlobal( mev->globalPos()) - m_grab);
+
+ if(m_form->formWidget()) {
+ QValueList<QRect> rectList;
+ for(QWidget *w = m_form->selectedWidgets()->first(); w; w = m_form->selectedWidgets()->next()) {
+ QRect drawRect = w->geometry();
+ QPoint p = mev->pos() - m_grab;
+ drawRect.moveBy(p.x(), p.y());
+ p = m_container->mapTo(m_form->widget(), QPoint(0, 0));
+ //drawRect = QRect( ((QWidget*)s)->mapTo(m_form->widget(), drawRect.topLeft()), drawRect.size());
+ drawRect.moveBy(p.x(), p.y());
+ rectList.append(drawRect);
+ }
+
+ m_form->formWidget()->drawRects(rectList, 2);
+ }
+}
+
+/// Other functions used by eventFilter
+void
+Container::moveSelectedWidgetsBy(int realdx, int realdy, QMouseEvent *mev)
+{
+ if (m_form->selectedWidget() == m_form->widget())
+ return; //do not move top-level widget
+
+ const int gridX = m_form->gridSize();
+ const int gridY = m_form->gridSize();
+ int dx=realdx, dy=realdy;
+
+ for(QWidget *w = m_form->selectedWidgets()->first(); w; w = m_form->selectedWidgets()->next())
+ {
+ if(!w || !w->parent() || w->parent()->inherits("QTabWidget") || w->parent()->inherits("QWidgetStack"))
+ continue;
+
+ if(w->parentWidget() && w->parentWidget()->isA("QWidgetStack"))
+ {
+ w = w->parentWidget(); // widget is WidgetStack page
+ if(w->parentWidget() && w->parentWidget()->inherits("QTabWidget")) // widget is tabwidget page
+ w = w->parentWidget();
+ }
+
+ int tmpx = w->x() + realdx;
+ int tmpy = w->y() + realdy;
+ if(tmpx < 0)
+ dx = QMAX(0 - w->x(), dx); // because dx is <0
+ else if(tmpx > w->parentWidget()->width() - gridX)
+ dx = QMIN(w->parentWidget()->width() - gridX - w->x(), dx);
+
+ if(tmpy < 0)
+ dy = QMAX(0 - w->y(), dy); // because dy is <0
+ else if(tmpy > w->parentWidget()->height() - gridY)
+ dy = QMIN(w->parentWidget()->height() - gridY - w->y(), dy);
+ }
+
+ for(QWidget *w = m_form->selectedWidgets()->first(); w; w = m_form->selectedWidgets()->next())
+ {
+ // Don't move tab widget pages (or widget stack pages)
+ if(!w || !w->parent() || w->parent()->inherits("QTabWidget") || w->parent()->inherits("QWidgetStack"))
+ continue;
+
+ if(w->parentWidget() && w->parentWidget()->isA("QWidgetStack"))
+ {
+ w = w->parentWidget(); // widget is WidgetStack page
+ if(w->parentWidget() && w->parentWidget()->inherits("QTabWidget")) // widget is tabwidget page
+ w = w->parentWidget();
+ }
+
+ int tmpx, tmpy;
+ if(!FormManager::self()->snapWidgetsToGrid() || (mev && mev->state() == (LeftButton|ControlButton|AltButton)) )
+ {
+ tmpx = w->x() + dx;
+ tmpy = w->y() + dy;
+ }
+ else
+ {
+ tmpx = int( float( w->x() + dx) / float(gridX) + 0.5) * gridX;
+ tmpy = int( float( w->y() + dy) / float(gridY) + 0.5) * gridY;
+ }
+
+ if((tmpx != w->x()) || (tmpy != w->y()))
+ w->move(tmpx,tmpy);
+ }
+}
+
+////////////
+
+DesignTimeDynamicChildWidgetHandler::DesignTimeDynamicChildWidgetHandler()
+ : m_item(0)
+{
+}
+
+DesignTimeDynamicChildWidgetHandler::~DesignTimeDynamicChildWidgetHandler()
+{
+}
+
+void
+DesignTimeDynamicChildWidgetHandler::childWidgetAdded(QWidget* w)
+{
+ if (m_item) {
+ installRecursiveEventFilter(w, m_item->eventEater());
+ }
+}
+
+#include "container.moc"
diff --git a/kexi/formeditor/container.h b/kexi/formeditor/container.h
new file mode 100644
index 000000000..b7036aa7d
--- /dev/null
+++ b/kexi/formeditor/container.h
@@ -0,0 +1,248 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef FORMEDITORCONTAINER_H
+#define FORMEDITORCONTAINER_H
+
+#include <qobject.h>
+#include <qguardedptr.h>
+#include <qptrlist.h>
+#include <qwidget.h>
+
+#include "utils.h"
+
+class QEvent;
+class QWidget;
+class QLayout;
+
+namespace KFormDesigner {
+
+class Container;
+class WidgetLibrary;
+class ObjectTreeItem;
+class Form;
+
+/**
+ * This class is used to filter the events from any widget (and all its subwidgets)
+ * and direct it to the Container.
+ */
+//! A class for redirecting events
+class KFORMEDITOR_EXPORT EventEater : public QObject
+{
+ Q_OBJECT
+
+ public:
+ /*! Constructs eater object. All events for \a widget and it's subwidgets
+ will be redirected to \a container. \a container will be also parent of eater object,
+ so you don't need to care about deleting it. */
+ EventEater(QWidget *widget, QObject *container);
+ ~EventEater();
+
+ //! Sets the object which will receive the events
+ void setContainer(QObject *container) { m_container = container; }
+ bool eventFilter(QObject *o, QEvent *ev);
+
+ private:
+ QGuardedPtr<QWidget> m_widget;
+ QGuardedPtr<QObject> m_container;
+};
+
+/**
+ * This class makes a container out of any QWidget. You can then create child widgets, and
+ the background is dotted.
+ */
+//! A class to make a container from any widget
+class KFORMEDITOR_EXPORT Container : public QObject
+{
+ Q_OBJECT
+
+ public:
+ enum LayoutType { NoLayout=0, HBox, VBox, Grid, HFlow, VFlow, /* special types */ HSplitter, VSplitter };
+
+ /**
+ * Creates a Container from the widget \a container, which have
+ \a toplevel as parent Container. */
+ Container(Container *toplevel, QWidget *container, QObject *parent=0, const char *name=0);
+ virtual ~Container();
+
+ //! \return a pointer to the toplevel Container, or 0 if this Container is toplevel
+ Container* toplevel();
+
+ //! \return The form this Container belongs to.
+ Form* form() const { return m_form; }
+
+ //! \return The watched widget.
+ QWidget* widget() const { return m_container; }
+
+ //! \return The ObjectTreeItem associated with this Container's widget.
+ ObjectTreeItem* objectTree() const { return m_tree; }
+
+ //! Sets the Form which this Container belongs to.
+ void setForm(Form *form);
+
+ /*! Sets the ObjectTree of this Container.\n
+ * NOTE: this is needed only if we are toplevel. */
+ void setObjectTree(ObjectTreeItem *t) { m_tree = t; }
+
+ //! \return a pointer to the QLayout of this Container, or 0 if there is not.
+ QLayout* layout() const { return m_layout; }
+
+ //! \return the type of the layout associated to this Container's widget (see LayoutType enum).
+ LayoutType layoutType() const { return m_layType; }
+
+ //! \return the margin of this Container.
+ int layoutMargin() { return m_margin; }
+
+ //! \return the spacing of this Container.
+ int layoutSpacing() { return m_spacing; }
+
+ /*! Sets this Container to use \a type of layout. The widget are inserted
+ automatically in the layout following their positions.
+ \sa createBoxLayout(), createGridLayout() */
+ void setLayout(LayoutType type);
+
+ //! Sets the spacing of this Container.
+ void setLayoutSpacing(int spacing) { m_spacing = spacing;}
+
+ //! Sets the margin of this Container.
+ void setLayoutMargin(int margin) { m_margin = margin;}
+
+ //! \return the string representing the layoutType \a type.
+ static QString layoutTypeToString(int type);
+
+ //! \return the LayoutType (an int) for a given layout name.
+ static LayoutType stringToLayoutType(const QString &name);
+
+ /*! Stops the inline editing of the current widget (as when you click
+ on another widget or press Esc). */
+ void stopInlineEditing() { m_state = DoingNothing; }
+
+ /*! This is the main function of Container, which filters the event sent
+ to the watched widget.\n It takes care of drawing the background and
+ the insert rect, of creating the new child widgets, of moving the widgets
+ and pop up a menu when right-clicking. */
+ virtual bool eventFilter(QObject *o, QEvent *e);
+
+ public slots:
+ /*! Sets \a selected to be the selected widget of this container
+ (and so of the Form). If \a add is true, the formerly selected widget
+ is still selected, and the new one is just added. If false, \a selected
+ replace the actually selected widget. If \a dontRaise is true, then
+ the widget \a selected (and its parent) won't be raised (eg when you
+ select widget in ObjectTreeView).
+ \sa Form::setSelectedWidget() */
+ void setSelectedWidget(QWidget *selected, bool add, bool dontRaise=false,
+ bool moreWillBeSelected = false);
+
+ /*! Unselects the widget \a w. The widget is removed from the Form's list
+ and its resizeHandles are removed. */
+ void unSelectWidget(QWidget *w);
+
+ /*! Deletes the widget \a w. Removes it from ObjectTree, and sets selection
+ to Container's widget. */
+ void deleteWidget(QWidget *w);
+
+ /*! Recreates the Container layout. Calls this when a widget has been moved
+ or added to update the layout. */
+ void reloadLayout();
+
+ protected slots:
+ /*! This slot is called when the watched widget is deleted. Deletes the Container too. */
+ void widgetDeleted();
+
+ protected:
+ /*! Internal function to create a HBoxLayout or VBoxLayout for this container.
+ \a list is a subclass of QObjectList that can sort widgets
+ following their position (such as HorWidgetList or VerWidgetList). */
+ void createBoxLayout(WidgetList *list);
+
+ /*! Internal function to create a KexiFlowLayout. */
+ void createFlowLayout();
+
+ /*! Internal function to create a GridLayout. if \a testOnly is true, the layout
+ is simulated, and only the widget's grid info aris filled. */
+ void createGridLayout(bool testOnly=false);
+
+ void drawConnection(QMouseEvent *mev);
+ void drawSelectionRect(QMouseEvent *mev);
+ void drawInsertRect(QMouseEvent *mev, QObject *s);
+ void drawCopiedWidgetRect(QMouseEvent *mev);
+
+ void moveSelectedWidgetsBy(int realdx, int realdy, QMouseEvent *mev=0);
+
+ private:
+ bool handleMouseReleaseEvent(QObject *s, QMouseEvent *mev);
+
+ // the watched container and it's toplevel one...
+ QGuardedPtr<QWidget> m_container;
+ QGuardedPtr<Container> m_toplevel;
+
+ int m_state;
+ enum { DoingNothing = 100, DrawingSelectionRect, CopyingWidget,
+ MovingWidget, InlineEditing };
+
+ // Layout
+ QLayout *m_layout;
+ LayoutType m_layType;
+ int m_margin, m_spacing;
+
+ // moving etc.
+ QPoint m_grab;
+ //QPoint m_initialPos;
+ QGuardedPtr<QWidget> m_moving;
+ //QRect m_copyRect;
+
+ //inserting
+ QPoint m_insertBegin;
+ QRect m_insertRect;
+ ObjectTreeItem *m_tree;
+
+ QGuardedPtr<Form> m_form;
+ bool m_mousePressEventReceived;
+ QMouseEvent m_mouseReleaseEvent;
+ QGuardedPtr<QObject> m_objectForMouseReleaseEvent;
+
+ friend class InsertWidgetCommand;
+ friend class PasteWidgetCommand;
+ friend class DeleteWidgetCommand;
+ friend class FormIO;
+};
+
+//! Interface for adding dynamically created (at design time) widget to event eater.
+/*! This is currently used by KexiDBFieldEdit from Kexi forms. */
+class KFORMEDITOR_EXPORT DesignTimeDynamicChildWidgetHandler
+{
+ public:
+ DesignTimeDynamicChildWidgetHandler();
+ ~DesignTimeDynamicChildWidgetHandler();
+
+ protected:
+ void childWidgetAdded(QWidget* w);
+ void assignItem(ObjectTreeItem* item) { m_item = item; }
+
+ private:
+ ObjectTreeItem* m_item;
+ friend class InsertWidgetCommand;
+ friend class FormIO;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/editlistviewdialog.cpp b/kexi/formeditor/editlistviewdialog.cpp
new file mode 100644
index 000000000..5b128ec87
--- /dev/null
+++ b/kexi/formeditor/editlistviewdialog.cpp
@@ -0,0 +1,460 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <qheader.h>
+#include <qlayout.h>
+
+#include <klistview.h>
+#include <ktabwidget.h>
+#include <klistbox.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <koproperty/editor.h>
+#include <koproperty/set.h>
+#include <koproperty/property.h>
+
+#include "editlistviewdialog.h"
+
+namespace KFormDesigner {
+
+//////////////////////////////////////////////////////////////////////////////////
+/// A Dialog to edit the contents of a listview /////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+
+EditListViewDialog::EditListViewDialog(QWidget *parent)
+//js(kde3.2 dependent) : KDialogBase(Tabbed, 0/* WFlags */, parent, "editlistview_dialog", true, i18n("Edit listview contents"), Ok|Cancel, Ok, false)
+: KDialogBase(Tabbed, i18n("Edit Listview Contents"), Ok|Cancel, Ok, parent, "editlistview_dialog", true /* modal */, false)
+{
+ m_column = addPage(i18n("Columns"));
+ m_contents = addPage(i18n("Contents"));
+
+ ///////// Setup the "Contents" page /////////////////////////////
+ QHBoxLayout *layout = new QHBoxLayout(m_contents, 0, 6);
+
+ //// Setup the icon toolbar /////////////////
+ QVBoxLayout *vlayout = new QVBoxLayout(layout, 3);
+ QToolButton *newRow = new QToolButton(m_contents);
+ newRow->setIconSet(BarIconSet("edit_add"));
+ newRow->setTextLabel(i18n("&Add Item"), true);
+ vlayout->addWidget(newRow);
+ m_buttons.insert(BNewRow, newRow);
+ connect(newRow, SIGNAL(clicked()), this, SLOT(newRow()));
+
+ QToolButton *newChild = new QToolButton(m_contents);
+ newChild->setIconSet(BarIconSet("1rightarrow"));
+ newChild->setTextLabel(i18n("New &Subitem"), true);
+ vlayout->addWidget(newChild);
+ m_buttons.insert(BNewChild, newChild);
+ connect(newChild, SIGNAL(clicked()), this, SLOT(newChildRow()));
+
+ QToolButton *delRow = new QToolButton(m_contents);
+ delRow->setIconSet(BarIconSet("edit_remove"));
+ delRow->setTextLabel(i18n("&Remove Item"), true);
+ vlayout->addWidget(delRow);
+ m_buttons.insert(BRemRow, delRow);
+ connect(delRow, SIGNAL(clicked()), this, SLOT(removeRow()));
+
+ QToolButton *rowUp = new QToolButton(m_contents);
+ rowUp->setIconSet(BarIconSet("1uparrow"));
+ rowUp->setTextLabel(i18n("Move Item &Up"), true);
+ vlayout->addWidget(rowUp);
+ m_buttons.insert(BRowUp, rowUp);
+ connect(rowUp, SIGNAL(clicked()), this, SLOT(MoveRowUp()));
+
+ QToolButton *rowDown = new QToolButton(m_contents);
+ rowDown->setIconSet(BarIconSet("1downarrow"));
+ rowDown->setTextLabel(i18n("Move Item &Down"), true);
+ vlayout->addWidget(rowDown);
+ m_buttons.insert(BRowDown, rowDown);
+ connect(rowDown, SIGNAL(clicked()), this, SLOT(MoveRowDown()));
+ vlayout->addStretch();
+
+ //// The listview ///////////
+ m_listview = new KListView(m_contents, "editlistview_listview");
+ m_listview->setItemsRenameable(true);
+ m_listview->setItemsMovable(true);
+ m_listview->setDragEnabled(true);
+ m_listview->setAllColumnsShowFocus(true);
+ m_listview->setRootIsDecorated(true);
+ m_listview->setDropVisualizer(true);
+ m_listview->setAcceptDrops(true);
+ m_listview->setSorting(-1);
+ layout->addWidget(m_listview);
+ m_listview->setFocus();
+ connect(m_listview, SIGNAL(currentChanged(QListViewItem*)), this, SLOT(updateButtons(QListViewItem*)));
+ connect(m_listview, SIGNAL(moved(QListViewItem*, QListViewItem*, QListViewItem*)), this, SLOT(updateButtons(QListViewItem*)));
+
+ /////////////////// Setup the columns page ////////////////
+ QHBoxLayout *hbox = new QHBoxLayout(m_column, 0, 6);
+
+ // The "item properties" field
+ m_editor = new KoProperty::Editor(m_column, "editcolumn_propeditor");
+ m_propSet = new KoProperty::Set(this, "columns");
+ m_propSet->addProperty(new KoProperty::Property("caption", "Caption", i18n("Caption"),i18n("Caption")));
+ m_propSet->addProperty(new KoProperty::Property("width", 100, i18n("Width"), i18n("Width")));
+ m_propSet->addProperty(new KoProperty::Property("clickable", QVariant(true, 3), i18n("Clickable"), i18n("Clickable") ));
+ m_propSet->addProperty(new KoProperty::Property("resizable", QVariant(true, 3), i18n("Resizable"), i18n("Resizable") ));
+ m_propSet->addProperty(new KoProperty::Property("fullwidth", QVariant(false, 3), i18n("Full Width"), i18n("Full Width") ));
+ m_editor->changeSet(m_propSet);
+ connect(m_propSet, SIGNAL(propertyChanged(KoProperty::Set & KoProperty::Property&)),
+ this, SLOT(changeProperty(KoProperty::Set & KoProperty::Property&)));
+
+ // Setup the icon toolbar //////////
+ QVBoxLayout *vbox = new QVBoxLayout(hbox, 3);
+ QToolButton *add = new QToolButton(m_column);
+ add->setIconSet(BarIconSet("edit_add"));
+ add->setTextLabel(i18n("&Add Item"), true);
+ vbox->addWidget(add);
+ m_buttons.insert(BColAdd, add);
+ connect(add, SIGNAL(clicked()), this, SLOT(newItem()));
+
+ QToolButton *remove = new QToolButton(m_column);
+ remove->setIconSet(BarIconSet("edit_remove"));
+ remove->setTextLabel(i18n("&Remove Item"), true);
+ vbox->addWidget(remove);
+ m_buttons.insert(BColRem, remove);
+ connect(remove, SIGNAL(clicked()), this, SLOT(removeItem()));
+
+ QToolButton *up = new QToolButton(m_column);
+ up->setIconSet(BarIconSet("1uparrow"));
+ up->setTextLabel(i18n("Move Item &Up"), true);
+ vbox->addWidget(up);
+ m_buttons.insert(BColUp, up);
+ connect(up, SIGNAL(clicked()), this, SLOT(MoveItemUp()));
+
+ QToolButton *down = new QToolButton(m_column);
+ down->setIconSet(BarIconSet("1downarrow"));
+ down->setTextLabel(i18n("Move Item &Down"), true);
+ vbox->addWidget(down);
+ m_buttons.insert(BColDown, down);
+ connect(down, SIGNAL(clicked()), this, SLOT(MoveItemDown()));
+ vbox->addStretch();
+
+ // The listbox with columns name /////
+ m_listbox = new KListBox(m_column, "editlistview_columns");
+ m_listbox->setFocus();
+ hbox->insertWidget(0, m_listbox);
+ hbox->addWidget(m_editor);
+ connect(m_listbox, SIGNAL(currentChanged(QListBoxItem*)), this, SLOT(updateItemProperties(QListBoxItem*)));
+
+ //// Init dialog and display it ////////////////////////
+ setInitialSize(QSize(500, 300), true);
+}
+
+int
+EditListViewDialog::exec(QListView *listview)
+{
+ if(!listview)
+ {
+ kdDebug() << "EditListViewDialog ERROR: no listview " << endl;
+ return 0;
+ }
+
+ // We copy the contents of the listview into our listview
+ for(int i = 0; i < listview->columns(); i++)
+ {
+ m_listview->addColumn(listview->columnText(i), listview->columnWidth(i));
+ m_listview->header()->setClickEnabled(listview->header()->isClickEnabled(i), i);
+ m_listview->header()->setResizeEnabled(listview->header()->isResizeEnabled(i), i);
+ m_listview->header()->setStretchEnabled(listview->header()->isStretchEnabled(i), i);
+ m_listview->setRenameable(i, true);
+ }
+ QListViewItem *item = listview->firstChild();
+ while(item) {
+ loadChildNodes(m_listview, item, 0);
+ item = item->nextSibling();
+ }
+
+ m_listview->setSelected(m_listview->firstChild(), true);
+ if(!m_listview->firstChild())
+ updateButtons(0);
+
+ for(int i = 0; i < listview->columns(); i++)
+ m_listbox->insertItem(listview->columnText(i));
+ m_listbox->setSelected(0, true);
+
+ // and we exec the dialog
+ int r = KDialogBase::exec();
+ if(r == QDialog::Accepted)
+ {
+ listview->clear();
+ // We copy the contents of our listview back in the listview
+ for(int i = 0; i < m_listview->columns(); i++)
+ {
+ if(listview->columns() <= i)
+ listview->addColumn(m_listview->columnText(i), m_listview->columnWidth(i));
+ else
+ {
+ listview->setColumnText(i, m_listview->columnText(i));
+ listview->setColumnWidth(i, m_listview->columnWidth(i));
+ }
+ listview->header()->setClickEnabled(m_listview->header()->isClickEnabled(i), i);
+ listview->header()->setResizeEnabled(m_listview->header()->isResizeEnabled(i), i);
+ listview->header()->setStretchEnabled(m_listview->header()->isStretchEnabled(i), i);
+ }
+
+ QListViewItem *item = m_listview->firstChild();
+ while(item)
+ {
+ loadChildNodes(listview, item, 0);
+ item = item->nextSibling();
+ }
+ }
+ return r;
+}
+
+/// Columns page slots ///////
+void
+EditListViewDialog::changeProperty(KoProperty::Set& set, KoProperty::Property& property)
+{
+ if(&set != m_propSet)
+ return;
+
+ QString name = property.name();
+ QVariant value = property.value();
+ if(name == "caption") {
+ m_propSet->blockSignals(true); // we need to block signals because changeItem will modify selection, and call updateItemProperties
+ m_listbox->changeItem(value.toString(), m_listbox->currentItem());
+ m_listview->setColumnText(m_listbox->currentItem(), value.toString());
+ m_propSet->blockSignals(false);
+ }
+ else if(name == "width")
+ m_listview->setColumnWidth(m_listbox->currentItem(), value.toInt());
+ else if(name == "resizable")
+ m_listview->header()->setResizeEnabled(value.toBool(), m_listbox->currentItem());
+ else if(name == "clickable")
+ m_listview->header()->setClickEnabled(value.toBool(), m_listbox->currentItem());
+ else if(name == "fullwidth")
+ m_listview->header()->setStretchEnabled(value.toBool(), m_listbox->currentItem());
+}
+
+void
+EditListViewDialog::updateItemProperties(QListBoxItem *item)
+{
+ if(!item)
+ return;
+
+ int id = m_listbox->index(item);
+ if(m_propSet) {
+ m_propSet->blockSignals(true); // we don't want changeProperty to be called
+ (*m_propSet)["caption"].setValue(m_listview->columnText(id), false);
+ (*m_propSet)["width"].setValue(m_listview->columnWidth(id), false);
+ (*m_propSet)["clickable"].setValue(QVariant(m_listview->header()->isClickEnabled(id), 4), false);
+ (*m_propSet)["resizable"].setValue(QVariant(m_listview->header()->isResizeEnabled(id), 4), false);
+ (*m_propSet)["fullwidth"].setValue(QVariant(m_listview->header()->isStretchEnabled(id), 4), false);
+ m_propSet->blockSignals(false);
+ m_editor->changeSet(m_propSet);
+ }
+
+ m_buttons[BColUp]->setEnabled(item->prev());
+ m_buttons[BColDown]->setEnabled(item->next());
+}
+
+void
+EditListViewDialog::newItem()
+{
+ m_listbox->insertItem(i18n("New Column"));
+ m_listview->addColumn(i18n("New Column"));
+ m_listview->setRenameable(m_listview->columns() - 1, true);
+ m_listbox->setCurrentItem(m_listbox->count() - 1);
+ m_buttons[BColRem]->setEnabled(true);
+}
+
+void
+EditListViewDialog::removeItem()
+{
+ int current = m_listbox->currentItem();
+ if(m_listbox->item(current + 1))
+ m_listbox->setCurrentItem(current +1);
+ else
+ m_listbox->setCurrentItem(current - 1);
+
+ m_listview->removeColumn(current);
+ m_listbox->removeItem(current);
+ if(m_listbox->count() == 0)
+ m_buttons[BColRem]->setEnabled(false);
+}
+
+void
+EditListViewDialog::MoveItemUp()
+{
+ int current = m_listbox->currentItem();
+ QString text = m_listbox->text(current);
+ m_listbox->blockSignals(true);
+
+ m_listbox->changeItem(m_listbox->text(current - 1), current);
+ m_listview->setColumnText(current, m_listview->columnText(current - 1));
+ m_listview->setColumnWidth(current, m_listview->columnWidth(current - 1));
+ m_listview->header()->setClickEnabled(m_listview->header()->isClickEnabled(current - 1), current);
+ m_listview->header()->setResizeEnabled(m_listview->header()->isResizeEnabled(current - 1), current);
+ m_listview->header()->setStretchEnabled(m_listview->header()->isStretchEnabled(current - 1), current);
+
+ m_listbox->changeItem(text, current - 1);
+ m_listview->setColumnText(current - 1, (*m_propSet)["caption"].value().toString());
+ m_listview->setColumnWidth(current - 1,(*m_propSet)["width"].value().toBool());
+ m_listview->header()->setClickEnabled((*m_propSet)["clickable"].value().toBool(), current - 1);
+ m_listview->header()->setResizeEnabled((*m_propSet)["resizable"].value().toBool(), current - 1);
+ m_listview->header()->setStretchEnabled((*m_propSet)["fullwidth"].value().toBool(), current - 1);
+
+ m_listbox->blockSignals(false);
+ m_listbox->setCurrentItem(current - 1);
+}
+
+void
+EditListViewDialog::MoveItemDown()
+{
+ int current = m_listbox->currentItem();
+ QString text = m_listbox->text(current);
+ m_listbox->blockSignals(true);
+
+ m_listbox->changeItem(m_listbox->text(current+1), current);
+ m_listview->setColumnText(current, m_listview->columnText(current + 1));
+ m_listview->setColumnWidth(current, m_listview->columnWidth(current + 1));
+ m_listview->header()->setClickEnabled(m_listview->header()->isClickEnabled(current + 1), current);
+ m_listview->header()->setResizeEnabled(m_listview->header()->isResizeEnabled(current + 1), current);
+ m_listview->header()->setStretchEnabled(m_listview->header()->isStretchEnabled(current + 1), current);
+
+ m_listbox->changeItem(text, current+1);
+ m_listview->setColumnText(current + 1, (*m_propSet)["caption"].value().toString());
+ m_listview->setColumnWidth(current + 1,(*m_propSet)["width"].value().toBool());
+ m_listview->header()->setClickEnabled((*m_propSet)["clickable"].value().toBool(), current + 1);
+ m_listview->header()->setResizeEnabled((*m_propSet)["resizable"].value().toBool(), current + 1);
+ m_listview->header()->setStretchEnabled((*m_propSet)["fullwidth"].value().toBool(), current + 1);
+
+ m_listbox->blockSignals(false);
+ m_listbox->setCurrentItem(current + 1);
+}
+
+
+/// Contents page slots ////////
+void
+EditListViewDialog::updateButtons(QListViewItem *item)
+{
+ if(!item)
+ {
+ for(int i = BNewChild; i <= BRowDown; i++)
+ m_buttons[i]->setEnabled(false);
+ return;
+ }
+
+ m_buttons[BNewChild]->setEnabled(true);
+ m_buttons[BRemRow]->setEnabled(true);
+ m_buttons[BRowUp]->setEnabled( (item->itemAbove() && (item->itemAbove()->parent() == item->parent())) );
+ m_buttons[BRowDown]->setEnabled(item->nextSibling());
+}
+
+void
+EditListViewDialog::loadChildNodes(QListView *listview, QListViewItem *item, QListViewItem *parent)
+{
+ QListViewItem *newItem;
+ if(listview->inherits("KListView"))
+ {
+ if(parent)
+ newItem = new KListViewItem(parent);
+ else
+ newItem = new KListViewItem(listview);
+ }
+ else
+ {
+ if(parent)
+ newItem = new QListViewItem(parent);
+ else
+ newItem = new QListViewItem(listview);
+ }
+
+ // We need to move the item at the end, which is the expected behaviour (by default it is inserted at the beginning)
+ QListViewItem *last;
+ if(parent)
+ last = parent->firstChild();
+ else
+ last = listview->firstChild();
+
+ while(last->nextSibling())
+ last = last->nextSibling();
+ newItem->moveItem(last);
+
+ // We copy the text of all the columns
+ for(int i = 0; i < listview->columns(); i++)
+ newItem->setText(i, item->text(i));
+
+ QListViewItem *child = item->firstChild();
+ if(child)
+ newItem->setOpen(true);
+ while(child) {
+ loadChildNodes(listview, child, newItem);
+ child = child->nextSibling();
+ }
+}
+
+void
+EditListViewDialog::newRow()
+{
+ KListViewItem *parent = (KListViewItem*)m_listview->selectedItem();
+ if(parent)
+ parent = (KListViewItem*)parent->parent();
+ KListViewItem *item;
+ if(parent)
+ item = new KListViewItem(parent, m_listview->selectedItem());
+ else
+ item = new KListViewItem(m_listview, m_listview->selectedItem());
+ item->setText(0, i18n("New Item"));
+ m_listview->setCurrentItem(item);
+}
+
+void
+EditListViewDialog::newChildRow()
+{
+ KListViewItem *parent = (KListViewItem*)m_listview->currentItem();
+ KListViewItem *item;
+ if(parent)
+ item = new KListViewItem(parent);
+ else
+ item = new KListViewItem(m_listview, m_listview->currentItem());
+ item->setText(0, i18n("Sub Item"));
+
+ m_listview->setCurrentItem(item);
+ parent->setOpen(true);
+}
+
+void
+EditListViewDialog::removeRow()
+{
+ delete m_listview->currentItem();
+}
+
+void
+EditListViewDialog::MoveRowUp()
+{
+ QListViewItem *item = m_listview->currentItem()->itemAbove();
+ item->moveItem(m_listview->currentItem());
+ updateButtons(m_listview->currentItem());
+}
+
+void
+EditListViewDialog::MoveRowDown()
+{
+ QListViewItem *before = m_listview->currentItem();
+ before->moveItem(before->nextSibling());
+ updateButtons(before);
+}
+
+}
+
+#include "editlistviewdialog.moc"
diff --git a/kexi/formeditor/editlistviewdialog.h b/kexi/formeditor/editlistviewdialog.h
new file mode 100644
index 000000000..09de44294
--- /dev/null
+++ b/kexi/formeditor/editlistviewdialog.h
@@ -0,0 +1,93 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef EDITLISTVIEW_DIALOG_H
+#define EDITLISTVIEW_DIALOG_H
+
+#include <qintdict.h>
+#include <qtoolbutton.h>
+#include <kdialogbase.h>
+
+class QFrame;
+class QListView;
+class QListViewItem;
+class KListViewItem;
+class KListView;
+class KListBox;
+class QListBoxItem;
+
+namespace KoProperty {
+ class Property;
+ class Set;
+ class Editor;
+}
+
+namespace KFormDesigner {
+
+//! A dialog to edit the contents of a listvuew (KListView or QListView)
+/*! The dialog contains two pages, one to edit columns and one to edit ist items.
+ KoProperty::Editor is used in columns to edit column properties
+ (there are two properties not supported by Qt Designer: 'width' and 'resizable').
+ The user can enter list contents inside the list
+ using KListViewItem::setRenameable(). Pixmaps are not yet supported. */
+class KFORMEDITOR_EXPORT EditListViewDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ EditListViewDialog(QWidget *parent);
+ ~EditListViewDialog() {}
+
+ int exec(QListView *listview);
+
+ public slots:
+ // Columns page
+ void updateItemProperties(QListBoxItem*);
+ void newItem();
+ void removeItem();
+ void MoveItemUp();
+ void MoveItemDown();
+ void changeProperty(KoProperty::Set& set, KoProperty::Property& property);
+
+ // Contents page
+ void updateButtons(QListViewItem*);
+ void newRow();
+ void newChildRow();
+ void removeRow();
+ void MoveRowUp();
+ void MoveRowDown();
+
+ protected:
+ /*! Loads all child items of \a item into \a listview (may be different from the \a items 's listview) as child of \a parent item.
+ This is used to copy the contents of a listview into another listview. */
+ void loadChildNodes(QListView *listview, QListViewItem *item, QListViewItem *parent);
+
+ protected:
+ enum { BNewRow = 10, BNewChild, BRemRow, BRowUp, BRowDown , BColAdd = 20, BColRem, BColUp, BColDown };
+ KoProperty::Editor *m_editor;
+ KoProperty::Set *m_propSet;
+ QFrame *m_contents, *m_column;
+ KListBox *m_listbox;
+ KListView *m_listview;
+ QIntDict<QToolButton> m_buttons;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/events.cpp b/kexi/formeditor/events.cpp
new file mode 100644
index 000000000..e96b9f4f1
--- /dev/null
+++ b/kexi/formeditor/events.cpp
@@ -0,0 +1,145 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qdom.h>
+#include <kdebug.h>
+
+#include "events.h"
+
+namespace KFormDesigner {
+
+Connection::Connection(const QString &sender, const QString &signal,
+ const QString &receiver, const QString &slot)
+{
+ m_sender = sender;
+ m_signal = signal;
+ m_receiver = receiver;
+ m_slot = slot;
+}
+
+ ///////////////////////////////////////
+
+ConnectionBuffer::ConnectionBuffer()
+{
+ setAutoDelete(true);
+}
+
+void
+ConnectionBuffer::fixName(const QString &oldName, const QString &newName)
+{
+ for(Connection *c = first(); c; c = next())
+ {
+ if(c->sender() == oldName)
+ c->setSender(newName);
+ if(c->receiver() == oldName)
+ c->setReceiver(newName);
+ }
+}
+
+ConnectionBuffer*
+ConnectionBuffer::allConnectionsForWidget(const QString &widget)
+{
+ ConnectionBuffer *list = new ConnectionBuffer();
+ list->setAutoDelete(false); // or it will delete all our connections
+ for(Connection *c = first(); c; c = next())
+ {
+ if((c->sender() == widget) || (c->receiver() == widget))
+ list->append(c);
+ }
+
+ return list;
+}
+
+void
+ConnectionBuffer::save(QDomNode &parentNode)
+{
+ if(isEmpty())
+ return;
+
+ QDomDocument domDoc = parentNode.ownerDocument();
+ QDomElement connections;
+ if(!parentNode.namedItem("connections").isNull())
+ connections = parentNode.namedItem("connections").toElement();
+ else
+ connections = domDoc.createElement("connections");
+ parentNode.appendChild(connections);
+
+ for(Connection *c = first(); c; c = next())
+ {
+ QDomElement connection = domDoc.createElement("connection");
+ connection.setAttribute("language", "C++");
+ connections.appendChild(connection);
+
+ QDomElement sender = domDoc.createElement("sender");
+ connection.appendChild(sender);
+ QDomText senderText = domDoc.createTextNode(c->sender());
+ sender.appendChild(senderText);
+
+ QDomElement signal = domDoc.createElement("signal");
+ connection.appendChild(signal);
+ QDomText signalText = domDoc.createTextNode(c->signal());
+ signal.appendChild(signalText);
+
+ QDomElement receiver = domDoc.createElement("receiver");
+ connection.appendChild(receiver);
+ QDomText receiverText = domDoc.createTextNode(c->receiver());
+ receiver.appendChild(receiverText);
+
+ QDomElement slot = domDoc.createElement("slot");
+ connection.appendChild(slot);
+ QDomText slotText = domDoc.createTextNode(c->slot());
+ slot.appendChild(slotText);
+ }
+}
+
+void
+ConnectionBuffer::saveAllConnectionsForWidget(const QString &widget, QDomNode parentNode)
+{
+ ConnectionBuffer *buff = allConnectionsForWidget(widget);
+ buff->save(parentNode);
+ delete buff;
+}
+
+void
+ConnectionBuffer::load(QDomNode node)
+{
+ for(QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ Connection *conn = new Connection();
+ conn->setSender(n.namedItem("sender").toElement().text());
+ conn->setSignal(n.namedItem("signal").toElement().text());
+ conn->setReceiver(n.namedItem("receiver").toElement().text());
+ conn->setSlot(n.namedItem("slot").toElement().text());
+ append(conn);
+ }
+}
+
+void
+ConnectionBuffer::removeAllConnectionsForWidget(const QString &widget)
+{
+ for(Connection *c = first(); c; c = next())
+ {
+ if((c->sender() == widget) || (c->receiver() == widget))
+ removeRef(c);
+ }
+}
+
+}
+
+//#include "events.moc"
diff --git a/kexi/formeditor/events.h b/kexi/formeditor/events.h
new file mode 100644
index 000000000..c9e1b2cd8
--- /dev/null
+++ b/kexi/formeditor/events.h
@@ -0,0 +1,78 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNEREVENTS_H
+#define KFORMDESIGNEREVENTS_H
+
+#include <qptrlist.h>
+#include <qstring.h>
+
+class QDomNode;
+
+namespace KFormDesigner {
+
+class KFORMEDITOR_EXPORT Connection
+{
+ public:
+ Connection(const QString &sender, const QString &signal,
+ const QString &receiver, const QString &slot);
+ Connection() {;}
+ ~Connection() {;}
+
+ QString sender() const { return m_sender; }
+ QString receiver() const { return m_receiver; }
+ QString signal() const { return m_signal; }
+ QString slot() const { return m_slot; }
+
+ void setSender(const QString &v) { m_sender = v; }
+ void setReceiver(const QString &v) { m_receiver = v; }
+ void setSignal(const QString &v) { m_signal = v; }
+ void setSlot(const QString &v) { m_slot = v; }
+
+ protected:
+ QString m_sender;
+ QString m_signal;
+ QString m_receiver;
+ QString m_slot;
+};
+
+typedef QPtrList<Connection> ConnectionList;
+
+class KFORMEDITOR_EXPORT ConnectionBuffer : public ConnectionList
+{
+ public:
+ ConnectionBuffer();
+ ~ConnectionBuffer() {;}
+
+ void save(QDomNode &parentNode);
+ void load(QDomNode parentNode);
+
+ /*! This function is called when a widget is renamed from \a oldname
+ to \a newname. All the Connections for this widget are updated. */
+ void fixName(const QString &oldname, const QString &newName);
+
+ ConnectionBuffer* allConnectionsForWidget(const QString &widget);
+ void saveAllConnectionsForWidget(const QString &widget, QDomNode parentNode);
+ void removeAllConnectionsForWidget(const QString &widget);
+};
+
+}
+
+#endif
+
diff --git a/kexi/formeditor/factories/Makefile.am b/kexi/formeditor/factories/Makefile.am
new file mode 100644
index 000000000..e19bf6b36
--- /dev/null
+++ b/kexi/formeditor/factories/Makefile.am
@@ -0,0 +1,20 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+INCLUDES = -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/formeditor \
+ -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/lib -I$(top_srcdir)/lib/koproperty -I$(top_srcdir)/lib/kofficecore $(all_includes)
+kde_module_LTLIBRARIES = kformdesigner_containers.la kformdesigner_stdwidgets.la
+kformdesigner_containers_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module
+kformdesigner_containers_la_SOURCES = containerfactory.cpp
+kformdesigner_containers_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la
+
+kformdesigner_stdwidgets_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module
+kformdesigner_stdwidgets_la_SOURCES = stdwidgetfactory.cpp
+kformdesigner_stdwidgets_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la
+
+
+servicesdir=$(kde_servicesdir)/kformdesigner
+services_DATA = kformdesigner_containers.desktop kformdesigner_stdwidgets.desktop
+
+METASOURCES = AUTO
+
diff --git a/kexi/formeditor/factories/containerfactory.cpp b/kexi/formeditor/factories/containerfactory.cpp
new file mode 100644
index 000000000..d098c290c
--- /dev/null
+++ b/kexi/formeditor/factories/containerfactory.cpp
@@ -0,0 +1,936 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qwidgetstack.h>
+#include <qframe.h>
+#include <qbuttongroup.h>
+#include <qwidget.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qstring.h>
+#include <qpopupmenu.h>
+#include <qdom.h>
+#include <qevent.h>
+#include <qobjectlist.h>
+#include <qpainter.h>
+#include <qvaluevector.h>
+#include <qfileinfo.h>
+#include <qscrollview.h>
+#include <qtabbar.h>
+#include <qsplitter.h>
+#include <qlayout.h>
+
+#include <kiconloader.h>
+#include <kgenericfactory.h>
+#include <ktextedit.h>
+#include <klineedit.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kdeversion.h>
+
+#include "containerfactory.h"
+#include "container.h"
+#include "form.h"
+#include "formIO.h"
+#include "objecttree.h"
+#include "commands.h"
+#include "formmanager.h"
+#include "widgetlibrary.h"
+#include <formeditor/utils.h>
+
+#if KDE_VERSION < KDE_MAKE_VERSION(3,1,9)
+# define KInputDialog QInputDialog
+# include <qinputdialog.h>
+# include <qlineedit.h>
+#else
+# include <kinputdialog.h>
+#endif
+
+ContainerWidget::ContainerWidget(QWidget *parent, const char *name)
+ : QWidget(parent, name)
+{
+}
+
+ContainerWidget::~ContainerWidget()
+{
+}
+
+QSize ContainerWidget::sizeHint() const
+{
+ return QSize(30,30); //default
+}
+
+void ContainerWidget::dragMoveEvent( QDragMoveEvent *e )
+{
+ QWidget::dragMoveEvent(e);
+ emit handleDragMoveEvent(e);
+}
+
+void ContainerWidget::dropEvent( QDropEvent *e )
+{
+ QWidget::dropEvent(e);
+ emit handleDropEvent(e);
+}
+
+////////////////////////
+
+GroupBox::GroupBox(const QString & title, QWidget *parent, const char *name)
+ : QGroupBox(title, parent, name)
+{
+}
+
+GroupBox::~GroupBox()
+{
+}
+
+void GroupBox::dragMoveEvent( QDragMoveEvent *e )
+{
+ QGroupBox::dragMoveEvent(e);
+ emit handleDragMoveEvent(e);
+}
+
+void GroupBox::dropEvent( QDropEvent *e )
+{
+ QGroupBox::dropEvent(e);
+ emit handleDropEvent(e);
+}
+
+////////////////////////
+
+KFDTabWidget::KFDTabWidget(QWidget *parent, const char *name)
+ : KFormDesigner::TabWidget(parent, name)
+{
+}
+
+KFDTabWidget::~KFDTabWidget()
+{
+}
+
+QSize
+KFDTabWidget::sizeHint() const
+{
+ QSize s(30,30); // default min size
+ for(int i=0; i < count(); i++)
+ s = s.expandedTo( KFormDesigner::getSizeFromChildren(page(i)) );
+
+ return s + QSize(10/*margin*/, tabBar()->height() + 20/*margin*/);
+}
+
+void KFDTabWidget::dragMoveEvent( QDragMoveEvent *e )
+{
+ TabWidgetBase::dragMoveEvent( e );
+ if (dynamic_cast<ContainerWidget*>(currentPage()))
+ emit dynamic_cast<ContainerWidget*>(currentPage())->handleDragMoveEvent(e);
+ emit handleDragMoveEvent(e);
+}
+
+void KFDTabWidget::dropEvent( QDropEvent *e )
+{
+ TabWidgetBase::dropEvent( e );
+ if (dynamic_cast<ContainerWidget*>(currentPage()))
+ emit dynamic_cast<ContainerWidget*>(currentPage())->handleDropEvent(e);
+ emit handleDropEvent(e);
+}
+
+/// Various layout widgets /////////////////:
+
+HBox::HBox(QWidget *parent, const char *name)
+ : QFrame(parent, name), m_preview(false)
+{}
+
+void
+HBox::paintEvent(QPaintEvent *)
+{
+ if(m_preview) return;
+ QPainter p(this);
+ p.setPen(QPen(red, 2, Qt::DashLine));
+ p.drawRect(1, 1, width()-1, height() - 1);
+}
+
+VBox::VBox(QWidget *parent, const char *name)
+ : QFrame(parent, name), m_preview(false)
+{}
+
+void
+VBox::paintEvent(QPaintEvent *)
+{
+ if(m_preview) return;
+ QPainter p(this);
+ p.setPen(QPen(blue, 2, Qt::DashLine));
+ p.drawRect(1, 1, width()-1, height() - 1);
+}
+
+Grid::Grid(QWidget *parent, const char *name)
+ : QFrame(parent, name), m_preview(false)
+{}
+
+void
+Grid::paintEvent(QPaintEvent *)
+{
+ if(m_preview) return;
+ QPainter p(this);
+ p.setPen(QPen(darkGreen, 2, Qt::DashLine));
+ p.drawRect(1, 1, width()-1, height() - 1);
+}
+
+HFlow::HFlow(QWidget *parent, const char *name)
+ : QFrame(parent, name), m_preview(false)
+{}
+
+void
+HFlow::paintEvent(QPaintEvent *)
+{
+ if(m_preview) return;
+ QPainter p(this);
+ p.setPen(QPen(magenta, 2, Qt::DashLine));
+ p.drawRect(1, 1, width()-1, height() - 1);
+}
+
+VFlow::VFlow(QWidget *parent, const char *name)
+ : QFrame(parent, name), m_preview(false)
+{}
+
+void
+VFlow::paintEvent(QPaintEvent *)
+{
+ if(m_preview) return;
+ QPainter p(this);
+ p.setPen(QPen(cyan, 2, Qt::DashLine));
+ p.drawRect(1, 1, width()-1, height() - 1);
+}
+
+QSize
+VFlow::sizeHint() const
+{
+ if(layout())
+ return layout()->sizeHint();
+ else
+ return QSize(700, 50); // default
+}
+
+/////// Tab related KCommand (to allow tab creation/deletion undoing)
+
+InsertPageCommand::InsertPageCommand(KFormDesigner::Container *container, QWidget *parent)
+ : KCommand()
+{
+ m_containername = container->widget()->name();
+ m_form = container->form();
+ m_parentname = parent->name();
+ m_pageid = -1;
+}
+
+void
+InsertPageCommand::execute()
+{
+ KFormDesigner::Container *container = m_form->objectTree()->lookup(m_containername)->container();
+ QWidget *parent = m_form->objectTree()->lookup(m_parentname)->widget();
+ if(m_name.isEmpty()) {
+ m_name = container->form()->objectTree()->generateUniqueName(
+ container->form()->library()->displayName("QWidget").latin1(),
+ /*!numberSuffixRequired*/false);
+ }
+
+ QWidget *page = container->form()->library()->createWidget("QWidget", parent, m_name.latin1(), container);
+// QWidget *page = new ContainerWidget(parent, m_name.latin1());
+// new KFormDesigner::Container(container, page, parent);
+
+ QCString classname = parent->className();
+ if(classname == "KFDTabWidget")
+ {
+ TabWidgetBase *tab = dynamic_cast<TabWidgetBase*>(parent);
+ QString n = i18n("Page %1").arg(tab->count() + 1);
+ tab->addTab(page, n);
+ tab->showPage(page);
+
+ KFormDesigner::ObjectTreeItem *item = container->form()->objectTree()->lookup(m_name);
+ item->addModifiedProperty("title", n);
+ }
+ else if(classname == "QWidgetStack")
+ {
+ QWidgetStack *stack = (QWidgetStack*)parent;
+ stack->addWidget(page, m_pageid);
+ stack->raiseWidget(page);
+ m_pageid = stack->id(page);
+
+ KFormDesigner::ObjectTreeItem *item = container->form()->objectTree()->lookup(m_name);
+ item->addModifiedProperty("id", stack->id(page));
+ }
+}
+
+void
+InsertPageCommand::unexecute()
+{
+ QWidget *page = m_form->objectTree()->lookup(m_name)->widget();
+ QWidget *parent = m_form->objectTree()->lookup(m_parentname)->widget();
+
+ KFormDesigner::WidgetList list;
+ list.append(page);
+ KCommand *com = new KFormDesigner::DeleteWidgetCommand(list, m_form);
+
+ QCString classname = parent->className();
+ if(classname == "KFDTabWidget")
+ {
+ TabWidgetBase *tab = dynamic_cast<TabWidgetBase*>(parent);
+ tab->removePage(page);
+ }
+ else if(classname == "QWidgetStack")
+ {
+ QWidgetStack *stack = (QWidgetStack*)parent;
+ int id = stack->id(page) - 1;
+ while(!stack->widget(id))
+ id--;
+
+ stack->raiseWidget(id);
+ stack->removeWidget(page);
+ }
+
+ com->execute();
+ delete com;
+}
+
+QString
+InsertPageCommand::name() const
+{
+ return i18n("Add Page");
+}
+
+/////// Sub forms ////////////////////////:
+
+SubForm::SubForm(QWidget *parent, const char *name)
+: QScrollView(parent, name), m_form(0), m_widget(0)
+{
+ setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
+ viewport()->setPaletteBackgroundColor(colorGroup().mid());
+}
+
+void
+SubForm::setFormName(const QString &name)
+{
+ if(name.isEmpty())
+ return;
+
+ QFileInfo info(name);
+ if(!info.exists()
+ || (KFormDesigner::FormManager::self()->activeForm()
+ && (info.fileName() == KFormDesigner::FormManager::self()->activeForm()->filename()) ) )
+ return; // we check if this is valid form
+
+ // we create the container widget
+ delete m_widget;
+ m_widget = new QWidget(viewport(), "subform_widget");
+// m_widget->show();
+ addChild(m_widget);
+ m_form = new KFormDesigner::Form(
+ KFormDesigner::FormManager::self()->activeForm()->library(), this->name());
+ m_form->createToplevel(m_widget);
+
+ // and load the sub form
+ KFormDesigner::FormIO::loadFormFromFile(m_form, m_widget, name);
+ m_form->setDesignMode(false);
+
+ m_formName = name;
+
+}
+
+///// The factory /////////////////////////
+
+ContainerFactory::ContainerFactory(QObject *parent, const char *, const QStringList &)
+ : KFormDesigner::WidgetFactory(parent, "containers")
+{
+ KFormDesigner::WidgetInfo *wBtnGroup = new KFormDesigner::WidgetInfo(this);
+ wBtnGroup->setPixmap("frame");
+ wBtnGroup->setClassName("QButtonGroup");
+ wBtnGroup->setName(i18n("Button Group"));
+ wBtnGroup->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "buttonGroup"));
+ wBtnGroup->setDescription(i18n("A simple container to group buttons"));
+ addClass(wBtnGroup);
+
+ KFormDesigner::WidgetInfo *wTabWidget = new KFormDesigner::WidgetInfo(this);
+ wTabWidget->setPixmap("tabwidget");
+ wTabWidget->setClassName("KFDTabWidget");
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9)
+ wTabWidget->addAlternateClassName("KTabWidget");
+ wTabWidget->addAlternateClassName("QTabWidget");
+//tmp: wTabWidget->setSavingName("QTabWidget");
+ wTabWidget->setSavingName("KTabWidget");
+#else
+ wTabWidget->setSavingName("QTabWidget");
+#endif
+ wTabWidget->setIncludeFileName("ktabwidget.h");
+ wTabWidget->setName(i18n("Tab Widget"));
+ wTabWidget->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "tabWidget"));
+ wTabWidget->setDescription(i18n("A widget to display multiple pages using tabs"));
+ addClass(wTabWidget);
+
+ KFormDesigner::WidgetInfo *wWidget = new KFormDesigner::WidgetInfo(this);
+ wWidget->setPixmap("frame");
+ wWidget->setClassName("QWidget");
+ wWidget->addAlternateClassName("ContainerWidget");
+ wWidget->setName(i18n("Basic container"));
+ wWidget->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "container"));
+ wWidget->setDescription(i18n("An empty container with no frame"));
+ addClass(wWidget);
+
+ KFormDesigner::WidgetInfo *wGroupBox = new KFormDesigner::WidgetInfo(this);
+ wGroupBox->setPixmap("groupbox");
+ wGroupBox->setClassName("QGroupBox");
+ wGroupBox->addAlternateClassName("GroupBox");
+ wGroupBox->setName(i18n("Group Box"));
+ wGroupBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "groupBox"));
+ wGroupBox->setDescription(i18n("A container to group some widgets"));
+ addClass(wGroupBox);
+
+ KFormDesigner::WidgetInfo *wFrame = new KFormDesigner::WidgetInfo(this);
+ wFrame->setPixmap("frame");
+ wFrame->setClassName("QFrame");
+ wFrame->setName(i18n("Frame"));
+ wFrame->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "frame"));
+ wFrame->setDescription(i18n("A simple frame container"));
+ addClass(wFrame);
+
+ KFormDesigner::WidgetInfo *wWidgetStack = new KFormDesigner::WidgetInfo(this);
+ wWidgetStack->setPixmap("widgetstack");
+ wWidgetStack->setClassName("QWidgetStack");
+ wWidgetStack->setName(i18n("Widget Stack"));
+ wWidgetStack->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "widgetStack"));
+ wWidgetStack->setDescription(i18n("A container with multiple pages"));
+ addClass(wWidgetStack);
+
+ KFormDesigner::WidgetInfo *wHBox = new KFormDesigner::WidgetInfo(this);
+ wHBox->setPixmap("frame");
+ wHBox->setClassName("HBox");
+ wHBox->setName(i18n("Horizontal Box"));
+ wHBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "horizontalBox"));
+ wHBox->setDescription(i18n("A simple container to group widgets horizontally"));
+ addClass(wHBox);
+
+ KFormDesigner::WidgetInfo *wVBox = new KFormDesigner::WidgetInfo(this);
+ wVBox->setPixmap("frame");
+ wVBox->setClassName("VBox");
+ wVBox->setName(i18n("Vertical Box"));
+ wVBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "verticalBox"));
+ wVBox->setDescription(i18n("A simple container to group widgets vertically"));
+ addClass(wVBox);
+
+ KFormDesigner::WidgetInfo *wGrid = new KFormDesigner::WidgetInfo(this);
+ wGrid->setPixmap("frame");
+ wGrid->setClassName("Grid");
+ wGrid->setName(i18n("Grid Box"));
+ wGrid->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "gridBox"));
+ wGrid->setDescription(i18n("A simple container to group widgets in a grid"));
+ addClass(wGrid);
+
+ KFormDesigner::WidgetInfo *wSplitter = new KFormDesigner::WidgetInfo(this);
+//! @todo horizontal/vertical splitter icons
+ wSplitter->setPixmap("frame");
+ wSplitter->setClassName("Splitter");
+ wSplitter->addAlternateClassName("QSplitter");
+ wSplitter->setName(i18n("Splitter"));
+ wSplitter->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "splitter"));
+ wSplitter->setDescription(i18n("A container that enables user to resize its children"));
+ addClass(wSplitter);
+
+ KFormDesigner::WidgetInfo *wHFlow = new KFormDesigner::WidgetInfo(this);
+//! @todo hflow icon
+ wHFlow->setPixmap("frame");
+ wHFlow->setClassName("HFlow");
+ wHFlow->setName(i18n("Row Layout"));
+ wHFlow->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "rowLayout"));
+ wHFlow->setDescription(i18n("A simple container to group widgets by rows"));
+ addClass(wHFlow);
+
+ KFormDesigner::WidgetInfo *wVFlow = new KFormDesigner::WidgetInfo(this);
+//! @todo vflow icon
+ wVFlow->setPixmap("frame");
+ wVFlow->setClassName("VFlow");
+ wVFlow->setName(i18n("Column Layout"));
+ wVFlow->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "columnLayout"));
+ wVFlow->setDescription(i18n("A simple container to group widgets by columns"));
+ addClass(wVFlow);
+
+ KFormDesigner::WidgetInfo *wSubForm = new KFormDesigner::WidgetInfo(this);
+ wSubForm->setPixmap("form");
+ wSubForm->setClassName("SubForm");
+ wSubForm->setName(i18n("Sub Form"));
+ wSubForm->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "subForm"));
+ wSubForm->setDescription(i18n("A form widget included in another Form"));
+ wSubForm->setAutoSyncForProperty( "formName", false );
+ addClass(wSubForm);
+
+ //groupbox
+ m_propDesc["title"] = i18n("Title");
+ m_propDesc["flat"] = i18n("Flat");
+
+ //tab widget
+ m_propDesc["tabPosition"] = i18n("Tab Position");
+ m_propDesc["currentPage"] = i18n("Current Page");
+ m_propDesc["tabShape"] = i18n("Tab Shape");
+
+ m_propDesc["tabPosition"] = i18n("Tab Position");
+ m_propDesc["tabPosition"] = i18n("Tab Position");
+
+ m_propValDesc["Rounded"] = i18n("for Tab Shape", "Rounded");
+ m_propValDesc["Triangular"] = i18n("for Tab Shape", "Triangular");
+}
+
+QWidget*
+ContainerFactory::createWidget(const QCString &c, QWidget *p, const char *n,
+ KFormDesigner::Container *container, int options)
+{
+ if(c == "QButtonGroup")
+ {
+ QString text = container->form()->library()->textForWidgetName(n, c);
+ QButtonGroup *w = new QButtonGroup(/*i18n("Button Group")*/text, p, n);
+ new KFormDesigner::Container(container, w, container);
+ return w;
+ }
+ else if(c == "KFDTabWidget")
+ {
+ KFDTabWidget *tab = new KFDTabWidget(p, n);
+#if defined(USE_KTabWidget) && KDE_VERSION >= KDE_MAKE_VERSION(3,1,9)
+ tab->setTabReorderingEnabled(true);
+ connect(tab, SIGNAL(movedTab(int,int)), this, SLOT(reorderTabs(int,int)));
+#endif
+ container->form()->objectTree()->addItem(container->objectTree(),
+ new KFormDesigner::ObjectTreeItem(
+ container->form()->library()->displayName(c), n, tab, container));
+// m_manager = container->form()->manager();
+
+ // if we are loading, don't add this tab
+ if(container->form()->interactiveMode())
+ {
+ //m_widget=tab;
+ setWidget(tab, container);
+// m_container=container;
+ addTabPage();
+ }
+
+ return tab;
+ }
+ else if(c == "QWidget" || c=="ContainerWidget")
+ {
+ QWidget *w = new ContainerWidget(p, n);
+ new KFormDesigner::Container(container, w, p);
+ return w;
+ }
+ else if(c == "QGroupBox" || c == "GroupBox")
+ {
+ QString text = container->form()->library()->textForWidgetName(n, c);
+ QGroupBox *w = new GroupBox(text, p, n);
+ new KFormDesigner::Container(container, w, container);
+ return w;
+ }
+ else if(c == "QFrame")
+ {
+ QFrame *w = new QFrame(p, n);
+ w->setLineWidth(2);
+ w->setFrameStyle(QFrame::StyledPanel|QFrame::Raised);
+ new KFormDesigner::Container(container, w, container);
+ return w;
+ }
+ else if(c == "QWidgetStack")
+ {
+ QWidgetStack *stack = new QWidgetStack(p, n);
+ stack->setLineWidth(2);
+ stack->setFrameStyle(QFrame::StyledPanel|QFrame::Raised);
+ container->form()->objectTree()->addItem( container->objectTree(),
+ new KFormDesigner::ObjectTreeItem(
+ container->form()->library()->displayName(c), n, stack, container));
+
+ if(container->form()->interactiveMode())
+ {
+ //m_widget = stack;
+ setWidget(stack, container);
+// m_container = container;
+ addStackPage();
+ }
+ return stack;
+ }
+ else if(c == "HBox") {
+ HBox *w = new HBox(p, n);
+ new KFormDesigner::Container(container, w, container);
+ return w;
+ }
+ else if(c == "VBox") {
+ VBox *w = new VBox(p, n);
+ new KFormDesigner::Container(container, w, container);
+ return w;
+ }
+ else if(c == "Grid") {
+ Grid *w = new Grid(p, n);
+ new KFormDesigner::Container(container, w, container);
+ return w;
+ }
+ else if(c == "HFlow") {
+ HFlow *w = new HFlow(p, n);
+ new KFormDesigner::Container(container, w, container);
+ return w;
+ }
+ else if(c == "VFlow") {
+ VFlow *w = new VFlow(p, n);
+ new KFormDesigner::Container(container, w, container);
+ return w;
+ }
+ else if(c == "SubForm") {
+ SubForm *subform = new SubForm(p, n);
+ return subform;
+ }
+ else if(c == "QSplitter") {
+ QSplitter *split = new QSplitter(p, n);
+ if (0 == (options & WidgetFactory::AnyOrientation))
+ split->setOrientation(
+ (options & WidgetFactory::VerticalOrientation) ? Qt::Vertical : Qt::Horizontal);
+ new KFormDesigner::Container(container, split, container);
+ return split;
+ }
+
+ return 0;
+}
+
+bool
+ContainerFactory::previewWidget(const QCString &classname, QWidget *widget, KFormDesigner::Container *container)
+{
+ if(classname == "WidgetStack")
+ {
+ QWidgetStack *stack = ((QWidgetStack*)widget);
+ KFormDesigner::ObjectTreeItem *tree = container->form()->objectTree()->lookup(widget->name());
+ if(!tree->modifiedProperties()->contains("frameShape"))
+ stack->setFrameStyle(QFrame::NoFrame);
+ }
+ else if(classname == "HBox")
+ ((HBox*)widget)->setPreviewMode();
+ else if(classname == "VBox")
+ ((VBox*)widget)->setPreviewMode();
+ else if(classname == "Grid")
+ ((Grid*)widget)->setPreviewMode();
+ else if(classname == "HFlow")
+ ((HFlow*)widget)->setPreviewMode();
+ else if(classname == "VFlow")
+ ((VFlow*)widget)->setPreviewMode();
+ else
+ return false;
+ return true;
+}
+
+bool
+ContainerFactory::createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *container)
+{
+ setWidget(w, container);
+ //m_widget = w;
+// m_container = container;
+
+ if((classname == "KFDTabWidget") || (w->parentWidget()->parentWidget()->inherits("QTabWidget")))
+ {
+ if(w->parentWidget()->parentWidget()->inherits("QTabWidget"))
+ {
+ //m_widget = w->parentWidget()->parentWidget();
+ setWidget(w->parentWidget()->parentWidget(), m_container->toplevel());
+// m_container = m_container->toplevel();
+ }
+
+ int id = menu->insertItem(SmallIconSet("tab_new"), i18n("Add Page"), this, SLOT(addTabPage()) );
+ id = menu->insertItem(SmallIconSet("edit"), i18n("Rename Page..."), this, SLOT(renameTabPage()));
+ id = menu->insertItem(SmallIconSet("tab_remove"), i18n("Remove Page"), this, SLOT(removeTabPage()));
+// if( dynamic_cast<TabWidgetBase*>(m_widget)->count() == 1)
+ if( dynamic_cast<TabWidgetBase*>(widget())->count() == 1)
+ menu->setItemEnabled(id, false);
+ return true;
+ }
+ else if(w->parentWidget()->isA("QWidgetStack") && !w->parentWidget()->parentWidget()->inherits("QTabWidget"))
+ {
+ //m_widget = w->parentWidget();
+ QWidgetStack *stack = (QWidgetStack*)w->parentWidget(); //m_widget;
+ setWidget(
+ w->parentWidget(),
+ container->form()->objectTree()->lookup(stack->name())->parent()->container()
+ );
+// m_container = container->form()->objectTree()->lookup(m_widget->name())->parent()->container();
+// m_container = container->form()->objectTree()->lookup(stack->name())->parent()->container();
+
+ int id = menu->insertItem(SmallIconSet("tab_new"), i18n("Add Page"), this, SLOT(addStackPage()) );
+
+ id = menu->insertItem(SmallIconSet("tab_remove"), i18n("Remove Page"), this, SLOT(removeStackPage()) );
+// if( ((QWidgetStack*)m_widget)->children()->count() == 4) // == the stack has only one page
+ if(stack->children()->count() == 4) // == the stack has only one page
+ menu->setItemEnabled(id, false);
+
+ id = menu->insertItem(SmallIconSet("next"), i18n("Jump to Next Page"), this, SLOT(nextStackPage()));
+ if(!stack->widget(stack->id(stack->visibleWidget())+1))
+ menu->setItemEnabled(id, false);
+
+ id = menu->insertItem(SmallIconSet("previous"), i18n("Jump to Previous Page"), this, SLOT(prevStackPage()));
+ if(!stack->widget(stack->id(stack->visibleWidget()) -1) )
+ menu->setItemEnabled(id, false);
+ return true;
+ }
+ return false;
+}
+
+bool
+ContainerFactory::startEditing(const QCString &classname, QWidget *w, KFormDesigner::Container *container)
+{
+ m_container = container;
+ if(classname == "QButtonGroup")
+ {
+ QButtonGroup *group = static_cast<QButtonGroup*>(w);
+ QRect r = QRect(group->x()+2, group->y()-5, group->width()-10, w->fontMetrics().height() + 10);
+ createEditor(classname, group->title(), group, container, r, Qt::AlignAuto);
+ return true;
+ }
+ if(classname == "QGroupBox" || classname == "GroupBox")
+ {
+ QGroupBox *group = static_cast<QGroupBox*>(w);
+ QRect r = QRect(group->x()+2, group->y()-5, group->width()-10, w->fontMetrics().height() + 10);
+ createEditor(classname, group->title(), group, container, r, Qt::AlignAuto);
+ return true;
+ }
+ return false;
+}
+
+bool
+ContainerFactory::saveSpecialProperty(const QCString &, const QString &name, const QVariant &, QWidget *w, QDomElement &parentNode, QDomDocument &parent)
+{
+ if((name == "title") && (w->parentWidget()->parentWidget()->inherits("QTabWidget")))
+ {
+ TabWidgetBase *tab = dynamic_cast<TabWidgetBase*>(w->parentWidget()->parentWidget());
+ KFormDesigner::FormIO::savePropertyElement(parentNode, parent, "attribute", "title", tab->tabLabel(w));
+ }
+ else if((name == "id") && (w->parentWidget()->isA("QWidgetStack")))
+ {
+ QWidgetStack *stack = (QWidgetStack*)w->parentWidget();
+ KFormDesigner::FormIO::savePropertyElement(parentNode, parent, "attribute", "id", stack->id(w));
+ }
+ else
+ return false;
+ return true;
+}
+
+bool
+ContainerFactory::readSpecialProperty(const QCString &, QDomElement &node, QWidget *w, KFormDesigner::ObjectTreeItem *item)
+{
+ QString name = node.attribute("name");
+ if((name == "title") && (item->parent()->widget()->inherits("QTabWidget")))
+ {
+ TabWidgetBase *tab = dynamic_cast<TabWidgetBase*>(w->parentWidget());
+ tab->addTab(w, node.firstChild().toElement().text());
+ item->addModifiedProperty("title", node.firstChild().toElement().text());
+ return true;
+ }
+
+ if((name == "id") && (w->parentWidget()->isA("QWidgetStack")))
+ {
+ QWidgetStack *stack = (QWidgetStack*)w->parentWidget();
+ int id = KFormDesigner::FormIO::readPropertyValue(node.firstChild(), w, name).toInt();
+ stack->addWidget(w, id);
+ stack->raiseWidget(w);
+ item->addModifiedProperty("id", id);
+ return true;
+ }
+
+ return false;
+}
+
+QValueList<QCString>
+ContainerFactory::autoSaveProperties(const QCString &c)
+{
+ QValueList<QCString> lst;
+// if(c == "SubForm")
+// lst << "formName";
+ if(c == "QSplitter")
+ lst << "orientation";
+ return lst;
+}
+
+bool
+ContainerFactory::isPropertyVisibleInternal(const QCString &classname,
+ QWidget *w, const QCString &property, bool isTopLevel)
+{
+ bool ok = true;
+
+ if((classname == "HBox") || (classname == "VBox") || (classname == "Grid") ||
+ (classname == "HFlow") || (classname == "VFlow"))
+ {
+ return property == "name" || property == "geometry";
+ }
+ else if (classname == "QGroupBox" || classname=="GroupBox") {
+ ok =
+#ifdef KEXI_NO_UNFINISHED
+/*! @todo Hidden for now in Kexi. "checkable" and "checked" props need adding
+a fake properties which will allow to properly work in design mode, otherwise
+child widgets become frozen when checked==true */
+ (m_showAdvancedProperties || (property != "checkable" && property != "checked")) &&
+#endif
+ true
+ ;
+ }
+ else if (classname == "KFDTabWidget") {
+ ok = (m_showAdvancedProperties || (property != "tabReorderingEnabled" && property != "hoverCloseButton" && property != "hoverCloseButtonDelayed"));
+ }
+
+ return ok && WidgetFactory::isPropertyVisibleInternal(classname, w, property, isTopLevel);
+}
+
+bool
+ContainerFactory::changeText(const QString &text)
+{
+ changeProperty("title", text, m_container->form());
+ return true;
+}
+
+void
+ContainerFactory::resizeEditor(QWidget *editor, QWidget *widget, const QCString &)
+{
+ QSize s = widget->size();
+ editor->move(widget->x() + 2, widget->y() - 5);
+ editor->resize(s.width() - 20, widget->fontMetrics().height() +10);
+}
+
+// Widget Specific slots used in menu items
+
+void ContainerFactory::addTabPage()
+{
+// if (!m_widget->inherits("QTabWidget"))
+ if (!widget()->inherits("QTabWidget"))
+ return;
+ KCommand *com = new InsertPageCommand(m_container, widget());
+ if(dynamic_cast<TabWidgetBase*>(widget())->count() == 0)
+ {
+ com->execute();
+ delete com;
+ }
+ else
+ m_container->form()->addCommand(com, true);
+}
+
+void ContainerFactory::removeTabPage()
+{
+ if (!widget()->inherits("QTabWidget"))
+ return;
+ TabWidgetBase *tab = dynamic_cast<TabWidgetBase*>(widget());
+ QWidget *w = tab->currentPage();
+
+ KFormDesigner::WidgetList list;
+ list.append(w);
+ KCommand *com = new KFormDesigner::DeleteWidgetCommand(list, m_container->form());
+ tab->removePage(w);
+ m_container->form()->addCommand(com, true);
+}
+
+void ContainerFactory::renameTabPage()
+{
+ if (!widget()->inherits("QTabWidget"))
+ return;
+ TabWidgetBase *tab = dynamic_cast<TabWidgetBase*>(widget());
+ QWidget *w = tab->currentPage();
+ bool ok;
+
+ QString name = KInputDialog::getText(i18n("New Page Title"), i18n("Enter a new title for the current page:"),
+#if KDE_VERSION < KDE_MAKE_VERSION(3,1,9)
+ QLineEdit::Normal,
+#endif
+ tab->tabLabel(w), &ok, w->topLevelWidget());
+ if(ok)
+ tab->changeTab(w, name);
+}
+
+void ContainerFactory::reorderTabs(int oldpos, int newpos)
+{
+ KFormDesigner::ObjectTreeItem *tab
+ = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(sender()->name());
+ if(!tab)
+ return;
+
+ KFormDesigner::ObjectTreeItem *item = tab->children()->take(oldpos);
+ tab->children()->insert(newpos, item);
+}
+
+void ContainerFactory::addStackPage()
+{
+ if (!widget()->isA("QWidgetStack"))
+ return;
+ KCommand *com = new InsertPageCommand(m_container, widget());
+ if(!((QWidgetStack*)widget())->visibleWidget())
+ {
+ com->execute();
+ delete com;
+ }
+ else
+ m_container->form()->addCommand(com, true);
+}
+
+void ContainerFactory::removeStackPage()
+{
+ if (!widget()->isA("QWidgetStack"))
+ return;
+ QWidgetStack *stack = (QWidgetStack*)widget();
+ QWidget *page = stack->visibleWidget();
+
+ KFormDesigner::WidgetList list;
+ list.append(page);
+ KCommand *com = new KFormDesigner::DeleteWidgetCommand(list, m_container->form());
+
+ // raise prev widget
+ int id = stack->id(page) - 1;
+ while(!stack->widget(id))
+ id--;
+ stack->raiseWidget(id);
+
+ stack->removeWidget(page);
+ m_container->form()->addCommand(com, true);
+}
+
+void ContainerFactory::prevStackPage()
+{
+ QWidgetStack *stack = (QWidgetStack*)widget();
+ int id = stack->id(stack->visibleWidget()) - 1;
+ if(stack->widget(id))
+ stack->raiseWidget(id);
+}
+
+void ContainerFactory::nextStackPage()
+{
+ QWidgetStack *stack = (QWidgetStack*)widget();
+ int id = stack->id(stack->visibleWidget()) + 1;
+ if(stack->widget(id))
+ stack->raiseWidget(id);
+}
+
+ContainerFactory::~ContainerFactory()
+{
+}
+
+KFORMDESIGNER_WIDGET_FACTORY(ContainerFactory, containers)
+
+#include "containerfactory.moc"
diff --git a/kexi/formeditor/factories/containerfactory.h b/kexi/formeditor/factories/containerfactory.h
new file mode 100644
index 000000000..1092d8524
--- /dev/null
+++ b/kexi/formeditor/factories/containerfactory.h
@@ -0,0 +1,271 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef CONTAINERFACTORY_H
+#define CONTAINERFACTORY_H
+
+#include <kcommand.h>
+
+#include "widgetfactory.h"
+#include "../utils.h"
+
+namespace KFormDesigner
+{
+ class Form;
+ class FormManager;
+ class Container;
+}
+
+class InsertPageCommand : public KCommand
+{
+ public:
+ InsertPageCommand(KFormDesigner::Container *container, QWidget *widget);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+ protected:
+ KFormDesigner::Form *m_form;
+ QString m_containername;
+ QString m_name;
+ QString m_parentname;
+ int m_pageid;
+};
+
+//! Helper widget (used when using 'Lay out horizontally')
+class KFORMEDITOR_EXPORT HBox : public QFrame
+{
+ Q_OBJECT
+
+ public:
+ HBox(QWidget *parent, const char *name);
+ virtual ~HBox(){;}
+ void setPreviewMode() {m_preview = true;}
+ virtual void paintEvent(QPaintEvent *ev);
+
+ protected:
+ bool m_preview;
+};
+
+//! Helper widget (used when using 'Lay out vertically')
+class KFORMEDITOR_EXPORT VBox : public QFrame
+{
+ Q_OBJECT
+
+ public:
+ VBox(QWidget *parent, const char *name);
+ virtual ~VBox(){;}
+ void setPreviewMode() {m_preview = true;}
+ virtual void paintEvent(QPaintEvent *ev);
+
+ protected:
+ bool m_preview;
+};
+
+//! Helper widget (used when using 'Lay out in a grid')
+class KFORMEDITOR_EXPORT Grid : public QFrame
+{
+ Q_OBJECT
+
+ public:
+ Grid(QWidget *parent, const char *name);
+ virtual ~Grid(){;}
+ void setPreviewMode() {m_preview = true;}
+ virtual void paintEvent(QPaintEvent *ev);
+
+ protected:
+ bool m_preview;
+};
+
+//! Helper widget (used when using 'Lay out with horizontal flow')
+class KFORMEDITOR_EXPORT HFlow : public QFrame
+{
+ Q_OBJECT
+
+ public:
+ HFlow(QWidget *parent, const char *name);
+ virtual ~HFlow(){;}
+ void setPreviewMode() {m_preview = true;}
+ virtual void paintEvent(QPaintEvent *ev);
+
+ protected:
+ bool m_preview;
+};
+
+//! Helper widget (used when using 'Lay out with horizontal flow')
+class KFORMEDITOR_EXPORT VFlow : public QFrame
+{
+ Q_OBJECT
+
+ public:
+ VFlow(QWidget *parent, const char *name);
+ virtual ~VFlow(){;}
+ void setPreviewMode() {m_preview = true;}
+ virtual void paintEvent(QPaintEvent *ev);
+ virtual QSize sizeHint() const;
+
+ protected:
+ bool m_preview;
+};
+
+//! A simple container widget
+class KFORMEDITOR_EXPORT ContainerWidget : public QWidget
+{
+ Q_OBJECT
+
+ friend class KFDTabWidget;
+
+ public:
+ ContainerWidget(QWidget *parent, const char *name);
+ virtual ~ContainerWidget();
+
+ virtual QSize sizeHint() const;
+
+ //! Used to emit handleDragMoveEvent() signal needed to control dragging over the container's surface
+ virtual void dragMoveEvent( QDragMoveEvent *e );
+
+ //! Used to emit handleDropEvent() signal needed to control dropping on the container's surface
+ virtual void dropEvent( QDropEvent *e );
+
+ signals:
+ //! Needed to control dragging over the container's surface
+ void handleDragMoveEvent(QDragMoveEvent *e);
+
+ //! Needed to control dropping on the container's surface
+ void handleDropEvent(QDropEvent *e);
+};
+
+//! A tab widget
+class KFORMEDITOR_EXPORT KFDTabWidget : public KFormDesigner::TabWidget
+{
+ Q_OBJECT
+
+ public:
+ KFDTabWidget(QWidget *parent, const char *name);
+ virtual ~KFDTabWidget();
+
+ virtual QSize sizeHint() const;
+
+ //! Used to emit handleDragMoveEvent() signal needed to control dragging over the container's surface
+ virtual void dragMoveEvent( QDragMoveEvent *e );
+
+ //! Used to emit handleDropEvent() signal needed to control dropping on the container's surface
+ virtual void dropEvent( QDropEvent *e );
+
+ signals:
+ //! Needed to control dragging over the container's surface
+ void handleDragMoveEvent(QDragMoveEvent *e);
+
+ //! Needed to control dropping on the container's surface
+ void handleDropEvent(QDropEvent *e);
+};
+
+//! A group box widget
+class KFORMEDITOR_EXPORT GroupBox : public QGroupBox
+{
+ Q_OBJECT
+
+ public:
+ GroupBox(const QString & title, QWidget *parent, const char *name);
+ virtual ~GroupBox();
+
+ //! Used to emit handleDragMoveEvent() signal needed to control dragging over the container's surface
+ virtual void dragMoveEvent( QDragMoveEvent *e );
+
+ //! Used to emit handleDropEvent() signal needed to control dropping on the container's surface
+ virtual void dropEvent( QDropEvent *e );
+
+ signals:
+ //! Needed to control dragging over the container's surface
+ void handleDragMoveEvent(QDragMoveEvent *e);
+
+ //! Needed to control dropping on the container's surface
+ void handleDropEvent(QDropEvent *e);
+};
+
+//! A form embedded as a widget inside other form
+class KFORMEDITOR_EXPORT SubForm : public QScrollView
+{
+ Q_OBJECT
+ Q_PROPERTY(QString formName READ formName WRITE setFormName DESIGNABLE true)
+
+ public:
+ SubForm(QWidget *parent, const char *name);
+ ~SubForm() {}
+
+ //! \return the name of the subform inside the db
+ QString formName() const { return m_formName; }
+ void setFormName(const QString &name);
+
+ private:
+// KFormDesigner::FormManager *m_manager;
+ KFormDesigner::Form *m_form;
+ QWidget *m_widget;
+ QString m_formName;
+};
+
+//! Standard Factory for all container widgets
+class ContainerFactory : public KFormDesigner::WidgetFactory
+{
+ Q_OBJECT
+
+ public:
+ ContainerFactory(QObject *parent, const char *name, const QStringList &args);
+ virtual ~ContainerFactory();
+
+ virtual QWidget *createWidget(const QCString & classname, QWidget *parent, const char *name, KFormDesigner::Container *container,
+ int options = DefaultOptions);
+ virtual bool createMenuActions(const QCString& classname, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *container);
+ virtual bool startEditing(const QCString &classname, QWidget *w,
+ KFormDesigner::Container *container);
+ virtual bool previewWidget(const QCString &classname, QWidget *widget,
+ KFormDesigner::Container *container);
+ virtual bool saveSpecialProperty(const QCString &classname, const QString &name,
+ const QVariant &value, QWidget *w, QDomElement &parentNode, QDomDocument &parent);
+ virtual bool readSpecialProperty(const QCString &classname, QDomElement &node, QWidget *w,
+ KFormDesigner::ObjectTreeItem *item);
+ virtual QValueList<QCString> autoSaveProperties(const QCString &classname);
+
+ protected:
+ virtual bool isPropertyVisibleInternal(const QCString &classname, QWidget *w,
+ const QCString &property, bool isTopLevel);
+ virtual bool changeText(const QString &newText);
+ virtual void resizeEditor(QWidget *editor, QWidget *widget, const QCString &classname);
+
+ public slots:
+ void addTabPage();
+ void addStackPage();
+ void renameTabPage();
+ void removeTabPage();
+ void removeStackPage();
+ void prevStackPage();
+ void nextStackPage();
+ void reorderTabs(int oldpos, int newpos);
+
+ private:
+// QWidget *m_widget;
+// KFormDesigner::Container *m_container;
+// KFormDesigner::FormManager *m_manager;
+};
+
+#endif
diff --git a/kexi/formeditor/factories/kformdesigner_containers.desktop b/kexi/formeditor/factories/kformdesigner_containers.desktop
new file mode 100644
index 000000000..ae30820c5
--- /dev/null
+++ b/kexi/formeditor/factories/kformdesigner_containers.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=KFormDesigner/WidgetFactory
+
+Name=Container Widgets
+Name[bg]=Контейнери
+Name[ca]=Estris contenidors
+Name[cy]=Celfigion Cynhwysydd
+Name[da]=Container-kontroller
+Name[de]=Container-Elemente
+Name[el]=Γραφικά συστατικά υποδοχείς
+Name[eo]=Ujaj fenestraĵoj
+Name[es]=Widgets contenedores
+Name[et]=Konteinervidinad
+Name[eu]=Trepeta edukitzaileak
+Name[fa]=عناصر محتوی
+Name[fi]=Säiliön osat
+Name[fr]=Conteneur d'éléments graphiques
+Name[fy]=Kontainerwidgets
+Name[gl]=Elementos Contedores
+Name[he]=כלי קיבול
+Name[hr]=Sadržajni widgeti
+Name[hu]=Tartóelemek
+Name[is]=Geymihlutir
+Name[it]=Oggetti contenitori
+Name[ja]=コンテナウィジェット
+Name[km]=ធាតុ​ក្រាហ្វិក​កុងតឺន័រ
+Name[lv]=Konteinera logdaļa
+Name[ms]=Widget Bekas
+Name[nb]=Beholder-elementer
+Name[nds]=Gelaatselementen
+Name[ne]=कन्टेनर विजेट
+Name[nl]=Containerwidgets
+Name[nn]=Kjeraldelement
+Name[pl]=Kontrolki pojemników
+Name[pt]=Elementos Contentores
+Name[pt_BR]=Widgets Recipientes
+Name[ru]=Контейнеры
+Name[se]=Lihtteáđat
+Name[sk]=Kontajnerové komponenty
+Name[sl]=Vsebovalni gradniki
+Name[sr]=Контејнерске контроле
+Name[sr@Latn]=Kontejnerske kontrole
+Name[sv]=Omgivande komponenter
+Name[ta]=கொள்கலன் சாளர உருக்கள்
+Name[tr]= Containers
+Name[uk]=Віджети контейнера
+Name[zh_CN]=容器部件
+Name[zh_TW]=容器視窗元件
+
+X-KDE-Library=kformdesigner_containers
+X-KFormDesigner-FactoryGroup=
+X-KFormDesigner-WidgetFactoryVersion=2
diff --git a/kexi/formeditor/factories/kformdesigner_stdwidgets.desktop b/kexi/formeditor/factories/kformdesigner_stdwidgets.desktop
new file mode 100644
index 000000000..d5d599fa0
--- /dev/null
+++ b/kexi/formeditor/factories/kformdesigner_stdwidgets.desktop
@@ -0,0 +1,55 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=KFormDesigner/WidgetFactory
+
+Name=Basic Widgets
+Name[bg]=Основни графични обекти
+Name[br]=Widgets Diazez
+Name[ca]=Estris estàndard
+Name[cy]=Celfigion Sylfaenol
+Name[da]=Basale kontroller
+Name[de]=Basis-Elemente
+Name[el]=Βασικά γραφικά συστατικά
+Name[eo]=Bazaj fenestraĵoj
+Name[es]=Widgets básicos
+Name[et]=Standardvidinad
+Name[eu]=Oinarrizko trepetak
+Name[fa]=عناصر پایه‌ای
+Name[fi]=yleiset elementit
+Name[fr]=Éléments graphiques basiques
+Name[fy]=Basiswidgets
+Name[gl]=Elementos Básicos
+Name[he]=פריטים בסיסיים
+Name[hr]=Osnovni widgeti
+Name[hu]=Alapelemek
+Name[is]=Grunnhlutir
+Name[it]=Oggetti di base
+Name[ja]=基本的なウィジェット
+Name[km]=ធាតុក្រាហ្វិកមូលដ្ឋាន
+Name[lv]=Pamata logdaļas
+Name[ms]=Widget Asas
+Name[nb]=Elementære elementer
+Name[nds]=Grundelementen
+Name[ne]=आधारभूत विजेट
+Name[nl]=Basiswidgets
+Name[nn]=Grunnleggjande element
+Name[pl]=Proste kontrolki
+Name[pt]=Elementos Básicos
+Name[pt_BR]=Widgets Básicos
+Name[ru]=Стандартные виджеты
+Name[se]=Oktageardanis áđat
+Name[sk]=Základné komponenty
+Name[sl]=Osnovni gradniki
+Name[sr]=Основне контроле
+Name[sr@Latn]=Osnovne kontrole
+Name[sv]=Standardkomponenter
+Name[ta]=அடிப்படை சாளர உருக்கள்
+Name[uk]=Основні віджети
+Name[uz]=Oddiy vidjetlar
+Name[uz@cyrillic]=Оддий виджетлар
+Name[zh_CN]=基本部件
+Name[zh_TW]=基本視窗元件
+
+X-KDE-Library=kformdesigner_stdwidgets
+X-KFormDesigner-FactoryGroup=
+X-KFormDesigner-WidgetFactoryVersion=2
diff --git a/kexi/formeditor/factories/stdwidgetfactory.cpp b/kexi/formeditor/factories/stdwidgetfactory.cpp
new file mode 100644
index 000000000..fade100a3
--- /dev/null
+++ b/kexi/formeditor/factories/stdwidgetfactory.cpp
@@ -0,0 +1,984 @@
+/***************************************************************************
+ * Copyright (C) 2003 by Lucijan Busch <lucijan@kde.org> *
+ * Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> *
+ * This program 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. *
+ ***************************************************************************/
+
+#include <qlabel.h>
+#include <qpopupmenu.h>
+#include <qcursor.h>
+#include <qradiobutton.h>
+#include <qcheckbox.h>
+#include <qslider.h>
+#include <qobjectlist.h>
+#include <qstring.h>
+#include <qvariant.h>
+#include <qheader.h>
+#include <qdom.h>
+#include <qstyle.h>
+#include <qvaluevector.h>
+
+#include <klineedit.h>
+#include <kpushbutton.h>
+#include <knuminput.h>
+#include <kcombobox.h>
+#include <klistbox.h>
+#include <ktextedit.h>
+#include <klistview.h>
+#include <kprogress.h>
+#include <kiconloader.h>
+#include <kgenericfactory.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kdeversion.h>
+
+#if KDE_VERSION < KDE_MAKE_VERSION(3,1,9)
+# include <qdatetimeedit.h>
+# define KTimeWidget QTimeEdit
+# define KDateWidget QDateEdit
+# define KDateTimeWidget QDateTimeEdit
+#else
+# include <ktimewidget.h>
+# include <kdatewidget.h>
+# include <kdatetimewidget.h>
+#endif
+
+#include "spring.h"
+#include "formIO.h"
+#include "form.h"
+#include "formmanager.h"
+#include "widgetlibrary.h"
+#include "widgetpropertyset.h"
+#include <koproperty/property.h>
+
+#include "stdwidgetfactory.h"
+
+// Some widgets subclass to allow event filtering and some other things
+KexiPictureLabel::KexiPictureLabel(const QPixmap &pix, QWidget *parent, const char *name)
+ : QLabel(parent, name)
+{
+ setPixmap(pix);
+ setScaledContents(false);
+}
+
+bool
+KexiPictureLabel::setProperty(const char *name, const QVariant &value)
+{
+ if(QString(name) == "pixmap")
+ resize(value.toPixmap().height(), value.toPixmap().width());
+ return QLabel::setProperty(name, value);
+}
+
+Line::Line(Qt::Orientation orient, QWidget *parent, const char *name)
+ : QFrame(parent, name)
+{
+ setFrameShadow(Sunken);
+ if(orient == Horizontal)
+ setFrameShape(HLine);
+ else
+ setFrameShape(VLine);
+}
+
+void
+Line::setOrientation(Qt::Orientation orient)
+{
+ if(orient == Horizontal)
+ setFrameShape(HLine);
+ else
+ setFrameShape(VLine);
+}
+
+Qt::Orientation
+Line::orientation() const
+{
+ if(frameShape() == HLine)
+ return Horizontal;
+ else
+ return Vertical;
+}
+
+// The factory itself
+
+StdWidgetFactory::StdWidgetFactory(QObject *parent, const char *, const QStringList &)
+ : KFormDesigner::WidgetFactory(parent, "stdwidgets")
+{
+ KFormDesigner::WidgetInfo *wFormWidget = new KFormDesigner::WidgetInfo(this);
+ wFormWidget->setPixmap("form");
+ wFormWidget->setClassName("FormWidgetBase");
+ wFormWidget->setName(i18n("Form"));
+ wFormWidget->setNamePrefix(i18n("This string will be used to name widgets of this class. It must _not_ contain white "
+ "spaces and non latin1 characters.", "form"));
+ wFormWidget->setDescription(i18n("A simple form widget"));
+ addClass(wFormWidget);
+
+ KFormDesigner::WidgetInfo *wCustomWidget = new KFormDesigner::WidgetInfo(this);
+ wCustomWidget->setPixmap("unknown_widget");
+ wCustomWidget->setClassName("CustomWidget");
+ wCustomWidget->setName(i18n("Custom Widget"));
+ wCustomWidget->setNamePrefix(i18n("This string will be used to name widgets of this class. It must _not_ contain white "
+ "spaces and non latin1 characters.", "customWidget"));
+ wCustomWidget->setDescription(i18n("A custom or non-supported widget"));
+ addClass(wCustomWidget);
+
+ KFormDesigner::WidgetInfo *wLabel = new KFormDesigner::WidgetInfo(this);
+ wLabel->setPixmap("label");
+ wLabel->setClassName("QLabel");
+ wLabel->setName(i18n("Text Label"));
+ wLabel->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "label"));
+ wLabel->setDescription(i18n("A widget to display text"));
+ addClass(wLabel);
+
+ KFormDesigner::WidgetInfo *wPixLabel = new KFormDesigner::WidgetInfo(this);
+ wPixLabel->setPixmap("pixmaplabel");
+ wPixLabel->setClassName("KexiPictureLabel");
+ wPixLabel->setName(i18n("Picture Label"));
+//! @todo Qt designer compatibility: maybe use this class when QLabel has a pixmap set...?
+ //wPixLabel->addAlternateClassName("QLabel");
+ wPixLabel->setSavingName("KexiPictureLabel");
+ wPixLabel->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "picture"));
+ wPixLabel->setDescription(i18n("A widget to display pictures"));
+ addClass(wPixLabel);
+
+ KFormDesigner::WidgetInfo *wLineEdit = new KFormDesigner::WidgetInfo(this);
+ wLineEdit->setPixmap("lineedit");
+ wLineEdit->setClassName("KLineEdit");
+ wLineEdit->addAlternateClassName("QLineEdit");
+ wLineEdit->setIncludeFileName("klineedit.h");
+ wLineEdit->setName(i18n("Line Edit"));
+ wLineEdit->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "lineEdit"));
+ wLineEdit->setDescription(i18n("A widget to input text"));
+ addClass(wLineEdit);
+
+ KFormDesigner::WidgetInfo *wSpring = new KFormDesigner::WidgetInfo(this);
+ wSpring->setPixmap("spring");
+ wSpring->setClassName("Spring");
+ wSpring->setName(i18n("Spring"));
+ wSpring->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "spring"));
+ wSpring->setDescription(i18n("A spring to place between widgets"));
+ addClass(wSpring);
+
+ KFormDesigner::WidgetInfo *wPushButton = new KFormDesigner::WidgetInfo(this);
+ wPushButton->setPixmap("button");
+ wPushButton->setClassName("KPushButton");
+ wPushButton->addAlternateClassName("QPushButton");
+ wPushButton->setIncludeFileName("kpushbutton.h");
+ wPushButton->setName(i18n("Push Button"));
+ wPushButton->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "button"));
+ wPushButton->setDescription(i18n("A simple push button to execute actions"));
+ addClass(wPushButton);
+
+ KFormDesigner::WidgetInfo *wRadioButton = new KFormDesigner::WidgetInfo(this);
+ wRadioButton->setPixmap("radio");
+ wRadioButton->setClassName("QRadioButton");
+ wRadioButton->setName(i18n("Option Button"));
+ wRadioButton->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "optionButton"));
+ wRadioButton->setDescription(i18n("An option button with text or pixmap label"));
+ addClass(wRadioButton);
+
+ KFormDesigner::WidgetInfo *wCheckBox = new KFormDesigner::WidgetInfo(this);
+ wCheckBox->setPixmap("check");
+ wCheckBox->setClassName("QCheckBox");
+ wCheckBox->setName(i18n("Check Box"));
+ wCheckBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "checkBox"));
+ wCheckBox->setDescription(i18n("A check box with text or pixmap label"));
+ addClass(wCheckBox);
+
+ KFormDesigner::WidgetInfo *wSpinBox = new KFormDesigner::WidgetInfo(this);
+ wSpinBox->setPixmap("spin");
+ wSpinBox->setClassName("KIntSpinBox");
+ wSpinBox->addAlternateClassName("QSpinBox");
+ wSpinBox->setIncludeFileName("knuminput.h");
+ wSpinBox->setName(i18n("Spin Box"));
+ wSpinBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "spinBox"));
+ wSpinBox->setDescription(i18n("A spin box widget"));
+ addClass(wSpinBox);
+
+ KFormDesigner::WidgetInfo *wComboBox = new KFormDesigner::WidgetInfo(this);
+ wComboBox->setPixmap("combo");
+ wComboBox->setClassName("KComboBox");
+ wComboBox->addAlternateClassName("QComboBox");
+ wComboBox->setIncludeFileName("kcombobox.h");
+ wComboBox->setName(i18n("Combo Box"));
+ wComboBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "comboBox"));
+ wComboBox->setDescription(i18n("A combo box widget"));
+ addClass(wComboBox);
+
+ KFormDesigner::WidgetInfo *wListBox = new KFormDesigner::WidgetInfo(this);
+ wListBox->setPixmap("listbox");
+ wListBox->setClassName("KListBox");
+ wListBox->addAlternateClassName("QListBox");
+ wListBox->setIncludeFileName("klistbox.h");
+ wListBox->setName(i18n("List Box"));
+ wListBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "listBox"));
+ wListBox->setDescription(i18n("A simple list widget"));
+ addClass(wListBox);
+
+ KFormDesigner::WidgetInfo *wTextEdit = new KFormDesigner::WidgetInfo(this);
+ wTextEdit->setPixmap("textedit");
+ wTextEdit->setClassName("KTextEdit");
+ wTextEdit->addAlternateClassName("QTextEdit");
+ wTextEdit->setIncludeFileName("ktextedit.h");
+ wTextEdit->setName(i18n("Text Editor"));
+ wTextEdit->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "textEditor"));
+ wTextEdit->setDescription(i18n("A simple single-page rich text editor"));
+ addClass(wTextEdit);
+
+ KFormDesigner::WidgetInfo *wListView = new KFormDesigner::WidgetInfo(this);
+ wListView->setPixmap("listview");
+ wListView->setClassName("KListView");
+ wListView->addAlternateClassName("QListView");
+ wListView->setIncludeFileName("klistview.h");
+ wListView->setName(i18n("List View"));
+ wListView->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "listView"));
+ wListView->setDescription(i18n("A list (or tree) widget"));
+ addClass(wListView);
+
+ KFormDesigner::WidgetInfo *wSlider = new KFormDesigner::WidgetInfo(this);
+ wSlider->setPixmap("slider");
+ wSlider->setClassName("QSlider");
+ wSlider->setName(i18n("Slider"));
+ wSlider->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "slider"));
+ wSlider->setDescription(i18n("An horizontal slider"));
+ addClass(wSlider);
+
+ KFormDesigner::WidgetInfo *wProgressBar = new KFormDesigner::WidgetInfo(this);
+ wProgressBar->setPixmap("progress");
+ wProgressBar->setClassName("KProgress");
+ wProgressBar->addAlternateClassName("QProgressBar");
+ wProgressBar->setIncludeFileName("kprogress.h");
+ wProgressBar->setName(i18n("Progress Bar"));
+ wProgressBar->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "progressBar"));
+ wProgressBar->setDescription(i18n("A progress indicator widget"));
+ addClass(wProgressBar);
+
+ KFormDesigner::WidgetInfo *wLine = new KFormDesigner::WidgetInfo(this);
+ wLine->setPixmap("line");
+ wLine->setClassName("Line");
+ wLine->setName(i18n("Line"));
+ wLine->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "line"));
+ wLine->setDescription(i18n("A line to be used as a separator"));
+ addClass(wLine);
+
+ KFormDesigner::WidgetInfo *wDate = new KFormDesigner::WidgetInfo(this);
+ wDate->setPixmap("dateedit");
+ wDate->setClassName("KDateWidget");
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9)
+ wDate->addAlternateClassName("QDateEdit");
+ wDate->setIncludeFileName("kdatewidget.h");
+#endif
+ wDate->setName(i18n("Date Widget"));
+ wDate->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "dateWidget"));
+ wDate->setDescription(i18n("A widget to input and display a date"));
+ addClass(wDate);
+
+ KFormDesigner::WidgetInfo *wTime = new KFormDesigner::WidgetInfo(this);
+ wTime->setPixmap("timeedit");
+ wTime->setClassName("KTimeWidget");
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9)
+ wTime->addAlternateClassName("QTimeEdit");
+ wTime->setIncludeFileName("ktimewidget.h");
+#endif
+ wTime->setName(i18n("Time Widget"));
+ wTime->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "timeWidget"));
+ wTime->setDescription(i18n("A widget to input and display a time"));
+ addClass(wTime);
+
+ KFormDesigner::WidgetInfo *wDateTime = new KFormDesigner::WidgetInfo(this);
+ wDateTime->setPixmap("datetimeedit");
+ wDateTime->setClassName("KDateTimeWidget");
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9)
+ wDateTime->addAlternateClassName("QDateTimeEdit");
+ wDateTime->setIncludeFileName("kdatetimewidget.h");
+#endif
+ wDateTime->setName(i18n("Date/Time Widget"));
+ wDateTime->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "dateTimeWidget"));
+ wDateTime->setDescription(i18n("A widget to input and display a time and a date"));
+ addClass(wDateTime);
+
+ m_propDesc["toggleButton"] = i18n("Toggle");
+ m_propDesc["autoRepeat"] = i18n("Auto Repeat");
+ m_propDesc["autoDefault"] = i18n("Auto Default");
+ m_propDesc["default"] = i18n("Default");
+ m_propDesc["flat"] = i18n("Flat");
+ m_propDesc["echoMode"] =
+ i18n("Echo mode for Line Edit widget eg. Normal, NoEcho, Password","Echo Mode");
+ m_propDesc["indent"] = i18n("Indent");
+ //line
+ m_propDesc["orientation"] = i18n("Orientation");
+ //checkbox
+ m_propDesc["checked"] = i18n("Checked checkbox", "Checked");
+ m_propDesc["tristate"] = i18n("Tristate checkbox", "Tristate");
+
+ //for EchoMode
+ m_propValDesc["Normal"] = i18n("For Echo Mode", "Normal");
+ m_propValDesc["NoEcho"] = i18n("For Echo Mode", "No Echo");
+ m_propValDesc["Password"] = i18n("For Echo Mode", "Password");
+
+ //for spring
+ m_propDesc["sizeType"] = i18n("Size Type");
+
+ //for labels
+ m_propDesc["textFormat"] = i18n("Text Format");
+ m_propValDesc["PlainText"] = i18n("For Text Format", "Plain");
+ m_propValDesc["RichText"] = i18n("For Text Format", "Hypertext");
+ m_propValDesc["AutoText"] = i18n("For Text Format", "Auto");
+ m_propValDesc["LogText"] = i18n("For Text Format", "Log");
+
+ //KTextEdit
+ m_propDesc["tabStopWidth"] = i18n("Tab Stop Width");
+ m_propDesc["tabChangesFocus"] = i18n("Tab Changes Focus");
+ m_propDesc["wrapPolicy"] = i18n("Word Wrap Policy");
+ m_propValDesc["AtWordBoundary"] = i18n("For Word Wrap Policy", "At Word Boundary");
+ m_propValDesc["Anywhere"] = i18n("For Word Wrap Policy", "Anywhere");
+ m_propValDesc["AtWordOrDocumentBoundary"] = i18n("For Word Wrap Policy", "At Word Boundary If Possible");
+ m_propDesc["wordWrap"] = i18n("Word Wrapping");
+ m_propDesc["wrapColumnOrWidth"] = i18n("Word Wrap Position");
+ m_propValDesc["NoWrap"] = i18n("For Word Wrap Position", "None");
+ m_propValDesc["WidgetWidth"] = i18n("For Word Wrap Position", "Widget's Width");
+ m_propValDesc["FixedPixelWidth"] = i18n("For Word Wrap Position", "In Pixels");
+ m_propValDesc["FixedColumnWidth"] = i18n("For Word Wrap Position", "In Columns");
+ m_propDesc["linkUnderline"] = i18n("Links Underlined");
+
+ //internal props
+ setInternalProperty("Line","orientationSelectionPopup","1");
+ setInternalProperty("Line","orientationSelectionPopup:horizontalIcon","line_horizontal");
+ setInternalProperty("Line","orientationSelectionPopup:verticalIcon","line_vertical");
+ setInternalProperty("Line","orientationSelectionPopup:horizontalText",i18n("Insert &Horizontal Line"));
+ setInternalProperty("Line","orientationSelectionPopup:verticalText",i18n("Insert &Vertical Line"));
+ setInternalProperty("Spring","orientationSelectionPopup","1");
+ setInternalProperty("Spring","orientationSelectionPopup:horizontalIcon","spring");
+ setInternalProperty("Spring","orientationSelectionPopup:verticalIcon","spring_vertical");
+ setInternalProperty("Spring","orientationSelectionPopup:horizontalText",i18n("Insert &Horizontal Spring"));
+ setInternalProperty("Spring","orientationSelectionPopup:verticalText",i18n("Insert &Vertical Spring"));
+}
+
+StdWidgetFactory::~StdWidgetFactory()
+{
+}
+
+QWidget*
+StdWidgetFactory::createWidget(const QCString &c, QWidget *p, const char *n,
+ KFormDesigner::Container *container, int options)
+{
+ QWidget *w=0;
+ QString text( container->form()->library()->textForWidgetName(n, c) );
+ const bool designMode = options & KFormDesigner::WidgetFactory::DesignViewMode;
+
+ if(c == "QLabel")
+ w = new QLabel(text, p, n);
+ else if(c == "KexiPictureLabel")
+ w = new KexiPictureLabel(DesktopIcon("image"), p, n);
+
+ else if(c == "KLineEdit")
+ {
+ w = new KLineEdit(p, n);
+ if (designMode)
+ w->setCursor(QCursor(Qt::ArrowCursor));
+ }
+ else if(c == "KPushButton")
+ w = new KPushButton(/*i18n("Button")*/text, p, n);
+
+ else if(c == "QRadioButton")
+ w = new QRadioButton(/*i18n("Radio Button")*/text, p, n);
+
+ else if(c == "QCheckBox")
+ w = new QCheckBox(/*i18n("Check Box")*/text, p, n);
+
+ else if(c == "KIntSpinBox")
+ w = new KIntSpinBox(p, n);
+
+ else if(c == "KComboBox")
+ w = new KComboBox(p, n);
+
+ else if(c == "KListBox")
+ w = new KListBox(p, n);
+
+ else if(c == "KTextEdit")
+ w = new KTextEdit(/*i18n("Enter your text here")*/text, QString::null, p, n);
+
+ else if(c == "KListView")
+ {
+ w = new KListView(p, n);
+ if(container->form()->interactiveMode())
+ ((KListView*)w)->addColumn(i18n("Column 1"));
+ ((KListView*)w)->setRootIsDecorated(true);
+ }
+ else if(c == "QSlider")
+ w = new QSlider(Qt::Horizontal, p, n);
+
+ else if(c == "KProgress")
+ w = new KProgress(p, n);
+
+ else if(c == "KDateWidget")
+ w = new KDateWidget(QDate::currentDate(), p, n);
+
+ else if(c == "KTimeWidget")
+ w = new KTimeWidget(QTime::currentTime(), p, n);
+
+ else if(c == "KDateTimeWidget")
+ w = new KDateTimeWidget(QDateTime::currentDateTime(), p, n);
+
+ else if(c == "Line")
+ w = new Line(options & WidgetFactory::VerticalOrientation ? Line::Vertical : Line::Horizontal, p, n);
+
+ else if(c == "Spring") {
+ w = new Spring(p, n);
+ if (0 == (options & WidgetFactory::AnyOrientation))
+ static_cast<Spring*>(w)->setOrientation(
+ (options & WidgetFactory::VerticalOrientation) ? Qt::Vertical : Qt::Horizontal);
+ }
+
+ if(w)
+ return w;
+
+ kdDebug() << "WARNING :: w == 0 " << endl;
+ return 0;
+}
+
+bool
+StdWidgetFactory::previewWidget(const QCString &classname, QWidget *widget, KFormDesigner::Container *)
+{
+ if(classname == "Spring") {
+ ((Spring*)widget)->setPreviewMode();
+ return true;
+ }
+ return false;
+}
+
+bool
+StdWidgetFactory::createMenuActions(const QCString &classname, QWidget *, QPopupMenu *menu,
+ KFormDesigner::Container *)
+{
+ if((classname == "QLabel") || (classname == "KTextEdit"))
+ {
+ menu->insertItem(SmallIconSet("edit"), i18n("Edit Rich Text"), this, SLOT(editText()));
+ return true;
+ }
+ else if(classname == "KListView")
+ {
+ menu->insertItem(SmallIconSet("edit"), i18n("Edit Listview Contents"), this, SLOT(editListContents()));
+ return true;
+ }
+
+ return false;
+}
+
+bool
+StdWidgetFactory::startEditing(const QCString &classname, QWidget *w, KFormDesigner::Container *container)
+{
+ setWidget(w, container);
+// m_container = container;
+ if(classname == "KLineEdit")
+ {
+ KLineEdit *lineedit = static_cast<KLineEdit*>(w);
+ createEditor(classname, lineedit->text(), lineedit, container, lineedit->geometry(), lineedit->alignment(), true);
+ return true;
+ }
+ else if(classname == "QLabel")
+ {
+ QLabel *label = static_cast<QLabel*>(w);
+ if(label->textFormat() == RichText)
+ {
+ //m_widget = w;
+// setWidget(w, container);
+ editText();
+ }
+ else
+ createEditor(classname, label->text(), label, container, label->geometry(), label->alignment());
+ return true;
+ }
+ else if(classname == "KPushButton")
+ {
+ KPushButton *push = static_cast<KPushButton*>(w);
+ QRect r = w->style().subRect(QStyle::SR_PushButtonContents, w);
+ QRect editorRect = QRect(push->x() + r.x(), push->y() + r.y(), r.width(), r.height());
+ //r.setX(r.x() + 5);
+ //r.setY(r.y() + 5);
+ //r.setWidth(r.width()-10);
+ //r.setHeight(r.height() - 10);
+ createEditor(classname, push->text(), push, container, editorRect, Qt::AlignCenter, false, false, Qt::PaletteButton);
+ return true;
+ }
+ else if(classname == "QRadioButton")
+ {
+ QRadioButton *radio = static_cast<QRadioButton*>(w);
+ QRect r = w->style().subRect(QStyle::SR_RadioButtonContents, w);
+ QRect editorRect = QRect(radio->x() + r.x(), radio->y() + r.y(), r.width(), r.height());
+ createEditor(classname, radio->text(), radio, container, editorRect, Qt::AlignAuto);
+ return true;
+ }
+ else if(classname == "QCheckBox")
+ {
+ QCheckBox *check = static_cast<QCheckBox*>(w);
+ //QRect r(check->geometry());
+ //r.setX(r.x() + 20);
+ QRect r = w->style().subRect(QStyle::SR_CheckBoxContents, w);
+ QRect editorRect = QRect(check->x() + r.x(), check->y() + r.y(), r.width(), r.height());
+ createEditor(classname, check->text(), check, container, editorRect, Qt::AlignAuto);
+ return true;
+ }
+ else if((classname == "KComboBox") || (classname == "KListBox"))
+ {
+ QStringList list;
+ if(classname == "KListBox")
+ {
+ KListBox *listbox = (KListBox*)w;
+ for(uint i=0; i < listbox->count(); i++)
+ list.append(listbox->text(i));
+ }
+ else if(classname == "KComboBox")
+ {
+ KComboBox *combo = (KComboBox*)w;
+ for(int i=0; i < combo->count(); i++)
+ list.append(combo->text(i));
+ }
+
+ if(editList(w, list))
+ {
+ if(classname == "KListBox")
+ {
+ ((KListBox*)w)->clear();
+ ((KListBox*)w)->insertStringList(list);
+ }
+ else if(classname == "KComboBox")
+ {
+ ((KComboBox*)w)->clear();
+ ((KComboBox*)w)->insertStringList(list);
+ }
+ }
+ return true;
+ }
+ else if((classname == "KTextEdit") || (classname == "KDateTimeWidget") || (classname == "KTimeWidget") ||
+ (classname == "KDateWidget") || (classname == "KIntSpinBox")) {
+ disableFilter(w, container);
+ return true;
+ }
+ return false;
+}
+
+bool
+StdWidgetFactory::clearWidgetContent(const QCString &classname, QWidget *w)
+{
+ if(classname == "KLineEdit")
+ ((KLineEdit*)w)->clear();
+ else if(classname == "KListBox")
+ ((KListBox*)w)->clear();
+ else if(classname == "KListView")
+ ((KListView*)w)->clear();
+ else if(classname == "KComboBox")
+ ((KComboBox*)w)->clear();
+ else if(classname == "KTextEdit")
+ ((KTextEdit*)w)->clear();
+ else
+ return false;
+ return true;
+}
+
+bool
+StdWidgetFactory::changeText(const QString &text)
+{
+ QCString n = WidgetFactory::widget()->className();
+ QWidget *w = WidgetFactory::widget();
+ if(n == "KIntSpinBox")
+ ((KIntSpinBox*)w)->setValue(text.toInt());
+ else
+ changeProperty("text", text, m_container->form());
+
+ /* By-hand method not needed as sizeHint() can do that for us
+ QFontMetrics fm = w->fontMetrics();
+ QSize s(fm.width( text ), fm.height());
+ int width;
+ if(n == "QLabel") // labels are resized to fit the text
+ {
+ w->resize(w->sizeHint());
+ WidgetFactory::m_editor->resize(w->size());
+ return;
+ }
+ // and other widgets are just enlarged if needed
+ else if(n == "KPushButton")
+ width = w->style().sizeFromContents( QStyle::CT_PushButton, w, s).width();
+ else if(n == "QCheckBox")
+ width = w->style().sizeFromContents( QStyle::CT_CheckBox, w, s).width();
+ else if(n == "QRadioButton")
+ width = w->style().sizeFromContents( QStyle::CT_RadioButton, w, s).width();
+ else
+ return;
+ int width = w->sizeHint().width();*/
+
+#if 0 //not needed here, size hint is used on creation in InsertWidgetCommand::execute()
+ if(w->width() < width)
+ {
+ w->resize(width, w->height() );
+ //WidgetFactory::m_editor->resize(w->size());
+ }
+#endif
+ return true;
+}
+
+void
+StdWidgetFactory::resizeEditor(QWidget *editor, QWidget *widget, const QCString &classname)
+{
+ QSize s = widget->size();
+ QPoint p = widget->pos();
+ QRect r;
+
+ if(classname == "QRadioButton")
+ {
+ r = widget->style().subRect(QStyle::SR_RadioButtonContents, widget);
+ p += r.topLeft();
+ s.setWidth(r.width());
+ }
+ else if(classname == "QCheckBox")
+ {
+ r = widget->style().subRect(QStyle::SR_CheckBoxContents, widget);
+ p += r.topLeft();
+ s.setWidth(r.width());
+ }
+ else if(classname == "KPushButton")
+ {
+ r = widget->style().subRect(QStyle::SR_PushButtonContents, widget);
+ p += r.topLeft();
+ s = r.size();
+ }
+
+ editor->resize(s);
+ editor->move(p);
+}
+
+bool
+StdWidgetFactory::saveSpecialProperty(const QCString &classname, const QString &name, const QVariant &, QWidget *w, QDomElement &parentNode, QDomDocument &domDoc)
+{
+ if(name == "list_items" && classname == "KComboBox")
+ {
+ KComboBox *combo = (KComboBox*)w;
+ for(int i=0; i < combo->count(); i++)
+ {
+ QDomElement item = domDoc.createElement("item");
+ KFormDesigner::FormIO::savePropertyElement(item, domDoc, "property", "text", combo->text(i));
+ parentNode.appendChild(item);
+ }
+ return true;
+ }
+ else if(name == "list_items" && classname == "KListBox")
+ {
+ KListBox *listbox = (KListBox*)w;
+ for(uint i=0; i < listbox->count(); i++)
+ {
+ QDomElement item = domDoc.createElement("item");
+ KFormDesigner::FormIO::savePropertyElement(item, domDoc, "property", "text", listbox->text(i));
+ parentNode.appendChild(item);
+ }
+ return true;
+ }
+ else if(name == "list_contents" && classname == "KListView")
+ {
+ KListView *listview = (KListView*)w;
+ // First we save the columns
+ for(int i = 0; i < listview->columns(); i++)
+ {
+ QDomElement item = domDoc.createElement("column");
+ KFormDesigner::FormIO::savePropertyElement(item, domDoc, "property", "text", listview->columnText(i));
+ KFormDesigner::FormIO::savePropertyElement(item, domDoc, "property", "width", listview->columnWidth(i));
+ KFormDesigner::FormIO::savePropertyElement(item, domDoc, "property", "resizable", listview->header()->isResizeEnabled(i));
+ KFormDesigner::FormIO::savePropertyElement(item, domDoc, "property", "clickable", listview->header()->isClickEnabled(i));
+ KFormDesigner::FormIO::savePropertyElement(item, domDoc, "property", "fullwidth", listview->header()->isStretchEnabled(i));
+ parentNode.appendChild(item);
+ }
+
+ // Then we save the list view items
+ QListViewItem *item = listview->firstChild();
+ while(item)
+ {
+ saveListItem(item, parentNode, domDoc);
+ item = item->nextSibling();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void
+StdWidgetFactory::saveListItem(QListViewItem *item, QDomNode &parentNode, QDomDocument &domDoc)
+{
+ QDomElement element = domDoc.createElement("item");
+ parentNode.appendChild(element);
+
+ // We save the text of each column
+ for(int i = 0; i < item->listView()->columns(); i++)
+ KFormDesigner::FormIO::savePropertyElement(element, domDoc, "property", "text", item->text(i));
+
+ // Then we save every sub items
+ QListViewItem *child = item->firstChild();
+ while(child)
+ {
+ saveListItem(child, element, domDoc);
+ child = child->nextSibling();
+ }
+}
+
+bool
+StdWidgetFactory::readSpecialProperty(const QCString &classname, QDomElement &node, QWidget *w, KFormDesigner::ObjectTreeItem *)
+{
+ QString tag = node.tagName();
+ QString name = node.attribute("name");
+
+ if((tag == "item") && (classname == "KComboBox"))
+ {
+ KComboBox *combo = (KComboBox*)w;
+ QVariant val = KFormDesigner::FormIO::readPropertyValue(node.firstChild().firstChild(), w, name);
+ if(val.canCast(QVariant::Pixmap))
+ combo->insertItem(val.toPixmap());
+ else
+ combo->insertItem(val.toString());
+ return true;
+ }
+
+ if((tag == "item") && (classname == "KListBox"))
+ {
+ KListBox *listbox = (KListBox*)w;
+ QVariant val = KFormDesigner::FormIO::readPropertyValue(node.firstChild().firstChild(), w, name);
+ if(val.canCast(QVariant::Pixmap))
+ listbox->insertItem(val.toPixmap());
+ else
+ listbox->insertItem(val.toString());
+ return true;
+ }
+
+ if((tag == "column") && (classname == "KListView"))
+ {
+ KListView *listview = (KListView*)w;
+ int id=0;
+ for(QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ QString prop = n.toElement().attribute("name");
+ QVariant val = KFormDesigner::FormIO::readPropertyValue(n.firstChild(), w, name);
+ if(prop == "text")
+ id = listview->addColumn(val.toString());
+ else if(prop == "width")
+ listview->setColumnWidth(id, val.toInt());
+ else if(prop == "resizable")
+ listview->header()->setResizeEnabled(val.toBool(), id);
+ else if(prop == "clickable")
+ listview->header()->setClickEnabled(val.toBool(), id);
+ else if(prop == "fullwidth")
+ listview->header()->setStretchEnabled(val.toBool(), id);
+ }
+ return true;
+ }
+ else if((tag == "item") && (classname == "KListView"))
+ {
+ KListView *listview = (KListView*)w;
+ readListItem(node, 0, listview);
+ return true;
+ }
+
+ return false;
+}
+
+void
+StdWidgetFactory::readListItem(QDomElement &node, QListViewItem *parent, KListView *listview)
+{
+ QListViewItem *item;
+ if(parent)
+ item = new KListViewItem(parent);
+ else
+ item = new KListViewItem(listview);
+
+ // We need to move the item at the end of the list
+ QListViewItem *last;
+ if(parent)
+ last = parent->firstChild();
+ else
+ last = listview->firstChild();
+
+ while(last->nextSibling())
+ last = last->nextSibling();
+ item->moveItem(last);
+
+ int i = 0;
+ for(QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ QDomElement childEl = n.toElement();
+ QString prop = childEl.attribute("name");
+ QString tag = childEl.tagName();
+
+ // We read sub items
+ if(tag == "item")
+ {
+ item->setOpen(true);
+ readListItem(childEl, item, listview);
+ }
+ // and column texts
+ else if((tag == "property") && (prop == "text"))
+ {
+ QVariant val = KFormDesigner::FormIO::readPropertyValue(n.firstChild(), listview, "item");
+ item->setText(i, val.toString());
+ i++;
+ }
+ }
+}
+
+bool
+StdWidgetFactory::isPropertyVisibleInternal(const QCString &classname,
+ QWidget *w, const QCString &property, bool isTopLevel)
+{
+ bool ok = true;
+ if(classname == "FormWidgetBase")
+ {
+ if(property == "iconText"
+ || property == "geometry" /*nonsense for toplevel widget*/)
+ return false;
+ }
+ else if (classname == "CustomWidget")
+ {
+ }
+ else if(classname == "Spring")
+ {
+ return Spring::isPropertyVisible(property);
+ }
+ else if(classname == "KexiPictureLabel")
+ {
+ if((property == "text") || (property == "indent") || (property == "textFormat") || (property == "font") || (property == "alignment"))
+ return false;
+ }
+ else if(classname == "QLabel")
+ {
+ if(property == "pixmap")
+ return false;
+ }
+ else if(classname == "KLineEdit")
+ {
+ if(property == "vAlign")
+ return false;
+ }
+ else if(classname == "KTextEdit")
+ ok = m_showAdvancedProperties ||
+ property!="undoDepth"
+ && property!="undoRedoEnabled" //always true!
+ && property!="dragAutoScroll" //always true!
+ && property!="overwriteMode" //always false!
+ && property!="resizePolicy"
+ && property!="autoFormatting" //too complex
+#ifdef KEXI_NO_UNFINISHED
+ && property!="paper"
+#endif
+ ;
+ else if(classname == "Line")
+ {
+ if((property == "frameShape") || (property == "font") || (property == "margin"))
+ return false;
+ }
+ else if(classname=="QCheckBox")
+ {
+ ok = m_showAdvancedProperties || (property != "autoRepeat");
+ }
+ else if(classname=="QRadioButton")
+ {
+ ok = m_showAdvancedProperties || (property != "autoRepeat");
+ }
+ else if(classname=="KPushButton")
+ {
+//! @todo reenable autoDefault / default if the top level window is dialog...
+ ok = m_showAdvancedProperties || (property != "autoDefault" && property != "default");
+ }
+ return ok && WidgetFactory::isPropertyVisibleInternal(classname, w, property, isTopLevel);
+}
+
+QValueList<QCString>
+StdWidgetFactory::autoSaveProperties(const QCString &classname)
+{
+ QValueList<QCString> l;
+
+ if(classname == "QLabel")
+ l << "text";
+ if(classname == "KPushButton")
+ l << "text";
+ else if(classname == "KexiPictureLabel")
+ l << "pixmap";
+ else if(classname == "KComboBox")
+ l << "list_items";
+ else if(classname == "KListBox")
+ l << "list_items";
+ else if(classname == "KListView")
+ l << "list_contents";
+ else if(classname == "Line")
+ l << "orientation";
+ else if(classname == "KTimeWidget")
+ l << "time";
+ else if(classname == "KDateWidget")
+ l << "date";
+ else if(classname == "KDateTimeWidget")
+ l << "dateTime";
+ else if(classname == "Spring")
+ l << "sizeType" << "orientation";
+ else if(classname == "KTextEdit")
+ l << "textFormat" << "text";
+
+ return l;
+}
+
+void
+StdWidgetFactory::editText()
+{
+ QCString classname = widget()->className();
+ QString text;
+ if(classname == "KTextEdit")
+ text = ((KTextEdit*)widget())->text();
+ else if(classname == "QLabel")
+ text = ((QLabel*)widget())->text();
+
+ if(editRichText(widget(), text))
+ {
+ changeProperty("textFormat", "RichText", m_container->form());
+ changeProperty("text", text, m_container->form());
+ }
+
+ if(classname == "QLabel")
+ widget()->resize(widget()->sizeHint());
+}
+
+void
+StdWidgetFactory::editListContents()
+{
+ if(widget()->inherits("QListView"))
+ editListView((QListView*)widget());
+}
+
+void
+StdWidgetFactory::setPropertyOptions( KFormDesigner::WidgetPropertySet& buf, const KFormDesigner::WidgetInfo& info, QWidget *w )
+{
+ Q_UNUSED( info );
+ Q_UNUSED( w );
+
+ if (buf.contains("indent")) {
+ buf["indent"].setOption("min", -1);
+ buf["indent"].setOption("minValueText", i18n("default indent value", "default"));
+ }
+}
+
+KFORMDESIGNER_WIDGET_FACTORY(StdWidgetFactory, stdwidgets)
+
+#include "stdwidgetfactory.moc"
+
diff --git a/kexi/formeditor/factories/stdwidgetfactory.h b/kexi/formeditor/factories/stdwidgetfactory.h
new file mode 100644
index 000000000..c0e56c5bb
--- /dev/null
+++ b/kexi/formeditor/factories/stdwidgetfactory.h
@@ -0,0 +1,99 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef STDWIDGETFACTORY_H
+#define STDWIDGETFACTORY_H
+
+#include <qframe.h>
+
+#include "widgetfactory.h"
+#include "container.h"
+
+class KFORMEDITOR_EXPORT KexiPictureLabel : public QLabel
+{
+ Q_OBJECT
+
+ public:
+ KexiPictureLabel(const QPixmap &pix, QWidget *parent, const char *name);
+ ~KexiPictureLabel(){;}
+
+ virtual bool setProperty(const char *name, const QVariant &value);
+};
+
+class KFORMEDITOR_EXPORT Line : public QFrame
+{
+ Q_OBJECT
+ Q_PROPERTY(Orientation orientation READ orientation WRITE setOrientation)
+
+ public:
+ Line(Orientation orient, QWidget *parent, const char *name);
+ ~Line(){;}
+
+ void setOrientation(Orientation orient);
+ Orientation orientation() const;
+};
+
+//! Factory for all basic widgets, including Spring (not containers)
+class StdWidgetFactory : public KFormDesigner::WidgetFactory
+{
+ Q_OBJECT
+
+ public:
+ StdWidgetFactory(QObject *parent, const char *name, const QStringList &args);
+ ~StdWidgetFactory();
+
+ virtual QWidget *createWidget(const QCString &c, QWidget *p, const char *n,
+ KFormDesigner::Container *container, int options = DefaultOptions);
+
+ virtual bool createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *container);
+ virtual bool startEditing(const QCString &classname, QWidget *w,
+ KFormDesigner::Container *container);
+ virtual bool previewWidget(const QCString &classname, QWidget *widget,
+ KFormDesigner::Container *container);
+ virtual bool clearWidgetContent(const QCString &classname, QWidget *w);
+
+ virtual bool saveSpecialProperty(const QCString &classname,
+ const QString &name, const QVariant &value, QWidget *w,
+ QDomElement &parentNode, QDomDocument &parent);
+ virtual bool readSpecialProperty(const QCString &classname, QDomElement &node,
+ QWidget *w, KFormDesigner::ObjectTreeItem *item);
+ virtual QValueList<QCString> autoSaveProperties(const QCString &classname);
+
+ virtual void setPropertyOptions( KFormDesigner::WidgetPropertySet& buf,
+ const KFormDesigner::WidgetInfo& info, QWidget *w );
+
+ public slots:
+ void editText();
+ void editListContents();
+
+ protected:
+ virtual bool isPropertyVisibleInternal(const QCString &classname, QWidget *w,
+ const QCString &property, bool isTopLevel);
+ virtual bool changeText(const QString &newText);
+ virtual void resizeEditor(QWidget *editor, QWidget *widget, const QCString &classname);
+ void saveListItem(QListViewItem *item, QDomNode &parentNode, QDomDocument &domDoc);
+ void readListItem(QDomElement &node, QListViewItem *parent, KListView *listview);
+
+ private:
+// KFormDesigner::Container *m_container;
+// QWidget *m_widget;
+};
+
+#endif
diff --git a/kexi/formeditor/form.cpp b/kexi/formeditor/form.cpp
new file mode 100644
index 000000000..a5d570025
--- /dev/null
+++ b/kexi/formeditor/form.cpp
@@ -0,0 +1,600 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qwidget.h>
+#include <qlabel.h>
+#include <qobjectlist.h>
+#include <qptrdict.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kcommand.h>
+#include <kaction.h>
+#include <kmessagebox.h>
+
+#include "container.h"
+#include "objecttree.h"
+#include "widgetpropertyset.h"
+#include "formIO.h"
+#include "formmanager.h"
+#include "widgetlibrary.h"
+#include "spring.h"
+#include "pixmapcollection.h"
+#include "events.h"
+#include "utils.h"
+#include "form.h"
+#include <koproperty/property.h>
+#include <kexiutils/utils.h>
+
+using namespace KFormDesigner;
+
+FormPrivate::FormPrivate()
+{
+ toplevel = 0;
+ topTree = 0;
+ widget = 0;
+ resizeHandles.setAutoDelete(true);
+ dirty = false;
+ interactive = true;
+ design = true;
+ autoTabstops = false;
+ tabstops.setAutoDelete(false);
+ connBuffer = new ConnectionBuffer();
+ formatVersion = KFormDesigner::version();
+ originalFormatVersion = KFormDesigner::version();
+}
+
+FormPrivate::~FormPrivate()
+{
+ delete history;
+ delete topTree;
+ delete connBuffer;
+ connBuffer = 0;
+ resizeHandles.setAutoDelete(false);
+ // otherwise, it tries to delete widgets which doesn't exist anymore
+}
+
+//--------------------------------------
+
+FormWidget::FormWidget()
+ : m_form(0)
+{
+}
+
+FormWidget::~FormWidget()
+{
+ if (m_form) {
+ m_form->setFormWidget(0);
+ }
+}
+
+//--------------------------------------
+
+Form::Form(WidgetLibrary* library, const char *name, bool designMode)
+ : QObject(library, name)
+ , m_lib(library)
+{
+ d = new FormPrivate();
+// d->manager = manager;
+ d->design = designMode;
+
+ // Init actions
+ d->collection = new KActionCollection(0, this);
+ d->history = new KCommandHistory(d->collection, true);
+ connect(d->history, SIGNAL(commandExecuted()), this, SLOT(slotCommandExecuted()));
+ connect(d->history, SIGNAL(documentRestored()), this, SLOT(slotFormRestored()));
+}
+
+Form::~Form()
+{
+ emit destroying();
+ delete d;
+ d = 0;
+}
+
+QWidget*
+Form::widget() const
+{
+ if(d->topTree)
+ return d->topTree->widget();
+ else if(d->toplevel)
+ return d->toplevel->widget();
+ else // preview form
+ return d->widget;
+}
+
+//////////////// Container -related functions ///////////////////////
+
+void
+Form::createToplevel(QWidget *container, FormWidget *formWidget, const QCString &)
+{
+ kdDebug() << "Form::createToplevel() container= "<< (container ? container->name() : "<NULL>")
+ << " formWidget=" << formWidget << "className=" << name() << endl;
+
+ setFormWidget( formWidget );
+ d->toplevel = new Container(0, container, this, name());
+ d->topTree = new ObjectTree(i18n("Form"), container->name(), container, d->toplevel);
+ d->toplevel->setObjectTree(d->topTree);
+ d->toplevel->setForm(this);
+ d->pixcollection = new PixmapCollection(container->name(), this);
+
+ d->topTree->setWidget(container);
+//! todo: copy caption in Kexi from object's caption
+// d->topTree->addModifiedProperty("caption", name());
+ //m_topTree->addModifiedProperty("icon");
+
+ connect(container, SIGNAL(destroyed()), this, SLOT(formDeleted()));
+
+ kdDebug() << "Form::createToplevel(): d->toplevel=" << d->toplevel << endl;
+}
+
+
+Container*
+Form::activeContainer()
+{
+ ObjectTreeItem *it;
+ if(d->selected.count() == 0)
+ return d->toplevel;
+
+ if(d->selected.count() == 1)
+ it = d->topTree->lookup(d->selected.last()->name());
+ else
+ it = commonParentContainer( &(d->selected) );
+
+ if (!it)
+ return 0;
+ if(it->container())
+ return it->container();
+ else
+ return it->parent()->container();
+}
+
+ObjectTreeItem*
+Form::commonParentContainer(WidgetList *wlist)
+{
+ ObjectTreeItem *item = 0;
+ WidgetList *list = new WidgetList();
+
+ // Creates a list of all widget parents
+ for(QWidget *w = wlist->first(); w; w = wlist->next())
+ {
+ if(list->findRef(w->parentWidget()) == -1)
+ list->append(w->parentWidget());
+ }
+
+ removeChildrenFromList(*list);
+
+ // one widget remains == the container we are looking for
+ if(list->count() == 1)
+ item = d->topTree->lookup(list->first()->name());
+ else // we need to go one level up
+ item = commonParentContainer(list);
+
+ delete list;
+ return item;
+}
+
+Container*
+Form::parentContainer(QWidget *w)
+{
+ ObjectTreeItem *it;
+ if(!w)
+ return 0;
+ // it = d->topTree->lookup(d->selected.last()->name());
+ //else
+ it = d->topTree->lookup(w->name());
+
+ if(it->parent()->container())
+ return it->parent()->container();
+ else
+ return it->parent()->parent()->container();
+}
+
+
+
+void
+Form::setDesignMode(bool design)
+{
+ d->design = design;
+ if(!design)
+ {
+ ObjectTreeDict *dict = new ObjectTreeDict( *(d->topTree->dict()) );
+ ObjectTreeDictIterator it(*dict);
+ for(; it.current(); ++it)
+ m_lib->previewWidget(it.current()->widget()->className(), it.current()->widget(), d->toplevel);
+ delete dict;
+
+ d->widget = d->topTree->widget();
+ delete (d->topTree);
+ d->topTree = 0;
+ delete (d->toplevel);
+ d->toplevel = 0;
+ }
+}
+
+
+///////////////////////////// Selection stuff ///////////////////////
+
+void
+Form::setSelectedWidget(QWidget *w, bool add, bool dontRaise, bool moreWillBeSelected)
+{
+ if((d->selected.isEmpty()) || (w == widget()) || (d->selected.first() == widget()))
+ add = false;
+
+ if(!w)
+ {
+ setSelectedWidget(widget());
+ return;
+ }
+
+ //raise selected widget and all possible parents
+ QWidget *wtmp = w;
+ while(!dontRaise && wtmp && wtmp->parentWidget() && (wtmp != widget()))
+ {
+ wtmp->raise();
+ if(d->resizeHandles[ wtmp->name() ])
+ d->resizeHandles[ wtmp->name() ]->raise();
+ wtmp = wtmp->parentWidget();
+ }
+
+ if (wtmp)
+ wtmp->setFocus();
+
+ if(!add)
+ {
+ d->selected.clear();
+ d->resizeHandles.clear();
+ }
+ d->selected.append(w);
+ emit selectionChanged(w, add, moreWillBeSelected);
+ emitActionSignals(false);
+
+ // WidgetStack and TabWidget pages widgets shouldn't have resize handles, but their parent
+ if(!FormManager::self()->isTopLevel(w) && w->parentWidget() && w->parentWidget()->isA("QWidgetStack"))
+ {
+ w = w->parentWidget();
+ if(w->parentWidget() && w->parentWidget()->inherits("QTabWidget"))
+ w = w->parentWidget();
+ }
+
+ if(w && w != widget())
+ d->resizeHandles.insert(w->name(), new ResizeHandleSet(w, this));
+}
+
+ResizeHandleSet*
+Form::resizeHandlesForWidget(QWidget* w)
+{
+ return d->resizeHandles[w->name()];
+}
+
+void
+Form::unSelectWidget(QWidget *w)
+{
+ d->selected.remove(w);
+ d->resizeHandles.remove(w->name());
+}
+
+void
+Form::selectFormWidget()
+{
+ setSelectedWidget(widget(), false);
+}
+
+void
+Form::clearSelection()
+{
+ d->selected.clear();
+ d->resizeHandles.clear();
+ emit selectionChanged(0, false);
+ emitActionSignals(false);
+}
+
+void
+Form::emitActionSignals(bool withUndoAction)
+{
+ // Update menu and toolbar items
+ if(d->selected.count() > 1)
+ FormManager::self()->emitWidgetSelected(this, true);
+ else if(d->selected.first() != widget())
+ FormManager::self()->emitWidgetSelected(this, false);
+ else
+ FormManager::self()->emitFormWidgetSelected(this);
+
+ if(!withUndoAction)
+ return;
+
+ KAction *undoAction = d->collection->action("edit_undo");
+ if(undoAction)
+ FormManager::self()->emitUndoEnabled(undoAction->isEnabled(), undoAction->text());
+
+ KAction *redoAction = d->collection->action("edit_redo");
+ if(redoAction)
+ FormManager::self()->emitRedoEnabled(redoAction->isEnabled(), redoAction->text());
+}
+
+void
+Form::emitSelectionSignals()
+{
+ emit selectionChanged(selectedWidgets()->first(), false);
+// for(QWidget *w = selectedWidgets()->next(); w; w = selectedWidgets()->next())
+// emit selectionChanged(selectedWidgets()->first(), true);
+ for (WidgetListIterator it(*selectedWidgets()); it.current(); ++it)
+ emit selectionChanged(it.current(), true);
+}
+
+/////////////////////////// Various slots and signals /////////////////////
+void
+Form::formDeleted()
+{
+// clearSelection();
+ d->selected.clear();
+ d->resizeHandles.setAutoDelete(false);
+ d->resizeHandles.clear();
+ d->resizeHandles.setAutoDelete(true);
+// emit selectionChanged(0, false);
+// emitActionSignals(false);
+
+ FormManager::self()->deleteForm(this);
+ //delete this;
+ deleteLater();
+}
+
+void
+Form::changeName(const QCString &oldname, const QCString &newname)
+{
+ if(oldname == newname)
+ return;
+ if(!d->topTree->rename(oldname, newname)) // rename failed
+ {
+ KMessageBox::sorry(widget()->topLevelWidget(),
+ i18n("Renaming widget \"%1\" to \"%2\" failed.").arg(oldname).arg(newname));
+//moved to WidgetPropertySet::slotChangeProperty()
+// KMessageBox::sorry(widget()->topLevelWidget(),
+// i18n("A widget with this name already exists. "
+// "Please choose another name or rename existing widget."));
+ kdDebug() << "Form::changeName() : ERROR : A widget named " << newname << " already exists" << endl;
+ FormManager::self()->propertySet()->property("name") = QVariant(oldname);
+ }
+ else
+ {
+ d->connBuffer->fixName(oldname, newname);
+ ResizeHandleSet *temp = d->resizeHandles.take(oldname);
+ d->resizeHandles.insert(newname, temp);
+ }
+}
+
+void
+Form::emitChildAdded(ObjectTreeItem *item)
+{
+ addWidgetToTabStops(item);
+ emit childAdded(item);
+}
+
+void
+Form::emitChildRemoved(ObjectTreeItem *item)
+{
+ d->tabstops.remove(item);
+ if(d->connBuffer)
+ d->connBuffer->removeAllConnectionsForWidget(item->name());
+ emit childRemoved(item);
+}
+
+void
+Form::addCommand(KCommand *command, bool execute)
+{
+ emit FormManager::self()->dirty(this, true);
+ d->dirty = true;
+ d->history->addCommand(command, execute);
+ if(!execute) // simulate command to activate 'undo' menu
+ slotCommandExecuted();
+}
+
+void
+Form::clearCommandHistory()
+{
+ d->history->clear();
+ FormManager::self()->emitUndoEnabled(false, QString::null);
+ FormManager::self()->emitRedoEnabled(false, QString::null);
+}
+
+void
+Form::slotCommandExecuted()
+{
+ emit FormManager::self()->dirty(this, true);
+ d->dirty = true;
+ // because actions text is changed after the commandExecuted() signal is emitted
+ QTimer::singleShot(10, this, SLOT(emitUndoEnabled()));
+ QTimer::singleShot(10, this, SLOT(emitRedoEnabled()));
+}
+
+void
+Form::emitUndoEnabled()
+{
+ KAction *undoAction = d->collection->action("edit_undo");
+ if(undoAction)
+ FormManager::self()->emitUndoEnabled(undoAction->isEnabled(), undoAction->text());
+}
+
+void
+Form::emitRedoEnabled()
+{
+ KAction *redoAction = d->collection->action("edit_redo");
+ if(redoAction)
+ FormManager::self()->emitRedoEnabled(redoAction->isEnabled(), redoAction->text());
+}
+
+void
+Form::slotFormRestored()
+{
+ emit FormManager::self()->dirty(this, false);
+ d->dirty = false;
+}
+
+
+/////////////////////////// Tab stops ////////////////////////
+
+void
+Form::addWidgetToTabStops(ObjectTreeItem *it)
+{
+ QWidget *w = it->widget();
+ if(!w)
+ return;
+ if(!(w->focusPolicy() & QWidget::TabFocus))
+ {
+ if (!w->children())
+ return;
+ // For composed widgets, we check if one of the child can have focus
+ for(QObjectListIterator chIt(*w->children()); chIt.current(); ++chIt) {
+ if(chIt.current()->isWidgetType()) {//QWidget::TabFocus flag will be checked later!
+ if(d->tabstops.findRef(it) == -1) {
+ d->tabstops.append(it);
+ return;
+ }
+ }
+ }
+ }
+ else if(d->tabstops.findRef(it) == -1) // not yet in the list
+ d->tabstops.append(it);
+}
+
+void
+Form::updateTabStopsOrder()
+{
+ for (ObjectTreeListIterator it(d->tabstops);it.current();) {
+ if(!(it.current()->widget()->focusPolicy() & QWidget::TabFocus)) {
+ kexidbg << "Form::updateTabStopsOrder(): widget removed because has no TabFocus: " << it.current()->widget()->name() << endl;
+ d->tabstops.remove( it.current() );
+ }
+ else
+ ++it;
+ }
+}
+
+//! Collects all the containers reculsively. Used by Form::autoAssignTabStops().
+void collectContainers(ObjectTreeItem* item, QPtrDict<char>& containers)
+{
+ if (!item->container())
+ return;
+ if (!containers[ item->container() ]) {
+ kdDebug() << "collectContainers() " << item->container()->objectTree()->className()
+ << " " << item->container()->objectTree()->name() << endl;
+ containers.insert( item->container(), (const char *)1 );
+ }
+ for (ObjectTreeListIterator it(*item->children()); it.current(); ++it)
+ collectContainers(it.current(), containers);
+}
+
+void
+Form::autoAssignTabStops()
+{
+ VerWidgetList list(toplevelContainer()->widget());
+ HorWidgetList hlist(toplevelContainer()->widget());
+
+ // 1. Collect all the containers, as we'll be sorting widgets groupped by containers
+ QPtrDict<char> containers;
+
+ collectContainers( toplevelContainer()->objectTree(), containers );
+
+ foreach_list(ObjectTreeListIterator, it, d->tabstops) {
+ if(it.current()->widget()) {
+ kdDebug() << "Form::autoAssignTabStops() widget to sort: " << it.current()->widget() << endl;
+ list.append(it.current()->widget());
+ }
+ }
+
+ list.sort();
+ foreach_list(QPtrListIterator<QWidget>, iter, list)
+ kdDebug() << iter.current()->className() << " " << iter.current()->name() << endl;
+
+ d->tabstops.clear();
+
+ /// We automatically sort widget from the top-left to bottom-right corner
+ //! \todo Handle RTL layout (ie from top-right to bottom-left)
+ foreach_list(WidgetListIterator, it, list) {
+ QWidget *w = it.current();
+ hlist.append(w);
+
+ ++it;
+ QWidget *nextw = it.current();
+ QObject *page_w = 0;
+ KFormDesigner::TabWidget *tab_w = KFormDesigner::findParent<KFormDesigner::TabWidget>(w, "KFormDesigner::TabWidget", page_w);
+ while (nextw) {
+ if (KexiUtils::hasParent(w, nextw)) // do not group (sort) widgets where on is a child of another
+ break;
+ if (nextw->y() >= (w->y() + 20))
+ break;
+ if (tab_w) {
+ QObject *page_nextw = 0;
+ KFormDesigner::TabWidget *tab_nextw = KFormDesigner::findParent<KFormDesigner::TabWidget>(nextw, "KFormDesigner::TabWidget", page_nextw);
+ if (tab_w == tab_nextw) {
+ if (page_w != page_nextw) // 'nextw' widget within different tab page
+ break;
+ }
+ }
+ hlist.append(nextw);
+ ++it;
+ nextw = it.current();
+ }
+ hlist.sort();
+
+ for(WidgetListIterator it2(hlist); it2.current() != 0; ++it2) {
+ ObjectTreeItem *tree = d->topTree->lookup(it2.current()->name());
+ if(tree) {
+ kdDebug() << "Form::autoAssignTabStops() adding " << tree->name() << endl;
+ d->tabstops.append(tree);
+ }
+ }
+
+ --it;
+ hlist.clear();
+ }
+}
+
+uint Form::formatVersion() const
+{
+ return d->formatVersion;
+}
+
+void Form::setFormatVersion(uint ver)
+{
+ d->formatVersion = ver;
+}
+uint Form::originalFormatVersion() const
+{
+ return d->originalFormatVersion;
+}
+
+void Form::setOriginalFormatVersion(uint ver)
+{
+ d->originalFormatVersion = ver;
+}
+
+void Form::setFormWidget(FormWidget* w)
+{
+ if (!d)
+ return;
+ d->formWidget = w;
+ if (!d->formWidget)
+ return;
+ d->formWidget->m_form = this;
+}
+
+#include "form.moc"
diff --git a/kexi/formeditor/form.h b/kexi/formeditor/form.h
new file mode 100644
index 000000000..899da9558
--- /dev/null
+++ b/kexi/formeditor/form.h
@@ -0,0 +1,397 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNERFORM_H
+#define KFORMDESIGNERFORM_H
+
+#include <qobject.h>
+#include <qptrlist.h>
+
+#include "resizehandle.h"
+#include "utils.h"
+#include "objecttree.h"
+
+class QWidget;
+class QDomElement;
+class KActionCollection;
+class KCommandHistory;
+class KCommand;
+class PixmapCollection;
+
+namespace KFormDesigner {
+
+class Container;
+class WidgetPropertySet;
+class WidgetLibrary;
+class FormManager;
+class ObjectTree;
+class ObjectTreeItem;
+class ConnectionBuffer;
+
+//! Base (virtual) class for all form widgets
+/*! You need to inherit this class, and implement the drawing functions. This is necessary
+ because you cannot inherit QWidget twice, and we want form widgets to be any widget.
+ See FormWidgetBase in test/kfd_part.cpp and just copy functions there. */
+class KFORMEDITOR_EXPORT FormWidget
+{
+ public:
+ FormWidget();
+ virtual ~FormWidget();
+
+ /*! This function draws the rects in the \a list in the Form, above of all widgets,
+ using double-buffering. \a type can be 1 (selection rect)
+ or 2 (insert rect, dotted). */
+
+ virtual void drawRects(const QValueList<QRect> &list, int type) = 0;
+
+ virtual void drawRect(const QRect &r, int type) = 0;
+
+ /*! This function inits the buffer used for double-buffering. Called before drawing rect. */
+ virtual void initBuffer() = 0;
+
+ /*! Clears the form, ie pastes the whole buffer to repaint the Form. */
+ virtual void clearForm() = 0;
+
+ /*! This function highlights two widgets (to is optional), which are
+ sender and receiver, and draws a link between them. */
+ virtual void highlightWidgets(QWidget *from, QWidget *to) = 0;
+
+ protected:
+ Form *m_form;
+
+ friend class Form;
+};
+
+//! @internal
+class FormPrivate
+{
+ public:
+ FormPrivate();
+ ~FormPrivate();
+
+// FormManager *manager;
+ QGuardedPtr<Container> toplevel;
+ ObjectTree *topTree;
+ QGuardedPtr<QWidget> widget;
+
+ WidgetList selected;
+ ResizeHandleSet::Dict resizeHandles;
+
+ bool dirty;
+ bool interactive;
+ bool design;
+ QString filename;
+
+ KCommandHistory *history;
+ KActionCollection *collection;
+
+ ObjectTreeList tabstops;
+ bool autoTabstops;
+ ConnectionBuffer *connBuffer;
+
+ PixmapCollection *pixcollection;
+
+ //! This map is used to store cursor shapes before inserting (so we can restore them later)
+ QMap<QObject*,QCursor> cursors;
+
+ //!This string list is used to store the widgets which hasMouseTracking() == true (eg lineedits)
+ QStringList *mouseTrackers;
+
+ FormWidget *formWidget;
+
+ //! A set of head properties to be stored in a .ui file.
+ //! This includes KFD format version.
+ QMap<QCString,QString> headerProperties;
+
+ //! Format version, set by FormIO or on creating a new form.
+ uint formatVersion;
+ //! Format version, set by FormIO's loader or on creating a new form.
+ uint originalFormatVersion;
+};
+
+/*!
+ This class represents one form and holds the corresponding ObjectTree and Containers.
+ It takes care of widget selection and pasting widgets.
+ **/
+ //! A simple class representing a form
+class KFORMEDITOR_EXPORT Form : public QObject
+{
+ Q_OBJECT
+
+ public:
+ /*! Creates a simple Form, child of the FormManager \a manager.
+ */
+ Form(WidgetLibrary* library, const char *name=0, bool designMode = true);
+ ~Form();
+
+ //! \return A pointer to the WidgetLibrary supporting this form.
+ WidgetLibrary* library() const { return m_lib; }
+
+ /*!
+ Creates a toplevel widget out of another widget.
+ \a container will become the Form toplevel widget,
+ will be associated to an ObjectTree and so on.
+ \code QWidget *toplevel = new QWidget(this);
+ form->createToplevel(toplevel); \endcode
+ */
+ void createToplevel(QWidget *container, FormWidget *formWidget =0,
+ const QCString &classname="QWidget");
+
+ /*! \return the toplevel Container or 0 if this is a preview Form or createToplevel()
+ has not been called yet. */
+ Container* toplevelContainer() const { return d->toplevel; }
+
+ //! \return the FormWidget that holds this Form
+ FormWidget* formWidget() const { return d->formWidget; }
+
+ //! \return a pointer to this form's ObjectTree.
+ ObjectTree* objectTree() const { return d->topTree; }
+
+ //! \return the form's toplevel widget, or 0 if designMode() == false.
+ QWidget* widget() const;
+
+// //! \return the FormManager parent of this form.
+// FormManager* manager() const { return d->manager; }
+
+ /*! \return A pointer to the currently active Container, ie the parent Container for a simple widget,
+ and the widget's Container if it is itself a container.
+ */
+ Container* activeContainer();
+
+ /*! \return A pointer to the parent Container of the currently selected widget.
+ It is the same as activeContainer() for a simple widget, but unlike this function
+ it will also return the parent Container if the widget itself is a Container.
+ */
+ Container* parentContainer(QWidget *w=0);
+
+ /*! \return The \ref Container which is a parent of all widgets in \a wlist.
+ Used by \ref activeContainer(), and to find where
+ to paste widgets when multiple widgets are selected. */
+ ObjectTreeItem* commonParentContainer(WidgetList *wlist);
+
+ //! \return the list of currently selected widgets in this form
+ WidgetList* selectedWidgets() const {return &(d->selected);}
+
+ /*! \return currently selected widget in this form,
+ or 0 if there is no widget selected or more than one widget selected.
+ \see selectedWidgets() */
+ QWidget* selectedWidget() const { return d->selected.count()==1 ? d->selected.first() : 0; }
+
+ /*! Emits the action signals, and optionaly the undo/redo related signals
+ if \a withUndoAction == true. See \a FormManager for signals description. */
+ void emitActionSignals(bool withUndoAction=true);
+
+ /*! Emits again all signal related to selection (ie Form::selectionChanged()).
+ Called eg when the user has the focus again. */
+ void emitSelectionSignals();
+
+ /*! Sets the Form interactivity mode. Form is not interactive when
+ pasting widgets, or loading a Form.
+ */
+ void setInteractiveMode(bool interactive) { d->interactive = interactive; }
+
+ /*! \return true if the Form is being updated by the user, ie the created
+ widget were drawn on the Form.
+ \return false if the Form is being updated by the program, ie the widget
+ are created by FormIO, and so composed widgets
+ should not be populated automatically (such as QTabWidget).
+ */
+ bool interactiveMode() const { return d->interactive; }
+
+ /*! If \a design is true, the Form is in Design Mode (by default).
+ If \a design is false, then the Form is in Preview Mode, so
+ the ObjectTree and the Containers are removed. */
+ void setDesignMode(bool design);
+
+ //! \return The actual mode of the Form.
+ bool designMode() const { return d->design; }
+
+ bool isModified() { return d->dirty; }
+
+ //! \return the distance between two dots in the form background.
+//! @todo make gridSize configurable at global level
+ int gridSize() { return 10; }
+
+ //! \return the default margin for all the layout inside this Form.
+ int defaultMargin() { return 11;}
+
+ //! \return the default spacing for all the layout inside this Form.
+ int defaultSpacing() { return 6;}
+
+ /*! This function is used by ObjectTree to emit childAdded() signal (as it is not a QObject). */
+ void emitChildAdded(ObjectTreeItem *item);
+
+ /*! This function is used by ObjectTree to emit childRemoved() signal (as it is not a QObject). */
+ void emitChildRemoved(ObjectTreeItem *item);
+
+ /*! \return The filename of the UI file this Form was saved to,
+ or QString::null if the Form hasn't be saved yet. */
+ QString filename() const { return d->filename; }
+
+ //! Sets the filename of this Form to \a filename.
+ void setFilename(const QString &file) { d->filename = file; }
+
+ KCommandHistory* commandHistory() const { return d->history; }
+ ConnectionBuffer* connectionBuffer() const { return d->connBuffer; }
+ PixmapCollection* pixmapCollection() const { return d->pixcollection; }
+
+ /*! Adds a widget in the form's command history. Please use it instead
+ of calling directly actionCollection()->addCommand(). */
+ void addCommand(KCommand *command, bool execute);
+
+ /*! Clears form's command history. */
+ void clearCommandHistory();
+
+ /*! \return A pointer to this Form tabstops list : it contains all the widget
+ that can have focus ( ie no labels, etc)
+ in the order of the tabs.*/
+ ObjectTreeList* tabStops() const { return &(d->tabstops); }
+
+ inline ObjectTreeListIterator tabStopsIterator() const { return ObjectTreeListIterator(d->tabstops); }
+
+ /*! Called (e.g. by KexiDBForm) when certain widgets can have updated focusPolicy properties
+ these having no TabFocus flags set are removed from tabStops() list. */
+ void updateTabStopsOrder();
+
+ /*! Adds the widget at the end of tabstops list. Called on widget creation. */
+ void addWidgetToTabStops(ObjectTreeItem *it);
+
+ /*! \return True if the Form automatically handles tab stops. */
+ bool autoTabStops() const { return d->autoTabstops; }
+
+ /*! If \a autoTab is true, then the Form will automatically handle tab stops,
+ and the "Edit Tab Order" dialog will be disabled.
+ The tab widget will be set from the top-left to the bottom-right corner.\n
+ If \ autoTab is false, then it's up to the user to change tab stops
+ (which are by default in order of creation).*/
+ void setAutoTabStops(bool autoTab) { d->autoTabstops = autoTab;}
+
+ /*! Tells the Form to reassign the tab stops because the widget layout has changed
+ (called for example before saving or displaying the tab order dialog).
+ Automatically sorts widget from the top-left to bottom-right corner.
+ Widget can be grouped with containers. In paticular, for tab widgets,
+ child widgets should ordered by parent tab's order. */
+ void autoAssignTabStops();
+
+#ifdef KEXI_DEBUG_GUI
+ //! For debugging purposes
+ QString m_recentlyLoadedUICode;
+#endif
+
+ /*! Internal: called by ResizeHandle when mouse move event causes first
+ resize handle's dragging. As a result, current widget's editing (if any)
+ is finished - see WidgetFactory::resetEditor(). */
+// void resizeHandleDraggingStarted(QWidget *draggedWidget);
+
+ ResizeHandleSet* resizeHandlesForWidget(QWidget* w);
+
+ /*! A set of value/key pairs provided to be stored as attributes in
+ <kfd:customHeader/> XML element (saved as a first child of \<UI> element). */
+ QMap<QCString,QString>* headerProperties() const { return &d->headerProperties; }
+
+ //! \return format version number for this form.
+ //! For new forms it is equal to KFormDesigner::version().
+ uint formatVersion() const;
+ void setFormatVersion(uint ver);
+
+ //! \return original format version number for this form (as loaded from .ui XML string)
+ //! For new forms it is equal to KFormDesigner::version().
+ uint originalFormatVersion() const;
+ void setOriginalFormatVersion(uint ver);
+
+ public slots:
+ /*! This slot is called when the name of a widget was changed in Property Editor.
+ It renames the ObjectTreeItem associated to this widget.
+ */
+ void changeName(const QCString &oldname, const QCString &newname);
+
+ /*! Sets \a selected to be the selected widget of this Form.
+ If \a add is true, the formerly selected widget is still selected,
+ and the new one is just added. If false, \a selected replace the actually selected widget.
+ The form widget is always selected alone.
+ \a moreWillBeSelected indicates whether more widgets will be selected soon
+ (so for multiselection we should not update the property pane before the last widget is selected) */
+ void setSelectedWidget(QWidget *selected, bool add=false, bool dontRaise=false,
+ bool moreWillBeSelected = false);
+
+ /*! Unselects the widget \a w. Te widget is removed from the Cntainer 's list
+ and its resizeHandle is removed. */
+ void unSelectWidget(QWidget *w);
+
+ /*! Sets the form widget (it will be uniquely selected widget). */
+ void selectFormWidget();
+
+ void clearSelection();
+
+ protected slots:
+ /*! This slot is called when the toplevel widget of this Form is deleted
+ (ie the window closed) so that the Form gets deleted at the same time.
+ */
+ void formDeleted();
+
+ void emitUndoEnabled();
+ void emitRedoEnabled();
+
+ /*! This slot is called when a command is executed. The undo/redo signals
+ are emitted to update actions. */
+ void slotCommandExecuted();
+
+ /*! This slot is called when form is restored, ie when the user has undone
+ all actions. The form modified flag is updated, and
+ \ref FormManager::dirty() is called. */
+ void slotFormRestored();
+
+ signals:
+ /*! This signal is emitted by setSelectedWidget() when user selects a new widget,
+ to update both Property Editor and ObjectTreeView.
+ \a w is the newly selected widget.
+ */
+ void selectionChanged(QWidget *w, bool add, bool moreWillBeSelected = false);
+
+ /*! This signal is emitted when a new widget is created, to update ObjectTreeView.
+ \a it is the ObjectTreeItem representing this new widget.
+ */
+ void childAdded(ObjectTreeItem *it);
+
+ /*! This signal is emitted when a widget is deleted, to update ObjectTreeView.
+ \a it is the ObjectTreeItem representing this deleted widget.
+ */
+ void childRemoved(ObjectTreeItem *it);
+
+ //! This signal emitted when Form is about to be destroyed
+ void destroying();
+
+ protected:
+ void setConnectionBuffer(ConnectionBuffer *b) { d->connBuffer = b; }
+
+ void setFormWidget(FormWidget* w);
+ private:
+ WidgetLibrary *m_lib;
+ FormPrivate *d;
+
+ friend class FormManager;
+ friend class FormWidget;
+ friend class ConnectionDialog;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/formIO.cpp b/kexi/formeditor/formIO.cpp
new file mode 100644
index 000000000..2327fb915
--- /dev/null
+++ b/kexi/formeditor/formIO.cpp
@@ -0,0 +1,1626 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+
+#include <qmetaobject.h>
+#include <qdom.h>
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qcursor.h>
+#include <qbuffer.h>
+#include <qimage.h>
+#include <qlayout.h>
+#include <qobjectlist.h>
+#include <qdatetime.h>
+#include <qlabel.h>
+#include <qpainter.h>
+
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kcommand.h>
+#include <kaccelmanager.h>
+
+#include "form.h"
+#include "container.h"
+#include "objecttree.h"
+#include "formmanager.h"
+#include "widgetlibrary.h"
+#include "spring.h"
+#include "pixmapcollection.h"
+#include "events.h"
+#include "utils.h"
+#include "kexiflowlayout.h"
+#include "widgetwithsubpropertiesinterface.h"
+#include "formIO.h"
+
+/// A blank widget used when the class name is not supported
+CustomWidget::CustomWidget(const QCString &className, QWidget *parent, const char *name)
+: QWidget(parent, name), m_className(className)
+{
+ setBackgroundMode(Qt::PaletteDark);
+}
+
+CustomWidget::~CustomWidget()
+{
+}
+
+void
+CustomWidget::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ p.setPen(palette().active().text());
+ QRect r(rect());
+ r.setX(r.x()+2);
+ p.drawText(r, Qt::AlignTop, m_className);
+}
+
+using namespace KFormDesigner;
+
+QDict<QLabel> *FormIO::m_buddies = 0;
+ObjectTreeItem *FormIO::m_currentItem = 0;
+Form *FormIO::m_currentForm = 0;
+bool FormIO::m_savePixmapsInline = false;
+
+// FormIO itself
+
+KFORMEDITOR_EXPORT uint KFormDesigner::version()
+{
+ return KFORMDESIGNER_VERSION;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+///////////// Saving/loading functions //////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+FormIO::FormIO()
+{
+}
+
+FormIO::~FormIO()
+{
+}
+
+bool
+FormIO::saveFormToFile(Form *form, const QString &filename)
+{
+ QString m_filename;
+ if(!form->filename().isNull() && filename.isNull())
+ m_filename = form->filename();
+
+ if(filename.isNull())
+ {
+ m_filename = KFileDialog::getSaveFileName(QString::null, i18n("*.ui|Qt Designer UI Files"));
+ if(m_filename.isNull())
+ return false;
+ }
+ else
+ m_filename = filename;
+ form->setFilename(m_filename);
+
+ QDomDocument domDoc;
+ if (!saveFormToDom(form, domDoc))
+ return false;
+
+ QFile file(m_filename);
+ if (!file.open(IO_WriteOnly))
+ return false;
+
+ QTextStream stream(&file);
+ stream << domDoc.toString(3) << endl;
+ file.close();
+
+ return true;
+}
+
+bool
+FormIO::saveFormToByteArray(Form *form, QByteArray &dest)
+{
+ QDomDocument domDoc;
+ if (!saveFormToDom(form, domDoc))
+ return false;
+ dest = domDoc.toCString();
+ return true;
+}
+
+bool
+FormIO::saveFormToString(Form *form, QString &dest, int indent)
+{
+ QDomDocument domDoc;
+ if (!saveFormToDom(form, domDoc))
+ return false;
+ dest = domDoc.toString(indent);
+ return true;
+}
+
+bool
+FormIO::saveFormToDom(Form *form, QDomDocument &domDoc)
+{
+ m_currentForm = form;
+
+ domDoc = QDomDocument("UI");
+ QDomElement uiElement = domDoc.createElement("UI");
+ domDoc.appendChild(uiElement);
+ uiElement.setAttribute("version", "3.1");
+ uiElement.setAttribute("stdsetdef", 1);
+
+ //update format version information
+ form->headerProperties()->insert("version", QString::number(form->formatVersion()));
+ //custom properties
+ QDomElement headerPropertiesEl = domDoc.createElement("kfd:customHeader");
+ for (QMapConstIterator<QCString,QString> it=form->headerProperties()->constBegin(); it!=form->headerProperties()->constEnd(); ++it) {
+ headerPropertiesEl.setAttribute(it.key(), it.data());
+ }
+ uiElement.appendChild(headerPropertiesEl);
+
+ /// We save the savePixmapsInline property in the Form
+ QDomElement inlinePix = domDoc.createElement("pixmapinproject");
+ uiElement.appendChild(inlinePix);
+
+ // We create the top class element
+ QDomElement baseClass = domDoc.createElement("class");
+ uiElement.appendChild(baseClass);
+ QDomText baseClassV = domDoc.createTextNode("QWidget");
+ baseClass.appendChild(baseClassV);
+
+ // Save the toplevel widgets, and so the whole Form
+ saveWidget(form->objectTree(), uiElement, domDoc);
+
+ // We then save the layoutdefaults element
+ QDomElement layoutDefaults = domDoc.createElement("layoutDefaults");
+ layoutDefaults.setAttribute("spacing", QString::number(form->defaultSpacing()));
+ layoutDefaults.setAttribute("margin", QString::number(form->defaultMargin()));
+ uiElement.appendChild(layoutDefaults);
+
+ /// Save tab Stops
+ if(form->autoTabStops())
+ form->autoAssignTabStops();
+ QDomElement tabStops = domDoc.createElement("tabstops");
+ uiElement.appendChild(tabStops);
+ for(ObjectTreeListIterator it( form->tabStopsIterator() ); it.current(); ++it)
+ {
+ QDomElement tabstop = domDoc.createElement("tabstop");
+ tabStops.appendChild(tabstop);
+ QDomText tabStopText = domDoc.createTextNode(it.current()->name());
+ tabstop.appendChild(tabStopText);
+ }
+
+ // Save the Form 's PixmapCollection
+ form->pixmapCollection()->save(uiElement);
+ // Save the Form connections
+ form->connectionBuffer()->save(uiElement);
+
+ form->commandHistory()->documentSaved();
+
+ m_currentForm = 0;
+ m_currentItem = 0;
+ //m_currentWidget = 0;
+
+ return true;
+}
+
+bool
+FormIO::loadFormFromByteArray(Form *form, QWidget *container, QByteArray &src, bool preview)
+{
+ QString errMsg;
+ int errLine;
+ int errCol;
+
+ QDomDocument inBuf;
+ bool parsed = inBuf.setContent(src, false, &errMsg, &errLine, &errCol);
+
+ if(!parsed)
+ {
+ kdDebug() << "WidgetWatcher::load(): " << errMsg << endl;
+ kdDebug() << "WidgetWatcher::load(): line: " << errLine << " col: " << errCol << endl;
+ return false;
+ }
+
+ if (!loadFormFromDom(form, container, inBuf))
+ return false;
+ if(preview)
+ form->setDesignMode(false);
+ return true;
+}
+
+bool
+FormIO::loadFormFromString(Form *form, QWidget *container, QString &src, bool preview)
+{
+ QString errMsg;
+ int errLine;
+ int errCol;
+
+#ifdef KEXI_DEBUG_GUI
+ form->m_recentlyLoadedUICode = src;
+#endif
+
+ QDomDocument inBuf;
+ bool parsed = inBuf.setContent(src, false, &errMsg, &errLine, &errCol);
+
+ if(!parsed)
+ {
+ kdDebug() << "WidgetWatcher::load(): " << errMsg << endl;
+ kdDebug() << "WidgetWatcher::load(): line: " << errLine << " col: " << errCol << endl;
+ return false;
+ }
+
+ if (!loadFormFromDom(form, container, inBuf))
+ return false;
+ if(preview)
+ form->setDesignMode(false);
+ return true;
+}
+
+bool
+FormIO::loadFormFromFile(Form *form, QWidget *container, const QString &filename)
+{
+ QString errMsg;
+ int errLine;
+ int errCol;
+ QString m_filename;
+
+ if(filename.isNull())
+ {
+ m_filename = KFileDialog::getOpenFileName(QString::null, i18n("*.ui|Qt Designer UI Files"));
+ if(m_filename.isNull())
+ return false;
+ }
+ else
+ m_filename = filename;
+
+ QFile file(m_filename);
+ if(!file.open(IO_ReadOnly))
+ {
+ kdDebug() << "Cannot open the file " << filename << endl;
+ return false;
+ }
+ QTextStream stream(&file);
+ QString text = stream.read();
+
+ QDomDocument inBuf;
+ bool parsed = inBuf.setContent(text, false, &errMsg, &errLine, &errCol);
+
+ if(!parsed)
+ {
+ kdDebug() << "WidgetWatcher::load(): " << errMsg << endl;
+ kdDebug() << "WidgetWatcher::load(): line: " << errLine << " col: " << errCol << endl;
+ return false;
+ }
+
+ return loadFormFromDom(form, container, inBuf);
+}
+
+bool
+FormIO::loadFormFromDom(Form *form, QWidget *container, QDomDocument &inBuf)
+{
+ m_currentForm = form;
+
+ QDomElement ui = inBuf.namedItem("UI").toElement();
+
+ //custom properties
+ form->headerProperties()->clear();
+ QDomElement headerPropertiesEl = ui.namedItem("kfd:customHeader").toElement();
+ QDomAttr attr = headerPropertiesEl.firstChild().toAttr();
+ while (!attr.isNull() && attr.isAttr()) {
+ form->headerProperties()->insert(attr.name().latin1(), attr.value());
+ attr = attr.nextSibling().toAttr();
+ }
+ //update format version information
+ uint ver = 1; //the default
+ if (form->headerProperties()->contains("version")) {
+ bool ok;
+ uint v = (*form->headerProperties())["version"].toUInt(&ok);
+ if (ok)
+ ver = v;
+ }
+ kdDebug() << "FormIO::loadFormFromDom(): original format version: " << ver << endl;
+ form->setOriginalFormatVersion( ver );
+ if (ver < KFormDesigner::version()) {
+//! @todo We can either 1) convert from old format and later save in a new one or 2) keep old format.
+//! To do this we may need to look at the original format version number.
+ kdDebug() << "FormIO::loadFormFromDom(): original format is older than current: " << KFormDesigner::version() << endl;
+ form->setFormatVersion( KFormDesigner::version() );
+ }
+ else
+ form->setFormatVersion( ver );
+
+ if (ver > KFormDesigner::version()) {
+//! @todo display information about too new format and that "some information will not be available".
+ kdDebug() << "FormIO::loadFormFromDom(): original format is newer than current: " << KFormDesigner::version() << endl;
+ }
+
+ // Load the pixmap collection
+ m_savePixmapsInline = ( (ui.namedItem("pixmapinproject").isNull()) || (!ui.namedItem("images").isNull()) );
+ form->pixmapCollection()->load(ui.namedItem("collection"));
+
+ QDomElement element = ui.namedItem("widget").toElement();
+ createToplevelWidget(form, container, element);
+
+ // Loading the tabstops
+ QDomElement tabStops = ui.namedItem("tabstops").toElement();
+// if(tabStops.isNull())
+// return 1;
+ if(!tabStops.isNull()) {
+ int i = 0;
+ uint itemsNotFound = 0;
+ for(QDomNode n = tabStops.firstChild(); !n.isNull(); n = n.nextSibling(), i++)
+ {
+ QString name = n.toElement().text();
+ ObjectTreeItem *item = form->objectTree()->lookup(name);
+ if(!item)
+ {
+ kdDebug() << "FormIO::loadFormFromDom ERROR : no ObjectTreeItem " << endl;
+ continue;
+ }
+ const int index = form->tabStops()->findRef(item);
+ /* Compute a real destination index: "a number of not found items so far". */
+ const int realIndex = i - itemsNotFound;
+ if((index != -1) && (index != realIndex)) // the widget is not in the same place, so we move it
+ {
+ form->tabStops()->remove(item);
+ form->tabStops()->insert(realIndex, item);
+ }
+ if(index == -1) {
+ itemsNotFound++;
+ kdDebug() << "FormIO: item '" << name << "' not in list" << endl;
+ }
+ }
+ }
+
+ // Load the form connections
+ form->connectionBuffer()->load(ui.namedItem("connections"));
+
+ m_currentForm = 0;
+ m_currentItem = 0;
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+///////////// Functions to save/load properties /////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+void
+FormIO::savePropertyValue(QDomElement &parentNode, QDomDocument &parent, const char *name,
+ const QVariant &value, QWidget *w, WidgetLibrary *lib)
+{
+ // Widget specific properties and attributes ///////////////
+// kdDebug() << "FormIO::savePropertyValue() Saving the property: " << name << endl;
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(w);
+ QWidget *subwidget = w;
+ bool addSubwidgetFlag = false;
+ int propertyId = w->metaObject()->findProperty(name, true);
+ if (propertyId == -1 && subpropIface && subpropIface->subwidget()) { // try property from subwidget
+ subwidget = subpropIface->subwidget();
+ propertyId = subpropIface->subwidget()->metaObject()->findProperty(name, true);
+ addSubwidgetFlag = true;
+ }
+ if(propertyId == -1)
+ {
+ kdDebug() << "FormIO::savePropertyValue() The object doesn't have this property. Let's try the WidgetLibrary." << endl;
+ if(lib)
+ lib->saveSpecialProperty(w->className(), name, value, w, parentNode, parent);
+ return;
+ }
+
+ const QMetaProperty *meta = subwidget->metaObject()->property(propertyId, true);
+ if (!meta->stored( subwidget )) //not storable
+ return;
+ QDomElement propertyE = parent.createElement("property");
+ propertyE.setAttribute("name", name);
+ if (addSubwidgetFlag)
+ propertyE.setAttribute("subwidget", "true");
+
+ if(meta && meta->isEnumType())
+ {
+ // this property is enum or set type
+ QDomElement type;
+ QDomText valueE;
+
+ if(meta->isSetType())
+ {
+ QStringList list = QStringList::fromStrList(meta->valueToKeys(value.toInt()));
+ type = parent.createElement("set");
+ valueE = parent.createTextNode(list.join("|"));
+ type.appendChild(valueE);
+ }
+ else
+ {
+ QString s = meta->valueToKey(value.toInt());
+ type = parent.createElement("enum");
+ valueE = parent.createTextNode(s);
+ type.appendChild(valueE);
+ }
+ propertyE.appendChild(type);
+ parentNode.appendChild(propertyE);
+ return;
+ }
+
+ if(value.type() == QVariant::Pixmap) {
+ QDomText valueE;
+ QDomElement type = parent.createElement("pixmap");
+ QCString property = propertyE.attribute("name").latin1();
+//todo QCString pixmapName = m_currentItem->widget()->property("pixmapName").toCString();
+ if(m_savePixmapsInline /* (js)too risky: || m_currentItem->pixmapName(property).isNull() */)
+ valueE = parent.createTextNode(saveImage(parent, value.toPixmap()));
+ else
+ valueE = parent.createTextNode(m_currentItem->pixmapName(property));
+ type.appendChild(valueE);
+ propertyE.appendChild(type);
+ parentNode.appendChild(propertyE);
+ return;
+ }
+
+ // Saving a "normal" property
+ writeVariant(parent, propertyE, value);
+ parentNode.appendChild(propertyE);
+}
+
+void
+FormIO::writeVariant(QDomDocument &parent, QDomElement &parentNode, QVariant value)
+{
+ QDomElement type;
+ QDomText valueE;
+
+ switch(value.type())
+ {
+ case QVariant::String:
+ {
+ type = parent.createElement("string");
+ valueE = parent.createTextNode(value.toString());
+ type.appendChild(valueE);
+ break;
+ }
+ case QVariant::CString:
+ {
+ type = parent.createElement("cstring");
+ valueE = parent.createTextNode(value.toString());
+ type.appendChild(valueE);
+ break;
+ }
+ case QVariant::Rect:
+ {
+ type = parent.createElement("rect");
+ QDomElement x = parent.createElement("x");
+ QDomElement y = parent.createElement("y");
+ QDomElement w = parent.createElement("width");
+ QDomElement h = parent.createElement("height");
+ QDomText valueX = parent.createTextNode(QString::number(value.toRect().x()));
+ QDomText valueY = parent.createTextNode(QString::number(value.toRect().y()));
+ QDomText valueW = parent.createTextNode(QString::number(value.toRect().width()));
+ QDomText valueH = parent.createTextNode(QString::number(value.toRect().height()));
+
+ x.appendChild(valueX);
+ y.appendChild(valueY);
+ w.appendChild(valueW);
+ h.appendChild(valueH);
+
+ type.appendChild(x);
+ type.appendChild(y);
+ type.appendChild(w);
+ type.appendChild(h);
+ break;
+ }
+ case QVariant::Color:
+ {
+ type = parent.createElement("color");
+ QDomElement r = parent.createElement("red");
+ QDomElement g = parent.createElement("green");
+ QDomElement b = parent.createElement("blue");
+ QDomText valueR = parent.createTextNode(QString::number(value.toColor().red()));
+ QDomText valueG = parent.createTextNode(QString::number(value.toColor().green()));
+ QDomText valueB = parent.createTextNode(QString::number(value.toColor().blue()));
+
+ r.appendChild(valueR);
+ g.appendChild(valueG);
+ b.appendChild(valueB);
+
+ type.appendChild(r);
+ type.appendChild(g);
+ type.appendChild(b);
+ break;
+ }
+ case QVariant::Bool:
+ {
+ type = parent.createElement("bool");
+ //valueE = parent.createTextNode(QString::number(value.toBool()));
+ valueE = parent.createTextNode(value.toBool() ? "true" : "false");
+ type.appendChild(valueE);
+ break;
+ }
+ case QVariant::Int:
+ case QVariant::UInt:
+ {
+ type = parent.createElement("number");
+ valueE = parent.createTextNode(QString::number(value.toInt()));
+ type.appendChild(valueE);
+ break;
+ }
+ case QVariant::Size:
+ {
+ type = parent.createElement("size");
+ QDomElement w = parent.createElement("width");
+ QDomElement h = parent.createElement("height");
+ QDomText valueW = parent.createTextNode(QString::number(value.toSize().width()));
+ QDomText valueH = parent.createTextNode(QString::number(value.toSize().height()));
+
+ w.appendChild(valueW);
+ h.appendChild(valueH);
+
+ type.appendChild(w);
+ type.appendChild(h);
+ break;
+ }
+ case QVariant::Point:
+ {
+ type = parent.createElement("point");
+ QDomElement x = parent.createElement("x");
+ QDomElement y = parent.createElement("y");
+ QDomText valueX = parent.createTextNode(QString::number(value.toPoint().x()));
+ QDomText valueY = parent.createTextNode(QString::number(value.toPoint().y()));
+
+ x.appendChild(valueX);
+ y.appendChild(valueY);
+
+ type.appendChild(x);
+ type.appendChild(y);
+ break;
+ }
+ case QVariant::Font:
+ {
+ type = parent.createElement("font");
+ QDomElement f = parent.createElement("family");
+ QDomElement p = parent.createElement("pointsize");
+ QDomElement w = parent.createElement("weight");
+ QDomElement b = parent.createElement("bold");
+ QDomElement i = parent.createElement("italic");
+ QDomElement u = parent.createElement("underline");
+ QDomElement s = parent.createElement("strikeout");
+ QDomText valueF = parent.createTextNode(value.toFont().family());
+ QDomText valueP = parent.createTextNode(QString::number(value.toFont().pointSize()));
+ QDomText valueW = parent.createTextNode(QString::number(value.toFont().weight()));
+ QDomText valueB = parent.createTextNode(QString::number(value.toFont().bold()));
+ QDomText valueI = parent.createTextNode(QString::number(value.toFont().italic()));
+ QDomText valueU = parent.createTextNode(QString::number(value.toFont().underline()));
+ QDomText valueS = parent.createTextNode(QString::number(value.toFont().strikeOut()));
+
+ f.appendChild(valueF);
+ p.appendChild(valueP);
+ w.appendChild(valueW);
+ b.appendChild(valueB);
+ i.appendChild(valueI);
+ u.appendChild(valueU);
+ s.appendChild(valueS);
+
+ type.appendChild(f);
+ type.appendChild(p);
+ type.appendChild(w);
+ type.appendChild(b);
+ type.appendChild(i);
+ type.appendChild(u);
+ type.appendChild(s);
+ break;
+ }
+ case QVariant::Cursor:
+ {
+ type = parent.createElement("cursor");
+ valueE = parent.createTextNode(QString::number(value.toCursor().shape()));
+ type.appendChild(valueE);
+ break;
+ }
+ case QVariant::SizePolicy:
+ {
+ type = parent.createElement("sizepolicy");
+ QDomElement h = parent.createElement("hsizetype");
+ QDomElement v = parent.createElement("vsizetype");
+ QDomElement hs = parent.createElement("horstretch");
+ QDomElement vs = parent.createElement("verstretch");
+ QDomText valueH = parent.createTextNode(QString::number(value.toSizePolicy().horData()));
+ QDomText valueV = parent.createTextNode(QString::number(value.toSizePolicy().verData()));
+ QDomText valueHS = parent.createTextNode(QString::number(value.toSizePolicy().horStretch()));
+ QDomText valueVS = parent.createTextNode(QString::number(value.toSizePolicy().verStretch()));
+
+ h.appendChild(valueH);
+ v.appendChild(valueV);
+ hs.appendChild(valueHS);
+ vs.appendChild(valueVS);
+
+ type.appendChild(h);
+ type.appendChild(v);
+ type.appendChild(hs);
+ type.appendChild(vs);
+ break;
+ }
+ case QVariant::Time:
+ {
+ type = parent.createElement("time");
+ QDomElement h = parent.createElement("hour");
+ QDomElement m = parent.createElement("minute");
+ QDomElement s = parent.createElement("second");
+ QDomText valueH = parent.createTextNode(QString::number(value.toTime().hour()));
+ QDomText valueM = parent.createTextNode(QString::number(value.toTime().minute()));
+ QDomText valueS = parent.createTextNode(QString::number(value.toTime().second()));
+
+ h.appendChild(valueH);
+ m.appendChild(valueM);
+ s.appendChild(valueS);
+
+ type.appendChild(h);
+ type.appendChild(m);
+ type.appendChild(s);
+ break;
+ }
+ case QVariant::Date:
+ {
+ type = parent.createElement("date");
+ QDomElement y = parent.createElement("year");
+ QDomElement m = parent.createElement("month");
+ QDomElement d = parent.createElement("day");
+ QDomText valueY = parent.createTextNode(QString::number(value.toDate().year()));
+ QDomText valueM = parent.createTextNode(QString::number(value.toDate().month()));
+ QDomText valueD = parent.createTextNode(QString::number(value.toDate().day()));
+
+ y.appendChild(valueY);
+ m.appendChild(valueM);
+ d.appendChild(valueD);
+
+ type.appendChild(y);
+ type.appendChild(m);
+ type.appendChild(d);
+ break;
+ }
+ case QVariant::DateTime:
+ {
+ type = parent.createElement("datetime");
+ QDomElement h = parent.createElement("hour");
+ QDomElement m = parent.createElement("minute");
+ QDomElement s = parent.createElement("second");
+ QDomElement y = parent.createElement("year");
+ QDomElement mo = parent.createElement("month");
+ QDomElement d = parent.createElement("day");
+ QDomText valueH = parent.createTextNode(QString::number(value.toDateTime().time().hour()));
+ QDomText valueM = parent.createTextNode(QString::number(value.toDateTime().time().minute()));
+ QDomText valueS = parent.createTextNode(QString::number(value.toDateTime().time().second()));
+ QDomText valueY = parent.createTextNode(QString::number(value.toDateTime().date().year()));
+ QDomText valueMo = parent.createTextNode(QString::number(value.toDateTime().date().month()));
+ QDomText valueD = parent.createTextNode(QString::number(value.toDateTime().date().day()));
+
+ h.appendChild(valueH);
+ m.appendChild(valueM);
+ s.appendChild(valueS);
+ y.appendChild(valueY);
+ mo.appendChild(valueMo);
+ d.appendChild(valueD);
+
+ type.appendChild(h);
+ type.appendChild(m);
+ type.appendChild(s);
+ type.appendChild(y);
+ type.appendChild(mo);
+ type.appendChild(d);
+ break;
+ }
+ default:
+ break;
+ }
+
+ parentNode.appendChild(type);
+}
+
+void
+FormIO::savePropertyElement(QDomElement &parentNode, QDomDocument &domDoc, const QString &tagName, const QString &property, const QVariant &value)
+{
+ QDomElement propertyE = domDoc.createElement(tagName);
+ propertyE.setAttribute("name", property);
+ writeVariant(domDoc, propertyE, value);
+ parentNode.appendChild(propertyE);
+}
+
+QVariant
+FormIO::readPropertyValue(QDomNode node, QObject *obj, const QString &name)
+{
+ QDomElement tag = node.toElement();
+ QString text = tag.text();
+ QString type = tag.tagName();
+
+ if(type == "string" || type == "cstring")
+ return text;
+ else if(type == "rect")
+ {
+ QDomElement x = node.namedItem("x").toElement();
+ QDomElement y = node.namedItem("y").toElement();
+ QDomElement w = node.namedItem("width").toElement();
+ QDomElement h = node.namedItem("height").toElement();
+
+ int rx = x.text().toInt();
+ int ry = y.text().toInt();
+ int rw = w.text().toInt();
+ int rh = h.text().toInt();
+
+ return QRect(rx, ry, rw, rh);
+ }
+ else if(type == "color")
+ {
+ QDomElement r = node.namedItem("red").toElement();
+ QDomElement g = node.namedItem("green").toElement();
+ QDomElement b = node.namedItem("blue").toElement();
+
+ int red = r.text().toInt();
+ int green = g.text().toInt();
+ int blue = b.text().toInt();
+
+ return QColor(red, green, blue);
+ }
+ else if(type == "bool")
+ {
+ if(text == "true")
+ return QVariant(true, 3);
+ else if(text == "false")
+ return QVariant(false, 3);
+ return QVariant(text.toInt(), 3);
+ }
+ else if(type == "number")
+ {
+ return text.toInt();
+ }
+ else if(type == "size")
+ {
+ QDomElement w = node.namedItem("width").toElement();
+ QDomElement h = node.namedItem("height").toElement();
+
+ return QSize(w.text().toInt(), h.text().toInt());
+ }
+ else if(type == "point")
+ {
+ QDomElement x = node.namedItem("x").toElement();
+ QDomElement y = node.namedItem("y").toElement();
+
+ return QPoint(x.text().toInt(), y.text().toInt());
+ }
+ else if(type == "font")
+ {
+ QDomElement fa = node.namedItem("family").toElement();
+ QDomElement p = node.namedItem("pointsize").toElement();
+ QDomElement w = node.namedItem("weight").toElement();
+ QDomElement b = node.namedItem("bold").toElement();
+ QDomElement i = node.namedItem("italic").toElement();
+ QDomElement u = node.namedItem("underline").toElement();
+ QDomElement s = node.namedItem("strikeout").toElement();
+
+ QFont f;
+ f.setFamily(fa.text());
+ f.setPointSize(p.text().toInt());
+ f.setWeight(w.text().toInt());
+ f.setBold(b.text().toInt());
+ f.setItalic(i.text().toInt());
+ f.setUnderline(u.text().toInt());
+ f.setStrikeOut(s.text().toInt());
+
+ return f;
+ }
+ else if(type == "cursor")
+ {
+ return QCursor(text.toInt());
+ }
+ else if(type == "time")
+ {
+ QDomElement h = node.namedItem("hour").toElement();
+ QDomElement m = node.namedItem("minute").toElement();
+ QDomElement s = node.namedItem("second").toElement();
+
+ return QTime(h.text().toInt(), m.text().toInt(), s.text().toInt());
+ }
+ else if(type == "date")
+ {
+ QDomElement y = node.namedItem("year").toElement();
+ QDomElement m = node.namedItem("month").toElement();
+ QDomElement d = node.namedItem("day").toElement();
+
+ return QDate(y.text().toInt(), m.text().toInt(), d.text().toInt());
+ }
+ else if(type == "datetime")
+ {
+ QDomElement h = node.namedItem("hour").toElement();
+ QDomElement m = node.namedItem("minute").toElement();
+ QDomElement s = node.namedItem("second").toElement();
+ QDomElement y = node.namedItem("year").toElement();
+ QDomElement mo = node.namedItem("month").toElement();
+ QDomElement d = node.namedItem("day").toElement();
+
+ QTime t(h.text().toInt(), m.text().toInt(), s.text().toInt());
+ QDate da(y.text().toInt(), mo.text().toInt(), d.text().toInt());
+
+ return QDateTime(da, t);
+ }
+ else if(type == "sizepolicy")
+ {
+ QDomElement h = node.namedItem("hsizetype").toElement();
+ QDomElement v = node.namedItem("vsizetype").toElement();
+ QDomElement hs = node.namedItem("horstretch").toElement();
+ QDomElement vs = node.namedItem("verstretch").toElement();
+
+ QSizePolicy s;
+ s.setHorData((QSizePolicy::SizeType)h.text().toInt());
+ s.setVerData((QSizePolicy::SizeType)v.text().toInt());
+ s.setHorStretch(hs.text().toInt());
+ s.setVerStretch(vs.text().toInt());
+ return s;
+ }
+ else if(type == "pixmap")
+ {
+ if(m_savePixmapsInline || !m_currentForm || !m_currentItem || !m_currentForm->pixmapCollection()->contains(text))
+ return loadImage(tag.ownerDocument(), text);
+ else
+ {
+ m_currentItem->setPixmapName(name.latin1(), text);
+ return m_currentForm->pixmapCollection()->getPixmap(text);
+ }
+ return QVariant(QPixmap());
+ }
+ else if(type == "enum")
+ return text;
+ else if(type == "set")
+ {
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(obj);
+ QObject *subobject = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : obj;
+ const int count = subobject->metaObject()->findProperty(name.latin1(), true);
+ const QMetaProperty *meta = count!=-1 ? subobject->metaObject()->property(count, true) : 0;
+
+ if (meta) {
+ if (meta->isSetType()) {
+ QStrList keys;
+ const QStringList list( QStringList::split("|", text) );
+ for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it)
+ keys.append((*it).latin1());
+
+ return meta->keysToValue(keys);
+ }
+ }
+ else {
+ // Metaproperty not found, probably because subwidget is not created.
+ // We will return a string list here with hope that names will
+ // be resolved and translated into an integer value later when subwidget is created,
+ // e.g. near KexiFormView::updateValuesForSubproperties()
+ return QStringList::split("|", text);
+ }
+ }
+ return QVariant();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+///////////// Functions to save/load widgets ////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+void
+FormIO::saveWidget(ObjectTreeItem *item, QDomElement &parent, QDomDocument &domDoc, bool insideGridLayout)
+{
+ if (!item)
+ return;
+ bool savedAlignment = false;
+ // we let Spring class handle saving itself
+ if(item->className() == "Spring")
+ {
+ Spring::saveSpring(item, parent, domDoc, insideGridLayout);
+ return;
+ }
+
+ bool resetCurrentForm = false;
+ m_currentItem = item;
+ if(!m_currentForm) // copying widget
+ {
+ resetCurrentForm = true;
+ m_currentForm = item->container() ? item->container()->form() : item->parent()->container()->form();
+ }
+
+
+ WidgetLibrary *lib = m_currentForm->library();
+// if(item->container())
+// lib = item->container()->form()->manager()->lib();
+// else
+// lib = item->parent()->container()->form()->manager()->lib();
+
+ // We create the "widget" element
+ QDomElement tclass = domDoc.createElement("widget");
+ parent.appendChild(tclass);
+
+ if(insideGridLayout)
+ {
+ tclass.setAttribute("row", item->gridRow());
+ tclass.setAttribute("column", item->gridCol());
+ if(item->spanMultipleCells())
+ {
+ tclass.setAttribute("rowspan", item->gridRowSpan());
+ tclass.setAttribute("colspan", item->gridColSpan());
+ }
+ }
+
+ if(!item->parent()) // Toplevel widget
+ tclass.setAttribute("class", "QWidget");
+ // For compatibility, HBox, VBox and Grid are saved as "QLayoutWidget"
+ else if(item->widget()->isA("HBox") || item->widget()->isA("VBox") || item->widget()->isA("Grid")
+ || item->widget()->isA("HFlow") || item->widget()->isA("VFlow"))
+ tclass.setAttribute("class", "QLayoutWidget");
+ else if(item->widget()->isA("CustomWidget"))
+ tclass.setAttribute("class", item->className());
+ else // Normal widgets
+ tclass.setAttribute("class", lib->savingName(item->widget()->className()) );
+
+ savePropertyValue(tclass, domDoc, "name", item->widget()->property("name"), item->widget());
+
+ // Important: save dataSource property FIRST before properties like "alignment"
+ // - needed when subproperties are defined after subwidget creation, and subwidget is created after setting "dataSource"
+ // (this is the case for KexiDBAutoField)
+//! @todo more properties like "dataSource" may needed here...
+// if (-1 != item->widget()->metaObject()->findProperty("dataSource"))
+ // savePropertyValue(tclass, domDoc, "dataSource", item->widget()->property("dataSource"), item->widget());
+
+ // We don't want to save the geometry if the widget is inside a layout (so parent.tagName() == "grid" for example)
+ if(item && !item->parent()) {
+ // save form widget size, but not its position
+ savePropertyValue(tclass, domDoc, "geometry",
+ QRect( QPoint(0,0), item->widget()->size()),
+ item->widget());
+ }
+ // normal widget (if == "UI', it means we're copying widget)
+ else if(parent.tagName() == "widget" || parent.tagName() == "UI")
+ savePropertyValue(tclass, domDoc, "geometry", item->widget()->property("geometry"), item->widget());
+
+ // Save the buddy widget for a label
+ if(item->widget()->inherits("QLabel") && ((QLabel*)item->widget())->buddy())
+ savePropertyElement(tclass, domDoc, "property", "buddy", ((QLabel*)item->widget())->buddy()->name());
+
+ // We save every property in the modifProp list of the ObjectTreeItem
+ QVariantMap *map = new QVariantMap( *(item->modifiedProperties()) );
+ QMap<QString,QVariant>::ConstIterator endIt = map->constEnd();
+ for(QMap<QString,QVariant>::ConstIterator it = map->constBegin(); it != endIt; ++it)
+ {
+ const QCString name( it.key().latin1() );
+ if(name == "hAlign" || name == "vAlign" || name == "wordbreak" || name == "alignment") {
+ if(!savedAlignment) // not to save it twice
+ {
+ savePropertyValue(tclass, domDoc, "alignment", item->widget()->property("alignment"), item->widget());
+ savedAlignment = true;
+ }
+ }
+ else if(name == "name" || name == "geometry" || name == "layout") {
+ // these have already been saved
+ }
+ else {
+ savePropertyValue(tclass, domDoc, it.key().latin1(), item->widget()->property(it.key().latin1()),
+ item->widget(), lib);
+ }
+ }
+ delete map;
+
+ if(item->widget()->isA("CustomWidget")) {
+ QDomDocument doc("TEMP");
+ doc.setContent(item->m_unknownProps);
+ for(QDomNode n = doc.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ tclass.appendChild(n.cloneNode());
+ }
+
+ }
+ // Saving container 's layout if there is one
+ QDomElement layout;
+ if(item->container() && item->container()->layoutType() != Container::NoLayout)
+ {
+ if(item->container()->layout()) // there is a layout
+ {
+ layout = domDoc.createElement("temp");
+ savePropertyValue(layout, domDoc, "name", "unnamed", item->widget());
+ if(item->modifiedProperties()->contains("layoutMargin"))
+ savePropertyElement(layout, domDoc, "property", "margin", item->container()->layoutMargin());
+ if(item->modifiedProperties()->contains("layoutSpacing"))
+ savePropertyElement(layout, domDoc, "property", "spacing", item->container()->layoutSpacing());
+ tclass.appendChild(layout);
+ }
+ }
+
+ int layoutType = item->container() ? item->container()->layoutType() : Container::NoLayout;
+ switch(layoutType) {
+ case Container::Grid: // grid layout
+ {
+ layout.setTagName("grid");
+ for(ObjectTreeItem *objIt = item->children()->first(); objIt; objIt = item->children()->next())
+ saveWidget(objIt, layout, domDoc, true);
+ break;
+ }
+ case Container::HBox: case Container::VBox:
+ {
+ // as we don't save geometry, we need to sort widgets in the right order, not creation order
+ WidgetList *list;
+ if(layout.tagName() == "hbox") {
+ list = new HorWidgetList(item->container()->form()->toplevelContainer()->widget());
+ layout.setTagName("hbox");
+ }
+ else {
+ list = new VerWidgetList(item->container()->form()->toplevelContainer()->widget());
+ layout.setTagName("vbox");
+ }
+
+ for(ObjectTreeItem *objTree = item->children()->first(); objTree; objTree = item->children()->next())
+ list->append(objTree->widget());
+ list->sort();
+
+ for(QWidget *obj = list->first(); obj; obj = list->next()) {
+ ObjectTreeItem *titem = item->container()->form()->objectTree()->lookup(obj->name());
+ if(item)
+ saveWidget(titem, layout, domDoc);
+ }
+ delete list;
+ break;
+ }
+ case Container::HFlow: case Container::VFlow:
+ {
+ layout.setTagName("grid");
+ KexiFlowLayout *flow = static_cast<KexiFlowLayout*>(item->container()->layout());
+ if(!flow) break;
+ WidgetList *list = (WidgetList*)flow->widgetList();
+
+ // save some special properties
+ savePropertyElement(layout, domDoc, "property", "customLayout", Container::layoutTypeToString(item->container()->layoutType()) );
+ savePropertyElement(layout, domDoc, "property", "justify", QVariant(static_cast<KexiFlowLayout*>(item->container()->layout())->isJustified(), 3) );
+
+ // fill the widget's grid info, ie just simulate grid layout
+ item->container()->createGridLayout(true);
+ for(QWidget *obj = list->first(); obj; obj = list->next()) {
+ ObjectTreeItem *titem = item->container()->form()->objectTree()->lookup(obj->name());
+ if(item)
+ saveWidget(titem, layout, domDoc, true); // save grid info for compatibility with QtDesigner
+ }
+ delete list;
+ break;
+ }
+ default:
+ {
+ for(ObjectTreeItem *objIt = item->children()->first(); objIt; objIt = item->children()->next())
+ saveWidget(objIt, tclass, domDoc);
+ }
+ }
+
+ addIncludeFileName(lib->includeFileName(item->widget()->className()), domDoc);
+
+ if(resetCurrentForm)
+ m_currentForm = 0;
+ m_currentItem = 0;
+}
+
+void
+FormIO::cleanClipboard(QDomElement &uiElement)
+{
+ // remove includehints element not needed
+ if(!uiElement.namedItem("includehints").isNull())
+ uiElement.removeChild(uiElement.namedItem("includehints"));
+ // and ensure images and connection are at the end
+ if(!uiElement.namedItem("connections").isNull())
+ uiElement.insertAfter(uiElement.namedItem("connections"), QDomNode());
+ if(!uiElement.namedItem("images").isNull())
+ uiElement.insertAfter(uiElement.namedItem("images"), QDomNode());
+}
+
+void
+FormIO::loadWidget(Container *container, const QDomElement &el, QWidget *parent)
+{
+ bool resetCurrentForm = false;
+ if(!m_currentForm) // pasting widget
+ {
+ resetCurrentForm = true;
+ m_currentForm = container->form();
+ }
+
+ // We first look for the widget's name
+ QString wname;
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if((n.toElement().tagName() == "property") && (n.toElement().attribute("name") == "name"))
+ {
+ wname = n.toElement().text();
+ break;
+ }
+ }
+
+ QWidget *w;
+ QCString classname, alternate;
+ // We translate some name (for compatibility)
+ if(el.tagName() == "spacer")
+ classname = "Spring";
+ else if(el.attribute("class") == "QLayoutWidget")
+ {
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ QString tagName = n.toElement().tagName();
+ if(tagName == "property")
+ continue;
+ if(tagName == "hbox")
+ classname = "HBox";
+ else if(tagName == "vbox")
+ classname = "VBox";
+ else if(tagName == "grid") {
+ // first, see if it is flow layout
+ for(QDomNode child = n.firstChild(); !child.isNull(); child = child.nextSibling()) {
+ if((child.toElement().tagName() == "property")
+ && (child.toElement().attribute("name") == "customLayout"))
+ {
+ classname = child.toElement().text().latin1();
+ break;
+ }
+ }
+
+ if(classname.isEmpty()) // normal grid
+ classname = "Grid";
+ }
+ }
+ }
+ else
+ // We check if this classname is an alternate one, and replace it if necessary
+ {
+ classname = el.attribute("class").latin1();
+ alternate = container->form()->library()->classNameForAlternate(classname);
+ }
+
+ if(alternate == "CustomWidget")
+ w = new CustomWidget(classname, container->widget(), wname.latin1());
+ else
+ {
+ if(!alternate.isNull())
+ classname = alternate;
+
+ int widgetOptions = WidgetFactory::DefaultOptions;
+ if (!container->form()->designMode()) {
+ widgetOptions ^= WidgetFactory::DesignViewMode;
+ }
+
+ if(!parent)
+ w = container->form()->library()->createWidget(classname, container->widget(),
+ wname.latin1(), container, widgetOptions);
+ else
+ w = container->form()->library()->createWidget(classname, parent, wname.latin1(),
+ container, widgetOptions);
+ }
+
+ if(!w)
+ return;
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0)
+//! @todo allow setting this for data view mode as well
+ if (m_currentForm->designMode()) {
+ //don't generate accelerators for widgets in design mode
+ KAcceleratorManager::setNoAccel(w);
+ }
+#endif
+ w->setStyle(&(container->widget()->style()));
+ w->show();
+
+ // We create and insert the ObjectTreeItem at the good place in the ObjectTree
+ ObjectTreeItem *item = container->form()->objectTree()->lookup(wname);
+ if (!item) {
+ // not yet created
+ item = new ObjectTreeItem(container->form()->library()->displayName(classname),
+ wname, w, container);
+ if(parent) {
+ ObjectTreeItem *titem = container->form()->objectTree()->lookup(parent->name());
+ if(titem)
+ container->form()->objectTree()->addItem(titem, item);
+ else
+ kdDebug() << "FORMIO :: ERROR no parent widget " << endl;
+ }
+ else
+ container->form()->objectTree()->addItem(container->objectTree(), item);
+ }
+ //assign item for its widget if it supports DesignTimeDynamicChildWidgetHandler interface
+ //(e.g. KexiDBAutoField)
+ if (container->form()->designMode() && dynamic_cast<DesignTimeDynamicChildWidgetHandler*>(w)) {
+ dynamic_cast<DesignTimeDynamicChildWidgetHandler*>(w)->assignItem(item);
+ }
+
+ m_currentItem = item;
+ // if we are inside a Grid, we need to insert the widget in the good cell
+ if(container->layoutType() == Container::Grid) {
+ QGridLayout *layout = (QGridLayout*)container->layout();
+ if(el.hasAttribute("rowspan")) { // widget spans multiple cells
+ if(layout)
+ layout->addMultiCellWidget(w, el.attribute("row").toInt(), el.attribute("row").toInt() + el.attribute("rowspan").toInt()-1,
+ el.attribute("column").toInt(), el.attribute("column").toInt() + el.attribute("colspan").toInt()-1);
+ item->setGridPos(el.attribute("row").toInt(), el.attribute("column").toInt(), el.attribute("rowspan").toInt(),
+ el.attribute("colspan").toInt());
+ }
+ else {
+ if(layout)
+ layout->addWidget(w, el.attribute("row").toInt(), el.attribute("column").toInt());
+ item->setGridPos(el.attribute("row").toInt(), el.attribute("column").toInt(), 0, 0);
+ }
+ }
+ else if(container->layout())
+ container->layout()->add(w);
+
+ readChildNodes(item, container, el, w);
+
+ if(item->container() && item->container()->layout())
+ item->container()->layout()->activate();
+
+ // We add the autoSaveProperties in the modifProp list of the ObjectTreeItem, so that they are saved later
+ QValueList<QCString> list(container->form()->library()->autoSaveProperties(w->className()));
+ QValueList<QCString>::ConstIterator endIt = list.constEnd();
+ KFormDesigner::WidgetWithSubpropertiesInterface* subpropIface
+ = dynamic_cast<KFormDesigner::WidgetWithSubpropertiesInterface*>(w);
+ QWidget *subwidget = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : w;
+ for(QValueList<QCString>::ConstIterator it = list.constBegin(); it != endIt; ++it) {
+ if(subwidget->metaObject()->findProperty(*it, true) != -1)
+ item->addModifiedProperty(*it, subwidget->property(*it));
+ }
+
+ if(resetCurrentForm)
+ m_currentForm = 0;
+ m_currentItem = 0;
+}
+
+void
+FormIO::createToplevelWidget(Form *form, QWidget *container, QDomElement &el)
+{
+ // We first look for the widget's name
+ QString wname;
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if((n.toElement().tagName() == "property") && (n.toElement().attribute("name") == "name"))
+ {
+ wname = n.toElement().text();
+ break;
+ }
+
+ }
+ // And rename the widget and its ObjectTreeItem
+ container->setName(wname.latin1());
+ if(form->objectTree())
+ form->objectTree()->rename(form->objectTree()->name(), wname);
+ form->setInteractiveMode(false);
+
+ QDict<QLabel> *oldBuddies = 0;
+ if(m_buddies) // save old buddies (for subforms)
+ oldBuddies = m_buddies;
+ m_buddies = new QDict<QLabel>();
+ m_currentItem = form->objectTree();
+
+ readChildNodes(form->objectTree(), form->toplevelContainer(), el, container);
+
+ // Now the Form is fully loaded, we can assign the buddies
+ QDictIterator<QLabel> it(*m_buddies);
+ for(; it.current(); ++it)
+ {
+ ObjectTreeItem *item = form->objectTree()->lookup(it.currentKey());
+ if(!item || !item->widget())
+ {
+ kdDebug() << "Cannot assign buddy for widget " << it.current()->name() << " to " << it.currentKey() << endl;
+ continue;
+ }
+ it.current()->setBuddy(item->widget());
+ }
+ delete m_buddies;
+ m_buddies = oldBuddies; // and restore it
+
+ m_currentItem = 0;
+
+ form->setInteractiveMode(true);
+}
+
+void
+FormIO::readChildNodes(ObjectTreeItem *item, Container *container, const QDomElement &el, QWidget *w)
+{
+ QString eltag = el.tagName();
+
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(w);
+ QWidget *subwidget = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : w;
+
+ for(QDomNode n = el.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ QString tag = n.toElement().tagName();
+ QDomElement node = n.toElement();
+
+ if((tag == "property") || (tag == "attribute"))
+ {
+ QString name = node.attribute("name");
+ //if(name == "geometry")
+ // hasGeometryProp = true;
+ if( ((eltag == "grid") || (eltag == "hbox") || (eltag == "vbox")) &&
+ (name == "name")) // we don't care about layout names
+ continue;
+
+ if (node.attribute("subwidget")=="true") {
+ //this is property for subwidget: remember it for delayed setting
+ //because now the subwidget could be not created yet (true e.g. for KexiDBAutoField)
+ const QVariant val( readPropertyValue(node.firstChild(), w, name) );
+ kdDebug() << val.toStringList() << endl;
+ item->addSubproperty( name.latin1(), val );
+ //subwidget->setProperty(name.latin1(), val);
+ item->addModifiedProperty( name.latin1(), val );
+ continue;
+ }
+
+ // We cannot assign the buddy now as the buddy widget may not be created yet
+ if(name == "buddy")
+ m_buddies->insert(readPropertyValue(node.firstChild(), w, name).toString(), (QLabel*)w);
+ else if(((eltag == "grid") || (eltag == "hbox") || (eltag == "vbox")) &&
+ item->container() && item->container()->layout()) {
+ // We load the margin of a Layout
+ if(name == "margin") {
+ int margin = readPropertyValue(node.firstChild(), w, name).toInt();
+ item->container()->setLayoutMargin(margin);
+ item->container()->layout()->setMargin(margin);
+ }
+ // We load the spacing of a Layout
+ else if(name == "spacing") {
+ int spacing = readPropertyValue(node.firstChild(), w, name).toInt();
+ item->container()->setLayoutSpacing(spacing);
+ item->container()->layout()->setSpacing(spacing);
+ }
+ else if((name == "justify")){
+ bool justify = readPropertyValue(node.firstChild(), w, name).toBool();
+ KexiFlowLayout *flow = static_cast<KexiFlowLayout*>(item->container()->layout());
+ if(flow)
+ flow->setJustified(justify);
+ }
+ }
+ // If the object doesn't have this property, we let the Factory handle it (maybe a special property)
+ else if(subwidget->metaObject()->findProperty(name.latin1(), true) == -1)
+ {
+ if(w->className() == QString::fromLatin1("CustomWidget"))
+ item->storeUnknownProperty(node);
+ else {
+ bool read = container->form()->library()->readSpecialProperty(
+ w->className(), node, w, item);
+ if(!read) // the factory doesn't support this property neither
+ item->storeUnknownProperty(node);
+ }
+ }
+ else // we have a normal property, let's load it
+ {
+ QVariant val( readPropertyValue(node.firstChild(), w, name) );
+ if(name == "geometry" && dynamic_cast<FormWidget*>(w)) {
+ //fix geometry if needed - this is top level form widget
+ QRect r( val.toRect() );
+ if (r.left()<0) //negative X!
+ r.moveLeft(0);
+ if (r.top()<0) //negative Y!
+ r.moveTop(0);
+ val = r;
+ }
+ subwidget->setProperty(name.latin1(), val);
+// int count = w->metaObject()->findProperty(name, true);
+// const QMetaProperty *meta = w->metaObject()->property(count, true);
+// if(meta && meta->isEnumType()) {
+// val = w->property(name.latin1()); //update: we want a numeric value of enum
+// }
+ item->addModifiedProperty(name.latin1(), val);
+ }
+ }
+ else if(tag == "widget") // a child widget
+ {
+ if(item->container()) // we are a Container
+ loadWidget(item->container(), node);
+ else
+ loadWidget(container, node, w);
+ }
+ else if(tag == "spacer") {
+ loadWidget(container, node, w);
+ }
+ else if(tag == "grid") {
+ // first, see if it is flow layout
+ QString layoutName;
+ for(QDomNode child = node.firstChild(); !child.isNull(); child = child.nextSibling()) {
+ if((child.toElement().tagName() == "property") && (child.toElement().attribute("name") == "customLayout")) {
+ layoutName = child.toElement().text();
+ break;
+ }
+ }
+
+ if(layoutName == "HFlow") {
+ item->container()->m_layType = Container::HFlow;
+ KexiFlowLayout *layout = new KexiFlowLayout(item->widget());
+ layout->setOrientation(Horizontal);
+ item->container()->m_layout = (QLayout*)layout;
+ }
+ else if(layoutName == "VFlow") {
+ item->container()->m_layType = Container::VFlow;
+ KexiFlowLayout *layout = new KexiFlowLayout(item->widget());
+ layout->setOrientation(Vertical);
+ item->container()->m_layout = (QLayout*)layout;
+ }
+ else { // grid layout
+ item->container()->m_layType = Container::Grid;
+ QGridLayout *layout = new QGridLayout(item->widget(), 1, 1);
+ item->container()->m_layout = (QLayout*)layout;
+ }
+ readChildNodes(item, container, node, w);
+ }
+ else if(tag == "vbox") {
+ item->container()->m_layType = Container::VBox;
+ QVBoxLayout *layout = new QVBoxLayout(item->widget());
+ item->container()->m_layout = (QLayout*)layout;
+ readChildNodes(item, container, node, w);
+ }
+ else if(tag == "hbox") {
+ item->container()->m_layType = Container::HBox;
+ QHBoxLayout *layout = new QHBoxLayout(item->widget());
+ item->container()->m_layout = (QLayout*)layout;
+ readChildNodes(item, container, node, w);
+ }
+ else {// unknown tag, we let the Factory handle it
+ if(w->className() == QString::fromLatin1("CustomWidget"))
+ item->storeUnknownProperty(node);
+ else {
+ bool read = container->form()->library()->readSpecialProperty(
+ w->className(), node, w, item);
+ if(!read) // the factory doesn't suport this property neither
+ item->storeUnknownProperty(node);
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+///////////// Helper functions //////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+void
+FormIO::addIncludeFileName(const QString &include, QDomDocument &domDoc)
+{
+ if(include.isEmpty())
+ return;
+
+ QDomElement includes;
+ QDomElement uiEl = domDoc.namedItem("UI").toElement();
+ if(uiEl.namedItem("includehints").isNull())
+ {
+ includes = domDoc.createElement("includehints");
+ uiEl.appendChild(includes);
+ }
+ else
+ includes = uiEl.namedItem("includehints").toElement();
+
+ // Check if this include has already been saved, and return if it is the case
+ for(QDomNode n = includes.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if(n.toElement().text() == include)
+ return;
+ }
+
+ QDomElement includeHint = domDoc.createElement("includehint");
+ includes.appendChild(includeHint);
+ QDomText includeText = domDoc.createTextNode(include);
+ includeHint.appendChild(includeText);
+}
+
+//////// Qt Designer code: these two functions were copied (and adapted) from Qt Designer for compatibility ////////
+
+QString
+FormIO::saveImage(QDomDocument &domDoc, const QPixmap &pixmap)
+{
+ QDomNode node = domDoc.namedItem("images");
+ QDomElement images;
+ if(node.isNull())
+ {
+ images = domDoc.createElement("images");
+ QDomElement ui = domDoc.namedItem("UI").toElement();
+ ui.appendChild(images);
+ }
+ else
+ images = node.toElement();
+
+ int count = images.childNodes().count();
+ QDomElement image = domDoc.createElement("image");
+ QString name = "image" + QString::number(count);
+ image.setAttribute("name", name);
+
+ QImage img = pixmap.convertToImage();
+ QByteArray ba;
+ QBuffer buf(ba);
+ buf.open( IO_WriteOnly | IO_Translate );
+ QString format = img.depth() > 1 ? "XPM" : "XBM";
+ QImageIO iio( &buf, format.latin1() );
+ iio.setImage( img );
+ iio.write();
+ buf.close();
+ QByteArray bazip = qCompress( ba );
+ ulong len = bazip.size();
+
+ QDomElement data = domDoc.createElement("data");
+ data.setAttribute("format", format + ".GZ");
+ data.setAttribute("length", ba.size());
+
+ static const char hexchars[] = "0123456789abcdef";
+ QString content;
+ for(int i = 4; i < (int)len; ++i)
+ {
+ uchar s = (uchar) bazip[i];
+ content += hexchars[s >> 4];
+ content += hexchars[s & 0x0f];
+ }
+
+ QDomText text = domDoc.createTextNode(content);
+ data.appendChild(text);
+ image.appendChild(data);
+ images.appendChild(image);
+
+ return name;
+}
+
+QPixmap
+FormIO::loadImage(QDomDocument domDoc, const QString& name)
+{
+ QDomElement images = domDoc.namedItem("UI").namedItem("images").toElement();
+ if(images.isNull())
+ return 0;
+
+ QDomElement image;
+ for(QDomNode n = images.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ if((n.toElement().tagName() == "image") && (n.toElement().attribute("name") == name))
+ {
+ image = n.toElement();
+ break;
+ }
+ }
+
+ QPixmap pix;
+ QString data = image.namedItem("data").toElement().text();
+ const int lengthOffset = 4;
+ int baSize = data.length() / 2 + lengthOffset;
+ uchar *ba = new uchar[baSize];
+ for(int i = lengthOffset; i < baSize; ++i)
+ {
+ char h = data[2 * (i-lengthOffset)].latin1();
+ char l = data[2 * (i-lengthOffset) + 1].latin1();
+ uchar r = 0;
+ if(h <= '9')
+ r += h - '0';
+ else
+ r += h - 'a' + 10;
+ r = r << 4;
+ if(l <= '9')
+ r += l - '0';
+ else
+ r += l - 'a' + 10;
+ ba[i] = r;
+ }
+
+ QString format = image.namedItem("data").toElement().attribute("format", "PNG");
+ if((format == "XPM.GZ") || (format == "XBM.GZ"))
+ {
+ ulong len = image.attribute("length").toULong();
+ if(len < data.length() * 5)
+ len = data.length() * 5;
+ // qUncompress() expects the first 4 bytes to be the expected length of
+ // the uncompressed data
+ ba[0] = ( len & 0xff000000 ) >> 24;
+ ba[1] = ( len & 0x00ff0000 ) >> 16;
+ ba[2] = ( len & 0x0000ff00 ) >> 8;
+ ba[3] = ( len & 0x000000ff );
+ QByteArray baunzip = qUncompress(ba, baSize);
+ pix.loadFromData( (const uchar*)baunzip.data(), baunzip.size(), format.left(format.find('.')).latin1() );
+ }
+ else
+ pix.loadFromData( (const uchar*)ba+lengthOffset, baSize-lengthOffset, format.latin1() );
+
+ delete[] ba;
+
+ return pix;
+}
+
+//////// End of Qt Designer code ////////////////////////////////////////////////////////
+
+#include "formIO.moc"
diff --git a/kexi/formeditor/formIO.h b/kexi/formeditor/formIO.h
new file mode 100644
index 000000000..b7337182a
--- /dev/null
+++ b/kexi/formeditor/formIO.h
@@ -0,0 +1,222 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef FORMIO_H
+#define FORMIO_H
+
+#include <qobject.h>
+#include <qdict.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <qmap.h>
+
+class QString;
+class QDomElement;
+class QDomNode;
+class QDomDocument;
+class QDomText;
+class QVariant;
+class QLabel;
+
+//! A blank widget displayed when class is not supported
+class KFORMEDITOR_EXPORT CustomWidget : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ CustomWidget(const QCString &className, QWidget *parent, const char *name);
+ virtual ~CustomWidget();
+
+ virtual void paintEvent(QPaintEvent *ev);
+
+ private:
+ QCString m_className;
+};
+
+namespace KFormDesigner {
+
+class WidgetPropertySet;
+class Form;
+class ObjectTreeItem;
+class Container;
+class WidgetLibrary;
+
+//! KFormDesigner API version number. Increased on every breaking of backward compatibility.
+//! Use KFormDesigner::version() to get real version number of the library.
+#define KFORMDESIGNER_VERSION 2
+
+//! \return KFormDesigner API version number for this library. This information is stored
+KFORMEDITOR_EXPORT uint version();
+
+/** This class act as a namespace for all .ui files related functions, ie saving/loading .ui files.
+ You don't need to create a FormIO object, as all methods are static.\n
+ This class is able to read and write Forms to .ui files, and to save each type of properties, including set and enum
+ properties, and pixmaps(pixmap-related code was taken from Qt Designer).
+ **/
+ //! A class to save/load forms from .ui files
+class KFORMEDITOR_EXPORT FormIO : public QObject
+{
+ Q_OBJECT
+
+ public:
+ FormIO();
+ ~FormIO();
+
+ /*! Save the Form in the \a domDoc QDomDocument. Called by saveForm().
+ \return true if saving succeeded.
+ \sa saveForm() */
+ static bool saveFormToDom(Form *form, QDomDocument &domDoc);
+
+ /*! Save the Form \a form to the file \a filename. If \a filename is null or not given,
+ a Save File dialog will be shown to choose dest file.
+ \return true if saving succeeded.
+ \todo Add errors code and error dialog
+ */
+ static bool saveFormToFile(Form *form, const QString &filename=QString::null);
+
+ /*! Saves the Form to the \a dest string. \a indent can be specified to apply indentation.
+ \return true if saving succeeded.
+ \sa saveForm()
+ */
+ static bool saveFormToString(Form *form, QString &dest, int indent = 0);
+
+ /*! Saves the \a form inside the \a dest QByteArray.
+ \return true if saving succeeded.
+ \sa saveFormToDom(), saveForm()
+ */
+ static bool saveFormToByteArray(Form *form, QByteArray &dest);
+
+ /*! Loads a form from the \a domDoc QDomDocument. Called by loadForm() and loadFormData().
+ \return true if loading succeeded. */
+ static bool loadFormFromDom(Form *form, QWidget *container, QDomDocument &domDoc);
+
+ /*! Loads a form from the \a src QByteArray.
+ \sa loadFormFromDom(), loadForm().
+ \return true if loading succeeded.
+ */
+ static bool loadFormFromByteArray(Form *form, QWidget *container, QByteArray &src,
+ bool preview=false);
+
+ static bool loadFormFromString(Form *form, QWidget *container, QString &src, bool preview=false);
+
+ /*! Loads the .ui file \a filename in the Form \a form. If \a filename is null or not given,
+ a Open File dialog will be shown to select the file to open.
+ createToplevelWidget() is used to load the Form's toplevel widget.
+ \return true if loading succeeded.
+ \todo Add errors code and error dialog
+ */
+ static bool loadFormFromFile(Form *form, QWidget *container, const QString &filename=QString::null);
+
+ /*! Saves the widget associated to the ObjectTreeItem \a item into DOM document \a domDoc,
+ with \a parent as parent node.
+ It calls readPropertyValue() for each object property, readAttribute() for each attribute and
+ itself to save child widgets.\n
+ \return true if saving succeeded.
+ This is used to copy/paste widgets.
+ */
+ static void saveWidget(ObjectTreeItem *item, QDomElement &parent, QDomDocument &domDoc,
+ bool insideGridLayout=false);
+
+ /*! Cleans the "UI" QDomElement after saving widget. It deletes the "includes" element
+ not needed when pasting, and make sure all the "widget" elements are at the beginning.
+ Call this after copying a widget, before pasting.*/
+ static void cleanClipboard(QDomElement &uiElement);
+
+ /*! Loads the widget associated to the QDomElement \a el into the Container \a container,
+ with \a parent as parent widget.
+ If parent = 0, the Container::widget() is used as parent widget.
+ This is used to copy/paste widgets.
+ */
+ static void loadWidget(Container *container,
+ const QDomElement &el, QWidget *parent=0);
+
+ /*! Save an element in the \a domDoc as child of \a parentNode.
+ The element will be saved like this :
+ \code <$(tagName) name = "$(property)">< value_as_XML ><$(tagName)/>
+ \endcode
+ */
+ static void savePropertyElement(QDomElement &parentNode, QDomDocument &domDoc, const QString &tagName,
+ const QString &property, const QVariant &value);
+
+ /*! Read an object property in the DOM doc.
+ \param node the QDomNode of the property
+ \param obj the widget whose property is being read
+ \param name the name of the property being read
+ */
+ static QVariant readPropertyValue(QDomNode node, QObject *obj, const QString &name);
+
+ /*! Write an object property in the DOM doc.
+ \param parentNode the DOM document to write to
+ \param name the name of the property being saved
+ \param value the value of this property
+ \param w the widget whose property is being saved
+ \param lib the widget library for which the property is being saved
+
+ Properties of subwidget are saved with subwidget="true" arribute added
+ to 'property' XML element.
+ */
+ static void savePropertyValue(QDomElement &parentNode, QDomDocument &parent, const char *name,
+ const QVariant &value, QWidget *w, WidgetLibrary *lib=0);
+
+ protected:
+ /*! Saves the QVariant \a value as text to be included in an xml file, with \a parentNode.*/
+ static void writeVariant(QDomDocument &parent, QDomElement &parentNode, QVariant value);
+
+ /*! Creates a toplevel widget from the QDomElement \a element in the Form \a form,
+ with \a parent as parent widget.
+ It calls readPropertyValue() and loadWidget() to load child widgets.
+ */
+ static void createToplevelWidget(Form *form, QWidget *container, QDomElement &element);
+
+ /*! \return the name of the pixmap saved, to use to access it
+ This function save the QPixmap \a pixmap into the DOM document \a domDoc.
+ The pixmap is converted to XPM and compressed for compatibility with Qt Designer.
+ Encoding code is taken from Designer.
+ */
+ static QString saveImage(QDomDocument &domDoc, const QPixmap &pixmap);
+
+ /*! \return the loaded pixmap
+ This function loads the pixmap named \a name in the DOM document \a domDoc.
+ Decoding code is taken from QT Designer.
+ */
+ static QPixmap loadImage(QDomDocument domDoc, const QString& name);
+
+ /*! Reads the child nodes of a "widget" element. */
+ static void readChildNodes(ObjectTreeItem *tree, Container *container,
+ const QDomElement &el, QWidget *w);
+
+ /*! Adds an include file name to be saved in the "includehints" part of .ui file,
+ which is needed by uic. */
+ static void addIncludeFileName(const QString &include, QDomDocument &domDoc);
+
+ private:
+ // This dict stores buddies associations until the Form is completely loaded.
+ static QDict<QLabel> *m_buddies;
+
+ /// Instead of having to pass these for every functions, we just store them in the class
+ //static QWidgdet *m_currentWidget;
+ static ObjectTreeItem *m_currentItem;
+ static Form *m_currentForm;
+ static bool m_savePixmapsInline;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/formmanager.cpp b/kexi/formeditor/formmanager.cpp
new file mode 100644
index 000000000..20b40cf94
--- /dev/null
+++ b/kexi/formeditor/formmanager.cpp
@@ -0,0 +1,1716 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+
+#include <qworkspace.h>
+#include <qcursor.h>
+#include <qstring.h>
+#include <qlabel.h>
+#include <qobjectlist.h>
+#include <qstylefactory.h>
+#include <qmetaobject.h>
+#include <qregexp.h>
+#include <qvaluevector.h>
+#include <qvbox.h>
+
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+#include <kstdaction.h>
+#include <kaction.h>
+#include <kxmlguiclient.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kstyle.h>
+#include <kactionclasses.h>
+#include <kapplication.h>
+#include <kglobal.h>
+#include <kglobalsettings.h>
+#include <kdialogbase.h>
+#include <ktextedit.h>
+#include <ktabwidget.h>
+#include <kfontdialog.h>
+
+#include <kdeversion.h>
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9) && !defined(Q_WS_WIN)
+# include <kactioncollection.h>
+#endif
+
+#include "widgetpropertyset.h"
+#include "objecttree.h"
+#include "widgetlibrary.h"
+#include "form.h"
+#include "container.h"
+#include "formIO.h"
+#include "objecttreeview.h"
+#include "commands.h"
+#include "tabstopdialog.h"
+#include "connectiondialog.h"
+#include "pixmapcollection.h"
+#include "events.h"
+#include "utils.h"
+#include "kfdpixmapedit.h"
+#include <koproperty/editor.h>
+#include <koproperty/property.h>
+#include <koproperty/factory.h>
+
+#include "formmanager.h"
+
+#define KFD_NO_STYLES //disables; styles support needs improvements
+
+namespace KFormDesigner {
+
+//! @internal
+class PropertyFactory : public KoProperty::CustomPropertyFactory
+{
+ public:
+ PropertyFactory(QObject *parent)
+ : KoProperty::CustomPropertyFactory(parent)
+// m_manager(manager)
+ {
+ }
+ virtual ~PropertyFactory() {}
+
+ KoProperty::CustomProperty* createCustomProperty(KoProperty::Property *) { return 0;}
+
+ KoProperty::Widget* createCustomWidget(KoProperty::Property *prop)
+ {
+ return new KFDPixmapEdit(prop);
+ }
+};
+
+}
+
+using namespace KFormDesigner;
+
+static KStaticDeleter<FormManager> m_managerDeleter;
+FormManager* FormManager::_self = 0L;
+
+FormManager::FormManager(QObject *parent, int options, const char *name)
+ : QObject(parent, name)
+#ifdef KEXI_DEBUG_GUI
+ , m_uiCodeDialog(0)
+ , m_options(options)
+#endif
+ , m_objectBlockingPropertyEditorUpdating(0)
+ , m_isRedoing(false)
+{
+ Q_UNUSED(options);
+#ifdef KEXI_STANDALONE
+ KGlobal::locale()->insertCatalogue("standalone_kformdesigner");
+#else
+ KGlobal::locale()->insertCatalogue("kformdesigner");
+#endif
+
+ connect( kapp, SIGNAL( settingsChanged(int) ), SLOT( slotSettingsChanged(int) ) );
+ slotSettingsChanged(KApplication::SETTINGS_SHORTCUTS);
+
+//moved to createWidgetLibrary() m_lib = new WidgetLibrary(this, supportedFactoryGroups);
+ m_propSet = new WidgetPropertySet(this);
+
+ //unused m_editor = 0;
+ m_active = 0;
+ m_inserting = false;
+ m_drawingSlot = false;
+ m_collection = 0;
+ m_connection = 0;
+ m_popup = 0;
+ m_treeview = 0;
+ m_emitSelectionSignalsUpdatesPropertySet = false;
+ m_domDoc.appendChild(m_domDoc.createElement("UI"));
+
+ m_deleteWidgetLater_list.setAutoDelete(true);
+ connect( &m_deleteWidgetLater_timer, SIGNAL(timeout()), this, SLOT(deleteWidgetLaterTimeout()));
+ connect( this, SIGNAL(connectionCreated(KFormDesigner::Form*, KFormDesigner::Connection&)),
+ this, SLOT(slotConnectionCreated(KFormDesigner::Form*, KFormDesigner::Connection&)));
+
+ // register kfd custom editors
+ KoProperty::FactoryManager::self()->registerFactoryForEditor(KoProperty::Pixmap,
+ new PropertyFactory(KoProperty::FactoryManager::self()));
+}
+
+FormManager::~FormManager()
+{
+ m_managerDeleter.setObject(_self, 0, false); //safe
+ delete m_popup;
+ delete m_connection;
+#ifdef KEXI_DEBUG_GUI
+ delete m_uiCodeDialog;
+#endif
+// delete m_propFactory;
+}
+
+
+FormManager* FormManager::self()
+{
+ return _self;
+}
+
+WidgetLibrary*
+FormManager::createWidgetLibrary(FormManager* m, const QStringList& supportedFactoryGroups)
+{
+ if(!_self)
+ m_managerDeleter.setObject( _self, m );
+ return new WidgetLibrary(_self, supportedFactoryGroups);
+}
+
+void
+FormManager::setEditor(KoProperty::Editor *editor)
+{
+ m_editor = editor;
+
+ if(editor)
+ editor->changeSet(m_propSet->set());
+}
+
+void
+FormManager::setObjectTreeView(ObjectTreeView *treeview)
+{
+ m_treeview = treeview;
+ if (m_treeview)
+ connect(m_propSet, SIGNAL(widgetNameChanged(const QCString&, const QCString&)),
+ m_treeview, SLOT(renameItem(const QCString&, const QCString&)));
+}
+
+ActionList
+FormManager::createActions(WidgetLibrary *lib, KActionCollection* collection, KXMLGUIClient* client)
+{
+ m_collection = collection;
+
+ ActionList actions = lib->createWidgetActions(client, m_collection, this, SLOT(insertWidget(const QCString &)));
+
+ if (m_options & HideSignalSlotConnections)
+ m_dragConnection = 0;
+ else {
+ m_dragConnection = new KToggleAction(i18n("Connect Signals/Slots"),
+ "signalslot", KShortcut(0), this, SLOT(startCreatingConnection()), m_collection,
+ "drag_connection");
+ //to be exclusive with any 'widget' action
+ m_dragConnection->setExclusiveGroup("LibActionWidgets");
+ m_dragConnection->setChecked(false);
+ actions.append(m_dragConnection);
+ }
+
+ m_pointer = new KToggleAction(i18n("Pointer"), "mouse_pointer", KShortcut(0),
+ this, SLOT(slotPointerClicked()), m_collection, "pointer");
+ m_pointer->setExclusiveGroup("LibActionWidgets"); //to be exclusive with any 'widget' action
+ m_pointer->setChecked(true);
+ actions.append(m_pointer);
+
+ m_snapToGrid = new KToggleAction(i18n("Snap to Grid"), QString::null, KShortcut(0),
+ 0, 0, m_collection, "snap_to_grid");
+ m_snapToGrid->setChecked(true);
+ actions.append(m_snapToGrid);
+
+ // Create the Style selection action (with a combo box in toolbar and submenu items)
+ KSelectAction *m_style = new KSelectAction( i18n("Style"), KShortcut(0),
+ this, SLOT(slotStyle()), m_collection, "change_style");
+ m_style->setEditable(false);
+
+ KGlobal::config()->setGroup("General");
+ QString currentStyle = QString::fromLatin1(kapp->style().name()).lower();
+ const QStringList styles = QStyleFactory::keys();
+ m_style->setItems(styles);
+ m_style->setCurrentItem(0);
+
+ QStringList::ConstIterator endIt = styles.constEnd();
+ int idx = 0;
+ for (QStringList::ConstIterator it = styles.constBegin(); it != endIt; ++it, ++idx)
+ {
+ if ((*it).lower() == currentStyle) {
+ m_style->setCurrentItem(idx);
+ break;
+ }
+ }
+
+ m_style->setToolTip(i18n("Set the current view style."));
+ m_style->setMenuAccelsEnabled(true);
+ actions.append(m_style);
+
+ lib->addCustomWidgetActions(m_collection);
+
+ return actions;
+}
+
+bool
+FormManager::isPasteEnabled()
+{
+ return m_domDoc.namedItem("UI").hasChildNodes();
+}
+
+void
+FormManager::undo()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ activeForm()->commandHistory()->undo();
+}
+
+void
+FormManager::redo()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ m_isRedoing = true;
+ activeForm()->commandHistory()->redo();
+ m_isRedoing = false;
+}
+
+void
+FormManager::insertWidget(const QCString &classname)
+{
+ if(m_drawingSlot)
+ stopCreatingConnection();
+
+ m_inserting = true;
+
+ Form *form;
+ for(form = m_forms.first(); form; form = m_forms.next())
+ {
+// form->d->cursors = new QMap<QString, QCursor>();
+ if (form->toplevelContainer())
+ form->widget()->setCursor(QCursor(CrossCursor));
+ QObjectList *l = form->widget()->queryList( "QWidget" );
+ for(QObject *o = l->first(); o; o = l->next())
+ {
+ if( ((QWidget*)o)->ownCursor() )
+ {
+// form->d->cursors->insert(o->name(), ((QWidget*)o)->cursor());
+ form->d->cursors.insert(o, static_cast<QWidget*>(o)->cursor());
+ static_cast<QWidget*>(o)->setCursor(QCursor(Qt::CrossCursor));
+ }
+
+ }
+ delete l;
+ }
+
+ m_selectedClass = classname;
+ m_pointer->setChecked(false);
+}
+
+void
+FormManager::stopInsert()
+{
+ if(m_drawingSlot)
+ stopCreatingConnection();
+ if(!m_inserting)
+ return;
+
+//#ifndef KEXI_NO_CURSOR_PROPERTY
+ Form *form;
+ for(form = m_forms.first(); form; form = m_forms.next())
+ {
+ form->widget()->unsetCursor();
+ QObjectList *l = form->widget()->queryList( "QWidget" );
+ for(QObject *o = l->first(); o; o = l->next())
+ {
+ static_cast<QWidget*>(o)->unsetCursor();
+#if 0
+ if( ((QWidget*)o)->ownCursor()) {
+ QMap<QObject*,QCursor>::ConstIterator curIt( form->d->cursors.find(o) );
+ if (curIt!=form->d->cursors.constEnd())
+ static_cast<QWidget*>(o)->setCursor( *curIt );
+// ((QWidget*)o)->setCursor( (*(form->d->cursors))[o->name()] ) ;
+ }
+#endif
+ }
+ delete l;
+// delete (form->d->cursors);
+// form->d->cursors = 0;
+ }
+//#endif
+ m_inserting = false;
+ m_pointer->setChecked(true);
+}
+
+void
+FormManager::slotPointerClicked()
+{
+ if(m_inserting)
+ stopInsert();
+ else if(m_dragConnection)
+ stopCreatingConnection();
+}
+
+void
+FormManager::startCreatingConnection()
+{
+ if (m_options & HideSignalSlotConnections)
+ return;
+
+ if(m_inserting)
+ stopInsert();
+
+ // We set a Pointing hand cursor while drawing the connection
+ Form *form;
+ for(form = m_forms.first(); form; form = m_forms.next())
+ {
+// form->d->cursors = new QMap<QString, QCursor>();
+ form->d->mouseTrackers = new QStringList();
+ if (form->toplevelContainer())
+ {
+ form->widget()->setCursor(QCursor(PointingHandCursor));
+ form->widget()->setMouseTracking(true);
+ }
+ QObjectList *l = form->widget()->queryList( "QWidget" );
+ for(QObject *o = l->first(); o; o = l->next())
+ {
+ QWidget *w = static_cast<QWidget*>(o);
+ if( w->ownCursor() )
+ {
+ form->d->cursors.insert(w, w->cursor());
+// form->d->cursors->insert(w->name(), w->cursor());
+ w->setCursor(QCursor(PointingHandCursor ));
+ }
+ if(w->hasMouseTracking())
+ form->d->mouseTrackers->append(w->name());
+ w->setMouseTracking(true);
+ }
+ delete l;
+ }
+ delete m_connection;
+ m_connection = new Connection();
+ m_drawingSlot = true;
+ if (m_dragConnection)
+ m_dragConnection->setChecked(true);
+}
+
+void
+FormManager::resetCreatedConnection()
+{
+ if (m_options & HideSignalSlotConnections)
+ return;
+
+ delete m_connection;
+ m_connection = new Connection();
+
+ if(m_active && m_active->formWidget()) {
+ Form *ff = (Form*)m_active;
+ FormWidget *fw = 0;
+ if (ff)
+ fw = ff->formWidget();
+ m_active->formWidget()->clearForm();
+ }
+ if (m_active && m_active->widget())
+ m_active->widget()->repaint();
+}
+
+void
+FormManager::stopCreatingConnection()
+{
+ if (m_options & HideSignalSlotConnections)
+ return;
+ if(!m_drawingSlot)
+ return;
+
+ if(m_active && m_active->formWidget())
+ m_active->formWidget()->clearForm();
+
+ Form *form;
+ for(form = m_forms.first(); form; form = m_forms.next())
+ {
+ form->widget()->unsetCursor();
+ form->widget()->setMouseTracking(false);
+ QObjectList *l = form->widget()->queryList( "QWidget" );
+ for(QObject *o = l->first(); o; o = l->next())
+ {
+ QWidget *w = (QWidget*)o;
+ if( w->ownCursor()) {
+ QMap<QObject*,QCursor>::ConstIterator curIt( form->d->cursors.find(o) );
+ if (curIt!=form->d->cursors.constEnd())
+ static_cast<QWidget*>(o)->setCursor( *curIt );
+ }
+// w->setCursor( (*(form->d->cursors))[o->name()] ) ;
+ w->setMouseTracking( !form->d->mouseTrackers->grep(w->name()).isEmpty() );
+ }
+ delete l;
+// delete (form->d->cursors);
+// form->d->cursors = 0;
+ delete (form->d->mouseTrackers);
+ form->d->mouseTrackers = 0;
+ }
+
+ if(m_connection->slot().isNull())
+ emit connectionAborted(activeForm());
+ delete m_connection;
+ m_connection = 0;
+ m_drawingSlot = false;
+ m_pointer->setChecked(true);
+}
+
+bool
+FormManager::snapWidgetsToGrid()
+{
+ return m_snapToGrid->isChecked();
+}
+
+void
+FormManager::windowChanged(QWidget *w)
+{
+ kdDebug() << "FormManager::windowChanged("
+ << (w ? (QString(w->className())+" "+w->name()) : QString("0")) << ")" << endl;
+
+ if(!w)
+ {
+ m_active = 0;
+ if(m_treeview)
+ m_treeview->setForm(0);
+ emit propertySetSwitched(0);
+ if(isCreatingConnection())
+ stopCreatingConnection();
+
+ emitNoFormSelected();
+ return;
+ }
+
+ Form *previousActive = m_active;
+ Form *form;
+ for(form = m_forms.first(); form; form = m_forms.next())
+ {
+ if(form->toplevelContainer() && form->widget() == w)
+ {
+ if(m_treeview)
+ m_treeview->setForm(form);
+ //if(m_propSet)
+ // m_propList->setCollection(form->pixmapCollection());
+
+ kdDebug() << "FormManager::windowChanged() active form is " << form->objectTree()->name() << endl;
+
+ if(m_collection)
+ {
+#ifndef KFD_NO_STYLES
+ // update the 'style' action
+ KSelectAction *m_style = (KSelectAction*)m_collection->action("change_style", "KSelectAction");
+ const QString currentStyle = form->widget()->style().name();
+ const QStringList styles = m_style->items();
+
+ int idx = 0;
+ QStringList::ConstIterator endIt = styles.constEnd();
+ for (QStringList::ConstIterator it = styles.constBegin(); it != endIt; ++it, ++idx)
+ {
+ if ((*it).lower() == currentStyle) {
+ kdDebug() << "Updating the style to " << currentStyle << endl;
+ m_style->setCurrentItem(idx);
+ break;
+ }
+ }
+#endif
+ }
+
+ if((form != previousActive) && isCreatingConnection())
+ resetCreatedConnection();
+
+ m_active = form;
+
+ emit dirty(form, form->isModified());
+ // update actions state
+ m_active->emitActionSignals();
+ //update the buffer too
+ form->emitSelectionSignals();
+ if (!m_emitSelectionSignalsUpdatesPropertySet)
+ showPropertySet( propertySet(), true );
+ return;
+ }
+ }
+
+ for(form = m_preview.first(); form; form = m_preview.next())
+ {
+ kdDebug() << (form->widget() ? form->widget()->name() : "") << endl;
+ if(form->toplevelContainer() && form->widget() == w) {
+ kdDebug() << "FormManager::windowChanged() active preview form is " << form->widget()->name() << endl;
+
+ if(m_collection)
+ {
+#ifndef KFD_NO_STYLES
+ // update the 'style' action
+ KSelectAction *m_style = (KSelectAction*)m_collection->action("change_style", "KSelectAction");
+ const QString currentStyle = form->widget()->style().name();
+ const QStringList styles = m_style->items();
+
+ int idx = 0;
+ QStringList::ConstIterator endIt = styles.constEnd();
+ for (QStringList::ConstIterator it = styles.constBegin(); it != endIt; ++it, ++idx)
+ {
+ if ((*it).lower() == currentStyle) {
+ kdDebug() << "Updating the style to " << currentStyle << endl;
+ m_style->setCurrentItem(idx);
+ break;
+ }
+ }
+#endif
+
+ resetCreatedConnection();
+ m_active = form;
+
+ emit dirty(form, false);
+ emitNoFormSelected();
+ showPropertySet(0);
+ return;
+ }
+ }
+ }
+ //m_active = 0;
+}
+
+Form*
+FormManager::activeForm() const
+{
+ return m_active;
+}
+
+Form*
+FormManager::formForWidget(QWidget *w)
+{
+ for(Form *form = m_forms.first(); form; form = m_forms.next()) {
+ if(form->toplevelContainer() && form->widget() == w)
+ return form;
+ }
+
+ return 0; // not one of toplevel widgets
+}
+
+void
+FormManager::deleteForm(Form *form)
+{
+ if (!form)
+ return;
+ if(m_forms.find(form) == -1)
+ m_preview.remove(form);
+ else
+ m_forms.remove(form);
+
+ if(m_forms.count() == 0) {
+ m_active = 0;
+ emit propertySetSwitched(0);
+ }
+}
+
+void
+FormManager::importForm(Form *form, bool preview)
+{
+ if(!preview)
+ initForm(form);
+ else
+ {
+ m_preview.append(form);
+ form->setDesignMode(false);
+ }
+}
+
+void
+FormManager::initForm(Form *form)
+{
+ m_forms.append(form);
+
+ if(m_treeview)
+ m_treeview->setForm(form);
+
+ m_active = form;
+
+ connect(form, SIGNAL(selectionChanged(QWidget*, bool, bool)),
+ m_propSet, SLOT(setSelectedWidgetWithoutReload(QWidget*, bool, bool)));
+ if(m_treeview)
+ {
+ connect(form, SIGNAL(selectionChanged(QWidget*, bool, bool)),
+ m_treeview, SLOT(setSelectedWidget(QWidget*, bool)));
+ connect(form, SIGNAL(childAdded(ObjectTreeItem* )), m_treeview, SLOT(addItem(ObjectTreeItem*)));
+ connect(form, SIGNAL(childRemoved(ObjectTreeItem* )), m_treeview, SLOT(removeItem(ObjectTreeItem*)));
+ }
+ connect(m_propSet, SIGNAL(widgetNameChanged(const QCString&, const QCString&)),
+ form, SLOT(changeName(const QCString&, const QCString&)));
+
+ form->setSelectedWidget(form->widget());
+ windowChanged(form->widget());
+}
+
+void
+FormManager::previewForm(Form *form, QWidget *container, Form *toForm)
+{
+ if(!form || !container || !form->objectTree())
+ return;
+ QDomDocument domDoc;
+ if (!FormIO::saveFormToDom(form, domDoc))
+ return;
+
+ Form *myform;
+ if(!toForm)
+ myform = new Form(form->library(), form->objectTree()->name().latin1(),
+ false/*!designMode, we need to set it early enough*/);
+ else
+ myform = toForm;
+ myform->createToplevel(container);
+ container->setStyle( &(form->widget()->style()) );
+
+ if (!FormIO::loadFormFromDom(myform, container, domDoc)) {
+ delete myform;
+ return;
+ }
+
+ myform->setDesignMode(false);
+ m_preview.append(myform);
+ container->show();
+}
+
+/*
+bool
+FormManager::loadFormFromDomInternal(Form *form, QWidget *container, QDomDocument &inBuf)
+{
+ return FormIO::loadFormFromDom(myform, container, domDoc);
+}
+
+bool
+FormManager::saveFormToStringInternal(Form *form, QString &dest, int indent)
+{
+ return KFormDesigner::FormIO::saveFormToString(form, dest, indent);
+}*/
+
+bool
+FormManager::isTopLevel(QWidget *w)
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return false;
+
+// kdDebug() << "FormManager::isTopLevel(): for: " << w->name() << " = "
+// << activeForm()->objectTree()->lookup(w->name())<< endl;
+
+ ObjectTreeItem *item = activeForm()->objectTree()->lookup(w->name());
+ if(!item)
+ return true;
+
+ return (!item->parent());
+}
+
+void
+FormManager::deleteWidget()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ QPtrList<QWidget> *list = activeForm()->selectedWidgets();
+ if(list->isEmpty())
+ return;
+
+ if (activeForm()->widget() == list->first()) {
+ //toplevel form is selected, cannot delete it
+ return;
+ }
+
+ KCommand *com = new DeleteWidgetCommand(*list, activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::copyWidget()
+{
+ if (!activeForm() || !activeForm()->objectTree())
+ return;
+
+ QPtrList<QWidget> *list = activeForm()->selectedWidgets();
+ if(list->isEmpty())
+ return;
+
+ removeChildrenFromList(*list);
+
+ // We clear the current clipboard
+ m_domDoc.setContent(QString(), true);
+ QDomElement parent = m_domDoc.createElement("UI");
+ m_domDoc.appendChild(parent);
+
+ QWidget *w;
+ for(w = list->first(); w; w = list->next())
+ {
+ ObjectTreeItem *it = activeForm()->objectTree()->lookup(w->name());
+ if (!it)
+ continue;
+
+ FormIO::saveWidget(it, parent, m_domDoc);
+ }
+
+ FormIO::cleanClipboard(parent);
+
+ activeForm()->emitActionSignals(); // to update 'Paste' item state
+}
+
+void
+FormManager::cutWidget()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ QPtrList<QWidget> *list = activeForm()->selectedWidgets();
+ if(list->isEmpty())
+ return;
+
+ KCommand *com = new CutWidgetCommand(*list, activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::pasteWidget()
+{
+ if(!m_domDoc.namedItem("UI").hasChildNodes())
+ return;
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ KCommand *com = new PasteWidgetCommand(m_domDoc, activeForm()->activeContainer(), m_insertPoint);
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::setInsertPoint(const QPoint &p)
+{
+ m_insertPoint = p;
+}
+
+void
+FormManager::createSignalMenu(QWidget *w)
+{
+ m_sigSlotMenu = new KPopupMenu();
+ m_sigSlotMenu->insertTitle(SmallIcon("connection"), i18n("Signals"));
+
+ QStrList list = w->metaObject()->signalNames(true);
+ QStrListIterator it(list);
+ for(; it.current() != 0; ++it)
+ m_sigSlotMenu->insertItem(*it);
+
+ int result = m_sigSlotMenu->exec(QCursor::pos());
+ if(result == -1)
+ resetCreatedConnection();
+ else
+ menuSignalChosen(result);
+
+ delete m_sigSlotMenu;
+ m_sigSlotMenu = 0;
+}
+
+void
+FormManager::createSlotMenu(QWidget *w)
+{
+ m_sigSlotMenu = new KPopupMenu();
+ m_sigSlotMenu->insertTitle(SmallIcon("connection"), i18n("Slots"));
+
+ QString signalArg( m_connection->signal().remove( QRegExp(".*[(]|[)]") ) );
+
+ QStrList list = w->metaObject()->slotNames(true);
+ QStrListIterator it(list);
+ for(; it.current() != 0; ++it)
+ {
+ // we add the slot only if it is compatible with the signal
+ QString slotArg(*it);
+ slotArg = slotArg.remove( QRegExp(".*[(]|[)]") );
+ if(!signalArg.startsWith(slotArg, true)) // args not compatible
+ continue;
+
+ m_sigSlotMenu->insertItem(*it);
+ }
+
+ int result = m_sigSlotMenu->exec(QCursor::pos());
+ if(result == -1)
+ resetCreatedConnection();
+ else
+ menuSignalChosen(result);
+
+ delete m_sigSlotMenu;
+ m_sigSlotMenu = 0;
+}
+
+void
+FormManager::createContextMenu(QWidget *w, Container *container, bool popupAtCursor)
+{
+ if(!activeForm() || !activeForm()->widget())
+ return;
+ const bool toplevelWidgetSelected = activeForm()->widget() == w;
+ const uint widgetsCount = container->form()->selectedWidgets()->count();
+ const bool multiple = widgetsCount > 1;
+ //const bool enableRemove = w != m_active->widget();
+ // We only enablelayout creation if more than one widget with the same parent are selected
+ const bool enableLayout = multiple || w == container->widget();
+
+ m_menuWidget = w;
+ QString n = container->form()->library()->displayName(w->className());
+// QValueVector<int> menuIds();
+
+ if (!m_popup) {
+ m_popup = new KPopupMenu();
+ }
+ else {
+ m_popup->clear();
+ }
+
+ //set title
+ if(!multiple)
+ {
+ if(w == container->form()->widget())
+ m_popup->insertTitle(SmallIcon("form"), i18n("%1 : Form").arg(w->name()) );
+ else
+ m_popup->insertTitle( SmallIcon(
+ container->form()->library()->iconName(w->className())), QString(w->name()) + " : " + n );
+ }
+ else
+ m_popup->insertTitle(SmallIcon("multiple_obj"), i18n("Multiple Widgets")
+ + QString(" (%1)").arg(widgetsCount));
+
+ KAction *a;
+#define PLUG_ACTION(_name, forceVisible) \
+ { a = action(_name); \
+ if (a && (forceVisible || a->isEnabled())) { \
+ if (separatorNeeded) \
+ m_popup->insertSeparator(); \
+ separatorNeeded = false; \
+ a->plug(m_popup); \
+ } \
+ }
+
+ bool separatorNeeded = false;
+
+ PLUG_ACTION("edit_cut", !toplevelWidgetSelected);
+ PLUG_ACTION("edit_copy", !toplevelWidgetSelected);
+ PLUG_ACTION("edit_paste", true);
+ PLUG_ACTION("edit_delete", !toplevelWidgetSelected);
+ separatorNeeded = true;
+ PLUG_ACTION("layout_menu", enableLayout);
+ PLUG_ACTION("break_layout", enableLayout);
+ separatorNeeded = true;
+ PLUG_ACTION("align_menu", !toplevelWidgetSelected);
+ PLUG_ACTION("adjust_size_menu", !toplevelWidgetSelected);
+ separatorNeeded = true;
+
+ // We create the buddy menu
+ if(!multiple && w->inherits("QLabel") && ((QLabel*)w)->text().contains("&") && (((QLabel*)w)->textFormat() != RichText))
+ {
+ if (separatorNeeded)
+ m_popup->insertSeparator();
+
+ KPopupMenu *sub = new KPopupMenu(w);
+ QWidget *buddy = ((QLabel*)w)->buddy();
+
+ sub->insertItem(i18n("No Buddy"), MenuNoBuddy);
+ if(!buddy)
+ sub->setItemChecked(MenuNoBuddy, true);
+ sub->insertSeparator();
+
+ // add all the widgets that can have focus
+ for(ObjectTreeListIterator it( container->form()->tabStopsIterator() ); it.current(); ++it)
+ {
+ int index = sub->insertItem(
+ SmallIcon(container->form()->library()->iconName(it.current()->className().latin1())),
+ it.current()->name());
+ if(it.current()->widget() == buddy)
+ sub->setItemChecked(index, true);
+ }
+
+ /*int id =*/ m_popup->insertItem(i18n("Choose Buddy..."), sub);
+// menuIds->append(id);
+ connect(sub, SIGNAL(activated(int)), this, SLOT(buddyChosen(int)));
+
+ separatorNeeded = true;
+ }
+
+ //int sigid=0;
+#ifdef KEXI_DEBUG_GUI
+ if(!multiple && !(m_options & HideEventsInPopupMenu))
+ {
+ if (separatorNeeded)
+ m_popup->insertSeparator();
+
+ // We create the signals menu
+ KPopupMenu *sigMenu = new KPopupMenu();
+ QStrList list = w->metaObject()->signalNames(true);
+ QStrListIterator it(list);
+ for(; it.current() != 0; ++it)
+ sigMenu->insertItem(*it);
+
+ int id = m_popup->insertItem(SmallIconSet(""), i18n("Events"), sigMenu);
+// menuIds->append(id);
+ if(list.isEmpty())
+ m_popup->setItemEnabled(id, false);
+ connect(sigMenu, SIGNAL(activated(int)), this, SLOT(menuSignalChosen(int)));
+ separatorNeeded = true;
+ }
+#endif
+
+ // Other items
+ if(!multiple)
+ {
+ int lastID = -1;
+ if (separatorNeeded) {
+ lastID = m_popup->insertSeparator();
+ }
+ const uint oldIndex = m_popup->count()-1;
+ container->form()->library()->createMenuActions(w->className(), w, m_popup, container);
+ if (oldIndex == (m_popup->count()-1)) {
+// for (uint i=oldIndex; i<m_popup->count(); i++) {
+// int id = m_popup->idAt( i );
+// if (id!=-1)
+// menuIds->append( id );
+// }
+ //nothing added
+ if (separatorNeeded) {
+ m_popup->removeItem( lastID );
+// menuIds->pop_back();
+ }
+ }
+ }
+
+ //show the popup at the selected widget
+ QPoint popupPos;
+ if (popupAtCursor) {
+ popupPos = QCursor::pos();
+ }
+ else {
+ WidgetList *lst = container->form()->selectedWidgets();
+ QWidget * sel_w = lst ? lst->first() : container->form()->selectedWidget();
+ popupPos = sel_w ? sel_w->mapToGlobal(QPoint(sel_w->width()/2, sel_w->height()/2)) : QCursor::pos();
+ }
+ m_insertPoint = container->widget()->mapFromGlobal(popupPos);
+ m_popup->exec(popupPos);//QCursor::pos());
+ m_insertPoint = QPoint();
+
+// QValueVector<int>::iterator it;
+// for(it = menuIds->begin(); it != menuIds->end(); ++it)
+// m_popup->removeItem(*it);
+}
+
+void
+FormManager::buddyChosen(int id)
+{
+ if(!m_menuWidget)
+ return;
+ QLabel *label = static_cast<QLabel*>((QWidget*)m_menuWidget);
+
+ if(id == MenuNoBuddy)
+ {
+ label->setBuddy(0);
+ return;
+ }
+
+ ObjectTreeItem *item = activeForm()->objectTree()->lookup(m_popup->text(id));
+ if(!item || !item->widget())
+ return;
+ label->setBuddy(item->widget());
+}
+
+void
+FormManager::menuSignalChosen(int id)
+{
+ if (m_options & HideSignalSlotConnections)
+ return;
+
+ //if(!m_menuWidget)
+ // return;
+ if(m_drawingSlot && m_sigSlotMenu)
+ {
+ if( m_connection->receiver().isNull() )
+ m_connection->setSignal(m_sigSlotMenu->text(id));
+ else
+ {
+ m_connection->setSlot(m_sigSlotMenu->text(id));
+ kdDebug() << "Finished creating the connection: sender=" << m_connection->sender() << "; signal=" << m_connection->signal() <<
+ "; receiver=" << m_connection->receiver() << "; slot=" << m_connection->slot() << endl;
+ emit connectionCreated(activeForm(), *m_connection);
+ stopCreatingConnection();
+ }
+ }
+ else if(m_menuWidget)
+ emit createFormSlot(m_active, m_menuWidget->name(), m_popup->text(id));
+}
+
+void
+FormManager::slotConnectionCreated(Form *form, Connection &connection)
+{
+ if (m_options & HideSignalSlotConnections)
+ return;
+ if(!form)
+ return;
+
+ Connection *c = new Connection(connection);
+ form->connectionBuffer()->append(c);
+}
+
+void
+FormManager::layoutHBox()
+{
+ createLayout(Container::HBox);
+}
+
+void
+FormManager::layoutVBox()
+{
+ createLayout(Container::VBox);
+}
+
+void
+FormManager::layoutGrid()
+{
+ createLayout(Container::Grid);
+}
+
+void
+FormManager::layoutHSplitter()
+{
+ createLayout(Container::HSplitter);
+}
+
+void
+FormManager::layoutVSplitter()
+{
+ createLayout(Container::VSplitter);
+}
+
+void
+FormManager::layoutHFlow()
+{
+ createLayout(Container::HFlow);
+}
+
+void
+FormManager::layoutVFlow()
+{
+ createLayout(Container::VFlow);
+}
+
+void
+FormManager::createLayout(int layoutType)
+{
+ WidgetList *list = m_active->selectedWidgets();
+ // if only one widget is selected (a container), we modify its layout
+ if (list->isEmpty()) {//sanity check
+ kdWarning() << "FormManager::createLayout(): list is empty!" << endl;
+ return;
+ }
+ if(list->count() == 1)
+ {
+ ObjectTreeItem *item = m_active->objectTree()->lookup(list->first()->name());
+ if(!item || !item->container() || !m_propSet->contains("layout"))
+ return;
+ (*m_propSet)["layout"] = Container::layoutTypeToString(layoutType);
+ return;
+ }
+
+ QWidget *parent = list->first()->parentWidget();
+ for(QWidget *w = list->first(); w; w = list->next())
+ {
+ kdDebug() << "comparing widget " << w->name() << " whose parent is " << w->parentWidget()->name() << " insteaed of " << parent->name() << endl;
+ if(w->parentWidget() != parent)
+ {
+ KMessageBox::sorry(m_active->widget()->topLevelWidget(), i18n("<b>Cannot create the layout.</b>\n"
+ "All selected widgets must have the same parent."));
+ kdDebug() << "FormManager::createLayout() widgets don't have the same parent widget" << endl;
+ return;
+ }
+ }
+
+ KCommand *com = new CreateLayoutCommand(layoutType, *list, m_active);
+ m_active->addCommand(com, true);
+}
+
+void
+FormManager::breakLayout()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ Container *container = activeForm()->activeContainer();
+ QCString c( container->widget()->className() );
+
+ if((c == "Grid") || (c == "VBox") || (c == "HBox") || (c == "HFlow") || (c == "VFlow"))
+ {
+ KCommand *com = new BreakLayoutCommand(container);
+ m_active->addCommand(com, true);
+ }
+ else // normal container
+ {
+ if(activeForm()->selectedWidgets()->count() == 1)
+ (*m_propSet)["layout"] = "NoLayout";
+ else
+ container->setLayout(Container::NoLayout);
+ }
+}
+
+void
+FormManager::showPropertySet(WidgetPropertySet *set, bool forceReload, const QCString& propertyToSelect)
+{
+ if (m_objectBlockingPropertyEditorUpdating)
+ return;
+
+/*unused if(m_editor) {
+ if (propertyToSelect.isEmpty() && forceReload)
+ m_editor->changeSet(set ? set->set() : 0, propertyToSelect);
+ else
+ m_editor->changeSet(set ? set->set() : 0);
+ }*/
+
+ emit propertySetSwitched(set ? set->set(): 0, /*preservePrevSelection*/forceReload, propertyToSelect);
+}
+
+void
+FormManager::blockPropertyEditorUpdating(void *blockingObject)
+{
+ if (!blockingObject || m_objectBlockingPropertyEditorUpdating)
+ return;
+ m_objectBlockingPropertyEditorUpdating = blockingObject;
+}
+
+void
+FormManager::unblockPropertyEditorUpdating(void *blockingObject, WidgetPropertySet *set)
+{
+ if (!blockingObject || m_objectBlockingPropertyEditorUpdating!=blockingObject)
+ return;
+
+ m_objectBlockingPropertyEditorUpdating = 0;
+ showPropertySet(set, true/*forceReload*/);
+}
+
+void
+FormManager::editTabOrder()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+ QWidget *topLevel = m_active->widget()->topLevelWidget();
+ TabStopDialog dlg(topLevel);
+ //const bool oldAutoTabStops = m_active->autoTabStops();
+ if (dlg.exec(m_active) == QDialog::Accepted) {
+ //inform about changing "autoTabStop" property
+ // -- this will be received eg. by Kexi, so custom "autoTabStop" property can be updated
+ emit autoTabStopsSet(m_active, dlg.autoTabStops());
+ //force set dirty
+ emit dirty(m_active, true);
+ }
+}
+
+void
+FormManager::slotStyle()
+{
+ if(!activeForm())
+ return;
+
+ KSelectAction *m_style = (KSelectAction*)m_collection->action("change_style", "KSelectAction");
+ QString style = m_style->currentText();
+ activeForm()->widget()->setStyle( style);
+
+ QObjectList *l = activeForm()->widget()->queryList( "QWidget" );
+ for(QObject *o = l->first(); o; o = l->next())
+ (static_cast<QWidget*>(o))->setStyle( style );
+ delete l;
+}
+
+void
+FormManager::editFormPixmapCollection()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ PixmapCollectionEditor dialog(activeForm()->pixmapCollection(), activeForm()->widget()->topLevelWidget());
+ dialog.exec();
+}
+
+void
+FormManager::editConnections()
+{
+ if (m_options & HideSignalSlotConnections)
+ return;
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ ConnectionDialog dialog(activeForm()->widget()->topLevelWidget());
+ dialog.exec(activeForm());
+}
+
+void
+FormManager::alignWidgets(int type)
+{
+ if(!activeForm() || !activeForm()->objectTree() || (activeForm()->selectedWidgets()->count() < 2))
+ return;
+
+ QWidget *parentWidget = activeForm()->selectedWidgets()->first()->parentWidget();
+
+ for(QWidget *w = activeForm()->selectedWidgets()->first(); w; w = activeForm()->selectedWidgets()->next())
+ {
+ if(w->parentWidget() != parentWidget)
+ {
+ kdDebug() << "FormManager::alignWidgets() type ==" << type << " widgets don't have the same parent widget" << endl;
+ return;
+ }
+ }
+
+ KCommand *com = new AlignWidgetsCommand(type, *(activeForm()->selectedWidgets()), activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::alignWidgetsToLeft()
+{
+ alignWidgets(AlignWidgetsCommand::AlignToLeft);
+}
+
+void
+FormManager::alignWidgetsToRight()
+{
+ alignWidgets(AlignWidgetsCommand::AlignToRight);
+}
+
+void
+FormManager::alignWidgetsToTop()
+{
+ alignWidgets(AlignWidgetsCommand::AlignToTop);
+}
+
+void
+FormManager::alignWidgetsToBottom()
+{
+ alignWidgets(AlignWidgetsCommand::AlignToBottom);
+}
+
+void
+FormManager::adjustWidgetSize()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ KCommand *com = new AdjustSizeCommand(AdjustSizeCommand::SizeToFit, *(activeForm()->selectedWidgets()), activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::alignWidgetsToGrid()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ KCommand *com = new AlignWidgetsCommand(AlignWidgetsCommand::AlignToGrid, *(activeForm()->selectedWidgets()), activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::adjustSizeToGrid()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ KCommand *com = new AdjustSizeCommand(AdjustSizeCommand::SizeToGrid, *(activeForm()->selectedWidgets()), activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::adjustWidthToSmall()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ KCommand *com = new AdjustSizeCommand(AdjustSizeCommand::SizeToSmallWidth, *(activeForm()->selectedWidgets()), activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::adjustWidthToBig()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ KCommand *com = new AdjustSizeCommand(AdjustSizeCommand::SizeToBigWidth, *(activeForm()->selectedWidgets()), activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::adjustHeightToSmall()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ KCommand *com = new AdjustSizeCommand(AdjustSizeCommand::SizeToSmallHeight, *(activeForm()->selectedWidgets()), activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::adjustHeightToBig()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ KCommand *com = new AdjustSizeCommand(AdjustSizeCommand::SizeToBigHeight, *(activeForm()->selectedWidgets()), activeForm());
+ activeForm()->addCommand(com, true);
+}
+
+void
+FormManager::bringWidgetToFront()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ for(QWidget *w = activeForm()->selectedWidgets()->first(); w; w = activeForm()->selectedWidgets()->next())
+ w->raise();
+}
+
+void
+FormManager::sendWidgetToBack()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ for(QWidget *w = activeForm()->selectedWidgets()->first(); w; w = activeForm()->selectedWidgets()->next())
+ w->lower();
+}
+
+void
+FormManager::selectAll()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ activeForm()->selectFormWidget();
+ uint count = activeForm()->objectTree()->children()->count();
+ for(ObjectTreeItem *it = activeForm()->objectTree()->children()->first(); it;
+ it = activeForm()->objectTree()->children()->next(), count--)
+ {
+ activeForm()->setSelectedWidget(it->widget(), /*add*/true, /*raise*/false, /*moreWillBeSelected*/count>1);
+ }
+}
+
+void
+FormManager::clearWidgetContent()
+{
+ if(!activeForm() || !activeForm()->objectTree())
+ return;
+
+ for(QWidget *w = activeForm()->selectedWidgets()->first(); w; w = activeForm()->selectedWidgets()->next())
+ activeForm()->library()->clearWidgetContent(w->className(), w);
+}
+
+void
+FormManager::deleteWidgetLater( QWidget *w )
+{
+ w->hide();
+ w->reparent(0, WType_TopLevel, QPoint(0,0));
+ m_deleteWidgetLater_list.append( w );
+ m_deleteWidgetLater_timer.start( 100, true );
+}
+
+void
+FormManager::deleteWidgetLaterTimeout()
+{
+ m_deleteWidgetLater_list.clear();
+}
+
+void
+FormManager::showFormUICode()
+{
+#ifdef KEXI_DEBUG_GUI
+ if(!activeForm())
+ return;
+
+ QString uiCode;
+ if (!FormIO::saveFormToString(activeForm(), uiCode, 3)) {
+ //! @todo show err?
+ return;
+ }
+
+ if (!m_uiCodeDialog) {
+ m_uiCodeDialog = new KDialogBase(0, "uiwindow", true, i18n("Form's UI Code"),
+ KDialogBase::Close, KDialogBase::Close);
+ m_uiCodeDialog->resize(700, 600);
+ QVBox *box = m_uiCodeDialog->makeVBoxMainWidget();
+ KTabWidget* tab = new KTabWidget(box);
+
+ m_currentUICodeDialogEditor = new KTextEdit(QString::null, QString::null, tab);
+ tab->addTab( m_currentUICodeDialogEditor, i18n("Current"));
+ m_currentUICodeDialogEditor->setReadOnly(true);
+ QFont f( m_currentUICodeDialogEditor->font() );
+ f.setFamily("courier");
+ m_currentUICodeDialogEditor->setFont(f);
+ m_currentUICodeDialogEditor->setTextFormat(Qt::PlainText);
+
+ m_originalUICodeDialogEditor = new KTextEdit(QString::null, QString::null, tab);
+ tab->addTab( m_originalUICodeDialogEditor, i18n("Original"));
+ m_originalUICodeDialogEditor->setReadOnly(true);
+ m_originalUICodeDialogEditor->setFont(f);
+ m_originalUICodeDialogEditor->setTextFormat(Qt::PlainText);
+ }
+ m_currentUICodeDialogEditor->setText( uiCode );
+ //indent and set our original doc as well:
+ QDomDocument doc;
+ doc.setContent( activeForm()->m_recentlyLoadedUICode );
+ m_originalUICodeDialogEditor->setText( doc.toString( 3 ) );
+ m_uiCodeDialog->show();
+#endif
+}
+
+void
+FormManager::slotSettingsChanged(int category)
+{
+ if (category==KApplication::SETTINGS_SHORTCUTS) {
+ m_contextMenuKey = KGlobalSettings::contextMenuKey();
+ }
+}
+
+void
+FormManager::emitWidgetSelected( KFormDesigner::Form* form, bool multiple )
+{
+ enableFormActions();
+ // Enable edit actions
+ enableAction("edit_copy", true);
+ enableAction("edit_cut", true);
+ enableAction("edit_delete", true);
+ enableAction("clear_contents", true);
+
+ // 'Align Widgets' menu
+ enableAction("align_menu", multiple);
+ enableAction("align_to_left", multiple);
+ enableAction("align_to_right", multiple);
+ enableAction("align_to_top", multiple);
+ enableAction("align_to_bottom", multiple);
+
+ enableAction("adjust_size_menu", true);
+ enableAction("adjust_width_small", multiple);
+ enableAction("adjust_width_big", multiple);
+ enableAction("adjust_height_small", multiple);
+ enableAction("adjust_height_big", multiple);
+
+ enableAction("format_raise", true);
+ enableAction("format_lower", true);
+
+ WidgetList *wlist = form->selectedWidgets();
+ bool fontEnabled = false;
+ for (WidgetListIterator it(*wlist); it.current(); ++it) {
+ if (-1 != it.current()->metaObject()->findProperty("font", true)) {
+ fontEnabled = true;
+ break;
+ }
+ }
+ enableAction("format_font", fontEnabled);
+
+ // If the widgets selected is a container, we enable layout actions
+ bool containerSelected = false;
+ if(!multiple)
+ {
+ KFormDesigner::ObjectTreeItem *item = 0;
+ if (form->selectedWidgets()->first())
+ form->objectTree()->lookup( form->selectedWidgets()->first()->name() );
+ if(item && item->container())
+ containerSelected = true;
+ }
+ const bool twoSelected = form->selectedWidgets()->count()==2;
+ // Layout actions
+ enableAction("layout_menu", multiple || containerSelected);
+ enableAction("layout_hbox", multiple || containerSelected);
+ enableAction("layout_vbox", multiple || containerSelected);
+ enableAction("layout_grid", multiple || containerSelected);
+ enableAction("layout_hsplitter", twoSelected);
+ enableAction("layout_vsplitter", twoSelected);
+
+ KFormDesigner::Container *container = activeForm() ? activeForm()->activeContainer() : 0;
+ if (container)
+ enableAction("break_layout", (container->layoutType() != KFormDesigner::Container::NoLayout));
+
+ emit widgetSelected(form, true);
+}
+
+void
+FormManager::emitFormWidgetSelected( KFormDesigner::Form* form )
+{
+// disableWidgetActions();
+ enableAction("edit_copy", false);
+ enableAction("edit_cut", false);
+ enableAction("edit_delete", false);
+ enableAction("clear_contents", false);
+
+ // Disable format functions
+ enableAction("align_menu", false);
+ enableAction("align_to_left", false);
+ enableAction("align_to_right", false);
+ enableAction("align_to_top", false);
+ enableAction("align_to_bottom", false);
+ enableAction("adjust_size_menu", false);
+ enableAction("format_raise", false);
+ enableAction("format_lower", false);
+
+ enableAction("format_font", false);
+
+ enableFormActions();
+
+ const bool twoSelected = form->selectedWidgets()->count()==2;
+ const bool hasChildren = !form->objectTree()->children()->isEmpty();
+
+ // Layout actions
+ enableAction("layout_menu", hasChildren);
+ enableAction("layout_hbox", hasChildren);
+ enableAction("layout_vbox", hasChildren);
+ enableAction("layout_grid", hasChildren);
+ enableAction("layout_hsplitter", twoSelected);
+ enableAction("layout_vsplitter", twoSelected);
+ enableAction("break_layout", (form->toplevelContainer()->layoutType() != KFormDesigner::Container::NoLayout));
+
+ emit formWidgetSelected( form );
+}
+
+void
+FormManager::emitNoFormSelected()
+{
+ disableWidgetActions();
+
+ // Disable edit actions
+// enableAction("edit_paste", false);
+// enableAction("edit_undo", false);
+// enableAction("edit_redo", false);
+
+ // Disable 'Tools' actions
+ enableAction("pixmap_collection", false);
+ if (!(m_options & HideSignalSlotConnections))
+ enableAction("form_connections", false);
+ enableAction("taborder", false);
+ enableAction("change_style", activeForm()!=0);
+
+ // Disable items in 'File'
+ if (!(m_options & SkipFileActions)) {
+ enableAction("file_save", false);
+ enableAction("file_save_as", false);
+ enableAction("preview_form", false);
+ }
+
+ emit noFormSelected();
+}
+
+void
+FormManager::enableFormActions()
+{
+ // Enable 'Tools' actions
+ enableAction("pixmap_collection", true);
+ if (!(m_options & HideSignalSlotConnections))
+ enableAction("form_connections", true);
+ enableAction("taborder", true);
+ enableAction("change_style", true);
+
+ // Enable items in 'File'
+ if (!(m_options & SkipFileActions)) {
+ enableAction("file_save", true);
+ enableAction("file_save_as", true);
+ enableAction("preview_form", true);
+ }
+
+ enableAction("edit_paste", isPasteEnabled());
+ enableAction("edit_select_all", true);
+}
+
+void
+FormManager::disableWidgetActions()
+{
+ // Disable edit actions
+ enableAction("edit_copy", false);
+ enableAction("edit_cut", false);
+ enableAction("edit_delete", false);
+ enableAction("clear_contents", false);
+
+ // Disable format functions
+ enableAction("align_menu", false);
+ enableAction("align_to_left", false);
+ enableAction("align_to_right", false);
+ enableAction("align_to_top", false);
+ enableAction("align_to_bottom", false);
+ enableAction("adjust_size_menu", false);
+ enableAction("format_raise", false);
+ enableAction("format_lower", false);
+
+ enableAction("layout_menu", false);
+ enableAction("layout_hbox", false);
+ enableAction("layout_vbox", false);
+ enableAction("layout_grid", false);
+ enableAction("layout_hsplitter", false);
+ enableAction("layout_vsplitter", false);
+ enableAction("break_layout", false);
+}
+
+void
+FormManager::emitUndoEnabled(bool enabled, const QString &text)
+{
+ enableAction("edit_undo", enabled);
+ emit undoEnabled(enabled, text);
+}
+
+void
+FormManager::emitRedoEnabled(bool enabled, const QString &text)
+{
+ enableAction("edit_redo", enabled);
+ emit redoEnabled(enabled, text);
+}
+
+void
+FormManager::changeFont()
+{
+ if (!m_active)
+ return;
+ WidgetList *wlist = m_active->selectedWidgets();
+ WidgetList widgetsWithFontProperty;
+ QWidget *widget;
+ QFont font;
+ bool oneFontSelected = true;
+ for (WidgetListIterator it(*wlist); (widget = it.current()); ++it) {
+ if (m_active->library()->isPropertyVisible(widget->className(), widget, "font")) {
+ widgetsWithFontProperty.append(widget);
+ if (oneFontSelected) {
+ if (widgetsWithFontProperty.count()==1)
+ font = widget->font();
+ else if (font != widget->font())
+ oneFontSelected = false;
+ }
+ }
+ }
+ if (widgetsWithFontProperty.isEmpty())
+ return;
+ if (!oneFontSelected) //many different fonts selected: pick a font from toplevel conatiner
+ font = m_active->widget()->font();
+
+ if (1==widgetsWithFontProperty.count()) {
+ //single widget's settings
+ widget = widgetsWithFontProperty.first();
+ KoProperty::Property &fontProp = m_propSet->property("font");
+ if (QDialog::Accepted != KFontDialog::getFont(font, false, m_active->widget()))
+ return;
+ fontProp = font;
+ return;
+ }
+ //multiple widgets
+ int diffFlags=0;
+ if (QDialog::Accepted != KFontDialog::getFontDiff(font, diffFlags, false, m_active->widget())
+ || 0==diffFlags)
+ return;
+ //update font
+ for (WidgetListIterator it(widgetsWithFontProperty); (widget = it.current()); ++it) {
+ QFont prevFont( widget->font() );
+ if (diffFlags & KFontChooser::FontDiffFamily)
+ prevFont.setFamily( font.family() );
+ if (diffFlags & KFontChooser::FontDiffStyle) {
+ prevFont.setBold( font.bold() );
+ prevFont.setItalic( font.italic() );
+ }
+ if (diffFlags & KFontChooser::FontDiffSize)
+ prevFont.setPointSize( font.pointSize() );
+/*! @todo this modification is not added to UNDO BUFFER:
+ do it when KoProperty::Set supports multiple selections */
+ widget->setFont( prevFont );
+ //temporary fix for dirty flag:
+ emit dirty(m_active, true);
+ }
+}
+
+#include "formmanager.moc"
diff --git a/kexi/formeditor/formmanager.h b/kexi/formeditor/formmanager.h
new file mode 100644
index 000000000..48e00b0fe
--- /dev/null
+++ b/kexi/formeditor/formmanager.h
@@ -0,0 +1,496 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef FORMMANAGER_H
+#define FORMMANAGER_H
+
+#include <qobject.h>
+#include <qdom.h>
+#include <qptrlist.h>
+#include <qtimer.h>
+#include <qguardedptr.h>
+#include <qstringlist.h>
+
+class QWidget;
+class QWorkspace;
+class KPopupMenu;
+class KActionCollection;
+class KAction;
+class KToggleAction;
+class KDialogBase;
+class KTextEdit;
+class KXMLGUIClient;
+class KMainWindow;
+
+namespace KoProperty {
+ class Editor;
+ class Set;
+ class Property;
+ class Widget;
+}
+
+namespace KFormDesigner {
+
+class WidgetPropertySet;
+class Form;
+class Container;
+class WidgetLibrary;
+class ObjectTreeView;
+class Connection;
+class FormManager;
+typedef QPtrList<KAction> ActionList;
+
+//! @internal
+//static FormManager* FormManager_static = 0;
+
+//! A class to manage (create/load/save) Forms
+/** This is Form Designer's main class, which is used by external APIs to access FormDesigner.
+ This is the class you have to use to integrate FormDesigner into another program.
+ It deals with creating, saving and loading Form, as well as widget insertion and copying.
+ It also ensures all the components (ObjectTreeView, Form and PropertyEditor) are synced,
+ and link them.
+ It holds the WidgetLibrary, the WidgetPropertySet, links to ObjectTreeView and PropertyEditor,
+ as well as the copied widget and the insert state.
+ **/
+class KFORMEDITOR_EXPORT FormManager : public QObject
+{
+ Q_OBJECT
+
+ public:
+ /*! Constructs FormManager object.
+ Using \a options you can control manager's behaviour, see Options. */
+ FormManager(QObject *parent = 0, int options = 0, const char *name = 0);
+
+ virtual ~FormManager();
+
+ //! Creates widget library for supportedFactoryGroups
+ //! and initializes FormManager singleton. \a m should be always the same for every call.
+ static WidgetLibrary* createWidgetLibrary(FormManager* m,
+ const QStringList& supportedFactoryGroups);
+
+ //! Access to FormManager singleton
+ static FormManager* self();
+
+ /*! Options for creating FormManager objects.
+ * These are really bit-flags and may be or-ed together.
+ */
+ enum Options { HideEventsInPopupMenu = 1, SkipFileActions = 2,
+ HideSignalSlotConnections = 4 }; //todo
+
+ /*! Creates all the KActions related to widget insertion, and plug them
+ into the \a collection. \a client XML GUI client is used to call
+ lib->addCustomWidgetActions(client).
+ These actions are automatically connected to \ref insertWidget() slot.
+ \return a QPtrList of the created actions.
+ */
+ ActionList createActions(WidgetLibrary *lib, KActionCollection* collection, KXMLGUIClient *client);
+
+ /*! Enables or disables actions \a name.
+ KFD uses KPart's, action collection here.
+ Kexi implements this to get (shared) actions defined elsewhere. */
+ virtual void enableAction( const char* name, bool enable ) = 0;
+
+ /*! \return action for \a name. @see enableAction() */
+ virtual KAction* action(const char* name) = 0;
+
+ bool isPasteEnabled();
+
+// //! \return A pointer to the WidgetLibrary owned by this Manager.
+// WidgetLibrary* lib() const { return m_lib; }
+
+ //! \return A pointer to the WidgetPropertySet owned by this Manager.
+ WidgetPropertySet* propertySet() const { return m_propSet; }
+
+ /*! \return true if one of the insert buttons was pressed and the forms
+ are ready to create a widget. */
+ bool isInserting() const { return m_inserting; }
+
+ /*! \return The name of the class being inserted, corresponding
+ to the menu item or the toolbar button clicked. */
+ QCString selectedClass() const { return m_selectedClass; }
+
+ /*! Sets the point where the pasted widget should be moved to. */
+ void setInsertPoint(const QPoint &p);
+
+ //! \return If we are creating a Connection by drag-and-drop or not.
+ bool isCreatingConnection() { return m_drawingSlot; }
+
+ //! \return the Connection being created.
+ Connection* createdConnection() { return m_connection; }
+
+ /*! Resets the Connection being created. We stay in Connection creation mode,
+ but we start a new connection (when the user clicks
+ outside of signals/slots menu). */
+ void resetCreatedConnection();
+
+ //! Creates and display a menu with all the signals of widget \a w.
+ void createSignalMenu(QWidget *w);
+
+ //! Creates and display a menu with all the slots of widget \a w.
+ void createSlotMenu(QWidget *w);
+
+ //! Emits the signal \ref createFormSlot(). Used by WidgetPropertySet.
+ void emitCreateSlot(const QString &widget, const QString &value)
+ { emit createFormSlot(m_active, widget, value); }
+
+ /*! \return The Form actually active and focused.
+ */
+ Form* activeForm() const;
+
+ /*! \return the Form whose toplevel widget is \a w, or 0
+ if there is not or the Form is in preview mode. */
+ Form* formForWidget(QWidget *w);
+
+ /*! \return true if \a w is a toplevel widget,
+ ie. it is the main widget of a Form (so it should have a caption ,
+ an icon ...) */
+ bool isTopLevel(QWidget *w);
+
+ //! \return A pointer to the KoProperty::Editor we use.
+ //unused KoProperty::Editor* propertyEditor() const { return m_editor; }
+
+ /*! Shows a property set \a set in a Property Editor.
+ If \a buff is 0, Property Editor will be cleared.
+ If \a forceReload is true, the set will be reloaded even
+ if it's the same as previous one.
+ If \a propertyToSelect is not empty, an item for this name will be selected
+ (usable when previously there was no set visible). */
+ virtual void showPropertySet(WidgetPropertySet *set, bool forceReload = false,
+ const QCString& propertyToSelect = QCString());
+
+ void blockPropertyEditorUpdating(void *blockingObject);
+
+ void unblockPropertyEditorUpdating(void *blockingObject, WidgetPropertySet *set);
+
+ /*! Sets the external property editor pane used by FormDesigner (it may be docked).*/
+ void setEditor(KoProperty::Editor *editor);
+
+ /*! Sets the external object tree view used by FormDesigner (it may be docked).
+ This function also connects appropriate signals and slots to ensure
+ sync with the current Form. */
+ void setObjectTreeView(ObjectTreeView *treeview);
+
+ /*! Previews the Form \a form using the widget \a w as toplevel container for this Form. */
+ void previewForm(Form *form, QWidget *w, Form *toForm=0);
+
+ /*! Adds a existing form w and changes it to a container */
+ void importForm(Form *form=0, bool preview=false);
+
+ /*! Deletes the Form \a form and removes it from our list. */
+ void deleteForm(Form *form);
+
+ /*! This function creates and displays the context menu corresponding to the widget \a w.
+ The menu item are disabled if necessary, and
+ the widget specific part is added (menu from the factory and buddy selection). */
+ void createContextMenu(QWidget *w, Container *container, bool popupAtCursor = true);
+
+ //! \return If we align widgets to grid or not.
+ bool snapWidgetsToGrid();
+
+ //! @internal used by Container
+ int contextMenuKey() const { return m_contextMenuKey; }
+
+ //! @internal
+ void emitWidgetSelected( KFormDesigner::Form* form, bool multiple );
+ //! @internal
+ void emitFormWidgetSelected( KFormDesigner::Form* form );
+ //! @internal
+ void emitNoFormSelected();
+
+ /*! @internal
+ \return true is redo action is being executed.
+ Used in WidgetPropertySet::slotPropertyChanged() */
+ bool isRedoing() const { return m_isRedoing; }
+
+ public slots:
+ /*! Deletes the selected widget in active Form and all of its children. */
+ void deleteWidget();
+
+ /*! Copies the slected widget and all its children of the active Form using an XML representation. */
+ void copyWidget();
+
+ /*! Cuts (ie Copies and deletes) the selected widget and all its children of
+ the active Form using an XML representation. */
+ void cutWidget();
+
+ /*! Pastes the XML representation of the copied or cut widget. The widget is
+ pasted when the user clicks the Form to
+ indicate the new position of the widget, or at the position of the contextual menu if there is one. */
+ void pasteWidget();
+
+ /*! Selects all toplevel widgets in trhe current form. */
+ void selectAll();
+
+ /*! Clears the contents of the selected widget(s) (eg for a line edit or a listview). */
+ void clearWidgetContent();
+
+ void undo();
+ void redo();
+
+ /*! Displays a dialog where the user can modify the tab order of the active Form,
+ by drag-n-drop or using up/down buttons. */
+ void editTabOrder();
+
+ /*! Adjusts the size of the selected widget, ie resize it to its size hint. */
+ void adjustWidgetSize();
+
+ /*! Creates a dialog to edit the \ref activeForm() PixmapCollection. */
+ void editFormPixmapCollection();
+
+ /*! Creates a dialog to edit the Connection of \ref activeForm(). */
+ void editConnections();
+
+ //! Lay out selected widgets using HBox layout (calls \ref CreateLayoutCommand).
+ void layoutHBox();
+ //! Lay out selected widgets using VBox layout.
+ void layoutVBox();
+ //! Lay out selected widgets using Grid layout.
+ void layoutGrid();
+ //! Lay out selected widgets in an horizontal splitter
+ void layoutHSplitter();
+ //! Lay out selected widgets in a verticak splitter
+ void layoutVSplitter();
+ //! Lay out selected widgets using HFlow layout
+ void layoutHFlow();
+ //! Lay out selected widgets using VFlow layout.
+ void layoutVFlow();
+
+ //! Breaks selected layout(calls \ref BreakLayoutCommand).
+ void breakLayout();
+
+ void alignWidgetsToLeft();
+ void alignWidgetsToRight();
+ void alignWidgetsToTop();
+ void alignWidgetsToBottom();
+ void alignWidgetsToGrid();
+
+ void adjustSizeToGrid();
+
+ //! Resize all selected widgets to the width of the narrowest widget.
+ void adjustWidthToSmall();
+
+ //! Resize all selected widgets to the width of the widest widget.
+ void adjustWidthToBig();
+
+ //! Resize all selected widgets to the height of the shortest widget.
+ void adjustHeightToSmall();
+
+ //! Resize all selected widgets to the height of the tallest widget.
+ void adjustHeightToBig();
+
+ void bringWidgetToFront();
+ void sendWidgetToBack();
+
+ /*! This slot is called when the user presses a "Widget" toolbar button
+ or a "Widget" menu item. Prepares all Forms for
+ creation of a new widget (ie changes cursor ...).
+ */
+ void insertWidget(const QCString &classname);
+
+ /*! Stops the current widget insertion (ie unset the cursor ...). */
+ void stopInsert();
+
+ //! Slot called when the user presses 'Pointer' icon. Switch to Default mode.
+ void slotPointerClicked();
+
+ //! Enter the Connection creation mode.
+ void startCreatingConnection();
+
+ //! Leave the Connection creation mode.
+ void stopCreatingConnection();
+
+ /*! Calls this slot when the window activated changes (eg connect
+ to QWorkspace::windowActivated(QWidget*)). You <b>need</b> to connect
+ to this slot, it will crash otherwise.
+ */
+ void windowChanged(QWidget *w);
+
+ //! Used to delayed widgets' deletion (in Container::deleteItem())
+ void deleteWidgetLater( QWidget *w );
+
+ /*! For debugging purposes only:
+ shows a text window containing contents of .ui XML definition of the current form. */
+ void showFormUICode();
+
+ /*! Executes font dialog and changes it for currently selected widget(s). */
+ void changeFont();
+
+ signals:
+ /*! This signal is emitted as the property set switched.
+ If \a forceReload is true, the set needs to be reloaded even
+ if it's the same as previous one. */
+ void propertySetSwitched(KoProperty::Set *set, bool forceReload = false, const QCString& propertyToSelect = QCString());
+
+ /*! This signal is emitted when any change is made to the Form \a form,
+ so it will need to be saved. */
+ void dirty(KFormDesigner::Form *form, bool isDirty=true);
+
+ /*! Signal emitted when a normal widget is selected inside \a form
+ (ie not form widget). If \a multiple is true,
+ then more than one widget is selected. Use this to update actions state. */
+ void widgetSelected(KFormDesigner::Form *form, bool multiple);
+
+ /*! Signal emitted when the form widget is selected inside \a form.
+ Use this to update actions state. */
+ void formWidgetSelected(KFormDesigner::Form *form);
+
+ /*! Signal emitted when no form (or a preview form) is selected.
+ Use this to update actions state. */
+ void noFormSelected();
+
+ /*! Signal emitted when undo action activation changes.
+ \a text is the full text of the action (including command name). */
+ void undoEnabled(bool enabled, const QString &text = QString::null);
+
+ /*! Signal emitted when redo action activation changes.
+ \a text is the full text of the action (including command name). */
+ void redoEnabled(bool enabled, const QString &text = QString::null);
+
+ /*! Signal emitted when the user choose a signal in 'Events' menu
+ in context menu, or in 'Events' in property editor.
+ The code editor should then create the slot connected to this signal. */
+ void createFormSlot(KFormDesigner::Form *form, const QString &widget, const QString &signal);
+
+ /*! Signal emitted when the Connection creation by drag-and-drop ends.
+ \a connection is the created Connection. You should copy it,
+ because it is deleted just after the signal is emitted. */
+ void connectionCreated(KFormDesigner::Form *form, KFormDesigner::Connection &connection);
+
+ /*! Signal emitted when the Connection creation by drag-and-drop is aborted by user. */
+ void connectionAborted(KFormDesigner::Form *form);
+
+ /*! Signal emitted when "autoTabStops" is changed. */
+ void autoTabStopsSet(KFormDesigner::Form *form, bool set);
+
+ /*! Signal emitted before the form gets finally deleted. \a form is still a valid pointer,
+ but the widgets inside the form are in unknown state. */
+ void aboutToDeleteForm(KFormDesigner::Form *form);
+
+ /*! Signal emitted when new form gets created. */
+ void formCreated(KFormDesigner::Form *form);
+
+ protected slots:
+ void deleteWidgetLaterTimeout();
+
+ /*! Slot called when a buddy is chosen in the buddy list. Sets the label buddy. */
+ void buddyChosen(int id);
+
+ /*! Slot called when the user chooses an item in signal (or slot) menu.
+ The \ref createdConnection() is updated, and the connection created
+ (for the signal menu). */
+ void menuSignalChosen(int id);
+
+ /*! Slot called when the user changes current style using combbox in toolbar or menu. */
+ void slotStyle();
+
+ void slotConnectionCreated(KFormDesigner::Form*, KFormDesigner::Connection&);
+
+ void slotSettingsChanged(int category);
+
+ protected:
+ /*! Inits the Form, adds it to m_forms, and conects slots. */
+ void initForm(Form *form);
+
+#if 0
+ /*! Default implementation just calls FormIO::loadFormFromDom().
+ Change this if you need to handle, eg. custom UI XML tags, as in Kexi's Form Designer. */
+ virtual bool loadFormFromDomInternal(Form *form, QWidget *container, QDomDocument &inBuf);
+
+ /*! Default implementation just calls FormIO::saveFormToString().
+ Change this if you need to handle, eg. custom UI XML tags, as in Kexi's Form Designer. */
+ virtual bool saveFormToStringInternal(Form *form, QString &dest, int indent = 0);
+#endif
+ /*! Function called by the "Lay out in..." menu items. It creates a layout from the
+ currently selected widgets (that must have the same parent).
+ Calls \ref CreateLayoutCommand. */
+ void createLayout(int layoutType);
+
+ /*! Function called by all other AlignWidgets*() function. Calls \ref AlignWidgetsCommand. */
+ void alignWidgets(int type);
+
+ void enableFormActions();
+ void disableWidgetActions();
+ void emitUndoEnabled(bool enabled, const QString &text);
+ void emitRedoEnabled(bool enabled, const QString &text);
+
+ /*! True if emitSelectionSignals() updates property set so showPropertySet() will
+ not be needed in windowChanged(). False by default. Set to true in KexiFormManager. */
+ bool m_emitSelectionSignalsUpdatesPropertySet : 1;
+
+ private:
+ static FormManager* _self;
+
+ //! Enum for menu items indexes
+ enum { MenuTitle = 200, MenuCopy, MenuCut, MenuPaste, MenuDelete, MenuHBox = 301,
+ MenuVBox, MenuGrid, MenuHSplitter, MenuVSplitter, MenuNoBuddy = 501 };
+
+ WidgetPropertySet *m_propSet;
+// WidgetLibrary *m_lib;
+ QGuardedPtr<KoProperty::Editor> m_editor;
+ QGuardedPtr<ObjectTreeView> m_treeview;
+ // Forms
+ QPtrList<Form> m_forms;
+ QPtrList<Form> m_preview;
+ QGuardedPtr<Form> m_active;
+
+ // Copy/Paste
+ QDomDocument m_domDoc;
+ KPopupMenu *m_popup;
+ QPoint m_insertPoint;
+ QGuardedPtr<QWidget> m_menuWidget;
+
+ // Insertion
+ bool m_inserting;
+ QCString m_selectedClass;
+
+ // Connection stuff
+ bool m_drawingSlot;
+ Connection *m_connection;
+ KPopupMenu *m_sigSlotMenu;
+
+ // Actions
+ KActionCollection *m_collection;
+ KToggleAction *m_pointer, *m_dragConnection, *m_snapToGrid;
+
+ //! Used to delayed widgets deletion
+ QTimer m_deleteWidgetLater_timer;
+ QPtrList<QWidget> m_deleteWidgetLater_list;
+
+#ifdef KEXI_DEBUG_GUI
+ KDialogBase *m_uiCodeDialog;
+ KTextEdit *m_currentUICodeDialogEditor;
+ KTextEdit *m_originalUICodeDialogEditor;
+#endif
+
+ int m_options; //!< @see Options enum
+ int m_contextMenuKey; //!< Id of context menu key (cached)
+
+ void *m_objectBlockingPropertyEditorUpdating;
+ bool m_isRedoing : 1;
+
+ friend class PropertyCommand;
+ friend class GeometryPropertyCommand;
+ friend class CutWidgetCommand;
+ friend class Form;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/kdevelop_plugin/Makefile.am b/kexi/formeditor/kdevelop_plugin/Makefile.am
new file mode 100644
index 000000000..62507e9ce
--- /dev/null
+++ b/kexi/formeditor/kdevelop_plugin/Makefile.am
@@ -0,0 +1,21 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+INCLUDES = -I$(top_srcdir)/kexi/formeditor -I$(top_srcdir)/kexi/core $(all_includes)
+METASOURCES = AUTO
+
+# KFormDesigner KDevelop plugin
+kde_module_LTLIBRARIES = libkformdesigner_kdev_part.la
+
+libkformdesigner_kdev_part_la_SOURCES = kfd_kdev_part.cpp
+libkformdesigner_kdev_part_la_LDFLAGS = -module $(KDE_PLUGIN) $(VER_INFO) $(all_libraries)
+libkformdesigner_kdev_part_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la \
+ -lkinterfacedesigner $(LIB_KFILE)
+
+# this is where the desktop file will go
+partdesktopdir = $(kde_servicesdir)
+partdesktop_DATA = kformdesigner_kdev_part.desktop
+
+# this is where the part's XML-GUI resource file goes
+partrcdir = $(kde_datadir)/kformdesigner_kdev_part
+partrc_DATA = kformdesigner_part.rc kformdesigner_part_shell.rc
+
diff --git a/kexi/formeditor/kdevelop_plugin/kfd_kdev_part.cpp b/kexi/formeditor/kdevelop_plugin/kfd_kdev_part.cpp
new file mode 100644
index 000000000..c3b6e4488
--- /dev/null
+++ b/kexi/formeditor/kdevelop_plugin/kfd_kdev_part.cpp
@@ -0,0 +1,694 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qworkspace.h>
+#include <qdockarea.h>
+#include <qdockwindow.h>
+#include <qhbox.h>
+#include <qpainter.h>
+#include <qevent.h>
+#include <qobjectlist.h>
+
+#include <kdeversion.h>
+#include <kaction.h>
+#include <kinstance.h>
+#include <klocale.h>
+#include <kaboutdata.h>
+#include <kdebug.h>
+#include <kstdaction.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kfiledialog.h>
+#include <klibloader.h>
+#include <kmessagebox.h>
+
+#include "form.h"
+#include "formIO.h"
+#include "objecttree.h"
+#include "container.h"
+#include "formmanager.h"
+#include "objecttreeview.h"
+
+#include "kfd_kdev_part.h"
+
+#define ENABLE_ACTION(name, enable) \
+ if(actionCollection()->action( name )) \
+ actionCollection()->action( name )->setEnabled( enable )
+
+KInstance *KFDFactory::m_instance = 0L;
+
+KFDFactory::KFDFactory()
+: KParts::Factory(0, "libkformdesigner_kdev_part")
+{}
+
+KFDFactory::~KFDFactory()
+{
+ if (m_instance)
+ {
+ delete m_instance->aboutData();
+ delete m_instance;
+ }
+
+ m_instance = 0;
+}
+
+KParts::Part*
+KFDFactory::createPartObject( QWidget *parentWidget, const char *, QObject *, const char *name,
+ const char *classname, const QStringList &args)
+{
+ bool readOnly = (classname == "KParts::ReadOnlyPart");
+ KFormDesignerKDevPart *part = new KFormDesignerKDevPart(parentWidget, name, readOnly, args);
+ return part;
+}
+
+KInstance*
+KFDFactory::instance()
+{
+ if (!m_instance)
+ m_instance = new KInstance(aboutData());
+ return m_instance;
+}
+
+KAboutData*
+KFDFactory::aboutData()
+{
+ KAboutData *about = new KAboutData("kformdesigner_kdev_part", I18N_NOOP("Form Designer Part"), "0.3");
+ return about;
+}
+
+// copied from kfd_part.cpp
+class KFDPart_FormManager : public KFormDesigner::FormManager
+{
+ public:
+ /*! Constructs FormManager object.
+ See WidgetLibrary's constructor documentation for information about
+ \a supportedFactoryGroups parameter.
+ Using \a options you can control manager's behaviour, see \ref Options. */
+ KFDPart_FormManager(KFormDesignerPart *part, int options = 0, const char *name = 0)
+ : KFormDesigner::FormManager(part, options, name)
+ , m_part(part)
+ {
+ }
+
+ virtual KAction* action( const char* name)
+ {
+ return m_part->actionCollection()->action( name );
+ }
+
+ virtual void enableAction( const char* name, bool enable ) {
+ if(m_part->actionCollection()->action( name ))
+ m_part->actionCollection()->action( name )->setEnabled( enable );
+ }
+
+ KFormDesignerPart *m_part;
+};
+
+//////////////
+
+KFormDesigner::WidgetLibrary* KFormDesignerKDevPart::static_formsLibrary = 0L;
+
+KFormDesignerKDevPart::KFormDesignerKDevPart(QWidget *parent, const char *name, bool readOnly, const QStringList &args)
+: Designer(parent, name), m_count(0)
+{
+ setInstance(KFDFactory::instance());
+ instance()->iconLoader()->addAppDir("kexi");
+ instance()->iconLoader()->addAppDir("kformdesigner");
+
+ setReadWrite(!readOnly);
+ m_uniqueFormMode = true;
+ m_openingFile = false;
+
+ if(!args.grep("multipleMode").isEmpty())
+ setUniqueFormMode(false);
+ m_inShell = (!args.grep("shell").isEmpty());
+
+ QHBox *container = new QHBox(parent, "kfd_container_widget");
+ container->setFocusPolicy(QWidget::ClickFocus);
+
+ m_workspace = new QWorkspace(container, "kfd_workspace");
+ m_workspace->show();
+ QStringList supportedFactoryGroups;
+/* @todo add configuration for supported factory groups */
+ static_formsLibrary = KFormDesigner::FormManager::createWidgetLibrary(
+ new KFDPart_FormManager(this, 0, "kfd_manager"), supportedFactoryGroups );
+
+ if(!readOnly)
+ {
+ QDockArea *dockArea = new QDockArea(Vertical, QDockArea::Reverse, container, "kfd_part_dockarea");
+
+ QDockWindow *dockTree = new QDockWindow(dockArea);
+ KFormDesigner::ObjectTreeView *view = new KFormDesigner::ObjectTreeView(dockTree);
+ dockTree->setWidget(view);
+ dockTree->setCaption(i18n("Objects"));
+ dockTree->setResizeEnabled(true);
+ dockTree->setFixedExtentWidth(256);
+
+ QDockWindow *dockEditor = new QDockWindow(dockArea);
+ KoProperty::Editor *editor = new KoProperty::Editor(dockEditor);
+ dockEditor->setWidget(editor);
+ dockEditor->setCaption(i18n("Properties"));
+ dockEditor->setResizeEnabled(true);
+
+ KFormDesigner::FormManager::self()->setEditor(editor);
+ KFormDesigner::FormManager::self()->setObjectTreeView(view);
+
+ setupActions();
+ setModified(false);
+
+ // action stuff
+ connect(KFormDesigner::FormManager::self(), SIGNAL(widgetSelected(KFormDesigner::Form*, bool)), SLOT(slotWidgetSelected(KFormDesigner::Form*, bool)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(formWidgetSelected(KFormDesigner::Form*)), SLOT(slotFormWidgetSelected(KFormDesigner::Form*)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(noFormSelected()), SLOT(slotNoFormSelected()));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(undoEnabled(bool, const QString&)), SLOT(setUndoEnabled(bool, const QString&)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(redoEnabled(bool, const QString&)), SLOT(setRedoEnabled(bool, const QString&)));
+
+ connect(KFormDesigner::FormManager::self(), SIGNAL(dirty(KFormDesigner::Form*, bool)), this, SLOT(slotFormModified(KFormDesigner::Form*, bool)));
+
+ connect(KFormDesigner::FormManager::self(), SIGNAL(createFormSlot(KFormDesigner::Form*, const QString&, const QString&)),
+ this, SLOT(slotCreateFormSlot(KFormDesigner::Form*, const QString&, const QString &)));
+ }
+
+ container->show();
+ setWidget(container);
+ connect(m_workspace, SIGNAL(windowActivated(QWidget*)), KFormDesigner::FormManager::self(), SLOT(windowChanged(QWidget*)));
+ slotNoFormSelected();
+}
+
+KFormDesigner::WidgetLibrary* KFormDesignerKDevPart::formsLibrary()
+{
+ return static_formsLibrary;
+}
+
+void
+KFormDesignerKDevPart::setupActions()
+{
+ KStdAction::open(this, SLOT(open()), actionCollection());
+ KStdAction::openNew(this, SLOT(createBlankForm()), actionCollection());
+ KStdAction::save(this, SLOT(save()), actionCollection());
+ KStdAction::saveAs(this, SLOT(saveAs()), actionCollection());
+ KStdAction::cut(KFormDesigner::FormManager::self(), SLOT(cutWidget()), actionCollection());
+ KStdAction::copy(KFormDesigner::FormManager::self(), SLOT(copyWidget()), actionCollection());
+ KStdAction::paste(KFormDesigner::FormManager::self(), SLOT(pasteWidget()), actionCollection());
+ KStdAction::undo(KFormDesigner::FormManager::self(), SLOT(undo()), actionCollection());
+ KStdAction::redo(KFormDesigner::FormManager::self(), SLOT(redo()), actionCollection());
+ KStdAction::selectAll(KFormDesigner::FormManager::self(), SLOT(selectAll()), actionCollection());
+ new KAction(i18n("Clear Widget Contents"), "editclear", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(clearWidgetContent()), actionCollection(), "clear_contents");
+ new KAction(i18n("Delete Widget"), "editdelete", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(deleteWidget()), actionCollection(), "edit_delete");
+ new KAction(i18n("Preview Form"), "filequickprint", "Ctrl+T", this, SLOT(slotPreviewForm()), actionCollection(), "preview_form");
+ new KAction(i18n("Edit Tab Order"), "tab_order", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(editTabOrder()), actionCollection(), "taborder");
+ new KAction(i18n("Edit Pixmap Collection"), "icons", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(editFormPixmapCollection()), actionCollection(), "pixmap_collection");
+ new KAction(i18n("Edit Form Connections"), "connections", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(editConnections()), actionCollection(), "form_connections");
+
+ new KAction(i18n("Lay Out Widgets &Horizontally"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutHBox()), actionCollection(), "layout_hbox");
+ new KAction(i18n("Lay Out Widgets &Vertically"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutVBox()), actionCollection(), "layout_vbox");
+ new KAction(i18n("Lay Out Widgets in &Grid"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutGrid()), actionCollection(), "layout_grid");
+ new KAction(i18n("&Break Layout"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(breakLayout()), actionCollection(), "break_layout");
+
+ new KAction(i18n("Bring Widget to Front"), "raise", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(bringWidgetToFront()), actionCollection(), "format_raise");
+ new KAction(i18n("Send Widget to Back"), "lower", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(sendWidgetToBack()), actionCollection(), "format_lower");
+
+ KActionMenu *alignMenu = new KActionMenu(i18n("Align Widgets' Positions"), "aopos2grid", actionCollection(), "align_menu");
+ alignMenu->insert( new KAction(i18n("To Left"), "aoleft", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToLeft()), actionCollection(), "align_to_left") );
+ alignMenu->insert( new KAction(i18n("To Right"), "aoright", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToRight()), actionCollection(), "align_to_right") );
+ alignMenu->insert( new KAction(i18n("To Top"), "aotop", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToTop()), actionCollection(), "align_to_top") );
+ alignMenu->insert( new KAction(i18n("To Bottom"), "aobottom", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToBottom()), actionCollection(), "align_to_bottom") );
+ alignMenu->insert( new KAction(i18n("To Grid"), "aopos2grid", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToGrid()), actionCollection(), "align_to_grid") );
+
+ KActionMenu *sizeMenu = new KActionMenu(i18n("Adjust Widgets' Sizes"), "aogrid", actionCollection(), "adjust_size_menu");
+ sizeMenu->insert( new KAction(i18n("To Fit"), "aofit", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustWidgetSize()), actionCollection(), "adjust_to_fit") );
+ sizeMenu->insert( new KAction(i18n("To Grid"), "aogrid", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustSizeToGrid()), actionCollection(), "adjust_size_grid") );
+ sizeMenu->insert( new KAction(i18n("To Shortest"), "aoshortest", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustHeightToSmall()), actionCollection(), "adjust_height_small") );
+ sizeMenu->insert( new KAction(i18n("To Tallest"), "aotallest", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustHeightToBig()), actionCollection(), "adjust_height_big") );
+ sizeMenu->insert( new KAction(i18n("To Narrowest"), "aonarrowest", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustWidthToSmall()), actionCollection(), "adjust_width_small") );
+ sizeMenu->insert( new KAction(i18n("To Widest"), "aowidest", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustWidthToBig()), actionCollection(), "adjust_width_big") );
+
+ if(m_inShell)
+ setXMLFile("kformdesigner_part_shell.rc");
+ else
+ setXMLFile("kformdesigner_part.rc");
+ KFormDesigner::FormManager::self()->createActions(formsLibrary(), actionCollection(), this);
+}
+
+void
+KFormDesignerKDevPart::createBlankForm()
+{
+ if(KFormDesigner::FormManager::self()->activeForm() && m_uniqueFormMode)
+ {
+ m_openingFile = true;
+ closeURL();
+ m_openingFile = false;
+ }
+
+ if(m_uniqueFormMode && KFormDesigner::FormManager::self()->activeForm() && !KFormDesigner::FormManager::self()->activeForm()->isModified() && KFormDesigner::FormManager::self()->activeForm()->filename().isNull())
+ return; // active form is already a blank one
+
+ QString n = i18n("Form") + QString::number(++m_count);
+ Form *form = new Form(formsLibrary(), n.latin1());
+ FormWidgetBase *w = new FormWidgetBase(this, m_workspace, n.latin1());
+
+ w->setCaption(n);
+ w->setIcon(SmallIcon("form"));
+ w->resize(350, 300);
+ w->show();
+ w->setFocus();
+
+ form->createToplevel(w, w);
+ KFormDesigner::FormManager::self()->importForm(form);
+}
+
+void
+KFormDesignerKDevPart::open()
+{
+ m_openingFile = true;
+ KURL url = KFileDialog::getOpenURL("::kformdesigner", i18n("*.ui|Qt Designer UI Files"), m_workspace->topLevelWidget());
+ if(!url.isEmpty())
+ ReadWritePart::openURL(url);
+ m_openingFile = false;
+}
+
+bool
+KFormDesignerKDevPart::openFile()
+{
+ Form *form = new Form(formsLibrary());
+ FormWidgetBase *w = new FormWidgetBase(this, m_workspace);
+ form->createToplevel(w, w);
+
+ if(!KFormDesigner::FormIO::loadForm(form, w, m_file))
+ {
+ delete form;
+ delete w;
+ return false;
+ }
+
+ w->show();
+ KFormDesigner::FormManager::self()->importForm(form, !isReadWrite());
+ return true;
+}
+
+bool
+KFormDesignerKDevPart::saveFile()
+{
+ KFormDesigner::FormIO::saveForm(KFormDesigner::FormManager::self()->activeForm(), m_file);
+ return true;
+}
+
+void
+KFormDesignerKDevPart::saveAs()
+{
+ KURL url = KFileDialog::getSaveURL("::kformdesigner", i18n("*.ui|Qt Designer UI Files"), m_workspace);
+ if(url.isEmpty())
+ return;
+ else
+ ReadWritePart::saveAs(url);
+}
+
+bool
+KFormDesignerKDevPart::closeForm(Form *form)
+{
+ int res = KMessageBox::warningYesNoCancel( m_workspace->topLevelWidget(),
+ i18n( "The form \"%1\" has been modified.\n"
+ "Do you want to save your changes or discard them?" ).arg( form->objectTree()->name() ),
+ i18n( "Close Form" ), KStdGuiItem::save(), KStdGuiItem::discard() );
+
+ if(res == KMessageBox::Yes)
+ save();
+
+ return (res != KMessageBox::Cancel);
+}
+
+bool
+KFormDesignerKDevPart::closeForms()
+{
+ QWidgetList list = m_workspace->windowList(QWorkspace::CreationOrder);
+ for(QWidget *w = list.first(); w; w = list.next())
+ if(w->close() == false)
+ return false;
+
+ return true;
+}
+
+bool
+KFormDesignerKDevPart::closeURL()
+{
+ if(!KFormDesigner::FormManager::self()->activeForm())
+ return true;
+
+ if(m_uniqueFormMode || !m_openingFile)
+ return closeForms();
+
+ return true;
+}
+
+void
+KFormDesignerKDevPart::slotFormModified(Form *, bool isDirty)
+{
+ setModified(isDirty);
+}
+
+void
+KFormDesignerKDevPart::slotPreviewForm()
+{
+ if(!KFormDesigner::FormManager::self()->activeForm())
+ return;
+
+ FormWidgetBase *w = new FormWidgetBase(this, m_workspace);
+ KFormDesigner::FormManager::self()->previewForm(KFormDesigner::FormManager::self()->activeForm(), w);
+}
+
+void
+KFormDesignerKDevPart::slotWidgetSelected(Form *form, bool multiple)
+{
+ enableFormActions();
+ // Enable edit actions
+ ENABLE_ACTION("edit_copy", true);
+ ENABLE_ACTION("edit_cut", true);
+ ENABLE_ACTION("edit_delete", true);
+ ENABLE_ACTION("clear_contents", true);
+
+ // 'Align Widgets' menu
+ ENABLE_ACTION("align_menu", multiple);
+ ENABLE_ACTION("align_to_left", multiple);
+ ENABLE_ACTION("align_to_right", multiple);
+ ENABLE_ACTION("align_to_top", multiple);
+ ENABLE_ACTION("align_to_bottom", multiple);
+
+ ENABLE_ACTION("adjust_size_menu", true);
+ ENABLE_ACTION("adjust_width_small", multiple);
+ ENABLE_ACTION("adjust_width_big", multiple);
+ ENABLE_ACTION("adjust_height_small", multiple);
+ ENABLE_ACTION("adjust_height_big", multiple);
+
+ ENABLE_ACTION("format_raise", true);
+ ENABLE_ACTION("format_lower", true);
+
+ // If the widgets selected is a container, we enable layout actions
+ if(!multiple)
+ {
+ KFormDesigner::ObjectTreeItem *item = form->objectTree()->lookup( form->selectedWidgets()->first()->name() );
+ if(item && item->container())
+ multiple = true;
+ }
+ // Layout actions
+ ENABLE_ACTION("layout_hbox", multiple);
+ ENABLE_ACTION("layout_vbox", multiple);
+ ENABLE_ACTION("layout_grid", multiple);
+
+ KFormDesigner::Container *container = KFormDesigner::FormManager::self()->activeForm()->activeContainer();
+ ENABLE_ACTION("break_layout", (container->layoutType() != KFormDesigner::Container::NoLayout));
+}
+
+void
+KFormDesignerKDevPart::slotFormWidgetSelected(Form *form)
+{
+ disableWidgetActions();
+ enableFormActions();
+
+ // Layout actions
+ ENABLE_ACTION("layout_hbox", true);
+ ENABLE_ACTION("layout_vbox", true);
+ ENABLE_ACTION("layout_grid", true);
+ ENABLE_ACTION("break_layout", (form->toplevelContainer()->layoutType() != KFormDesigner::Container::NoLayout));
+}
+
+void
+KFormDesignerKDevPart::slotNoFormSelected()
+{
+ disableWidgetActions();
+
+ // Disable paste action
+ ENABLE_ACTION("edit_paste", false);
+
+ ENABLE_ACTION("edit_undo", false);
+ ENABLE_ACTION("edit_redo", false);
+
+ // Disable 'Tools' actions
+ ENABLE_ACTION("pixmap_collection", false);
+ ENABLE_ACTION("form_connections", false);
+ ENABLE_ACTION("taborder", false);
+ ENABLE_ACTION("change_style", false);
+
+ // Disable items in 'File'
+ ENABLE_ACTION("file_save", false);
+ ENABLE_ACTION("file_save_as", false);
+ ENABLE_ACTION("preview_form", false);
+}
+
+void
+KFormDesignerKDevPart::enableFormActions()
+{
+ // Enable 'Tools' actions
+ ENABLE_ACTION("pixmap_collection", true);
+ ENABLE_ACTION("form_connections", true);
+ ENABLE_ACTION("taborder", true);
+ ENABLE_ACTION("change_style", true);
+
+ // Enable items in 'File'
+ ENABLE_ACTION("file_save", true);
+ ENABLE_ACTION("file_save_as", true);
+ ENABLE_ACTION("preview_form", true);
+
+ ENABLE_ACTION("edit_paste", KFormDesigner::FormManager::self()->isPasteEnabled());
+ ENABLE_ACTION("edit_select_all", true);
+}
+
+void
+KFormDesignerKDevPart::disableWidgetActions()
+{
+ // Disable edit actions
+ ENABLE_ACTION("edit_copy", false);
+ ENABLE_ACTION("edit_cut", false);
+ ENABLE_ACTION("edit_delete", false);
+ ENABLE_ACTION("clear_contents", false);
+
+ // Disable format functions
+ ENABLE_ACTION("align_menu", false);
+ ENABLE_ACTION("align_to_left", false);
+ ENABLE_ACTION("align_to_right", false);
+ ENABLE_ACTION("align_to_top", false);
+ ENABLE_ACTION("align_to_bottom", false);
+ ENABLE_ACTION("adjust_size_menu", false);
+ ENABLE_ACTION("format_raise", false);
+ ENABLE_ACTION("format_lower", false);
+
+ ENABLE_ACTION("layout_hbox", false);
+ ENABLE_ACTION("layout_vbox", false);
+ ENABLE_ACTION("layout_grid", false);
+ ENABLE_ACTION("break_layout", false);
+}
+
+void
+KFormDesignerKDevPart::setUndoEnabled(bool enabled, const QString &text)
+{
+ KAction *undoAction = actionCollection()->action("edit_undo");
+ if(undoAction)
+ {
+ undoAction->setEnabled(enabled);
+ if(!text.isNull())
+ undoAction->setText(text);
+ }
+}
+
+void
+KFormDesignerKDevPart::setRedoEnabled(bool enabled, const QString &text)
+{
+ KAction *redoAction = actionCollection()->action("edit_redo");
+ if(redoAction)
+ {
+ redoAction->setEnabled(enabled);
+ if(!text.isNull())
+ redoAction->setText(text);
+ }
+}
+
+void
+KFormDesignerKDevPart::slotCreateFormSlot(Form *form, const QString &widget, const QString &signal)
+{
+ Function f;
+ f.returnType = "void";
+ f.function = widget + "_" + signal;
+ f.specifier = "non virtual";
+ f.access = "public";
+ f.type = ftQtSlot;
+ emit addedFunction(designerType(), form->objectTree()->name(), f);
+}
+
+KFormDesignerKDevPart::~KFormDesignerKDevPart()
+{
+}
+
+
+////// FormWidgetBase : helper widget to draw rects on top of widgets
+
+//repaint all children widgets
+static void repaintAll(QWidget *w)
+{
+ QObjectList *list = w->queryList("QWidget");
+ QObjectListIt it(*list);
+ for (QObject *obj; (obj=it.current()); ++it ) {
+ static_cast<QWidget*>(obj)->repaint();
+ }
+ delete list;
+}
+
+void
+FormWidgetBase::drawRects(const QValueList<QRect> &list, int type)
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ if (prev_rect.isValid()) {
+ //redraw prev. selection's rectangle
+ p.drawPixmap( QPoint(prev_rect.x()-2, prev_rect.y()-2), buffer, QRect(prev_rect.x()-2, prev_rect.y()-2, prev_rect.width()+4, prev_rect.height()+4));
+ }
+ p.setBrush(QBrush::NoBrush);
+ if(type == 1) // selection rect
+ p.setPen(QPen(white, 1, Qt::DotLine));
+ else if(type == 2) // insert rect
+ p.setPen(QPen(white, 2));
+ p.setRasterOp(XorROP);
+
+ prev_rect = QRect();
+ QValueList<QRect>::ConstIterator endIt = list.constEnd();
+ for(QValueList<QRect>::ConstIterator it = list.constBegin(); it != endIt; ++it) {
+ p.drawRect(*it);
+ prev_rect = prev_rect.unite(*it);
+ }
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+}
+
+void
+FormWidgetBase::drawRect(const QRect& r, int type)
+{
+ QValueList<QRect> l;
+ l.append(r);
+ drawRects(l, type);
+}
+
+void
+FormWidgetBase::initRect()
+{
+ repaintAll(this);
+ buffer.resize( width(), height() );
+ buffer = QPixmap::grabWindow( winId() );
+ prev_rect = QRect();
+}
+
+void
+FormWidgetBase::clearRect()
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ //redraw entire form surface
+ p.drawPixmap( QPoint(0,0), buffer, QRect(0,0,buffer.width(), buffer.height()) );
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+
+ repaintAll(this);
+}
+
+void
+FormWidgetBase::highlightWidgets(QWidget *from, QWidget *to)//, const QPoint &point)
+{
+ QPoint fromPoint, toPoint;
+ if(from && from->parentWidget() && (from != this))
+ fromPoint = from->parentWidget()->mapTo(this, from->pos());
+ if(to && to->parentWidget() && (to != this))
+ toPoint = to->parentWidget()->mapTo(this, to->pos());
+
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ if (prev_rect.isValid()) {
+ //redraw prev. selection's rectangle
+ p.drawPixmap( QPoint(prev_rect.x(), prev_rect.y()), buffer, QRect(prev_rect.x(), prev_rect.y(), prev_rect.width(), prev_rect.height()));
+ }
+
+ p.setPen( QPen(Qt::red, 2) );
+
+ if(to)
+ {
+ QPixmap pix1 = QPixmap::grabWidget(from);
+ QPixmap pix2 = QPixmap::grabWidget(to);
+
+ if((from != this) && (to != this))
+ p.drawLine( from->parentWidget()->mapTo(this, from->geometry().center()), to->parentWidget()->mapTo(this, to->geometry().center()) );
+
+ p.drawPixmap(fromPoint.x(), fromPoint.y(), pix1);
+ p.drawPixmap(toPoint.x(), toPoint.y(), pix2);
+
+ if(to == this)
+ p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4);
+ else
+ p.drawRoundRect(toPoint.x(), toPoint.y(), to->width(), to->height(), 5, 5);
+ }
+
+ if(from == this)
+ p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4);
+ else
+ p.drawRoundRect(fromPoint.x(), fromPoint.y(), from->width(), from->height(), 5, 5);
+
+ if((to == this) || (from == this))
+ prev_rect = QRect(0, 0, buffer.width(), buffer.height());
+ else if(to)
+ {
+ prev_rect.setX( (fromPoint.x() < toPoint.x()) ? (fromPoint.x() - 5) : (toPoint.x() - 5) );
+ prev_rect.setY( (fromPoint.y() < toPoint.y()) ? (fromPoint.y() - 5) : (toPoint.y() - 5) );
+ prev_rect.setRight( (fromPoint.x() < toPoint.x()) ? (toPoint.x() + to->width() + 10) : (fromPoint.x() + from->width() + 10) );
+ prev_rect.setBottom( (fromPoint.y() < toPoint.y()) ? (toPoint.y() + to->height() + 10) : (fromPoint.y() + from->height() + 10) ) ;
+ }
+ else
+ prev_rect = QRect(fromPoint.x()- 5, fromPoint.y() -5, from->width() + 10, from->height() + 10);
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+}
+
+void
+FormWidgetBase::closeEvent(QCloseEvent *ev)
+{
+ Form *form = KFormDesigner::FormManager::self()->formForWidget(this);
+ if(!form || !form->isModified() || !form->objectTree()) // == preview form
+ ev->accept();
+ else
+ {
+ bool close = m_part->closeForm(form);
+ if(close)
+ ev->accept();
+ else
+ ev->ignore();
+ }
+}
+
+K_EXPORT_COMPONENT_FACTORY(libkformdesigner_kdev_part, KFDFactory)
+
+#include "kfd_kdev_part.moc"
+
diff --git a/kexi/formeditor/kdevelop_plugin/kfd_kdev_part.h b/kexi/formeditor/kdevelop_plugin/kfd_kdev_part.h
new file mode 100644
index 000000000..52ce5f274
--- /dev/null
+++ b/kexi/formeditor/kdevelop_plugin/kfd_kdev_part.h
@@ -0,0 +1,139 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNER_KDEVELOP_PART_H
+#define KFORMDESIGNER_KDEVELOP_PART_H
+
+#include <qwidget.h>
+#include <qpixmap.h>
+
+#include <kinterfacedesigner/designer.h>
+#include <kparts/factory.h>
+
+#include "form.h"
+
+class KAboutData;
+class KInstance;
+class QWorkspace;
+class QCloseEvent;
+
+using KFormDesigner::Form;
+using namespace KInterfaceDesigner;
+
+class KFORMEDITOR_EXPORT KFDFactory : public KParts::Factory
+{
+ Q_OBJECT
+
+ public:
+ KFDFactory();
+ virtual ~KFDFactory();
+
+ virtual KParts::Part* createPartObject(QWidget *parentWidget=0, const char *widgetName=0, QObject *parent=0, const char *name=0,
+ const char *classname="KParts::Part", const QStringList &args=QStringList());
+
+ static KInstance *instance();
+ static KAboutData *aboutData();
+
+ private:
+ static KInstance *m_instance;
+};
+
+class KFORMEDITOR_EXPORT KFormDesignerKDevPart : public Designer
+{
+ Q_OBJECT
+
+ public:
+ KFormDesignerKDevPart(QWidget *parent, const char *name, bool readOnly=true, const QStringList &args=QStringList());
+ virtual ~KFormDesignerKDevPart();
+
+ virtual DesignerType designerType() { return QtDesigner; }
+ virtual void openProject(const QString &) {}
+
+// KFormDesigner::FormManager* manager() { return m_manager; }
+ void setUniqueFormMode(bool enable) { m_uniqueFormMode = enable; }
+
+ bool closeForm(Form *form);
+ bool closeForms();
+
+ virtual bool closeURL();
+
+ static KFormDesigner::WidgetLibrary* formsLibrary();
+
+ public slots:
+ /*! Creates a new blank Form. The new Form is shown and become the active Form. */
+ void createBlankForm();
+ /*! Loads a Form from a UI file. A "Open File" dialog is shown to select the file. The loaded Form is shown and becomes
+ the active Form. */
+ void open();
+ void slotPreviewForm();
+ void saveAs();
+ void slotCreateFormSlot(KFormDesigner::Form *form, const QString &widget, const QString &signal);
+
+ protected slots:
+ void slotWidgetSelected(KFormDesigner::Form *form, bool multiple);
+ void slotFormWidgetSelected(KFormDesigner::Form *form);
+ void slotNoFormSelected();
+ void slotFormModified(KFormDesigner::Form *form, bool isDirty);
+ void setUndoEnabled(bool enabled, const QString &text);
+ void setRedoEnabled(bool enabled, const QString &text);
+
+ protected:
+ virtual bool openFile();
+ virtual bool saveFile();
+ void disableWidgetActions();
+ void enableFormActions();
+ void setupActions();
+
+ private:
+ static KFormDesigner::WidgetLibrary* static_formsLibrary;
+// KFormDesigner::FormManager *m_manager;
+ QWorkspace *m_workspace;
+ int m_count;
+ bool m_uniqueFormMode;
+ bool m_openingFile;
+ bool m_inShell;
+};
+
+//! Helper: this widget is used to create form's surface
+class KFORMEDITOR_EXPORT FormWidgetBase : public QWidget, public KFormDesigner::FormWidget
+{
+ Q_OBJECT
+
+ public:
+ FormWidgetBase(KFormDesignerKDevPart *part, QWidget *parent = 0, const char *name = 0, int WFlags = WDestructiveClose)
+ : QWidget(parent, name, WFlags), m_part(part) {}
+ ~FormWidgetBase() {;}
+
+ void drawRect(const QRect& r, int type);
+ void drawRects(const QValueList<QRect> &list, int type);
+ void initRect();
+ void clearRect();
+ void highlightWidgets(QWidget *from, QWidget *to);//, const QPoint &p);
+
+ protected:
+ void closeEvent(QCloseEvent *ev);
+
+ private:
+ QPixmap buffer; //!< stores grabbed entire form's area for redraw
+ QRect prev_rect; //!< previously selected rectangle
+ KFormDesignerKDevPart *m_part;
+};
+
+#endif
+
diff --git a/kexi/formeditor/kdevelop_plugin/kformdesigner_kdev_part.desktop b/kexi/formeditor/kdevelop_plugin/kformdesigner_kdev_part.desktop
new file mode 100644
index 000000000..c94e628f6
--- /dev/null
+++ b/kexi/formeditor/kdevelop_plugin/kformdesigner_kdev_part.desktop
@@ -0,0 +1,50 @@
+[Desktop Entry]
+Name=Form Designer KDevelop Plugin
+Name[bg]=Приставка на KDevelop за проектиране на форми
+Name[ca]=Endollat de disseny de formulari de KDevelop
+Name[cy]=Ategyn KDevelop: Dylunydd Ffurflenni
+Name[da]=Form Designer KDEvelop-plugin
+Name[de]=KDevelop-Modul zum Formularentwurf
+Name[el]=Πρόσθετο σχεδίασης φόρμας του KDevelop
+Name[eo]=Formulardesegnila kromaĵo por KDevelop
+Name[es]=Complemento de diseñador de formularios de KDevelop
+Name[et]=Vormikujundaja KDevelopi plugin
+Name[eu]=Formularioak diseinatzeko KDevelop-en plugina
+Name[fa]=وصلۀ KDevelop طراح برگه
+Name[fi]=KDevelopin lomakkeen suunnittelija -liitännäinen
+Name[fr]=Module externe KDevelop de composition d'interfaces graphiques
+Name[fy]=KDevelop-plugin FormDesigner
+Name[gl]=Plugin de Deseño de Formularios para Kdevelop
+Name[he]=תוסף מעצב טפסים ל־KDevelop
+Name[hr]=KDevelop dodatak dizajnera obrazaca
+Name[hu]=Űrlaptervező modul a KDevelophoz
+Name[is]=Form hönnunar KDevelop íforrit
+Name[it]=Plugin di KDevelop per il progetto dei moduli
+Name[ja]=フォームデザイナー Kdevelop プラグイン
+Name[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​រចនា​សំណុំបែបបទ​សម្រាប់ KDevelop
+Name[lv]=KDevelop formu veidošanas spraudnis
+Name[ms]=Plugin KDevelop Pereka Bentuk Borang
+Name[nb]=KDevelop-tillegg for skjemautforming
+Name[nds]=KDevelop-Moduul för't Opstellen vun Kiekwarken
+Name[ne]=फारम डिजाइनकर्ता केडीई विकास प्लगइन
+Name[nl]=KDevelop-plugin FormDesigner
+Name[nn]=KDevelop-tillegg for skjemautforming
+Name[pl]=Wtyczka projektanta formularzy dla KDevelop
+Name[pt]='Plugin' para o KDevelop de Desenho de Formulários
+Name[pt_BR]=Plugin do Desenhista de Formulário do KDevelop
+Name[ru]=Модуль форм KDevelop
+Name[sk]=KDevelop modul na vytváranie formulárov (Form Designer)
+Name[sl]=Vstavek za oblikovanje obrazcev za KDevelop
+Name[sr]=Прикључак дизајнера форми за KDevelop
+Name[sr@Latn]=Priključak dizajnera formi za KDevelop
+Name[sv]=KDevelop insticksprogram för formulärkonstruktion
+Name[ta]=படிவ வடிவமைப்பாளர் கேடெவெலப் சொருகுப்பொருள்
+Name[tr]=FormTasarımcısı KDevelop Eklentisi
+Name[uk]=Втулок дизайнера форм KDevelop
+Name[zh_CN]=表单设计器 KDevelop 插件
+Name[zh_TW]=表單設計師 KDevelop 外掛程式
+MimeType=application/x-designer;
+ServiceTypes=KParts/ReadOnlyPart,KParts/ReadWritePart
+X-KDE-Library=libkformdesigner_kdev_part
+Type=Service
+InitialPreference=0
diff --git a/kexi/formeditor/kdevelop_plugin/kformdesigner_part.rc b/kexi/formeditor/kdevelop_plugin/kformdesigner_part.rc
new file mode 100644
index 000000000..3cadda53f
--- /dev/null
+++ b/kexi/formeditor/kdevelop_plugin/kformdesigner_part.rc
@@ -0,0 +1,129 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kfd_part" version="1">
+
+<MenuBar>
+ <Menu name="edit" noMerge="1">
+ <Action name="edit_undo" group="edit_undo_merge"/>
+ <Action name="edit_redo" group="edit_undo_merge"/>
+ <Separator group="edit_undo_merge"/>
+ <Action name="edit_cut" group="edit_paste_merge"/>
+ <Action name="edit_copy" group="edit_paste_merge"/>
+ <Action name="edit_paste" group="edit_paste_merge"/>
+ <Action name="delete_widget" group="edit_paste_merge"/>
+ <Action name="clear_contents" group="edit_paste_merge"/>
+ <Action name="edit_select_all" group="edit_select_merge"/>
+ <Separator group="edit_paste_merge"/>
+ </Menu>
+ <Menu name="tools" noMerge="1">
+ <Merge/>
+ <Separator/>
+ <Action name="taborder"/>
+ <Action name="change_style"/>
+ <Action name="pixmap_collection"/>
+ <Action name="form_connections"/>
+ </Menu>
+ <Menu name="widgets" noMerge="1">
+ <text>&amp;Widgets</text>
+ <Merge/>
+ <Action name="pointer"/>
+ <Action name="drag_connection"/>
+ <Separator/>
+ <Action name="library_widget_SubForm"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_MyTabWidget"/>
+ <Action name="library_widget_QWidgetStack"/>
+ <Action name="library_widget_QFrame"/>
+ <Separator/>
+ <Action name="library_widget_KLineEdit"/>
+ <Action name="library_widget_QLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_QRadioButton"/>
+ <Action name="library_widget_QCheckBox"/>
+ <Action name="library_widget_KIntSpinBox"/>
+ <Action name="library_widget_KComboBox"/>
+ <Action name="library_widget_KListBox"/>
+ <Action name="library_widget_KTextEdit"/>
+ <Action name="library_widget_KListView"/>
+ <Action name="library_widget_QSlider"/>
+ <Action name="library_widget_KProgress"/>
+ <Action name="library_widget_KTimeWidget"/>
+ <Action name="library_widget_KDateWidget"/>
+ <Action name="library_widget_KDateTimeWidget"/>
+ <Action name="library_widget_Line"/>
+ <Action name="library_widget_Spring"/>
+ </Menu>
+ <Menu name="format" noMerge="1">
+ <text>&amp;Format</text>
+ <Action name="snap_to_grid"/>
+ <Separator/>
+ <Action name="layout_hbox"/>
+ <Action name="layout_vbox"/>
+ <Action name="layout_grid"/>
+ <Action name="break_layout"/>
+ <Separator/>
+ <Action name="align_menu"/>
+ <Action name="adjust_size_menu"/>
+ <Separator/>
+ <Action name="format_raise"/>
+ <Action name="format_lower"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="fileToolBar" noMerge="1"><text>Main Toolbar</text>
+ <Action name="preview_form"/>
+ <Separator/>
+ <Action name="edit_undo"/>
+ <Action name="edit_redo"/>
+ <Separator/>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="edit_paste"/>
+ <Action name="delete_widget"/>
+ <Action name="clear_contents"/>
+</ToolBar>
+<ToolBar name="containers" fullWidth="false" noMerge="1">
+ <text>Containers</text>
+ <Action name="library_widget_SubForm"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_MyTabWidget"/>
+ <Action name="library_widget_QWidgetStack"/>
+ <Action name="library_widget_QFrame"/>
+</ToolBar>
+<ToolBar name="widgets" fullWidth="false" noMerge="1">
+ <text>Widgets</text>
+ <Action name="pointer"/>
+ <Action name="drag_connection"/>
+ <Separator/>
+ <Action name="library_widget_KLineEdit"/>
+ <Action name="library_widget_QLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_QRadioButton"/>
+ <Action name="library_widget_QCheckBox"/>
+ <Action name="library_widget_KIntSpinBox"/>
+ <Action name="library_widget_KComboBox"/>
+ <Action name="library_widget_KListBox"/>
+ <Action name="library_widget_KTextEdit"/>
+ <Action name="library_widget_KListView"/>
+ <Action name="library_widget_QSlider"/>
+ <Action name="library_widget_KProgress"/>
+ <Action name="library_widget_KTimeWidget"/>
+ <Action name="library_widget_KDateWidget"/>
+ <Action name="library_widget_KDateTimeWidget"/>
+ <Action name="library_widget_Line"/>
+ <Action name="library_widget_Spring"/>
+ <ActionList name="undo_actions" />
+</ToolBar>
+<ToolBar name="tools" fullWidth="false" noMerge="1">
+<text>Tools Toolbar</text>
+ <Action name="pixmap_collection"/>
+ <Action name="change_style"/>
+</ToolBar>
+<ToolBar name="format" fullWidth="false" noMerge="1">
+<text>Format Toolbar</text>
+ <Action name="align_menu"/>
+ <Action name="adjust_size_menu"/>
+</ToolBar>
+
+</kpartgui> \ No newline at end of file
diff --git a/kexi/formeditor/kdevelop_plugin/kformdesigner_part_shell.rc b/kexi/formeditor/kdevelop_plugin/kformdesigner_part_shell.rc
new file mode 100644
index 000000000..8b5927520
--- /dev/null
+++ b/kexi/formeditor/kdevelop_plugin/kformdesigner_part_shell.rc
@@ -0,0 +1,142 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kfd_part" version="1">
+
+<MenuBar>
+ <Menu name="file" noMerge="1">
+ <Action name="file_new"/>
+ <Action name="file_open"/>
+ <Separator />
+ <Action name="file_save"/>
+ <Action name="file_save_as"/>
+ <Separator/>
+ </Menu>
+ <Menu name="edit" noMerge="1">
+ <Action name="edit_undo" group="edit_undo_merge"/>
+ <Action name="edit_redo" group="edit_undo_merge"/>
+ <Separator group="edit_undo_merge"/>
+ <Action name="edit_cut" group="edit_paste_merge"/>
+ <Action name="edit_copy" group="edit_paste_merge"/>
+ <Action name="edit_paste" group="edit_paste_merge"/>
+ <Action name="delete_widget" group="edit_paste_merge"/>
+ <Action name="clear_contents" group="edit_paste_merge"/>
+ <Separator group="edit_select_merge"/>
+ <Action name="edit_select_all" group="edit_select_merge"/>
+ <Separator group="edit_paste_merge"/>
+ </Menu>
+ <Menu name="view" noMerge="1">
+ <Action name="preview_form"/>
+ </Menu>
+ <Menu name="tools" noMerge="1">
+ <Action name="taborder"/>
+ <Action name="change_style"/>
+ <Action name="pixmap_collection"/>
+ <Action name="form_connections"/>
+ </Menu>
+ <Menu name="widgets" noMerge="1">
+ <text>&amp;Widgets</text>
+ <Merge/>
+ <Action name="pointer"/>
+ <Action name="drag_connection"/>
+ <Separator/>
+ <Action name="library_widget_SubForm"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_MyTabWidget"/>
+ <Action name="library_widget_QWidgetStack"/>
+ <Action name="library_widget_QFrame"/>
+ <Separator/>
+ <Action name="library_widget_KLineEdit"/>
+ <Action name="library_widget_QLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_QRadioButton"/>
+ <Action name="library_widget_QCheckBox"/>
+ <Action name="library_widget_KIntSpinBox"/>
+ <Action name="library_widget_KComboBox"/>
+ <Action name="library_widget_KListBox"/>
+ <Action name="library_widget_KTextEdit"/>
+ <Action name="library_widget_KListView"/>
+ <Action name="library_widget_QSlider"/>
+ <Action name="library_widget_KProgress"/>
+ <Action name="library_widget_KTimeWidget"/>
+ <Action name="library_widget_KDateWidget"/>
+ <Action name="library_widget_KDateTimeWidget"/>
+ <Action name="library_widget_Line"/>
+ <Action name="library_widget_Spring"/>
+ </Menu>
+ <Menu name="format" noMerge="1">
+ <text>&amp;Format</text>
+ <Action name="snap_to_grid"/>
+ <Separator/>
+ <Action name="layout_hbox"/>
+ <Action name="layout_vbox"/>
+ <Action name="layout_grid"/>
+ <Action name="break_layout"/>
+ <Separator/>
+ <Action name="align_menu"/>
+ <Action name="adjust_size_menu"/>
+ <Separator/>
+ <Action name="format_raise"/>
+ <Action name="format_lower"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="fileToolBar" noMerge="1"><text>Main Toolbar</text>
+ <Action name="file_new"/>
+ <Action name="file_open"/>
+ <Action name="file_save"/>
+ <Action name="preview_form"/>
+ <Separator/>
+ <Action name="edit_undo"/>
+ <Action name="edit_redo"/>
+ <Separator/>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="edit_paste"/>
+ <Action name="delete_widget"/>
+ <Action name="clear_contents"/>
+</ToolBar>
+<ToolBar name="containers" fullWidth="false" noMerge="1">
+ <text>Containers</text>
+ <Action name="library_widget_SubForm"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_MyTabWidget"/>
+ <Action name="library_widget_QWidgetStack"/>
+ <Action name="library_widget_QFrame"/>
+</ToolBar>
+<ToolBar name="widgets" fullWidth="false" noMerge="1">
+ <text>Widgets</text>
+ <Action name="pointer"/>
+ <Action name="drag_connection"/>
+ <Separator/>
+ <Action name="library_widget_KLineEdit"/>
+ <Action name="library_widget_QLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_QRadioButton"/>
+ <Action name="library_widget_QCheckBox"/>
+ <Action name="library_widget_KIntSpinBox"/>
+ <Action name="library_widget_KComboBox"/>
+ <Action name="library_widget_KListBox"/>
+ <Action name="library_widget_KTextEdit"/>
+ <Action name="library_widget_KListView"/>
+ <Action name="library_widget_QSlider"/>
+ <Action name="library_widget_KProgress"/>
+ <Action name="library_widget_KTimeWidget"/>
+ <Action name="library_widget_KDateWidget"/>
+ <Action name="library_widget_KDateTimeWidget"/>
+ <Action name="library_widget_Line"/>
+ <Action name="library_widget_Spring"/>
+ <ActionList name="undo_actions" />
+</ToolBar>
+<ToolBar name="tools" fullWidth="false" noMerge="1">
+<text>Tools Toolbar</text>
+ <Action name="pixmap_collection"/>
+ <Action name="change_style"/>
+</ToolBar>
+<ToolBar name="format" fullWidth="false" noMerge="1">
+<text>Format Toolbar</text>
+ <Action name="align_menu"/>
+ <Action name="adjust_size_menu"/>
+</ToolBar>
+
+</kpartgui> \ No newline at end of file
diff --git a/kexi/formeditor/kfdpixmapedit.cpp b/kexi/formeditor/kfdpixmapedit.cpp
new file mode 100644
index 000000000..e8a967614
--- /dev/null
+++ b/kexi/formeditor/kfdpixmapedit.cpp
@@ -0,0 +1,59 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kfdpixmapedit.h"
+
+#include <kdebug.h>
+
+#include <koproperty/property.h>
+#include "pixmapcollection.h"
+#include "formmanager.h"
+#include "form.h"
+#include "objecttree.h"
+
+using namespace KFormDesigner;
+
+KFDPixmapEdit::KFDPixmapEdit(KoProperty::Property *property, QWidget *parent, const char *name)
+ : KoProperty::PixmapEdit(property, parent, name)
+{
+// m_manager = manager;
+}
+
+KFDPixmapEdit::~KFDPixmapEdit()
+{}
+
+void
+KFDPixmapEdit::selectPixmap()
+{
+ KoProperty::PixmapEdit::selectPixmap();
+#if 0 //will be reenabled for new image collection
+ if(!m_manager->activeForm() || !property())
+ return;
+
+ ObjectTreeItem *item = m_manager->activeForm()->objectTree()->lookup(m_manager->activeForm()->selectedWidget()->name());
+ QString name = item ? item->pixmapName(property()->name()) : "";
+ PixmapCollectionChooser dialog( m_manager->activeForm()->pixmapCollection(), name, topLevelWidget() );
+ if(dialog.exec() == QDialog::Accepted) {
+ setValue(dialog.pixmap(), true);
+ item->setPixmapName(property()->name(), dialog.pixmapName());
+ }
+#endif
+}
+
+#include "kfdpixmapedit.moc"
diff --git a/kexi/formeditor/kfdpixmapedit.h b/kexi/formeditor/kfdpixmapedit.h
new file mode 100644
index 000000000..0d183ea4e
--- /dev/null
+++ b/kexi/formeditor/kfdpixmapedit.h
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMEDITOR_PIXMAPEDIT_H
+#define KFORMEDITOR_PIXMAPEDIT_H
+
+#include <koproperty/editors/pixmapedit.h>
+
+namespace KFormDesigner {
+
+class KFORMEDITOR_EXPORT KFDPixmapEdit : public KoProperty::PixmapEdit
+{
+ Q_OBJECT
+
+ public:
+ KFDPixmapEdit(KoProperty::Property *property, QWidget *parent=0, const char *name=0);
+ virtual ~KFDPixmapEdit();
+
+ public slots:
+ virtual void selectPixmap();
+
+ private:
+// FormManager *m_manager;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/libactionwidget.cpp b/kexi/formeditor/libactionwidget.cpp
new file mode 100644
index 000000000..8a17c3c3b
--- /dev/null
+++ b/kexi/formeditor/libactionwidget.cpp
@@ -0,0 +1,52 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+
+#include "libactionwidget.h"
+#include "widgetfactory.h"
+
+using namespace KFormDesigner;
+
+LibActionWidget::LibActionWidget(WidgetInfo *w, KActionCollection *c)
+ : KToggleAction(w->name(), w->pixmap(), 0/*Key_F5*/, 0, 0 /*SLOT(slotWidget())*/,
+ c, QString("library_widget_" + w->className()).latin1())
+{
+// kdDebug() << "LibActionWidget::LibActionWidget(): " << QString("library_widget_" + w->className()).latin1() << endl;
+ m_className = w->className();
+ setExclusiveGroup("LibActionWidgets");
+ setToolTip(w->name());
+ setWhatsThis(w->description());
+// connect(this, SIGNAL(activated()), this, SLOT(slotWidget()));
+}
+
+void
+LibActionWidget::slotActivated()
+{
+ KToggleAction::slotActivated();
+ if (isChecked())
+ emit prepareInsert(m_className);
+}
+
+LibActionWidget::~LibActionWidget()
+{
+}
+
+#include "libactionwidget.moc"
diff --git a/kexi/formeditor/libactionwidget.h b/kexi/formeditor/libactionwidget.h
new file mode 100644
index 000000000..bfa45f6a4
--- /dev/null
+++ b/kexi/formeditor/libactionwidget.h
@@ -0,0 +1,60 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef LIBACTIONWIDGET_H
+#define LIBACTIONWIDGET_H
+
+
+#include <kactionclasses.h>
+
+namespace KFormDesigner
+{
+
+class WidgetInfo;
+
+/**
+ * KToggleAction subclass which remembers the matching class name.
+ */
+class KFORMEDITOR_EXPORT LibActionWidget : public KToggleAction
+{
+ Q_OBJECT
+ public:
+ /** LibActionWidget object is initialized to be mutually
+ exclusive with all other LibActionWidget objects */
+ LibActionWidget(WidgetInfo *, KActionCollection *collection);
+ virtual ~LibActionWidget();
+
+ signals:
+ /**
+ * emits a signal containing the class name
+ */
+ void prepareInsert(const QCString &className);
+
+ protected slots:
+ /** reimplemented from KToggleAction */
+ virtual void slotActivated();
+
+ private:
+ QCString m_className;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/objecttree.cpp b/kexi/formeditor/objecttree.cpp
new file mode 100644
index 000000000..d5708773d
--- /dev/null
+++ b/kexi/formeditor/objecttree.cpp
@@ -0,0 +1,244 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+#include <qwidget.h>
+#include <qvariant.h>
+#include <qdom.h>
+#include <qtextstream.h>
+
+#include "form.h"
+#include "container.h"
+#include "objecttree.h"
+
+
+using namespace KFormDesigner;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+///////////////// ObjectTreeItem /////////////
+////////////////////////////////////////////////////////////////////////////////////////
+
+
+ObjectTreeItem::ObjectTreeItem(const QString &classn, const QString &name, QWidget *widget,
+ Container *parentContainer, Container *container)
+ : m_enabled(true), m_row(-1), m_col(-1), m_rowspan(-1), m_colspan(-1), m_span(false)
+{
+ m_className = classn;
+ m_name = name;
+ m_widget = widget;
+ m_container = container;
+ m_eater = new EventEater(widget, parentContainer);
+ m_parent = 0;
+ m_subprops = 0;
+}
+
+ObjectTreeItem::~ObjectTreeItem()
+{
+// kdDebug() << "ObjectTreeItem deleted: " << name() << endl;
+ delete m_subprops;
+}
+
+void
+ObjectTreeItem::rename(const QString &name)
+{
+ m_name = name;
+}
+
+void
+ObjectTreeItem::addChild(ObjectTreeItem *c)
+{
+ m_children.append(c);
+ c->setParent(this);
+}
+
+void
+ObjectTreeItem::removeChild(ObjectTreeItem *c)
+{
+ m_children.remove(c);
+}
+
+void
+ObjectTreeItem::addModifiedProperty(const QCString &property, const QVariant &oldValue)
+{
+ if(property == "name")
+ return;
+
+ if(!m_props.contains(property)) {
+ m_props.insert(property, oldValue);
+ kdDebug() << "ObjectTree::adModProperty(): Added this property in the list: " << property << " oldValue: " << oldValue << endl;
+ }
+}
+
+void
+ObjectTreeItem::addSubproperty(const QCString &property, const QVariant& value)
+{
+ if (!m_subprops)
+ m_subprops = new QMap<QString, QVariant>();
+ if (!m_props.contains(property))
+ m_subprops->insert( property, value );
+}
+
+void
+ObjectTreeItem::storeUnknownProperty(QDomElement &el)
+{
+ if(!el.isNull()) {
+ QTextStream ts(m_unknownProps, IO_WriteOnly|IO_Append );
+ el.save(ts, 0);
+ }
+}
+
+void
+ObjectTreeItem::setPixmapName(const QCString &property, const QString &name)
+{
+ m_pixmapNames[property] = name;
+}
+
+QString
+ObjectTreeItem::pixmapName(const QCString &property)
+{
+ if(m_pixmapNames.contains(property))
+ return m_pixmapNames[property];
+ return QString::null;
+}
+
+void
+ObjectTreeItem::setGridPos(int row, int col, int rowspan, int colspan)
+{
+ m_row = row; m_col = col;
+ m_rowspan = rowspan;
+ m_colspan = colspan;
+ if(colspan || rowspan)
+ m_span = true;
+ else
+ m_span = false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/// ObjectTree /////////
+////////////////////////////////////////////////////////////////////////////////////////
+
+ObjectTree::ObjectTree(const QString &classn, const QString &name, QWidget *widget, Container *container)
+ : ObjectTreeItem(classn, name, widget, container, container)
+{
+}
+
+ObjectTree::~ObjectTree()
+{
+// for(ObjectTreeItem *it = children()->first(); it; it = children()->next())
+// removeItem(it->name());
+ while (children()->first()) {
+ removeItem(children()->first());
+ }
+}
+
+bool
+ObjectTree::rename(const QString &oldname, const QString &newname)
+{
+ if(oldname == m_name)
+ {
+ ObjectTreeItem::rename(newname);
+ return true;
+ }
+
+ ObjectTreeItem *it = lookup(oldname);
+ if(!it)
+ return false;
+
+ it->rename(newname);
+ m_treeDict.remove(oldname);
+ m_treeDict.insert(newname, it);
+
+ return true;
+}
+
+bool
+ObjectTree::reparent(const QString &name, const QString &newparent)
+{
+ ObjectTreeItem *item = lookup(name);
+ if(!item) return false;
+ ObjectTreeItem *parent = lookup(newparent);
+ if(!parent) return false;
+
+ item->parent()->removeChild(item);
+ parent->addChild(item);
+ return true;
+}
+
+ObjectTreeItem*
+ObjectTree::lookup(const QString &name)
+{
+ if(name == this->name())
+ return this;
+ else
+ return m_treeDict[name];
+}
+
+void
+ObjectTree::addItem(ObjectTreeItem *parent, ObjectTreeItem *c)
+{
+ m_treeDict.insert(c->name(), c);
+
+ if(!parent)
+ parent = this;
+ parent->addChild(c);
+ m_container->form()->emitChildAdded(c);
+
+ kdDebug() << "ObjectTree::addItem(): adding " << c->name() << " to " << parent->name() << endl;
+}
+
+void
+ObjectTree::removeItem(const QString &name)
+{
+ ObjectTreeItem *c = lookup(name);
+ removeItem(c);
+}
+
+void
+ObjectTree::removeItem(ObjectTreeItem *c)
+{
+ if (m_container && m_container->form())
+ m_container->form()->emitChildRemoved(c);
+
+ for(ObjectTreeItem *it = c->children()->first(); it; it = c->children()->next())
+ removeItem(it->name());
+
+ m_treeDict.remove(c->name());
+ c->parent()->removeChild(c);
+ delete c;
+}
+
+QCString
+ObjectTree::generateUniqueName(const QCString &prefix, bool numberSuffixRequired)
+{
+ /* old way of naming widgets
+ int appendix = m_names[c] + 1;
+ QString name(c);
+ name.append(QString::number(appendix));
+ m_names[c] = appendix;*/
+ if (!numberSuffixRequired && !lookup(prefix))
+ return prefix;
+ QString name( prefix );
+ int i = 2; //start from 2, i.e. we have: "widget", "widget2", etc.
+ while(lookup(name + QString::number(i)))
+ i++;
+
+ return (name + QString::number(i)).latin1();
+}
+
diff --git a/kexi/formeditor/objecttree.h b/kexi/formeditor/objecttree.h
new file mode 100644
index 000000000..252ee54c0
--- /dev/null
+++ b/kexi/formeditor/objecttree.h
@@ -0,0 +1,184 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNEROBJECTTREE_H
+#define KFORMDESIGNEROBJECTTREE_H
+
+#include <qptrlist.h>
+#include <qmap.h>
+#include <qdict.h>
+#include <qvariant.h>
+#include <qstring.h>
+#include <qguardedptr.h>
+
+class QWidget;
+class QDomElement;
+
+namespace KFormDesigner {
+
+class ObjectTreeItem;
+class Container;
+class EventEater;
+
+//! @short An list of ObjectTreeItem pointers.
+typedef QPtrList<ObjectTreeItem> ObjectTreeList;
+
+//! @short An iterator for ObjectTreeList.
+typedef QPtrListIterator<ObjectTreeItem> ObjectTreeListIterator;
+
+//! @short A QString-based disctionary of ObjectTreeItem pointers.
+typedef QDict<ObjectTreeItem> ObjectTreeDict;
+
+//! @short An iterator for ObjectTreeDict.
+typedef QDictIterator<ObjectTreeItem> ObjectTreeDictIterator;
+
+//! @short A QString -> QVarinat map.
+typedef QMap<QString, QVariant> QVariantMap;
+
+//! @short A const iterator for QVariantMap.
+typedef QMapConstIterator<QString, QVariant> QVariantMapConstIterator;
+
+/*!
+ @short An item representing a widget
+ Holds the properties of a widget (classname, name, parent, children ..).
+ @author Lucijan Busch <lucijan@kde.org>
+ */
+class KFORMEDITOR_EXPORT ObjectTreeItem
+{
+ public:
+ ObjectTreeItem(const QString &className, const QString &name, QWidget *widget, Container *parentContainer, Container *container=0);
+ virtual ~ObjectTreeItem();
+
+ QString name() const { return m_name; }
+ QString className() const { return m_className; }
+ QWidget* widget() const { return m_widget; }
+ EventEater* eventEater() const { return m_eater; }
+ ObjectTreeItem* parent() const { return m_parent; }
+ ObjectTreeList* children() { return &m_children; }
+
+ /*! \return a QMap<QString, QVariant> of all modified properties for this widget.
+ The QVariant is the old value (ie first value) of the property whose name is the QString. */
+ const QVariantMap* modifiedProperties() const { return &m_props;}
+
+ //! \return the widget's Container, or 0 if the widget is not a Container.
+ Container* container() const { return m_container;}
+
+ void setWidget(QWidget *w) { m_widget = w; }
+ void setParent(ObjectTreeItem *parent) { m_parent = parent;}
+
+ void debug(int ident);
+ void rename(const QString &name);
+
+ void addChild(ObjectTreeItem *it);
+ void removeChild(ObjectTreeItem *it);
+
+ /*! Adds \a property in the list of the modified properties for this object.
+ These modified properties are written in the .ui files when saving the form.
+ */
+ void addModifiedProperty(const QCString &property, const QVariant &oldValue);
+ void storeUnknownProperty(QDomElement &el);
+
+ /*! Adds subproperty \a property value \a value (a property of subwidget).
+ Remembering it for delayed setting is needed because on loading
+ the subwidget could be not created yet (true e.g. for KexiDBAutoField). */
+ void addSubproperty(const QCString &property, const QVariant& value);
+
+ /*! \return subproperties for this item, added by addSubproperty()
+ or 0 is there are no subproperties. */
+ QMap<QString, QVariant>* subproperties() const { return m_subprops; }
+
+ void setPixmapName(const QCString &property, const QString &name);
+ QString pixmapName(const QCString &property);
+
+ void setEnabled(bool enabled) { m_enabled = enabled; }
+ bool isEnabled() const { return m_enabled; }
+
+ int gridRow() const { return m_row; }
+ int gridCol() const { return m_col; }
+ int gridRowSpan() const { return m_rowspan; }
+ int gridColSpan() const { return m_colspan; }
+ bool spanMultipleCells() const { return m_span; }
+ void setGridPos(int row, int col, int rowspan, int colspan);
+
+ protected:
+ QString m_className;
+ QString m_name;
+ ObjectTreeList m_children;
+ QGuardedPtr<Container> m_container;
+ QMap<QString, QVariant> m_props;
+ QMap<QString, QVariant> *m_subprops;
+ QString m_unknownProps;
+ QMap<QCString, QString> m_pixmapNames;
+ ObjectTreeItem* m_parent;
+ QGuardedPtr<QWidget> m_widget;
+ QGuardedPtr<EventEater> m_eater;
+
+ bool m_enabled;
+
+ int m_row, m_col, m_rowspan, m_colspan;
+ bool m_span;
+
+ friend class ObjectTree;
+ friend class FormIO;
+};
+
+/*! @short Represents all the objects available within a form.
+ This class holds ObjectTreeItem for each widget in a Form. */
+class KFORMEDITOR_EXPORT ObjectTree : public ObjectTreeItem
+{
+ public:
+ ObjectTree(const QString &className=QString::null, const QString &name=QString::null,
+ QWidget *widget=0, Container *container=0);
+ virtual ~ObjectTree();
+
+ /*! Renames the item named \a oldname to \a newname. \return false if widget named \a newname
+ already exists and renaming failed. */
+ bool rename(const QString &oldname, const QString &newname );
+ /*! Sets \a newparent as new parent for the item whose name is \a name. */
+ bool reparent(const QString &name, const QString &newparent);
+
+ /*! \return the ObjectTreeItem named \a name, or 0 if doesn't exist. */
+ ObjectTreeItem* lookup(const QString &name);
+
+ /*! \return a dict containing all ObjectTreeItem in this ObjectTree. If you want to iterate on
+ this dict, use ObjectTreeDictIterator. */
+ ObjectTreeDict* dict() { return &m_treeDict; }
+
+ void addItem(ObjectTreeItem *parent, ObjectTreeItem *c);
+ void removeItem(const QString &name);
+ void removeItem(ObjectTreeItem *c);
+
+ /*! Generates a new, unique name for a new widget using prefix \a prefix
+ (e.g. if \a prefix is "lineEdit", "lineEdit1" is returned).
+ \a prefix must be a valid identifier.
+ If \a numberSuffixRequired is true (the default) a number suffix is mandatory.
+ If \a numberSuffixRequired is false and there's a widget prefix \a prefix,
+ then \a prefix is returned (e.g. if \a prefix is "lineEdit", and "lineEdit" doesn't exist yet,
+ "lineEdit" is returned). */
+ QCString generateUniqueName(const QCString &prefix, bool numberSuffixRequired = true);
+
+ private:
+ ObjectTreeDict m_treeDict;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/objecttreeview.cpp b/kexi/formeditor/objecttreeview.cpp
new file mode 100644
index 000000000..22ed6d7f9
--- /dev/null
+++ b/kexi/formeditor/objecttreeview.cpp
@@ -0,0 +1,377 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+
+#include <qpainter.h>
+
+#include <kiconloader.h>
+#include <klocale.h>
+
+#include "objecttree.h"
+#include "form.h"
+#include "container.h"
+#include "formmanager.h"
+#include "widgetlibrary.h"
+
+#include "objecttreeview.h"
+
+using namespace KFormDesigner;
+
+ObjectTreeViewItem::ObjectTreeViewItem(ObjectTreeViewItem *parent, ObjectTreeItem *item)
+ : KListViewItem(parent, item->name(), item->className())
+{
+ m_item = item;
+}
+
+ObjectTreeViewItem::ObjectTreeViewItem(KListView *list, ObjectTreeItem *item)
+ : KListViewItem(list, item ? item->name() : QString::null, item ? item->className() : QString::null)
+{
+ m_item = item;
+}
+
+ObjectTreeViewItem::~ObjectTreeViewItem()
+{
+}
+
+QString
+ObjectTreeViewItem::name() const
+{
+ if(m_item)
+ return m_item->name();
+ else
+ return QString::null;
+}
+
+void
+ObjectTreeViewItem::paintCell(QPainter *p, const QColorGroup & cg, int column, int width, int align)
+{
+ int margin = listView()->itemMargin();
+ if(column == 1)
+ {
+ if(!m_item)
+ return;
+ KListViewItem::paintCell(p, cg, column, width, align);
+ }
+ else
+ {
+ if(!m_item)
+ return;
+
+ p->fillRect(0,0,width, height(), QBrush(backgroundColor()));
+
+ if(isSelected())
+ {
+ p->fillRect(0,0,width, height(), QBrush(cg.highlight()));
+ p->setPen(cg.highlightedText());
+ }
+
+ QFont f = listView()->font();
+ p->save();
+ if(isSelected())
+ f.setBold(true);
+ p->setFont(f);
+ if(depth() == 0) // for edit tab order dialog
+ {
+ QString iconName
+ = ((ObjectTreeView*)listView())->iconNameForClass(m_item->widget()->className());
+ p->drawPixmap(margin, (height() - IconSize(KIcon::Small))/2 , SmallIcon(iconName));
+ p->drawText(
+ QRect(2*margin + IconSize(KIcon::Small),0,width, height()-1),
+ Qt::AlignVCenter, m_item->name());
+ }
+ else
+ p->drawText(QRect(margin,0,width, height()-1), Qt::AlignVCenter, m_item->name());
+ p->restore();
+
+ p->setPen( QColor(200,200,200) ); //like in t.v.
+ p->drawLine(width-1, 0, width-1, height()-1);
+ }
+
+ p->setPen( QColor(200,200,200) ); //like in t.v.
+ p->drawLine(-150, height()-1, width, height()-1 );
+}
+
+void
+ObjectTreeViewItem::paintBranches(QPainter *p, const QColorGroup &cg, int w, int y, int h)
+{
+ p->eraseRect(0,0,w,h);
+ ObjectTreeViewItem *item = (ObjectTreeViewItem*)firstChild();
+ if(!item || !item->m_item || !item->m_item->widget())
+ return;
+
+ p->save();
+ p->translate(0,y);
+ while(item)
+ {
+ p->fillRect(0,0,w, item->height(), QBrush(item->backgroundColor()));
+ p->fillRect(-150,0,150, item->height(), QBrush(item->backgroundColor()));
+ p->save();
+ p->setPen( QColor(200,200,200) ); //like in t.v.
+ p->drawLine(-150, item->height()-1, w, item->height()-1 );
+ p->restore();
+
+ if(item->isSelected())
+ {
+ p->fillRect(0,0,w, item->height(), QBrush(cg.highlight()));
+ p->fillRect(-150,0,150, item->height(), QBrush(cg.highlight()));
+ }
+
+ QString iconName
+ = ((ObjectTreeView*)listView())->iconNameForClass(item->m_item->widget()->className());
+ p->drawPixmap(
+ (w - IconSize(KIcon::Small))/2, (item->height() - IconSize(KIcon::Small))/2 ,
+ SmallIcon(iconName));
+
+ p->translate(0, item->totalHeight());
+ item = (ObjectTreeViewItem*)item->nextSibling();
+ }
+ p->restore();
+}
+
+void
+ObjectTreeViewItem::setup()
+{
+ KListViewItem::setup();
+ if(!m_item)
+ setHeight(0);
+}
+
+void
+ObjectTreeViewItem::setOpen( bool o )
+{
+ //don't allow to collapse the node, user may be tricked because we're not displaying [+] marks
+ if (o)
+ KListViewItem::setOpen(o);
+}
+
+// ObjectTreeView itself ----------------
+
+ObjectTreeView::ObjectTreeView(QWidget *parent, const char *name, bool tabStop)
+ : KListView(parent, name)
+ , m_form(0)
+{
+ addColumn(i18n("Name"), 130);
+ addColumn(i18n("Widget's type", "Type"), 100);
+
+ installEventFilter(this);
+
+ connect((QObject*)header(), SIGNAL(sectionHandleDoubleClicked(int)), this, SLOT(slotColumnSizeChanged(int)));
+ if(!tabStop)
+ {
+ setSelectionModeExt(Extended);
+ connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
+ connect(this, SIGNAL(contextMenu(KListView *, QListViewItem *, const QPoint&)), this, SLOT(displayContextMenu(KListView*, QListViewItem*, const QPoint&)));
+ }
+
+ setFullWidth(true);
+ setAllColumnsShowFocus(true);
+ setItemMargin(3);
+ setSorting(-1);
+}
+
+ObjectTreeView::~ObjectTreeView()
+{
+}
+
+QSize
+ObjectTreeView::sizeHint() const
+{
+ return QSize( QFontMetrics(font()).width(columnText(0)+columnText(1)+" "),
+ KListView::sizeHint().height());
+}
+
+QString
+ObjectTreeView::iconNameForClass(const QCString &classname)
+{
+ return m_form->library()->iconName(classname);
+}
+
+void
+ObjectTreeView::slotColumnSizeChanged(int)
+{
+ setColumnWidth(1, viewport()->width() - columnWidth(0));
+}
+
+void
+ObjectTreeView::displayContextMenu(KListView *list, QListViewItem *item, const QPoint &)
+{
+ if(list != this || !m_form || !item)
+ return;
+
+ QWidget *w = ((ObjectTreeViewItem*)item)->m_item->widget();
+ if(!w)
+ return;
+
+ FormManager::self()->createContextMenu(w, m_form->activeContainer());
+}
+
+ObjectTreeViewItem*
+ObjectTreeView::findItem(const QString &name)
+{
+ QListViewItemIterator it(this);
+ while(it.current())
+ {
+ ObjectTreeViewItem *item = static_cast<ObjectTreeViewItem*>(it.current());
+ if(item->name() == name)
+ {
+ return item;
+ }
+ it++;
+ }
+ return 0;
+}
+
+void
+ObjectTreeView::setSelectedWidget(QWidget *w, bool add)
+{
+ blockSignals(true); // to avoid recursion
+
+ if(!w)
+ {
+ clearSelection();
+ blockSignals(false);
+ return;
+ }
+
+ if(selectedItems().count() == 0)
+ add = false;
+
+ if(!add)
+ clearSelection();
+
+
+ QListViewItem *item = (QListViewItem*) findItem(w->name());
+ if(!add)
+ {
+ setCurrentItem(item);
+ setSelectionAnchor(item);
+ setSelected(item, true);
+ }
+ else
+ setSelected(item, true);
+
+ blockSignals(false);
+}
+
+void
+ObjectTreeView::slotSelectionChanged()
+{
+ const bool hadFocus = hasFocus();
+ QPtrList<QListViewItem> list = selectedItems();
+ m_form->selectFormWidget();
+ for(QListViewItem *item = list.first(); item; item = list.next())
+ {
+ ObjectTreeViewItem *it = static_cast<ObjectTreeViewItem*>(item);
+ QWidget *w = it->objectTree()->widget();
+ if(w && (m_form->selectedWidgets()->findRef(w) == -1))
+ m_form->setSelectedWidget(w, true, true);
+ }
+ if (hadFocus)
+ setFocus(); //restore focus
+}
+
+void
+ObjectTreeView::addItem(ObjectTreeItem *item)
+{
+ ObjectTreeViewItem *parent=0;
+
+ parent = findItem(item->parent()->name());
+ if(!parent)
+ return;
+
+ loadTree(item, parent);
+}
+
+void
+ObjectTreeView::removeItem(ObjectTreeItem *item)
+{
+ if(!item)
+ return;
+ ObjectTreeViewItem *it = findItem(item->name());
+ delete it;
+}
+
+void
+ObjectTreeView::renameItem(const QCString &oldname, const QCString &newname)
+{
+ if(findItem(newname))
+ return;
+ ObjectTreeViewItem *item = findItem(oldname);
+ if(!item)
+ return;
+ item->setText(0, newname);
+}
+
+void
+ObjectTreeView::setForm(Form *form)
+{
+ if (m_form)
+ disconnect(m_form, SIGNAL(destroying()), this, SLOT(slotBeforeFormDestroyed()));
+ m_form = form;
+ m_topItem = 0;
+ clear();
+
+ if(!m_form)
+ return;
+
+ connect(m_form, SIGNAL(destroying()), this, SLOT(slotBeforeFormDestroyed()));
+
+ // Creates the hidden top Item
+ m_topItem = new ObjectTreeViewItem(this);
+ m_topItem->setSelectable(false);
+ m_topItem->setOpen(true);
+
+ ObjectTree *tree = m_form->objectTree();
+ loadTree(tree, m_topItem);
+
+ if(!form->selectedWidgets()->isEmpty())
+ setSelectedWidget(form->selectedWidgets()->first());
+ else
+ setSelectedWidget(form->widget());
+}
+
+void
+ObjectTreeView::slotBeforeFormDestroyed()
+{
+ setForm(0);
+}
+
+ObjectTreeViewItem*
+ObjectTreeView::loadTree(ObjectTreeItem *item, ObjectTreeViewItem *parent)
+{
+ if(!item)
+ return 0;
+ ObjectTreeViewItem *treeItem = new ObjectTreeViewItem(parent, item);
+ treeItem->setOpen(true);
+
+ // The item is inserted by default at the beginning, but we want it to be at the end, so we move it
+ QListViewItem *last = parent->firstChild();
+ while(last->nextSibling())
+ last = last->nextSibling();
+ treeItem->moveItem(last);
+
+ ObjectTreeList *list = item->children();
+ for(ObjectTreeItem *it = list->first(); it; it = list->next())
+ loadTree(it, treeItem);
+
+ return treeItem;
+}
+
+#include "objecttreeview.moc"
diff --git a/kexi/formeditor/objecttreeview.h b/kexi/formeditor/objecttreeview.h
new file mode 100644
index 000000000..393f3ba08
--- /dev/null
+++ b/kexi/formeditor/objecttreeview.h
@@ -0,0 +1,129 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef OBJECTTREEVIEW_H
+#define OBJECTTREEVIEW_H
+
+#include <klistview.h>
+
+namespace KFormDesigner {
+
+class ObjectTreeItem;
+class Form;
+
+//! @short An item in ObjectTreeView associated with an ObjectTreeItem.
+class KFORMEDITOR_EXPORT ObjectTreeViewItem : public KListViewItem
+{
+ public:
+ ObjectTreeViewItem(ObjectTreeViewItem *parent, ObjectTreeItem *item);
+ ObjectTreeViewItem(KListView *list, ObjectTreeItem *item=0);
+ virtual ~ObjectTreeViewItem();
+
+ //! \return the item name, ie the ObjectTreeItem name
+ QString name() const;
+
+ //! \return the ObjectTreeItem associated to this item.
+ ObjectTreeItem* objectTree() const { return m_item; }
+
+ virtual void setOpen( bool o );
+
+ protected:
+ //! Reimplemented to draw custom contents (copied from Property Editor)
+ virtual void paintCell(QPainter *p, const QColorGroup & cg, int column, int width, int align);
+
+ //! Reimplemented to draw custom contents (copied from Property Editor)
+ virtual void paintBranches(QPainter *p, const QColorGroup &cg, int w, int y, int h);
+
+ //! Reimplemented to draw custom contents (copied from Property Editor)
+ virtual void setup();
+
+ private:
+ ObjectTreeItem *m_item;
+
+ friend class ObjectTreeView;
+};
+
+/*! @short A graphical view of Form's ObjectTree.
+ This is a KListView which represents an item for each widget in the form.
+ The actually selected widget is written bold
+ and selected. Clicking on a list item selects the corresponding widget in the Form.
+ */
+class KFORMEDITOR_EXPORT ObjectTreeView : public KListView
+{
+ Q_OBJECT
+
+ public:
+ ObjectTreeView(QWidget *parent=0, const char *name=0, bool tabStop = false);
+ virtual ~ObjectTreeView();
+
+ virtual QSize sizeHint() const;
+
+ /*! Sets \a form as the current Form in the list. The list will automatically
+ be filled with an item for each widget in the Form, and selection will be synced.
+ Nothing happens if \a form is already the current Form.
+ */
+ void setForm(Form *form);
+
+ //! \return the pixmap name for a given class, to be shown next to the widget name.
+ QString iconNameForClass(const QCString &classname);
+
+ public slots:
+ /*! Sets the widget \a w as selected item, so it will be written bold.
+ It is added to current selection if \a add is true. */
+ void setSelectedWidget(QWidget *w, bool add=false);
+
+ /*! Adds the ObjectTreeItem \a item in the list, with the appropriate parent. */
+ void addItem(ObjectTreeItem *item);
+
+ /*! Removess the ObjectTreeItem \a item from the list. */
+ void removeItem(ObjectTreeItem *item);
+
+ /*! Just renames the list item from \a oldname to \a newname. */
+ void renameItem(const QCString &oldname, const QCString &newname);
+
+ protected slots:
+ /*! This slot is called when the user right-click a list item.
+ The widget context menu is shown, as inisde the Form. */
+ void displayContextMenu(KListView *list, QListViewItem *item, const QPoint &p);
+
+ void slotColumnSizeChanged(int);
+
+ /*! The selected list item has changed, so we emit a signal to update the Form. */
+ void slotSelectionChanged();
+
+ /*! Called before Form object is destroyed. */
+ void slotBeforeFormDestroyed();
+
+ protected:
+ //! Internal function to fill the list.
+ ObjectTreeViewItem* loadTree(ObjectTreeItem *item, ObjectTreeViewItem *parent);
+
+ //! \return The item whose name is \a name.
+ ObjectTreeViewItem* findItem(const QString &name);
+
+ private:
+ Form *m_form;
+ ObjectTreeViewItem *m_topItem;
+
+ friend class TabStopDialog;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/resizehandle.cpp b/kexi/formeditor/resizehandle.cpp
new file mode 100644
index 000000000..0731926f4
--- /dev/null
+++ b/kexi/formeditor/resizehandle.cpp
@@ -0,0 +1,339 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qpainter.h>
+#include <qcursor.h>
+
+#include "form.h"
+#include "formmanager.h"
+#include "resizehandle.h"
+#include "container.h"
+#include "widgetfactory.h"
+#include "widgetlibrary.h"
+
+#define MINIMUM_WIDTH 10
+#define MINIMUM_HEIGHT 10
+
+using namespace KFormDesigner;
+
+ResizeHandle::ResizeHandle(ResizeHandleSet *set, HandlePos pos, bool editing)
+ : QWidget(set->m_widget->parentWidget()), m_set(set)
+{
+// setBackgroundMode(Qt::NoBackground);
+ m_dragging = false;
+ //m_editing = editing;
+ setEditingMode(editing);
+ setFixedWidth(6);
+ setFixedHeight(6);
+ m_pos = pos;
+ //m_buddy = buddy;
+ //buddy->installEventFilter(this);
+ m_set->m_widget->installEventFilter(this);
+//js installEventFilter(this);
+
+ updatePos();
+ show();
+}
+
+ResizeHandle::~ResizeHandle()
+{
+}
+
+void ResizeHandle::setEditingMode(bool editing)
+{
+ if(editing)
+ setBackgroundColor(blue);
+ else
+ setBackgroundColor(black);
+}
+
+void ResizeHandle::updatePos()
+{
+ switch (m_pos)
+ {
+ case TopLeft:
+ move(m_set->m_widget->x() - 3, m_set->m_widget->y() - 3);
+ setCursor(QCursor(SizeFDiagCursor));
+ break;
+ case TopCenter:
+ move(m_set->m_widget->x() + m_set->m_widget->width()/2 - 3, m_set->m_widget->y() - 3);
+ setCursor(QCursor(SizeVerCursor));
+ break;
+ case TopRight:
+ move(m_set->m_widget->x() + m_set->m_widget->width() - 3, m_set->m_widget->y() - 3);
+ setCursor(QCursor(SizeBDiagCursor));
+ break;
+ case LeftCenter:
+ move(m_set->m_widget->x() - 3, m_set->m_widget->y() + m_set->m_widget->height()/2 - 3);
+ setCursor(QCursor(SizeHorCursor));
+ break;
+ case RightCenter:
+ move(m_set->m_widget->x() + m_set->m_widget->width() - 3, m_set->m_widget->y() + m_set->m_widget->height()/2 - 3);
+ setCursor(QCursor(SizeHorCursor));
+ break;
+ case BottomLeft:
+ move(m_set->m_widget->x() - 3, m_set->m_widget->y() + m_set->m_widget->height() - 3);
+ setCursor(QCursor(SizeBDiagCursor));
+ break;
+ case BottomCenter:
+ move(m_set->m_widget->x() + m_set->m_widget->width()/2 - 3, m_set->m_widget->y() + m_set->m_widget->height() - 3);
+ setCursor(QCursor(SizeVerCursor));
+ break;
+ case BottomRight:
+ move(m_set->m_widget->x() + m_set->m_widget->width() - 3, m_set->m_widget->y() + m_set->m_widget->height() - 3);
+ setCursor(QCursor(SizeFDiagCursor));
+ break;
+ }
+}
+
+bool ResizeHandle::eventFilter(QObject *o, QEvent *ev)
+{
+ if (((ev->type() == QEvent::Move) || (ev->type() == QEvent::Resize)) && o == m_set->m_widget)
+ {
+ //QTimer::singleShot(0,this,SLOT(updatePos()));
+ updatePos();
+ }
+/* else if (ev->type() == QEvent::Paint && o == this) {
+ QPainter p;
+ p.begin(m_set->m_widget, true);
+ const bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ p.setPen(QPen(white, 10));
+ p.setRasterOp(XorROP);
+ p.drawRect( 20, 20, 100, 100 );//m_set->m_widget->x(), m_set->m_widget->y(), 150, 150 );
+ p.drawRect( m_set->m_widget->x(), m_set->m_widget->y(), 150, 150 );
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+
+ return true;
+ }*/
+ return false;
+}
+
+void ResizeHandle::mousePressEvent(QMouseEvent *ev)
+{
+ const bool startDragging = !m_dragging;
+ m_dragging = true;
+ m_x = ev->x();
+ m_y = ev->y();
+ if (startDragging) {
+// m_form->resizeHandleDraggingStarted(m_set->widget());
+ WidgetFactory *wfactory = m_set->m_form->library()->factoryForClassName(m_set->widget()->className());
+ if (wfactory)
+ wfactory->resetEditor();
+ }
+}
+
+void ResizeHandle::mouseMoveEvent(QMouseEvent *ev)
+{
+ int gridX = m_set->m_form->gridSize();
+ int gridY = m_set->m_form->gridSize();
+
+ if (!m_dragging) return;
+ //if(m_editing) return;
+
+ int tmpx = m_set->m_widget->x();
+ int tmpy = m_set->m_widget->y();
+ int tmpw = m_set->m_widget->width();
+ int tmph = m_set->m_widget->height();
+
+ int dummyx = ev->x() - m_x;
+ int dummyy = ev->y() - m_y;
+
+ if(FormManager::self()->snapWidgetsToGrid() && (ev->state() != (LeftButton|ControlButton|AltButton)))
+ {
+ dummyy = (int) ( ((float)dummyy) / ((float)gridY) + 0.5 );
+ dummyy *= gridY;
+ dummyx = (int) ( ((float)dummyx) / ((float)gridX) + 0.5 );
+ dummyx *= gridX;
+ }
+
+ switch (m_pos)
+ {
+ case TopRight:
+ tmpw += dummyx;
+ tmpy += dummyy;
+ tmph -= dummyy;
+ break;
+ case RightCenter:
+ tmpw += dummyx;
+ break;
+ case BottomRight:
+ tmpw += dummyx;
+ tmph += dummyy;
+ break;
+ case TopCenter:
+ tmpy += dummyy;
+ tmph -= dummyy;
+ break;
+ case BottomCenter:
+ tmph=tmph+dummyy;
+ break;
+ case TopLeft:
+ tmpx += dummyx;
+ tmpw -= dummyx;
+ tmpy += dummyy;
+ tmph -= dummyy;
+ break;
+ case LeftCenter:
+ tmpx += dummyx;
+ tmpw -= dummyx;
+ break;
+ case BottomLeft:
+ tmpx += dummyx;
+ tmpw -= dummyx;
+ tmph += dummyy;
+ break;
+ }
+
+ // Not move the top-left corner further than the bottom-right corner
+ if(tmpx >= m_set->m_widget->x() + m_set->m_widget->width())
+ {
+ tmpx = m_set->m_widget->x() + m_set->m_widget->width() - MINIMUM_WIDTH;
+ tmpw = MINIMUM_WIDTH;
+ }
+
+ if(tmpy >= m_set->m_widget->y() + m_set->m_widget->height())
+ {
+ tmpy = m_set->m_widget->y() + m_set->m_widget->height() - MINIMUM_HEIGHT;
+ tmph = MINIMUM_HEIGHT;
+ }
+
+ // Do not resize a widget outside of parent boundaries
+ if(tmpx < 0)
+ {
+ tmpw += tmpx;
+ tmpx = 0;
+ }
+ else if(tmpx + tmpw > m_set->m_widget->parentWidget()->width())
+ tmpw = m_set->m_widget->parentWidget()->width() - tmpx;
+
+ if(tmpy < 0)
+ {
+ tmph += tmpy;
+ tmpy = 0;
+ }
+ else if(tmpy + tmph > m_set->m_widget->parentWidget()->height())
+ tmph = m_set->m_widget->parentWidget()->height() - tmpy;
+
+ const bool shouldBeMoved = (tmpx != m_set->m_widget->x()) || (tmpy != m_set->m_widget->y());
+ const bool shouldBeResized = (tmpw != m_set->m_widget->width()) || (tmph != m_set->m_widget->height());
+
+ if (shouldBeMoved && shouldBeResized)
+ m_set->m_widget->hide();
+
+ // Resize it
+ if (shouldBeResized)
+ {
+ // Keep a QSize(10, 10) minimum size
+ tmpw = (tmpw < MINIMUM_WIDTH) ? MINIMUM_WIDTH : tmpw;
+ tmph = (tmph < MINIMUM_HEIGHT) ? MINIMUM_HEIGHT : tmph;
+ m_set->m_widget->resize(tmpw,tmph);
+ }
+
+ // Move the widget if necessary
+ if (shouldBeMoved)
+ m_set->m_widget->move(tmpx,tmpy);
+
+ if (shouldBeMoved && shouldBeResized)
+ m_set->m_widget->show();
+}
+
+void ResizeHandle::mouseReleaseEvent(QMouseEvent *)
+{
+ m_dragging = false;
+}
+
+void ResizeHandle::paintEvent( QPaintEvent * )
+{
+ //draw XORed background
+
+ /*QPainter p(this);
+ p.setRasterOp(XorROP);
+ p.fillRect(QRect(0, 0, 6, 6),white);
+ bitBlt( this, QPoint(0,0), parentWidget(), rect(), XorROP);*/
+}
+
+/////////////// ResizeHandleSet //////////////////
+
+ResizeHandleSet::ResizeHandleSet(QWidget *modify, Form *form, bool editing)
+: QObject(modify->parentWidget()), /*m_widget(modify),*/ m_form(form)
+{
+ m_widget = 0;
+ /*QWidget *parent = modify->parentWidget();
+
+ handles[0] = new ResizeHandle( modify, ResizeHandle::TopLeft, editing);
+ handles[1] = new ResizeHandle( modify, ResizeHandle::TopCenter, editing);
+ handles[2] = new ResizeHandle( modify, ResizeHandle::TopRight, editing);
+ handles[3] = new ResizeHandle( modify, ResizeHandle::LeftCenter, editing);
+ handles[4] = new ResizeHandle( modify, ResizeHandle::RightCenter, editing);
+ handles[5] = new ResizeHandle( modify, ResizeHandle::BottomLeft, editing);
+ handles[6] = new ResizeHandle( modify, ResizeHandle::BottomCenter, editing);
+ handles[7] = new ResizeHandle( modify, ResizeHandle::BottomRight, editing);*/
+ setWidget(modify, editing);
+}
+
+ResizeHandleSet::~ResizeHandleSet()
+{
+ for (int i = 0; i < 8; i++)
+ delete m_handles[i];
+}
+
+void
+ResizeHandleSet::setWidget(QWidget *modify, bool editing)
+{
+ if(modify == m_widget)
+ return;
+
+ if(m_widget) {
+ for(int i = 0; i < 8; i++)
+ delete m_handles[i];
+ }
+
+ m_widget = modify;
+
+ m_handles[0] = new ResizeHandle(this, ResizeHandle::TopLeft, editing);
+ m_handles[1] = new ResizeHandle(this, ResizeHandle::TopCenter, editing);
+ m_handles[2] = new ResizeHandle(this, ResizeHandle::TopRight, editing);
+ m_handles[3] = new ResizeHandle(this, ResizeHandle::LeftCenter, editing);
+ m_handles[4] = new ResizeHandle(this, ResizeHandle::RightCenter, editing);
+ m_handles[5] = new ResizeHandle(this, ResizeHandle::BottomLeft, editing);
+ m_handles[6] = new ResizeHandle(this, ResizeHandle::BottomCenter, editing);
+ m_handles[7] = new ResizeHandle(this, ResizeHandle::BottomRight, editing);
+}
+
+void
+ResizeHandleSet::raise()
+{
+ for(int i = 0; i < 8; i++)
+ m_handles[i]->raise();
+}
+
+void ResizeHandleSet::setEditingMode(bool editing)
+{
+ for(int i = 0; i < 8; i++)
+ m_handles[i]->setEditingMode(editing);
+}
+
+#include "resizehandle.moc"
diff --git a/kexi/formeditor/resizehandle.h b/kexi/formeditor/resizehandle.h
new file mode 100644
index 000000000..15ddbf364
--- /dev/null
+++ b/kexi/formeditor/resizehandle.h
@@ -0,0 +1,102 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFE_RESIZEHANDLER_H
+#define KFE_RESIZEHANDLER_H
+
+#include <qdict.h>
+#include <qguardedptr.h>
+#include <qwidget.h>
+
+/**
+ *@author Joseph Wenninger
+ */
+
+namespace KFormDesigner
+{
+
+class Form;
+class ResizeHandleSet;
+
+/**
+* a single widget which represents a dot for resizing a widget
+* @author Joseph Wenninger
+*/
+class KFORMEDITOR_EXPORT ResizeHandle : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ enum HandlePos { TopLeft = 0, TopCenter = 2, TopRight = 4, LeftCenter = 8, RightCenter = 16,
+ BottomLeft = 32, BottomCenter = 64, BottomRight = 128 };
+ ResizeHandle(ResizeHandleSet *set, HandlePos pos, bool editing=false);
+ virtual ~ResizeHandle();
+ void setEditingMode(bool editing);
+
+ protected:
+ virtual void mousePressEvent(QMouseEvent *ev);
+ virtual void mouseMoveEvent(QMouseEvent *ev);
+ virtual void mouseReleaseEvent(QMouseEvent *ev);
+ virtual void paintEvent( QPaintEvent *ev );
+
+ protected slots:
+ bool eventFilter(QObject *obj, QEvent *ev);
+ void updatePos();
+
+ private:
+ ResizeHandleSet *m_set;
+ HandlePos m_pos;
+ //QWidget *m_buddy;
+ bool m_dragging;
+ //bool m_editing;
+ int m_x;
+ int m_y;
+};
+
+/**
+* a set of resize handles (for resizing widgets)
+* @author Joseph Wenninger
+*/
+class KFORMEDITOR_EXPORT ResizeHandleSet: public QObject
+{
+ Q_OBJECT
+
+ public:
+ typedef QDict<ResizeHandleSet> Dict;
+
+ ResizeHandleSet(QWidget *modify, Form *form, bool editing = false);
+ ~ResizeHandleSet();
+
+ void setWidget(QWidget *modify, bool editing = false);
+ QWidget *widget() const { return m_widget; }
+ void raise();
+ void setEditingMode(bool editing);
+
+ private:
+ QGuardedPtr<ResizeHandle> m_handles[8];
+ QGuardedPtr<QWidget> m_widget;
+ QGuardedPtr<Form> m_form;
+ bool m_editing;
+
+ friend class ResizeHandle;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/richtextdialog.cpp b/kexi/formeditor/richtextdialog.cpp
new file mode 100644
index 000000000..f4596ed5a
--- /dev/null
+++ b/kexi/formeditor/richtextdialog.cpp
@@ -0,0 +1,210 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <qlayout.h>
+
+#include <ktoolbar.h>
+#include <kfontcombo.h>
+#include <kcolorcombo.h>
+#include <ktoolbarradiogroup.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include "richtextdialog.h"
+
+namespace KFormDesigner {
+
+//////////////////////////////////////////////////////////////////////////////////
+//////////////// A simple dialog to edit rich text ////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////
+
+RichTextDialog::RichTextDialog(QWidget *parent, const QString &text)
+: KDialogBase(parent, "richtext_dialog", true, i18n("Edit Rich Text"), Ok|Cancel, Ok, false)
+{
+ QFrame *frame = makeMainWidget();
+ QVBoxLayout *l = new QVBoxLayout(frame);
+ l->setAutoAdd(true);
+
+ m_toolbar = new KToolBar(frame);
+ m_toolbar->setFlat(true);
+ m_toolbar->show();
+
+ m_fcombo = new KFontCombo(m_toolbar);
+ m_toolbar->insertWidget(TBFont, 40, m_fcombo);
+ connect(m_fcombo, SIGNAL(textChanged(const QString&)), this, SLOT(changeFont(const QString &)));
+
+ m_toolbar->insertSeparator();
+
+ m_colCombo = new KColorCombo(m_toolbar);
+ m_toolbar->insertWidget(TBColor, 30, m_colCombo);
+ connect(m_colCombo, SIGNAL(activated(const QColor&)), this, SLOT(changeColor(const QColor&)));
+
+ m_toolbar->insertButton("text_bold", TBBold, true, i18n("Bold"));
+ m_toolbar->insertButton("text_italic", TBItalic, true, i18n("Italic"));
+ m_toolbar->insertButton("text_under", TBUnder, true, i18n("Underline"));
+ m_toolbar->setToggle(TBBold, true);
+ m_toolbar->setToggle(TBItalic, true);
+ m_toolbar->setToggle(TBUnder, true);
+ m_toolbar->insertSeparator();
+
+ m_toolbar->insertButton("text_super", TBSuper, true, i18n("Superscript"));
+ m_toolbar->insertButton("text_sub", TBSub, true, i18n("Subscript"));
+ m_toolbar->setToggle(TBSuper, true);
+ m_toolbar->setToggle(TBSub, true);
+ m_toolbar->insertSeparator();
+
+ KToolBarRadioGroup *group = new KToolBarRadioGroup(m_toolbar);
+ m_toolbar->insertButton("text_left", TBLeft, true, i18n("Left Align"));
+ m_toolbar->setToggle(TBLeft, true);
+ group->addButton(TBLeft);
+ m_toolbar->insertButton("text_center", TBCenter, true, i18n("Centered"));
+ m_toolbar->setToggle(TBCenter, true);
+ group->addButton(TBCenter);
+ m_toolbar->insertButton("text_right", TBRight, true, i18n("Right Align"));
+ m_toolbar->setToggle(TBRight, true);
+ group->addButton(TBRight);
+ m_toolbar->insertButton("text_block", TBJustify, true, i18n("Justified"));
+ m_toolbar->setToggle(TBJustify, true);
+ group->addButton(TBJustify);
+
+ connect(m_toolbar, SIGNAL(toggled(int)), this, SLOT(buttonToggled(int)));
+
+ m_edit = new KTextEdit(text, QString::null, frame, "richtext_edit");
+ m_edit->setTextFormat(RichText);
+ m_edit->setFocus();
+
+ connect(m_edit, SIGNAL(cursorPositionChanged(int, int)), this, SLOT(cursorPositionChanged(int, int)));
+ connect(m_edit, SIGNAL(clicked(int, int)), this, SLOT(cursorPositionChanged(int, int)));
+ connect(m_edit, SIGNAL(currentVerticalAlignmentChanged(VerticalAlignment)), this, SLOT(slotVerticalAlignmentChanged(VerticalAlignment)));
+
+ m_edit->moveCursor(QTextEdit::MoveEnd, false);
+ cursorPositionChanged(0, 0);
+ m_edit->show();
+ frame->show();
+}
+
+QString
+RichTextDialog::text()
+{
+ return m_edit->text();
+}
+
+void
+RichTextDialog::changeFont(const QString &font)
+{
+ m_edit->setFamily(font);
+}
+
+void
+RichTextDialog::changeColor(const QColor &color)
+{
+ m_edit->setColor(color);
+}
+
+void
+RichTextDialog::buttonToggled(int id)
+{
+ bool isOn = m_toolbar->isButtonOn(id);
+
+ switch(id)
+ {
+ case TBBold: m_edit->setBold(isOn); break;
+ case TBItalic: m_edit->setItalic(isOn); break;
+ case TBUnder: m_edit->setUnderline(isOn); break;
+ case TBSuper:
+ {
+ if(isOn && m_toolbar->isButtonOn(TBSub))
+ m_toolbar->setButton(TBSub, false);
+ m_edit->setVerticalAlignment(isOn ? QTextEdit::AlignSuperScript : QTextEdit::AlignNormal);
+ break;
+ }
+ case TBSub:
+ {
+ if(isOn && m_toolbar->isButtonOn(TBSuper))
+ m_toolbar->setButton(TBSuper, false);
+ m_edit->setVerticalAlignment(isOn ? QTextEdit::AlignSubScript : QTextEdit::AlignNormal);
+ break;
+ }
+ case TBLeft: case TBCenter:
+ case TBRight: case TBJustify:
+ {
+ if(!isOn) break;
+ switch(id)
+ {
+ case TBLeft: m_edit->setAlignment(Qt::AlignLeft); break;
+ case TBCenter: m_edit->setAlignment(Qt::AlignCenter); break;
+ case TBRight: m_edit->setAlignment(Qt::AlignRight); break;
+ case TBJustify: m_edit->setAlignment(Qt::AlignJustify); break;
+ default: break;
+ }
+ }
+ default: break;
+ }
+
+}
+
+void
+RichTextDialog::cursorPositionChanged(int, int)
+{
+ m_fcombo->setCurrentFont(m_edit->currentFont().family());
+ m_colCombo->setColor(m_edit->color());
+ m_toolbar->setButton(TBBold, m_edit->bold());
+ m_toolbar->setButton(TBItalic, m_edit->italic());
+ m_toolbar->setButton(TBUnder, m_edit->underline());
+
+ int id = 0;
+ switch(m_edit->alignment())
+ {
+ case Qt::AlignLeft: id = TBLeft; break;
+ case Qt::AlignCenter: id = TBCenter; break;
+ case Qt::AlignRight: id = TBRight; break;
+ case Qt::AlignJustify: id = TBJustify; break;
+ default: id = TBLeft; break;
+ }
+ m_toolbar->setButton(id, true);
+}
+
+void
+RichTextDialog::slotVerticalAlignmentChanged(VerticalAlignment align)
+{
+ switch(align)
+ {
+ case QTextEdit::AlignSuperScript:
+ {
+ m_toolbar->setButton(TBSuper, true);
+ m_toolbar->setButton(TBSub, false);
+ break;
+ }
+ case QTextEdit::AlignSubScript:
+ {
+ m_toolbar->setButton(TBSub, true);
+ m_toolbar->setButton(TBSuper, false);
+ break;
+ }
+ default:
+ {
+ m_toolbar->setButton(TBSuper, false);
+ m_toolbar->setButton(TBSub, false);
+ }
+ }
+}
+
+
+}
+
+#include "richtextdialog.moc"
diff --git a/kexi/formeditor/richtextdialog.h b/kexi/formeditor/richtextdialog.h
new file mode 100644
index 000000000..83c964644
--- /dev/null
+++ b/kexi/formeditor/richtextdialog.h
@@ -0,0 +1,63 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef RICHTEXTEDIT_DIALOG_H
+#define RICHTEXTEDIT_DIALOG_H
+
+#include <kdialogbase.h>
+#include <ktextedit.h>
+
+class KToolBar;
+class KFontCombo;
+class KColorCombo;
+
+namespace KFormDesigner {
+
+//! A simple dialog to edit rich text
+/*! It allows to change font name, style and color, alignment. */
+class KFORMEDITOR_EXPORT RichTextDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ RichTextDialog(QWidget *parent, const QString &text);
+ ~RichTextDialog(){;}
+
+ QString text();
+
+ enum VerticalAlignment{AlignNormal = QTextEdit::AlignNormal, AlignSuperScript = QTextEdit::AlignSuperScript, AlignSubScript = QTextEdit::AlignSubScript};
+
+ public slots:
+ void changeFont(const QString &);
+ void changeColor(const QColor&);
+ void buttonToggled(int);
+ void cursorPositionChanged(int, int);
+ void slotVerticalAlignmentChanged(VerticalAlignment align);
+
+ private:
+ enum { TBFont = 100, TBColor, TBBold, TBItalic, TBUnder, TBSuper, TBSub, TBLeft = 201, TBCenter, TBRight, TBJustify };
+ KToolBar *m_toolbar;
+ KTextEdit *m_edit;
+ KFontCombo *m_fcombo;
+ KColorCombo *m_colCombo;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/scripting/Makefile.am b/kexi/formeditor/scripting/Makefile.am
new file mode 100644
index 000000000..f03ccfdff
--- /dev/null
+++ b/kexi/formeditor/scripting/Makefile.am
@@ -0,0 +1,18 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_LTLIBRARIES = libkformdesigner_scripting.la
+
+libkformdesigner_scripting_la_SOURCES = formscript.cpp scriptIO.cpp scriptmanager.cpp
+
+libkformdesigner_scripting_la_LDFLAGS = $(all_libraries) -Wnounresolved
+libkformdesigner_scripting_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la
+
+libkformdesigner_scripting_la_METASOURCES = AUTO
+
+SUBDIRS = .
+
+INCLUDES= -I$(top_srcdir)/kexi/formeditor \
+ -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/scripting \
+ -I$(top_srcdir)/kexi/core $(all_includes)
+
diff --git a/kexi/formeditor/scripting/formscript.cpp b/kexi/formeditor/scripting/formscript.cpp
new file mode 100644
index 000000000..b598066e2
--- /dev/null
+++ b/kexi/formeditor/scripting/formscript.cpp
@@ -0,0 +1,109 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "formscript.h"
+#include "scriptmanager.h"
+
+#include "form.h"
+#include "objecttree.h"
+// Kross Includes
+#include "main/manager.h"
+#include "main/scriptcontainer.h"
+#include "api/exception.h"
+
+#include <kdebug.h>
+
+using namespace KFormDesigner;
+
+FormScript::FormScript(Form *form, ScriptManager *manager, const char *name)
+ : QObject(manager, name), m_manager(manager), m_form(form)
+{
+ m_script = manager->krossManager()->getScriptContainer(form->widget()->name());
+}
+
+FormScript::~FormScript()
+{
+}
+
+QString
+FormScript::getCode(const QString &)
+{
+ /// \todo Allow to select only one function
+ return m_script->getCode();
+}
+
+void
+FormScript::setCode(const QString &code)
+{
+ m_script->setCode(code);
+}
+
+void
+FormScript::appendCode(const QString &code)
+{
+ QString s = m_script->getCode();
+ s.append("\n");
+ s.append(code);
+ m_script->setCode(s);
+}
+
+bool
+FormScript::execute(const QString &functionName)
+{
+ /// \todo support return value and arguments
+ try {
+ m_script->callFunction(functionName);
+ }
+ catch(Kross::Api::Exception& e) {
+ kdDebug() << QString("EXCEPTION type='%1' description='%2'").arg(e.type()).arg(e.description()) << endl;
+ return false;
+ }
+ return true;
+}
+
+void
+FormScript::connectEvents()
+{
+ if(!m_form || !m_form->objectTree())
+ return;
+ // first call addQObject for each widget in the Form
+ ObjectTreeDict *dict = m_form->objectTree()->dict();
+ ObjectTreeDictIterator it(*dict);
+ for(; it.current(); ++it)
+ m_script->addQObject(it.current()->widget());
+ m_script->addQObject(m_form->widget());
+
+ // Then we connect all signals
+ QValueListConstIterator<Event*> endIt = m_list.constEnd();
+ for(QValueListConstIterator<Event*> it = m_list.constBegin(); it != endIt; ++it) {
+ if( (*it)->type() == Event::Slot) {
+ connect((*it)->sender(), (*it)->signal(), (*it)->receiver(), (*it)->slot());
+ }
+ else if( (*it)->type() == Event::UserFunction) {
+ m_script->connect((*it)->sender(), (*it)->signal(), (*it)->slot());
+ }
+ else if( (*it)->type() == Event::Action) {
+ /// \todo connect signals with actions
+ }
+ }
+}
+
+
+#include "formscript.moc"
+
diff --git a/kexi/formeditor/scripting/formscript.h b/kexi/formeditor/scripting/formscript.h
new file mode 100644
index 000000000..e3d63d74f
--- /dev/null
+++ b/kexi/formeditor/scripting/formscript.h
@@ -0,0 +1,78 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef FORMSCRIPT_H
+#define FORMSCRIPT_H
+
+#include "kexievents.h"
+
+#include <qobject.h>
+#include <qstring.h>
+#include <ksharedptr.h>
+
+class ScriptManager;
+
+namespace KFormDesigner {
+ class Form;
+}
+
+namespace Kross {
+ namespace Api {
+ class ScriptContainer;
+ }
+}
+
+using namespace KFormDesigner;
+
+//! A class that stores the code and events related to a single form
+class FormScript : public QObject
+{
+ Q_OBJECT
+
+ public:
+ FormScript(Form *form, ScriptManager *manager, const char *name=0);
+ ~FormScript();
+
+ EventList* eventList() { return &m_list; }
+ Kross::Api::ScriptContainer* scriptContainer() { return m_script; }
+
+ /*! \return The code of funtionName. If parameter is empty, it returns the full code of this form.*/
+ QString getCode(const QString &functionName=QString::null);
+ /*! Replaces the actual form code with the string \a code.
+ Called eg by (future) script editor. */
+ void setCode(const QString &code);
+ /*! Adds the string \a code at the end of current code. Used to add a function in the script. */
+ void appendCode(const QString &code);
+
+ /*! Executes the \a functionName.
+ \todo how do we give parameters? */
+ bool execute(const QString &functionName);
+ /*! Really connects all events in the list.
+ Also calls Kross;;Api::Manager::addObject for each widget in the form to allow the user to
+ use these widgets in the script. */
+ void connectEvents();
+
+ private:
+ ScriptManager *m_manager;
+ Form *m_form;
+ KSharedPtr<Kross::Api::ScriptContainer> m_script;
+ EventList m_list;
+};
+
+#endif
diff --git a/kexi/formeditor/scripting/scriptIO.cpp b/kexi/formeditor/scripting/scriptIO.cpp
new file mode 100644
index 000000000..8620a8aea
--- /dev/null
+++ b/kexi/formeditor/scripting/scriptIO.cpp
@@ -0,0 +1,186 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "scriptIO.h"
+#include "formscript.h"
+#include "kexievents.h"
+
+#include "form.h"
+#include "objecttree.h"
+// Kross includes
+#include "main/scriptcontainer.h"
+
+bool
+ScriptIO::saveFormEvents(QDomNode &parentNode, FormScript *formScript)
+{
+ QDomDocument domDoc = parentNode.ownerDocument();
+
+ // Save the form's code
+ if(!formScript->getCode().isEmpty()) {
+ QDomElement script = domDoc.createElement("script");
+ script.setAttribute("language", formScript->scriptContainer()->getInterpreterName());
+ parentNode.appendChild(script);
+ QDomText scriptCode = domDoc.createTextNode(formScript->getCode());
+ script.appendChild(scriptCode);
+ }
+
+ // Save all form events
+ if(!formScript->eventList()->isEmpty())
+ saveEventList(formScript->eventList(), parentNode);
+ return true;
+}
+
+bool
+ScriptIO::loadFormEvents(QDomNode &parentNode, Form *form, ScriptManager *manager)
+{
+ QDomElement script = parentNode.namedItem("script").toElement();
+ QDomElement events = parentNode.namedItem("events").toElement();
+
+ // Load script code
+ FormScript *formScript = new FormScript(form, manager);
+ if(!script.isNull()) {
+ formScript->scriptContainer()->setInterpreterName(script.attribute("language"));
+ formScript->setCode(script.text());
+ }
+
+ // Load all events in the EventList
+ if(!events.isNull()) {
+ for(QDomNode n = events.firstChild(); !n.isNull(); n = n.nextSibling())
+ loadEvent(n, formScript->eventList(), form);
+ }
+ return true;
+}
+
+bool
+ScriptIO::saveAllEventsForWidget(QObject *widget, FormScript *formScript, QDomNode &node)
+{
+ EventList *l = formScript->eventList()->allEventsForObject(widget);
+ saveEventList(l, node);
+ return true;
+}
+
+void
+ScriptIO::saveEvent(Event *event, QDomNode &parentNode)
+{
+ if(!event)
+ return;
+
+ QDomDocument domDoc = parentNode.ownerDocument();
+ QDomElement eventNode = domDoc.createElement("event");
+ eventNode.setAttribute("type", event->type());
+ parentNode.appendChild(eventNode);
+
+ switch(event->type()) {
+ case Event::Slot: {
+ QDomElement sender = domDoc.createElement("sender");
+ eventNode.appendChild(sender);
+ QDomText senderText = domDoc.createTextNode(event->sender() ? event->sender()->name() : "");
+ sender.appendChild(senderText);
+
+ QDomElement signal = domDoc.createElement("signal");
+ eventNode.appendChild(signal);
+ QDomText signalText = domDoc.createTextNode(event->signal());
+ signal.appendChild(signalText);
+
+ QDomElement receiver = domDoc.createElement("receiver");
+ eventNode.appendChild(receiver);
+ QDomText receiverText = domDoc.createTextNode(event->receiver() ? event->receiver()->name() : "");
+ receiver.appendChild(receiverText);
+
+ QDomElement slot = domDoc.createElement("slot");
+ eventNode.appendChild(slot);
+ QDomText slotText = domDoc.createTextNode(event->slot());
+ slot.appendChild(slotText);
+ break;
+ }
+
+ case Event::UserFunction: {
+ QDomElement sender = domDoc.createElement("sender");
+ eventNode.appendChild(sender);
+ QDomText senderText = domDoc.createTextNode(event->sender() ? event->sender()->name() : "");
+ sender.appendChild(senderText);
+
+ QDomElement signal = domDoc.createElement("signal");
+ eventNode.appendChild(signal);
+ QDomText signalText = domDoc.createTextNode(event->signal());
+ signal.appendChild(signalText);
+
+ QDomElement function = domDoc.createElement("function");
+ eventNode.appendChild(function);
+ QDomText functionText = domDoc.createTextNode(event->slot());
+ function.appendChild(functionText);
+ break;
+ }
+
+ case Event::Action:
+ // !\todo
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+ScriptIO::saveEventList(EventList *list, QDomNode &parentNode)
+{
+ if(!list || list->isEmpty())
+ return;
+
+ QDomDocument domDoc = parentNode.ownerDocument();
+ QDomElement events = domDoc.createElement("events");
+ parentNode.appendChild(events);
+
+ QValueListConstIterator<Event*> endIt = list->constEnd();
+ for(QValueListConstIterator<Event*> it = list->constBegin(); it != endIt; ++it)
+ saveEvent((*it), events);
+}
+
+void
+ScriptIO::loadEvent(QDomNode &node, EventList *list, Form *form)
+{
+ int type = node.toElement().attribute("type").toInt();
+ Event *event = new Event();
+
+ switch(type) {
+ case Event::Slot: {
+ ObjectTreeItem *sender = form->objectTree()->lookup(node.namedItem("sender").toElement().text());
+ event->setSender(sender ? sender->widget() : 0);
+ event->setSignal(node.namedItem("signal").toElement().text().local8Bit());
+ ObjectTreeItem *receiver = form->objectTree()->lookup(node.namedItem("receiver").toElement().text());
+ event->setReceiver(receiver ? receiver->widget() : 0);
+ event->setSlot(node.namedItem("slot").toElement().text().local8Bit());
+ event->setType(Event::Slot);
+ break;
+ }
+
+ case Event::UserFunction: {
+ ObjectTreeItem *sender = form->objectTree()->lookup(node.namedItem("sender").toElement().text());
+ event->setSender(sender ? sender->widget() : 0);
+ event->setSignal(node.namedItem("signal").toElement().text().local8Bit());
+ event->setSlot(node.namedItem("function").toElement().text().local8Bit());
+ event->setType(Event::UserFunction);
+ break;
+ }
+ default: break;
+ }
+
+ list->addEvent(event);
+}
+
diff --git a/kexi/formeditor/scripting/scriptIO.h b/kexi/formeditor/scripting/scriptIO.h
new file mode 100644
index 000000000..9fed06a65
--- /dev/null
+++ b/kexi/formeditor/scripting/scriptIO.h
@@ -0,0 +1,64 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef SCRIPTIO_H
+#define SCRIPTIO_H
+
+#include <qdom.h>
+
+class QString;
+class QObject;
+class Event;
+class EventList;
+class ScriptManager;
+class FormScript;
+
+namespace KFormDesigner {
+ class Form;
+}
+
+using namespace KFormDesigner;
+
+//! A static class to deal with loading/saving events from/to XML
+class ScriptIO
+{
+ public:
+ /*! Save the evnts of a form.
+ Creates an \<events\> tag, and then one \<event\> tag for each event.
+ Each event contains \<sender\> and \<receiver\> tags, with attributes depending on event type. */
+ static bool saveFormEvents(QDomNode &parentNode, FormScript *script);
+ /*! Reads the \<events\> tag (\a parentNode), then creates and fills a FormScript object linked to this \a form.
+ The new FormScript object is then added to ScriptManager list.*/
+ static bool loadFormEvents(QDomNode &parentNode, Form *form, ScriptManager *manager);
+
+ /*! Save only the events related to widget \a name in the FormScript \a fscript.
+ Used eg when copying/pasting widgets to keep also events related to it.*/
+ static bool saveAllEventsForWidget(QObject *widget, FormScript *fscript, QDomNode &node);
+
+ static void saveEvent(Event *event, QDomNode &parentNode);
+ static void saveEventList(EventList *list, QDomNode &parentNode);
+ static void loadEvent(QDomNode &node, EventList *list, Form *form);
+
+ protected:
+ ScriptIO() {;}
+ ~ScriptIO() {;}
+};
+
+#endif
+
diff --git a/kexi/formeditor/scripting/scriptmanager.cpp b/kexi/formeditor/scripting/scriptmanager.cpp
new file mode 100644
index 000000000..c572c6858
--- /dev/null
+++ b/kexi/formeditor/scripting/scriptmanager.cpp
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "scriptmanager.h"
+#include "formscript.h"
+
+#include "form.h"
+#include "formmanager.h"
+// Kross includes
+#include "main/manager.h"
+
+using KFormDesigner::Form;
+
+ScriptManager::ScriptManager(QObject *parent, const char *name)
+ : QObject(parent, name)
+{
+ m_manager = Kross::Api::Manager::scriptManager();
+ m_dict.setAutoDelete(true);
+}
+
+ScriptManager::~ScriptManager()
+{
+}
+
+FormScript*
+ScriptManager::newFormScript(Form *form)
+{
+ FormScript *script = new FormScript(form, this);
+ m_dict.insert(form, script);
+ return script;
+}
+
+FormScript*
+ScriptManager::scriptForForm(Form *form)
+{
+ return m_dict[form];
+}
+
+void
+ScriptManager::setFormManager(FormManager *manager)
+{
+ m_formManager = manager;
+ connect(m_formManager, SIGNAL(aboutToDeleteForm(KFormDesigner::Form*)), this, SLOT(slotFormDeleted(KFormDesigner::Form*)));
+ connect(m_formManager, SIGNAL(formCreated(KFormDesigner::Form*)), this, SLOT(newFormScript(KFormDesigner::Form*)));
+}
+
+void
+ScriptManager::slotFormDeleted(KFormDesigner::Form *form)
+{
+ m_dict.remove(form);
+}
+
+#include "scriptmanager.moc"
+
diff --git a/kexi/formeditor/scripting/scriptmanager.h b/kexi/formeditor/scripting/scriptmanager.h
new file mode 100644
index 000000000..258ce2cc4
--- /dev/null
+++ b/kexi/formeditor/scripting/scriptmanager.h
@@ -0,0 +1,69 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef SCRIPTMANAGER_H
+#define SCRIPTMANAGER_H
+
+#include <qobject.h>
+#include <qptrdict.h>
+
+class FormScript;
+
+namespace Kross {
+ namespace Api {
+ class Manager;
+ }
+}
+
+namespace KFormDesigner {
+ class FormManager;
+ class Form;
+}
+
+using namespace KFormDesigner;
+
+class ScriptManager : public QObject
+{
+ Q_OBJECT
+
+ public:
+ ScriptManager(QObject *parent=0, const char *name=0);
+ ~ScriptManager();
+
+ /*! \return The FormScript object associated to this Form. */
+ FormScript* scriptForForm(Form *form);
+
+ void setFormManager(FormManager *manager);
+ FormManager* formManager() { return m_formManager; }
+ Kross::Api::Manager* krossManager() { return m_manager; }
+
+ public slots:
+ /*! Called when a form is deleted. It is removed from the dict. */
+ void slotFormDeleted(KFormDesigner::Form *form);
+ /*! \return A new FormScript object associated to the Form \a form. */
+ FormScript* newFormScript(KFormDesigner::Form *form);
+
+ private:
+ Kross::Api::Manager *m_manager;
+ KFormDesigner::FormManager *m_formManager;
+ QPtrDict<FormScript> m_dict;
+};
+
+#endif
+
diff --git a/kexi/formeditor/spring.cpp b/kexi/formeditor/spring.cpp
new file mode 100644
index 000000000..e09cc2d7c
--- /dev/null
+++ b/kexi/formeditor/spring.cpp
@@ -0,0 +1,158 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qsizepolicy.h>
+#include <qpainter.h>
+#include <qdom.h>
+#include <qvariant.h>
+
+#include <kdebug.h>
+
+#include "objecttree.h"
+#include "container.h"
+#include "form.h"
+#include "formIO.h"
+#include "widgetlibrary.h"
+
+#include "spring.h"
+
+Spring::Spring(QWidget *parent, const char *name)
+ : QWidget(parent, name)
+{
+ m_edit = true;
+ m_orient = Horizontal;
+ setSizeType((SizeType)QSizePolicy::Expanding);
+}
+
+void
+Spring::setOrientation(Orientation orient)
+{
+ SizeType type = sizeType();
+ m_orient = orient;
+ setSizeType(type);
+ repaint();
+}
+
+Spring::SizeType
+Spring::sizeType() const
+{
+ if(m_orient == Vertical)
+ return (SizeType)sizePolicy().verData();
+ else
+ return (SizeType)sizePolicy().horData();
+}
+
+void
+Spring::setSizeType(SizeType size)
+{
+ if(m_orient == Vertical)
+ setSizePolicy(QSizePolicy::Minimum, (QSizePolicy::SizeType)size);
+ else
+ setSizePolicy( (QSizePolicy::SizeType)size, QSizePolicy::Minimum);
+}
+
+void
+Spring::paintEvent(QPaintEvent *ev)
+{
+ if(!m_edit)
+ return;
+
+ QPainter p(this);
+ if(!ev->erased())
+ p.eraseRect(0,0,width(), height());
+ p.setPen(QPen(Qt::white, 1));
+ p.setRasterOp(Qt::XorROP);
+ QPointArray pa(4);
+ if(m_orient == Vertical) {
+ uint part = (height()+16) / 16;
+ if (part<3)
+ part=3;
+ uint w = width()-1;
+ uint offset = 0;
+ for (uint i=0; i<4; i++, offset+=(part*4)) {
+ pa.putPoints(0, 4,
+ w/2,offset, w,offset+part, w,offset+part, w/2,offset+part*2);
+ p.drawCubicBezier(pa, 0);
+ pa.putPoints(0, 4,
+ w/2,offset+part*2, 0,offset+part*3, 0,offset+part*3, w/2,offset+part*4);
+ p.drawCubicBezier(pa, 0);
+ }
+ }
+ else {
+ uint part = (width()+16) / 16;
+ if (part<3)
+ part=3;
+ uint h = height()-1;
+ uint offset = 0;
+ for (uint i=0; i<4; i++, offset+=(part*4)) {
+ pa.putPoints(0, 4,
+ offset,h/2, offset+part,0, offset+part,0, offset+part*2,h/2);
+ p.drawCubicBezier(pa, 0);
+ pa.putPoints(0, 4,
+ offset+part*2,h/2, offset+part*3,h, offset+part*3,h, offset+part*4,h/2);
+ p.drawCubicBezier(pa, 0);
+ }
+ }
+}
+
+bool
+Spring::isPropertyVisible(const QCString &name)
+{
+ if((name == "name") || (name == "sizeType") || (name == "orientation") || (name == "geometry"))
+ return true;
+
+ return false;
+}
+
+
+void
+Spring::saveSpring(KFormDesigner::ObjectTreeItem *item, QDomElement &parentNode, QDomDocument &domDoc, bool insideGridLayout)
+{
+ QDomElement tclass = domDoc.createElement("spacer");
+ parentNode.appendChild(tclass);
+
+ if(insideGridLayout)
+ {
+ tclass.setAttribute("row", item->gridRow());
+ tclass.setAttribute("column", item->gridCol());
+ if(item->spanMultipleCells())
+ {
+ tclass.setAttribute("rowspan", item->gridRowSpan());
+ tclass.setAttribute("colspan", item->gridColSpan());
+ }
+ }
+
+ KFormDesigner::FormIO::savePropertyValue(tclass, domDoc, "name", item->widget()->property("name"), item->widget());
+
+ if(parentNode.tagName() == "widget")
+ KFormDesigner::FormIO::savePropertyValue(tclass, domDoc, "geometry", item->widget()->property("geometry"), item->widget());
+
+ if(!item->widget()->sizeHint().isValid())
+ KFormDesigner::FormIO::savePropertyValue(tclass, domDoc, "sizeHint", item->widget()->property("size"), item->widget());
+ else
+ KFormDesigner::FormIO::savePropertyValue(tclass, domDoc, "sizeHint", item->widget()->property("sizeHint"), item->widget());
+
+ KFormDesigner::FormIO::savePropertyValue(tclass, domDoc, "orientation", item->widget()->property("orientation"), item->widget());
+ KFormDesigner::FormIO::savePropertyValue(tclass, domDoc, "sizeType", item->widget()->property("sizeType"), item->widget());
+}
+
+
+#include "spring.moc"
+
diff --git a/kexi/formeditor/spring.h b/kexi/formeditor/spring.h
new file mode 100644
index 000000000..7ca3d35f4
--- /dev/null
+++ b/kexi/formeditor/spring.h
@@ -0,0 +1,74 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef SPACER_H
+#define SPACER_H
+
+#include <qwidget.h>
+
+class QDomElement;
+class QDomDocument;
+
+namespace KFormDesigner {
+
+class ObjectTreeItem;
+class Container;
+class WidgetLibrary;
+
+}
+
+class KFORMEDITOR_EXPORT Spring : public QWidget
+{
+ Q_OBJECT
+ Q_ENUMS(SizeType)
+ Q_PROPERTY(Orientation orientation READ orientation WRITE setOrientation)
+ Q_PROPERTY(SizeType sizeType READ sizeType WRITE setSizeType)
+
+ private:
+ enum {HSize = 6, HMask = 0x3f, VMask = HMask << HSize, MayGrow = 1, ExpMask = 2, MayShrink = 4 };
+ public:
+ enum SizeType {Fixed = 0, Minimum = MayGrow, Maximum = MayShrink, Preferred = MayGrow|MayShrink , MinimumExpanding = Minimum|ExpMask,
+ Expanding = MinimumExpanding|MayShrink };
+
+ public:
+ Spring(QWidget *parent, const char *name);
+ ~Spring() {;}
+
+ static bool isPropertyVisible(const QCString &name);
+ static void saveSpring(KFormDesigner::ObjectTreeItem *item, QDomElement &parent, QDomDocument &domDoc, bool insideGridLayout);
+
+ void setOrientation(Orientation orient);
+ Orientation orientation() const { return m_orient;}
+ void setSizeType(SizeType size);
+ SizeType sizeType() const;
+
+ void setPreviewMode() { m_edit = false; }
+
+ private:
+ void paintEvent(QPaintEvent *ev);
+
+ private:
+ Orientation m_orient;
+ bool m_edit;
+};
+
+
+#endif
+
+
diff --git a/kexi/formeditor/tabstopdialog.cpp b/kexi/formeditor/tabstopdialog.cpp
new file mode 100644
index 000000000..067c333a0
--- /dev/null
+++ b/kexi/formeditor/tabstopdialog.cpp
@@ -0,0 +1,168 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qtooltip.h>
+
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kpushbutton.h>
+
+#include "form.h"
+#include "objecttreeview.h"
+
+#include "tabstopdialog.h"
+
+using namespace KFormDesigner;
+
+//////////////////////////////////////////////////////////////////////////////////
+////////// The Tab Stop Dialog to edit tab order ///////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////
+
+TabStopDialog::TabStopDialog(QWidget *parent)
+: KDialogBase(parent, "tabstop_dialog", true, i18n("Edit Tab Order"), Ok|Cancel, Ok, false)
+{
+ QFrame *frame = makeMainWidget();
+ QGridLayout *l = new QGridLayout(frame, 2, 2, 0, 6);
+ m_treeview = new ObjectTreeView(frame, "tabstops_treeview", true);
+ m_treeview->setItemsMovable(true);
+ m_treeview->setDragEnabled(true);
+ m_treeview->setDropVisualizer(true);
+ m_treeview->setAcceptDrops(true);
+ m_treeview->setFocus();
+ l->addWidget(m_treeview, 0, 0);
+
+ m_treeview->m_form = 0;
+ connect(m_treeview, SIGNAL(currentChanged(QListViewItem*)), this, SLOT(updateButtons(QListViewItem*)));
+ connect(m_treeview, SIGNAL(moved(QListViewItem*, QListViewItem*, QListViewItem*)), this, SLOT(updateButtons(QListViewItem*)));
+
+ QVBoxLayout *vbox = new QVBoxLayout();
+ l->addLayout(vbox, 0, 1);
+ m_btnUp = new KPushButton(SmallIconSet("1uparrow"), i18n("Move Up"), frame);
+ QToolTip::add( m_btnUp, i18n("Move widget up") );
+ vbox->addWidget(m_btnUp);
+ connect(m_btnUp, SIGNAL(clicked()), this, SLOT(moveItemUp()));
+
+ m_btnDown = new KPushButton(SmallIconSet("1downarrow"), i18n("Move Down"), frame);
+ QToolTip::add( m_btnDown, i18n("Move widget down") );
+ vbox->addWidget(m_btnDown);
+ connect(m_btnDown, SIGNAL(clicked()), this, SLOT(moveItemDown()));
+ vbox->addStretch();
+
+ m_check = new QCheckBox(i18n("Handle tab order automatically"), frame, "tabstops_check");
+ connect(m_check, SIGNAL(toggled(bool)), this, SLOT(slotRadioClicked(bool)));
+ l->addMultiCellWidget(m_check, 1, 1, 0, 1);
+
+ updateGeometry();
+ setInitialSize(QSize(500+m_btnUp->width(), QMAX(400,m_treeview->height())));
+}
+
+TabStopDialog::~TabStopDialog()
+{
+}
+
+int TabStopDialog::exec(Form *form)
+{
+ m_treeview->clear();
+ m_treeview->m_form = form;
+
+ if(form->autoTabStops())
+ form->autoAssignTabStops();
+ form->updateTabStopsOrder();
+ ObjectTreeListIterator it( form->tabStopsIterator() );
+ it.toLast();
+ for(;it.current(); --it)
+ new ObjectTreeViewItem(m_treeview, it.current());
+
+ m_check->setChecked(form->autoTabStops());
+
+ if (m_treeview->firstChild()) {
+ m_treeview->setCurrentItem(m_treeview->firstChild());
+ m_treeview->setSelected(m_treeview->firstChild(), true);
+ }
+
+ if (QDialog::Rejected == KDialogBase::exec())
+ return QDialog::Rejected;
+
+ //accepted
+ form->setAutoTabStops(m_check->isChecked());
+ if(form->autoTabStops())
+ {
+ form->autoAssignTabStops();
+ return QDialog::Accepted;
+ }
+
+ //add items to the order list
+ form->tabStops()->clear();
+ ObjectTreeViewItem *item = (ObjectTreeViewItem*)m_treeview->firstChild();
+ while(item)
+ {
+ ObjectTreeItem *tree = item->objectTree();
+ if(tree)
+ form->tabStops()->append(tree);
+ item = (ObjectTreeViewItem*)item->nextSibling();
+ }
+ return QDialog::Accepted;
+}
+
+void
+TabStopDialog::moveItemUp()
+{
+ if (!m_treeview->selectedItem())
+ return;
+ QListViewItem *before = m_treeview->selectedItem()->itemAbove();
+ before->moveItem(m_treeview->selectedItem());
+ updateButtons(m_treeview->selectedItem());
+}
+
+void
+TabStopDialog::moveItemDown()
+{
+ QListViewItem *item = m_treeview->selectedItem();
+ if (!item)
+ return;
+ item->moveItem( item->nextSibling());
+ updateButtons(item);
+}
+
+void
+TabStopDialog::updateButtons(QListViewItem *item)
+{
+ m_btnUp->setEnabled( item && (item->itemAbove() && m_treeview->isEnabled()
+ /*&& (item->itemAbove()->parent() == item->parent()))*/ ));
+ m_btnDown->setEnabled( item && item->nextSibling() && m_treeview->isEnabled() );
+}
+
+void
+TabStopDialog::slotRadioClicked(bool isOn)
+{
+ m_treeview->setEnabled(!isOn);
+ updateButtons( m_treeview->selectedItem() );
+}
+
+bool
+TabStopDialog::autoTabStops() const
+{
+ return m_check->isChecked();
+}
+
+#include "tabstopdialog.moc"
+
diff --git a/kexi/formeditor/tabstopdialog.h b/kexi/formeditor/tabstopdialog.h
new file mode 100644
index 000000000..9ae6a88ae
--- /dev/null
+++ b/kexi/formeditor/tabstopdialog.h
@@ -0,0 +1,63 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef TABSTOPEDIT_DIALOG_H
+#define TABSTOPEDIT_DIALOG_H
+
+#include <kdialogbase.h>
+
+class QListViewItem;
+class QCheckBox;
+class QToolButton;
+class KPushButton;
+
+namespace KFormDesigner {
+
+class Form;
+class ObjectTreeView;
+
+//! A dialog to edit Form tab stops
+/*! The user can change the order by dragging list items or using buttons at the right.
+ The tab stops can be arranged automatically (see \ref Form::autoAssignTabStops()). */
+class KFORMEDITOR_EXPORT TabStopDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ TabStopDialog(QWidget *parent);
+ virtual ~TabStopDialog();
+
+ public slots:
+ int exec(KFormDesigner::Form *form);
+ void moveItemUp();
+ void moveItemDown();
+ void updateButtons(QListViewItem*);
+ void slotRadioClicked(bool isOn);
+
+ bool autoTabStops() const;
+
+ protected:
+ ObjectTreeView *m_treeview;
+ KPushButton *m_btnUp, *m_btnDown;
+ QCheckBox *m_check;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/test/Makefile.am b/kexi/formeditor/test/Makefile.am
new file mode 100644
index 000000000..f039daeb3
--- /dev/null
+++ b/kexi/formeditor/test/Makefile.am
@@ -0,0 +1,46 @@
+## Makefile.am for kformdesigner
+
+include $(top_srcdir)/kexi/Makefile.global
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+bin_PROGRAMS = kformdesigner
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/formeditor \
+ -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore $(all_includes)
+
+kformdesigner_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
+kformdesigner_LDADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la
+
+kformdesigner_SOURCES = main.cpp kfd_mainwindow.cpp
+
+# client stuff
+
+METASOURCES = AUTO
+
+kdelnkdir = $(kde_appsdir)/Development
+kdelnk_DATA = kformdesigner.desktop
+
+rcdir = $(kde_datadir)/kformdesigner
+rc_DATA = kfd_mainwindow.rc
+
+
+# KFormDesigner KPart
+kde_module_LTLIBRARIES = libkformdesigner_part.la
+
+libkformdesigner_part_la_SOURCES = kfd_part.cpp
+libkformdesigner_part_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) $(VER_INFO)
+libkformdesigner_part_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la $(LIB_KFILE)
+
+# this is where the desktop file will go
+partdesktopdir = $(kde_servicesdir)
+partdesktop_DATA = kformdesigner_part.desktop
+
+# this is where the part's XML-GUI resource file goes
+partrcdir = $(kde_datadir)/kformdesigner_part
+partrc_DATA = kformdesigner_part.rc kformdesigner_part_shell.rc
+
+KDE_ICON = kformdesigner
diff --git a/kexi/formeditor/test/cr16-app-kformdesigner.png b/kexi/formeditor/test/cr16-app-kformdesigner.png
new file mode 100644
index 000000000..fee34b1ac
--- /dev/null
+++ b/kexi/formeditor/test/cr16-app-kformdesigner.png
Binary files differ
diff --git a/kexi/formeditor/test/cr22-app-kformdesigner.png b/kexi/formeditor/test/cr22-app-kformdesigner.png
new file mode 100644
index 000000000..50f608cca
--- /dev/null
+++ b/kexi/formeditor/test/cr22-app-kformdesigner.png
Binary files differ
diff --git a/kexi/formeditor/test/cr32-app-kformdesigner.png b/kexi/formeditor/test/cr32-app-kformdesigner.png
new file mode 100644
index 000000000..e98d13d9b
--- /dev/null
+++ b/kexi/formeditor/test/cr32-app-kformdesigner.png
Binary files differ
diff --git a/kexi/formeditor/test/kfd_mainwindow.cpp b/kexi/formeditor/test/kfd_mainwindow.cpp
new file mode 100644
index 000000000..e9d52e47a
--- /dev/null
+++ b/kexi/formeditor/test/kfd_mainwindow.cpp
@@ -0,0 +1,88 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <kaction.h>
+#include <kstdaction.h>
+#include <kurl.h>
+#include <kdebug.h>
+#include <klibloader.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kapplication.h>
+
+#include "kfd_mainwindow.h"
+
+KFDMainWindow::KFDMainWindow()
+ : KParts::MainWindow()
+{
+ setXMLFile("kfd_mainwindow.rc");
+
+ setupActions();
+ //statusBar()->show();
+
+ KLibFactory *factory = KLibLoader::self()->factory("libkformdesigner_part");
+ if (factory)
+ {
+ QStringList list;
+ list << "shell" << "multipleMode";
+ m_part = static_cast<KParts::ReadWritePart *>( factory->create(this, "kformdesigner_part", "KParts::ReadWritePart", list) );
+
+ if(m_part)
+ {
+ setCentralWidget(m_part->widget());
+ createGUI(m_part);
+ }
+ }
+ else
+ {
+ KMessageBox::error(this, i18n("Could not find the KFormDesigner part. Please check your installation."));
+ kapp->quit();
+ return;
+ }
+
+ setAutoSaveSettings();
+}
+
+void
+KFDMainWindow::loadUIFile(const QString &filename)
+{
+ loadUIFile(KURL::fromPathOrURL(filename));
+}
+
+void
+KFDMainWindow::loadUIFile(const KURL &url)
+{
+ m_part->openURL(url);
+}
+
+void
+KFDMainWindow::setupActions()
+{
+ KStdAction::quit(kapp, SLOT(quit()), actionCollection());
+}
+
+bool
+KFDMainWindow::queryClose()
+{
+ if(!m_part)
+ return true;
+
+ return m_part->closeURL();
+}
+
+#include "kfd_mainwindow.moc"
diff --git a/kexi/formeditor/test/kfd_mainwindow.h b/kexi/formeditor/test/kfd_mainwindow.h
new file mode 100644
index 000000000..03892a1ed
--- /dev/null
+++ b/kexi/formeditor/test/kfd_mainwindow.h
@@ -0,0 +1,46 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNER_MAINWINDOW_H
+#define KFORMDESIGNER_MAINWINDOW_H
+
+#include <kparts/mainwindow.h>
+
+class KFDMainWindow : public KParts::MainWindow
+{
+ Q_OBJECT
+
+ public:
+ KFDMainWindow();
+ ~KFDMainWindow() {;}
+
+ /** @todo change it to bool */
+ void loadUIFile(const QString &filename);
+ /** @todo change it to bool */
+ void loadUIFile(const KURL &url);
+ virtual bool queryClose();
+
+ private:
+ void setupActions();
+
+ private:
+ KParts::ReadWritePart *m_part;
+};
+
+#endif
diff --git a/kexi/formeditor/test/kfd_mainwindow.rc b/kexi/formeditor/test/kfd_mainwindow.rc
new file mode 100644
index 000000000..66f48600c
--- /dev/null
+++ b/kexi/formeditor/test/kfd_mainwindow.rc
@@ -0,0 +1,26 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kfd_mainwindow" version="1">
+<MenuBar>
+ <Menu noMerge="1" name="file"><text>&amp;File</text>
+ <Merge/>
+ <Separator/>
+ <Action name="file_quit"/>
+ </Menu>
+ <Menu noMerge="1" name="edit"><text>&amp;Edit</text>
+ <Merge/>
+ </Menu>
+ <Menu noMerge="1" name="view"><text>&amp;View</text>
+ <Merge/>
+ </Menu>
+ <Menu noMerge="1" name="tools"><text>&amp;Tools</text>
+ <Merge/>
+ </Menu>
+ <Menu noMerge="1" name="widgets"><text>&amp;Widgets</text>
+ <Merge/>
+ </Menu>
+ <Menu noMerge="1" name="format"><text>&amp;Format</text>
+ <Merge/>
+ </Menu>
+ <Merge/>
+</MenuBar>
+</kpartgui> \ No newline at end of file
diff --git a/kexi/formeditor/test/kfd_part.cpp b/kexi/formeditor/test/kfd_part.cpp
new file mode 100644
index 000000000..48832be8f
--- /dev/null
+++ b/kexi/formeditor/test/kfd_part.cpp
@@ -0,0 +1,729 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qworkspace.h>
+#include <qdockarea.h>
+#include <qdockwindow.h>
+#include <qhbox.h>
+#include <qpainter.h>
+#include <qevent.h>
+#include <qobjectlist.h>
+
+#include <kdeversion.h>
+#include <kaction.h>
+#include <kinstance.h>
+#include <klocale.h>
+#include <kaboutdata.h>
+#include <kdebug.h>
+#include <kstdaction.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kfiledialog.h>
+#include <klibloader.h>
+#include <kmessagebox.h>
+
+#include "form.h"
+#include "formIO.h"
+#include "objecttree.h"
+#include "container.h"
+#include "formmanager.h"
+#include "objecttreeview.h"
+#include <koproperty/set.h>
+#include <koproperty/editor.h>
+
+#include "kfd_part.h"
+
+/*
+#define ENABLE_ACTION(name, enable)
+ if(actionCollection()->action( name ))
+ actionCollection()->action( name )->setEnabled( enable )
+*/
+
+class KFDPart_FormManager : public KFormDesigner::FormManager
+{
+ public:
+ /*! Constructs FormManager object.
+ See WidgetLibrary's constructor documentation for information about
+ \a supportedFactoryGroups parameter.
+ Using \a options you can control manager's behaviour, see \ref Options. */
+ KFDPart_FormManager(KFormDesignerPart *part, int options = 0, const char *name = 0)
+ : KFormDesigner::FormManager(part, options, name)
+ , m_part(part)
+ {
+ }
+
+ virtual KAction* action( const char* name)
+ {
+ return m_part->actionCollection()->action( name );
+ }
+
+ virtual void enableAction( const char* name, bool enable ) {
+ if(m_part->actionCollection()->action( name ))
+ m_part->actionCollection()->action( name )->setEnabled( enable );
+ }
+
+ KFormDesignerPart *m_part;
+};
+
+KInstance *KFDFactory::m_instance = 0L;
+
+KFDFactory::KFDFactory()
+{}
+
+KFDFactory::~KFDFactory()
+{
+ if (m_instance)
+ {
+ delete m_instance->aboutData();
+ delete m_instance;
+ }
+
+ m_instance = 0;
+}
+
+KParts::Part*
+KFDFactory::createPartObject( QWidget *parentWidget, const char *, QObject *, const char *name,
+ const char *classname, const QStringList &args)
+{
+ bool readOnly = (classname == "KParts::ReadOnlyPart");
+ KFormDesignerPart *part = new KFormDesignerPart(parentWidget, name, readOnly, args);
+ return part;
+}
+
+KInstance*
+KFDFactory::instance()
+{
+ if (!m_instance)
+ m_instance = new KInstance(aboutData());
+ return m_instance;
+}
+
+KAboutData*
+KFDFactory::aboutData()
+{
+ KAboutData *about = new KAboutData("kformdesigner_part", I18N_NOOP("Form Designer Part"), "0.3");
+ return about;
+}
+
+KFormDesigner::WidgetLibrary* KFormDesignerPart::static_formsLibrary = 0L;
+
+KFormDesignerPart::KFormDesignerPart(QWidget *parent, const char *name, bool readOnly, const QStringList &args)
+: KParts::ReadWritePart(parent, name), m_count(0)
+{
+ setInstance(KFDFactory::instance());
+ instance()->iconLoader()->addAppDir("kexi");
+ instance()->iconLoader()->addAppDir("kformdesigner");
+
+ setReadWrite(!readOnly);
+ m_uniqueFormMode = true;
+ m_openingFile = false;
+
+ if(!args.grep("multipleMode").isEmpty())
+ setUniqueFormMode(false);
+ m_inShell = (!args.grep("shell").isEmpty());
+
+ QHBox *container = new QHBox(parent, "kfd_container_widget");
+
+ m_workspace = new QWorkspace(container, "kfd_workspace");
+ m_workspace->show();
+
+ QStringList supportedFactoryGroups;
+/* @todo add configuration for supported factory groups */
+ static_formsLibrary = KFormDesigner::FormManager::createWidgetLibrary(
+ new KFDPart_FormManager(this, 0, "kfd_manager"), supportedFactoryGroups );
+
+ if(!readOnly)
+ {
+ QDockArea *dockArea = new QDockArea(Vertical, QDockArea::Reverse, container, "kfd_part_dockarea");
+
+ QDockWindow *dockTree = new QDockWindow(dockArea);
+ KFormDesigner::ObjectTreeView *view = new KFormDesigner::ObjectTreeView(dockTree);
+ dockTree->setWidget(view);
+ dockTree->setCaption(i18n("Objects"));
+ dockTree->setResizeEnabled(true);
+ dockTree->setFixedExtentWidth(256);
+
+ QDockWindow *dockEditor = new QDockWindow(dockArea);
+ m_editor = new KoProperty::Editor(dockEditor);
+ dockEditor->setWidget(m_editor);
+ dockEditor->setCaption(i18n("Properties"));
+ dockEditor->setResizeEnabled(true);
+
+ KFormDesigner::FormManager::self()->setEditor(m_editor);
+ KFormDesigner::FormManager::self()->setObjectTreeView(view);
+
+ setupActions();
+ setModified(false);
+
+ // action stuff
+// connect(m_manager, SIGNAL(widgetSelected(KFormDesigner::Form*, bool)), SLOT(slotWidgetSelected(KFormDesigner::Form*, bool)));
+// connect(m_manager, SIGNAL(formWidgetSelected(KFormDesigner::Form*)), SLOT(slotFormWidgetSelected(KFormDesigner::Form*)));
+// connect(m_manager, SIGNAL(noFormSelected()), SLOT(slotNoFormSelected()));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(undoEnabled(bool, const QString&)), SLOT(setUndoEnabled(bool, const QString&)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(redoEnabled(bool, const QString&)), SLOT(setRedoEnabled(bool, const QString&)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(dirty(KFormDesigner::Form*, bool)), this, SLOT(slotFormModified(KFormDesigner::Form*, bool)));
+ }
+
+ container->show();
+ setWidget(container);
+ connect(m_workspace, SIGNAL(windowActivated(QWidget*)), KFormDesigner::FormManager::self(), SLOT(windowChanged(QWidget*)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(propertySetSwitched(KoProperty::Set*, bool, const QCString&)),
+ this, SLOT(slotPropertySetSwitched(KoProperty::Set*, bool, const QCString&)));
+
+// slotNoFormSelected();
+ KFormDesigner::FormManager::self()->emitNoFormSelected();
+}
+
+KFormDesignerPart::~KFormDesignerPart()
+{
+}
+
+KFormDesigner::WidgetLibrary* KFormDesignerPart::formsLibrary()
+{
+ return static_formsLibrary;
+}
+
+void
+KFormDesignerPart::setupActions()
+{
+ KStdAction::open(this, SLOT(open()), actionCollection());
+ KStdAction::openNew(this, SLOT(createBlankForm()), actionCollection());
+ KStdAction::save(this, SLOT(save()), actionCollection());
+ KStdAction::saveAs(this, SLOT(saveAs()), actionCollection());
+ KStdAction::cut(KFormDesigner::FormManager::self(), SLOT(cutWidget()), actionCollection());
+ KStdAction::copy(KFormDesigner::FormManager::self(), SLOT(copyWidget()), actionCollection());
+ KStdAction::paste(KFormDesigner::FormManager::self(), SLOT(pasteWidget()), actionCollection());
+ KStdAction::undo(KFormDesigner::FormManager::self(), SLOT(undo()), actionCollection());
+ KStdAction::redo(KFormDesigner::FormManager::self(), SLOT(redo()), actionCollection());
+ KStdAction::selectAll(KFormDesigner::FormManager::self(), SLOT(selectAll()), actionCollection());
+ new KAction(i18n("Clear Widget Contents"), "editclear", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(clearWidgetContent()), actionCollection(), "clear_contents");
+ new KAction(i18n("Delete Widget"), "editdelete", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(deleteWidget()), actionCollection(), "edit_delete");
+ new KAction(i18n("Preview Form"), "filequickprint", CTRL+Key_T, this, SLOT(slotPreviewForm()), actionCollection(), "preview_form");
+ new KAction(i18n("Edit Tab Order"), "tab_order", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(editTabOrder()), actionCollection(), "taborder");
+ new KAction(i18n("Edit Pixmap Collection"), "icons", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(editFormPixmapCollection()), actionCollection(), "pixmap_collection");
+ new KAction(i18n("Edit Form Connections"), "connections", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(editConnections()), actionCollection(), "form_connections");
+
+ KActionMenu *layoutMenu = new KActionMenu(i18n("Group Widgets"), "", actionCollection(), "layout_menu");
+ layoutMenu->insert(new KAction(i18n("&Horizontally"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutHBox()), actionCollection(), "layout_hbox"));
+ layoutMenu->insert(new KAction(i18n("&Vertically"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutVBox()), actionCollection(), "layout_vbox"));
+ layoutMenu->insert(new KAction(i18n("In &Grid"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutGrid()), actionCollection(), "layout_grid"));
+ layoutMenu->insert(new KAction(i18n("By &Rows"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutHFlow()), actionCollection(), "layout_hflow"));
+ layoutMenu->insert(new KAction(i18n("By &Columns"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutVFlow()), actionCollection(), "layout_vflow"));
+ layoutMenu->insert(new KAction(i18n("Horizontally in &Splitter"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutHSplitter()), actionCollection(), "layout_hsplitter"));
+ layoutMenu->insert(new KAction(i18n("Verti&cally in Splitter"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutVSplitter()), actionCollection(), "layout_vsplitter"));
+ new KAction(i18n("&Ungroup Widgets"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(breakLayout()), actionCollection(), "break_layout");
+
+/*
+ new KAction(i18n("Lay Out Widgets &Horizontally"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutHBox()), actionCollection(), "layout_hbox");
+ new KAction(i18n("Lay Out Widgets &Vertically"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutVBox()), actionCollection(), "layout_vbox");
+ new KAction(i18n("Lay Out Widgets in &Grid"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutGrid()), actionCollection(), "layout_grid");
+ new KAction(i18n("Lay Out Widgets H&orizontally in Splitter"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutHSplitter()), actionCollection(), "layout_hsplitter");
+ new KAction(i18n("Lay Out Widgets Verti&cally in Splitter"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(layoutVSplitter()), actionCollection(), "layout_vsplitter");
+ new KAction(i18n("&Break Layout"), QString::null, KShortcut(0), KFormDesigner::FormManager::self(), SLOT(breakLayout()), actionCollection(), "break_layout");
+*/
+ new KAction(i18n("Bring Widget to Front"), "raise", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(bringWidgetToFront()), actionCollection(), "format_raise");
+ new KAction(i18n("Send Widget to Back"), "lower", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(sendWidgetToBack()), actionCollection(), "format_lower");
+
+ KActionMenu *alignMenu = new KActionMenu(i18n("Align Widgets' Positions"), "aopos2grid", actionCollection(), "align_menu");
+ alignMenu->insert( new KAction(i18n("To Left"), "aoleft", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToLeft()), actionCollection(), "align_to_left") );
+ alignMenu->insert( new KAction(i18n("To Right"), "aoright", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToRight()), actionCollection(), "align_to_right") );
+ alignMenu->insert( new KAction(i18n("To Top"), "aotop", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToTop()), actionCollection(), "align_to_top") );
+ alignMenu->insert( new KAction(i18n("To Bottom"), "aobottom", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToBottom()), actionCollection(), "align_to_bottom") );
+ alignMenu->insert( new KAction(i18n("To Grid"), "aopos2grid", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(alignWidgetsToGrid()), actionCollection(), "align_to_grid") );
+
+ KActionMenu *sizeMenu = new KActionMenu(i18n("Adjust Widgets' Sizes"), "aogrid", actionCollection(), "adjust_size_menu");
+ sizeMenu->insert( new KAction(i18n("To Fit"), "aofit", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustWidgetSize()), actionCollection(), "adjust_to_fit") );
+ sizeMenu->insert( new KAction(i18n("To Grid"), "aogrid", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustSizeToGrid()), actionCollection(), "adjust_size_grid") );
+ sizeMenu->insert( new KAction(i18n("To Shortest"), "aoshortest", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustHeightToSmall()), actionCollection(), "adjust_height_small") );
+ sizeMenu->insert( new KAction(i18n("To Tallest"), "aotallest", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustHeightToBig()), actionCollection(), "adjust_height_big") );
+ sizeMenu->insert( new KAction(i18n("To Narrowest"), "aonarrowest", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustWidthToSmall()), actionCollection(), "adjust_width_small") );
+ sizeMenu->insert( new KAction(i18n("To Widest"), "aowidest", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(adjustWidthToBig()), actionCollection(), "adjust_width_big") );
+
+ if(m_inShell)
+ setXMLFile("kformdesigner_part_shell.rc");
+ else
+ setXMLFile("kformdesigner_part.rc");
+ KFormDesigner::FormManager::self()->createActions(formsLibrary(), actionCollection(), this);
+}
+
+void
+KFormDesignerPart::createBlankForm()
+{
+ if(KFormDesigner::FormManager::self()->activeForm() && m_uniqueFormMode)
+ {
+ m_openingFile = true;
+ closeURL();
+ m_openingFile = false;
+ }
+
+ if(m_uniqueFormMode && KFormDesigner::FormManager::self()->activeForm()
+ && !KFormDesigner::FormManager::self()->activeForm()->isModified()
+ && KFormDesigner::FormManager::self()->activeForm()->filename().isNull())
+ return; // active form is already a blank one
+
+ QString n = i18n("Form") + QString::number(++m_count);
+ Form *form = new Form(formsLibrary(), n.latin1(),
+ false/*!designMode, we need to set it early enough*/);
+ FormWidgetBase *w = new FormWidgetBase(this, m_workspace, n.latin1());
+
+ w->setCaption(n);
+ w->setIcon(SmallIcon("form"));
+ w->resize(350, 300);
+ w->show();
+ w->setFocus();
+
+ form->createToplevel(w, w);
+ KFormDesigner::FormManager::self()->importForm(form);
+}
+
+void
+KFormDesignerPart::open()
+{
+ m_openingFile = true;
+ KURL url = KFileDialog::getOpenURL("::kformdesigner", i18n("*.ui|Qt Designer UI Files"), m_workspace->topLevelWidget());
+ if(!url.isEmpty())
+ ReadWritePart::openURL(url);
+ m_openingFile = false;
+}
+
+bool
+KFormDesignerPart::openFile()
+{
+ Form *form = new Form(formsLibrary());
+ FormWidgetBase *w = new FormWidgetBase(this, m_workspace);
+ form->createToplevel(w, w);
+
+ if(!KFormDesigner::FormIO::loadFormFromFile(form, w, m_file))
+ {
+ delete form;
+ delete w;
+ return false;
+ }
+
+ w->show();
+ KFormDesigner::FormManager::self()->importForm(form, !isReadWrite());
+ return true;
+}
+
+bool
+KFormDesignerPart::saveFile()
+{
+ KFormDesigner::FormIO::saveFormToFile(KFormDesigner::FormManager::self()->activeForm(), m_file);
+ return true;
+}
+
+void
+KFormDesignerPart::saveAs()
+{
+ KURL url = KFileDialog::getSaveURL("::kformdesigner", i18n("*.ui|Qt Designer UI Files"),
+ m_workspace->topLevelWidget());
+ if(url.isEmpty())
+ return;
+ else
+ ReadWritePart::saveAs(url);
+}
+
+bool
+KFormDesignerPart::closeForm(Form *form)
+{
+ int res = KMessageBox::questionYesNoCancel( m_workspace->topLevelWidget(),
+ i18n( "The form \"%1\" has been modified.\n"
+ "Do you want to save your changes or discard them?" ).arg( form->objectTree()->name() ),
+ i18n( "Close Form" ), KStdGuiItem::save(), KStdGuiItem::discard() );
+
+ if(res == KMessageBox::Yes)
+ save();
+
+ return (res != KMessageBox::Cancel);
+}
+
+bool
+KFormDesignerPart::closeForms()
+{
+ QWidgetList list = m_workspace->windowList(QWorkspace::CreationOrder);
+ for(QWidget *w = list.first(); w; w = list.next())
+ if(w->close() == false)
+ return false;
+
+ return true;
+}
+
+bool
+KFormDesignerPart::closeURL()
+{
+ if(!KFormDesigner::FormManager::self()->activeForm())
+ return true;
+
+ if(m_uniqueFormMode || !m_openingFile) {
+ if (!closeForms())
+ return false;
+ }
+
+ delete (KoProperty::Editor*)m_editor;
+ return true;
+}
+
+void
+KFormDesignerPart::slotFormModified(Form *, bool isDirty)
+{
+ setModified(isDirty);
+}
+
+void
+KFormDesignerPart::slotPreviewForm()
+{
+ if(!KFormDesigner::FormManager::self()->activeForm())
+ return;
+
+ FormWidgetBase *w = new FormWidgetBase(this, m_workspace);
+ KFormDesigner::FormManager::self()->previewForm(KFormDesigner::FormManager::self()->activeForm(), w);
+}
+
+#if 0
+
+void
+KFormDesignerPart::slotWidgetSelected(Form *form, bool multiple)
+{
+ enableFormActions();
+ // Enable edit actions
+ ENABLE_ACTION("edit_copy", true);
+ ENABLE_ACTION("edit_cut", true);
+ ENABLE_ACTION("edit_delete", true);
+ ENABLE_ACTION("clear_contents", true);
+
+ // 'Align Widgets' menu
+ ENABLE_ACTION("align_menu", multiple);
+ ENABLE_ACTION("align_to_left", multiple);
+ ENABLE_ACTION("align_to_right", multiple);
+ ENABLE_ACTION("align_to_top", multiple);
+ ENABLE_ACTION("align_to_bottom", multiple);
+
+ ENABLE_ACTION("adjust_size_menu", true);
+ ENABLE_ACTION("adjust_width_small", multiple);
+ ENABLE_ACTION("adjust_width_big", multiple);
+ ENABLE_ACTION("adjust_height_small", multiple);
+ ENABLE_ACTION("adjust_height_big", multiple);
+
+ ENABLE_ACTION("format_raise", true);
+ ENABLE_ACTION("format_lower", true);
+
+ // If the widgets selected is a container, we enable layout actions
+ bool containerSelected = false;
+ if(!multiple)
+ {
+ KFormDesigner::ObjectTreeItem *item = form->objectTree()->lookup( form->selectedWidgets()->first()->name() );
+ if(item && item->container())
+ containerSelected = true;
+ }
+ const bool twoSelected = form->selectedWidgets()->count()==2;
+ // Layout actions
+ ENABLE_ACTION("layout_menu", multiple || containerSelected);
+ ENABLE_ACTION("layout_hbox", multiple || containerSelected);
+ ENABLE_ACTION("layout_vbox", multiple || containerSelected);
+ ENABLE_ACTION("layout_grid", multiple || containerSelected);
+ ENABLE_ACTION("layout_hsplitter", twoSelected);
+ ENABLE_ACTION("layout_vsplitter", twoSelected);
+
+ KFormDesigner::Container *container = KFormDesigner::FormManager::self()->activeForm()->activeContainer();
+ ENABLE_ACTION("break_layout", (container->layoutType() != KFormDesigner::Container::NoLayout));
+}
+
+void
+KFormDesignerPart::slotFormWidgetSelected(Form *form)
+{
+ disableWidgetActions();
+ enableFormActions();
+
+ const bool twoSelected = form->selectedWidgets()->count()==2;
+ const bool hasChildren = !form->objectTree()->children()->isEmpty();
+
+ // Layout actions
+ ENABLE_ACTION("layout_menu", hasChildren);
+ ENABLE_ACTION("layout_hbox", hasChildren);
+ ENABLE_ACTION("layout_vbox", hasChildren);
+ ENABLE_ACTION("layout_grid", hasChildren);
+ ENABLE_ACTION("layout_hsplitter", twoSelected);
+ ENABLE_ACTION("layout_vsplitter", twoSelected);
+ ENABLE_ACTION("break_layout", (form->toplevelContainer()->layoutType() != KFormDesigner::Container::NoLayout));
+}
+
+void
+KFormDesignerPart::slotNoFormSelected()
+{
+ disableWidgetActions();
+
+ // Disable paste action
+ ENABLE_ACTION("edit_paste", false);
+
+ ENABLE_ACTION("edit_undo", false);
+ ENABLE_ACTION("edit_redo", false);
+
+ // Disable 'Tools' actions
+ ENABLE_ACTION("pixmap_collection", false);
+ ENABLE_ACTION("form_connections", false);
+ ENABLE_ACTION("taborder", false);
+ ENABLE_ACTION("change_style", KFormDesigner::FormManager::self()->activeForm());
+
+ // Disable items in 'File'
+ ENABLE_ACTION("file_save", false);
+ ENABLE_ACTION("file_save_as", false);
+ ENABLE_ACTION("preview_form", false);
+}
+
+void
+KFormDesignerPart::enableFormActions()
+{
+ // Enable 'Tools' actions
+ ENABLE_ACTION("pixmap_collection", true);
+ ENABLE_ACTION("form_connections", true);
+ ENABLE_ACTION("taborder", true);
+ ENABLE_ACTION("change_style", true);
+
+ // Enable items in 'File'
+ ENABLE_ACTION("file_save", true);
+ ENABLE_ACTION("file_save_as", true);
+ ENABLE_ACTION("preview_form", true);
+
+ ENABLE_ACTION("edit_paste", KFormDesigner::FormManager::self()->isPasteEnabled());
+ ENABLE_ACTION("edit_select_all", true);
+}
+
+void
+KFormDesignerPart::disableWidgetActions()
+{
+ // Disable edit actions
+ ENABLE_ACTION("edit_copy", false);
+ ENABLE_ACTION("edit_cut", false);
+ ENABLE_ACTION("edit_delete", false);
+ ENABLE_ACTION("clear_contents", false);
+
+ // Disable format functions
+ ENABLE_ACTION("align_menu", false);
+ ENABLE_ACTION("align_to_left", false);
+ ENABLE_ACTION("align_to_right", false);
+ ENABLE_ACTION("align_to_top", false);
+ ENABLE_ACTION("align_to_bottom", false);
+ ENABLE_ACTION("adjust_size_menu", false);
+ ENABLE_ACTION("format_raise", false);
+ ENABLE_ACTION("format_lower", false);
+
+ ENABLE_ACTION("layout_menu", false);
+ ENABLE_ACTION("layout_hbox", false);
+ ENABLE_ACTION("layout_vbox", false);
+ ENABLE_ACTION("layout_grid", false);
+ ENABLE_ACTION("layout_hsplitter", false);
+ ENABLE_ACTION("layout_vsplitter", false);
+ ENABLE_ACTION("break_layout", false);
+}
+#endif
+
+void
+KFormDesignerPart::setUndoEnabled(bool enabled, const QString &text)
+{
+ KAction *undoAction = actionCollection()->action("edit_undo");
+ if(undoAction)
+ {
+ if(!text.isNull())
+ undoAction->setText(text);
+ }
+}
+
+void
+KFormDesignerPart::setRedoEnabled(bool enabled, const QString &text)
+{
+ KAction *redoAction = actionCollection()->action("edit_redo");
+ if(redoAction)
+ {
+ if(!text.isNull())
+ redoAction->setText(text);
+ }
+}
+
+
+////// FormWidgetBase : helper widget to draw rects on top of widgets
+
+//repaint all children widgets
+static void repaintAll(QWidget *w)
+{
+ w->repaint();
+ QObjectList *list = w->queryList("QWidget");
+ QObjectListIt it(*list);
+ for (QObject *obj; (obj=it.current()); ++it ) {
+ static_cast<QWidget*>(obj)->repaint();
+ }
+ delete list;
+}
+
+void
+FormWidgetBase::drawRects(const QValueList<QRect> &list, int type)
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ if (prev_rect.isValid()) {
+ //redraw prev. selection's rectangle
+ p.drawPixmap( QPoint(prev_rect.x()-2, prev_rect.y()-2), buffer, QRect(prev_rect.x()-2, prev_rect.y()-2, prev_rect.width()+4, prev_rect.height()+4));
+ }
+ p.setBrush(QBrush::NoBrush);
+ if(type == 1) // selection rect
+ p.setPen(QPen(white, 1, Qt::DotLine));
+ else if(type == 2) // insert rect
+ p.setPen(QPen(white, 2));
+ p.setRasterOp(XorROP);
+
+ prev_rect = QRect();
+ QValueList<QRect>::ConstIterator endIt = list.constEnd();
+ for(QValueList<QRect>::ConstIterator it = list.constBegin(); it != endIt; ++it) {
+ p.drawRect(*it);
+ prev_rect = prev_rect.unite(*it);
+ }
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+}
+
+void
+FormWidgetBase::drawRect(const QRect& r, int type)
+{
+ QValueList<QRect> l;
+ l.append(r);
+ drawRects(l, type);
+}
+
+void
+FormWidgetBase::initBuffer()
+{
+ repaintAll(this);
+ buffer.resize( width(), height() );
+ buffer = QPixmap::grabWindow( winId() );
+ prev_rect = QRect();
+}
+
+void
+FormWidgetBase::clearForm()
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ //redraw entire form surface
+ p.drawPixmap( QPoint(0,0), buffer, QRect(0,0,buffer.width(), buffer.height()) );
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+
+ repaintAll(this);
+}
+
+void
+FormWidgetBase::highlightWidgets(QWidget *from, QWidget *to)//, const QPoint &point)
+{
+ QPoint fromPoint, toPoint;
+ if(from && from->parentWidget() && (from != this))
+ fromPoint = from->parentWidget()->mapTo(this, from->pos());
+ if(to && to->parentWidget() && (to != this))
+ toPoint = to->parentWidget()->mapTo(this, to->pos());
+
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ if (prev_rect.isValid()) {
+ //redraw prev. selection's rectangle
+ p.drawPixmap( QPoint(prev_rect.x(), prev_rect.y()), buffer, QRect(prev_rect.x(), prev_rect.y(), prev_rect.width(), prev_rect.height()));
+ }
+
+ p.setPen( QPen(Qt::red, 2) );
+
+ if(to)
+ {
+ QPixmap pix1 = QPixmap::grabWidget(from);
+ QPixmap pix2 = QPixmap::grabWidget(to);
+
+ if((from != this) && (to != this))
+ p.drawLine( from->parentWidget()->mapTo(this, from->geometry().center()), to->parentWidget()->mapTo(this, to->geometry().center()) );
+
+ p.drawPixmap(fromPoint.x(), fromPoint.y(), pix1);
+ p.drawPixmap(toPoint.x(), toPoint.y(), pix2);
+
+ if(to == this)
+ p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4);
+ else
+ p.drawRoundRect(toPoint.x(), toPoint.y(), to->width(), to->height(), 5, 5);
+ }
+
+ if(from == this)
+ p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4);
+ else
+ p.drawRoundRect(fromPoint.x(), fromPoint.y(), from->width(), from->height(), 5, 5);
+
+ if((to == this) || (from == this))
+ prev_rect = QRect(0, 0, buffer.width(), buffer.height());
+ else if(to)
+ {
+ prev_rect.setX( (fromPoint.x() < toPoint.x()) ? (fromPoint.x() - 5) : (toPoint.x() - 5) );
+ prev_rect.setY( (fromPoint.y() < toPoint.y()) ? (fromPoint.y() - 5) : (toPoint.y() - 5) );
+ prev_rect.setRight( (fromPoint.x() < toPoint.x()) ? (toPoint.x() + to->width() + 10) : (fromPoint.x() + from->width() + 10) );
+ prev_rect.setBottom( (fromPoint.y() < toPoint.y()) ? (toPoint.y() + to->height() + 10) : (fromPoint.y() + from->height() + 10) ) ;
+ }
+ else
+ prev_rect = QRect(fromPoint.x()- 5, fromPoint.y() -5, from->width() + 10, from->height() + 10);
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+}
+
+void
+FormWidgetBase::closeEvent(QCloseEvent *ev)
+{
+ Form *form = KFormDesigner::FormManager::self()->formForWidget(this);
+ if(!form || !form->isModified() || !form->objectTree()) // == preview form
+ ev->accept();
+ else
+ {
+ bool close = m_part->closeForm(form);
+ if(close)
+ ev->accept();
+ else
+ ev->ignore();
+ }
+}
+
+void KFormDesignerPart::slotPropertySetSwitched(KoProperty::Set *set, bool forceReload,
+ const QCString& propertyToSelect)
+{
+ if (m_editor) {
+ if (propertyToSelect.isEmpty() && forceReload)
+ m_editor->changeSet(set, propertyToSelect);
+ else
+ m_editor->changeSet(set);
+ }
+}
+
+K_EXPORT_COMPONENT_FACTORY(libkformdesigner_part, KFDFactory)
+
+#include "kfd_part.moc"
+
diff --git a/kexi/formeditor/test/kfd_part.h b/kexi/formeditor/test/kfd_part.h
new file mode 100644
index 000000000..77b809cab
--- /dev/null
+++ b/kexi/formeditor/test/kfd_part.h
@@ -0,0 +1,142 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNER_PART_H
+#define KFORMDESIGNER_PART_H
+
+#include <qwidget.h>
+#include <qpixmap.h>
+
+#include <kparts/part.h>
+#include <kparts/factory.h>
+
+#include "form.h"
+
+class KAboutData;
+class KInstance;
+class QWorkspace;
+class QCloseEvent;
+
+using KFormDesigner::Form;
+
+class KFORMEDITOR_EXPORT KFDFactory : public KParts::Factory
+{
+ Q_OBJECT
+
+ public:
+ KFDFactory();
+ virtual ~KFDFactory();
+
+ virtual KParts::Part* createPartObject(QWidget *parentWidget=0, const char *widgetName=0, QObject *parent=0, const char *name=0,
+ const char *classname="KParts::Part", const QStringList &args=QStringList());
+
+ static KInstance *instance();
+ static KAboutData *aboutData();
+
+ private:
+ static KInstance *m_instance;
+};
+
+class KFORMEDITOR_EXPORT KFormDesignerPart: public KParts::ReadWritePart
+{
+ Q_OBJECT
+
+ public:
+ KFormDesignerPart(QWidget *parent, const char *name, bool readOnly=true, const QStringList &args=QStringList());
+ virtual ~KFormDesignerPart();
+
+ static KFormDesigner::WidgetLibrary* formsLibrary();
+
+// KFormDesigner::FormManager* manager() { return m_manager; }
+ void setUniqueFormMode(bool enable) { m_uniqueFormMode = enable; }
+
+ bool closeForm(Form *form);
+ bool closeForms();
+
+ virtual bool closeURL();
+
+ public slots:
+ /*! Creates a new blank Form. The new Form is shown and become the active Form. */
+ void createBlankForm();
+
+ /*! Loads a Form from a UI file. A "Open File" dialog is shown to select the file. The loaded Form is shown and becomes
+ the active Form. */
+ void open();
+
+ void slotPreviewForm();
+ void saveAs();
+ //void slotCreateFormSlot(KFormDesigner::Form *form, const QString &widget, const QString &signal);
+
+ protected slots:
+ void slotFormModified(KFormDesigner::Form *form, bool isDirty);
+//moved to manager void slotWidgetSelected(KFormDesigner::Form *form, bool multiple);
+//moved to manager void slotFormWidgetSelected(KFormDesigner::Form *form);
+//moved to manager void slotNoFormSelected();
+ void setUndoEnabled(bool enabled, const QString &text);
+ void setRedoEnabled(bool enabled, const QString &text);
+
+ /*! Shows a property set \a set in a Property Editor. */
+ void slotPropertySetSwitched(KoProperty::Set *set, bool forceReload = false,
+ const QCString& propertyToSelect = QCString());
+
+ protected:
+ virtual bool openFile();
+ virtual bool saveFile();
+ void disableWidgetActions();
+ void enableFormActions();
+ void setupActions();
+
+ private:
+ static KFormDesigner::WidgetLibrary* static_formsLibrary;
+// KFormDesigner::FormManager *m_manager;
+ QWorkspace *m_workspace;
+ QGuardedPtr<KoProperty::Editor> m_editor;
+ int m_count;
+ bool m_uniqueFormMode;
+ bool m_openingFile;
+ bool m_inShell;
+};
+
+//! Helper: this widget is used to create form's surface
+class KFORMEDITOR_EXPORT FormWidgetBase : public QWidget, public KFormDesigner::FormWidget
+{
+ Q_OBJECT
+
+ public:
+ FormWidgetBase(KFormDesignerPart *part, QWidget *parent = 0, const char *name = 0, int WFlags = WDestructiveClose)
+ : QWidget(parent, name, WFlags), m_part(part) {}
+ ~FormWidgetBase() {;}
+
+ void drawRect(const QRect& r, int type);
+ void drawRects(const QValueList<QRect> &list, int type);
+ void initBuffer();
+ void clearForm();
+ void highlightWidgets(QWidget *from, QWidget *to);//, const QPoint &p);
+
+ protected:
+ void closeEvent(QCloseEvent *ev);
+
+ private:
+ QPixmap buffer; //!< stores grabbed entire form's area for redraw
+ QRect prev_rect; //!< previously selected rectangle
+ KFormDesignerPart *m_part;
+};
+
+#endif
+
diff --git a/kexi/formeditor/test/kformdesigner.desktop b/kexi/formeditor/test/kformdesigner.desktop
new file mode 100644
index 000000000..4a018915d
--- /dev/null
+++ b/kexi/formeditor/test/kformdesigner.desktop
@@ -0,0 +1,61 @@
+[Desktop Entry]
+Name=KFormDesigner
+Name[cs]=Návrhář formulářů
+Name[cy]=DylunyddKForm
+Name[fa]=طراح KForm
+Name[ne]=केडीई फाराम डिजाइनकर्ता
+Name[sv]=Kformdesigner
+Name[ta]=படிவம் வடிவமைப்பவர்
+Name[tg]=KДизайнгари шакл
+Name[tr]=KFormTasarımcısı
+Name[zh_CN]=KForm 设计器
+Exec=kformdesigner %i %m -caption "%c"
+Icon=kformdesigner
+Type=Application
+DocPath=kformdesigner/kformdesigner.html
+GenericName=Form Designer
+GenericName[bg]=Проектиране на форми
+GenericName[br]=Ergrafer ar paperenn-reol
+GenericName[ca]=Dissenyador de formulari
+GenericName[cy]=Dylunydd Ffurflenni
+GenericName[de]=Formular-Designer
+GenericName[el]=Σχεδιαστής φόρμας
+GenericName[eo]=Formulardesegnilo
+GenericName[es]=Diseñador de formularios
+GenericName[et]=Vormikujundaja
+GenericName[eu]=Formularioen diseinatzailea
+GenericName[fa]=طراح برگه
+GenericName[fi]=Lomakkeen suunnittleija
+GenericName[fr]=Concepteur d'interfaces graphiques
+GenericName[gl]=Deseño de Formularios
+GenericName[he]=מעצב טפסים
+GenericName[hr]=Dizajner obrazaca
+GenericName[hu]=Űrlaptervező
+GenericName[is]=Form hönnuður
+GenericName[it]=Progetto dei moduli
+GenericName[ja]=フォームデザイナー
+GenericName[km]=កម្មវិធី​រចនា​សំណុំបែបបទ
+GenericName[lv]=Formu veidotājs
+GenericName[ms]=Pereka Bentuk Borang
+GenericName[nb]=Skjemautforming
+GenericName[nds]=Kiekwark-Maker
+GenericName[ne]=फारम डिजाइनकर्ता
+GenericName[nn]=Skjemautforming
+GenericName[pl]=Projektant formularzy
+GenericName[pt]=Desenho de Formulários
+GenericName[pt_BR]=Desenhista de Formulário
+GenericName[ru]=Редактор форм
+GenericName[se]=Skovvehápmejeaddji
+GenericName[sl]=Oblikovalec obrazcev
+GenericName[sr]=Дизајнер форми
+GenericName[sr@Latn]=Dizajner formi
+GenericName[sv]=Formulärkonstruktion
+GenericName[uk]=Дизайнер форм
+GenericName[uz]=Shakl dizayneri
+GenericName[uz@cyrillic]=Шакл дизайнери
+GenericName[zh_CN]=表单设计器
+GenericName[zh_TW]=表單設計師
+MimeType=application/x-designer;
+Terminal=false
+Categories=Qt;KDE;Development;
+
diff --git a/kexi/formeditor/test/kformdesigner_part.desktop b/kexi/formeditor/test/kformdesigner_part.desktop
new file mode 100644
index 000000000..b0f384544
--- /dev/null
+++ b/kexi/formeditor/test/kformdesigner_part.desktop
@@ -0,0 +1,52 @@
+[Desktop Entry]
+Name=Form Designer
+Name[bg]=Проектиране на форми
+Name[br]=Ergrafer ar paperenn-reol
+Name[ca]=Dissenyador de formulari
+Name[cs]=Návrhář formulářů
+Name[cy]=Dylunydd Ffurflenni
+Name[de]=Formular-Designer
+Name[el]=Σχεδιαστής φόρμας
+Name[eo]=Formulardesegnilo
+Name[es]=Diseñador de formularios
+Name[et]=Vormikujundaja
+Name[eu]=Formularioen diseinatzailea
+Name[fa]=طراح برگه
+Name[fi]=Lomakkeen suunnittelija
+Name[fr]=Composeur d'interfaces graphiques
+Name[gl]=Deseño de Formularios
+Name[he]=מעצב טפסים
+Name[hr]=Dizajner obrazaca
+Name[hu]=Űrlaptervező
+Name[is]=Form hönnuður
+Name[it]=Progetto dei moduli
+Name[ja]=フォームデザイナー
+Name[km]=កម្មវិធី​រចនា​សំណុំបែបបទ
+Name[lt]=Formų kūrimo programa
+Name[lv]=Formu veidotājs
+Name[ms]=Pereka Bentuk Borang
+Name[nb]=Skjemautforming
+Name[nds]=Kiekwark-Maker
+Name[ne]=फारम डिजाइनकर्ता
+Name[nn]=Skjemautforming
+Name[pl]=Projektant formularzy
+Name[pt]=Desenho de Formulários
+Name[pt_BR]=Desenhista de Formulário
+Name[ru]=Редактор форм
+Name[se]=Skovvehápmejeaddji
+Name[sl]=Oblikovalec obrazcev
+Name[sr]=Дизајнер форми
+Name[sr@Latn]=Dizajner formi
+Name[sv]=Formulärkonstruktion
+Name[ta]=படிவ வடிவமைப்பாளர்
+Name[tg]=Дизайнгари шакл
+Name[tr]=FormTasarımcısı
+Name[uk]=Дизайнер форм
+Name[uz]=Shakl dizayneri
+Name[uz@cyrillic]=Шакл дизайнери
+Name[zh_CN]=表单设计器
+Name[zh_TW]=表單設計師
+MimeType=application/x-designer;
+ServiceTypes=KParts/ReadOnlyPart,KParts/ReadWritePart
+X-KDE-Library=libkformdesigner_part
+Type=Service
diff --git a/kexi/formeditor/test/kformdesigner_part.rc b/kexi/formeditor/test/kformdesigner_part.rc
new file mode 100644
index 000000000..03c25f19a
--- /dev/null
+++ b/kexi/formeditor/test/kformdesigner_part.rc
@@ -0,0 +1,125 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kfd_part" version="2">
+
+<MenuBar>
+ <Menu name="edit" noMerge="1">
+ <Action name="edit_undo" group="edit_undo_merge"/>
+ <Action name="edit_redo" group="edit_undo_merge"/>
+ <Separator group="edit_undo_merge"/>
+ <Action name="edit_cut" group="edit_paste_merge"/>
+ <Action name="edit_copy" group="edit_paste_merge"/>
+ <Action name="edit_paste" group="edit_paste_merge"/>
+ <Action name="edit_delete" group="edit_paste_merge"/>
+ <Action name="clear_contents" group="edit_paste_merge"/>
+ <Action name="edit_select_all" group="edit_select_merge"/>
+ </Menu>
+ <Menu name="tools" noMerge="1">
+ <Merge/>
+ <Separator/>
+ <Action name="taborder"/>
+ <Action name="change_style"/>
+ <Action name="pixmap_collection"/>
+ <Action name="form_connections"/>
+ </Menu>
+ <Menu name="widgets" noMerge="1">
+ <text>&amp;Widgets</text>
+ <Merge/>
+ <Action name="pointer"/>
+ <Action name="drag_connection"/>
+ <Separator/>
+ <Action name="library_widget_SubForm"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_KFDTabWidget"/>
+ <Action name="library_widget_QWidgetStack"/>
+ <Action name="library_widget_QFrame"/>
+ <Separator/>
+ <Action name="library_widget_KLineEdit"/>
+ <Action name="library_widget_QLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_QRadioButton"/>
+ <Action name="library_widget_QCheckBox"/>
+ <Action name="library_widget_KIntSpinBox"/>
+ <Action name="library_widget_KComboBox"/>
+ <Action name="library_widget_KListBox"/>
+ <Action name="library_widget_KTextEdit"/>
+ <Action name="library_widget_KListView"/>
+ <Action name="library_widget_QSlider"/>
+ <Action name="library_widget_KProgress"/>
+ <Action name="library_widget_KTimeWidget"/>
+ <Action name="library_widget_KDateWidget"/>
+ <Action name="library_widget_KDateTimeWidget"/>
+ <Action name="library_widget_Line"/>
+ <Action name="library_widget_Spring"/>
+ </Menu>
+ <Menu name="format" noMerge="1">
+ <text>&amp;Format</text>
+ <Action name="snap_to_grid"/>
+ <Separator/>
+ <Action name="layout_menu"/>
+ <Action name="break_layout"/>
+ <Separator/>
+ <Action name="formpart_break_layout"/>
+ <Action name="align_menu"/>
+ <Action name="adjust_size_menu"/>
+ <Separator/>
+ <Action name="format_raise"/>
+ <Action name="format_lower"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="fileToolBar" noMerge="1"><text>Main Toolbar</text>
+ <Action name="preview_form"/>
+ <Separator/>
+ <Action name="edit_undo"/>
+ <Action name="edit_redo"/>
+ <Separator/>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="edit_paste"/>
+</ToolBar>
+<ToolBar name="containers" fullWidth="false" noMerge="1">
+ <text>Containers</text>
+ <Action name="library_widget_SubForm"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_KFDTabWidget"/>
+ <Action name="library_widget_QWidgetStack"/>
+ <Action name="library_widget_QFrame"/>
+</ToolBar>
+<ToolBar name="widgets" fullWidth="false" noMerge="1">
+ <text>Widgets</text>
+ <Action name="pointer"/>
+ <Action name="drag_connection"/>
+ <Separator/>
+ <Action name="library_widget_KLineEdit"/>
+ <Action name="library_widget_QLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_QRadioButton"/>
+ <Action name="library_widget_QCheckBox"/>
+ <Action name="library_widget_KIntSpinBox"/>
+ <Action name="library_widget_KComboBox"/>
+ <Action name="library_widget_KListBox"/>
+ <Action name="library_widget_KTextEdit"/>
+ <Action name="library_widget_KListView"/>
+ <Action name="library_widget_QSlider"/>
+ <Action name="library_widget_KProgress"/>
+ <Action name="library_widget_KTimeWidget"/>
+ <Action name="library_widget_KDateWidget"/>
+ <Action name="library_widget_KDateTimeWidget"/>
+ <Action name="library_widget_Line"/>
+ <Action name="library_widget_Spring"/>
+ <ActionList name="undo_actions" />
+</ToolBar>
+<ToolBar name="tools" fullWidth="false" noMerge="1">
+<text>Tools Toolbar</text>
+ <Action name="pixmap_collection"/>
+ <!-- <Action name="change_style"/> !-->
+</ToolBar>
+<ToolBar name="format" fullWidth="false" noMerge="1">
+<text>Format Toolbar</text>
+ <Action name="align_menu"/>
+ <Action name="adjust_size_menu"/>
+</ToolBar>
+
+</kpartgui> \ No newline at end of file
diff --git a/kexi/formeditor/test/kformdesigner_part_shell.rc b/kexi/formeditor/test/kformdesigner_part_shell.rc
new file mode 100644
index 000000000..8163cab3b
--- /dev/null
+++ b/kexi/formeditor/test/kformdesigner_part_shell.rc
@@ -0,0 +1,138 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kfd_part" version="2">
+
+<MenuBar>
+ <Menu name="file" noMerge="1">
+ <Action name="file_new"/>
+ <Action name="file_open"/>
+ <Separator />
+ <Action name="file_save"/>
+ <Action name="file_save_as"/>
+ <Separator/>
+ </Menu>
+ <Menu name="edit" noMerge="1">
+ <Action name="edit_undo" group="edit_undo_merge"/>
+ <Action name="edit_redo" group="edit_undo_merge"/>
+ <Separator group="edit_undo_merge"/>
+ <Action name="edit_cut" group="edit_paste_merge"/>
+ <Action name="edit_copy" group="edit_paste_merge"/>
+ <Action name="edit_paste" group="edit_paste_merge"/>
+ <Action name="edit_delete" group="edit_paste_merge"/>
+ <Action name="clear_contents" group="edit_paste_merge"/>
+ <Separator group="edit_select_merge"/>
+ <Action name="edit_select_all" group="edit_select_merge"/>
+ <Separator group="edit_paste_merge"/>
+ </Menu>
+ <Menu name="view" noMerge="1">
+ <Action name="preview_form"/>
+ </Menu>
+ <Menu name="tools" noMerge="1">
+ <Action name="taborder"/>
+ <Action name="change_style"/>
+ <Action name="pixmap_collection"/>
+ <Action name="form_connections"/>
+ </Menu>
+ <Menu name="widgets" noMerge="1">
+ <text>&amp;Widgets</text>
+ <Merge/>
+ <Action name="pointer"/>
+ <Action name="drag_connection"/>
+ <Separator/>
+ <Action name="library_widget_SubForm"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_KFDTabWidget"/>
+ <Action name="library_widget_QWidgetStack"/>
+ <Action name="library_widget_QFrame"/>
+ <Separator/>
+ <Action name="library_widget_KLineEdit"/>
+ <Action name="library_widget_QLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_QRadioButton"/>
+ <Action name="library_widget_QCheckBox"/>
+ <Action name="library_widget_KIntSpinBox"/>
+ <Action name="library_widget_KComboBox"/>
+ <Action name="library_widget_KListBox"/>
+ <Action name="library_widget_KTextEdit"/>
+ <Action name="library_widget_KListView"/>
+ <Action name="library_widget_QSlider"/>
+ <Action name="library_widget_KProgress"/>
+ <Action name="library_widget_KTimeWidget"/>
+ <Action name="library_widget_KDateWidget"/>
+ <Action name="library_widget_KDateTimeWidget"/>
+ <Action name="library_widget_Line"/>
+ <Action name="library_widget_Spring"/>
+ </Menu>
+ <Menu name="format" noMerge="1">
+ <text>&amp;Format</text>
+ <Action name="snap_to_grid"/>
+ <Separator/>
+ <Action name="layout_menu"/>
+ <Action name="break_layout"/>
+ <Separator/>
+ <Action name="align_menu"/>
+ <Action name="adjust_size_menu"/>
+ <Separator/>
+ <Action name="format_raise"/>
+ <Action name="format_lower"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="fileToolBar" noMerge="1"><text>Main Toolbar</text>
+ <Action name="file_new"/>
+ <Action name="file_open"/>
+ <Action name="file_save"/>
+ <Action name="preview_form"/>
+ <Separator/>
+ <Action name="edit_undo"/>
+ <Action name="edit_redo"/>
+ <Separator/>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="edit_paste"/>
+</ToolBar>
+<ToolBar name="containers" fullWidth="false" noMerge="1">
+ <text>Containers</text>
+ <Action name="library_widget_SubForm"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_KFDTabWidget"/>
+ <Action name="library_widget_QWidgetStack"/>
+ <Action name="library_widget_QFrame"/>
+</ToolBar>
+<ToolBar name="widgets" fullWidth="false" noMerge="1">
+ <text>Widgets</text>
+ <Action name="pointer"/>
+ <Action name="drag_connection"/>
+ <Separator/>
+ <Action name="library_widget_KLineEdit"/>
+ <Action name="library_widget_QLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_QRadioButton"/>
+ <Action name="library_widget_QCheckBox"/>
+ <Action name="library_widget_KIntSpinBox"/>
+ <Action name="library_widget_KComboBox"/>
+ <Action name="library_widget_KListBox"/>
+ <Action name="library_widget_KTextEdit"/>
+ <Action name="library_widget_KListView"/>
+ <Action name="library_widget_QSlider"/>
+ <Action name="library_widget_KProgress"/>
+ <Action name="library_widget_KTimeWidget"/>
+ <Action name="library_widget_KDateWidget"/>
+ <Action name="library_widget_KDateTimeWidget"/>
+ <Action name="library_widget_Line"/>
+ <Action name="library_widget_Spring"/>
+ <ActionList name="undo_actions" />
+</ToolBar>
+<ToolBar name="tools" fullWidth="false" noMerge="1">
+<text>Tools Toolbar</text>
+ <Action name="pixmap_collection"/>
+ <!-- <Action name="change_style"/> !-->
+</ToolBar>
+<ToolBar name="format" fullWidth="false" noMerge="1">
+<text>Format Toolbar</text>
+ <Action name="align_menu"/>
+ <Action name="adjust_size_menu"/>
+</ToolBar>
+
+</kpartgui> \ No newline at end of file
diff --git a/kexi/formeditor/test/main.cpp b/kexi/formeditor/test/main.cpp
new file mode 100644
index 000000000..f4866f29f
--- /dev/null
+++ b/kexi/formeditor/test/main.cpp
@@ -0,0 +1,81 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include "kfd_mainwindow.h"
+
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+#include <klocale.h>
+
+static const char *description =
+ I18N_NOOP("KFormDesigner");
+
+static const char *version = "0.3";
+
+static KCmdLineOptions options[] =
+{
+ { "+[URL]", I18N_NOOP( "Document to open" ), 0 },
+ KCmdLineLastOption
+};
+
+int main(int argc, char **argv)
+{
+ KAboutData about("kformdesigner", I18N_NOOP("KFormDesigner"), version, description,
+ KAboutData::License_LGPL, "(C) 2003-2005 Kexi Team", 0, 0);
+ about.addCredit( "Lucijan Busch", "Original author", 0, "lucijan@kde.org" );
+ about.addAuthor( "Cedric Pasteur", 0, "cedric.pasteur@free.fr");
+ about.addCredit( "Jarosław Staniek", "Win32 version, some icons, many fixes, ideas and bug reports", "js@iidea.pl", 0);
+ about.addCredit( "Kristof Borrey ", "Icons", 0, "kristof.borrey@skynet.be" );
+ KCmdLineArgs::init(argc, argv, &about);
+ KCmdLineArgs::addCmdLineOptions(options);
+ KApplication app;
+
+ KGlobal::iconLoader()->addAppDir("kexi");
+
+ KFDMainWindow *v = new KFDMainWindow();
+ if (!v->centralWidget()) { //KFD part could be not found
+ delete v;
+ return 1;
+ }
+ app.setMainWidget(v);
+ v->show();
+
+
+
+ // see if we are starting with session management
+ if (app.isRestored())
+ {
+ RESTORE(KFDMainWindow);
+ }
+ else
+ {
+ // no session.. just start up normally
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ if (args->count() >= 1)
+ {
+ for (int i = 0; i < args->count(); i++)
+ /** @todo report loading errors here */
+ v->loadUIFile(args->url(i));
+ }
+ args->clear();
+ }
+
+ return app.exec();
+}
diff --git a/kexi/formeditor/utils.cpp b/kexi/formeditor/utils.cpp
new file mode 100644
index 000000000..0c3acf59c
--- /dev/null
+++ b/kexi/formeditor/utils.cpp
@@ -0,0 +1,184 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qcursor.h>
+#include <qobjectlist.h>
+#include <qtabwidget.h>
+#include <qtabbar.h>
+
+#include <kdebug.h>
+#include <kexiutils/utils.h>
+
+#include "form.h"
+#include "objecttree.h"
+#include "utils.h"
+
+using namespace KFormDesigner;
+
+void
+KFormDesigner::removeChildrenFromList(WidgetList &list)
+{
+ for(WidgetListIterator it(list); it.current() != 0; ++it) {
+ QWidget *w = it.current();
+
+ // If any widget in the list is a child of this widget, we remove it from the list
+ for(WidgetListIterator it2(list); it2.current() != 0; ++it2) {
+ QWidget *widg = it2.current();
+ if((w != widg) && (w->child(widg->name())))
+ {
+ kdDebug() << "Removing the widget " << widg->name() << "which is a child of " << w->name() << endl;
+ list.remove(widg);
+ }
+ }
+ }
+}
+
+void
+KFormDesigner::installRecursiveEventFilter(QObject *object, QObject *container)
+{
+ if(!object || !container|| !object->isWidgetType())
+ return;
+
+ kdDebug() << "Installing event filter on widget: " << object->name() << " directed to " << container->name() << endl;
+ object->installEventFilter(container);
+ if(((QWidget*)object)->ownCursor())
+ ((QWidget*)object)->setCursor(QCursor(Qt::ArrowCursor));
+
+ if(!object->children())
+ return;
+
+ QObjectList list = *(object->children());
+ for(QObject *obj = list.first(); obj; obj = list.next())
+ installRecursiveEventFilter(obj, container);
+}
+
+void
+KFormDesigner::removeRecursiveEventFilter(QObject *object, QObject *container)
+{
+ object->removeEventFilter(container);
+ if(!object->isWidgetType())
+ return;
+ if(!object->children())
+ return;
+
+ QObjectList list = *(object->children());
+ for(QObject *obj = list.first(); obj; obj = list.next())
+ removeRecursiveEventFilter(obj, container);
+}
+
+void
+KFormDesigner::setRecursiveCursor(QWidget *w, Form *form)
+{
+ ObjectTreeItem *tree = form->objectTree()->lookup(w->name());
+ if(tree && ((tree->modifiedProperties()->contains("cursor")) || !tree->children()->isEmpty())
+ && !w->inherits("QLineEdit") && !w->inherits("QTextEdit")
+ ) //fix weird behaviour
+ return; // if the user has set a cursor for this widget or this is a container, don't change it
+
+ if(w->ownCursor())
+ w->setCursor(Qt::ArrowCursor);
+
+ QObjectList *l = w->queryList( "QWidget" );
+ for(QObject *o = l->first(); o; o = l->next())
+ ((QWidget*)o)->setCursor(Qt::ArrowCursor);
+ delete l;
+}
+
+QSize
+KFormDesigner::getSizeFromChildren(QWidget *w, const char *inheritClass)
+{
+ int tmpw = 0, tmph = 0;
+ QObjectList *list = w->queryList(inheritClass, 0, false, false);
+ for(QObject *o = list->first(); o; o = list->next()) {
+ QRect r = ((QWidget*)o)->geometry();
+ tmpw = QMAX(tmpw, r.right());
+ tmph = QMAX(tmph, r.bottom());
+ }
+
+ delete list;
+ return QSize(tmpw, tmph) + QSize(10, 10);
+}
+
+// -----------------
+
+HorWidgetList::HorWidgetList(QWidget *topLevelWidget)
+ : WidgetList()
+ , m_topLevelWidget(topLevelWidget)
+{
+}
+
+HorWidgetList::~HorWidgetList()
+{
+}
+
+int HorWidgetList::compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2)
+{
+ QWidget *w1 = static_cast<QWidget*>(item1);
+ QWidget *w2 = static_cast<QWidget*>(item2);
+ return w1->mapTo(m_topLevelWidget, QPoint(0,0)).x() - w2->mapTo(m_topLevelWidget, QPoint(0,0)).x();
+}
+
+// -----------------
+
+VerWidgetList::VerWidgetList(QWidget *topLevelWidget)
+ : WidgetList()
+ , m_topLevelWidget(topLevelWidget)
+{
+}
+
+VerWidgetList::~VerWidgetList()
+{
+}
+
+int VerWidgetList::compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2)
+{
+ QWidget *w1 = static_cast<QWidget*>(item1);
+ QWidget *w2 = static_cast<QWidget*>(item2);
+
+ int y1, y2;
+ QObject *page1 = 0;
+ TabWidget *tw1 = KFormDesigner::findParent<KFormDesigner::TabWidget>(w1, "KFormDesigner::TabWidget", page1);
+ if (tw1) // special case
+ y1 = w1->mapTo(m_topLevelWidget, QPoint(0,0)).y() + tw1->tabBarHeight() -2 -2;
+ else
+ y1 = w1->mapTo(m_topLevelWidget, QPoint(0,0)).y();
+
+ QObject *page2 = 0;
+ TabWidget *tw2 = KFormDesigner::findParent<KFormDesigner::TabWidget>(w2, "KFormDesigner::TabWidget", page2);
+ if (tw1 && tw2 && tw1 == tw2 && page1 != page2) {
+ // this sorts widgets by tabs there're put in
+ return tw1->indexOf(static_cast<QWidget*>(page1)) - tw2->indexOf(static_cast<QWidget*>(page2));
+ }
+
+ if (tw2) // special case
+ y2 = w2->mapTo(m_topLevelWidget, QPoint(0,0)).y() + tw2->tabBarHeight() -2 -2;
+ else
+ y2 = w2->mapTo(m_topLevelWidget, QPoint(0,0)).y();
+
+ kdDebug() << w1->name() << ": " << y1 << " "
+ << " | " << w2->name() << ": " << y2 << endl;
+
+
+ //kdDebug() << w1->name() << ": " << w1->mapTo(m_topLevelWidget, QPoint(0,0)) << " " << w1->y()
+ //<< " | " << w2->name() << ":" /*<< w2->mapFrom(m_topLevelWidget, QPoint(0,w2->y()))*/ << " " << w2->y() << endl;
+ return y1 - y2;
+}
+
+#include "utils.moc"
diff --git a/kexi/formeditor/utils.h b/kexi/formeditor/utils.h
new file mode 100644
index 000000000..d5384e45a
--- /dev/null
+++ b/kexi/formeditor/utils.h
@@ -0,0 +1,113 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef FORMEDITORUTILS_H
+#define FORMEDITORUTILS_H
+
+#include <qptrlist.h>
+#include <qtabbar.h>
+#include <qtabwidget.h>
+
+//! @todo replace QTabWidget by KTabWidget after the bug with & is fixed:
+#define TabWidgetBase QTabWidget
+//#define USE_KTabWidget //todo: uncomment
+
+namespace KFormDesigner {
+
+class Form;
+
+/*! \return parent object of \a o that inherits \a className or NULL if no such parent
+ If the parent is found, \a prevPrev is set to a child of child of the parent,
+ what for TabWidget means the page widget. */
+template<class type>
+type* findParent(QObject* o, const char* className, QObject* &prevPrev)
+{
+ if (!o || !className || className[0]=='\0')
+ return 0;
+ QObject *prev = o;
+ while ( ((o=o->parent())) && !o->inherits(className) ) {
+ prevPrev = prev;
+ prev = o;
+ }
+ return static_cast<type*>(o);
+}
+
+//! A tab widget providing information about height of the tab bar.
+class KFORMEDITOR_EXPORT TabWidget : public TabWidgetBase
+{
+ Q_OBJECT
+ public:
+ TabWidget(QWidget *parent, const char *name)
+ : TabWidgetBase(parent, name) {}
+ virtual ~TabWidget() {}
+ int tabBarHeight() const { return tabBar()->height(); }
+};
+
+//! @short A list of widget pointers.
+typedef QPtrList<QWidget> WidgetList;
+
+//! @short An iterator for WidgetList.
+typedef QPtrListIterator<QWidget> WidgetListIterator;
+
+//! @short A helper for sorting widgets horizontally
+class HorWidgetList : public WidgetList
+{
+ public:
+ HorWidgetList(QWidget *topLevelWidget);
+ virtual ~HorWidgetList();
+ protected:
+ virtual int compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2);
+ QWidget *m_topLevelWidget;
+};
+
+//! @short A helper for sorting widgets vertically
+class VerWidgetList : public WidgetList
+{
+ public:
+ VerWidgetList(QWidget *topLevelWidget);
+ virtual ~VerWidgetList();
+ protected:
+ virtual int compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2);
+ QWidget *m_topLevelWidget;
+};
+
+/*! This function is used to remove all the child widgets from a list, and
+ keep only the "toplevel" ones. */
+KFORMEDITOR_EXPORT void removeChildrenFromList(WidgetList &list);
+
+/*! This helper function install an event filter on \a object and all of its
+ children, directed to \a container.
+ This is necessary to filter events for composed widgets. */
+KFORMEDITOR_EXPORT void installRecursiveEventFilter(QObject *object, QObject *container);
+
+/*! This helper function removes an event filter installed before
+ on \a object and all of its children.
+ This is necessary to filter events for composed widgets. */
+KFORMEDITOR_EXPORT void removeRecursiveEventFilter(QObject *object, QObject *container);
+
+KFORMEDITOR_EXPORT void setRecursiveCursor(QWidget *w, Form *form);
+
+/*! \return the size of \a w children. This can be used eg to get widget's sizeHint. */
+KFORMEDITOR_EXPORT QSize getSizeFromChildren(QWidget *widget, const char *inheritClass="QWidget");
+
+}
+
+#endif
+
diff --git a/kexi/formeditor/widgetfactory.cpp b/kexi/formeditor/widgetfactory.cpp
new file mode 100644
index 000000000..8122013c1
--- /dev/null
+++ b/kexi/formeditor/widgetfactory.cpp
@@ -0,0 +1,725 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "widgetfactory.h"
+
+#include <qcursor.h>
+#include <qobjectlist.h>
+#include <qdict.h>
+#include <qmetaobject.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+//#ifdef KEXI_KTEXTEDIT
+#include <ktextedit.h>
+//#else
+#include <klineedit.h>
+//#endif
+#include <kdialogbase.h>
+#include <keditlistbox.h>
+#include <kxmlguiclient.h>
+#include <kactioncollection.h>
+
+#include "richtextdialog.h"
+#include "editlistviewdialog.h"
+#include "resizehandle.h"
+#include "formmanager.h"
+#include "form.h"
+#include "container.h"
+#include "objecttree.h"
+#include "widgetlibrary.h"
+#include "utils.h"
+#include "widgetpropertyset.h"
+#include "widgetwithsubpropertiesinterface.h"
+#include <koproperty/property.h>
+
+using namespace KFormDesigner;
+
+///// Widget Info //////////////////////////
+
+WidgetInfo::WidgetInfo(WidgetFactory *f)
+ : m_inheritedClass(0)
+ , m_overriddenAlternateNames(0)
+ , m_factory(f)
+ , m_propertiesWithDisabledAutoSync(0)
+ , m_customTypesForProperty(0)
+{
+}
+
+WidgetInfo::WidgetInfo(WidgetFactory *f, const char* parentFactoryName,
+ const char* inheritedClassName)
+ : m_parentFactoryName( QCString("kformdesigner_")+parentFactoryName )
+ , m_inheritedClassName(inheritedClassName)
+ , m_inheritedClass(0)
+ , m_overriddenAlternateNames(0)
+ , m_factory(f)
+ , m_propertiesWithDisabledAutoSync(0)
+ , m_customTypesForProperty(0)
+{
+ m_class = inheritedClassName;
+}
+
+WidgetInfo::~WidgetInfo()
+{
+ delete m_overriddenAlternateNames;
+ delete m_propertiesWithDisabledAutoSync;
+ delete m_customTypesForProperty;
+}
+
+void WidgetInfo::addAlternateClassName(const QCString& alternateName, bool override)
+{
+ m_alternateNames += alternateName;
+ if (override) {
+ if (!m_overriddenAlternateNames)
+ m_overriddenAlternateNames = new QAsciiDict<char>(101);
+ m_overriddenAlternateNames->insert(alternateName, (char*)1);
+ }
+ else {
+ if (m_overriddenAlternateNames)
+ m_overriddenAlternateNames->take(alternateName);
+ }
+}
+
+bool WidgetInfo::isOverriddenClassName(const QCString& alternateName) const
+{
+ return m_overriddenAlternateNames && (m_overriddenAlternateNames->find(alternateName) != 0);
+}
+
+void WidgetInfo::setAutoSyncForProperty(const char *propertyName, tristate flag)
+{
+ if (!m_propertiesWithDisabledAutoSync) {
+ if (~flag)
+ return;
+ m_propertiesWithDisabledAutoSync = new QAsciiDict<char>(101);
+ }
+
+ if (~flag) {
+ m_propertiesWithDisabledAutoSync->remove(propertyName);
+ }
+ else {
+ m_propertiesWithDisabledAutoSync->insert(propertyName, flag==true ? (char*)1 : (char*)2);
+ }
+}
+
+tristate WidgetInfo::autoSyncForProperty(const char *propertyName) const
+{
+ char* flag = m_propertiesWithDisabledAutoSync ? m_propertiesWithDisabledAutoSync->find(propertyName) : 0;
+ if (!flag)
+ return cancelled;
+ return flag==(char*)1 ? true : false;
+}
+
+void WidgetInfo::setCustomTypeForProperty(const char *propertyName, int type)
+{
+ if (!propertyName || type==KoProperty::Auto)
+ return;
+ if (!m_customTypesForProperty) {
+ m_customTypesForProperty = new QMap<QCString,int>();
+ }
+ m_customTypesForProperty->replace(propertyName, type);
+}
+
+int WidgetInfo::customTypeForProperty(const char *propertyName) const
+{
+ if (!m_customTypesForProperty || !m_customTypesForProperty->contains(propertyName))
+ return KoProperty::Auto;
+ return (*m_customTypesForProperty)[propertyName];
+}
+
+
+///// Widget Factory //////////////////////////
+
+WidgetFactory::WidgetFactory(QObject *parent, const char *name)
+ : QObject(parent, (const char*)(QCString("kformdesigner_")+name))
+{
+ m_showAdvancedProperties = true;
+ m_classesByName.setAutoDelete(true);
+ m_hiddenClasses = 0;
+ m_guiClient = 0;
+}
+
+WidgetFactory::~WidgetFactory()
+{
+ delete m_hiddenClasses;
+}
+
+void WidgetFactory::addClass(WidgetInfo *w)
+{
+ WidgetInfo *oldw = m_classesByName[w->className()];
+ if (oldw==w)
+ return;
+ if (oldw) {
+ kdWarning() << "WidgetFactory::addClass(): class with name '" << w->className()
+ << "' already exists for factory '" << name() << "'" << endl;
+ return;
+ }
+ m_classesByName.insert( w->className(), w );
+}
+
+void WidgetFactory::hideClass(const char *classname)
+{
+ if (!m_hiddenClasses)
+ m_hiddenClasses = new QAsciiDict<char>(101, false);
+ m_hiddenClasses->insert(classname, (char*)1);
+}
+
+void
+WidgetFactory::createEditor(const QCString &classname, const QString &text,
+ QWidget *w, Container *container, QRect geometry,
+ int align, bool useFrame, bool multiLine, BackgroundMode background)
+{
+//#ifdef KEXI_KTEXTEDIT
+ if (multiLine) {
+ KTextEdit *textedit = new KTextEdit(text, QString::null, w->parentWidget());
+ textedit->setTextFormat(Qt::PlainText);
+ textedit->setAlignment(align);
+ if (dynamic_cast<QTextEdit*>(w)) {
+ textedit->setWordWrap(dynamic_cast<QTextEdit*>(w)->wordWrap());
+ textedit->setWrapPolicy(dynamic_cast<QTextEdit*>(w)->wrapPolicy());
+ }
+ textedit->setPalette(w->palette());
+ textedit->setFont(w->font());
+ textedit->setResizePolicy(QScrollView::Manual);
+ textedit->setGeometry(geometry);
+ if(background == Qt::NoBackground)
+ textedit->setBackgroundMode(w->backgroundMode());
+ else
+ textedit->setBackgroundMode(background);
+// textedit->setPaletteBackgroundColor(textedit->colorGroup().color( QColorGroup::Base ));
+ textedit->setPaletteBackgroundColor(w->paletteBackgroundColor());
+ for(int i =0; i <= textedit->paragraphs(); i++)
+ textedit->setParagraphBackgroundColor(i, w->paletteBackgroundColor());
+ textedit->selectAll(true);
+ textedit->setColor(w->paletteForegroundColor());
+ textedit->selectAll(false);
+ textedit->moveCursor(QTextEdit::MoveEnd, false);
+ textedit->setParagraphBackgroundColor(0, w->paletteBackgroundColor());
+ textedit->setVScrollBarMode(QScrollView::AlwaysOff); //ok?
+ textedit->setHScrollBarMode(QScrollView::AlwaysOff); //ok?
+ textedit->installEventFilter(this);
+ textedit->setFrameShape(useFrame ? QFrame::LineEditPanel : QFrame::NoFrame);
+ textedit->setMargin(2); //to move away from resize handle
+ textedit->show();
+ textedit->setFocus();
+ textedit->selectAll();
+ setEditor(w, textedit);
+
+ connect(textedit, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
+ connect(w, SIGNAL(destroyed()), this, SLOT(widgetDestroyed()));
+ connect(textedit, SIGNAL(destroyed()), this, SLOT(editorDeleted()));
+//#else
+ }
+ else {
+ KLineEdit *editor = new KLineEdit(text, w->parentWidget());
+ editor->setAlignment(align);
+ editor->setPalette(w->palette());
+ editor->setFont(w->font());
+ editor->setGeometry(geometry);
+ if(background == Qt::NoBackground)
+ editor->setBackgroundMode(w->backgroundMode());
+ else
+ editor->setBackgroundMode(background);
+ editor->installEventFilter(this);
+ editor->setFrame(useFrame);
+ editor->setMargin(2); //to move away from resize handle
+ editor->show();
+ editor->setFocus();
+ editor->selectAll();
+ connect(editor, SIGNAL(textChanged(const QString&)), this, SLOT(changeTextInternal(const QString&)));
+ connect(w, SIGNAL(destroyed()), this, SLOT(widgetDestroyed()));
+ connect(editor, SIGNAL(destroyed()), this, SLOT(editorDeleted()));
+
+ setEditor(w, editor);
+// m_editor = editor;
+ }
+ //copy properties if available
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(w);
+ QWidget *subwidget = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : w;
+ if (-1!=m_editor->metaObject()->findProperty("margin", true) && -1!=subwidget->metaObject()->findProperty("margin", true))
+ m_editor->setProperty("margin", subwidget->property("margin"));
+//#endif
+//js m_handles = new ResizeHandleSet(w, container->form(), true);
+ m_handles = container->form()->resizeHandlesForWidget(w);
+ if (m_handles) {
+ m_handles->setEditingMode(true);
+ m_handles->raise();
+ }
+
+ ObjectTreeItem *tree = container->form()->objectTree()->lookup(w->name());
+ if(!tree)
+ return;
+ tree->eventEater()->setContainer(this);
+
+ //m_widget = w;
+ setWidget(w, container);
+ m_editedWidgetClass = classname;
+ m_firstText = text;
+// m_container = container;
+
+ changeTextInternal(text); // to update size of the widget
+}
+
+void
+WidgetFactory::disableFilter(QWidget *w, Container *container)
+{
+ ObjectTreeItem *tree = container->form()->objectTree()->lookup(w->name());
+ if(!tree)
+ return;
+ tree->eventEater()->setContainer(this);
+
+ w->setFocus();
+//js m_handles = new ResizeHandleSet(w, container->form(), true);
+ m_handles = container->form()->resizeHandlesForWidget(w);
+ if (m_handles) {
+ m_handles->setEditingMode(true);
+ m_handles->raise();
+ }
+
+ //m_widget = w;
+ setWidget(w, container);
+// m_container = container;
+ setEditor(w, 0);
+// m_editor = 0;
+
+ // widget is disabled, so we re-enable it while editing
+ if(!tree->isEnabled()) {
+ QPalette p = w->palette();
+ QColorGroup cg = p.active();
+ p.setActive(p.disabled());
+ p.setDisabled(cg);
+ w->setPalette(p);
+ }
+
+ connect(w, SIGNAL(destroyed()), this, SLOT(widgetDestroyed()));
+}
+
+bool
+WidgetFactory::editList(QWidget *w, QStringList &list)
+{
+ KDialogBase dialog(w->topLevelWidget(), "stringlist_dialog", true, i18n("Edit List of Items"),
+ KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, false);
+
+ KEditListBox *edit = new KEditListBox(i18n("Contents of %1").arg(w->name()), &dialog, "editlist");
+ dialog.setMainWidget(edit);
+ edit->insertStringList(list);
+// edit->show();
+
+ if(dialog.exec() == QDialog::Accepted)
+ {
+ list = edit->items();
+ return true;
+ }
+ return false;
+}
+
+bool
+WidgetFactory::editRichText(QWidget *w, QString &text)
+{
+ RichTextDialog dlg(w, text);
+ if(dlg.exec()== QDialog::Accepted)
+ {
+ text = dlg.text();
+ return true;
+ }
+ return false;
+}
+
+void
+WidgetFactory::editListView(QListView *listview)
+{
+ EditListViewDialog dlg(((QWidget*)listview)->topLevelWidget());
+ //dlg.exec(listview);
+}
+
+bool
+WidgetFactory::eventFilter(QObject *obj, QEvent *ev)
+{
+ if( ((ev->type() == QEvent::Resize) || (ev->type() == QEvent::Move) ) && (obj == m_widget) && editor(m_widget)) {
+ // resize widget using resize handles
+ QWidget *ed = editor(m_widget);
+ resizeEditor(ed, m_widget, m_widget->className());
+ }
+ else if((ev->type() == QEvent::Paint) && (obj == m_widget) && editor(m_widget)) {
+ // paint event for container edited (eg button group)
+ return m_container->eventFilter(obj, ev);
+ }
+ else if((ev->type() == QEvent::MouseButtonPress) && (obj == m_widget) && editor(m_widget)) {
+ // click outside editor --> cancel editing
+ Container *cont = m_container;
+ resetEditor();
+ return cont->eventFilter(obj, ev);
+ }
+
+ if(ev->type() == QEvent::FocusOut)
+ {
+ QWidget *w = editor(m_widget);
+ if (!w)
+ w = (QWidget *)m_widget;
+ if(obj != (QObject *)w)
+ return false;
+
+ QWidget *focus = w->topLevelWidget()->focusWidget();
+ if(focus && w != focus && !w->child(focus->name(), focus->className()))
+ resetEditor();
+ }
+ else if(ev->type() == QEvent::KeyPress)
+ {
+ QWidget *w = editor(m_widget);
+ if (!w)
+ w = (QWidget *)m_widget;
+ if(obj != (QObject *)w)
+ return false;
+
+ QKeyEvent *e = static_cast<QKeyEvent*>(ev);
+ if(((e->key() == Qt::Key_Return) || (e->key() == Qt::Key_Enter)) && (e->state() != AltButton))
+ resetEditor();
+ if(e->key() == Qt::Key_Escape)
+ {
+ setEditorText(m_firstText);
+ //changeText(m_firstText);
+ resetEditor();
+ }
+ }
+ else if(ev->type() == QEvent::ContextMenu) {
+ QWidget *w = editor(m_widget);
+ if (!w)
+ w = (QWidget *)m_widget;
+ if(obj != (QObject *)w)
+ return false;
+
+ return true;
+ }
+// if(obj == m_widget)
+// return m_container->eventFilter(obj, ev);
+// else
+ return false;
+}
+
+void
+WidgetFactory::resetEditor()
+{
+ if (m_container)
+ m_container->stopInlineEditing();
+
+ QWidget *ed = editor(m_widget);
+ if(m_widget)
+ {
+ ObjectTreeItem *tree = m_container ? m_container->form()->objectTree()->lookup(m_widget->name()) : 0;
+ if(!tree)
+ {
+ kdDebug() << "WidgetFactory::resetEditor() : error cannot found a tree item " << endl;
+ return;
+ }
+ tree->eventEater()->setContainer(m_container);
+ if(m_widget) {// && !ed)
+ setRecursiveCursor(m_widget, m_container->form());
+ if (m_widget->inherits("QLineEdit") || m_widget->inherits("QTextEdit")) { //fix weird behaviour
+ m_widget->unsetCursor();
+ m_widget->setCursor(Qt::ArrowCursor);
+ }
+ }
+
+ // disable again the widget
+ if(!ed && !tree->isEnabled()) {
+ QPalette p = m_widget->palette();
+ QColorGroup cg = p.active();
+ p.setActive(p.disabled());
+ p.setDisabled(cg);
+ m_widget->setPalette(p);
+ }
+ }
+ if(ed)
+ {
+ changeTextInternal(editorText());
+ disconnect(ed, 0, this, 0);
+ ed->deleteLater();
+ }
+
+ if(m_widget)
+ {
+ disconnect(m_widget, 0, this, 0);
+ m_widget->repaint();
+ }
+
+//js delete m_handles;
+ if (m_handles) {
+ m_handles->setEditingMode(false);
+ }
+ setEditor(m_widget, 0);
+// m_editor = 0;
+ setWidget(0, 0);
+ //m_widget = 0;
+ m_handles = 0;
+// m_container = 0;
+}
+
+void
+WidgetFactory::widgetDestroyed()
+{
+ if(m_editor)
+ {
+ m_editor->deleteLater();
+ m_editor = 0;
+ }
+
+//js delete m_handles;
+ if (m_handles) {
+ m_handles->setEditingMode(false);
+
+ }
+ m_widget = 0;
+ m_handles = 0;
+ m_container = 0;
+}
+
+void
+WidgetFactory::editorDeleted()
+{
+//js delete m_handles;
+ if (m_handles) {
+ m_handles->setEditingMode(false);
+ }
+ setEditor(m_widget, 0);
+ setWidget(0, 0);
+// m_widget = 0;
+ m_handles = 0;
+// m_container = 0;
+}
+
+void
+WidgetFactory::changeProperty(const char *name, const QVariant &value, Form *form)
+//WidgetFactory::changeProperty(const char *name, const QVariant &value, Container *container)
+{
+// if (!form->manager())
+// return;
+ if(form->selectedWidgets()->count() > 1)
+ { // If eg multiple labels are selected, we only want to change the text of one of them (the one the user cliked on)
+ if(m_widget)
+ m_widget->setProperty(name, value);
+ else
+ form->selectedWidgets()->first()->setProperty(name, value);
+ }
+ else
+ {
+ WidgetPropertySet *set = KFormDesigner::FormManager::self()->propertySet();
+ if(set->contains(name))
+ (*set)[name] = value;
+ }
+}
+
+/*
+void
+WidgetFactory::addPropertyDescription(Container *container, const char *prop, const QString &desc)
+{
+ WidgetPropertySet *buff = container->form()->manager()->buffer();
+ buff->addPropertyDescription(prop, desc);
+}
+
+void
+WidgetFactory::addValueDescription(Container *container, const char *value, const QString &desc)
+{
+ WidgetPropertySet *buff = container->form()->manager()->buffer();
+ buff->addValueDescription(value, desc);
+}*/
+
+bool
+WidgetFactory::isPropertyVisible(const QCString &classname, QWidget *w,
+ const QCString &property, bool multiple, bool isTopLevel)
+{
+ if (multiple)
+ {
+ return property=="font" || property=="paletteBackgroundColor" || property=="enabled"
+ || property=="paletteForegroundColor" || property=="cursor" || property=="paletteBackgroundPixmap";
+ }
+
+// if(d->properties.isEmpty() && !isTopLevel)
+// d->properties << "caption" << "icon" << "sizeIncrement" << "iconText";
+// if(! (d->properties.grep(property)).isEmpty() )
+// return false;
+
+ return isPropertyVisibleInternal(classname, w, property, isTopLevel);
+// return !multiple && isPropertyVisibleInternal(classname, w, property);
+}
+
+bool
+WidgetFactory::isPropertyVisibleInternal(const QCString &, QWidget *w,
+ const QCString &property, bool isTopLevel)
+{
+ Q_UNUSED( w );
+
+#ifdef KEXI_NO_CURSOR_PROPERTY
+//! @todo temporary unless cursor works properly in the Designer
+ if (property=="cursor")
+ return false;
+#endif
+
+ if (!isTopLevel
+ && (property=="caption" || property=="icon" || property=="sizeIncrement" || property=="iconText")) {
+ // don't show these properties for a non-toplevel widget
+ return false;
+ }
+ return true;
+}
+
+bool
+WidgetFactory::propertySetShouldBeReloadedAfterPropertyChange(const QCString& classname, QWidget *w,
+ const QCString& property)
+{
+ Q_UNUSED(classname);
+ Q_UNUSED(w);
+ Q_UNUSED(property);
+ return false;
+}
+
+void
+WidgetFactory::resizeEditor(QWidget *, QWidget *, const QCString&)
+{
+}
+
+void
+WidgetFactory::slotTextChanged()
+{
+ changeTextInternal(editorText());
+}
+
+bool
+WidgetFactory::clearWidgetContent(const QCString &, QWidget *)
+{
+ return false;
+}
+
+void
+WidgetFactory::changeTextInternal(const QString& text)
+{
+ if (changeText( text ))
+ return;
+ //try in inherited
+ if (!m_editedWidgetClass.isEmpty()) {
+ WidgetInfo *wi = m_classesByName[ m_editedWidgetClass ];
+ if (wi && wi->inheritedClass()) {
+// wi->inheritedClass()->factory()->m_container = m_container;
+ wi->inheritedClass()->factory()->changeText( text );
+ }
+ }
+}
+
+bool
+WidgetFactory::changeText(const QString& text)
+{
+ changeProperty( "text", text, m_container->form() );
+ return true;
+}
+
+bool
+WidgetFactory::readSpecialProperty(const QCString &, QDomElement &, QWidget *, ObjectTreeItem *)
+{
+ return false;
+}
+
+bool
+WidgetFactory::saveSpecialProperty(const QCString &, const QString &, const QVariant&, QWidget *, QDomElement &, QDomDocument &)
+{
+ return false;
+}
+
+bool WidgetFactory::inheritsFactories()
+{
+ for (QAsciiDictIterator<WidgetInfo> it(m_classesByName); it.current(); ++it) {
+ if (!it.current()->parentFactoryName().isEmpty())
+ return true;
+ }
+ return false;
+}
+
+QString WidgetFactory::editorText() const {
+ QWidget *ed = editor(m_widget);
+ return dynamic_cast<KTextEdit*>(ed) ? dynamic_cast<KTextEdit*>(ed)->text() : dynamic_cast<KLineEdit*>(ed)->text();
+}
+
+void WidgetFactory::setEditorText(const QString& text) {
+ QWidget *ed = editor(m_widget);
+ if (dynamic_cast<KTextEdit*>(ed))
+ dynamic_cast<KTextEdit*>(ed)->setText(text);
+ else
+ dynamic_cast<KLineEdit*>(ed)->setText(text);
+}
+
+void WidgetFactory::setEditor(QWidget *widget, QWidget *editor)
+{
+ if (!widget)
+ return;
+ WidgetInfo *winfo = m_classesByName[widget->className()];
+ if (!winfo || winfo->parentFactoryName().isEmpty()) {
+ m_editor = editor;
+ }
+ else {
+ WidgetFactory *f = m_library->factory(winfo->parentFactoryName());
+ if (f!=this)
+ f->setEditor(widget, editor);
+ m_editor = editor; //keep a copy
+ }
+}
+
+QWidget *WidgetFactory::editor(QWidget *widget) const
+{
+ if (!widget)
+ return 0;
+ WidgetInfo *winfo = m_classesByName[widget->className()];
+ if (!winfo || winfo->parentFactoryName().isEmpty()) {
+ return m_editor;
+ }
+ else {
+ WidgetFactory *f = m_library->factoryForClassName(widget->className());
+ if (f!=this)
+ return f->editor(widget);
+ return m_editor;
+ }
+}
+
+void WidgetFactory::setWidget(QWidget *widget, Container* container)
+{
+ WidgetInfo *winfo = widget ? m_classesByName[widget->className()] : 0;
+ if (winfo && !winfo->parentFactoryName().isEmpty()) {
+ WidgetFactory *f = m_library->factory(winfo->parentFactoryName());
+ if (f!=this)
+ f->setWidget(widget, container);
+ }
+ m_widget = widget; //keep a copy
+ m_container = container;
+}
+
+QWidget *WidgetFactory::widget() const
+{
+ return m_widget;
+}
+
+void WidgetFactory::setInternalProperty(const QCString& classname, const QCString& property,
+ const QString& value)
+{
+ m_internalProp[classname+":"+property]=value;
+}
+
+void WidgetFactory::setPropertyOptions( WidgetPropertySet& /*buf*/, const WidgetInfo& /*info*/, QWidget * /*w*/ )
+{
+ //nothing
+}
+
+#include "widgetfactory.moc"
diff --git a/kexi/formeditor/widgetfactory.desktop b/kexi/formeditor/widgetfactory.desktop
new file mode 100644
index 000000000..1bf68d9ed
--- /dev/null
+++ b/kexi/formeditor/widgetfactory.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=KFormDesigner/WidgetFactory
+Comment=Widget Factory Base
+Comment[bg]=Създаване на графични обекти
+Comment[ca]=Factoria base d'estris
+Comment[cy]=Bâs Ffatri Celfigion
+Comment[de]=Basis für die Erstellung von Bedienelementen
+Comment[el]=Βάση μηχανής γραφικών συστατικών
+Comment[eo]=Fenestraĵfabrikbazo
+Comment[es]=Base de fábrica de elementos
+Comment[et]=Vidinate põhibaas
+Comment[eu]=Trepeten faktoriaren oinarria
+Comment[fa]=پایۀ کارخانۀ عنصر
+Comment[fi]=Käyttöliittymäelementtien pohja
+Comment[fr]=Base de création de widgets
+Comment[gl]=Fábrica de Elementos
+Comment[he]=בסיס למפעל פריטים
+Comment[hr]=Baza widget tvornice
+Comment[hu]=Widgetkészítő-alap
+Comment[is]=Hluta smiðjugrunnur
+Comment[it]=Base della fabbrica di oggetti
+Comment[ja]=ウィジェットファクトリーベース
+Comment[lv]=Logdaļas fabrikas bāze
+Comment[ms]=Pangkalan Kilang Widget
+Comment[nb]=Base for elementfabrikk
+Comment[nds]=Basis för't Opstellen vun Stüerelementen
+Comment[ne]=विजेट फ्याक्ट्री आधार
+Comment[nn]=Base for elementfabrikk
+Comment[pl]=Podstawowa fabryka kontrolek
+Comment[pt]=Fábrica de Elementos
+Comment[pt_BR]=Widget de Fábrica Base
+Comment[ru]=Базовое приложение, содержащее офисный элемент управления
+Comment[se]=Áhtafabrihka vuođđu
+Comment[sk]=Základ pre vytváracie rozhranie prvkov
+Comment[sl]=Tovarna gradnikov
+Comment[sr]=Основа фабрике контрола
+Comment[sr@Latn]=Osnova fabrike kontrola
+Comment[sv]=Bas för att skapa grafiska komponenter
+Comment[ta]= widget தொழிற்சாலை தளம்
+Comment[tg]=Гузориши база, ки таркиботи идораи офис дар дохил дорад
+Comment[uk]=База фабрики віджетів
+Comment[zh_CN]=基础组件工厂
+Comment[zh_TW]=視窗元件工廠基地
+
+[PropertyDef::X-KFormDesigner-FactoryGroup]
+Type=QString
+
+[PropertyDef::X-KFormDesigner-WidgetFactoryVersion]
+Type=int
+
+[PropertyDef::X-KFormDesigner-XMLGUIFileName]
+Type=QString
diff --git a/kexi/formeditor/widgetfactory.h b/kexi/formeditor/widgetfactory.h
new file mode 100644
index 000000000..43736ebcd
--- /dev/null
+++ b/kexi/formeditor/widgetfactory.h
@@ -0,0 +1,518 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNERWIDGETFACTORY_H
+#define KFORMDESIGNERWIDGETFACTORY_H
+
+
+#include <qobject.h>
+#include <qguardedptr.h>
+#include <qpixmap.h>
+#include <qpopupmenu.h>
+#include <qasciidict.h>
+
+#include <kexiutils/tristate.h>
+
+// class QPixmap;
+template<class type> class QValueVector;
+template<class type> class QPtrList;
+template<class type> class QDict;
+class QWidget;
+class QDomElement;
+class QDomDocument;
+class QVariant;
+class QListView;
+class KActionCollection;
+class KTextEdit;
+class KLineEdit;
+class KXMLGUIClient;
+
+namespace KoProperty {
+ class Set;
+}
+
+namespace KFormDesigner {
+
+class WidgetFactory;
+class WidgetLibrary;
+class Container;
+class ResizeHandleSet;
+class ObjectTreeItem;
+class WidgetPropertySet;
+class Form;
+
+/**
+ * This class holds properties of widget classes provided by a factory.
+ */
+class KFORMEDITOR_EXPORT WidgetInfo
+{
+ public:
+ typedef QPtrList<WidgetInfo> List;
+ typedef QAsciiDict<WidgetInfo> Dict;
+
+ WidgetInfo(WidgetFactory *f);
+
+ WidgetInfo(WidgetFactory *f, const char* parentFactoryName, const char* inheritedClassName = 0);
+
+ virtual ~WidgetInfo();
+
+ //! \return a pixmap associated with the widget
+ QString pixmap() const { return m_pixmap; }
+
+ //! \return the class name of a widget e.g. 'QLineEdit'
+ QCString className() const { return m_class; }
+
+ /*! \return the name used to name widget, that will appear eg in scripts (must not contain spaces
+ nor non-latin1 characters) */
+ QString namePrefix() const { return m_prefixName; }
+
+ //! \return the real name e.g. 'Line Edit', showed eg in ObjectTreeView
+ QString name() const { return m_name; }
+
+ QString description() const { return m_desc; }
+ QString includeFileName() const { return m_include; }
+ QValueList<QCString> alternateClassNames() const { return m_alternateNames; }
+ QString savingName() const { return m_saveName; }
+ WidgetFactory *factory() const { return m_factory; }
+
+ void setPixmap(const QString &p) { m_pixmap = p; }
+ void setClassName(const QCString &s) { m_class = s; }
+ void setName(const QString &n) { m_name = n; }
+ void setNamePrefix(const QString &n) { m_prefixName = n; }
+ void setDescription(const QString &desc) { m_desc = desc;}
+
+ /*! Sets the C++ include file corresponding to this class,
+ that uic will need to add when creating the file. You don't have to set this for Qt std widgets.*/
+ void setIncludeFileName(const QString &name) { m_include = name;}
+
+ /*! Sets alternate names for this class.
+ If this name is found when loading a .ui file, the className() will be used instead.
+ It allows to support both KDE and Qt versions of widget, without duplicating code.
+ As a rule, className() should always return a class name which is inherited from
+ alternate class. For example KListView class has alternate QListView class.
+
+ \a override parameter overrides class name of a widget,
+ even if it was implemented in other factory.
+ By default it's set to false, what means that no other class is overridden
+ by this widget class if there is already a class implementing it
+ (no matter in which factory).
+ By forced overriding existing class with other - custom, user
+ will be able to see more or less properties and experience different behaviour.
+ For example, in Kexi application, KLineEdit class contains additional
+ "datasource" property for binding to database sources.
+ */
+ void addAlternateClassName(const QCString& alternateName, bool override = false);
+
+ /*! \return true is a class \a alternateName is defined as alternate name with
+ 'override' flag set to true, using addAlternateClassName().
+ If this flag is set to false (the default) or there's no such alternate class
+ name defined. */
+ bool isOverriddenClassName(const QCString& alternateName) const;
+
+ /*! Sets the name that will be written in the .ui file when saving.
+ This name must be one of alternate names (or loading will be impossible).
+
+ On form data saving to XML .ui format, saveName is used instead,
+ so .ui format is not broken and still usable with other software as Qt Designer.
+ Custom properties are saved as well with 'stdset' attribute set to 0. */
+ void setSavingName(const QString &saveName) { m_saveName = saveName; }
+
+ /*! Sets autoSync flag for property \a propertyName.
+ This allows to override autoSync flag for certain widget's property, because
+ e.g. KoProperty::Editor can have autoSync flag set to false or true, but
+ not all properties have to comply with that.
+ \a flag equal to cancelled value means there is no overriding (the default). */
+ void setAutoSyncForProperty(const char *propertyName, tristate flag);
+
+ /*! \return autoSync override value (true or false) for \a propertyName.
+ If cancelled value is returned, there is no overriding (the default). */
+ tristate autoSyncForProperty(const char *propertyName) const;
+
+ QCString parentFactoryName() const { return m_parentFactoryName; }
+
+ WidgetInfo* inheritedClass() const { return m_inheritedClass; }
+
+ /*! Sets custom type \a type for property \a propertyName.
+ This allows to override default type, especially when custom property
+ and custom property editor item has to be used. */
+ void setCustomTypeForProperty(const char *propertyName, int type);
+
+ /*! \return custom type for property \a propertyName. If no specific custom type has been assigned,
+ KoProperty::Auto is returned.
+ @see setCustomTypeForProperty() */
+ int customTypeForProperty(const char *propertyName) const;
+
+ protected:
+ QCString m_parentFactoryName, m_inheritedClassName; //!< Used for inheriting widgets between factories
+ WidgetInfo* m_inheritedClass;
+
+ private:
+ QString m_pixmap;
+ QCString m_class;
+ QString m_name;
+ QString m_prefixName;
+ QString m_desc;
+ QString m_include;
+ QValueList<QCString> m_alternateNames;
+ QAsciiDict<char> *m_overriddenAlternateNames;
+ QString m_saveName;
+ QGuardedPtr<WidgetFactory> m_factory;
+ QAsciiDict<char> *m_propertiesWithDisabledAutoSync;
+ QMap<QCString,int> *m_customTypesForProperty;
+
+ friend class WidgetLibrary;
+};
+
+//! The base class for all widget Factories
+/*! This is the class you need to inherit to create a new Factory. There are few
+ virtuals you need to implement, and some other functions
+ to implement if you want more features.\n \n
+
+ <b>Widget Creation</b>\n
+ To be able to create widgets, you need to implement the create() function, an classes(),
+ which should return all the widgets supported by this factory.\n \n
+
+ <b>GUI Integration</b>\n
+ The following functions allow you to customize even more the look-n-feel of your widgets inside KFormDesigner.
+ You can use createMenuActions() to add custom items in widget's context menu. The previewWidget()
+ is called when the Form gets in Preview mode, and you have a last opportunity to remove all editing-related
+ stuff (see eg \ref Spring class).\n
+ You can also choose which properties to show in the Property Editor.
+ By default, most all properties are shown (see implementation for details),
+ but you can hide some reimplementing isPropertyVisibleInternal() (don't forget to call superclass' method)
+ To add new properties, just define new Q_PROPERTY in widget class definition.\n \n
+
+ <b>Inline editing</b>\n
+ KFormDesigner allow you to edit the widget's contents inside Form, without using a dialog.
+ You can of course customize the behaviour of your widgets, using startEditing(). There are some editing
+ modes already implemented in WidgetFactroy, but you can create your own if you want:
+ \li Editing using a line edit (createEditor()): a line edit is created on top of widget,
+ where the user inputs text. As the text changes, changeText() is called
+ (where you should set your widget's text and resize widget to fit the text if needed) and resizeEditor()
+ to update editor's position when widget is moved/resized.\n
+ \li Editing by disabling event filter: if you call disableFilter(), the event filter
+ on the object is temporarily disabled, so the widget behaves as usual. This
+ can be used for more complex widgets, such as spinbox, date/time edit, etc.
+ \li Other modes: there are 3 other modes, to edit a string list: editList()
+ (for combo box, listbox), to edit rich text: editRichText() (for labels, etc.)
+ and to edit a listview: editListView(). \n \n
+
+ <b>Widget saving/loading</b>\n
+ You can also control how your widget are saved/loaded. You can choose which properties to save
+ (see autoSaveProperties()), and save/load custom properties, ie
+ properties that are not Q_PROPERTY but you want to save in the UI file. This is used eg to
+ save combo box or listview contents (see saveSpecialProperty() and
+ readSpecialProperty()). \n \n
+
+ <b>Special internal properties</b>\n
+ Use void setInternalProperty(const QCString& classname, const QCString& property, const QString& value);
+ to set values of special internal properties.
+ Currently these properties are used for customizing popup menu items used for orientation selection.
+ Customization for class ClassName should look like:
+ <code> void setInternalProperty("ClassName", "orientationSelectionPopup", "myicon"); </code>
+ Available internal properties:
+ * "orientationSelectionPopup" - set it to "1" if you want a given class to offer orientation selection,
+ so orientation selection popup will be displayed when needed.
+ * "orientationSelectionPopup:horizontalIcon" - sets a name of icon for "Horizontal" item
+ for objects of class 'ClassName'. Set this property only for classes supporting orientations.
+ * "orientationSelectionPopup:verticalIcon" - the same for "Vertical" item.
+ Set this property only for classes supporting orientations.
+ * "orientationSelectionPopup:horizontalText" - sets a i18n'd text for "Horizontal" item
+ for objects of class 'ClassName', e.g. i18n("Insert Horizontal Line").
+ Set this property only for classes supporting orientations.
+ * "orientationSelectionPopup:verticalText" - the same for "Vertical" item,
+ e.g. i18n("Insert Vertical Line"). Set this property only for classes supporting orientations.
+ * "dontStartEditingOnInserting" - if not empty, WidgetFactory::startEditing() will not be executed upon
+ widget inseting by a user.
+ * "forceShowAdvancedProperty:{propertyname}" - set it to "1" for "{propertyname}" advanced property
+ if you want to force it to be visible even if WidgetLibrary::setAdvancedPropertiesVisible(false)
+ has been called. For example, setting "forceShowAdvancedProperty:pixmap" to "1"
+ unhides "pixmap" property for a given class.
+
+ See StdWidgetFactory::StdWidgetFactory() for properties like
+ "Line:orientationSelectionPopup:horizontalIcon".
+
+ \n\n
+ See the standard factories in formeditor/factories for an example of factories,
+ and how to deal with complex widgets (eg tabwidget).
+ */
+class KFORMEDITOR_EXPORT WidgetFactory : public QObject
+{
+ Q_OBJECT
+ public:
+ //! Options used in createWidget()
+ enum CreateWidgetOptions {
+ AnyOrientation = 1, //!< any orientation hint
+ HorizontalOrientation = 2, //!< horizontal orientation hint
+ VerticalOrientation = 4, //!< vertical orientation hint
+ DesignViewMode = 8, //!< create widget in design view mode, otherwise preview mode
+ DefaultOptions = AnyOrientation | DesignViewMode
+ };
+
+ WidgetFactory(QObject *parent=0, const char *name=0);
+ virtual ~WidgetFactory();
+
+ /*! Adds a new class described by \a w. */
+ void addClass(WidgetInfo *w);
+
+ /*! This method allows to force a class \a classname to hidden.
+ It is useful if you do not want a class to be available
+ (e.g. because it is not implemented well yet for our purposes).
+ All widget libraries are affected by this setting. */
+ void hideClass(const char *classname);
+
+ /**
+ * \return all classes which are provided by this factory
+ */
+ const WidgetInfo::Dict classes() const { return m_classesByName; }
+
+ /**
+ * Creates a widget (and if needed a KFormDesigner::Container)
+ * \return the created widget
+ * \param classname the classname of the widget, which should get created
+ * \param parent the parent for the created widget
+ * \param name the name of the created widget
+ * \param container the toplevel Container (if a container should get created)
+ * \param options options for the created widget: orientation and view mode (see CreateWidgetOptions)
+ */
+ virtual QWidget* createWidget(const QCString &classname, QWidget *parent, const char *name,
+ KFormDesigner::Container *container,
+ int options = DefaultOptions) = 0;
+
+ /*! Creates custom actions. Reimplement this if you need to add some
+ actions coming from the factory. */
+ virtual void createCustomActions(KActionCollection *col) { Q_UNUSED(col); };
+
+ /*! This function can be used to add custom items in widget \a w context
+ menu \a menu. */
+ virtual bool createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *container)=0;
+
+ /*! Creates (if necessary) an editor to edit the contents of the widget directly in the Form
+ (eg creates a line edit to change the text of a label). \a classname is
+ the class the widget belongs to, \a w is the widget to edit
+ and \a container is the parent container of this widget (to access Form etc.).
+ */
+ virtual bool startEditing(const QCString &classname, QWidget *w, Container *container)=0;
+
+ /*! This function is called just before the Form is previewed. It allows widgets
+ to make changes before switching (ie for a Spring, hiding the cross) */
+ virtual bool previewWidget(const QCString &classname, QWidget *widget, Container *container)=0;
+
+ virtual bool clearWidgetContent(const QCString &classname, QWidget *w);
+
+ /*! This function is called when FormIO finds a property, at save time,
+ that it cannot handle (ie not a normal property).
+ This way you can save special properties, for example the contents of a listbox.
+ \sa readSpecialProperty()
+ */
+ virtual bool saveSpecialProperty(const QCString &classname, const QString &name,
+ const QVariant &value, QWidget *w,
+ QDomElement &parentNode, QDomDocument &parent);
+
+ /*! This function is called when FormIO finds a property or an unknown
+ element in a .ui file. You can this way load a special property, for
+ example the contents of a listbox.
+ \sa saveSpecialProperty()
+ */
+ virtual bool readSpecialProperty(const QCString &classname, QDomElement &node,
+ QWidget *w, ObjectTreeItem *item);
+
+ /*! This function is used to know whether the \a property for the widget \a w
+ should be shown or not in the PropertyEditor. If \a multiple is true,
+ then multiple widgets of the same class are selected, and you should
+ only show properties shared by widgets (eg font, color). By default,
+ all properties are shown if multiple == true, and none if multiple == false. */
+ bool isPropertyVisible(const QCString &classname, QWidget *w,
+ const QCString &property, bool multiple, bool isTopLevel);
+
+ /*! You need to return here a list of the properties that should automatically be saved
+ for a widget belonging to \a classname, and your custom properties (eg "text"
+ for label or button, "contents" for combobox...). */
+ virtual QValueList<QCString> autoSaveProperties(const QCString &classname)=0;
+
+ /*! \return The i18n'ed name of the property whose name is \a name,
+ that will be displayed in PropertyEditor. */
+ inline QString propertyDescForName(const QCString &name) { return m_propDesc[name]; };
+
+ /*! \return The i18n'ed name of the property's value whose name is \a name. */
+ inline QString propertyDescForValue(const QCString &name) { return m_propValDesc[name]; };
+
+ /*! This method is called after WidgetPropertySet was filled with properties
+ of a widget \a w, of class defined by \a info.
+ Default implementation does nothing.
+ Implement this if you need to set options for properties within the set \a buf. */
+ virtual void setPropertyOptions( WidgetPropertySet& buf, const WidgetInfo& info, QWidget *w );
+
+ /*! \return internal property \a property for a class \a classname.
+ Internal properties are not stored within objects, but can be just provided
+ to describe classes' details. */
+ inline QString internalProperty(const QCString& classname, const QCString& property) const {
+ return m_internalProp[classname+":"+property];
+ }
+
+ protected:
+ /*! This function is called when we want to know whether the property should be visible.
+ Implement it in the factory; don't forget to call implementation in the superclass.
+ Default implementation hides "caption", "icon", "sizeIncrement" and "iconText" properties. */
+ virtual bool isPropertyVisibleInternal(const QCString &classname, QWidget *w,
+ const QCString &property, bool isTopLevel);
+
+ /*! Sometimes property sets should be reloaded when a given property value changed.
+ Implement it in the factory. Default implementation always returns false. */
+ virtual bool propertySetShouldBeReloadedAfterPropertyChange(const QCString& classname, QWidget *w,
+ const QCString& property);
+
+ /*! This function creates a KLineEdit to input some text and edit a widget's contents.
+ This can be used in startEditing(). \a text is the text to display by default
+ in the line edit, \a w is the edited widget, \a geometry is the geometry the new line
+ edit should have, and \a align is Qt::AlignmentFlags of the new line edit. */
+ void createEditor(const QCString &classname, const QString &text,
+ QWidget *w, Container *container, QRect geometry,
+ int align, bool useFrame=false, bool multiLine = false,
+ BackgroundMode background = Qt::NoBackground);
+
+ /*! This function provides a simple editing mode : it justs disable event filtering
+ for the widget, and it install it again when
+ the widget loose focus or Enter is pressed.
+ */
+ void disableFilter(QWidget *w, Container *container);
+
+ /*! This function creates a little dialog (a KEditListBox) to modify the contents
+ of a list (of strings). It can be used to modify the contents
+ of a combo box for instance. The modified list is copied
+ into \a list when the user presses "Ok".*/
+ bool editList(QWidget *w, QStringList &list);
+
+ /*! This function creates a little editor to modify rich text. It supports alignment,
+ subscript and superscript and all basic formatting properties.
+ If the user presses "Ok", the edited text is put in \a text.
+ If he presses "Cancel", nothing happens. */
+ bool editRichText(QWidget *w, QString &text);
+
+ /*! This function creates a dialog to modify the contents of a ListView. You can modify both
+ columns and list items. The listview is automatically updated if the user presses "Ok".*/
+ void editListView(QListView *listview);
+
+ /*! This function destroys the editor when it loses focus or Enter is pressed. */
+ virtual bool eventFilter(QObject *obj, QEvent *ev);
+
+ /*! This function is used to modify a property of a widget (eg after editing it).
+ Please use it instead of w->setProperty() to allow sync inside PropertyEditor.
+ */
+ void changeProperty(const char *name, const QVariant &value, Form *form);
+
+ /*! This function is called when the widget is resized,
+ and the \a editor size needs to be updated. */
+ virtual void resizeEditor(QWidget *editor, QWidget *widget, const QCString &classname);
+
+// /*! Adds the i18n'ed description of a property, which will be shown in PropertyEditor. */
+// void addPropertyDescription(Container *container, const char *prop, const QString &desc);
+
+// /*! Adds the i18n'ed description of a property value, which will be shown in PropertyEditor. */
+// void addValueDescription(Container *container, const char *value, const QString &desc);
+
+ /*! \return true if at least one class defined by this factory inherits
+ a class from other factory. Used in WidgetLibrary::loadFactories()
+ to load factories in proper order. */
+ bool inheritsFactories();
+
+ public slots:
+
+ /*! @internal. This slot is called when the editor has lost focus or the user pressed Enter.
+ It destroys the editor or installs again the event filter on the widget. */
+ void resetEditor();
+
+ protected slots:
+ /*!
+ Default implementation changes "text" property.
+ You have to reimplement this function for editing inside the Form to work if your widget's
+ property you want to change isn't named "text".
+ This slot is called when the line edit text changes, and you have to make
+ it really change the good property of the widget using changeProperty() (text, or title, etc.).
+ */
+ virtual bool changeText(const QString &newText);
+
+ void changeTextInternal(const QString& text);
+
+ void slotTextChanged();
+
+ /*! This slot is called when the editor is destroyed.*/
+ void editorDeleted();
+ void widgetDestroyed();
+
+ protected:
+ QString editorText() const;
+ void setEditorText(const QString& text);
+ void setEditor(QWidget *widget, QWidget *editor);
+ QWidget *editor(QWidget *widget) const;
+ void setWidget(QWidget *widget, Container *container);
+ QWidget *widget() const;
+
+ /*! Assigns \a value for internal property \a property for a class \a classname.
+ Internal properties are not stored within objects, but can be provided
+ to describe classes' details. */
+ void setInternalProperty(const QCString& classname, const QCString& property, const QString& value);
+
+ WidgetLibrary *m_library;
+ QCString m_editedWidgetClass;
+//#ifdef KEXI_KTEXTEDIT
+// QGuardedPtr<KTextEdit> m_editor;
+//#else
+// QGuardedPtr<KLineEdit> m_editor;
+//#endif
+ QString m_firstText;
+ QGuardedPtr<ResizeHandleSet> m_handles;
+ QGuardedPtr<Container> m_container;
+// WidgetInfo::List m_classes;
+ WidgetInfo::Dict m_classesByName;
+ QAsciiDict<char>* m_hiddenClasses;
+
+ //! i18n stuff
+ QMap<QCString, QString> m_propDesc;
+ QMap<QCString, QString> m_propValDesc;
+ //! internal properties
+ QMap<QCString, QString> m_internalProp;
+
+ /*! flag useful to decide whether to hide some properties.
+ It's value is inherited from WidgetLibrary. */
+ bool m_showAdvancedProperties;
+
+ /*! Contains name of an XMLGUI file providing toolbar buttons
+ (and menu items in the future?) for the factory.
+ Can be empty, e.g. for the main factory which has XMLGUI defined in the shell window itself
+ (e.g. kexiformpartinstui.rc for Kexi Forms). This name is set in WidgetLibrary::loadFactories() */
+ QString m_xmlGUIFileName;
+
+ KXMLGUIClient *m_guiClient;
+
+ QGuardedPtr<QWidget> m_widget;
+ QGuardedPtr<QWidget> m_editor;
+
+ friend class WidgetLibrary;
+};
+
+//! macro to declare KFormDesigner-compatible widget factory as a KDE Component factory
+#define KFORMDESIGNER_WIDGET_FACTORY(factoryClassName, libraryName) \
+ K_EXPORT_COMPONENT_FACTORY(kformdesigner_ ## libraryName, KGenericFactory<factoryClassName>("kformdesigner_" # libraryName))
+
+}
+#endif
diff --git a/kexi/formeditor/widgetlibrary.cpp b/kexi/formeditor/widgetlibrary.cpp
new file mode 100644
index 000000000..1a1981952
--- /dev/null
+++ b/kexi/formeditor/widgetlibrary.cpp
@@ -0,0 +1,769 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qdom.h>
+#include <qstrlist.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <klibloader.h>
+#include <kparts/componentfactory.h>
+#include <ktrader.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+
+#include "widgetfactory.h"
+#include "widgetlibrary.h"
+#include "libactionwidget.h"
+#include "container.h"
+#include "form.h"
+#include "formIO.h"
+
+namespace KFormDesigner {
+
+//! @internal
+class XMLGUIClient : public QObject, public KXMLGUIClient
+{
+ public:
+ XMLGUIClient(KXMLGUIClient* parent, const QString& xmlFileName)
+ : QObject(parent->actionCollection()), KXMLGUIClient(parent)
+ {
+ setXMLFile( xmlFileName, true /*merge*/ );
+ }
+};
+
+//! @internal
+class WidgetLibraryPrivate
+{
+ public:
+ WidgetLibraryPrivate()
+ : widgets(101)
+// , alternateWidgets(101)
+ , services(101, false)
+ , supportedFactoryGroups(17, false)
+ , factories(101, false)
+ , advancedProperties(1009, true)
+ , hiddenClasses(101, true)
+ , showAdvancedProperties(true)
+ , factoriesLoaded(false)
+ {
+ services.setAutoDelete(true);
+ advancedProperties.insert("autoMask", (char*)1);
+ advancedProperties.insert("baseSize", (char*)1);
+ advancedProperties.insert("mouseTracking", (char*)1);
+ advancedProperties.insert("acceptDrops", (char*)1);
+ advancedProperties.insert("cursorPosition", (char*)1);
+ advancedProperties.insert("contextMenuEnabled", (char*)1);
+ advancedProperties.insert("trapEnterKeyEvent", (char*)1);
+ advancedProperties.insert("dragEnabled", (char*)1);
+ advancedProperties.insert("enableSqueezedText", (char*)1);
+ advancedProperties.insert("sizeIncrement", (char*)1);
+/*! @todo: reenable */ advancedProperties.insert("palette", (char*)1);
+ advancedProperties.insert("backgroundOrigin", (char*)1);
+ advancedProperties.insert("backgroundMode", (char*)1);//this is rather useless
+ advancedProperties.insert("layout", (char*)1);// too large risk to break things
+ // by providing this in propeditor
+ advancedProperties.insert("minimumSize", (char*)1);
+ advancedProperties.insert("maximumSize", (char*)1);
+#ifdef KEXI_NO_UNFINISHED
+/*! @todo reenable */
+ advancedProperties.insert("paletteBackgroundPixmap", (char*)1);
+ advancedProperties.insert("icon", (char*)1);
+ advancedProperties.insert("pixmap", (char*)1);
+ advancedProperties.insert("accel", (char*)1);
+#endif
+ }
+ // dict which associates a class name with a Widget class
+ WidgetInfo::Dict widgets;//, alternateWidgets;
+ QAsciiDict<KService::Ptr> services;
+ QAsciiDict<char> supportedFactoryGroups;
+ QAsciiDict<WidgetFactory> factories;
+ QAsciiDict<char> advancedProperties;
+ QAsciiDict<char> hiddenClasses;
+ bool showAdvancedProperties : 1;
+ bool factoriesLoaded : 1;
+};
+}
+
+using namespace KFormDesigner;
+
+//-------------------------------------------
+
+WidgetLibrary::WidgetLibrary(QObject *parent, const QStringList& supportedFactoryGroups)
+ : QObject(parent)
+ , d(new WidgetLibraryPrivate())
+{
+ for (QStringList::ConstIterator it = supportedFactoryGroups.constBegin();
+ it!=supportedFactoryGroups.constEnd(); ++it)
+ {
+ d->supportedFactoryGroups.insert( (*it).lower().latin1(), (char*)1);
+ }
+ lookupFactories();
+}
+
+WidgetLibrary::~WidgetLibrary()
+{
+ delete d;
+}
+
+void
+WidgetLibrary::loadFactoryWidgets(WidgetFactory *f)
+{
+ const WidgetInfo::Dict widgets = f->classes();
+ WidgetInfo *w;
+ for(QAsciiDictIterator<WidgetInfo> it(widgets); (w = it.current()); ++it)
+ {
+ if (0 != d->hiddenClasses[ w->className() ])
+ continue; //this class is hidden
+ // check if we want to inherit a widget from a different factory
+ if (!w->m_parentFactoryName.isEmpty() && !w->m_inheritedClassName.isEmpty()) {
+ WidgetFactory *parentFactory = d->factories[w->m_parentFactoryName];
+ if (!parentFactory) {
+ kdWarning() << "WidgetLibrary::loadFactoryWidgets(): class '" << w->className()
+ << "' - no such parent factory '" << w->m_parentFactoryName << "'" << endl;
+ continue;
+ }
+ WidgetInfo* inheritedClass = parentFactory->m_classesByName[ w->m_inheritedClassName ];
+ if (!inheritedClass) {
+ kdWarning() << "WidgetLibrary::loadFactoryWidgets(): class '" << w->m_inheritedClassName
+ << "' - no such class to inherit in factory '" << w->m_parentFactoryName << "'" << endl;
+ continue;
+ }
+ //ok: inherit properties:
+ w->m_inheritedClass = inheritedClass;
+ if (w->pixmap().isEmpty())
+ w->setPixmap( inheritedClass->pixmap() );
+ //ok?
+ foreach (QValueList<QCString>::ConstIterator, it_alt, inheritedClass->m_alternateNames) {
+ w->addAlternateClassName( *it_alt, inheritedClass->isOverriddenClassName( *it_alt ) );
+ }
+ if (w->includeFileName().isEmpty())
+ w->setIncludeFileName( inheritedClass->includeFileName() );
+ if (w->name().isEmpty())
+ w->setName( inheritedClass->name() );
+ if (w->namePrefix().isEmpty())
+ w->setNamePrefix( inheritedClass->namePrefix() );
+ if (w->description().isEmpty())
+ w->setDescription( inheritedClass->description() );
+ }
+
+// kdDebug() << "WidgetLibrary::addFactory(): adding class " << w->className() << endl;
+ QValueList<QCString> l = w->alternateClassNames();
+ l.prepend( w->className() );
+ //d->widgets.insert(w->className(), w);
+// if(!w->alternateClassName().isEmpty()) {
+// QStringList l = QStringList::split("|", w->alternateClassName());
+ QValueList<QCString>::ConstIterator endIt = l.constEnd();
+ for(QValueList<QCString>::ConstIterator it = l.constBegin(); it != endIt; ++it) {
+ WidgetInfo *widgetForClass = d->widgets.find( *it );
+ if (!widgetForClass || (widgetForClass && !widgetForClass->isOverriddenClassName(*it))) {
+ //insert a widgetinfo, if:
+ //1) this class has no alternate class assigned yet, or
+ //2) this class has alternate class assigned but without 'override' flag
+ d->widgets.replace( *it, w);
+ }
+
+/* WidgetInfo *widgetForClass = d->alternateWidgets.find(*it);
+ if (!widgetForClass || (widgetForClass && !widgetForClass->isOverriddenClassName(*it))) {
+ //insert a widgetinfo, if:
+ //1) this class has no alternate class assigned yet, or
+ //2) this class has alternate class assigned but without 'override' flag
+ d->alternateWidgets.replace(*it, w);
+ }*/
+ }
+ }
+}
+
+void
+WidgetLibrary::lookupFactories()
+{
+ KTrader::OfferList tlist = KTrader::self()->query("KFormDesigner/WidgetFactory");
+ KTrader::OfferList::ConstIterator it, end( tlist.constEnd() );
+ for( it = tlist.constBegin(); it != end; ++it)
+ {
+ KService::Ptr ptr = (*it);
+ KService::Ptr* existingService = (d->services)[ptr->library().latin1()];
+ if (existingService) {
+ kdWarning() << "WidgetLibrary::lookupFactories(): factory '" << ptr->name()
+ << "' already found (library="<< (*existingService)->library()
+ <<")! skipping this one: library=" << ptr->library() << endl;
+ continue;
+ }
+ kdDebug() << "WidgetLibrary::lookupFactories(): found factory: " << ptr->name() << endl;
+
+ QCString groupName = ptr->property("X-KFormDesigner-FactoryGroup").toCString();
+ if (!groupName.isEmpty() && !d->supportedFactoryGroups[groupName]) {
+ kdDebug() << "WidgetLibrary::lookupFactories(): factory group '" << groupName
+ << "' is unsupported by this application (library=" << ptr->library() << ")"<< endl;
+ continue;
+ }
+ const uint factoryVersion = ptr->property("X-KFormDesigner-WidgetFactoryVersion").toUInt();
+ if (KFormDesigner::version()!=factoryVersion) {
+ kdWarning() << QString("WidgetLibrary::lookupFactories(): factory '%1'"
+ " has version '%2' but required Widget Factory version is '%3'\n"
+ " -- skipping this factory!").arg(ptr->library()).arg(factoryVersion)
+ .arg(KFormDesigner::version()) << endl;
+ continue;
+ }
+ d->services.insert(ptr->library().latin1(), new KService::Ptr( ptr ));
+ }
+}
+
+void
+WidgetLibrary::loadFactories()
+{
+ if (d->factoriesLoaded)
+ return;
+ d->factoriesLoaded = true;
+ for (QAsciiDictIterator<KService::Ptr> it(d->services); it.current(); ++it) {
+ WidgetFactory *f = KParts::ComponentFactory::createInstanceFromService<WidgetFactory>(
+ *it.current(), this, (*it.current())->library().latin1(), QStringList());
+ if (!f) {
+ kdWarning() << "WidgetLibrary::loadFactories(): creating factory failed! "
+ << (*it.current())->library() << endl;
+ continue;
+ }
+ f->m_library = this;
+ f->m_showAdvancedProperties = d->showAdvancedProperties; //inherit this flag from the library
+ f->m_xmlGUIFileName = (*it.current())->property("X-KFormDesigner-XMLGUIFileName").toString();
+ d->factories.insert( f->name(), f );
+
+ //collect information about classes to be hidden
+ if (f->m_hiddenClasses) {
+ for (QAsciiDictIterator<char> it2(*f->m_hiddenClasses); it2.current(); ++it2) {
+ d->hiddenClasses.replace( it2.currentKey(), (char*)1 );
+ }
+ }
+ }
+
+ //now we have factories instantiated: load widgets
+ QPtrList<WidgetFactory> loadLater;
+ for (QAsciiDictIterator<WidgetFactory> it(d->factories); it.current(); ++it) {
+ //ONE LEVEL, FLAT INHERITANCE, but works!
+ //if this factory inherits from something, load its witgets later
+//! @todo improve
+ if (it.current()->inheritsFactories())
+ loadLater.append( it.current() );
+ else
+ loadFactoryWidgets(it.current());
+ }
+ //load now the rest
+ for (QPtrListIterator<WidgetFactory> it(loadLater); it.current(); ++it) {
+ loadFactoryWidgets(it.current());
+ }
+}
+
+/* old
+QString
+WidgetLibrary::createXML()
+{
+ loadFactories();
+
+ QDomDocument doc("kpartgui");
+ QDomElement root = doc.createElement("kpartgui");
+
+ root.setAttribute("name", "kformdesigner");
+ root.setAttribute("version", "0.3");
+ doc.appendChild(root);
+
+ QDomElement toolbar = doc.createElement("ToolBar");
+ toolbar.setAttribute("name", "widgets");
+ root.appendChild(toolbar);
+
+ QDomElement texttb = doc.createElement("text");
+ toolbar.appendChild(texttb);
+ QDomText ttext = doc.createTextNode("Widgets");
+ texttb.appendChild(ttext);
+
+ QDomElement menubar = doc.createElement("MenuBar");
+ toolbar.setAttribute("name", "widgets");
+ root.appendChild(menubar);
+
+ QDomElement Mtextb = doc.createElement("text");
+ toolbar.appendChild(Mtextb);
+ QDomText Mtext = doc.createTextNode("Widgets");
+ Mtextb.appendChild(Mtext);
+ QDomElement menu = doc.createElement("Menu");
+ menu.setAttribute("name", "widgets");
+
+ QAsciiDictIterator<WidgetInfo> it(d->widgets);
+ int i = 0;
+ for(; it.current(); ++it)
+ {
+ QDomElement action = doc.createElement("Action");
+ action.setAttribute("name", "library_widget" + it.current()->className());
+ toolbar.appendChild(action);
+
+ i++;
+ }
+
+ return doc.toString();
+}*/
+
+ActionList
+WidgetLibrary::createWidgetActions(KXMLGUIClient* client, KActionCollection *parent,
+ QObject *receiver, const char *slot)
+{
+ loadFactories();
+
+ // init XML gui clients (custom factories have their own .rc files)
+ for (QAsciiDictIterator<WidgetFactory> it(d->factories); it.current(); ++it)
+ {
+ if (it.current()->m_xmlGUIFileName.isEmpty()) { // probably a built-in factory, with GUI file like kexiformpartinstui.rc
+ it.current()->m_guiClient = 0;
+ }
+ else { // a custom factory with its own .rc file
+ it.current()->m_guiClient = new XMLGUIClient(client, it.current()->m_xmlGUIFileName);
+ }
+ }
+
+ ActionList actions;
+ for (QAsciiDictIterator<WidgetInfo> it(d->widgets); it.current(); ++it)
+ {
+ LibActionWidget *a = new LibActionWidget(it.current(),
+ it.current()->factory()->m_guiClient
+ ? it.current()->factory()->m_guiClient->actionCollection() : parent);
+ connect(a, SIGNAL(prepareInsert(const QCString &)), receiver, slot);
+ actions.append(a);
+ }
+ return actions;
+}
+
+void
+WidgetLibrary::addCustomWidgetActions(KActionCollection *col)
+{
+ for (QAsciiDictIterator<WidgetFactory> it(d->factories); it.current(); ++it)
+ {
+ it.current()->createCustomActions(
+ it.current()->m_guiClient
+ ? it.current()->m_guiClient->actionCollection() : col);
+ }
+}
+
+QWidget*
+WidgetLibrary::createWidget(const QCString &classname, QWidget *parent, const char *name, Container *c,
+ int options)
+{
+ loadFactories();
+ WidgetInfo *wclass = d->widgets[classname];
+ if(!wclass)
+ return 0;
+
+ QWidget *widget = wclass->factory()->createWidget(wclass->className(), parent, name, c, options);
+ if (!widget) {
+ //try to instantiate from inherited class
+ if (wclass->inheritedClass())
+ widget = wclass->inheritedClass()->factory()->createWidget(
+ wclass->className(), parent, name, c, options);
+ if (!widget)
+ return 0;
+ }
+ widget->setAcceptDrops(true);
+ emit widgetCreated(widget);
+ return widget;
+}
+
+bool
+WidgetLibrary::createMenuActions(const QCString &c, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *container)
+{
+ loadFactories();
+ WidgetInfo *wclass = d->widgets[c];
+ if(!wclass)
+ return false;
+
+ wclass->factory()->m_widget = w;
+ wclass->factory()->m_container = container;
+ if (wclass->factory()->createMenuActions(c, w, menu, container))
+ return true;
+ //try from inherited class
+ if (wclass->inheritedClass())
+ return wclass->inheritedClass()->factory()
+ ->createMenuActions(wclass->className(), w, menu, container);
+ return false;
+}
+
+bool
+WidgetLibrary::startEditing(const QCString &classname, QWidget *w, Container *container)
+{
+ loadFactories();
+ WidgetInfo *wclass = d->widgets[classname];
+ if(!wclass)
+ return false;
+
+ if (wclass->factory()->startEditing(classname, w, container))
+ return true;
+ //try from inherited class
+ if (wclass->inheritedClass())
+ return wclass->inheritedClass()->factory()->startEditing(wclass->className(), w, container);
+ return false;
+}
+
+bool
+WidgetLibrary::previewWidget(const QCString &classname, QWidget *widget, Container *container)
+{
+ loadFactories();
+ WidgetInfo *wclass = d->widgets[classname];
+ if(!wclass)
+ return false;
+
+ if (wclass->factory()->previewWidget(classname, widget, container))
+ return true;
+ //try from inherited class
+ if (wclass->inheritedClass())
+ return wclass->inheritedClass()->factory()->previewWidget(wclass->className(), widget, container);
+ return false;
+}
+
+bool
+WidgetLibrary::clearWidgetContent(const QCString &classname, QWidget *w)
+{
+ loadFactories();
+ WidgetInfo *wclass = d->widgets[classname];
+ if(!wclass)
+ return false;
+
+ if (wclass->factory()->clearWidgetContent(classname, w))
+ return true;
+ //try from inherited class
+ if (wclass->inheritedClass())
+ return wclass->inheritedClass()->factory()->clearWidgetContent(wclass->className(), w);
+ return false;
+}
+
+QString
+WidgetLibrary::displayName(const QCString &classname)
+{
+ loadFactories();
+ WidgetInfo *wi = d->widgets.find(classname);
+ if(wi)
+ return wi->name();
+
+ return classname;
+}
+
+QString
+WidgetLibrary::savingName(const QCString &classname)
+{
+ loadFactories();
+ QString s;
+ WidgetInfo *wi = d->widgets.find(classname);
+ if(wi && !wi->savingName().isEmpty())
+ return wi->savingName();
+
+ return classname;
+}
+
+QString
+WidgetLibrary::namePrefix(const QCString &classname)
+{
+ loadFactories();
+ WidgetInfo *wi = d->widgets.find(classname);
+ if(wi)
+ return wi->namePrefix();
+
+ return classname;
+}
+
+QString
+WidgetLibrary::textForWidgetName(const QCString &name, const QCString &className)
+{
+ loadFactories();
+ WidgetInfo *widget = d->widgets[className];
+ if(!widget)
+ return QString::null;
+
+ QString newName = name;
+ newName.remove(widget->namePrefix());
+ newName = widget->name() + " " + newName;
+ return newName;
+}
+
+QCString
+WidgetLibrary::classNameForAlternate(const QCString &classname)
+{
+ loadFactories();
+ if(d->widgets.find(classname))
+ return classname;
+
+ WidgetInfo *wi = d->widgets[classname];
+ if (wi) {
+ return wi->className();
+ }
+
+ // widget not supported
+ return "CustomWidget";
+}
+
+QString
+WidgetLibrary::includeFileName(const QCString &classname)
+{
+ loadFactories();
+ WidgetInfo *wi = d->widgets.find(classname);
+ if(wi)
+ return wi->includeFileName();
+
+ return QString::null;
+}
+
+QString
+WidgetLibrary::iconName(const QCString &classname)
+{
+ loadFactories();
+ WidgetInfo *wi = d->widgets.find(classname);
+ if(wi)
+ return wi->pixmap();
+
+ return QString::fromLatin1("unknown_widget");
+}
+
+bool
+WidgetLibrary::saveSpecialProperty(const QCString &classname, const QString &name, const QVariant &value, QWidget *w, QDomElement &parentNode, QDomDocument &parent)
+{
+ loadFactories();
+ WidgetInfo *wi = d->widgets.find(classname);
+ if (!wi)
+ return false;
+
+ if (wi->factory()->saveSpecialProperty(classname, name, value, w, parentNode, parent))
+ return true;
+ //try from inherited class
+ if (wi->inheritedClass())
+ return wi->inheritedClass()->factory()->saveSpecialProperty(wi->className(), name, value, w, parentNode, parent);
+ return false;
+}
+
+bool
+WidgetLibrary::readSpecialProperty(const QCString &classname, QDomElement &node, QWidget *w, ObjectTreeItem *item)
+{
+ loadFactories();
+ WidgetInfo *wi = d->widgets.find(classname);
+ if (!wi)
+ return false;
+ if (wi->factory()->readSpecialProperty(classname, node, w, item))
+ return true;
+ //try from inherited class
+ if (wi->inheritedClass())
+ return wi->inheritedClass()->factory()->readSpecialProperty(wi->className(), node, w, item);
+ return false;
+}
+
+void WidgetLibrary::setAdvancedPropertiesVisible(bool set)
+{
+ d->showAdvancedProperties = set;
+}
+
+bool WidgetLibrary::advancedPropertiesVisible() const
+{
+ return d->showAdvancedProperties;
+}
+
+bool
+WidgetLibrary::isPropertyVisible(const QCString &classname, QWidget *w,
+ const QCString &property, bool multiple, bool isTopLevel)
+{
+ if (isTopLevel) {
+ // no focus policy for top-level form widget...
+ if (!d->showAdvancedProperties && property == "focusPolicy")
+ return false;
+ }
+
+ loadFactories();
+ WidgetInfo *wi = d->widgets.find(classname);
+ if (!wi)
+ return false;
+ if (!d->showAdvancedProperties && d->advancedProperties[ property ]) {
+ //this is advanced property, should we hide it?
+ if (wi->factory()->internalProperty(classname, "forceShowAdvancedProperty:"+property).isEmpty()
+ && (!wi->inheritedClass() || wi->inheritedClass()->factory()->internalProperty(classname, "forceShowAdvancedProperty:"+property).isEmpty()))
+ {
+ return false; //hide it
+ }
+ }
+
+ if (!wi->factory()->isPropertyVisible(classname, w, property, multiple, isTopLevel))
+ return false;
+ //try from inherited class
+ if (wi->inheritedClass()
+ && !wi->inheritedClass()->factory()->isPropertyVisible(wi->className(), w, property, multiple, isTopLevel))
+ return false;
+
+ return true;
+}
+
+QValueList<QCString>
+WidgetLibrary::autoSaveProperties(const QCString &classname)
+{
+ loadFactories();
+ WidgetInfo *wi = d->widgets.find(classname);
+ if(!wi)
+ return QValueList<QCString>();
+ QValueList<QCString> lst;
+ //prepend from inherited class
+ if (wi->inheritedClass())
+ lst = wi->inheritedClass()->factory()->autoSaveProperties(wi->className());
+ lst += wi->factory()->autoSaveProperties(classname);
+ return lst;
+}
+
+WidgetInfo*
+WidgetLibrary::widgetInfoForClassName(const char* classname)
+{
+ loadFactories();
+ return d->widgets.find(classname);
+}
+
+WidgetFactory*
+WidgetLibrary::factoryForClassName(const char* classname)
+{
+ WidgetInfo *wi = widgetInfoForClassName(classname);
+ return wi ? wi->factory() : 0;
+}
+
+QString WidgetLibrary::propertyDescForName(WidgetInfo *winfo, const QCString& propertyName)
+{
+ if (!winfo || !winfo->factory())
+ return QString::null;
+ QString desc( winfo->factory()->propertyDescForName(propertyName) );
+ if (!desc.isEmpty())
+ return desc;
+ if (winfo->m_parentFactoryName.isEmpty())
+ return QString::null;
+
+ //try in parent factory, if exists
+ WidgetFactory *parentFactory = d->factories[winfo->m_parentFactoryName];
+ if (!parentFactory)
+ return QString::null;
+
+ return parentFactory->propertyDescForName(propertyName);
+}
+
+QString WidgetLibrary::propertyDescForValue(WidgetInfo *winfo, const QCString& name)
+{
+ if (!winfo->factory())
+ return QString::null;
+ QString desc( winfo->factory()->propertyDescForValue(name) );
+ if (!desc.isEmpty())
+ return desc;
+ if (winfo->m_parentFactoryName.isEmpty())
+ return QString::null;
+
+ //try in parent factory, if exists
+ WidgetFactory *parentFactory = d->factories[winfo->m_parentFactoryName];
+ if (!parentFactory)
+ return QString::null;
+
+ return parentFactory->propertyDescForValue(name);
+}
+
+void WidgetLibrary::setPropertyOptions( WidgetPropertySet& buf, const WidgetInfo& winfo, QWidget* w )
+{
+ if (!winfo.factory())
+ return;
+ winfo.factory()->setPropertyOptions(buf, winfo, w);
+ if (winfo.m_parentFactoryName.isEmpty())
+ return;
+ WidgetFactory *parentFactory = d->factories[winfo.m_parentFactoryName];
+ if (!parentFactory)
+ return;
+ parentFactory->setPropertyOptions(buf, winfo, w);
+}
+
+WidgetFactory* WidgetLibrary::factory(const char* factoryName) const
+{
+ return d->factories[factoryName];
+}
+
+QString WidgetLibrary::internalProperty(const QCString& classname, const QCString& property)
+{
+ loadFactories();
+ WidgetInfo *wclass = d->widgets[classname];
+ if(!wclass)
+ return QString::null;
+ QString value( wclass->factory()->internalProperty(classname, property) );
+ if (value.isEmpty() && wclass->inheritedClass())
+ return wclass->inheritedClass()->factory()->internalProperty(classname, property);
+ return value;
+}
+
+WidgetFactory::CreateWidgetOptions WidgetLibrary::showOrientationSelectionPopup(
+ const QCString &classname, QWidget* parent, const QPoint& pos)
+{
+ loadFactories();
+ WidgetInfo *wclass = d->widgets[classname];
+ if(!wclass)
+ return WidgetFactory::AnyOrientation;
+
+ //get custom icons and strings
+ QPixmap iconHorizontal, iconVertical;
+ QString iconName( wclass->factory()->internalProperty(classname, "orientationSelectionPopup:horizontalIcon") );
+ if (iconName.isEmpty() && wclass->inheritedClass())
+ iconName = wclass->inheritedClass()->factory()->internalProperty(classname, "orientationSelectionPopup:horizontalIcon");
+ if (!iconName.isEmpty())
+ iconHorizontal = SmallIcon(iconName);
+
+ iconName = wclass->factory()->internalProperty(classname, "orientationSelectionPopup:verticalIcon");
+ if (iconName.isEmpty() && wclass->inheritedClass())
+ iconName = wclass->inheritedClass()->factory()->internalProperty(classname, "orientationSelectionPopup:verticalIcon");
+ if (!iconName.isEmpty())
+ iconVertical = SmallIcon(iconName);
+
+ QString textHorizontal = wclass->factory()->internalProperty(classname, "orientationSelectionPopup:horizontalText");
+ if (textHorizontal.isEmpty() && wclass->inheritedClass())
+ iconName = wclass->inheritedClass()->factory()->internalProperty(classname, "orientationSelectionPopup:horizontalText");
+ if (textHorizontal.isEmpty()) //default
+ textHorizontal = i18n("Insert Horizontal Widget", "Insert Horizontal");
+
+ QString textVertical = wclass->factory()->internalProperty(classname, "orientationSelectionPopup:verticalText");
+ if (textVertical.isEmpty() && wclass->inheritedClass())
+ iconName = wclass->inheritedClass()->factory()->internalProperty(classname, "orientationSelectionPopup:verticalText");
+ if (textVertical.isEmpty()) //default
+ textVertical = i18n("Insert Vertical Widget", "Insert Vertical");
+
+ KPopupMenu* popup = new KPopupMenu(parent, "orientationSelectionPopup");
+ popup->insertTitle(SmallIcon(wclass->pixmap()), i18n("Insert Widget: %1").arg(wclass->name()));
+ popup->insertItem(iconHorizontal, textHorizontal, 1);
+ popup->insertItem(iconVertical, textVertical, 2);
+ popup->insertSeparator();
+ popup->insertItem(SmallIcon("button_cancel"), i18n("Cancel"), 3);
+ WidgetFactory::CreateWidgetOptions result;
+ switch (popup->exec(pos)) {
+ case 1:
+ result = WidgetFactory::HorizontalOrientation; break;
+ case 2:
+ result = WidgetFactory::VerticalOrientation; break;
+ default:
+ result = WidgetFactory::AnyOrientation; //means "cancelled"
+ }
+ delete popup;
+ return result;
+}
+
+bool WidgetLibrary::propertySetShouldBeReloadedAfterPropertyChange(
+ const QCString& classname, QWidget *w, const QCString& property)
+{
+ WidgetInfo *winfo = widgetInfoForClassName(classname);
+ if (!winfo)
+ return false;
+ return winfo->factory()->propertySetShouldBeReloadedAfterPropertyChange(classname, w, property);
+}
+
+#include "widgetlibrary.moc"
diff --git a/kexi/formeditor/widgetlibrary.h b/kexi/formeditor/widgetlibrary.h
new file mode 100644
index 000000000..f4f8c1f3f
--- /dev/null
+++ b/kexi/formeditor/widgetlibrary.h
@@ -0,0 +1,212 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNERWIDGETLIBRARY_H
+#define KFORMDESIGNERWIDGETLIBRARY_H
+
+#include <qobject.h>
+#include <qmap.h>
+#include <qdict.h>
+
+#include "widgetfactory.h"
+
+template<class type> class QPtrList;
+template<class type> class QValueVector;
+class KActionCollection;
+class KAction;
+class QWidget;
+class QPopupMenu;
+class QVariant;
+class QDomDocument;
+class QDomElement;
+
+namespace KFormDesigner {
+
+class Container;
+class ObjectTreeItem;
+class WidgetLibraryPrivate;
+class WidgetPropertySet;
+
+typedef QPtrList<KAction> ActionList;
+
+/**
+ * This class searches for factories and provides KActions for widget creation.
+ * Every widget can be located using this library.
+ * You call WidgetLibrary functions instead of calling directly factories.
+ * See WidgetFactory for a description of the functions.
+ */
+class KFORMEDITOR_EXPORT WidgetLibrary : public QObject
+{
+ Q_OBJECT
+
+ public:
+ /*! Constructs WidgetLibrary object.
+ In \a supportedFactoryGroups you can provide
+ factory group list to be supported. Factory groups are defined by
+ "X-KFormDesigner-FactoryGroup" field in every factory serviece's .desktop file.
+ By default (when supportedFactoryGroups is empty) only factories having empty
+ "X-KFormDesigner-FactoryGroup" field will be loaded.
+ Factory group names are case-insensitive. */
+ WidgetLibrary(QObject *parent=0, const QStringList& supportedFactoryGroups = QStringList());
+
+ virtual ~WidgetLibrary();
+
+ /**
+ * creates actions for widget creating
+ */
+ ActionList createWidgetActions(KXMLGUIClient* client, KActionCollection *parent,
+ QObject *receiver, const char *slot);
+
+ void addCustomWidgetActions(KActionCollection *col);
+
+//old /**
+//old * creates the XML for widget actions
+//old */
+//old QString createXML();
+
+ /**
+ * searches the right factory and creates a widget.
+ * \return the widget or 0 if something falid
+ */
+ QWidget *createWidget(const QCString &classname, QWidget *parent, const char *name, Container *c,
+ int options = WidgetFactory::DefaultOptions);
+
+ bool createMenuActions(const QCString &c, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *container);
+
+ /**
+ * Shows orientation selection popup.
+ * \return one of the following values:
+ * - WidgetFactory::AnyOrientation (means no selection has been made, i.e. it was cancelled)
+ * - WidgetFactory::HorizontalOrientation
+ * - WidgetFactory::VerticalOrientation
+ */
+ WidgetFactory::CreateWidgetOptions showOrientationSelectionPopup(
+ const QCString &classname, QWidget* parent, const QPoint& pos);
+
+ QString internalProperty(const QCString& classname, const QCString& property);
+
+ QString displayName(const QCString &classname);
+ QString namePrefix(const QCString &classname);
+ QString textForWidgetName(const QCString &name, const QCString &className);
+
+ /*! Checks if the \a classname is an alternate classname,
+ and returns the good classname.
+ If \a classname is not alternate, \a classname is returned. */
+ QCString classNameForAlternate(const QCString &classname);
+ QString iconName(const QCString &classname);
+ QString includeFileName(const QCString &classname);
+ QString savingName(const QCString &classname);
+
+ bool startEditing(const QCString &classname, QWidget *w, Container *container);
+ bool previewWidget(const QCString &classname, QWidget *widget, Container *container);
+ bool clearWidgetContent(const QCString &classname, QWidget *w);
+
+ bool saveSpecialProperty(const QCString &classname, const QString &name,
+ const QVariant &value, QWidget *w, QDomElement &parentNode, QDomDocument &parent);
+ bool readSpecialProperty(const QCString &classname, QDomElement &node, QWidget *w,
+ ObjectTreeItem *item);
+ bool isPropertyVisible(const QCString &classname, QWidget *w,
+ const QCString &property, bool multiple = false, bool isTopLevel = false);
+
+ QValueList<QCString> autoSaveProperties(const QCString &classname);
+
+ WidgetInfo* widgetInfoForClassName(const char* classname);
+
+ WidgetFactory* factoryForClassName(const char* className);
+
+ WidgetFactory* factory(const char* factoryName) const;
+
+ /*! \return true if advanced properties like "mouseTracking" should
+ be user-visible. True by default (in KFD), but Kexi set's this to false.
+ See WidgetLibraryPrivate class implementation for complete list
+ of advanced properties. */
+ bool advancedPropertiesVisible() const;
+
+ /*! Sets advanced properties to be visible or not. */
+ void setAdvancedPropertiesVisible(bool set);
+
+ /*! \return The i18n'ed name of the property \a propertyName
+ for a class described by \a winfo. The name can be displayed in
+ PropertyEditor. The name is retrieved from class' widget library.
+ If this library doesn't define description for such property,
+ and there is a parent library for \a winfo defined, parent library
+ is asked for returning description string.
+ Eventually, if even this failed, empty string is returned.
+ @see WidgetFactory::propertyDescForName() */
+ QString propertyDescForName(WidgetInfo *winfo, const QCString& propertyName);
+
+ /*! \return The i18n'ed name of the property's value whose name is \a name.
+ Works in the same way as propertyDescForName(): if actual library
+ does not define a description we are looking for, parent factory is asked
+ to return such description.
+ Eventually, if even this failed, empty string is returned.
+ @see WidgetFactory::propertyDescForValue() */
+ QString propertyDescForValue(WidgetInfo *winfo, const QCString& name);
+
+ /*! Used by WidgetPropertySet::setWidget() after creating properties. */
+ void setPropertyOptions( WidgetPropertySet &list, const WidgetInfo& winfo, QWidget* w );
+
+ /*! \return true if property sets should be reloaded for \a property property,
+ \a classname class and widget \a w when a given property value changed. */
+ bool propertySetShouldBeReloadedAfterPropertyChange(const QCString& classname, QWidget *w,
+ const QCString& property);
+
+ signals:
+ void prepareInsert(const QCString &c);
+
+ //! Received by KexiFormPart::slotWidgetCreatedByFormsLibrary() so we can add drag/drop
+ //! connection for the new widget
+ void widgetCreated(QWidget *widget);
+
+ protected:
+ /**
+ * Adds a factory to the library, creates actions for widgets in the added factory.
+ * This function is not called directly but by the factory locater.
+ */
+ void loadFactoryWidgets(WidgetFactory *f);
+
+#if 0 //UNIMPLEMENTED
+ /**
+ * you can restrict the loaded factories by setting the filter to a pattern
+ * like 'kexi|containers' in that case only factory containing 'kexi' or containers will be loaded.
+ * this is useful if you want to embedd formeditor and provide e.g. a LineEdit with special features
+ * but don't want to confuse the user... are you confused now?
+ * NB: not implemented yet
+ */
+ void setFilter(const QRegExp &expr);
+#endif
+
+ /**
+ * Lookups widget factories list (note that this function get called once in ctor)
+ */
+ void lookupFactories();
+
+ /**
+ * Loads widget factories found in lookupFactories(). This is called once.
+ */
+ void loadFactories();
+
+ WidgetLibraryPrivate *d;
+};
+
+}
+#endif
diff --git a/kexi/formeditor/widgetpropertyset.cpp b/kexi/formeditor/widgetpropertyset.cpp
new file mode 100644
index 000000000..497fb5e64
--- /dev/null
+++ b/kexi/formeditor/widgetpropertyset.cpp
@@ -0,0 +1,1120 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include "widgetpropertyset.h"
+
+#include <qstringlist.h>
+#include <qstrlist.h>
+#include <qmetaobject.h>
+#include <qvariant.h>
+#include <qevent.h>
+#include <qlayout.h>
+#include <qapplication.h>
+#include <qeventloop.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+
+#include "objecttree.h"
+#include "form.h"
+#include "container.h"
+#include "formmanager.h"
+#include "widgetlibrary.h"
+#include "commands.h"
+#include "widgetwithsubpropertiesinterface.h"
+
+#include <kexiutils/utils.h>
+#include <kexiutils/identifier.h>
+
+using namespace KFormDesigner;
+
+namespace KFormDesigner {
+
+//! @internal
+typedef QValueList< QGuardedPtr<QWidget> > QGuardedWidgetList;
+
+//! @internal
+class WidgetPropertySetPrivate
+{
+ public:
+ WidgetPropertySetPrivate()
+ : lastCommand(0), lastGeoCommand(0),
+ isUndoing(false), slotPropertyChangedEnabled(true),
+ slotPropertyChanged_addCommandEnabled(true),
+ origActiveColors(0)
+ {}
+ ~WidgetPropertySetPrivate()
+ {
+ delete origActiveColors;
+ }
+
+ KoProperty::Set set;
+ // list of properties (not) to show in editor
+ QStringList properties;
+ // list of widgets
+ QGuardedWidgetList widgets;
+// FormManager *manager;
+
+ // used to update command's value when undoing
+ PropertyCommand *lastCommand;
+ GeometryPropertyCommand *lastGeoCommand;
+ bool isUndoing : 1;
+ bool slotPropertyChangedEnabled : 1;
+ bool slotPropertyChanged_addCommandEnabled : 1;
+
+ // helper to change color palette when switching 'enabled' property
+ QColorGroup* origActiveColors;
+
+ // i18n stuff
+ QMap<QCString, QString> propCaption;
+ QMap<QCString, QString> propValCaption;
+};
+}
+
+WidgetPropertySet::WidgetPropertySet(QObject *parent)
+ : QObject(parent, "kfd_widgetPropertySet")
+{
+ d = new WidgetPropertySetPrivate();
+// d->manager = manager;
+
+ connect(&d->set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)),
+ this, SLOT(slotPropertyChanged(KoProperty::Set&, KoProperty::Property&)));
+ connect(&d->set, SIGNAL(propertyReset(KoProperty::Set&, KoProperty::Property&)),
+ this, SLOT(slotPropertyReset(KoProperty::Set&, KoProperty::Property&)));
+
+ initPropertiesDescription();
+}
+
+WidgetPropertySet::~WidgetPropertySet()
+{
+ delete d;
+}
+
+/*FormManager*
+WidgetPropertySet::manager()
+{
+ return d->manager;
+}*/
+
+KoProperty::Property&
+WidgetPropertySet::operator[](const QCString &name)
+{
+ return d->set[name];
+}
+
+KoProperty::Property&
+WidgetPropertySet::property(const QCString &name)
+{
+ return d->set[name];
+}
+
+bool
+WidgetPropertySet::contains(const QCString &property)
+{
+ return d->set.contains(property);
+}
+
+KoProperty::Set*
+WidgetPropertySet::set()
+{
+ return &(d->set);
+}
+
+void
+WidgetPropertySet::clearSet(bool dontSignalShowPropertySet)
+{
+ saveModifiedProperties();
+
+ if (!dontSignalShowPropertySet)
+ KFormDesigner::FormManager::self()->showPropertySet(0);
+ d->widgets.clear();
+ d->lastCommand = 0;
+ d->lastGeoCommand = 0;
+ d->properties.clear();
+ d->set.clear();
+
+ if(!d->widgets.isEmpty()) {
+ d->widgets.first()->removeEventFilter(this);
+ disconnect(d->widgets.first(), 0, this, 0);
+ }
+}
+
+void
+WidgetPropertySet::saveModifiedProperties()
+{
+ QWidget * w = d->widgets.first();
+ if(!w || d->widgets.count() > 1 || !KFormDesigner::FormManager::self()->activeForm() || !KFormDesigner::FormManager::self()->activeForm()->objectTree())
+ return;
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(w->name());
+ if(!tree)
+ return;
+
+ for(KoProperty::Set::Iterator it(d->set); it.current(); ++it) {
+ if(it.current()->isModified())
+ tree->addModifiedProperty(it.current()->name(), it.current()->oldValue());
+ }
+}
+
+void
+WidgetPropertySet::setUndoing(bool isUndoing)
+{
+ d->isUndoing = isUndoing;
+}
+
+bool
+WidgetPropertySet::isUndoing()
+{
+ return d->isUndoing;
+}
+
+/////////////// Functions related to adding widgets /////////////////////////////////////
+
+void
+WidgetPropertySet::setSelectedWidget(QWidget *w, bool add, bool forceReload, bool moreWillBeSelected)
+{
+ if(!w) {
+ clearSet();
+ return;
+ }
+
+ // don't add a widget twice
+ if(!forceReload && d->widgets.contains(QGuardedPtr<QWidget>(w))) {
+ kdWarning() << "WidgetPropertySet::setSelectedWidget() Widget is already selected" << endl;
+ return;
+ }
+ // if our list is empty,don't use add parameter value
+ if(d->widgets.count() == 0)
+ add = false;
+
+ QCString prevProperty;
+ if(add)
+ addWidget(w);
+ else {
+ if (forceReload) {
+ KFormDesigner::FormManager::self()->showPropertySet(0, true/*force*/);
+ prevProperty = d->set.prevSelection();
+ }
+ clearSet(true); //clear but do not reload to avoid blinking
+ d->widgets.append(QGuardedPtr<QWidget>(w));
+ createPropertiesForWidget(w);
+
+ w->installEventFilter(this);
+ connect(w, SIGNAL(destroyed()), this, SLOT(slotWidgetDestroyed()));
+ }
+
+ if (!moreWillBeSelected)
+ KFormDesigner::FormManager::self()->showPropertySet(this, true/*force*/, prevProperty);
+}
+
+void
+WidgetPropertySet::addWidget(QWidget *w)
+{
+ d->widgets.append(QGuardedPtr<QWidget>(w));
+
+ // Reset some stuff
+ d->lastCommand = 0;
+ d->lastGeoCommand = 0;
+ d->properties.clear();
+
+ QCString classname;
+ if(d->widgets.first()->className() == w->className())
+ classname = d->widgets.first()->className();
+
+ // show only properties shared by widget (properties chosen by factory)
+ bool isTopLevel = KFormDesigner::FormManager::self()->isTopLevel(w);
+
+ //WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(w);
+// QWidget *subwidget = isSubproperty ? subpropIface->subwidget() : w;
+
+ for(KoProperty::Set::Iterator it(d->set); it.current(); ++it) {
+ kdDebug() << it.currentKey() << endl;
+ if(!isPropertyVisible(it.currentKey(), isTopLevel, classname))
+ d->set[it.currentKey()].setVisible(false);
+ }
+
+ if (d->widgets.count()>=2) {
+ //second widget, update metainfo
+ d->set["this:className"].setValue("special:multiple");
+ d->set["this:classString"].setValue(
+ i18n("Multiple Widgets") + QString(" (%1)").arg(d->widgets.count()) );
+ d->set["this:iconName"].setValue("multiple_obj");
+ //name doesn't make sense for now
+ d->set["name"].setValue("");
+ }
+}
+
+void
+WidgetPropertySet::createPropertiesForWidget(QWidget *w)
+{
+ Form *form;
+ if (!KFormDesigner::FormManager::self()
+ || !(form = KFormDesigner::FormManager::self()->activeForm())
+ || !KFormDesigner::FormManager::self()->activeForm()->objectTree())
+ {
+ kdWarning() << "WidgetPropertySet::createPropertiesForWidget() no manager or active form!!!" << endl;
+ return;
+ }
+ ObjectTreeItem *tree = form->objectTree()->lookup(w->name());
+ if(!tree)
+ return;
+
+ const QVariantMap* modifiedProperties = tree->modifiedProperties();
+ QVariantMapConstIterator modifiedPropertiesIt;
+ bool isTopLevel = KFormDesigner::FormManager::self()->isTopLevel(w);
+// int count = 0;
+ KoProperty::Property *newProp = 0;
+ WidgetInfo *winfo = form->library()->widgetInfoForClassName(w->className());
+ if (!winfo) {
+ kdWarning() << "WidgetPropertySet::createPropertiesForWidget() no widget info for class "
+ << w->className() << endl;
+ return;
+ }
+
+ QStrList pList = w->metaObject()->propertyNames(true);
+ QStrListIterator it(pList);
+
+ // add subproperties if available
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(w);
+ QStrList tmpList; //used to allocate copy of names
+ if (subpropIface) {
+ QValueList<QCString> subproperies(
+ subpropIface->subproperies() );
+ foreach(QValueListConstIterator<QCString>, it, subproperies ) {
+ tmpList.append( *it );
+ pList.append( tmpList.last() );
+ kdDebug() << "Added subproperty: " << *it << endl;
+ }
+ }
+
+ // iterate over the property list, and create Property objects
+ for(; it.current() != 0; ++it) {
+ //kdDebug() << ">> " << it.current() << endl;
+ const QMetaProperty *subMeta = // special case - subproperty
+ subpropIface ? subpropIface->findMetaSubproperty(it.current()) : 0;
+ const QMetaProperty *meta = subMeta ? subMeta
+ : w->metaObject()->property( w->metaObject()->findProperty(*it, true), true);
+ if (!meta)
+ continue;
+ const char* propertyName = meta->name();
+ QWidget *subwidget = subMeta/*subpropIface*/ ? subpropIface->subwidget() : w;
+ WidgetInfo *subwinfo = form->library()->widgetInfoForClassName(subwidget->className());
+// kdDebug() << "$$$ " << subwidget->className() << endl;
+
+ if(subwinfo && meta->designable(subwidget) && !d->set.contains(propertyName)) {
+ //! \todo add another list for property description
+ QString desc( d->propCaption[meta->name()] );
+ //! \todo change i18n
+ if (desc.isEmpty()) //try to get property description from factory
+ desc = form->library()->propertyDescForName(subwinfo, propertyName);
+
+ modifiedPropertiesIt = modifiedProperties->find(propertyName);
+ const bool oldValueExists = modifiedPropertiesIt!=modifiedProperties->constEnd();
+
+ if(meta->isEnumType()) {
+ if(qstrcmp(propertyName, "alignment") == 0) {
+ createAlignProperty(meta, w, subwidget);
+ continue;
+ }
+
+ QStringList keys = QStringList::fromStrList( meta->enumKeys() );
+ newProp = new KoProperty::Property(propertyName, createValueList(subwinfo, keys),
+ /* assign current or older value */
+ meta->valueToKey(
+ oldValueExists ? modifiedPropertiesIt.data().toInt() : subwidget->property(propertyName).toInt() ),
+ desc, desc );
+ //now set current value, so the old one is stored as old
+ if (oldValueExists) {
+ newProp->setValue( meta->valueToKey( subwidget->property(propertyName).toInt() ) );
+ }
+ }
+ else {
+ newProp = new KoProperty::Property(propertyName,
+ /* assign current or older value */
+ oldValueExists ? modifiedPropertiesIt.data() : subwidget->property(propertyName),
+ desc, desc, subwinfo->customTypeForProperty(propertyName));
+ //now set current value, so the old one is stored as old
+ if (oldValueExists) {
+ newProp->setValue( subwidget->property(propertyName) );
+ }
+ }
+
+ d->set.addProperty(newProp);
+ if(!isPropertyVisible(propertyName, isTopLevel))
+ newProp->setVisible(false);
+ //! TMP
+ if(newProp->type() == 0) // invalid type == null pixmap ?
+ newProp->setType(KoProperty::Pixmap);
+ }
+
+// if(0==qstrcmp(propertyName, "name"))
+// (*this)["name"].setAutoSync(0); // name should be updated only when pressing Enter
+
+ // \todo js what does this mean? why do you use WidgetInfo and not WidgetLibrary
+ /*if (winfo) {
+ tristate autoSync = winfo->autoSyncForProperty( propertyName );
+ if (! ~autoSync)
+ d->set[propertyName].setAutoSync( autoSync );
+ }*/
+
+ // update the Property.oldValue() and isModified() using the value stored in the ObjectTreeItem
+ updatePropertyValue(tree, propertyName, meta);
+ }
+
+ (*this)["name"].setAutoSync(false); // name should be updated only when pressing Enter
+ (*this)["enabled"].setValue( QVariant(tree->isEnabled(), 3));
+
+ if (winfo) {
+ form->library()->setPropertyOptions(*this, *winfo, w);
+ d->set.addProperty( newProp = new KoProperty::Property("this:classString", winfo->name()) );
+ newProp->setVisible(false);
+ d->set.addProperty( newProp = new KoProperty::Property("this:iconName", winfo->pixmap()) );
+ newProp->setVisible(false);
+ }
+ d->set.addProperty( newProp = new KoProperty::Property("this:className", w->className()) );
+ newProp->setVisible(false);
+
+ /*! let's forget it for now, until we have new complete events editor
+ if (m_manager->lib()->advancedPropertiesVisible()) {
+ // add the signals property
+ QStrList strlist = w->metaObject()->signalNames(true);
+ QStrListIterator strIt(strlist);
+ QStringList list;
+ for(; strIt.current() != 0; ++strIt)
+ list.append(*strIt);
+ Property *prop = new Property("signals", i18n("Events")"",
+ new KexiProperty::ListData(list, descList(winfo, list)),
+ ));
+ }*/
+
+ if(KFormDesigner::FormManager::self()->activeForm() && tree->container()) // we are a container -> layout property
+ createLayoutProperty(tree);
+}
+
+void
+WidgetPropertySet::updatePropertyValue(ObjectTreeItem *tree, const char *property, const QMetaProperty *meta)
+{
+ const char *propertyName = meta ? meta->name() : property;
+ if (!d->set.contains(propertyName))
+ return;
+ KoProperty::Property p( d->set[propertyName] );
+
+//! \todo what about set properties, and lists properties
+ QMap<QString, QVariant>::ConstIterator it( tree->modifiedProperties()->find(propertyName) );
+ if (it != tree->modifiedProperties()->constEnd()) {
+ blockSignals(true);
+ if(meta && meta->isEnumType()) {
+ p.setValue( meta->valueToKey( it.data().toInt() ), false );
+ }
+ else {
+ p.setValue(it.data(), false );
+ }
+ p.setValue(p.value(), true);
+ blockSignals(false);
+ }
+}
+
+bool
+WidgetPropertySet::isPropertyVisible(const QCString &property, bool isTopLevel, const QCString &classname)
+{
+ const bool multiple = d->widgets.count() >= 2;
+ if(multiple && classname.isEmpty())
+ return false;
+/* moved to WidgetLibrary::isPropertyVisible()
+ if(d->widgets.count() < 2)
+ {
+ if(d->properties.isEmpty() && !isTopLevel)
+ d->properties << "caption" << "icon" << "sizeIncrement" << "iconText";
+ // don't show these properties for a non-toplevel widget
+
+ if(! (d->properties.grep(property)).isEmpty() )
+ return false;
+ }
+ else
+ {
+ if(classname.isEmpty())
+ return false;
+
+ if(d->properties.isEmpty()) {
+ d->properties << "font" << "paletteBackgroundColor" << "enabled" << "paletteForegroundColor"
+ << "cursor" << "paletteBackgroundPixmap";
+ } // properties always shown in multiple mode
+ if(! (d->properties.grep(property)).isEmpty() )
+ return true;
+ }
+*/
+
+// return KFormDesigner::FormManager::self()->lib()->isPropertyVisible(d->widgets.first()->className(), d->widgets.first(),
+ QWidget *w = d->widgets.first();
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(w);
+ QWidget *subwidget;
+ if (subpropIface && subpropIface->findMetaSubproperty(property)) // special case - subproperty
+ subwidget = subpropIface->subwidget();
+ else
+ subwidget = w;
+
+ return KFormDesigner::FormManager::self()->activeForm()->library()->isPropertyVisible(
+ subwidget->className(), subwidget, property, multiple, isTopLevel);
+}
+
+//////////////// Slots called when properties are modified ///////////////
+
+void
+WidgetPropertySet::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& p)
+{
+ Q_UNUSED( set );
+
+ if(!d->slotPropertyChangedEnabled || !KFormDesigner::FormManager::self() || !KFormDesigner::FormManager::self()->activeForm()
+ || ! KFormDesigner::FormManager::self()->activeForm()->objectTree())
+ return;
+
+ QCString property = p.name();
+ if (0==property.find("this:"))
+ return; //starts with magical prefix: it's a "meta" prop.
+
+ QVariant value = p.value();
+
+ // check if the name is valid (ie is correct identifier) and there is no name conflict
+ if(property == "name") {
+ if(d->widgets.count()!=1)
+ return;
+ if(!isNameValid(value.toString()))
+ return;
+ }
+ // a widget with a background pixmap should have its own origin
+ else if(property == "paletteBackgroundPixmap") {
+ d->set["backgroundOrigin"] = "WidgetOrigin";
+ //else if(property == "signals")
+ // return;
+ // special types of properties handled separately
+ } else if((property == "hAlign") || (property == "vAlign") || (property == "wordbreak")) {
+ saveAlignProperty(property);
+ return;
+ }
+ else if((property == "layout") || (property == "layoutMargin") || (property == "layoutSpacing")) {
+ saveLayoutProperty(property, value);
+ return;
+ }
+ // we cannot really disable the widget, we just change its color palette
+ else if(property == "enabled") {
+ saveEnabledProperty(value.toBool());
+ return;
+ }
+
+ // make sure we are not already undoing -> avoid recursion
+ if(d->isUndoing && !KFormDesigner::FormManager::self()->isRedoing())
+ return;
+
+ const bool alterLastCommand = d->lastCommand && d->lastCommand->property() == property;
+
+ if(d->widgets.count() == 1) // one widget selected
+ {
+ // If the last command is the same, we just change its value
+ if(alterLastCommand && !KFormDesigner::FormManager::self()->isRedoing())
+ d->lastCommand->setValue(value);
+ else {
+// if(m_widgets.first() && ((m_widgets.first() != m_manager->activeForm()->widget()) || (property != "geometry"))) {
+ if (d->slotPropertyChanged_addCommandEnabled && !KFormDesigner::FormManager::self()->isRedoing()) {
+ d->lastCommand = new PropertyCommand(this, d->widgets.first()->name(),
+ d->widgets.first()->property(property), value, property);
+ KFormDesigner::FormManager::self()->activeForm()->addCommand(d->lastCommand, false);
+ }
+
+ // If the property is changed, we add it in ObjectTreeItem modifProp
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(d->widgets.first()->name());
+ if (tree && p.isModified())
+ tree->addModifiedProperty(property, d->widgets.first()->property(property));
+ }
+
+ if(property == "name")
+ emit widgetNameChanged(d->widgets.first()->name(), p.value().toCString());
+ d->widgets.first()->setProperty(property, value);
+ emitWidgetPropertyChanged(d->widgets.first(), property, value);
+ }
+ else
+ {
+ if(alterLastCommand && !KFormDesigner::FormManager::self()->isRedoing())
+ d->lastCommand->setValue(value);
+ else {
+ if (d->slotPropertyChanged_addCommandEnabled && !KFormDesigner::FormManager::self()->isRedoing()) {
+ // We store old values for each widget
+ QMap<QCString, QVariant> list;
+ // for(QWidget *w = d->widgets.first(); w; w = d->widgets.next())
+ foreach(QGuardedWidgetList::ConstIterator, it, d->widgets)
+ list.insert((*it)->name(), (*it)->property(property));
+
+ d->lastCommand = new PropertyCommand(this, list, value, property);
+ KFormDesigner::FormManager::self()->activeForm()->addCommand(d->lastCommand, false);
+ }
+ }
+
+// for(QWidget *w = d->widgets.first(); w; w = d->widgets.next())
+ foreach(QGuardedWidgetList::ConstIterator, it, d->widgets) {
+ if (!alterLastCommand) {
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()
+ ->lookup((*it)->name());
+ if(tree && p.isModified())
+ tree->addModifiedProperty(property, (*it)->property(property));
+ }
+ (*it)->setProperty(property, value);
+ emitWidgetPropertyChanged((*it), property, value);
+ }
+ }
+}
+
+void WidgetPropertySet::emitWidgetPropertyChanged(QWidget *w, const QCString& property, const QVariant& value)
+{
+ emit widgetPropertyChanged(w, property, value);
+
+ Form *form = KFormDesigner::FormManager::self()->activeForm();
+ if (form && form->library()->propertySetShouldBeReloadedAfterPropertyChange( w->className(), w, property)) {
+ //setSelectedWidget(0, false);
+ qApp->eventLoop()->processEvents(QEventLoop::AllEvents); //be sure events related to editors are consumed
+ setSelectedWidget(w, /*!add*/false, /*forceReload*/true);
+ qApp->eventLoop()->processEvents(QEventLoop::AllEvents); //be sure events related to editors are consumed
+ //KFormDesigner::FormManager::self()->showPropertySet(this, true/*forceReload*/);
+ }
+}
+
+void
+WidgetPropertySet::createPropertyCommandsInDesignMode(QWidget* widget,
+ const QMap<QCString, QVariant> &propValues, CommandGroup *group, bool addToActiveForm,
+ bool execFlagForSubCommands)
+{
+ if (!widget || propValues.isEmpty())
+ return;
+
+ //is this widget selected? (if so, use property system)
+ const bool widgetIsSelected = KFormDesigner::FormManager::self()->activeForm()->selectedWidget() == widget;
+
+ d->slotPropertyChanged_addCommandEnabled = false;
+ QMap<QCString, QVariant>::ConstIterator endIt = propValues.constEnd();
+// CommandGroup *group = new CommandGroup(commandName);
+ for(QMap<QCString, QVariant>::ConstIterator it = propValues.constBegin(); it != endIt; ++it)
+ {
+ if (!d->set.contains(it.key())) {
+ kdWarning() << "WidgetPropertySet::createPropertyCommandsInDesignMode(): \"" <<it.key()<<"\" property not found"<<endl;
+ continue;
+ }
+ PropertyCommand *subCommand = new PropertyCommand(this, widget->name(),
+ widget->property(it.key()), it.data(), it.key());
+ group->addCommand( subCommand, execFlagForSubCommands);
+ if (widgetIsSelected) {
+ d->set[it.key()].setValue(it.data());
+ }
+ else {
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(widget);
+ QWidget *subwidget = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : widget;
+ if (-1 != subwidget->metaObject()->findProperty(it.key(), true) && subwidget->property(it.key())!=it.data()) {
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(widget->name());
+ if (tree)
+ tree->addModifiedProperty(it.key(), subwidget->property(it.key()));
+ subwidget->setProperty(it.key(), it.data());
+ emit widgetPropertyChanged(widget, it.key(), it.data());
+ }
+ }
+ }
+ d->lastCommand = 0;
+ if (addToActiveForm)
+ KFormDesigner::FormManager::self()->activeForm()->addCommand(group, false/*no exec*/);
+ d->slotPropertyChanged_addCommandEnabled = true;
+// }
+}
+
+//! \todo make it support undo
+void
+WidgetPropertySet::saveEnabledProperty(bool value)
+{
+// for(QWidget *w = d->widgets.first(); w; w = d->widgets.next()) {
+ foreach(QGuardedWidgetList::ConstIterator, it, d->widgets) {
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()
+ ->lookup((*it)->name());
+ if(tree->isEnabled() == value)
+ continue;
+
+ QPalette p( (*it)->palette() );
+ if (!d->origActiveColors)
+ d->origActiveColors = new QColorGroup( p.active() );
+ if (value) {
+ if (d->origActiveColors)
+ p.setActive( *d->origActiveColors ); //revert
+ }
+ else {
+ QColorGroup cg = p.disabled();
+ //also make base color a bit disabled-like
+ cg.setColor(QColorGroup::Base, cg.color(QColorGroup::Background));
+ p.setActive(cg);
+ }
+ (*it)->setPalette(p);
+
+ tree->setEnabled(value);
+ emit widgetPropertyChanged((*it), "enabled", QVariant(value, 3));
+ }
+}
+
+bool
+WidgetPropertySet::isNameValid(const QString &name)
+{
+ //! \todo add to undo buffer
+ QWidget *w = d->widgets.first();
+ //also update widget's name in QObject member
+ if (!KexiUtils::isIdentifier(name)) {
+ KMessageBox::sorry(KFormDesigner::FormManager::self()->activeForm()->widget(),
+ i18n("Could not rename widget \"%1\" to \"%2\" because "
+ "\"%3\" is not a valid name (identifier) for a widget.\n")
+ .arg(w->name()).arg(name).arg(name));
+ d->slotPropertyChangedEnabled = false;
+ d->set["name"].resetValue();
+ d->slotPropertyChangedEnabled = true;
+ return false;
+ }
+
+ if (KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(name)) {
+ KMessageBox::sorry( KFormDesigner::FormManager::self()->activeForm()->widget(),
+ i18n("Could not rename widget \"%1\" to \"%2\" "
+ "because a widget with the name \"%3\" already exists.\n")
+ .arg(w->name()).arg(name).arg(name));
+ d->slotPropertyChangedEnabled = false;
+ d->set["name"].resetValue();
+ d->slotPropertyChangedEnabled = true;
+ return false;
+ }
+
+ return true; //ie name is correct
+}
+
+void
+WidgetPropertySet::slotPropertyReset(KoProperty::Set& set, KoProperty::Property& property)
+{
+ Q_UNUSED( set );
+
+ if(d->widgets.count() < 2)
+ return;
+
+ // We use the old value in modifProp for each widget
+// for(QWidget *w = d->widgets.first(); w; w = d->widgets.next()) {
+ foreach(QGuardedWidgetList::ConstIterator, it, d->widgets) {
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup((*it)->name());
+ if(tree->modifiedProperties()->contains(property.name()))
+ (*it)->setProperty(property.name(), tree->modifiedProperties()->find(property.name()).data());
+ }
+}
+
+void
+WidgetPropertySet::slotWidgetDestroyed()
+{
+// if(d->widgets.contains(QGuardedPtr<const QWidget>( dynamic_cast<const QWidget*>(sender()) ))) {
+ //only clear this set if it contains the destroyed widget
+ foreach(QGuardedWidgetList::ConstIterator, it, d->widgets) {
+ if (dynamic_cast<const QWidget*>(sender()) == *it) {
+ clearSet();
+ break;
+ }
+ }
+}
+
+bool
+WidgetPropertySet::eventFilter(QObject *o, QEvent *ev)
+{
+ if(d->widgets.count() > 0 && o == d->widgets.first() && d->widgets.count() < 2)
+ {
+ if((ev->type() == QEvent::Resize) || (ev->type() == QEvent::Move)) {
+ if(!d->set.contains("geometry"))
+ return false;
+ if(d->set["geometry"].value() == o->property("geometry")) // to avoid infinite recursion
+ return false;
+
+ d->set["geometry"] = static_cast<QWidget*>(o)->geometry();
+ }
+ }
+ else if(d->widgets.count() > 1 && ev->type() == QEvent::Move) // the widget is being moved, we update the property
+ {
+ if(d->isUndoing)
+ return false;
+
+ if(d->lastGeoCommand)
+ d->lastGeoCommand->setPos(static_cast<QMoveEvent*>(ev)->pos());
+ else {
+ QStringList list;
+ foreach(QGuardedWidgetList::ConstIterator, it, d->widgets)
+ list.append((*it)->name());
+
+ d->lastGeoCommand = new GeometryPropertyCommand(this, list, static_cast<QMoveEvent*>(ev)->oldPos());
+ if (KFormDesigner::FormManager::self()->activeForm())
+ KFormDesigner::FormManager::self()->activeForm()->addCommand(d->lastGeoCommand, false);
+ }
+ }
+
+ return false;
+}
+
+// Alignment-related functions /////////////////////////////
+
+void
+WidgetPropertySet::createAlignProperty(const QMetaProperty *meta, QWidget *widget, QWidget *subwidget)
+{
+ if (!KFormDesigner::FormManager::self()->activeForm()
+|| !KFormDesigner::FormManager::self()->activeForm()->objectTree())
+ return;
+
+ QStringList list;
+ QString value;
+ const int alignment = subwidget->property("alignment").toInt();
+ const QStringList keys( QStringList::fromStrList( meta->valueToKeys(alignment) ) );
+
+ QStrList *enumKeys = new QStrList(meta->enumKeys());
+ const QStringList possibleValues( QStringList::fromStrList(*enumKeys) );
+ delete enumKeys;
+
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(widget->name());
+ bool isTopLevel = KFormDesigner::FormManager::self()->isTopLevel(widget);
+
+ if(possibleValues.find("AlignHCenter")!=possibleValues.constEnd()) {
+ // Create the horizontal alignment property
+ if(keys.find("AlignHCenter")!=keys.constEnd() || keys.find("AlignCenter")!=keys.constEnd())
+ value = "AlignHCenter";
+ else if(keys.find("AlignRight")!=keys.constEnd())
+ value = "AlignRight";
+ else if(keys.find("AlignLeft")!=keys.constEnd())
+ value = "AlignLeft";
+ else if(keys.find("AlignJustify")!=keys.constEnd())
+ value = "AlignJustify";
+ else
+ value = "AlignAuto";
+
+ list << "AlignAuto" << "AlignLeft" << "AlignRight" << "AlignHCenter" << "AlignJustify";
+ KoProperty::Property *p = new KoProperty::Property("hAlign", createValueList(0, list), value,
+ i18n("Translators: please keep this string short (less than 20 chars)", "Hor. Alignment"),
+ i18n("Horizontal Alignment"));
+ d->set.addProperty(p);
+ if(!isPropertyVisible(p->name(), isTopLevel)) {
+ p->setVisible(false);
+ }
+ updatePropertyValue(tree, "hAlign");
+ list.clear();
+ }
+
+ if(possibleValues.find("AlignTop")!=possibleValues.constEnd())
+ {
+ // Create the ver alignment property
+ if(keys.find("AlignTop")!=keys.constEnd())
+ value = "AlignTop";
+ else if(keys.find("AlignBottom")!=keys.constEnd())
+ value = "AlignBottom";
+ else
+ value = "AlignVCenter";
+
+ list << "AlignTop" << "AlignVCenter" << "AlignBottom";
+ KoProperty::Property *p = new KoProperty::Property("vAlign", createValueList(0, list), value,
+ i18n("Translators: please keep this string short (less than 20 chars)", "Ver. Alignment"),
+ i18n("Vertical Alignment"));
+ d->set.addProperty(p);
+ if(!isPropertyVisible(p->name(), isTopLevel)) {
+ p->setVisible(false);
+ }
+ updatePropertyValue(tree, "vAlign");
+ }
+
+ if(possibleValues.find("WordBreak")!=possibleValues.constEnd()
+// && isPropertyVisible("wordbreak", false, subwidget->className())
+// && !subWidget->inherits("QLineEdit") /* QLineEdit doesn't support 'word break' is this generic enough?*/
+ ) {
+ // Create the wordbreak property
+ KoProperty::Property *p = new KoProperty::Property("wordbreak",
+ QVariant(alignment & Qt::WordBreak, 3), i18n("Word Break"), i18n("Word Break") );
+ d->set.addProperty(p);
+ updatePropertyValue(tree, "wordbreak");
+ if (!KFormDesigner::FormManager::self()->activeForm()->library()->isPropertyVisible(
+ subwidget->className(), subwidget, p->name(), false/*multiple*/, isTopLevel))
+ {
+ p->setVisible(false);
+ }
+ }
+}
+
+void
+WidgetPropertySet::saveAlignProperty(const QString &property)
+{
+ if (!KFormDesigner::FormManager::self()->activeForm())
+ return;
+
+ QStrList list;
+ if( d->set.contains("hAlign") )
+ list.append( d->set["hAlign"].value().toCString() );
+ if( d->set.contains("vAlign") )
+ list.append( d->set["vAlign"].value().toCString() );
+ if( d->set.contains("wordbreak") && d->set["wordbreak"].value().toBool() )
+ list.append("WordBreak");
+
+ WidgetWithSubpropertiesInterface* subpropIface = dynamic_cast<WidgetWithSubpropertiesInterface*>(
+ (QWidget*)d->widgets.first() );
+ QWidget *subwidget = (subpropIface && subpropIface->subwidget()) ? subpropIface->subwidget() : (QWidget*)d->widgets.first();
+ int count = subwidget->metaObject()->findProperty("alignment", true);
+ const QMetaProperty *meta = subwidget->metaObject()->property(count, true);
+ subwidget->setProperty("alignment", meta->keysToValue(list));
+
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(
+ d->widgets.first()->name() );
+ if(tree && d->set[property.latin1()].isModified())
+ tree->addModifiedProperty(property.latin1(), d->set[property.latin1()].oldValue());
+
+ if(d->isUndoing)
+ return;
+
+ if(d->lastCommand && d->lastCommand->property() == "alignment")
+ d->lastCommand->setValue(meta->keysToValue(list));
+ else {
+ d->lastCommand = new PropertyCommand(this, d->widgets.first()->name(),
+ subwidget->property("alignment"), meta->keysToValue(list), "alignment");
+ KFormDesigner::FormManager::self()->activeForm()->addCommand(d->lastCommand, false);
+ }
+}
+
+// Layout-related functions //////////////////////////
+
+void
+WidgetPropertySet::createLayoutProperty(ObjectTreeItem *item)
+{
+ Container *container = item->container();
+ if (!container || !KFormDesigner::FormManager::self()->activeForm() ||
+ !KFormDesigner::FormManager::self()->activeForm()->objectTree() || !container->widget())
+ return;
+ // special containers have no 'layout' property, as it should not be changed
+ QCString className = container->widget()->className();
+ if((className == "HBox") || (className == "VBox") || (className == "Grid"))
+ return;
+
+ QStringList list;
+ QString value = Container::layoutTypeToString(container->layoutType());
+
+ list << "NoLayout" << "HBox" << "VBox" << "Grid" << "HFlow" << "VFlow";
+
+ KoProperty::Property *p = new KoProperty::Property("layout", createValueList(0, list), value,
+ i18n("Container's Layout"), i18n("Container's Layout"));
+ p->setVisible( container->form()->library()->advancedPropertiesVisible() );
+ d->set.addProperty(p);
+
+ updatePropertyValue(item, "layout");
+
+ p = new KoProperty::Property("layoutMargin", container->layoutMargin(), i18n("Layout Margin"), i18n("Layout Margin"));
+ d->set.addProperty(p);
+ updatePropertyValue(item, "layoutMargin");
+ if(container->layoutType() == Container::NoLayout)
+ p->setVisible(false);
+
+ p = new KoProperty::Property("layoutSpacing", container->layoutSpacing(),
+ i18n("Layout Spacing"), i18n("Layout Spacing"));
+ d->set.addProperty(p);
+ updatePropertyValue(item, "layoutSpacing");
+ if(container->layoutType() == Container::NoLayout)
+ p->setVisible(false);
+
+}
+
+void
+WidgetPropertySet::saveLayoutProperty(const QString &prop, const QVariant &value)
+{
+ Container *container=0;
+ if(!KFormDesigner::FormManager::self()->activeForm() || !KFormDesigner::FormManager::self()->activeForm()->objectTree())
+ return;
+ ObjectTreeItem *item = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(d->widgets.first()->name());
+ if(!item)
+ return;
+ container = item->container();
+
+ if(prop == "layout") {
+ Container::LayoutType type = Container::stringToLayoutType(value.toString());
+
+ if(d->lastCommand && d->lastCommand->property() == "layout" && !d->isUndoing)
+ d->lastCommand->setValue(value);
+ else if(!d->isUndoing) {
+ d->lastCommand = new LayoutPropertyCommand(this, d->widgets.first()->name(),
+ d->set["layout"].oldValue(), value);
+ KFormDesigner::FormManager::self()->activeForm()->addCommand(d->lastCommand, false);
+ }
+
+ container->setLayout(type);
+ bool show = (type != Container::NoLayout);
+ if(show != d->set["layoutMargin"].isVisible()) {
+ d->set["layoutMargin"].setVisible(show);
+ d->set["layoutSpacing"].setVisible(show);
+ KFormDesigner::FormManager::self()->showPropertySet(this, true/*force*/);
+ }
+ return;
+ }
+
+ if(prop == "layoutMargin" && container->layout()) {
+ container->setLayoutMargin(value.toInt());
+ container->layout()->setMargin(value.toInt());
+ }
+ else if(prop == "layoutSpacing" && container->layout()) {
+ container->setLayoutSpacing(value.toInt());
+ container->layout()->setSpacing(value.toInt());
+ }
+
+ ObjectTreeItem *tree = KFormDesigner::FormManager::self()->activeForm()->objectTree()->lookup(d->widgets.first()->name());
+ if(tree && d->set[ prop.latin1() ].isModified())
+ tree->addModifiedProperty(prop.latin1(), d->set[prop.latin1()].oldValue());
+
+ if(d->isUndoing)
+ return;
+
+ if(d->lastCommand && (QString(d->lastCommand->property()) == prop))
+ d->lastCommand->setValue(value);
+ else {
+ d->lastCommand = new PropertyCommand(this, d->widgets.first()->name(),
+ d->set[ prop.latin1() ].oldValue(), value, prop.latin1());
+ KFormDesigner::FormManager::self()->activeForm()->addCommand(d->lastCommand, false);
+ }
+}
+
+
+
+////////////////////////////////////////// i18n related functions ////////
+
+void
+WidgetPropertySet::initPropertiesDescription()
+{
+//! \todo perhaps a few of them shouldn't be translated within KFD mode,
+//! to be more Qt Designer friendly?
+ d->propCaption["name"] = i18n("Name");
+ d->propCaption["caption"] = i18n("Caption");
+ d->propCaption["text"] = i18n("Text");
+ d->propCaption["paletteBackgroundPixmap"] = i18n("Background Pixmap");
+ d->propCaption["enabled"] = i18n("Enabled");
+ d->propCaption["geometry"] = i18n("Geometry");
+ d->propCaption["sizePolicy"] = i18n("Size Policy");
+ d->propCaption["minimumSize"] = i18n("Minimum Size");
+ d->propCaption["maximumSize"] = i18n("Maximum Size");
+ d->propCaption["font"] = i18n("Font");
+ d->propCaption["cursor"] = i18n("Cursor");
+ d->propCaption["paletteForegroundColor"] = i18n("Foreground Color");
+ d->propCaption["paletteBackgroundColor"] = i18n("Background Color");
+ d->propCaption["focusPolicy"] = i18n("Focus Policy");
+ d->propCaption["margin"] = i18n("Margin");
+ d->propCaption["readOnly"] = i18n("Read Only");
+ //any QFrame
+ d->propCaption["frame"] = i18n("Frame");
+ d->propCaption["lineWidth"] = i18n("Frame Width");
+ d->propCaption["midLineWidth"] = i18n("Mid Frame Width");
+ d->propCaption["frameShape"] = i18n("Frame Shape");
+ d->propCaption["frameShadow"] = i18n("Frame Shadow");
+ //any QScrollbar
+ d->propCaption["vScrollBarMode"] = i18n("Vertical ScrollBar");
+ d->propCaption["hScrollBarMode"] = i18n("Horizontal ScrollBar");
+
+ d->propValCaption["NoBackground"] = i18n("No Background");
+ d->propValCaption["PaletteForeground"] = i18n("Palette Foreground");
+ d->propValCaption["AutoText"] = i18n("Auto (HINT: for AutoText)", "Auto");
+
+ d->propValCaption["AlignAuto"] = i18n("Auto (HINT: for Align)", "Auto");
+ d->propValCaption["AlignLeft"] = i18n("Left (HINT: for Align)", "Left");
+ d->propValCaption["AlignRight"] = i18n("Right (HINT: for Align)", "Right");
+ d->propValCaption["AlignHCenter"] = i18n("Center (HINT: for Align)", "Center");
+ d->propValCaption["AlignJustify"] = i18n("Justify (HINT: for Align)", "Justify");
+ d->propValCaption["AlignVCenter"] = i18n("Center (HINT: for Align)", "Center");
+ d->propValCaption["AlignTop"] = i18n("Top (HINT: for Align)", "Top");
+ d->propValCaption["AlignBottom"] = i18n("Bottom (HINT: for Align)", "Bottom");
+
+ d->propValCaption["NoFrame"] = i18n("No Frame (HINT: for Frame Shape)", "No Frame");
+ d->propValCaption["Box"] = i18n("Box (HINT: for Frame Shape)", "Box");
+ d->propValCaption["Panel"] = i18n("Panel (HINT: for Frame Shape)", "Panel");
+ d->propValCaption["WinPanel"] = i18n("Windows Panel (HINT: for Frame Shape)", "Windows Panel");
+ d->propValCaption["HLine"] = i18n("Horiz. Line (HINT: for Frame Shape)", "Horiz. Line");
+ d->propValCaption["VLine"] = i18n("Vertical Line (HINT: for Frame Shape)", "Vertical Line");
+ d->propValCaption["StyledPanel"] = i18n("Styled (HINT: for Frame Shape)", "Styled");
+ d->propValCaption["PopupPanel"] = i18n("Popup (HINT: for Frame Shape)", "Popup");
+ d->propValCaption["MenuBarPanel"] = i18n("Menu Bar (HINT: for Frame Shape)", "Menu Bar");
+ d->propValCaption["ToolBarPanel"] = i18n("Toolbar (HINT: for Frame Shape)", "Toolbar");
+ d->propValCaption["LineEditPanel"] = i18n("Text Box (HINT: for Frame Shape)", "Text Box");
+ d->propValCaption["TabWidgetPanel"] = i18n("Tab Widget (HINT: for Frame Shape)", "Tab Widget");
+ d->propValCaption["GroupBoxPanel"] = i18n("Group Box (HINT: for Frame Shape)", "Group Box");
+
+ d->propValCaption["Plain"] = i18n("Plain (HINT: for Frame Shadow)", "Plain");
+ d->propValCaption["Raised"] = i18n("Raised (HINT: for Frame Shadow)", "Raised");
+ d->propValCaption["Sunken"] = i18n("Sunken (HINT: for Frame Shadow)", "Sunken");
+ d->propValCaption["MShadow"] = i18n("for Frame Shadow", "Internal");
+
+ d->propValCaption["NoFocus"] = i18n("No Focus (HINT: for Focus)", "No Focus");
+ d->propValCaption["TabFocus"] = i18n("Tab (HINT: for Focus)", "Tab");
+ d->propValCaption["ClickFocus"] = i18n("Click (HINT: for Focus)", "Click");
+ d->propValCaption["StrongFocus"] = i18n("Tab/Click (HINT: for Focus)", "Tab/Click");
+ d->propValCaption["WheelFocus"] = i18n("Tab/Click/MouseWheel (HINT: for Focus)", "Tab/Click/MouseWheel");
+
+ d->propValCaption["Auto"] = i18n("Auto");
+ d->propValCaption["AlwaysOff"] = i18n("Always Off");
+ d->propValCaption["AlwaysOn"] = i18n("Always On");
+
+ //orientation
+ d->propValCaption["Horizontal"] = i18n("Horizontal");
+ d->propValCaption["Vertical"] = i18n("Vertical");
+}
+
+QString
+WidgetPropertySet::propertyCaption(const QCString &name)
+{
+ return d->propCaption[name];
+}
+
+QString
+WidgetPropertySet::valueCaption(const QCString &name)
+{
+ return d->propValCaption[name];
+}
+
+KoProperty::Property::ListData*
+WidgetPropertySet::createValueList(WidgetInfo *winfo, const QStringList &list)
+{
+// QMap <QString, QVariant> map;
+ QStringList names;
+ QStringList::ConstIterator endIt = list.end();
+ for(QStringList::ConstIterator it = list.begin(); it != endIt; ++it) {
+ QString n( d->propValCaption[ (*it).latin1() ] );
+ if (n.isEmpty()) { //try within factory and (maybe) parent factory
+ if (winfo)
+ n = KFormDesigner::FormManager::self()->activeForm()->library()->propertyDescForValue( winfo, (*it).latin1() );
+ if (n.isEmpty())
+ names.append( *it ); //untranslated
+// map.insert(*it, (*it).latin1()); //untranslated
+ else
+ names.append( n );
+// map.insert(*it, n);
+ }
+ else
+ names.append( n );
+// map.insert(*it, n);
+ }
+ return new KoProperty::Property::ListData(list, names);
+}
+
+void
+WidgetPropertySet::addPropertyCaption(const QCString &property, const QString &caption)
+{
+ if(!d->propCaption.contains(property))
+ d->propCaption[property] = caption;
+}
+
+void
+WidgetPropertySet::addValueCaption(const QCString &value, const QString &caption)
+{
+ if(!d->propValCaption.contains(value))
+ d->propValCaption[value] = caption;
+}
+
+#include "widgetpropertyset.moc"
diff --git a/kexi/formeditor/widgetpropertyset.h b/kexi/formeditor/widgetpropertyset.h
new file mode 100644
index 000000000..71e7b2254
--- /dev/null
+++ b/kexi/formeditor/widgetpropertyset.h
@@ -0,0 +1,206 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFD_WIDGETPROPERTYSET_H
+#define KFD_WIDGETPROPERTYSET_H
+
+#include <qobject.h>
+#include <qstrlist.h>
+
+#include <koproperty/set.h>
+#include <koproperty/property.h>
+
+class QMetaObject;
+class QWidget;
+
+namespace KFormDesigner {
+
+class FormManager;
+class ObjectTreeItem;
+class WidgetPropertySetPrivate;
+class WidgetInfo;
+class CommandGroup;
+
+class KFORMEDITOR_EXPORT WidgetPropertySet : public QObject
+{
+ Q_OBJECT
+
+ public:
+ WidgetPropertySet(QObject *parent);
+ ~WidgetPropertySet();
+
+// FormManager* manager();
+
+ KoProperty::Property& operator[](const QCString &name);
+
+ KoProperty::Property& property(const QCString &name);
+
+ bool contains(const QCString &property);
+
+ /*! i18n function used by factories to add new property caption.
+ Should be called on Factory creation. */
+ void addPropertyCaption(const QCString &property, const QString &caption);
+
+ void addValueCaption(const QCString &value, const QString &caption);
+
+ public slots:
+ /*! Sets the widget which properties are shown in the property editor.
+ If \a add is true, the list switch to multiple widget mode
+ (only common properties are shown). Should be directly
+ connected to Form::widgetSelected() signal.
+ If \a forceReload is true, the the properties will be redisplayed in the property editor
+ even if these were already displayed.
+ If \a showPropertySet is true (the default), property editor will be updated for the current selection.
+ This flag is set to false when we're selecting multiple widgets. */
+ void setSelectedWidget(QWidget *w, bool add = false, bool forceReload = false,
+ bool moreWillBeSelected = false);
+
+ void setSelectedWidgetWithoutReload(QWidget *w, bool add = false, bool moreWillBeSelected = false) {
+ setSelectedWidget(w, add, false, moreWillBeSelected);
+ }
+
+ /*! This function is called every time a property is modifed. It also takes
+ care of saving set and enum properties. */
+ void slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property);
+
+ /*! This slot is called when a property is reset using the "reload" button in PropertyEditor. */
+ void slotPropertyReset(KoProperty::Set& set, KoProperty::Property& property);
+
+ /*! This slot is called when the watched widget is destroyed. Resets the buffer.*/
+ void slotWidgetDestroyed();
+
+// void setPropertyValueInDesignMode(QWidget* widget, const QMap<QCString, QVariant> &propValues,
+ void createPropertyCommandsInDesignMode(QWidget* widget, const QMap<QCString,
+ QVariant> &propValues, CommandGroup *group, bool addToActiveForm = true,
+ bool execFlagForSubCommands = false);
+
+ signals:
+ /*! This signal is emitted when a property was changed.
+ \a widg is the widget concerned, \a property
+ is the name of the modified property, and \a v is the new value of this property. */
+ void widgetPropertyChanged(QWidget *w, const QCString &property, const QVariant &v);
+
+ /*! This signal is emitted when the name of the widget is modified.
+ \a oldname is the name of the widget before the
+ change, \a newname is the name after renaming. */
+ void widgetNameChanged(const QCString &oldname, const QCString &newname);
+
+ protected:
+ /*! Adds the widget in d->widgets, and updates property visibilty. */
+ void addWidget(QWidget *w);
+
+ /*! Fills the list with properties related to the widget \a w. Also updates
+ properties old value and changed state. */
+ void createPropertiesForWidget(QWidget *w);
+
+ /*! Creates a map property description->prop. value from
+ the list of keys \a list. */
+ KoProperty::Property::ListData* createValueList(WidgetInfo *winfo, const QStringList &list);
+
+ /*! Changes \a property old value and changed state, using the value
+ stored in \a tree. Optional \a meta can be specified if you need to handle enum values. */
+ void updatePropertyValue(ObjectTreeItem *tree, const char *property, const QMetaProperty *meta = 0);
+
+ /*! \return the property list hold by this object. Do not modify the list,
+ just use this method to change Editor's list. */
+ KoProperty::Set* set();
+
+ /*! Clears the set, and reset all members. */
+ void clearSet(bool dontSignalShowPropertySet = false);
+
+ /*! Saves old values of modified properties in ObjectTreeItem, so
+ that we can restore them later.*/
+ void saveModifiedProperties();
+
+ /*! Checks if the name entered by user is valid, ie that it is
+ a valid identifier, and that there is no name conflict. */
+ bool isNameValid(const QString &name);
+
+ /*! Saves 'enabled' property, and takes care of updating widget's palette. */
+ void saveEnabledProperty(bool value);
+
+ /*! This function filters the event of the selected widget to
+ automatically updates the "geometry" property
+ when the widget is moved or resized in the Form. */
+ bool eventFilter(QObject *o, QEvent *ev);
+
+ /*! Changes undoing state of the list. Used by Undo command to
+ prevent recursion. */
+ void setUndoing(bool isUndoing);
+
+ bool isUndoing();
+
+ /*! This function is used to filter the properties to be shown
+ (ie not show "caption" if the widget isn't toplevel).
+ \return true if the property should be shown. False otherwise.*/
+ bool isPropertyVisible(const QCString &property, bool isTopLevel,
+ const QCString &classname=QCString());
+
+ // Following functions are used to create special types of properties, different
+ // from Q_PROPERTY
+
+ /*! Creates the properties related to alignment (ie hAlign, vAlign and WordBreak) for
+ the QWidget \a widget. \a subwidget is the same as \a widget if the widget itself handles
+ the property and it's a child widget if the child handles the property.
+ For example, the second case is true for KexiDBAutoField.
+ \a meta is the QMetaProperty for "alignment" property" of subwidget. */
+ void createAlignProperty(const QMetaProperty *meta, QWidget *widget, QWidget *subwidget);
+
+ /*! Saves the properties related to alignment (ie hAlign, vAlign and WordBreak)
+ and modifies the "alignment" property of the widget.*/
+ void saveAlignProperty(const QString &property);
+
+ /*! Creates the "layout" property, for the Container representing \a item. */
+ void createLayoutProperty(ObjectTreeItem *item);
+
+ /*! Saves the "layout" property and changes the Container 's layout (
+ using Container::setLayout() ).*/
+ void saveLayoutProperty(const QString &property, const QVariant &value);
+
+ // Some i18n functions
+ //! Adds translations for general properties, by adding items in d->propDesc
+ void initPropertiesDescription();
+
+ /*! \return The i18n'ed name of the property whose name is \a name, that will be
+ displayed in PropertyEditor. */
+ QString propertyCaption(const QCString &name);
+
+ /*! \return The i18n'ed name of the property's value whose name is \a name. */
+ QString valueCaption(const QCString &name);
+
+ /*! \return The i18n'ed list of values, that will be shown by Property
+ Editor (using descFromValue()).*/
+ //QStringList captionForList(const QStringList &list);
+
+ //! Helper
+ void emitWidgetPropertyChanged(QWidget *w, const QCString& property, const QVariant& value);
+
+ private:
+ WidgetPropertySetPrivate *d;
+
+ friend class FormManager;
+ friend class PropertyCommand;
+ friend class LayoutPropertyCommand;
+ friend class GeometryPropertyCommand;
+};
+
+}
+
+#endif
diff --git a/kexi/formeditor/widgetwithsubpropertiesinterface.cpp b/kexi/formeditor/widgetwithsubpropertiesinterface.cpp
new file mode 100644
index 000000000..812379e18
--- /dev/null
+++ b/kexi/formeditor/widgetwithsubpropertiesinterface.cpp
@@ -0,0 +1,98 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "widgetwithsubpropertiesinterface.h"
+
+#include <qmetaobject.h>
+#include <qasciidict.h>
+
+#include <kdebug.h>
+
+using namespace KFormDesigner;
+
+WidgetWithSubpropertiesInterface::WidgetWithSubpropertiesInterface()
+{
+}
+
+WidgetWithSubpropertiesInterface::~WidgetWithSubpropertiesInterface()
+{
+}
+
+void WidgetWithSubpropertiesInterface::setSubwidget(QWidget *widget)
+{
+ m_subwidget = widget;
+ m_subproperies.clear();
+ QAsciiDict<char> addedSubproperies(1024);
+ if (m_subwidget) {
+ //remember properties in the subwidget that are not present in the parent
+ for( QMetaObject *metaObject = m_subwidget->metaObject(); metaObject; metaObject = metaObject->superClass()) {
+ const int numProperties = metaObject->numProperties();
+ for (int i = 0; i < numProperties; i++) {
+ const char *propertyName = metaObject->property( i )->name();
+ if (dynamic_cast<QObject*>(this)->metaObject()->findProperty( propertyName, true )==-1
+ && !addedSubproperies.find( propertyName ) )
+ {
+ m_subproperies.append( propertyName );
+ addedSubproperies.insert( propertyName, (char*)1 );
+ kdDebug() << propertyName << endl;
+ }
+ }
+ }
+ qHeapSort( m_subproperies );
+ }
+}
+
+QWidget* WidgetWithSubpropertiesInterface::subwidget() const
+{
+ return m_subwidget;
+}
+
+QValueList<QCString> WidgetWithSubpropertiesInterface::subproperies() const
+{
+ return m_subproperies;
+}
+
+const QMetaProperty *WidgetWithSubpropertiesInterface::findMetaSubproperty(const char * name) const
+{
+ if (!m_subwidget || m_subproperies.find(name) == m_subproperies.constEnd()) {
+ return 0;
+ }
+ const int index = m_subwidget->metaObject()->findProperty( name, true );
+ if (index==-1)
+ return 0;
+ return m_subwidget->metaObject()->property( index, true );
+}
+
+QVariant WidgetWithSubpropertiesInterface::subproperty( const char * name, bool &ok ) const
+{
+ if (!m_subwidget || m_subproperies.find(name) == m_subproperies.constEnd()) {
+ ok = false;
+ return QVariant();
+ }
+ ok = true;
+ return m_subwidget->property( name );
+}
+
+bool WidgetWithSubpropertiesInterface::setSubproperty( const char * name, const QVariant & value )
+{
+ if (!m_subwidget || m_subproperies.find(name) == m_subproperies.end()) {
+ return false;
+ }
+ return m_subwidget->setProperty( name, value );
+}
diff --git a/kexi/formeditor/widgetwithsubpropertiesinterface.h b/kexi/formeditor/widgetwithsubpropertiesinterface.h
new file mode 100644
index 000000000..878fefcae
--- /dev/null
+++ b/kexi/formeditor/widgetwithsubpropertiesinterface.h
@@ -0,0 +1,72 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WIDGETWITHSUBPROPERTIESINTERFACE_H
+#define WIDGETWITHSUBPROPERTIESINTERFACE_H
+
+#include <qcstring.h>
+#include <qvaluelist.h>
+#include <qwidget.h>
+#include <qguardedptr.h>
+#include <qvariant.h>
+
+namespace KFormDesigner {
+
+//! An interface for declaring form widgets to have subproperties.
+/*! Currently used in KexiDBAutoField to allow editing specific properties
+ of its internal editor. For example, if the autofield is of type Image Box,
+ the Image Box widget has some specific properties like "lineWidth".
+ Such properties are provided by the parent KexiDBAutoField object as subproperties. */
+class KFORMEDITOR_EXPORT WidgetWithSubpropertiesInterface
+{
+ public:
+ WidgetWithSubpropertiesInterface();
+ virtual ~WidgetWithSubpropertiesInterface();
+
+ //! Sets \a widget subwidget handling subproperties. Setting 0 clears subwidget.
+//! @todo maybe someone wants to add more than one widget here?
+ void setSubwidget(QWidget *widget);
+
+ //! \return the assigned subwidget.
+ QWidget* subwidget() const;
+
+ //! \return a list of subproperties available for this widget.
+ //! This is achieved by only listing those properties that are available in the
+ QValueList<QCString> subproperies() const;
+
+ //! \return a meta property for a widget's subproperty or 0 if there
+ //! is no such subproperty.
+ const QMetaProperty *findMetaSubproperty(const char * name) const;
+
+ //! \return a value of widget's subproperty. \a ok is set to true on success
+ //! and to false on failure.
+ QVariant subproperty( const char * name, bool &ok ) const;
+
+ //! Sets a subproperty value \a value for a subproperty \a name
+ //! \return true on successful setting and false when there
+ //! is no such a subproperty in the subwidget or QObject::setProperty() failed.
+ bool setSubproperty( const char * name, const QVariant & value );
+
+ protected:
+ QGuardedPtr<QWidget> m_subwidget;
+ QValueList<QCString> m_subproperies;
+};
+}
+
+#endif
diff --git a/kexi/kexi.desktop b/kexi/kexi.desktop
new file mode 100644
index 000000000..3ba9104ca
--- /dev/null
+++ b/kexi/kexi.desktop
@@ -0,0 +1,100 @@
+[Desktop Entry]
+Name=Kexi
+Name[hi]=के-एक्जाई
+Name[lo]=ສອຍຄີວ - K
+Name[ne]=केक्सी
+Type=Application
+Exec=kexi
+Icon=kexi
+MimeType=application/x-kexiproject-sqlite;application/x-sqlite2;application/x-kexiproject-shortcut;application/x-kexiproject-sqlite2;application/x-kexiproject-sqlite3;application/x-sqlite3;application/x-kexi-connectiondata;application/x-msaccess
+Terminal=false
+GenericName=Database Creator
+GenericName[bg]=Създаване на бази данни
+GenericName[br]=Aozer ar stlennvon
+GenericName[ca]=Creador de bases de dades
+GenericName[cs]=Tvůrce databází
+GenericName[cy]=Creuydd Cronfa Ddata
+GenericName[da]=Opretter af databaser
+GenericName[de]=Datenbank-Erstellung
+GenericName[el]=Δημιουργός βάσης δεδομένων
+GenericName[eo]=Datumbazkreilo
+GenericName[es]=Creador de bases de datos
+GenericName[et]=Andmebaaside imelihtne loomine
+GenericName[eu]=Datu-base sortzailea
+GenericName[fa]=ایجادکنندۀ دادگان
+GenericName[fi]=Tietokannan luoja
+GenericName[fr]=Créateur de base de données
+GenericName[fy]=Databank oanmeitsje
+GenericName[gl]=Criación de Bases de Datos
+GenericName[he]=יוצר מסדי נתונים
+GenericName[hr]=Dizajner baza podataka
+GenericName[hu]=Adatbázis-kezelő
+GenericName[is]=Gagnagrunns hönnuður
+GenericName[it]=Creatore di banche dati
+GenericName[ja]=データベース作成
+GenericName[km]=កម្មវិធី​បង្កើត​មូលដ្ឋាន​ទិន្នន័យ
+GenericName[lt]=Duomenų bazių kūrimo programa
+GenericName[lv]=Datu bāzu veidotājs
+GenericName[ms]=Pencipta Pangkalan Data
+GenericName[nb]=Databaseoppretting
+GenericName[nds]=Datenbank-Opstellen
+GenericName[ne]=डाटाबेस सर्जक
+GenericName[nl]=Database aanmaken
+GenericName[nn]=Databaseoppretting
+GenericName[pl]=Asystent tworzenia baz danych
+GenericName[pt]=Criação de Bases de Dados
+GenericName[pt_BR]=Criador de Banco de Dados
+GenericName[ru]=Базы данных
+GenericName[se]=Diehtovuođđoráhkadeaddji
+GenericName[sk]=Správa databázy
+GenericName[sl]=Ustvarjanje zbirk podatkov
+GenericName[sr]=Стваралац база података
+GenericName[sr@Latn]=Stvaralac baza podataka
+GenericName[sv]=Databasskapare
+GenericName[ta]=தரவுத்தளம் உருவாக்கி
+GenericName[tr]=Veritabanı Üreticisi
+GenericName[uk]=Створення баз даних
+GenericName[uz]=Maʼlumot baza yaratuvchi
+GenericName[uz@cyrillic]=Маълумот база яратувчи
+GenericName[zh_CN]=数据库创建器
+GenericName[zh_TW]=資料庫建立程式
+Comment=Develop desktop database applications
+Comment[bg]=Разработка на приложение за БД за работния плот
+Comment[ca]=Aplicacions de desenvolupament de bases de dades d'escriptori
+Comment[da]=Udvikl databaseprogrammer for desktoppen
+Comment[de]=Desktop-Datenbankanwendungen entwickeln
+Comment[el]=Ανάπτυξη εφαρμογών βάσεων δεδομένων
+Comment[eo]=Evoluigi tabulajn datumbaz-aplikaĵojn
+Comment[es]=Desarrollo de aplicaciones de escritorio para base de datos
+Comment[et]=Töölaua andmebaasirakenduste arendamine
+Comment[fa]=توسعۀ کاربردهای دادگان رومیزی
+Comment[fy]=Untwikkel databankapplikaasjes
+Comment[gl]=Desenvolve aplicacións de base de dados de escritorio
+Comment[hu]=Adatbázist kezelő alkalmazások fejlesztése
+Comment[it]=Sviluppa applicazioni per banche dati da desktop
+Comment[ja]=デスクトップ データベース アプリケーションを開発
+Comment[km]= កម្មវិធី​អភិវឌ្ឍន៍ មូលដ្ឋាន​ទិន្នន័យ​ផ្ទៃតុ​s
+Comment[lt]=Programa leidžia kurti darbastalio duomenų bazes
+Comment[lv]=Darbvirsmas datu bāzu veidošanai
+Comment[nb]=Utvikle databaseprogrammer for skrivebordet
+Comment[nds]=Datenbankprogrammen för den Schriefdisch opstellen
+Comment[ne]=डेस्कटप डाटाबेस अनुप्रयोग विकास गर्नुहोस्
+Comment[nl]=Ontwikkel databasetoepassingen
+Comment[pl]=Rozwój aplikacji bazodanowych
+Comment[pt]=Desenvolver aplicações de bases de dados para o utilizador
+Comment[pt_BR]=Desenvolve aplicações de banco de dados
+Comment[ru]=Работа с базами данных
+Comment[se]=Ovdánahte diehtovuođoprográmmaid čállinbeavddi várás
+Comment[sk]=Vytvárať databázové aplikácie
+Comment[sl]=Razvijte namizne programe za delo z zbirkami podatkov
+Comment[sr]=Развијање примена база података за радну површину
+Comment[sr@Latn]=Razvijanje primena baza podataka za radnu površinu
+Comment[sv]=Utveckla databasprogram för skrivbordet
+Comment[uk]=Розробка програм для баз даних
+Comment[uz]=Maʼlumot bazasi asosida dasturlarni tuzish
+Comment[uz@cyrillic]=Маълумот базаси асосида дастурларни тузиш
+Comment[zh_TW]=開發桌面資料庫應用程式
+DocPath=kexi/index.html
+X-KDE-NativeMimeType=application/x-kexiproject-sqlite
+X-KDE-NOTKoDocumentEmbeddable=true
+Categories=Qt;KDE;Office;
diff --git a/kexi/kexi_export.h b/kexi/kexi_export.h
new file mode 100644
index 000000000..11ed970a4
--- /dev/null
+++ b/kexi/kexi_export.h
@@ -0,0 +1,192 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KEXI_EXPORT_H_
+#define _KEXI_EXPORT_H_
+
+#include <kexidb/kexidb_export.h>
+
+#ifdef MAKE_KEXICORE_LIB
+# define KEXICORE_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXICORE_EXPORT KDE_IMPORT
+#else
+# define KEXICORE_EXPORT
+#endif
+
+#ifdef MAKE_KEXIMAIN_LIB
+# define KEXIMAIN_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIMAIN_EXPORT KDE_IMPORT
+#else
+# define KEXIMAIN_EXPORT
+#endif
+
+#ifdef MAKE_KEXITABLEFILTERS_LIB
+# define KEXITABLEFILTERS_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXITABLEFILTERS_EXPORT KDE_IMPORT
+#else
+# define KEXITABLEFILTERS_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIDATATABLE_LIB
+# define KEXIDATATABLE_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIDATATABLE_EXPORT KDE_IMPORT
+#else
+# define KEXIDATATABLE_EXPORT //for apps
+#endif
+
+#ifndef KEXIEXTWIDGETS_EXPORT //tmp
+
+#ifdef MAKE_KEXIEXTWIDGETS_LIB
+# define KEXIEXTWIDGETS_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIEXTWIDGETS_EXPORT KDE_IMPORT
+#else
+# define KEXIEXTWIDGETS_EXPORT //for apps
+#endif
+
+#endif
+
+#ifdef MAKE_KFORMEDITOR_LIB
+# define KFORMEDITOR_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KFORMEDITOR_EXPORT KDE_IMPORT
+#else
+# define KFORMEDITOR_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIPRJWIZARD_LIB
+# define KEXIPRJWIZARD_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIPRJWIZARD_EXPORT KDE_IMPORT
+#else
+# define KEXIPRJWIZARD_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIFILTER_LIB
+# define KEXIFILTER_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIFILTER_EXPORT KDE_IMPORT
+#else
+# define KEXIFILTER_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIWIDGETS_LIB
+# define KEXIWIDGETS_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIWIDGETS_EXPORT KDE_IMPORT
+#else
+# define KEXIWIDGETS_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIUUID_LIB
+# define KEXIUUID_EXPORT KDE_EXPORT
+#else
+# define KEXIUUID_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIRELATIONSVIEW_LIB
+# define KEXIRELATIONSVIEW_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIRELATIONSVIEW_EXPORT KDE_IMPORT
+#else
+# define KEXIRELATIONSVIEW_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIGUIUTILS_LIB
+# define KEXIGUIUTILS_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIGUIUTILS_EXPORT KDE_IMPORT
+#else
+# define KEXIGUIUTILS_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KROSS_MAIN_LIB
+# define KROSS_MAIN_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KROSS_MAIN_EXPORT KDE_IMPORT
+#else
+# define KROSS_MAIN_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIFORMUTILS_LIB
+# define KEXIFORMUTILS_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIFORMUTILS_EXPORT KDE_IMPORT
+#else
+# define KEXIFORMUTILS_EXPORT //for apps
+#endif
+
+#ifdef MAKE_KEXIREPORTUTILS_LIB
+# define KEXIREPORTUTILS_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIREPORTUTILS_EXPORT KDE_IMPORT
+#else
+# define KEXIREPORTUTILS_EXPORT //for apps
+#endif
+
+/* temporary */
+#ifndef KOPROPERTY_EXPORT
+# ifdef MAKE_KOPROPERTY_LIB
+# define KOPROPERTY_EXPORT KDE_EXPORT
+# elif defined(KDE_MAKE_LIB)
+# define KOPROPERTY_EXPORT KDE_IMPORT
+# else
+# define KOPROPERTY_EXPORT
+# endif
+#endif
+
+/* temporary */
+#ifndef KOMACRO_EXPORT
+# ifdef MAKE_KOMACRO_LIB
+# define KOMACRO_EXPORT KDE_EXPORT
+# elif defined(KDE_MAKE_LIB)
+# define KOMACRO_EXPORT KDE_IMPORT
+# else
+# define KOMACRO_EXPORT
+# endif
+#endif
+
+/* additional default options */
+#ifndef KEXI_NO_CTXT_HELP
+# define KEXI_NO_CTXT_HELP
+#endif
+# define KDE_CXXFLAGS
+
+/* -- compile-time settings -- */
+#if defined(Q_WS_WIN) || defined(KEXI_OPTIONS)
+/* defined in a .pro file or 'KEXI_OPTIONS' env. variable */
+#else
+
+/* unfinished features visibility */
+/* -- undefined because people asked why these doesn't work: --
+# define KEXI_SHOW_UNIMPLEMENTED
+# define KEXI_STARTUP_SHOW_TEMPLATES
+# define KEXI_STARTUP_SHOW_RECENT
+# define KEXI_REPORTS_SUPPORT
+*/
+# define KEXI_NO_UNFINISHED
+# define KEXI_FORMS_SUPPORT
+//# define KEXI_NO_FEEDBACK_AGENT
+#endif
+
+#endif //KEXI_EXPORT_H
diff --git a/kexi/kexi_global.h b/kexi/kexi_global.h
new file mode 100644
index 000000000..e833c7d5e
--- /dev/null
+++ b/kexi/kexi_global.h
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+ Copyright (c) 2003-2005 Kexi Team
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KEXI_GLOBAL_
+#define _KEXI_GLOBAL_
+
+/*! Only defined for standalone releases,
+ should be commented out for releases bundled with KOffice. *
+ Affects translation file names: "kexi.po" becomes "standalone_kexi.po" (used in main.cpp)
+ and "kformdesigner.po" becomes "standalone_kformdesigner.po" (used in FormManager)
+ */
+//#define KEXI_STANDALONE 1
+
+#include <kexi_export.h>
+#include <config.h>
+
+#define kexidbg kdDebug(44010) //! General debug area for Kexi
+#define kexicoredbg kdDebug(44020) //! Debug area for Kexi Core
+#define kexipluginsdbg kdDebug(44021) //! Debug area for Kexi Plugins
+#define kexiwarn kdWarning(44010)
+#define kexicorewarn kdWarning(44020)
+#define kexipluginswarn kdWarning(44021)
+
+/* useful macros */
+
+/*! a shortcut for iterating over lists or maps, eg. QMap, QValueList */
+#define foreach(_class, _variable, _list) \
+ for (_class _variable = (_list).constBegin(); _variable!=(_list).constEnd(); ++_variable)
+
+/*! nonconst version of foreach iterator */
+#define foreach_nonconst(_class, _variable, _list) \
+ for (_class _variable = (_list).begin(); _variable!=(_list).end(); ++_variable)
+
+/*! a shortcut for iterating over QPtrList and QPtrDict */
+#define foreach_list(_class, _variable, _list) \
+ for (_class _variable(_list); _variable.current(); ++_variable)
+
+#define foreach_dict(_class, _variable, _list) foreach_list(_class, _variable, _list)
+
+#ifndef futureI18n
+# ifdef USE_FUTURE_I18N
+# define futureI18n(a) QObject::tr(a)
+# define futureI18n2(a,b) QObject::tr(b)
+# else
+# define futureI18n(a) QString(a)
+# define futureI18n2(a,b) QString(b)
+# endif
+#endif
+
+#ifndef FUTURE_I18N_NOOP
+# define FUTURE_I18N_NOOP(x) (x)
+#endif
+
+#endif /* _KEXI_GLOBAL_ */
diff --git a/kexi/kexi_version.h b/kexi/kexi_version.h
new file mode 100644
index 000000000..eaf1c7ecc
--- /dev/null
+++ b/kexi/kexi_version.h
@@ -0,0 +1,96 @@
+/* This file is part of the KDE project
+ Copyright (c) 2003-2005 Kexi Team
+
+ Version information based on kofficeversion.h,
+ Copyright (c) 2003 KOffice Team
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KEXI_VERSION_
+#define _KEXI_VERSION_
+
+#ifdef CUSTOM_VERSION /* user-friendly version info */
+# include "custom_global.h"
+#else /* default */
+# define KEXI_APP_NAME "Kexi"
+#endif
+
+#ifndef KEXI_VERSION_STRING
+# define KEXI_VERSION_STRING "1.1.3"
+#endif
+
+#define KEXI_VERSION_MAJOR 1
+#define KEXI_VERSION_MINOR 1
+#define KEXI_VERSION_RELEASE 3
+
+#define KEXI_MAKE_VERSION( a,b,c ) (((a) << 16) | ((b) << 8) | (c))
+
+#define KEXI_VERSION \
+ KEXI_MAKE_VERSION(KEXI_VERSION_MAJOR,KEXI_VERSION_MINOR,KEXI_VERSION_RELEASE)
+
+#define KEXI_IS_VERSION(a,b,c) ( KEXI_VERSION >= KEXI_MAKE_VERSION(a,b,c) )
+
+/**
+ * Namespace for general Kexi functions.
+ */
+namespace Kexi
+{
+ /**
+ * Returns the encoded number of Kexi's version, see the KEXI_VERSION macro.
+ * In contrary to that macro this function returns the number of the actually
+ * installed Kexi version, not the number of the Kexi version that was
+ * installed when the program was compiled.
+ * @return the version number, encoded in a single uint
+ */
+ KEXICORE_EXPORT unsigned int version();
+ /**
+ * Returns the major number of Kexi's version, e.g.
+ * 1 for Kexi 1.2.3.
+ * @return the major version number
+ */
+ KEXICORE_EXPORT unsigned int versionMajor();
+ /**
+ * Returns the minor number of Kexi's version, e.g.
+ * 2 for Kexi 1.2.3.
+ * @return the minor version number
+ */
+ KEXICORE_EXPORT unsigned int versionMinor();
+ /**
+ * Returns the release of Kexi's version, e.g.
+ * 3 for Kexi 1.2.3.
+ * @return the release number
+ */
+ KEXICORE_EXPORT unsigned int versionRelease();
+ /**
+ * Returns the Kexi version as string, e.g. "1.2.3"
+ * Sometimes it may be even something like "1.2.3 beta 2"
+ * @return the Kexi version. You can keep the string forever
+ */
+ KEXICORE_EXPORT const char *versionString();
+}
+
+/*
+ * This is the version a part has to be only increase it if the
+ * interface isn't binary compatible anymore.
+ *
+ * Note: update X-Kexi-PartVersion values in kexi*handler.desktop
+ * files every time you are increasing this value.
+ */
+
+#define KEXI_PART_VERSION 2
+
+#endif /* _KEXI_VERSION_ */
diff --git a/kexi/kexidb/Makefile.am b/kexi/kexidb/Makefile.am
new file mode 100644
index 000000000..8b4195e2f
--- /dev/null
+++ b/kexi/kexidb/Makefile.am
@@ -0,0 +1,64 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexidb.la
+
+INCLUDES = -I$(top_srcdir)/kexi $(all_includes)
+
+SUBDIRS = . parser drivers
+
+libkexidb_la_METASOURCES = AUTO
+
+libkexidb_la_SOURCES = drivermanager.cpp driver.cpp driver_p.cpp connection.cpp \
+ keywords.cpp object.cpp field.cpp utils.cpp expression.cpp \
+ connectiondata.cpp fieldlist.cpp tableschema.cpp cursor.cpp transaction.cpp \
+ indexschema.cpp queryschemaparameter.cpp queryschema.cpp schemadata.cpp global.cpp \
+ relationship.cpp roweditbuffer.cpp msghandler.cpp \
+ dbobjectnamevalidator.cpp fieldvalidator.cpp preparedstatement.cpp \
+ dbproperties.cpp admin.cpp alter.cpp lookupfieldschema.cpp simplecommandlineapp.cpp
+
+noinst_HEADERS = drivermanager_p.h utils_p.h
+
+kexidbincludedir=$(includedir)/kexidb
+kexidbinclude_HEADERS= \
+admin.h \
+alter.h \
+connectiondata.h \
+connection.h \
+cursor.h \
+dbobjectnamevalidator.h \
+dbproperties.h \
+driver.h \
+drivermanager.h \
+driver_p.h \
+error.h \
+expression.h \
+field.h \
+fieldlist.h \
+fieldvalidator.h \
+global.h \
+indexschema.h \
+kexidb_export.h \
+lookupfieldschema.h \
+msghandler.h \
+object.h \
+preparedstatement.h \
+queryschema.h \
+queryschemaparameter.h \
+relationship.h \
+roweditbuffer.h \
+schemadata.h \
+simplecommandlineapp.h \
+tableschema.h \
+transaction.h \
+utils.h \
+parser/parser.h
+
+libkexidb_la_LIBADD = $(LIB_QT) $(LIB_KDECORE) $(LIB_KIO) \
+ $(top_builddir)/kexi/kexiutils/libkexiutils.la
+libkexidb_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO)
+
+kde_servicetypes_DATA = kexidb_driver.desktop
+
+
+KDE_CXXFLAGS += -D__KEXIDB__= -include $(top_srcdir)/kexi/kexidb/global.h
+
diff --git a/kexi/kexidb/admin.cpp b/kexi/kexidb/admin.cpp
new file mode 100644
index 000000000..5d05af9e0
--- /dev/null
+++ b/kexi/kexidb/admin.cpp
@@ -0,0 +1,42 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "admin.h"
+#include "driver_p.h"
+
+using namespace KexiDB;
+
+AdminTools::AdminTools()
+ : Object()
+ , d( new Private() )
+{
+}
+
+AdminTools::~AdminTools()
+{
+ delete d;
+}
+
+bool AdminTools::vacuum(const ConnectionData& data, const QString& databaseName)
+{
+ Q_UNUSED(data);
+ Q_UNUSED(databaseName);
+ clearError();
+ return false;
+}
diff --git a/kexi/kexidb/admin.h b/kexi/kexidb/admin.h
new file mode 100644
index 000000000..0a7a0d464
--- /dev/null
+++ b/kexi/kexidb/admin.h
@@ -0,0 +1,56 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_ADMIN_H
+#define KEXIDB_ADMIN_H
+
+#include "object.h"
+
+namespace KexiDB
+{
+class Connection;
+class ConnectionData;
+
+//! @short An interface containing a set of tools for database administration
+/*! Can be implemented in database drivers. @see Driver::adminTools
+*/
+class KEXI_DB_EXPORT AdminTools : public Object
+{
+ public:
+ AdminTools();
+ virtual ~AdminTools();
+
+ /*! Performs vacuum (compacting) for connection \a data.
+ Can be implemented for your driver.
+ Note: in most cases the database should not be opened.
+
+ Currently it is implemented for SQLite drivers.
+
+ \return true on success, false on failure
+ (then you can get error status from the AdminTools object). */
+ virtual bool vacuum(const ConnectionData& data, const QString& databaseName);
+ //virtual bool vacuum(Connection& conn);
+
+ protected:
+ class Private;
+ Private *d;
+};
+}
+
+#endif
diff --git a/kexi/kexidb/alter.cpp b/kexi/kexidb/alter.cpp
new file mode 100644
index 000000000..f894b2998
--- /dev/null
+++ b/kexi/kexidb/alter.cpp
@@ -0,0 +1,1115 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "alter.h"
+#include "utils.h"
+#include <kexiutils/utils.h>
+
+#include <qmap.h>
+
+#include <kstaticdeleter.h>
+
+#include <stdlib.h>
+
+namespace KexiDB {
+class AlterTableHandler::Private
+{
+ public:
+ Private()
+ {}
+ ~Private()
+ {}
+ ActionList actions;
+ QGuardedPtr<Connection> conn;
+};
+}
+
+using namespace KexiDB;
+
+//! a global instance used to when returning null is needed
+AlterTableHandler::ChangeFieldPropertyAction nullChangeFieldPropertyAction(true);
+AlterTableHandler::RemoveFieldAction nullRemoveFieldAction(true);
+AlterTableHandler::InsertFieldAction nullInsertFieldAction(true);
+AlterTableHandler::MoveFieldPositionAction nullMoveFieldPositionAction(true);
+
+//--------------------------------------------------------
+
+AlterTableHandler::ActionBase::ActionBase(bool null)
+ : m_alteringRequirements(0)
+ , m_order(-1)
+ , m_null(null)
+{
+}
+
+AlterTableHandler::ActionBase::~ActionBase()
+{
+}
+
+AlterTableHandler::ChangeFieldPropertyAction& AlterTableHandler::ActionBase::toChangeFieldPropertyAction()
+{
+ if (dynamic_cast<ChangeFieldPropertyAction*>(this))
+ return *dynamic_cast<ChangeFieldPropertyAction*>(this);
+ return nullChangeFieldPropertyAction;
+}
+
+AlterTableHandler::RemoveFieldAction& AlterTableHandler::ActionBase::toRemoveFieldAction()
+{
+ if (dynamic_cast<RemoveFieldAction*>(this))
+ return *dynamic_cast<RemoveFieldAction*>(this);
+ return nullRemoveFieldAction;
+}
+
+AlterTableHandler::InsertFieldAction& AlterTableHandler::ActionBase::toInsertFieldAction()
+{
+ if (dynamic_cast<InsertFieldAction*>(this))
+ return *dynamic_cast<InsertFieldAction*>(this);
+ return nullInsertFieldAction;
+}
+
+AlterTableHandler::MoveFieldPositionAction& AlterTableHandler::ActionBase::toMoveFieldPositionAction()
+{
+ if (dynamic_cast<MoveFieldPositionAction*>(this))
+ return *dynamic_cast<MoveFieldPositionAction*>(this);
+ return nullMoveFieldPositionAction;
+}
+
+//--------------------------------------------------------
+
+AlterTableHandler::FieldActionBase::FieldActionBase(const QString& fieldName, int uid)
+ : ActionBase()
+ , m_fieldUID(uid)
+ , m_fieldName(fieldName)
+{
+}
+
+AlterTableHandler::FieldActionBase::FieldActionBase(bool)
+ : ActionBase(true)
+ , m_fieldUID(-1)
+{
+}
+
+AlterTableHandler::FieldActionBase::~FieldActionBase()
+{
+}
+
+//--------------------------------------------------------
+
+static KStaticDeleter< QMap<QCString,int> > KexiDB_alteringTypeForProperty_deleter;
+QMap<QCString,int> *KexiDB_alteringTypeForProperty = 0;
+
+int AlterTableHandler::alteringTypeForProperty(const QCString& propertyName)
+{
+ if (!KexiDB_alteringTypeForProperty) {
+ KexiDB_alteringTypeForProperty_deleter.setObject( KexiDB_alteringTypeForProperty,
+ new QMap<QCString,int>() );
+#define I(name, type) \
+ KexiDB_alteringTypeForProperty->insert(QCString(name).lower(), (int)AlterTableHandler::type)
+#define I2(name, type1, type2) \
+ flag = (int)AlterTableHandler::type1|(int)AlterTableHandler::type2; \
+ if (flag & AlterTableHandler::PhysicalAlteringRequired) \
+ flag |= AlterTableHandler::MainSchemaAlteringRequired; \
+ KexiDB_alteringTypeForProperty->insert(QCString(name).lower(), flag)
+
+ /* useful links:
+ http://dev.mysql.com/doc/refman/5.0/en/create-table.html
+ */
+ // ExtendedSchemaAlteringRequired is here because when the field is renamed,
+ // we need to do the same rename in extended table schema: <field name="...">
+ int flag;
+ I2("name", PhysicalAlteringRequired, MainSchemaAlteringRequired);
+ I2("type", PhysicalAlteringRequired, DataConversionRequired);
+ I("caption", MainSchemaAlteringRequired);
+ I("description", MainSchemaAlteringRequired);
+ I2("unsigned", PhysicalAlteringRequired, DataConversionRequired); // always?
+ I2("length", PhysicalAlteringRequired, DataConversionRequired); // always?
+ I2("precision", PhysicalAlteringRequired, DataConversionRequired); // always?
+ I("width", MainSchemaAlteringRequired);
+ // defaultValue: depends on backend, for mysql it can only by a constant or now()...
+ // -- should we look at Driver here?
+#ifdef KEXI_NO_UNFINISHED
+//! @todo reenable
+ I("defaultValue", MainSchemaAlteringRequired);
+#else
+ I2("defaultValue", PhysicalAlteringRequired, MainSchemaAlteringRequired);
+#endif
+ I2("primaryKey", PhysicalAlteringRequired, DataConversionRequired);
+ I2("unique", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here
+ I2("notNull", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here
+ // allowEmpty: only support it just at kexi level? maybe there is a backend that supports this?
+ I2("allowEmpty", PhysicalAlteringRequired, MainSchemaAlteringRequired);
+ I2("autoIncrement", PhysicalAlteringRequired, DataConversionRequired); // data conversion may be hard here
+ I2("indexed", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here
+
+ // easier cases follow...
+ I("visibleDecimalPlaces", ExtendedSchemaAlteringRequired);
+
+ // lookup-field-related properties...
+/*moved to KexiDB::isExtendedTableFieldProperty()
+ I("boundColumn", ExtendedSchemaAlteringRequired);
+ I("rowSource", ExtendedSchemaAlteringRequired);
+ I("rowSourceType", ExtendedSchemaAlteringRequired);
+ I("rowSourceValues", ExtendedSchemaAlteringRequired);
+ I("visibleColumn", ExtendedSchemaAlteringRequired);
+ I("columnWidths", ExtendedSchemaAlteringRequired);
+ I("showColumnHeaders", ExtendedSchemaAlteringRequired);
+ I("listRows", ExtendedSchemaAlteringRequired);
+ I("limitToList", ExtendedSchemaAlteringRequired);
+ I("displayWidget", ExtendedSchemaAlteringRequired);*/
+
+ //more to come...
+#undef I
+#undef I2
+ }
+ const int res = (*KexiDB_alteringTypeForProperty)[propertyName.lower()];
+ if (res == 0) {
+ if (KexiDB::isExtendedTableFieldProperty(propertyName))
+ return (int)ExtendedSchemaAlteringRequired;
+ KexiDBWarn << QString("AlterTableHandler::alteringTypeForProperty(): property \"%1\" not found!")
+ .arg(propertyName) << endl;
+ }
+ return res;
+}
+
+//---
+
+AlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction(
+ const QString& fieldName, const QString& propertyName, const QVariant& newValue, int uid)
+ : FieldActionBase(fieldName, uid)
+ , m_propertyName(propertyName)
+ , m_newValue(newValue)
+{
+}
+
+AlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction(bool)
+ : FieldActionBase(true)
+{
+}
+
+AlterTableHandler::ChangeFieldPropertyAction::~ChangeFieldPropertyAction()
+{
+}
+
+void AlterTableHandler::ChangeFieldPropertyAction::updateAlteringRequirements()
+{
+// m_alteringRequirements = ???;
+ setAlteringRequirements( alteringTypeForProperty( m_propertyName.latin1() ) );
+}
+
+QString AlterTableHandler::ChangeFieldPropertyAction::debugString(const DebugOptions& debugOptions)
+{
+ QString s = QString("Set \"%1\" property for table field \"%2\" to \"%3\"")
+ .arg(m_propertyName).arg(fieldName()).arg(m_newValue.toString());
+ if (debugOptions.showUID)
+ s.append(QString(" (UID=%1)").arg(m_fieldUID));
+ return s;
+}
+
+static AlterTableHandler::ActionDict* createActionDict(
+ AlterTableHandler::ActionDictDict &fieldActions, int forFieldUID )
+{
+ AlterTableHandler::ActionDict* dict = new AlterTableHandler::ActionDict(101, false);
+ dict->setAutoDelete(true);
+ fieldActions.insert( forFieldUID, dict );
+ return dict;
+}
+
+static void debugAction(AlterTableHandler::ActionBase *action, int nestingLevel,
+ bool simulate, const QString& prependString = QString::null, QString* debugTarget = 0)
+{
+ QString debugString;
+ if (!debugTarget)
+ debugString = prependString;
+ if (action) {
+ AlterTableHandler::ActionBase::DebugOptions debugOptions;
+ debugOptions.showUID = debugTarget==0;
+ debugOptions.showFieldDebug = debugTarget!=0;
+ debugString += action->debugString( debugOptions );
+ }
+ else {
+ if (!debugTarget)
+ debugString += "[No action]"; //hmm
+ }
+ if (debugTarget) {
+ if (!debugString.isEmpty())
+ *debugTarget += debugString + '\n';
+ }
+ else {
+ KexiDBDbg << debugString << endl;
+#ifdef KEXI_DEBUG_GUI
+ if (simulate)
+ KexiUtils::addAlterTableActionDebug(debugString, nestingLevel);
+#endif
+ }
+}
+
+static void debugActionDict(AlterTableHandler::ActionDict *dict, int fieldUID, bool simulate)
+{
+ QString fieldName;
+ AlterTableHandler::ActionDictIterator it(*dict);
+ if (dynamic_cast<AlterTableHandler::FieldActionBase*>(it.current())) //retrieve field name from the 1st related action
+ fieldName = dynamic_cast<AlterTableHandler::FieldActionBase*>(it.current())->fieldName();
+ else
+ fieldName = "??";
+ QString dbg = QString("Action dict for field \"%1\" (%2, UID=%3):")
+ .arg(fieldName).arg(dict->count()).arg(fieldUID);
+ KexiDBDbg << dbg << endl;
+#ifdef KEXI_DEBUG_GUI
+ if (simulate)
+ KexiUtils::addAlterTableActionDebug(dbg, 1);
+#endif
+ for (;it.current(); ++it) {
+ debugAction(it.current(), 2, simulate);
+ }
+}
+
+static void debugFieldActions(const AlterTableHandler::ActionDictDict &fieldActions, bool simulate)
+{
+#ifdef KEXI_DEBUG_GUI
+ if (simulate)
+ KexiUtils::addAlterTableActionDebug("** Simplified Field Actions:");
+#endif
+ for (AlterTableHandler::ActionDictDictIterator it(fieldActions); it.current(); ++it) {
+ debugActionDict(it.current(), it.currentKey(), simulate);
+ }
+}
+
+/*!
+ Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation.
+ Case 1. (special)
+ when new action=[rename A to B]
+ and exists=[rename B to C]
+ =>
+ remove [rename B to C]
+ and set result to new [rename A to C]
+ and go to 1b.
+ Case 1b. when new action=[rename A to B]
+ and actions exist like [set property P to C in field B]
+ or like [delete field B]
+ or like [move field B]
+ =>
+ change B to A for all these actions
+ Case 2. when new action=[change property in field A] (property != name)
+ and exists=[remove A] or exists=[change property in field A]
+ =>
+ do not add [change property in field A] because it will be removed anyway or the property will change
+*/
+void AlterTableHandler::ChangeFieldPropertyAction::simplifyActions(ActionDictDict &fieldActions)
+{
+ ActionDict *actionsLikeThis = fieldActions[ uid() ];
+ if (m_propertyName=="name") {
+ // Case 1. special: name1 -> name2, i.e. rename action
+ QString newName( newValue().toString() );
+ // try to find rename(newName, otherName) action
+ ActionBase *renameActionLikeThis = actionsLikeThis ? actionsLikeThis->find( "name" ) : 0;
+ if (dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)) {
+ // 1. instead of having rename(fieldName(), newValue()) action,
+ // let's have rename(fieldName(), otherName) action
+ dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue
+ = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue;
+/* AlterTableHandler::ChangeFieldPropertyAction* newRenameAction
+ = new AlterTableHandler::ChangeFieldPropertyAction( *this );
+ newRenameAction->m_newValue = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue;
+ // (m_order is the same as in newAction)
+ // replace prev. rename action (if any)
+ actionsLikeThis->remove( "name" );
+ ActionDict *adict = fieldActions[ fieldName().latin1() ];
+ if (!adict)
+ adict = createActionDict( fieldActions, fieldName() );
+ adict->insert(m_propertyName.latin1(), newRenameAction);*/
+ }
+ else {
+ ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->find( ":remove:" ) : 0;
+ if (removeActionForThisField) {
+ //if this field is going to be removed, jsut change the action's field name
+ // and do not add a new action
+ }
+ else {
+ //just insert a copy of the rename action
+ if (!actionsLikeThis)
+ actionsLikeThis = createActionDict( fieldActions, uid() ); //fieldName() );
+ AlterTableHandler::ChangeFieldPropertyAction* newRenameAction
+ = new AlterTableHandler::ChangeFieldPropertyAction( *this );
+ KexiDBDbg << "ChangeFieldPropertyAction::simplifyActions(): insert into '"
+ << fieldName() << "' dict:" << newRenameAction->debugString() << endl;
+ actionsLikeThis->insert( m_propertyName.latin1(), newRenameAction );
+ return;
+ }
+ }
+ if (actionsLikeThis) {
+ // Case 1b. change "field name" information to fieldName() in any action that
+ // is related to newName
+ // e.g. if there is setCaption("B", "captionA") action after rename("A","B"),
+ // replace setCaption action with setCaption("A", "captionA")
+ foreach_dict (ActionDictIterator, it, *actionsLikeThis) {
+ dynamic_cast<FieldActionBase*>(it.current())->setFieldName( fieldName() );
+ }
+ }
+ return;
+ }
+ ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->find( ":remove:" ) : 0;
+ if (removeActionForThisField) {
+ //if this field is going to be removed, do not add a new action
+ return;
+ }
+ // Case 2. other cases: just give up with adding this "intermediate" action
+ // so, e.g. [ setCaption(A, "captionA"), setCaption(A, "captionB") ]
+ // becomes: [ setCaption(A, "captionB") ]
+ // because adding this action does nothing
+ ActionDict *nextActionsLikeThis = fieldActions[ uid() ]; //fieldName().latin1() ];
+ if (!nextActionsLikeThis || !nextActionsLikeThis->find( m_propertyName.latin1() )) {
+ //no such action, add this
+ AlterTableHandler::ChangeFieldPropertyAction* newAction
+ = new AlterTableHandler::ChangeFieldPropertyAction( *this );
+ if (!nextActionsLikeThis)
+ nextActionsLikeThis = createActionDict( fieldActions, uid() );//fieldName() );
+ nextActionsLikeThis->insert( m_propertyName.latin1(), newAction );
+ }
+}
+
+bool AlterTableHandler::ChangeFieldPropertyAction::shouldBeRemoved(ActionDictDict &fieldActions)
+{
+ Q_UNUSED(fieldActions);
+ return fieldName().lower() == m_newValue.toString().lower();
+}
+
+tristate AlterTableHandler::ChangeFieldPropertyAction::updateTableSchema(TableSchema &table, Field* field,
+ QMap<QString, QString>& fieldMap)
+{
+ //1. Simpler cases first: changes that do not affect table schema at all
+ // "caption", "description", "width", "visibleDecimalPlaces"
+ if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.latin1())) {
+ bool result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue());
+ return result;
+ }
+
+ if (m_propertyName=="name") {
+ if (fieldMap[ field->name() ] == field->name())
+ fieldMap.remove( field->name() );
+ fieldMap.insert( newValue().toString(), field->name() );
+ table.renameField(field, newValue().toString());
+ return true;
+ }
+ return cancelled;
+}
+
+/*! Many of the properties must be applied using a separate algorithm.
+*/
+tristate AlterTableHandler::ChangeFieldPropertyAction::execute(Connection &conn, TableSchema &table)
+{
+ Q_UNUSED(conn);
+ Field *field = table.field( fieldName() );
+ if (!field) {
+ //! @todo errmsg
+ return false;
+ }
+ bool result;
+ //1. Simpler cases first: changes that do not affect table schema at all
+ // "caption", "description", "width", "visibleDecimalPlaces"
+ if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.latin1())) {
+ result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue());
+ return result;
+ }
+
+//todo
+return true;
+
+ //2. Harder cases, that often require special care
+ if (m_propertyName=="name") {
+ /*mysql:
+ A. Get real field type (it's safer):
+ let <TYPE> be the 2nd "Type" column from result of "DESCRIBE tablename oldfieldname"
+ ( http://dev.mysql.com/doc/refman/5.0/en/describe.html )
+ B. Run "ALTER TABLE tablename CHANGE oldfieldname newfieldname <TYPE>";
+ ( http://dev.mysql.com/doc/refman/5.0/en/alter-table.html )
+ */
+ }
+ if (m_propertyName=="type") {
+ /*mysql:
+ A. Like A. for "name" property above
+ B. Construct <TYPE> string, eg. "varchar(50)" using the driver
+ C. Like B. for "name" property above
+ (mysql then truncate the values for changes like varchar -> integer,
+ and properly convert the values for changes like integer -> varchar)
+
+ TODO: more cases to check
+ */
+ }
+ if (m_propertyName=="length") {
+ //use "select max( length(o_name) ) from kexi__Objects"
+
+ }
+ if (m_propertyName=="primaryKey") {
+//! @todo
+ }
+
+/*
+ "name", "unsigned", "precision",
+ "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty",
+ "autoIncrement", "indexed",
+
+
+ bool result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue());
+*/
+ return result;
+}
+
+//--------------------------------------------------------
+
+AlterTableHandler::RemoveFieldAction::RemoveFieldAction(const QString& fieldName, int uid)
+ : FieldActionBase(fieldName, uid)
+{
+}
+
+AlterTableHandler::RemoveFieldAction::RemoveFieldAction(bool)
+ : FieldActionBase(true)
+{
+}
+
+AlterTableHandler::RemoveFieldAction::~RemoveFieldAction()
+{
+}
+
+void AlterTableHandler::RemoveFieldAction::updateAlteringRequirements()
+{
+//! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ?
+
+ setAlteringRequirements( PhysicalAlteringRequired );
+ //! @todo
+}
+
+QString AlterTableHandler::RemoveFieldAction::debugString(const DebugOptions& debugOptions)
+{
+ QString s = QString("Remove table field \"%1\"").arg(fieldName());
+ if (debugOptions.showUID)
+ s.append(QString(" (UID=%1)").arg(uid()));
+ return s;
+}
+
+/*!
+ Legend: A,B==objects, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation.
+ Preconditions: we assume there cannot be such case encountered: ([remove A], [do something related on A])
+ (except for [remove A], [insert A])
+ General Case: it's safe to always insert a [remove A] action.
+*/
+void AlterTableHandler::RemoveFieldAction::simplifyActions(ActionDictDict &fieldActions)
+{
+ //! @todo not checked
+ AlterTableHandler::RemoveFieldAction* newAction
+ = new AlterTableHandler::RemoveFieldAction( *this );
+ ActionDict *actionsLikeThis = fieldActions[ uid() ]; //fieldName().latin1() ];
+ if (!actionsLikeThis)
+ actionsLikeThis = createActionDict( fieldActions, uid() ); //fieldName() );
+ actionsLikeThis->insert( ":remove:", newAction ); //special
+}
+
+tristate AlterTableHandler::RemoveFieldAction::updateTableSchema(TableSchema &table, Field* field,
+ QMap<QString, QString>& fieldMap)
+{
+ fieldMap.remove( field->name() );
+ table.removeField(field);
+ return true;
+}
+
+tristate AlterTableHandler::RemoveFieldAction::execute(Connection& conn, TableSchema& table)
+{
+ Q_UNUSED(conn);
+ Q_UNUSED(table);
+ //! @todo
+ return true;
+}
+
+//--------------------------------------------------------
+
+AlterTableHandler::InsertFieldAction::InsertFieldAction(int fieldIndex, KexiDB::Field *field, int uid)
+ : FieldActionBase(field->name(), uid)
+ , m_index(fieldIndex)
+ , m_field(0)
+{
+ Q_ASSERT(field);
+ setField(field);
+}
+
+AlterTableHandler::InsertFieldAction::InsertFieldAction(const InsertFieldAction& action)
+ : FieldActionBase(action) //action.fieldName(), action.uid())
+ , m_index(action.index())
+{
+ m_field = new KexiDB::Field( action.field() );
+}
+
+AlterTableHandler::InsertFieldAction::InsertFieldAction(bool)
+ : FieldActionBase(true)
+ , m_index(0)
+ , m_field(0)
+{
+}
+
+AlterTableHandler::InsertFieldAction::~InsertFieldAction()
+{
+ delete m_field;
+}
+
+void AlterTableHandler::InsertFieldAction::setField(KexiDB::Field* field)
+{
+ if (m_field)
+ delete m_field;
+ m_field = field;
+ setFieldName(m_field ? m_field->name() : QString::null);
+}
+
+void AlterTableHandler::InsertFieldAction::updateAlteringRequirements()
+{
+//! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ?
+
+ setAlteringRequirements( PhysicalAlteringRequired );
+ //! @todo
+}
+
+QString AlterTableHandler::InsertFieldAction::debugString(const DebugOptions& debugOptions)
+{
+ QString s = QString("Insert table field \"%1\" at position %2")
+ .arg(m_field->name()).arg(m_index);
+ if (debugOptions.showUID)
+ s.append(QString(" (UID=%1)").arg(m_fieldUID));
+ if (debugOptions.showFieldDebug)
+ s.append(QString(" (%1)").arg(m_field->debugString()));
+ return s;
+}
+
+/*!
+ Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation.
+
+
+ Case 1: there are "change property" actions after the Insert action.
+ -> change the properties in the Insert action itself and remove the "change property" actions.
+ Examples:
+ [Insert A] && [rename A to B] => [Insert B]
+ [Insert A] && [change property P in field A] => [Insert A with P altered]
+ Comment: we need to do this reduction because otherwise we'd need to do psyhical altering
+ right after [Insert A] if [rename A to B] follows.
+*/
+void AlterTableHandler::InsertFieldAction::simplifyActions(ActionDictDict &fieldActions)
+{
+ // Try to find actions related to this action
+ ActionDict *actionsForThisField = fieldActions[ uid() ]; //m_field->name().latin1() ];
+
+ ActionBase *removeActionForThisField = actionsForThisField ? actionsForThisField->find( ":remove:" ) : 0;
+ if (removeActionForThisField) {
+ //if this field is going to be removed, do not add a new action
+ //and remove the "Remove" action
+ actionsForThisField->remove(":remove:");
+ return;
+ }
+ if (actionsForThisField) {
+ //collect property values that have to be changed in this field
+ QMap<QCString, QVariant> values;
+ for (ActionDictIterator it(*actionsForThisField); it.current();) {
+ ChangeFieldPropertyAction* changePropertyAction = dynamic_cast<ChangeFieldPropertyAction*>(it.current());
+ if (changePropertyAction) {
+ //if this field is going to be renamed, also update fieldName()
+ if (changePropertyAction->propertyName()=="name") {
+ setFieldName(changePropertyAction->newValue().toString());
+ }
+ values.insert( changePropertyAction->propertyName().latin1(), changePropertyAction->newValue() );
+ //the subsequent "change property" action is no longer needed
+ actionsForThisField->remove(changePropertyAction->propertyName().latin1());
+ }
+ else {
+ ++it;
+ }
+ }
+ if (!values.isEmpty()) {
+ //update field, so it will be created as one step
+ KexiDB::Field *f = new KexiDB::Field( field() );
+ if (KexiDB::setFieldProperties( *f, values )) {
+ //field() = f;
+ setField( f );
+ field().debug();
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(
+ QString("** Property-set actions moved to field definition itself:\n")+field().debugString(), 0);
+#endif
+ }
+ else {
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(
+ QString("** Failed to set properties for field ")+field().debugString(), 0);
+#endif
+ KexiDBWarn << "AlterTableHandler::InsertFieldAction::simplifyActions(): KexiDB::setFieldProperties() failed!" << endl;
+ delete f;
+ }
+ }
+ }
+ //ok, insert this action
+ //! @todo not checked
+ AlterTableHandler::InsertFieldAction* newAction
+ = new AlterTableHandler::InsertFieldAction( *this );
+ if (!actionsForThisField)
+ actionsForThisField = createActionDict( fieldActions, uid() );
+ actionsForThisField->insert( ":insert:", newAction ); //special
+}
+
+tristate AlterTableHandler::InsertFieldAction::updateTableSchema(TableSchema &table, Field* field,
+ QMap<QString, QString>& fieldMap)
+{
+ //in most cases we won't add the field to fieldMap
+ Q_UNUSED(field);
+//! @todo add it only when there should be fixed value (e.g. default) set for this new field...
+ fieldMap.remove( this->field().name() );
+ table.insertField(index(), new Field(this->field()));
+ return true;
+}
+
+tristate AlterTableHandler::InsertFieldAction::execute(Connection& conn, TableSchema& table)
+{
+ Q_UNUSED(conn);
+ Q_UNUSED(table);
+ //! @todo
+ return true;
+}
+
+//--------------------------------------------------------
+
+AlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(
+ int fieldIndex, const QString& fieldName, int uid)
+ : FieldActionBase(fieldName, uid)
+ , m_index(fieldIndex)
+{
+}
+
+AlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(bool)
+ : FieldActionBase(true)
+{
+}
+
+AlterTableHandler::MoveFieldPositionAction::~MoveFieldPositionAction()
+{
+}
+
+void AlterTableHandler::MoveFieldPositionAction::updateAlteringRequirements()
+{
+ setAlteringRequirements( MainSchemaAlteringRequired );
+ //! @todo
+}
+
+QString AlterTableHandler::MoveFieldPositionAction::debugString(const DebugOptions& debugOptions)
+{
+ QString s = QString("Move table field \"%1\" to position %2")
+ .arg(fieldName()).arg(m_index);
+ if (debugOptions.showUID)
+ s.append(QString(" (UID=%1)").arg(uid()));
+ return s;
+}
+
+void AlterTableHandler::MoveFieldPositionAction::simplifyActions(ActionDictDict &fieldActions)
+{
+ Q_UNUSED(fieldActions);
+ //! @todo
+}
+
+tristate AlterTableHandler::MoveFieldPositionAction::execute(Connection& conn, TableSchema& table)
+{
+ Q_UNUSED(conn);
+ Q_UNUSED(table);
+ //! @todo
+ return true;
+}
+
+//--------------------------------------------------------
+
+AlterTableHandler::AlterTableHandler(Connection &conn)
+ : Object()
+ , d( new Private() )
+{
+ d->conn = &conn;
+}
+
+AlterTableHandler::~AlterTableHandler()
+{
+ delete d;
+}
+
+void AlterTableHandler::addAction(ActionBase* action)
+{
+ d->actions.append(action);
+}
+
+AlterTableHandler& AlterTableHandler::operator<< ( ActionBase* action )
+{
+ d->actions.append(action);
+ return *this;
+}
+
+const AlterTableHandler::ActionList& AlterTableHandler::actions() const
+{
+ return d->actions;
+}
+
+void AlterTableHandler::removeAction(int index)
+{
+ d->actions.remove( d->actions.at(index) );
+}
+
+void AlterTableHandler::clear()
+{
+ d->actions.clear();
+}
+
+void AlterTableHandler::setActions(const ActionList& actions)
+{
+ d->actions = actions;
+}
+
+void AlterTableHandler::debug()
+{
+ KexiDBDbg << "AlterTableHandler's actions:" << endl;
+ foreach_list (ActionListIterator, it, d->actions)
+ it.current()->debug();
+}
+
+TableSchema* AlterTableHandler::execute(const QString& tableName, ExecutionArguments& args)
+{
+ args.result = false;
+ if (!d->conn) {
+//! @todo err msg?
+ return 0;
+ }
+ if (d->conn->isReadOnly()) {
+//! @todo err msg?
+ return 0;
+ }
+ if (!d->conn->isDatabaseUsed()) {
+//! @todo err msg?
+ return 0;
+ }
+ TableSchema *oldTable = d->conn->tableSchema(tableName);
+ if (!oldTable) {
+//! @todo err msg?
+ return 0;
+ }
+
+ if (!args.debugString)
+ debug();
+
+ // Find a sum of requirements...
+ int allActionsCount = 0;
+ for(ActionListIterator it(d->actions); it.current(); ++it, allActionsCount++) {
+ it.current()->updateAlteringRequirements();
+ it.current()->m_order = allActionsCount;
+ }
+
+ /* Simplify actions list if possible and check for errors
+
+ How to do it?
+ - track property changes/deletions in reversed order
+ - reduce intermediate actions
+
+ Trivial example 1:
+ *action1: "rename field a to b"
+ *action2: "rename field b to c"
+ *action3: "rename field c to d"
+
+ After reduction:
+ *action1: "rename field a to d"
+ Summing up: we have tracked what happens to field curently named "d"
+ and eventually discovered that it was originally named "a".
+
+ Trivial example 2:
+ *action1: "rename field a to b"
+ *action2: "rename field b to c"
+ *action3: "remove field b"
+ After reduction:
+ *action3: "remove field b"
+ Summing up: we have noticed that field "b" has beed eventually removed
+ so we needed to find all actions related to this field and remove them.
+ This is good optimization, as some of the eventually removed actions would
+ be difficult to perform and/or costly, what would be a waste of resources
+ and a source of unwanted questions sent to the user.
+ */
+
+ ActionListIterator it(d->actions);
+
+ // Fields-related actions.
+ ActionDictDict fieldActions(3001);
+ fieldActions.setAutoDelete(true);
+ ActionBase* action;
+ for(it.toLast(); (action = it.current()); --it) {
+ action->simplifyActions( fieldActions );
+ }
+
+ if (!args.debugString)
+ debugFieldActions(fieldActions, args.simulate);
+
+ // Prepare actions for execution ----
+ // - Sort actions by order
+ ActionVector actionsVector(allActionsCount);
+ int currentActionsCount = 0; //some actions may be removed
+ args.requirements = 0;
+ QDict<char> fieldsWithChangedMainSchema(997); // Used to collect fields with changed main schema.
+ // This will be used when recreateTable is false to update kexi__fields
+ for (ActionDictDictIterator it(fieldActions); it.current(); ++it) {
+ for (AlterTableHandler::ActionDictIterator it2(*it.current());it2.current(); ++it2, currentActionsCount++) {
+ if (it2.current()->shouldBeRemoved(fieldActions))
+ continue;
+ actionsVector.insert( it2.current()->m_order, it2.current() );
+ // a sum of requirements...
+ const int r = it2.current()->alteringRequirements();
+ args.requirements |= r;
+ if (r & MainSchemaAlteringRequired && dynamic_cast<ChangeFieldPropertyAction*>(it2.current())) {
+ // Remember, this will be used when recreateTable is false to update kexi__fields, below.
+ fieldsWithChangedMainSchema.insert(
+ dynamic_cast<ChangeFieldPropertyAction*>(it2.current())->fieldName(), (char*)1 );
+ }
+ }
+ }
+ // - Debug
+ QString dbg = QString("** Overall altering requirements: %1").arg(args.requirements);
+ KexiDBDbg << dbg << endl;
+
+ if (args.onlyComputeRequirements) {
+ args.result = true;
+ return 0;
+ }
+
+ const bool recreateTable = (args.requirements & PhysicalAlteringRequired);
+
+#ifdef KEXI_DEBUG_GUI
+ if (args.simulate)
+ KexiUtils::addAlterTableActionDebug(dbg, 0);
+#endif
+ dbg = QString("** Ordered, simplified actions (%1, was %2):").arg(currentActionsCount).arg(allActionsCount);
+ KexiDBDbg << dbg << endl;
+#ifdef KEXI_DEBUG_GUI
+ if (args.simulate)
+ KexiUtils::addAlterTableActionDebug(dbg, 0);
+#endif
+ for (int i=0; i<allActionsCount; i++) {
+ debugAction(actionsVector[i], 1, args.simulate, QString("%1: ").arg(i+1), args.debugString);
+ }
+
+ if (args.requirements == 0) {//nothing to do
+ args.result = true;
+ return oldTable;
+ }
+ if (args.simulate) {//do not execute
+ args.result = true;
+ return oldTable;
+ }
+// @todo transaction!
+
+ // Create new TableSchema
+ TableSchema *newTable = recreateTable ? new TableSchema(*oldTable, false/*!copy id*/) : oldTable;
+ // find nonexisting temp name for new table schema
+ if (recreateTable) {
+ QString tempDestTableName;
+ while (true) {
+ tempDestTableName = QString("%1_temp%2%3").arg(newTable->name()).arg(QString::number(rand(), 16)).arg(QString::number(rand(), 16));
+ if (!d->conn->tableSchema(tempDestTableName))
+ break;
+ }
+ newTable->setName( tempDestTableName );
+ }
+ oldTable->debug();
+ if (recreateTable && !args.debugString)
+ newTable->debug();
+
+ // Update table schema in memory ----
+ int lastUID = -1;
+ Field *currentField = 0;
+ QMap<QString, QString> fieldMap; // a map from new value to old value
+ foreach_list( Field::ListIterator, it, newTable->fieldsIterator() ) {
+ fieldMap.insert( it.current()->name(), it.current()->name() );
+ }
+ for (int i=0; i<allActionsCount; i++) {
+ action = actionsVector[i];
+ if (!action)
+ continue;
+ //remember the current Field object because soon we may be unable to find it by name:
+ FieldActionBase *fieldAction = dynamic_cast<FieldActionBase*>(action);
+ if (!fieldAction) {
+ currentField = 0;
+ }
+ else {
+ if (lastUID != fieldAction->uid()) {
+ currentField = newTable->field( fieldAction->fieldName() );
+ lastUID = currentField ? fieldAction->uid() : -1;
+ }
+ InsertFieldAction *insertFieldAction = dynamic_cast<InsertFieldAction*>(action);
+ if (insertFieldAction && insertFieldAction->index()>(int)newTable->fieldCount()) {
+ //update index: there can be empty rows
+ insertFieldAction->setIndex(newTable->fieldCount());
+ }
+ }
+ //if (!currentField)
+ // continue;
+ args.result = action->updateTableSchema(*newTable, currentField, fieldMap);
+ if (args.result!=true) {
+ if (recreateTable)
+ delete newTable;
+ return 0;
+ }
+ }
+
+ if (recreateTable) {
+ // Create the destination table with temporary name
+ if (!d->conn->createTable( newTable, false )) {
+ setError(d->conn);
+ delete newTable;
+ args.result = false;
+ return 0;
+ }
+ }
+
+#if 0//todo
+ // Execute actions ----
+ for (int i=0; i<allActionsCount; i++) {
+ action = actionsVector[i];
+ if (!action)
+ continue;
+ args.result = action->execute(*d->conn, *newTable);
+ if (!args.result || ~args.result) {
+//! @todo delete newTable...
+ args.result = false;
+ return 0;
+ }
+ }
+#endif
+
+ // update extended table schema after executing the actions
+ if (!d->conn->storeExtendedTableSchemaData(*newTable)) {
+//! @todo better errmsg?
+ setError(d->conn);
+//! @todo delete newTable...
+ args.result = false;
+ return 0;
+ }
+
+ if (recreateTable) {
+ // Copy the data:
+ // Build "INSERT INTO ... SELECT FROM ..." SQL statement
+ // The order is based on the order of the source table fields.
+ // Notes:
+ // -Some source fields can be skipped in case when there are deleted fields.
+ // -Some destination fields can be skipped in case when there
+ // are new empty fields without fixed/default value.
+ QString sql = QString("INSERT INTO %1 (").arg(d->conn->escapeIdentifier(newTable->name()));
+ //insert list of dest. fields
+ bool first = true;
+ QString sourceFields;
+ foreach_list( Field::ListIterator, it, newTable->fieldsIterator() ) {
+ Field * const f = it.current();
+ QString renamedFieldName( fieldMap[ f->name() ] );
+ QString sourceSQLString;
+ if (!renamedFieldName.isEmpty()) {
+ //this field should be renamed
+ sourceSQLString = d->conn->escapeIdentifier(renamedFieldName);
+ }
+ else if (!f->defaultValue().isNull()) {
+ //this field has a default value defined
+//! @todo support expressions (eg. TODAY()) as a default value
+//! @todo this field can be notNull or notEmpty - check whether the default is ok
+//! (or do this checking also in the Table Designer?)
+ sourceSQLString = d->conn->driver()->valueToSQL( f->type(), f->defaultValue() );
+ }
+ else if (f->isNotNull()) {
+ //this field cannot be null
+ sourceSQLString = d->conn->driver()->valueToSQL(
+ f->type(), KexiDB::emptyValueForType( f->type() ) );
+ }
+ else if (f->isNotEmpty()) {
+ //this field cannot be empty - use any nonempty value..., e.g. " " for text or 0 for number
+ sourceSQLString = d->conn->driver()->valueToSQL(
+ f->type(), KexiDB::notEmptyValueForType( f->type() ) );
+ }
+//! @todo support unique, validatationRule, unsigned flags...
+//! @todo check for foreignKey values...
+
+ if (!sourceSQLString.isEmpty()) {
+ if (first) {
+ first = false;
+ }
+ else {
+ sql.append( ", " );
+ sourceFields.append( ", " );
+ }
+ sql.append( d->conn->escapeIdentifier( f->name() ) );
+ sourceFields.append( sourceSQLString );
+ }
+ }
+ sql.append(QString(") SELECT ") + sourceFields + " FROM " + oldTable->name());
+ KexiDBDbg << " ** " << sql << endl;
+ if (!d->conn->executeSQL( sql )) {
+ setError(d->conn);
+//! @todo delete newTable...
+ args.result = false;
+ return 0;
+ }
+
+ const QString oldTableName = oldTable->name();
+/* args.result = d->conn->dropTable( oldTable );
+ if (!args.result || ~args.result) {
+ setError(d->conn);
+//! @todo delete newTable...
+ return 0;
+ }
+ oldTable = 0;*/
+
+ // Replace the old table with the new one (oldTable will be destroyed)
+ if (!d->conn->alterTableName(*newTable, oldTableName, true /*replace*/)) {
+ setError(d->conn);
+//! @todo delete newTable...
+ args.result = false;
+ return 0;
+ }
+ oldTable = 0;
+ }
+
+ if (!recreateTable) {
+ if ((MainSchemaAlteringRequired & args.requirements) && !fieldsWithChangedMainSchema.isEmpty()) {
+ //update main schema (kexi__fields) for changed fields
+ foreach_list(QDictIterator<char>, it, fieldsWithChangedMainSchema) {
+ Field *f = newTable->field( it.currentKey() );
+ if (f) {
+ if (!d->conn->storeMainFieldSchema(f)) {
+ setError(d->conn);
+ //! @todo delete newTable...
+ args.result = false;
+ return 0;
+ }
+ }
+ }
+ }
+ }
+
+ args.result = true;
+ return newTable;
+}
+
+/*TableSchema* AlterTableHandler::execute(const QString& tableName, tristate &result, bool simulate)
+{
+ return executeInternal( tableName, result, simulate, 0 );
+}
+
+tristate AlterTableHandler::simulateExecution(const QString& tableName, QString& debugString)
+{
+ tristate result;
+ (void)executeInternal( tableName, result, true//simulate
+ , &debugString );
+ return result;
+}
+*/
diff --git a/kexi/kexidb/alter.h b/kexi/kexidb/alter.h
new file mode 100644
index 000000000..1e6d8e876
--- /dev/null
+++ b/kexi/kexidb/alter.h
@@ -0,0 +1,468 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_ALTER_H
+#define KEXIDB_ALTER_H
+
+#include "connection.h"
+
+#include <qvaluelist.h>
+#include <qasciidict.h>
+
+#include <kdebug.h>
+
+namespace KexiDB
+{
+class Connection;
+class ConnectionData;
+
+//! @short A tool for handling altering database table schema.
+/*! In relational (and other) databases, table schema altering is not an easy task.
+ It may be considered as easy if there is no data that user wants to keep while
+ the table schema is altered. Otherwise, if the table is alredy filled with data,
+ there could be no easy algorithm like:
+ 1. Drop existing table
+ 2. Create new one with altered schema.
+
+ Instead, more complex algorithm is needed. To perform the table schema alteration,
+ a list of well defined atomic operations is used as a "recipe".
+
+ 1. Look at the current data, and:
+ 1.1. analyze what values will be removed (in case of impossible conversion
+ or table field removal);
+ 1.2. analyze what values can be converted (e.g. from numeric types to text), and so on.
+ 2. Optimize the atomic actions knowing that sometimes a compilation of one action
+ and another that's opposite to the first means "do nothing". The optimization
+ is a simulating of actions' execution.
+ For example, when both action A="change field name from 'city' to 'town'"
+ and action B="change field name from 'town' to 'city'" is specified, the compilation
+ of the actions means "change field name from 'city' to 'city'", what is a NULL action.
+ On the other hand, we need to execute all the actions on the destination table
+ in proper order, and not just drop them. For the mentioned example, between actions
+ A and B there can be an action like C="change the type of field 'city' to LongText".
+ If A and B were simply removed, C would become invalid (there is no 'city' field).
+ 3. Ask user whether she agrees with the results of analysis mentioned in 1.
+ 3.2. Additionally, it may be possible to get some hints from the user, as humans usually
+ know more about logic behind the altered table schema than any machine.
+ If the user provided hints about the altering, apply them to the actions list.
+ 4. Create (empty) destination table schema with temporary name, using
+ the information collected so far.
+ 5. Copy the data from the source to destionation table. Convert values,
+ move them between fields, using the information collected.
+ 6. Remove the source table.
+ 7. Rename the destination table to the name previously assigned for the source table.
+
+ Notes:
+ * The actions 4 to 7 should be performed within a database transaction.
+ * [todo] We want to take care about database relationships as well.
+ For example, is a table field is removed, relationships related to this field should
+ be also removed (similar rules as in the Query Designer).
+ * Especially, care about primary keys and uniquess (indices). Recreate them when needed.
+ The problem could be if such analysis may require to fetch the entire table data
+ to the client side. Use "SELECT INTO" statments if possible to avoid such a treat.
+
+ The AlterTableHandler is used in Kexi's Table Designer.
+ Already opened Connection object is needed.
+
+ Use case:
+ \code
+ Connection *conn = ...
+
+ // add some actions (in reality this is performed by tracking user's actions)
+ // Actions 1, 2 will require physical table altering PhysicalAltering
+ // Action 3 will only require changes in kexi__fields
+ // Action 4 will only require changes in extended table schema written in kexi__objectdata
+ AlterTable::ActionList list;
+
+ // 1. rename the "city" field to "town"
+ list << new ChangeFieldPropertyAction("city", "name", "town")
+
+ // 2. change type of "town" field to "LongText"
+ << new ChangeFieldPropertyAction("town", "type", "LongText")
+
+ // 3. set caption of "town" field to "Town"
+ << new ChangeFieldPropertyAction("town", "caption", "Town")
+
+ // 4. set visible decimal places to 4 for "cost" field
+ << new ChangeFieldPropertyAction("cost", "visibleDecimalPlaces", 4)
+
+ AlterTableHandler::execute( *conn );
+
+ \endcode
+
+ Actions for Alter
+*/
+class KEXI_DB_EXPORT AlterTableHandler : public Object
+{
+ public:
+ class ChangeFieldPropertyAction;
+ class RemoveFieldAction;
+ class InsertFieldAction;
+ class MoveFieldPositionAction;
+
+ //! Defines flags for possible altering requirements; can be combined.
+ enum AlteringRequirements {
+ /*! Physical table altering is required; e.g. ALTER TABLE ADD COLUMN. */
+ PhysicalAlteringRequired = 1,
+
+ /*! Data conversion is required; e.g. converting integer
+ values to string after changing column type from integer to text. */
+ DataConversionRequired = 2,
+
+ /* Changes to the main table schema (in kexi__fields) required,
+ this does not require physical changes for the table;
+ e.g. changing value of the "caption" or "description" property. */
+ MainSchemaAlteringRequired = 4,
+
+ /* Only changes to extended table schema required,
+ this does not require physical changes for the table;
+ e.g. changing value of the "visibleDecimalPlaces" property
+ or any of the custom properties. */
+ ExtendedSchemaAlteringRequired = 8,
+
+ /*! Convenience flag, changes to the main or extended schema is required. */
+ SchemaAlteringRequired = ExtendedSchemaAlteringRequired | MainSchemaAlteringRequired
+ };
+
+ class ActionBase;
+ typedef QAsciiDict<ActionBase> ActionDict; //!< for collecting actions related to a single field
+ typedef QIntDict<ActionDict> ActionDictDict; //!< for collecting groups of actions by field UID
+ typedef QAsciiDictIterator<ActionBase> ActionDictIterator;
+ typedef QIntDictIterator<ActionDict> ActionDictDictIterator;
+ typedef QPtrVector<ActionBase> ActionVector; //!< for collecting actions related to a single field
+
+ //! Defines a type for action list.
+ typedef QPtrList<ActionBase> ActionList;
+
+ //! Defines a type for action list's iterator.
+ typedef QPtrListIterator<ActionBase> ActionListIterator;
+
+ //! Abstract base class used for implementing all the AlterTable actions.
+ class KEXI_DB_EXPORT ActionBase {
+ public:
+ ActionBase(bool null = false);
+ virtual ~ActionBase();
+
+ ChangeFieldPropertyAction& toChangeFieldPropertyAction();
+ RemoveFieldAction& toRemoveFieldAction();
+ InsertFieldAction& toInsertFieldAction();
+ MoveFieldPositionAction& toMoveFieldPositionAction();
+
+ //! \return true if the action is NULL; used in the Table Designer
+ //! for temporarily collecting actions that have no effect at all.
+ bool isNull() const { return m_null; }
+
+ //! Controls debug options for actions. Used in debugString() and debug().
+ class DebugOptions
+ {
+ public:
+ DebugOptions() : showUID(true), showFieldDebug(false) {}
+
+ //! true if UID should be added to the action debug string (the default)
+ bool showUID : 1;
+
+ //! true if the field associated with the action (if exists) should
+ //! be appended to the debug string (default is false)
+ bool showFieldDebug : 1;
+ };
+
+ virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()) {
+ Q_UNUSED(debugOptions); return "ActionBase"; }
+ void debug(const DebugOptions& debugOptions = DebugOptions()) {
+ KexiDBDbg << debugString(debugOptions)
+ << " (req = " << alteringRequirements() << ")" << endl; }
+
+ protected:
+ //! Sets requirements for altering; used internally by AlterTableHandler object
+ void setAlteringRequirements( int alteringRequirements )
+ { m_alteringRequirements = alteringRequirements; }
+
+ int alteringRequirements() const { return m_alteringRequirements; }
+
+ virtual void updateAlteringRequirements() {};
+
+ /*! Simplifies \a fieldActions dictionary. If this action has to be inserted
+ Into the dictionary, an ActionDict is created first and then a copy of this action
+ is inserted into it. */
+ virtual void simplifyActions(ActionDictDict &fieldActions) { Q_UNUSED(fieldActions); }
+
+ /*! After calling simplifyActions() for each action,
+ shouldBeRemoved() is called for them as an additional step.
+ This is used for ChangeFieldPropertyAction items so actions
+ that do not change property values are removed. */
+ virtual bool shouldBeRemoved(ActionDictDict &fieldActions) {
+ Q_UNUSED(fieldActions); return false; }
+
+ virtual tristate updateTableSchema(TableSchema &table, Field* field,
+ QMap<QString, QString>& fieldMap)
+ { Q_UNUSED(table); Q_UNUSED(field); Q_UNUSED(fieldMap); return true; }
+
+ private:
+ //! Performs physical execution of this action.
+ virtual tristate execute(Connection& /*conn*/, TableSchema& /*table*/) { return true; }
+
+ //! requirements for altering; used internally by AlterTableHandler object
+ int m_alteringRequirements;
+
+ //! @internal used for "simplify" algorithm
+ int m_order;
+
+ bool m_null : 1;
+
+ friend class AlterTableHandler;
+ };
+
+ //! Abstract base class used for implementing table field-related actions.
+ class KEXI_DB_EXPORT FieldActionBase : public ActionBase {
+ public:
+ FieldActionBase(const QString& fieldName, int uid);
+ FieldActionBase(bool);
+ virtual ~FieldActionBase();
+
+ //! \return field name for this action
+ QString fieldName() const { return m_fieldName; }
+
+ /*! \return field's unique identifier
+ This id is needed because in the meantime there can be more than one
+ field sharing the same name, so we need to identify them unambiguously.
+ After the (valid) altering is completed all the names will be unique.
+
+ Example scenario when user exchanged the field names:
+ 1. At the beginning: [field A], [field B]
+ 2. Rename the 1st field to B: [field B], [field B]
+ 3. Rename the 2nd field to A: [field B], [field A] */
+ int uid() const { return m_fieldUID; }
+
+ //! Sets field name for this action
+ void setFieldName(const QString& fieldName) { m_fieldName = fieldName; }
+
+ protected:
+
+ //! field's unique identifier, @see uid()
+ int m_fieldUID;
+ private:
+ QString m_fieldName;
+ };
+
+ /*! Defines an action for changing a single property value of a table field.
+ Supported properties are currently:
+ "name", "type", "caption", "description", "unsigned", "length", "precision",
+ "width", "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty",
+ "autoIncrement", "indexed", "visibleDecimalPlaces"
+
+ More to come.
+ */
+ class KEXI_DB_EXPORT ChangeFieldPropertyAction : public FieldActionBase {
+ public:
+ ChangeFieldPropertyAction(const QString& fieldName,
+ const QString& propertyName, const QVariant& newValue, int uid);
+ //! @internal, used for constructing null action
+ ChangeFieldPropertyAction(bool null);
+ virtual ~ChangeFieldPropertyAction();
+
+ QString propertyName() const { return m_propertyName; }
+ QVariant newValue() const { return m_newValue; }
+ virtual QString debugString(const DebugOptions& debugOptions = DebugOptions());
+
+ virtual void simplifyActions(ActionDictDict &fieldActions);
+
+ virtual bool shouldBeRemoved(ActionDictDict &fieldActions);
+
+ virtual tristate updateTableSchema(TableSchema &table, Field* field,
+ QMap<QString, QString>& fieldMap);
+
+ protected:
+ virtual void updateAlteringRequirements();
+
+ //! Performs physical execution of this action.
+ virtual tristate execute(Connection &conn, TableSchema &table);
+
+ QString m_propertyName;
+ QVariant m_newValue;
+ };
+
+ //! Defines an action for removing a single table field.
+ class KEXI_DB_EXPORT RemoveFieldAction : public FieldActionBase {
+ public:
+ RemoveFieldAction(const QString& fieldName, int uid);
+ RemoveFieldAction(bool);
+ virtual ~RemoveFieldAction();
+
+ virtual QString debugString(const DebugOptions& debugOptions = DebugOptions());
+
+ virtual void simplifyActions(ActionDictDict &fieldActions);
+
+ virtual tristate updateTableSchema(TableSchema &table, Field* field,
+ QMap<QString, QString>& fieldMap);
+
+ protected:
+ virtual void updateAlteringRequirements();
+
+ //! Performs physical execution of this action.
+ virtual tristate execute(Connection &conn, TableSchema &table);
+ };
+
+ //! Defines an action for inserting a single table field.
+ class KEXI_DB_EXPORT InsertFieldAction : public FieldActionBase {
+ public:
+ InsertFieldAction(int fieldIndex, KexiDB::Field *newField, int uid);
+ //copy ctor
+ InsertFieldAction(const InsertFieldAction& action);
+ InsertFieldAction(bool);
+ virtual ~InsertFieldAction();
+
+ int index() const { return m_index; }
+ void setIndex( int index ) { m_index = index; }
+ KexiDB::Field& field() const { return *m_field; }
+ void setField(KexiDB::Field* field);
+ virtual QString debugString(const DebugOptions& debugOptions = DebugOptions());
+
+ virtual void simplifyActions(ActionDictDict &fieldActions);
+
+ virtual tristate updateTableSchema(TableSchema &table, Field* field,
+ QMap<QString, QString>& fieldMap);
+
+ protected:
+ virtual void updateAlteringRequirements();
+
+ //! Performs physical execution of this action.
+ virtual tristate execute(Connection &conn, TableSchema &table);
+
+ int m_index;
+
+ private:
+ KexiDB::Field *m_field;
+ };
+
+ /*! Defines an action for moving a single table field to a different
+ position within table schema. */
+ class KEXI_DB_EXPORT MoveFieldPositionAction : public FieldActionBase {
+ public:
+ MoveFieldPositionAction(int fieldIndex, const QString& fieldName, int uid);
+ MoveFieldPositionAction(bool);
+ virtual ~MoveFieldPositionAction();
+
+ int index() const { return m_index; }
+ virtual QString debugString(const DebugOptions& debugOptions = DebugOptions());
+
+ virtual void simplifyActions(ActionDictDict &fieldActions);
+
+ protected:
+ virtual void updateAlteringRequirements();
+
+ //! Performs physical execution of this action.
+ virtual tristate execute(Connection &conn, TableSchema &table);
+
+ int m_index;
+ };
+
+ AlterTableHandler(Connection &conn);
+
+ virtual ~AlterTableHandler();
+
+ /*! Appends \a action for the alter table tool. */
+ void addAction(ActionBase* action);
+
+ /*! Provided for convenience, @see addAction(const ActionBase& action). */
+ AlterTableHandler& operator<< ( ActionBase* action );
+
+ /*! Removes an action from the alter table tool at index \a index. */
+ void removeAction(int index);
+
+ /*! Removes all actions from the alter table tool. */
+ void clear();
+
+ /*! Sets \a actions for the alter table tool. Previous actions are cleared.
+ \a actions will be owned by the AlterTableHandler object. */
+ void setActions(const ActionList& actions);
+
+ /*! \return a list of actions for this AlterTable object.
+ Use ActionBase::ListIterator to iterate over the list items. */
+ const ActionList& actions() const;
+
+ //! Arguments for AlterTableHandler::execute().
+ class ExecutionArguments {
+ public:
+ ExecutionArguments()
+ : debugString(0)
+ , requirements(0)
+ , result(false)
+ , simulate(false)
+ , onlyComputeRequirements(false)
+ {
+ }
+ /*! If not 0, debug is directed here. Used only in the alter table test suite. */
+ QString* debugString;
+ /*! Requrements computed, a combination of AlteringRequirements values. */
+ int requirements;
+ /*! Set to true on success, to false on failure. */
+ tristate result;
+ /*! Used only in the alter table test suite. */
+ bool simulate : 1;
+ /*! Set to true if requirements should be computed
+ and the execute() method should return afterwards. */
+ bool onlyComputeRequirements;
+ };
+
+ /*! Performs table alteration using predefined actions for table named \a tableName,
+ assuming it already exists. The Connection object passed to the constructor must exist,
+ must be connected and a database must be used. The connection must not be read-only.
+
+ If args.simulate is true, the execution is only simulated, i.e. al lactions are processed
+ like for regular execution but no changes are performed physically.
+ This mode is used only for debugging purposes.
+
+ @todo For some cases, table schema can completely change, so it will be needed
+ to refresh all objects depending on it.
+ Implement this!
+
+ Sets args.result to true on success, to false on failure or when the above requirements are not met
+ (then, you can get a detailed error message from KexiDB::Object).
+ When the action has been cancelled (stopped), args.result is set to cancelled value.
+ If args.debugString is not 0, it will be filled with debugging output.
+ \return the new table schema object created as a result of schema altering.
+ The old table is returned if recreating table schema was not necessary or args.simulate is true.
+ 0 is returned if args.result is not true. */
+ TableSchema* execute(const QString& tableName, ExecutionArguments & args);
+
+ //! Displays debug information about all actions collected by the handler.
+ void debug();
+
+ /*! Like execute() with simulate set to true, but debug is directed to debugString.
+ This function is used only in the alter table test suite. */
+// tristate simulateExecution(const QString& tableName, QString& debugString);
+
+ /*! Helper. \return a combination of AlteringRequirements values decribing altering type required
+ when a given property field's \a propertyName is altered.
+ Used internally AlterTableHandler. Moreover it can be also used in the Table Designer's code
+ as a temporary replacement before AlterTableHandler is fully implemented.
+ Thus, it is possible to identify properties that have no PhysicalAlteringRequired flag set
+ (e.g. caption or extended properties like visibleDecimalPlaces. */
+ static int alteringTypeForProperty(const QCString& propertyName);
+
+ protected:
+// TableSchema* executeInternal(const QString& tableName, tristate& result, bool simulate = false,
+// QString* debugString = 0);
+
+ class Private;
+ Private *d;
+};
+}
+
+#endif
diff --git a/kexi/kexidb/common.pro b/kexi/kexidb/common.pro
new file mode 100644
index 000000000..18235e2f9
--- /dev/null
+++ b/kexi/kexidb/common.pro
@@ -0,0 +1,8 @@
+# kexidb global rules
+
+include( $(KEXI)/common.pro )
+
+win32:DEFINES += __KEXIDB__
+
+win32:QMAKE_CXXFLAGS += /FI$(KEXI)/kexidb/global.h
+win32:QMAKE_CFLAGS += /FI$(KEXI)/kexidb/global.h
diff --git a/kexi/kexidb/connection.cpp b/kexi/kexidb/connection.cpp
new file mode 100644
index 000000000..1a401a8ab
--- /dev/null
+++ b/kexi/kexidb/connection.cpp
@@ -0,0 +1,3552 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/connection.h>
+
+#include "error.h"
+#include "connection_p.h"
+#include "connectiondata.h"
+#include "driver.h"
+#include "driver_p.h"
+#include "schemadata.h"
+#include "tableschema.h"
+#include "relationship.h"
+#include "transaction.h"
+#include "cursor.h"
+#include "global.h"
+#include "roweditbuffer.h"
+#include "utils.h"
+#include "dbproperties.h"
+#include "lookupfieldschema.h"
+#include "parser/parser.h"
+
+#include <kexiutils/utils.h>
+#include <kexiutils/identifier.h>
+
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qguardedptr.h>
+#include <qdom.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#define KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION 1
+
+//#define KEXIDB_LOOKUP_FIELD_TEST
+
+namespace KexiDB {
+
+Connection::SelectStatementOptions::SelectStatementOptions()
+ : identifierEscaping(Driver::EscapeDriver|Driver::EscapeAsNecessary)
+ , alsoRetrieveROWID(false)
+ , addVisibleLookupColumns(true)
+{
+}
+
+Connection::SelectStatementOptions::~SelectStatementOptions()
+{
+}
+
+//================================================
+
+ConnectionInternal::ConnectionInternal(Connection *conn)
+ : connection(conn)
+{
+}
+
+ConnectionInternal::~ConnectionInternal()
+{
+}
+
+//================================================
+//! @internal
+class ConnectionPrivate
+{
+ public:
+ ConnectionPrivate(Connection* const conn, ConnectionData &conn_data)
+ : conn(conn)
+ , conn_data(&conn_data)
+ , tableSchemaChangeListeners(101)
+ , m_parser(0)
+ , tables_byname(101, false)
+ , queries_byname(101, false)
+ , kexiDBSystemTables(101)
+ , dont_remove_transactions(false)
+ , skip_databaseExists_check_in_useDatabase(false)
+ , default_trans_started_inside(false)
+ , isConnected(false)
+ , autoCommit(true)
+ {
+ tableSchemaChangeListeners.setAutoDelete(true);
+ obsoleteQueries.setAutoDelete(true);
+
+ tables.setAutoDelete(true);
+ tables_byname.setAutoDelete(false);//tables is owner, not me
+ kexiDBSystemTables.setAutoDelete(true);//only system tables
+ queries.setAutoDelete(true);
+ queries_byname.setAutoDelete(false);//queries is owner, not me
+
+ //reasonable sizes: TODO
+ tables.resize(101);
+ queries.resize(101);
+ }
+ ~ConnectionPrivate()
+ {
+ delete m_parser;
+ }
+
+ void errorInvalidDBContents(const QString& details) {
+ conn->setError( ERR_INVALID_DATABASE_CONTENTS, i18n("Invalid database contents. ")+details);
+ }
+
+ QString strItIsASystemObject() const {
+ return i18n("It is a system object.");
+ }
+
+ inline Parser *parser() { return m_parser ? m_parser : (m_parser = new Parser(conn)); }
+
+ Connection* const conn; //!< The \a Connection instance this \a ConnectionPrivate belongs to.
+ QGuardedPtr<ConnectionData> conn_data; //!< the \a ConnectionData used within that connection.
+
+ /*! Default transaction handle.
+ If transactions are supported: Any operation on database (e.g. inserts)
+ that is started without specifying transaction context, will be performed
+ in the context of this transaction. */
+ Transaction default_trans;
+ QValueList<Transaction> transactions;
+
+ QPtrDict< QPtrList<Connection::TableSchemaChangeListenerInterface> > tableSchemaChangeListeners;
+
+ //! Used in Connection::setQuerySchemaObsolete( const QString& queryName )
+ //! to collect obsolete queries. THese are deleted on connection deleting.
+ QPtrList<QuerySchema> obsoleteQueries;
+
+
+ //! server version information for this connection.
+ KexiDB::ServerVersionInfo serverVersion;
+
+ //! Daabase version information for this connection.
+ KexiDB::DatabaseVersionInfo databaseVersion;
+
+ Parser *m_parser;
+
+ //! Table schemas retrieved on demand with tableSchema()
+ QIntDict<TableSchema> tables;
+ QDict<TableSchema> tables_byname;
+ QIntDict<QuerySchema> queries;
+ QDict<QuerySchema> queries_byname;
+
+ //! used just for removing system TableSchema objects on db close.
+ QPtrDict<TableSchema> kexiDBSystemTables;
+
+ //! Database properties
+ DatabaseProperties* dbProperties;
+
+ QString availableDatabaseName; //!< used by anyAvailableDatabaseName()
+ QString usedDatabase; //!< database name that is opened now (the currentDatabase() name)
+
+ //! true if rollbackTransaction() and commitTransaction() shouldn't remove
+ //! the transaction object from 'transactions' list; used by closeDatabase()
+ bool dont_remove_transactions : 1;
+
+ //! used to avoid endless recursion between useDatabase() and databaseExists()
+ //! when useTemporaryDatabaseIfNeeded() works
+ bool skip_databaseExists_check_in_useDatabase : 1;
+
+ /*! Used when single transactions are only supported (Driver::SingleTransactions).
+ True value means default transaction has been started inside connection object
+ (by beginAutoCommitTransaction()), otherwise default transaction has been started outside
+ of the object (e.g. before createTable()), so we shouldn't autocommit the transaction
+ in commitAutoCommitTransaction(). Also, beginAutoCommitTransaction() doesn't restarts
+ transaction if default_trans_started_inside is false. Such behaviour allows user to
+ execute a sequence of actions like CREATE TABLE...; INSERT DATA...; within a single transaction
+ and commit it or rollback by hand. */
+ bool default_trans_started_inside : 1;
+
+ bool isConnected : 1;
+
+ bool autoCommit : 1;
+
+ /*! True for read only connection. Used especially for file-based drivers. */
+ bool readOnly : 1;
+};
+
+}//namespace KexiDB
+
+//================================================
+using namespace KexiDB;
+
+//! static: list of internal KexiDB system table names
+QStringList KexiDB_kexiDBSystemTableNames;
+
+Connection::Connection( Driver *driver, ConnectionData &conn_data )
+ : QObject()
+ ,KexiDB::Object()
+ ,d(new ConnectionPrivate(this, conn_data))
+ ,m_driver(driver)
+ ,m_destructor_started(false)
+{
+ d->dbProperties = new DatabaseProperties(this);
+ m_cursors.setAutoDelete(true);
+// d->transactions.setAutoDelete(true);
+ //reasonable sizes: TODO
+ m_cursors.resize(101);
+// d->transactions.resize(101);//woohoo! so many transactions?
+ m_sql.reserve(0x4000);
+}
+
+void Connection::destroy()
+{
+ disconnect();
+ //do not allow the driver to touch me: I will kill myself.
+ m_driver->d->connections.take( this );
+}
+
+Connection::~Connection()
+{
+ m_destructor_started = true;
+// KexiDBDbg << "Connection::~Connection()" << endl;
+ delete d->dbProperties;
+ delete d;
+ d = 0;
+/* if (m_driver) {
+ if (m_is_connected) {
+ //delete own table schemas
+ d->tables.clear();
+ //delete own cursors:
+ m_cursors.clear();
+ }
+ //do not allow the driver to touch me: I will kill myself.
+ m_driver->m_connections.take( this );
+ }*/
+}
+
+ConnectionData* Connection::data() const
+{
+ return d->conn_data;
+}
+
+bool Connection::connect()
+{
+ clearError();
+ if (d->isConnected) {
+ setError(ERR_ALREADY_CONNECTED, i18n("Connection already established.") );
+ return false;
+ }
+
+ d->serverVersion.clear();
+ if (!(d->isConnected = drv_connect(d->serverVersion))) {
+ setError(m_driver->isFileDriver() ?
+ i18n("Could not open \"%1\" project file.").arg(QDir::convertSeparators(d->conn_data->fileName()))
+ : i18n("Could not connect to \"%1\" database server.").arg(d->conn_data->serverInfoString()) );
+ }
+ return d->isConnected;
+}
+
+bool Connection::isDatabaseUsed() const
+{
+ return !d->usedDatabase.isEmpty() && d->isConnected && drv_isDatabaseUsed();
+}
+
+void Connection::clearError()
+{
+ Object::clearError();
+ m_sql = QString::null;
+}
+
+bool Connection::disconnect()
+{
+ clearError();
+ if (!d->isConnected)
+ return true;
+
+ if (!closeDatabase())
+ return false;
+
+ bool ok = drv_disconnect();
+ if (ok)
+ d->isConnected = false;
+ return ok;
+}
+
+bool Connection::isConnected() const
+{
+ return d->isConnected;
+}
+
+bool Connection::checkConnected()
+{
+ if (d->isConnected) {
+ clearError();
+ return true;
+ }
+ setError(ERR_NO_CONNECTION, i18n("Not connected to the database server.") );
+ return false;
+}
+
+bool Connection::checkIsDatabaseUsed()
+{
+ if (isDatabaseUsed()) {
+ clearError();
+ return true;
+ }
+ setError(ERR_NO_DB_USED, i18n("Currently no database is used.") );
+ return false;
+}
+
+QStringList Connection::databaseNames(bool also_system_db)
+{
+ KexiDBDbg << "Connection::databaseNames("<<also_system_db<<")"<< endl;
+ if (!checkConnected())
+ return QStringList();
+
+ QString tmpdbName;
+ //some engines need to have opened any database before executing "create database"
+ if (!useTemporaryDatabaseIfNeeded(tmpdbName))
+ return QStringList();
+
+ QStringList list, non_system_list;
+
+ bool ret = drv_getDatabasesList( list );
+
+ if (!tmpdbName.isEmpty()) {
+ //whatever result is - now we have to close temporary opened database:
+ if (!closeDatabase())
+ return QStringList();
+ }
+
+ if (!ret)
+ return QStringList();
+
+ if (also_system_db)
+ return list;
+ //filter system databases:
+ for (QStringList::ConstIterator it = list.constBegin(); it!=list.constEnd(); ++it) {
+ KexiDBDbg << "Connection::databaseNames(): " << *it << endl;
+ if (!m_driver->isSystemDatabaseName(*it)) {
+ KexiDBDbg << "add " << *it << endl;
+ non_system_list << (*it);
+ }
+ }
+ return non_system_list;
+}
+
+bool Connection::drv_getDatabasesList( QStringList &list )
+{
+ list.clear();
+ return true;
+}
+
+bool Connection::drv_databaseExists( const QString &dbName, bool ignoreErrors )
+{
+ QStringList list = databaseNames(true);//also system
+ if (error()) {
+ return false;
+ }
+
+ if (list.find( dbName )==list.end()) {
+ if (!ignoreErrors)
+ setError(ERR_OBJECT_NOT_FOUND, i18n("The database \"%1\" does not exist.").arg(dbName));
+ return false;
+ }
+
+ return true;
+}
+
+bool Connection::databaseExists( const QString &dbName, bool ignoreErrors )
+{
+// KexiDBDbg << "Connection::databaseExists(" << dbName << "," << ignoreErrors << ")" << endl;
+ if (!checkConnected())
+ return false;
+ clearError();
+
+ if (m_driver->isFileDriver()) {
+ //for file-based db: file must exists and be accessible
+//js: moved from useDatabase():
+ QFileInfo file(d->conn_data->fileName());
+ if (!file.exists() || ( !file.isFile() && !file.isSymLink()) ) {
+ if (!ignoreErrors)
+ setError(ERR_OBJECT_NOT_FOUND, i18n("Database file \"%1\" does not exist.")
+ .arg(QDir::convertSeparators(d->conn_data->fileName())) );
+ return false;
+ }
+ if (!file.isReadable()) {
+ if (!ignoreErrors)
+ setError(ERR_ACCESS_RIGHTS, i18n("Database file \"%1\" is not readable.")
+ .arg(QDir::convertSeparators(d->conn_data->fileName())) );
+ return false;
+ }
+ if (!file.isWritable()) {
+ if (!ignoreErrors)
+ setError(ERR_ACCESS_RIGHTS, i18n("Database file \"%1\" is not writable.")
+ .arg(QDir::convertSeparators(d->conn_data->fileName())) );
+ return false;
+ }
+ return true;
+ }
+
+ QString tmpdbName;
+ //some engines need to have opened any database before executing "create database"
+ const bool orig_skip_databaseExists_check_in_useDatabase = d->skip_databaseExists_check_in_useDatabase;
+ d->skip_databaseExists_check_in_useDatabase = true;
+ bool ret = useTemporaryDatabaseIfNeeded(tmpdbName);
+ d->skip_databaseExists_check_in_useDatabase = orig_skip_databaseExists_check_in_useDatabase;
+ if (!ret)
+ return false;
+
+ ret = drv_databaseExists(dbName, ignoreErrors);
+
+ if (!tmpdbName.isEmpty()) {
+ //whatever result is - now we have to close temporary opened database:
+ if (!closeDatabase())
+ return false;
+ }
+
+ return ret;
+}
+
+#define createDatabase_CLOSE \
+ { if (!closeDatabase()) { \
+ setError(i18n("Database \"%1\" created but could not be closed after creation.").arg(dbName) ); \
+ return false; \
+ } }
+
+#define createDatabase_ERROR \
+ { createDatabase_CLOSE; return false; }
+
+
+bool Connection::createDatabase( const QString &dbName )
+{
+ if (!checkConnected())
+ return false;
+
+ if (databaseExists( dbName )) {
+ setError(ERR_OBJECT_EXISTS, i18n("Database \"%1\" already exists.").arg(dbName) );
+ return false;
+ }
+ if (m_driver->isSystemDatabaseName( dbName )) {
+ setError(ERR_SYSTEM_NAME_RESERVED,
+ i18n("Cannot create database \"%1\". This name is reserved for system database.").arg(dbName) );
+ return false;
+ }
+ if (m_driver->isFileDriver()) {
+ //update connection data if filename differs
+ d->conn_data->setFileName( dbName );
+ }
+
+ QString tmpdbName;
+ //some engines need to have opened any database before executing "create database"
+ if (!useTemporaryDatabaseIfNeeded(tmpdbName))
+ return false;
+
+ //low-level create
+ if (!drv_createDatabase( dbName )) {
+ setError(i18n("Error creating database \"%1\" on the server.").arg(dbName) );
+ closeDatabase();//sanity
+ return false;
+ }
+
+ if (!tmpdbName.isEmpty()) {
+ //whatever result is - now we have to close temporary opened database:
+ if (!closeDatabase())
+ return false;
+ }
+
+ if (!tmpdbName.isEmpty() || !m_driver->d->isDBOpenedAfterCreate) {
+ //db need to be opened
+ if (!useDatabase( dbName, false/*not yet kexi compatible!*/ )) {
+ setError(i18n("Database \"%1\" created but could not be opened.").arg(dbName) );
+ return false;
+ }
+ }
+ else {
+ //just for the rule
+ d->usedDatabase = dbName;
+ }
+
+ Transaction trans;
+ if (m_driver->transactionsSupported()) {
+ trans = beginTransaction();
+ if (!trans.active())
+ return false;
+ }
+//not needed since closeDatabase() rollbacks transaction: TransactionGuard trans_g(this);
+// if (error())
+// return false;
+
+ //-create system tables schema objects
+ if (!setupKexiDBSystemSchema())
+ return false;
+
+ //-physically create system tables
+ for (QPtrDictIterator<TableSchema> it(d->kexiDBSystemTables); it.current(); ++it) {
+ if (!drv_createTable( it.current()->name() ))
+ createDatabase_ERROR;
+ }
+
+/* moved to KexiProject...
+
+ //-create default part info
+ TableSchema *ts;
+ if (!(ts = tableSchema("kexi__parts")))
+ createDatabase_ERROR;
+ FieldList *fl = ts->subList("p_id", "p_name", "p_mime", "p_url");
+ if (!fl)
+ createDatabase_ERROR;
+ if (!insertRecord(*fl, QVariant(1), QVariant("Tables"), QVariant("kexi/table"), QVariant("http://koffice.org/kexi/")))
+ createDatabase_ERROR;
+ if (!insertRecord(*fl, QVariant(2), QVariant("Queries"), QVariant("kexi/query"), QVariant("http://koffice.org/kexi/")))
+ createDatabase_ERROR;
+*/
+
+ //-insert KexiDB version info:
+ TableSchema *t_db = tableSchema("kexi__db");
+ if (!t_db)
+ createDatabase_ERROR;
+ if ( !insertRecord(*t_db, "kexidb_major_ver", KexiDB::version().major)
+ || !insertRecord(*t_db, "kexidb_minor_ver", KexiDB::version().minor))
+ createDatabase_ERROR;
+
+ if (trans.active() && !commitTransaction(trans))
+ createDatabase_ERROR;
+
+ createDatabase_CLOSE;
+ return true;
+}
+
+#undef createDatabase_CLOSE
+#undef createDatabase_ERROR
+
+bool Connection::useDatabase( const QString &dbName, bool kexiCompatible, bool *cancelled, MessageHandler* msgHandler )
+{
+ if (cancelled)
+ *cancelled = false;
+ KexiDBDbg << "Connection::useDatabase(" << dbName << "," << kexiCompatible <<")" << endl;
+ if (!checkConnected())
+ return false;
+
+ if (dbName.isEmpty())
+ return false;
+ QString my_dbName = dbName;
+// if (my_dbName.isEmpty()) {
+// const QStringList& db_lst = databaseNames();
+// if (!db_lst.isEmpty())
+// my_dbName = db_lst.first();
+// }
+ if (d->usedDatabase == my_dbName)
+ return true; //already used
+
+ if (!d->skip_databaseExists_check_in_useDatabase) {
+ if (!databaseExists(my_dbName, false /*don't ignore errors*/))
+ return false; //database must exist
+ }
+
+ if (!d->usedDatabase.isEmpty() && !closeDatabase()) //close db if already used
+ return false;
+
+ d->usedDatabase = "";
+
+ if (!drv_useDatabase( my_dbName, cancelled, msgHandler )) {
+ if (cancelled && *cancelled)
+ return false;
+ QString msg(i18n("Opening database \"%1\" failed.").arg( my_dbName ));
+ if (error())
+ setError( this, msg );
+ else
+ setError( msg );
+ return false;
+ }
+
+ //-create system tables schema objects
+ if (!setupKexiDBSystemSchema())
+ return false;
+
+ if (kexiCompatible && my_dbName.lower()!=anyAvailableDatabaseName().lower()) {
+ //-get global database information
+ int num;
+ bool ok;
+// static QString notfound_str = i18n("\"%1\" database property not found");
+ num = d->dbProperties->value("kexidb_major_ver").toInt(&ok);
+ if (!ok)
+ return false;
+ d->databaseVersion.major = num;
+/* if (true!=querySingleNumber(
+ "select db_value from kexi__db where db_property=" + m_driver->escapeString(QString("kexidb_major_ver")), num)) {
+ d->errorInvalidDBContents(notfound_str.arg("kexidb_major_ver"));
+ return false;
+ }*/
+ num = d->dbProperties->value("kexidb_minor_ver").toInt(&ok);
+ if (!ok)
+ return false;
+ d->databaseVersion.minor = num;
+/* if (true!=querySingleNumber(
+ "select db_value from kexi__db where db_property=" + m_driver->escapeString(QString("kexidb_minor_ver")), num)) {
+ d->errorInvalidDBContents(notfound_str.arg("kexidb_minor_ver"));
+ return false;
+ }*/
+
+#if 0 //this is already checked in DriverManagerInternal::lookupDrivers()
+ //** error if major version does not match
+ if (m_driver->versionMajor()!=KexiDB::versionMajor()) {
+ setError(ERR_INCOMPAT_DATABASE_VERSION,
+ i18n("Database version (%1) does not match Kexi application's version (%2)")
+ .arg( QString("%1.%2").arg(versionMajor()).arg(versionMinor()) )
+ .arg( QString("%1.%2").arg(KexiDB::versionMajor()).arg(KexiDB::versionMinor()) ) );
+ return false;
+ }
+ if (m_driver->versionMinor()!=KexiDB::versionMinor()) {
+ //js TODO: COMPATIBILITY CODE HERE!
+ //js TODO: CONVERSION CODE HERE (or signal that conversion is needed)
+ }
+#endif
+ }
+ d->usedDatabase = my_dbName;
+ return true;
+}
+
+bool Connection::closeDatabase()
+{
+ if (d->usedDatabase.isEmpty())
+ return true; //no db used
+ if (!checkConnected())
+ return true;
+
+ bool ret = true;
+
+/*! \todo (js) add CLEVER algorithm here for nested transactions */
+ if (m_driver->transactionsSupported()) {
+ //rollback all transactions
+ QValueList<Transaction>::ConstIterator it;
+ d->dont_remove_transactions=true; //lock!
+ for (it=d->transactions.constBegin(); it!= d->transactions.constEnd(); ++it) {
+ if (!rollbackTransaction(*it)) {//rollback as much as you can, don't stop on prev. errors
+ ret = false;
+ }
+ else {
+ KexiDBDbg << "Connection::closeDatabase(): transaction rolled back!" << endl;
+ KexiDBDbg << "Connection::closeDatabase(): trans.refcount==" <<
+ ((*it).m_data ? QString::number((*it).m_data->refcount) : "(null)") << endl;
+ }
+ }
+ d->dont_remove_transactions=false; //unlock!
+ d->transactions.clear(); //free trans. data
+ }
+
+ //delete own cursors:
+ m_cursors.clear();
+ //delete own schemas
+ d->tables.clear();
+ d->kexiDBSystemTables.clear();
+ d->queries.clear();
+
+ if (!drv_closeDatabase())
+ return false;
+
+ d->usedDatabase = "";
+// KexiDBDbg << "Connection::closeDatabase(): " << ret << endl;
+ return ret;
+}
+
+QString Connection::currentDatabase() const
+{
+ return d->usedDatabase;
+}
+
+bool Connection::useTemporaryDatabaseIfNeeded(QString &tmpdbName)
+{
+ if (!m_driver->isFileDriver() && m_driver->beh->USING_DATABASE_REQUIRED_TO_CONNECT
+ && !isDatabaseUsed()) {
+ //we have no db used, but it is required by engine to have used any!
+ tmpdbName = anyAvailableDatabaseName();
+ if (tmpdbName.isEmpty()) {
+ setError(ERR_NO_DB_USED, i18n("Cannot find any database for temporary connection.") );
+ return false;
+ }
+ const bool orig_skip_databaseExists_check_in_useDatabase = d->skip_databaseExists_check_in_useDatabase;
+ d->skip_databaseExists_check_in_useDatabase = true;
+ bool ret = useDatabase(tmpdbName, false);
+ d->skip_databaseExists_check_in_useDatabase = orig_skip_databaseExists_check_in_useDatabase;
+ if (!ret) {
+ setError(errorNum(),
+ i18n("Error during starting temporary connection using \"%1\" database name.")
+ .arg(tmpdbName) );
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Connection::dropDatabase( const QString &dbName )
+{
+ if (!checkConnected())
+ return false;
+
+ QString dbToDrop;
+ if (dbName.isEmpty() && d->usedDatabase.isEmpty()) {
+ if (!m_driver->isFileDriver()
+ || (m_driver->isFileDriver() && d->conn_data->fileName().isEmpty()) ) {
+ setError(ERR_NO_NAME_SPECIFIED, i18n("Cannot drop database - name not specified.") );
+ return false;
+ }
+ //this is a file driver so reuse previously passed filename
+ dbToDrop = d->conn_data->fileName();
+ }
+ else {
+ if (dbName.isEmpty()) {
+ dbToDrop = d->usedDatabase;
+ } else {
+ if (m_driver->isFileDriver()) //lets get full path
+ dbToDrop = QFileInfo(dbName).absFilePath();
+ else
+ dbToDrop = dbName;
+ }
+ }
+
+ if (dbToDrop.isEmpty()) {
+ setError(ERR_NO_NAME_SPECIFIED, i18n("Cannot delete database - name not specified.") );
+ return false;
+ }
+
+ if (m_driver->isSystemDatabaseName( dbToDrop )) {
+ setError(ERR_SYSTEM_NAME_RESERVED, i18n("Cannot delete system database \"%1\".").arg(dbToDrop) );
+ return false;
+ }
+
+ if (isDatabaseUsed() && d->usedDatabase == dbToDrop) {
+ //we need to close database because cannot drop used this database
+ if (!closeDatabase())
+ return false;
+ }
+
+ QString tmpdbName;
+ //some engines need to have opened any database before executing "drop database"
+ if (!useTemporaryDatabaseIfNeeded(tmpdbName))
+ return false;
+
+ //ok, now we have access to dropping
+ bool ret = drv_dropDatabase( dbToDrop );
+
+ if (!tmpdbName.isEmpty()) {
+ //whatever result is - now we have to close temporary opened database:
+ if (!closeDatabase())
+ return false;
+ }
+ return ret;
+}
+
+QStringList Connection::objectNames(int objType, bool* ok)
+{
+ QStringList list;
+
+ if (!checkIsDatabaseUsed()) {
+ if(ok)
+ *ok = false;
+ return list;
+ }
+
+ QString sql;
+ if (objType==KexiDB::AnyObjectType)
+ sql = "SELECT o_name FROM kexi__objects";
+ else
+ sql = QString::fromLatin1("SELECT o_name FROM kexi__objects WHERE o_type=%1").arg(objType);
+
+ Cursor *c = executeQuery(sql);
+ if (!c) {
+ if(ok)
+ *ok = false;
+ return list;
+ }
+
+ for (c->moveFirst(); !c->eof(); c->moveNext()) {
+ QString name = c->value(0).toString();
+ if (KexiUtils::isIdentifier( name )) {
+ list.append(name);
+ }
+ }
+
+ if (!deleteCursor(c)) {
+ if(ok)
+ *ok = false;
+ return list;
+ }
+
+ if(ok)
+ *ok = true;
+ return list;
+}
+
+QStringList Connection::tableNames(bool also_system_tables)
+{
+ bool ok = true;
+ QStringList list = objectNames(TableObjectType, &ok);
+ if (also_system_tables && ok) {
+ list += Connection::kexiDBSystemTableNames();
+ }
+ return list;
+}
+
+//! \todo (js): this will depend on KexiDB lib version
+const QStringList& Connection::kexiDBSystemTableNames()
+{
+ if (KexiDB_kexiDBSystemTableNames.isEmpty()) {
+ KexiDB_kexiDBSystemTableNames
+ << "kexi__objects"
+ << "kexi__objectdata"
+ << "kexi__fields"
+// << "kexi__querydata"
+// << "kexi__queryfields"
+// << "kexi__querytables"
+ << "kexi__db"
+ ;
+ }
+ return KexiDB_kexiDBSystemTableNames;
+}
+
+KexiDB::ServerVersionInfo* Connection::serverVersion() const
+{
+ return isConnected() ? &d->serverVersion : 0;
+}
+
+KexiDB::DatabaseVersionInfo* Connection::databaseVersion() const
+{
+ return isDatabaseUsed() ? &d->databaseVersion : 0;
+}
+
+DatabaseProperties& Connection::databaseProperties()
+{
+ return *d->dbProperties;
+}
+
+QValueList<int> Connection::tableIds()
+{
+ return objectIds(KexiDB::TableObjectType);
+}
+
+QValueList<int> Connection::queryIds()
+{
+ return objectIds(KexiDB::QueryObjectType);
+}
+
+QValueList<int> Connection::objectIds(int objType)
+{
+ QValueList<int> list;
+
+ if (!checkIsDatabaseUsed())
+ return list;
+
+ Cursor *c = executeQuery(
+ QString::fromLatin1("SELECT o_id, o_name FROM kexi__objects WHERE o_type=%1").arg(objType));
+ if (!c)
+ return list;
+ for (c->moveFirst(); !c->eof(); c->moveNext())
+ {
+ QString tname = c->value(1).toString(); //kexi__objects.o_name
+ if (KexiUtils::isIdentifier( tname )) {
+ list.append(c->value(0).toInt()); //kexi__objects.o_id
+ }
+ }
+
+ deleteCursor(c);
+
+ return list;
+}
+
+QString Connection::createTableStatement( const KexiDB::TableSchema& tableSchema ) const
+{
+// Each SQL identifier needs to be escaped in the generated query.
+ QString sql;
+ sql.reserve(4096);
+ sql = "CREATE TABLE " + escapeIdentifier(tableSchema.name()) + " (";
+ bool first=true;
+ Field::ListIterator it( tableSchema.m_fields );
+ Field *field;
+ for (;(field = it.current())!=0; ++it) {
+ if (first)
+ first = false;
+ else
+ sql += ", ";
+ QString v = escapeIdentifier(field->name()) + " ";
+ const bool autoinc = field->isAutoIncrement();
+ const bool pk = field->isPrimaryKey() || (autoinc && m_driver->beh->AUTO_INCREMENT_REQUIRES_PK);
+//TODO: warning: ^^^^^ this allows only one autonumber per table when AUTO_INCREMENT_REQUIRES_PK==true!
+ if (autoinc && m_driver->beh->SPECIAL_AUTO_INCREMENT_DEF) {
+ if (pk)
+ v += m_driver->beh->AUTO_INCREMENT_TYPE + " " + m_driver->beh->AUTO_INCREMENT_PK_FIELD_OPTION;
+ else
+ v += m_driver->beh->AUTO_INCREMENT_TYPE + " " + m_driver->beh->AUTO_INCREMENT_FIELD_OPTION;
+ }
+ else {
+ if (autoinc && !m_driver->beh->AUTO_INCREMENT_TYPE.isEmpty())
+ v += m_driver->beh->AUTO_INCREMENT_TYPE;
+ else
+ v += m_driver->sqlTypeName(field->type(), field->precision());
+
+ if (field->isUnsigned())
+ v += (" " + m_driver->beh->UNSIGNED_TYPE_KEYWORD);
+
+ if (field->isFPNumericType() && field->precision()>0) {
+ if (field->scale()>0)
+ v += QString::fromLatin1("(%1,%2)").arg(field->precision()).arg(field->scale());
+ else
+ v += QString::fromLatin1("(%1)").arg(field->precision());
+ }
+ else if (field->type()==Field::Text && field->length()>0)
+ v += QString::fromLatin1("(%1)").arg(field->length());
+
+ if (autoinc)
+ v += (" " +
+ (pk ? m_driver->beh->AUTO_INCREMENT_PK_FIELD_OPTION : m_driver->beh->AUTO_INCREMENT_FIELD_OPTION));
+ else
+ //TODO: here is automatically a single-field key created
+ if (pk)
+ v += " PRIMARY KEY";
+ if (!pk && field->isUniqueKey())
+ v += " UNIQUE";
+///@todo IS this ok for all engines?: if (!autoinc && !field->isPrimaryKey() && field->isNotNull())
+ if (!autoinc && !pk && field->isNotNull())
+ v += " NOT NULL"; //only add not null option if no autocommit is set
+ if (field->defaultValue().isValid()) {
+ QString valToSQL( m_driver->valueToSQL( field, field->defaultValue() ) );
+ if (!valToSQL.isEmpty()) //for sanity
+ v += QString::fromLatin1(" DEFAULT ") + valToSQL;
+ }
+ }
+ sql += v;
+ }
+ sql += ")";
+ return sql;
+}
+
+//yeah, it is very efficient:
+#define C_A(a) , const QVariant& c ## a
+
+#define V_A0 m_driver->valueToSQL( tableSchema.field(0), c0 )
+#define V_A(a) +","+m_driver->valueToSQL( \
+ tableSchema.field(a) ? tableSchema.field(a)->type() : Field::Text, c ## a )
+
+// KexiDBDbg << "******** " << QString("INSERT INTO ") +
+// escapeIdentifier(tableSchema.name()) +
+// " VALUES (" + vals + ")" <<endl;
+
+#define C_INS_REC(args, vals) \
+ bool Connection::insertRecord(KexiDB::TableSchema &tableSchema args) {\
+ return executeSQL( \
+ QString("INSERT INTO ") + escapeIdentifier(tableSchema.name()) + " VALUES (" + vals + ")" \
+ ); \
+ }
+
+#define C_INS_REC_ALL \
+C_INS_REC( C_A(0), V_A0 ) \
+C_INS_REC( C_A(0) C_A(1), V_A0 V_A(1) ) \
+C_INS_REC( C_A(0) C_A(1) C_A(2), V_A0 V_A(1) V_A(2) ) \
+C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3), V_A0 V_A(1) V_A(2) V_A(3) ) \
+C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) ) \
+C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) ) \
+C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) ) \
+C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6) C_A(7), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) V_A(7) )
+
+C_INS_REC_ALL
+
+#undef V_A0
+#undef V_A
+#undef C_INS_REC
+
+#define V_A0 value += m_driver->valueToSQL( flist->first(), c0 );
+#define V_A( a ) value += ("," + m_driver->valueToSQL( flist->next(), c ## a ));
+//#define V_ALAST( a ) valueToSQL( flist->last(), c ## a )
+
+
+#define C_INS_REC(args, vals) \
+ bool Connection::insertRecord(FieldList& fields args) \
+ { \
+ QString value; \
+ Field::List *flist = fields.fields(); \
+ vals \
+ return executeSQL( \
+ QString("INSERT INTO ") + \
+ ((fields.fields()->first() && fields.fields()->first()->table()) ? \
+ escapeIdentifier(fields.fields()->first()->table()->name()) : \
+ "??") \
+ + "(" + fields.sqlFieldsList(m_driver) + ") VALUES (" + value + ")" \
+ ); \
+ }
+
+C_INS_REC_ALL
+
+#undef C_A
+#undef V_A
+#undef V_ALAST
+#undef C_INS_REC
+#undef C_INS_REC_ALL
+
+bool Connection::insertRecord(TableSchema &tableSchema, QValueList<QVariant>& values)
+{
+// Each SQL identifier needs to be escaped in the generated query.
+ Field::List *fields = tableSchema.fields();
+ Field *f = fields->first();
+// QString s_val;
+// s_val.reserve(4096);
+ m_sql = QString::null;
+ QValueList<QVariant>::ConstIterator it = values.constBegin();
+// int i=0;
+ while (f && (it!=values.end())) {
+ if (m_sql.isEmpty())
+ m_sql = QString("INSERT INTO ") +
+ escapeIdentifier(tableSchema.name()) +
+ " VALUES (";
+ else
+ m_sql += ",";
+ m_sql += m_driver->valueToSQL( f, *it );
+// KexiDBDbg << "val" << i++ << ": " << m_driver->valueToSQL( f, *it ) << endl;
+ ++it;
+ f=fields->next();
+ }
+ m_sql += ")";
+
+// KexiDBDbg<<"******** "<< m_sql << endl;
+ return executeSQL(m_sql);
+}
+
+bool Connection::insertRecord(FieldList& fields, QValueList<QVariant>& values)
+{
+// Each SQL identifier needs to be escaped in the generated query.
+ Field::List *flist = fields.fields();
+ Field *f = flist->first();
+ if (!f)
+ return false;
+// QString s_val;
+// s_val.reserve(4096);
+ m_sql = QString::null;
+ QValueList<QVariant>::ConstIterator it = values.constBegin();
+// int i=0;
+ while (f && (it!=values.constEnd())) {
+ if (m_sql.isEmpty())
+ m_sql = QString("INSERT INTO ") +
+ escapeIdentifier(flist->first()->table()->name()) + "(" +
+ fields.sqlFieldsList(m_driver) + ") VALUES (";
+ else
+ m_sql += ",";
+ m_sql += m_driver->valueToSQL( f, *it );
+// KexiDBDbg << "val" << i++ << ": " << m_driver->valueToSQL( f, *it ) << endl;
+ ++it;
+ f=flist->next();
+ }
+ m_sql += ")";
+
+ return executeSQL(m_sql);
+}
+
+bool Connection::executeSQL( const QString& statement )
+{
+ m_sql = statement; //remember for error handling
+ if (!drv_executeSQL( m_sql )) {
+ m_errMsg = QString::null; //clear as this could be most probably jsut "Unknown error" string.
+ m_errorSql = statement;
+ setError(this, ERR_SQL_EXECUTION_ERROR, i18n("Error while executing SQL statement."));
+ return false;
+ }
+ return true;
+}
+
+QString Connection::selectStatement( KexiDB::QuerySchema& querySchema,
+ const QValueList<QVariant>& params,
+ const SelectStatementOptions& options) const
+{
+//"SELECT FROM ..." is theoretically allowed "
+//if (querySchema.fieldCount()<1)
+// return QString::null;
+// Each SQL identifier needs to be escaped in the generated query.
+
+ if (!querySchema.statement().isEmpty())
+ return querySchema.statement();
+
+//! @todo looking at singleTable is visually nice but a field name can conflict
+//! with function or variable name...
+ Field *f;
+ uint number = 0;
+ bool singleTable = querySchema.tables()->count() <= 1;
+ if (singleTable) {
+ //make sure we will have single table:
+ for (Field::ListIterator it = querySchema.fieldsIterator(); (f = it.current()); ++it, number++) {
+ if (querySchema.isColumnVisible(number) && f->table() && f->table()->lookupFieldSchema( *f )) {
+ //uups, no, there's at least one left join
+ singleTable = false;
+ break;
+ }
+ }
+ }
+
+ QString sql; //final sql string
+ sql.reserve(4096);
+//unused QString s_from_additional; //additional tables list needed for lookup fields
+ QString s_additional_joins; //additional joins needed for lookup fields
+ QString s_additional_fields; //additional fields to append to the fields list
+ uint internalUniqueTableAliasNumber = 0; //used to build internalUniqueTableAliases
+ uint internalUniqueQueryAliasNumber = 0; //used to build internalUniqueQueryAliases
+ number = 0;
+ QPtrList<QuerySchema> subqueries_for_lookup_data; // subqueries will be added to FROM section
+ QString kexidb_subquery_prefix("__kexidb_subquery_");
+ for (Field::ListIterator it = querySchema.fieldsIterator(); (f = it.current()); ++it, number++) {
+ if (querySchema.isColumnVisible(number)) {
+ if (!sql.isEmpty())
+ sql += QString::fromLatin1(", ");
+
+ if (f->isQueryAsterisk()) {
+ if (!singleTable && static_cast<QueryAsterisk*>(f)->isSingleTableAsterisk()) //single-table *
+ sql += escapeIdentifier(f->table()->name(), options.identifierEscaping) +
+ QString::fromLatin1(".*");
+ else //all-tables * (or simplified table.* when there's only one table)
+ sql += QString::fromLatin1("*");
+ }
+ else {
+ if (f->isExpression()) {
+ sql += f->expression()->toString();
+ }
+ else {
+ if (!f->table()) //sanity check
+ return QString::null;
+
+ QString tableName;
+ int tablePosition = querySchema.tableBoundToColumn(number);
+ if (tablePosition>=0)
+ tableName = querySchema.tableAlias(tablePosition);
+ if (tableName.isEmpty())
+ tableName = f->table()->name();
+
+ if (!singleTable) {
+ sql += (escapeIdentifier(tableName, options.identifierEscaping) + ".");
+ }
+ sql += escapeIdentifier(f->name(), options.identifierEscaping);
+ }
+ QString aliasString = QString(querySchema.columnAlias(number));
+ if (!aliasString.isEmpty())
+ sql += (QString::fromLatin1(" AS ") + aliasString);
+//! @todo add option that allows to omit "AS" keyword
+ }
+ LookupFieldSchema *lookupFieldSchema = (options.addVisibleLookupColumns && f->table())
+ ? f->table()->lookupFieldSchema( *f ) : 0;
+ if (lookupFieldSchema && lookupFieldSchema->boundColumn()>=0) {
+ // Lookup field schema found
+ // Now we also need to fetch "visible" value from the lookup table, not only the value of binding.
+ // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken)
+ // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField"
+ LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource();
+ if (rowSource.type()==LookupFieldSchema::RowSource::Table) {
+ TableSchema *lookupTable = querySchema.connection()->tableSchema( rowSource.name() );
+ FieldList* visibleColumns = 0;
+ Field *boundField = 0;
+ if (lookupTable
+ && (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount()
+ && (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() ))
+ && (boundField = lookupTable->field( lookupFieldSchema->boundColumn() )))
+ {
+ //add LEFT OUTER JOIN
+ if (!s_additional_joins.isEmpty())
+ s_additional_joins += QString::fromLatin1(" ");
+ QString internalUniqueTableAlias( QString("__kexidb_") + lookupTable->name() + "_"
+ + QString::number(internalUniqueTableAliasNumber++) );
+ s_additional_joins += QString("LEFT OUTER JOIN %1 AS %2 ON %3.%4=%5.%6")
+ .arg(escapeIdentifier(lookupTable->name(), options.identifierEscaping))
+ .arg(internalUniqueTableAlias)
+ .arg(escapeIdentifier(f->table()->name(), options.identifierEscaping))
+ .arg(escapeIdentifier(f->name(), options.identifierEscaping))
+ .arg(internalUniqueTableAlias)
+ .arg(escapeIdentifier(boundField->name(), options.identifierEscaping));
+
+ //add visibleField to the list of SELECTed fields //if it is not yet present there
+//not needed if (!querySchema.findTableField( visibleField->table()->name()+"."+visibleField->name() )) {
+#if 0
+ if (!querySchema.table( visibleField->table()->name() )) {
+/* not true
+ //table should be added after FROM
+ if (!s_from_additional.isEmpty())
+ s_from_additional += QString::fromLatin1(", ");
+ s_from_additional += escapeIdentifier(visibleField->table()->name(), options.identifierEscaping);
+ */
+ }
+#endif
+ if (!s_additional_fields.isEmpty())
+ s_additional_fields += QString::fromLatin1(", ");
+// s_additional_fields += (internalUniqueTableAlias + "." //escapeIdentifier(visibleField->table()->name(), options.identifierEscaping) + "."
+// escapeIdentifier(visibleField->name(), options.identifierEscaping));
+//! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?"
+//! @todo Add possibility for joining the values at client side.
+ s_additional_fields += visibleColumns->sqlFieldsList(
+ driver(), " || ' ' || ", internalUniqueTableAlias, options.identifierEscaping);
+ }
+ delete visibleColumns;
+ }
+ else if (rowSource.type()==LookupFieldSchema::RowSource::Query) {
+ QuerySchema *lookupQuery = querySchema.connection()->querySchema( rowSource.name() );
+ if (!lookupQuery) {
+ KexiDBWarn << "Connection::selectStatement(): !lookupQuery" << endl;
+ return QString::null;
+ }
+ const QueryColumnInfo::Vector fieldsExpanded( lookupQuery->fieldsExpanded() );
+ if ((uint)lookupFieldSchema->boundColumn() >= fieldsExpanded.count()) {
+ KexiDBWarn << "Connection::selectStatement(): (uint)lookupFieldSchema->boundColumn() >= fieldsExpanded.count()" << endl;
+ return QString::null;
+ }
+ QueryColumnInfo *boundColumnInfo = fieldsExpanded.at( lookupFieldSchema->boundColumn() );
+ if (!boundColumnInfo) {
+ KexiDBWarn << "Connection::selectStatement(): !boundColumnInfo" << endl;
+ return QString::null;
+ }
+ Field *boundField = boundColumnInfo->field;
+ if (!boundField) {
+ KexiDBWarn << "Connection::selectStatement(): !boundField" << endl;
+ return QString::null;
+ }
+ //add LEFT OUTER JOIN
+ if (!s_additional_joins.isEmpty())
+ s_additional_joins += QString::fromLatin1(" ");
+ QString internalUniqueQueryAlias(
+ kexidb_subquery_prefix + lookupQuery->name() + "_"
+ + QString::number(internalUniqueQueryAliasNumber++) );
+ s_additional_joins += QString("LEFT OUTER JOIN (%1) AS %2 ON %3.%4=%5.%6")
+ .arg(selectStatement( *lookupQuery, params, options ))
+ .arg(internalUniqueQueryAlias)
+ .arg(escapeIdentifier(f->table()->name(), options.identifierEscaping))
+ .arg(escapeIdentifier(f->name(), options.identifierEscaping))
+ .arg(internalUniqueQueryAlias)
+ .arg(escapeIdentifier(boundColumnInfo->aliasOrName(), options.identifierEscaping));
+
+ if (!s_additional_fields.isEmpty())
+ s_additional_fields += QString::fromLatin1(", ");
+ const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() );
+ QString expression;
+ foreach (QValueList<uint>::ConstIterator, visibleColumnsIt, visibleColumns) {
+//! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?"
+//! @todo Add possibility for joining the values at client side.
+ if (fieldsExpanded.count() <= (*visibleColumnsIt)) {
+ KexiDBWarn << "Connection::selectStatement(): fieldsExpanded.count() <= (*visibleColumnsIt) : "
+ << fieldsExpanded.count() << " <= " << *visibleColumnsIt << endl;
+ return QString::null;
+ }
+ if (!expression.isEmpty())
+ expression += " || ' ' || ";
+ expression += (internalUniqueQueryAlias + "." +
+ escapeIdentifier(fieldsExpanded[*visibleColumnsIt]->aliasOrName(),
+ options.identifierEscaping));
+ }
+ s_additional_fields += expression;
+//subqueries_for_lookup_data.append(lookupQuery);
+ }
+ else {
+ KexiDBWarn << "Connection::selectStatement(): unsupported row source type "
+ << rowSource.typeName() << endl;
+ return QString();
+ }
+ }
+ }
+ }
+
+ //add lookup fields
+ if (!s_additional_fields.isEmpty())
+ sql += (QString::fromLatin1(", ") + s_additional_fields);
+
+ if (options.alsoRetrieveROWID) { //append rowid column
+ QString s;
+ if (!sql.isEmpty())
+ s = QString::fromLatin1(", ");
+ if (querySchema.masterTable())
+ s += (escapeIdentifier(querySchema.masterTable()->name())+".");
+ s += m_driver->beh->ROW_ID_FIELD_NAME;
+ sql += s;
+ }
+
+ sql.prepend("SELECT ");
+ TableSchema::List* tables = querySchema.tables();
+ if ((tables && !tables->isEmpty()) || !subqueries_for_lookup_data.isEmpty()) {
+ sql += QString::fromLatin1(" FROM ");
+ QString s_from;
+ if (tables) {
+ TableSchema *table;
+ number = 0;
+ for (TableSchema::ListIterator it(*tables); (table = it.current());
+ ++it, number++)
+ {
+ if (!s_from.isEmpty())
+ s_from += QString::fromLatin1(", ");
+ s_from += escapeIdentifier(table->name(), options.identifierEscaping);
+ QString aliasString = QString(querySchema.tableAlias(number));
+ if (!aliasString.isEmpty())
+ s_from += (QString::fromLatin1(" AS ") + aliasString);
+ }
+ /*unused if (!s_from_additional.isEmpty()) {//additional tables list needed for lookup fields
+ if (!s_from.isEmpty())
+ s_from += QString::fromLatin1(", ");
+ s_from += s_from_additional;
+ }*/
+ }
+ // add subqueries for lookup data
+ uint subqueries_for_lookup_data_counter = 0;
+ for (QPtrListIterator<QuerySchema> it(subqueries_for_lookup_data);
+ subqueries_for_lookup_data.current(); ++it, subqueries_for_lookup_data_counter++)
+ {
+ if (!s_from.isEmpty())
+ s_from += QString::fromLatin1(", ");
+ s_from += QString::fromLatin1("(");
+ s_from += selectStatement( *it.current(), params, options );
+ s_from += QString::fromLatin1(") AS %1%2")
+ .arg(kexidb_subquery_prefix).arg(subqueries_for_lookup_data_counter);
+ }
+ sql += s_from;
+ }
+ QString s_where;
+ s_where.reserve(4096);
+
+ //JOINS
+ if (!s_additional_joins.isEmpty()) {
+ sql += QString::fromLatin1(" ") + s_additional_joins + QString::fromLatin1(" ");
+ }
+
+//@todo: we're using WHERE for joins now; use INNER/LEFT/RIGHT JOIN later
+
+ //WHERE
+ Relationship *rel;
+ bool wasWhere = false; //for later use
+ for (Relationship::ListIterator it(*querySchema.relationships()); (rel = it.current()); ++it) {
+ if (s_where.isEmpty()) {
+ wasWhere = true;
+ }
+ else
+ s_where += QString::fromLatin1(" AND ");
+ Field::Pair *pair;
+ QString s_where_sub;
+ for (QPtrListIterator<Field::Pair> p_it(*rel->fieldPairs()); (pair = p_it.current()); ++p_it) {
+ if (!s_where_sub.isEmpty())
+ s_where_sub += QString::fromLatin1(" AND ");
+ s_where_sub += (
+ escapeIdentifier(pair->first->table()->name(), options.identifierEscaping) +
+ QString::fromLatin1(".") +
+ escapeIdentifier(pair->first->name(), options.identifierEscaping) +
+ QString::fromLatin1(" = ") +
+ escapeIdentifier(pair->second->table()->name(), options.identifierEscaping) +
+ QString::fromLatin1(".") +
+ escapeIdentifier(pair->second->name(), options.identifierEscaping));
+ }
+ if (rel->fieldPairs()->count()>1) {
+ s_where_sub.prepend("(");
+ s_where_sub += QString::fromLatin1(")");
+ }
+ s_where += s_where_sub;
+ }
+ //EXPLICITLY SPECIFIED WHERE EXPRESSION
+ if (querySchema.whereExpression()) {
+ QuerySchemaParameterValueListIterator paramValuesIt(*m_driver, params);
+ QuerySchemaParameterValueListIterator *paramValuesItPtr = params.isEmpty() ? 0 : &paramValuesIt;
+ if (wasWhere) {
+//TODO: () are not always needed
+ s_where = "(" + s_where + ") AND (" + querySchema.whereExpression()->toString(paramValuesItPtr) + ")";
+ }
+ else {
+ s_where = querySchema.whereExpression()->toString(paramValuesItPtr);
+ }
+ }
+ if (!s_where.isEmpty())
+ sql += QString::fromLatin1(" WHERE ") + s_where;
+//! \todo (js) add other sql parts
+ //(use wasWhere here)
+
+ // ORDER BY
+ QString orderByString(
+ querySchema.orderByColumnList().toSQLString(!singleTable/*includeTableName*/,
+ driver(), options.identifierEscaping) );
+ const QValueVector<int> pkeyFieldsOrder( querySchema.pkeyFieldsOrder() );
+ if (orderByString.isEmpty() && !pkeyFieldsOrder.isEmpty()) {
+ //add automatic ORDER BY if there is no explicity defined (especially helps when there are complex JOINs)
+ OrderByColumnList automaticPKOrderBy;
+ const QueryColumnInfo::Vector fieldsExpanded( querySchema.fieldsExpanded() );
+ foreach (QValueVector<int>::ConstIterator, it, pkeyFieldsOrder) {
+ if ((*it) < 0) // no field mentioned in this query
+ continue;
+ if ((*it) >= (int)fieldsExpanded.count()) {
+ KexiDBWarn << "Connection::selectStatement(): ORDER BY: (*it) >= fieldsExpanded.count() - "
+ << (*it) << " >= " << fieldsExpanded.count() << endl;
+ continue;
+ }
+ QueryColumnInfo *ci = fieldsExpanded[ *it ];
+ automaticPKOrderBy.appendColumn( *ci );
+ }
+ orderByString = automaticPKOrderBy.toSQLString(!singleTable/*includeTableName*/,
+ driver(), options.identifierEscaping);
+ }
+ if (!orderByString.isEmpty())
+ sql += (" ORDER BY " + orderByString);
+
+ //KexiDBDbg << sql << endl;
+ return sql;
+}
+
+QString Connection::selectStatement( KexiDB::TableSchema& tableSchema,
+ const SelectStatementOptions& options) const
+{
+ return selectStatement( *tableSchema.query(), options );
+}
+
+Field* Connection::findSystemFieldName(KexiDB::FieldList* fieldlist)
+{
+ Field *f = fieldlist->fields()->first();
+ while (f) {
+ if (m_driver->isSystemFieldName( f->name() ))
+ return f;
+ f = fieldlist->fields()->next();
+ }
+ return 0;
+}
+
+Q_ULLONG Connection::lastInsertedAutoIncValue(const QString& aiFieldName, const QString& tableName,
+ Q_ULLONG* ROWID)
+{
+ Q_ULLONG row_id = drv_lastInsertRowID();
+ if (ROWID)
+ *ROWID = row_id;
+ if (m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) {
+ return row_id;
+ }
+ RowData rdata;
+ if (row_id<=0 || true!=querySingleRecord(
+ QString::fromLatin1("SELECT ") + tableName + QString::fromLatin1(".") + aiFieldName + QString::fromLatin1(" FROM ") + tableName
+ + QString::fromLatin1(" WHERE ") + m_driver->beh->ROW_ID_FIELD_NAME + QString::fromLatin1("=") + QString::number(row_id), rdata))
+ {
+// KexiDBDbg << "Connection::lastInsertedAutoIncValue(): row_id<=0 || true!=querySingleRecord()" << endl;
+ return (Q_ULLONG)-1; //ULL;
+ }
+ return rdata[0].toULongLong();
+}
+
+Q_ULLONG Connection::lastInsertedAutoIncValue(const QString& aiFieldName,
+ const KexiDB::TableSchema& table, Q_ULLONG* ROWID)
+{
+ return lastInsertedAutoIncValue(aiFieldName,table.name(), ROWID);
+}
+
+//! Creates a Field list for kexi__fields, for sanity. Used by createTable()
+static FieldList* createFieldListForKexi__Fields(TableSchema *kexi__fieldsSchema)
+{
+ if (!kexi__fieldsSchema)
+ return 0;
+ return kexi__fieldsSchema->subList(
+ "t_id",
+ "f_type",
+ "f_name",
+ "f_length",
+ "f_precision",
+ "f_constraints",
+ "f_options",
+ "f_default",
+ "f_order",
+ "f_caption",
+ "f_help"
+ );
+}
+
+//! builds a list of values for field's \a f properties. Used by createTable().
+void buildValuesForKexi__Fields(QValueList<QVariant>& vals, Field* f)
+{
+ vals.clear();
+ vals
+ << QVariant(f->table()->id())
+ << QVariant(f->type())
+ << QVariant(f->name())
+ << QVariant(f->isFPNumericType() ? f->scale() : f->length())
+ << QVariant(f->isFPNumericType() ? f->precision() : 0)
+ << QVariant(f->constraints())
+ << QVariant(f->options())
+ // KexiDB::variantToString() is needed here because the value can be of any QVariant type,
+ // depending on f->type()
+ << (f->defaultValue().isNull()
+ ? QVariant() : QVariant(KexiDB::variantToString( f->defaultValue() )))
+ << QVariant(f->order())
+ << QVariant(f->caption())
+ << QVariant(f->description());
+}
+
+bool Connection::storeMainFieldSchema(Field *field)
+{
+ if (!field || !field->table())
+ return false;
+ FieldList *fl = createFieldListForKexi__Fields(d->tables_byname["kexi__fields"]);
+ if (!fl)
+ return false;
+
+ QValueList<QVariant> vals;
+ buildValuesForKexi__Fields(vals, field);
+ QValueList<QVariant>::ConstIterator valsIt = vals.constBegin();
+ Field *f;
+ bool first = true;
+ QString sql = "UPDATE kexi__fields SET ";
+ for (Field::ListIterator it( fl->fieldsIterator() ); (f = it.current()); ++it, ++valsIt) {
+ sql.append( (first ? QString::null : QString(", ")) +
+ f->name() + "=" + m_driver->valueToSQL( f, *valsIt ) );
+ if (first)
+ first = false;
+ }
+ delete fl;
+
+ sql.append(QString(" WHERE t_id=") + QString::number( field->table()->id() )
+ + " AND f_name=" + m_driver->valueToSQL( Field::Text, field->name() ) );
+ return executeSQL( sql );
+}
+
+#define createTable_ERR \
+ { KexiDBDbg << "Connection::createTable(): ERROR!" <<endl; \
+ setError(this, i18n("Creating table failed.")); \
+ rollbackAutoCommitTransaction(tg.transaction()); \
+ return false; }
+ //setError( errorNum(), i18n("Creating table failed.") + " " + errorMsg());
+
+//! Creates a table according to the given schema
+/*! Creates a table according to the given TableSchema, adding the table and
+ column definitions to kexi__* tables. Checks that a database is in use,
+ that the table name is not that of a system table, and that the schema
+ defines at least one column.
+ If the table exists, and replaceExisting is true, the table is replaced.
+ Otherwise, the table is not replaced.
+*/
+bool Connection::createTable( KexiDB::TableSchema* tableSchema, bool replaceExisting )
+{
+ if (!tableSchema || !checkIsDatabaseUsed())
+ return false;
+
+ //check if there are any fields
+ if (tableSchema->fieldCount()<1) {
+ clearError();
+ setError(ERR_CANNOT_CREATE_EMPTY_OBJECT, i18n("Cannot create table without fields."));
+ return false;
+ }
+ const bool internalTable = dynamic_cast<InternalTableSchema*>(tableSchema);
+
+ const QString &tableName = tableSchema->name().lower();
+
+ if (!internalTable) {
+ if (m_driver->isSystemObjectName( tableName )) {
+ clearError();
+ setError(ERR_SYSTEM_NAME_RESERVED, i18n("System name \"%1\" cannot be used as table name.")
+ .arg(tableSchema->name()));
+ return false;
+ }
+
+ Field *sys_field = findSystemFieldName(tableSchema);
+ if (sys_field) {
+ clearError();
+ setError(ERR_SYSTEM_NAME_RESERVED,
+ i18n("System name \"%1\" cannot be used as one of fields in \"%2\" table.")
+ .arg(sys_field->name()).arg(tableName));
+ return false;
+ }
+ }
+
+ bool previousSchemaStillKept = false;
+
+ KexiDB::TableSchema *existingTable = 0;
+ if (replaceExisting) {
+ //get previous table (do not retrieve, though)
+ existingTable = d->tables_byname[tableName];
+ if (existingTable) {
+ if (existingTable == tableSchema) {
+ clearError();
+ setError(ERR_OBJECT_EXISTS,
+ i18n("Could not create the same table \"%1\" twice.").arg(tableSchema->name()) );
+ return false;
+ }
+//TODO(js): update any structure (e.g. queries) that depend on this table!
+ if (existingTable->id()>0)
+ tableSchema->m_id = existingTable->id(); //copy id from existing table
+ previousSchemaStillKept = true;
+ if (!dropTable( existingTable, false /*alsoRemoveSchema*/ ))
+ return false;
+ }
+ }
+ else {
+ if (this->tableSchema( tableSchema->name() ) != 0) {
+ clearError();
+ setError(ERR_OBJECT_EXISTS, i18n("Table \"%1\" already exists.").arg(tableSchema->name()) );
+ return false;
+ }
+ }
+
+/* if (replaceExisting) {
+ //get previous table (do not retrieve, though)
+ KexiDB::TableSchema *existingTable = d->tables_byname.take(name);
+ if (oldTable) {
+ }*/
+
+ TransactionGuard tg;
+ if (!beginAutoCommitTransaction(tg))
+ return false;
+
+ if (!drv_createTable(*tableSchema))
+ createTable_ERR;
+
+ //add schema data to kexi__* tables
+ if (!internalTable) {
+ //update kexi__objects
+ if (!storeObjectSchemaData( *tableSchema, true ))
+ createTable_ERR;
+
+ TableSchema *ts = d->tables_byname["kexi__fields"];
+ if (!ts)
+ return false;
+ //for sanity: remove field info (if any) for this table id
+ if (!KexiDB::deleteRow(*this, ts, "t_id", tableSchema->id()))
+ return false;
+
+ FieldList *fl = createFieldListForKexi__Fields(d->tables_byname["kexi__fields"]);
+ if (!fl)
+ return false;
+
+// int order = 0;
+ Field *f;
+ for (Field::ListIterator it( *tableSchema->fields() ); (f = it.current()); ++it/*, order++*/) {
+ QValueList<QVariant> vals;
+ buildValuesForKexi__Fields(vals, f);
+ if (!insertRecord(*fl, vals ))
+ createTable_ERR;
+ }
+ delete fl;
+
+ if (!storeExtendedTableSchemaData(*tableSchema))
+ createTable_ERR;
+ }
+
+ //finally:
+/* if (replaceExisting) {
+ if (existingTable) {
+ d->tables.take(existingTable->id());
+ delete existingTable;
+ }
+ }*/
+
+ bool res = commitAutoCommitTransaction(tg.transaction());
+
+ if (res) {
+ if (internalTable) {
+ //insert the internal table into structures
+ insertInternalTableSchema(tableSchema);
+ }
+ else {
+ if (previousSchemaStillKept) {
+ //remove previous table schema
+ removeTableSchemaInternal(tableSchema);
+ }
+ //store one schema object locally:
+ d->tables.insert(tableSchema->id(), tableSchema);
+ d->tables_byname.insert(tableSchema->name().lower(), tableSchema);
+ }
+ //ok, this table is not created by the connection
+ tableSchema->m_conn = this;
+ }
+ return res;
+}
+
+void Connection::removeTableSchemaInternal(TableSchema *tableSchema)
+{
+ d->tables_byname.remove(tableSchema->name());
+ d->tables.remove(tableSchema->id());
+}
+
+bool Connection::removeObject( uint objId )
+{
+ clearError();
+ //remove table schema from kexi__* tables
+ if (!KexiDB::deleteRow(*this, d->tables_byname["kexi__objects"], "o_id", objId) //schema entry
+ || !KexiDB::deleteRow(*this, d->tables_byname["kexi__objectdata"], "o_id", objId)) {//data blocks
+ setError(ERR_DELETE_SERVER_ERROR, i18n("Could not remove object's data."));
+ return false;
+ }
+ return true;
+}
+
+bool Connection::drv_dropTable( const QString& name )
+{
+ m_sql = "DROP TABLE " + escapeIdentifier(name);
+ return executeSQL(m_sql);
+}
+
+//! Drops a table corresponding to the name in the given schema
+/*! Drops a table according to the name given by the TableSchema, removing the
+ table and column definitions to kexi__* tables. Checks first that the
+ table is not a system table.
+
+ TODO: Should check that a database is currently in use? (c.f. createTable)
+*/
+tristate Connection::dropTable( KexiDB::TableSchema* tableSchema )
+{
+ return dropTable( tableSchema, true );
+}
+
+tristate Connection::dropTable( KexiDB::TableSchema* tableSchema, bool alsoRemoveSchema)
+{
+// Each SQL identifier needs to be escaped in the generated query.
+ clearError();
+ if (!tableSchema)
+ return false;
+
+ QString errmsg(i18n("Table \"%1\" cannot be removed.\n"));
+ //be sure that we handle the correct TableSchema object:
+ if (tableSchema->id() < 0
+ || this->tableSchema(tableSchema->name())!=tableSchema
+ || this->tableSchema(tableSchema->id())!=tableSchema)
+ {
+ setError(ERR_OBJECT_NOT_FOUND, errmsg.arg(tableSchema->name())
+ +i18n("Unexpected name or identifier."));
+ return false;
+ }
+
+ tristate res = closeAllTableSchemaChangeListeners(*tableSchema);
+ if (true!=res)
+ return res;
+
+ //sanity checks:
+ if (m_driver->isSystemObjectName( tableSchema->name() )) {
+ setError(ERR_SYSTEM_NAME_RESERVED, errmsg.arg(tableSchema->name()) + d->strItIsASystemObject());
+ return false;
+ }
+
+ TransactionGuard tg;
+ if (!beginAutoCommitTransaction(tg))
+ return false;
+
+ //for sanity we're checking if this table exists physically
+ if (drv_containsTable(tableSchema->name())) {
+ if (!drv_dropTable(tableSchema->name()))
+ return false;
+ }
+
+ TableSchema *ts = d->tables_byname["kexi__fields"];
+ if (!KexiDB::deleteRow(*this, ts, "t_id", tableSchema->id())) //field entries
+ return false;
+
+ //remove table schema from kexi__objects table
+ if (!removeObject( tableSchema->id() )) {
+ return false;
+ }
+
+ if (alsoRemoveSchema) {
+//! \todo js: update any structure (e.g. queries) that depend on this table!
+ tristate res = removeDataBlock( tableSchema->id(), "extended_schema");
+ if (!res)
+ return false;
+ removeTableSchemaInternal(tableSchema);
+ }
+ return commitAutoCommitTransaction(tg.transaction());
+}
+
+tristate Connection::dropTable( const QString& table )
+{
+ clearError();
+ TableSchema* ts = tableSchema( table );
+ if (!ts) {
+ setError(ERR_OBJECT_NOT_FOUND, i18n("Table \"%1\" does not exist.")
+ .arg(table));
+ return false;
+ }
+ return dropTable(ts);
+}
+
+tristate Connection::alterTable( TableSchema& tableSchema, TableSchema& newTableSchema )
+{
+ clearError();
+ tristate res = closeAllTableSchemaChangeListeners(tableSchema);
+ if (true!=res)
+ return res;
+
+ if (&tableSchema == &newTableSchema) {
+ setError(ERR_OBJECT_THE_SAME, i18n("Could not alter table \"%1\" using the same table.")
+ .arg(tableSchema.name()));
+ return false;
+ }
+//TODO(js): implement real altering
+//TODO(js): update any structure (e.g. query) that depend on this table!
+ bool ok, empty;
+#if 0//TODO ucomment:
+ empty = isEmpty( tableSchema, ok ) && ok;
+#else
+ empty = true;
+#endif
+ if (empty) {
+ ok = createTable(&newTableSchema, true/*replace*/);
+ }
+ return ok;
+}
+
+bool Connection::alterTableName(TableSchema& tableSchema, const QString& newName, bool replace)
+{
+ clearError();
+ if (&tableSchema!=d->tables[tableSchema.id()]) {
+ setError(ERR_OBJECT_NOT_FOUND, i18n("Unknown table \"%1\"").arg(tableSchema.name()));
+ return false;
+ }
+ if (newName.isEmpty() || !KexiUtils::isIdentifier(newName)) {
+ setError(ERR_INVALID_IDENTIFIER, i18n("Invalid table name \"%1\"").arg(newName));
+ return false;
+ }
+ const QString oldTableName = tableSchema.name();
+ const QString newTableName = newName.lower().stripWhiteSpace();
+ if (oldTableName.lower().stripWhiteSpace() == newTableName) {
+ setError(ERR_OBJECT_THE_SAME, i18n("Could rename table \"%1\" using the same name.")
+ .arg(newTableName));
+ return false;
+ }
+//TODO: alter table name for server DB backends!
+//TODO: what about objects (queries/forms) that use old name?
+//TODO
+ TableSchema *tableToReplace = this->tableSchema( newName );
+ const bool destTableExists = tableToReplace != 0;
+ const int origID = destTableExists ? tableToReplace->id() : -1; //will be reused in the new table
+ if (!replace && destTableExists) {
+ setError(ERR_OBJECT_EXISTS,
+ i18n("Could not rename table \"%1\" to \"%2\". Table \"%3\" already exists.")
+ .arg(tableSchema.name()).arg(newName).arg(newName));
+ return false;
+ }
+
+//helper:
+#define alterTableName_ERR \
+ tableSchema.setName(oldTableName) //restore old name
+
+ TransactionGuard tg;
+ if (!beginAutoCommitTransaction(tg))
+ return false;
+
+ // drop the table replaced (with schema)
+ if (destTableExists) {
+ if (!replace) {
+ return false;
+ }
+ if (!dropTable( newName )) {
+ return false;
+ }
+
+ // the new table owns the previous table's id:
+ if (!executeSQL(QString::fromLatin1("UPDATE kexi__objects SET o_id=%1 WHERE o_id=%2 AND o_type=%3")
+ .arg(origID).arg(tableSchema.id()).arg((int)TableObjectType)))
+ {
+ return false;
+ }
+ if (!executeSQL(QString::fromLatin1("UPDATE kexi__fields SET t_id=%1 WHERE t_id=%2")
+ .arg(origID).arg(tableSchema.id())))
+ {
+ return false;
+ }
+ d->tables.take(tableSchema.id());
+ d->tables.insert(origID, &tableSchema);
+ //maintain table ID
+ tableSchema.m_id = origID;
+ }
+
+ if (!drv_alterTableName(tableSchema, newTableName)) {
+ alterTableName_ERR;
+ return false;
+ }
+
+ // Update kexi__objects
+ //TODO
+ if (!executeSQL(QString::fromLatin1("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2")
+ .arg(m_driver->escapeString(tableSchema.name())).arg(tableSchema.id())))
+ {
+ alterTableName_ERR;
+ return false;
+ }
+//TODO what about caption?
+
+ //restore old name: it will be changed soon!
+ tableSchema.setName(oldTableName);
+
+ if (!commitAutoCommitTransaction(tg.transaction())) {
+ alterTableName_ERR;
+ return false;
+ }
+
+ //update tableSchema:
+ d->tables_byname.take(tableSchema.name());
+ tableSchema.setName(newTableName);
+ d->tables_byname.insert(tableSchema.name(), &tableSchema);
+ return true;
+}
+
+bool Connection::drv_alterTableName(TableSchema& tableSchema, const QString& newName)
+{
+ const QString oldTableName = tableSchema.name();
+ tableSchema.setName(newName);
+
+ if (!executeSQL(QString::fromLatin1("ALTER TABLE %1 RENAME TO %2")
+ .arg(escapeIdentifier(oldTableName)).arg(escapeIdentifier(newName))))
+ {
+ tableSchema.setName(oldTableName); //restore old name
+ return false;
+ }
+ return true;
+}
+
+bool Connection::dropQuery( KexiDB::QuerySchema* querySchema )
+{
+ clearError();
+ if (!querySchema)
+ return false;
+
+ TransactionGuard tg;
+ if (!beginAutoCommitTransaction(tg))
+ return false;
+
+/* TableSchema *ts = d->tables_byname["kexi__querydata"];
+ if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id()))
+ return false;
+
+ ts = d->tables_byname["kexi__queryfields"];
+ if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id()))
+ return false;
+
+ ts = d->tables_byname["kexi__querytables"];
+ if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id()))
+ return false;*/
+
+ //remove query schema from kexi__objects table
+ if (!removeObject( querySchema->id() )) {
+ return false;
+ }
+
+//TODO(js): update any structure that depend on this table!
+ d->queries_byname.remove(querySchema->name());
+ d->queries.remove(querySchema->id());
+
+ return commitAutoCommitTransaction(tg.transaction());
+}
+
+bool Connection::dropQuery( const QString& query )
+{
+ clearError();
+ QuerySchema* qs = querySchema( query );
+ if (!qs) {
+ setError(ERR_OBJECT_NOT_FOUND, i18n("Query \"%1\" does not exist.")
+ .arg(query));
+ return false;
+ }
+ return dropQuery(qs);
+}
+
+bool Connection::drv_createTable( const KexiDB::TableSchema& tableSchema )
+{
+ m_sql = createTableStatement(tableSchema);
+ KexiDBDbg<<"******** "<<m_sql<<endl;
+ return executeSQL(m_sql);
+}
+
+bool Connection::drv_createTable( const QString& tableSchemaName )
+{
+ TableSchema *ts = d->tables_byname[tableSchemaName];
+ if (!ts)
+ return false;
+ return drv_createTable(*ts);
+}
+
+bool Connection::beginAutoCommitTransaction(TransactionGuard &tg)
+{
+ if ((m_driver->d->features & Driver::IgnoreTransactions)
+ || !d->autoCommit)
+ {
+ tg.setTransaction( Transaction() );
+ return true;
+ }
+
+ // commit current transaction (if present) for drivers
+ // that allow single transaction per connection
+ if (m_driver->d->features & Driver::SingleTransactions) {
+ if (d->default_trans_started_inside) //only commit internally started transaction
+ if (!commitTransaction(d->default_trans, true)) {
+ tg.setTransaction( Transaction() );
+ return false; //we have a real error
+ }
+
+ d->default_trans_started_inside = d->default_trans.isNull();
+ if (!d->default_trans_started_inside) {
+ tg.setTransaction( d->default_trans );
+ tg.doNothing();
+ return true; //reuse externally started transaction
+ }
+ }
+ else if (!(m_driver->d->features & Driver::MultipleTransactions)) {
+ tg.setTransaction( Transaction() );
+ return true; //no trans. supported at all - just return
+ }
+ tg.setTransaction( beginTransaction() );
+ return !error();
+}
+
+bool Connection::commitAutoCommitTransaction(const Transaction& trans)
+{
+ if (m_driver->d->features & Driver::IgnoreTransactions)
+ return true;
+ if (trans.isNull() || !m_driver->transactionsSupported())
+ return true;
+ if (m_driver->d->features & Driver::SingleTransactions) {
+ if (!d->default_trans_started_inside) //only commit internally started transaction
+ return true; //give up
+ }
+ return commitTransaction(trans, true);
+}
+
+bool Connection::rollbackAutoCommitTransaction(const Transaction& trans)
+{
+ if (trans.isNull() || !m_driver->transactionsSupported())
+ return true;
+ return rollbackTransaction(trans);
+}
+
+#define SET_ERR_TRANS_NOT_SUPP \
+ { setError(ERR_UNSUPPORTED_DRV_FEATURE, \
+ i18n("Transactions are not supported for \"%1\" driver.").arg(m_driver->name() )); }
+
+#define SET_BEGIN_TR_ERROR \
+ { if (!error()) \
+ setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Begin transaction failed")); }
+
+Transaction Connection::beginTransaction()
+{
+ if (!checkIsDatabaseUsed())
+ return Transaction::null;
+ Transaction trans;
+ if (m_driver->d->features & Driver::IgnoreTransactions) {
+ //we're creating dummy transaction data here,
+ //so it will look like active
+ trans.m_data = new TransactionData(this);
+ d->transactions.append(trans);
+ return trans;
+ }
+ if (m_driver->d->features & Driver::SingleTransactions) {
+ if (d->default_trans.active()) {
+ setError(ERR_TRANSACTION_ACTIVE, i18n("Transaction already started.") );
+ return Transaction::null;
+ }
+ if (!(trans.m_data = drv_beginTransaction())) {
+ SET_BEGIN_TR_ERROR;
+ return Transaction::null;
+ }
+ d->default_trans = trans;
+ d->transactions.append(trans);
+ return d->default_trans;
+ }
+ if (m_driver->d->features & Driver::MultipleTransactions) {
+ if (!(trans.m_data = drv_beginTransaction())) {
+ SET_BEGIN_TR_ERROR;
+ return Transaction::null;
+ }
+ d->transactions.append(trans);
+ return trans;
+ }
+
+ SET_ERR_TRANS_NOT_SUPP;
+ return Transaction::null;
+}
+
+bool Connection::commitTransaction(const Transaction trans, bool ignore_inactive)
+{
+ if (!isDatabaseUsed())
+ return false;
+// if (!checkIsDatabaseUsed())
+ //return false;
+ if ( !m_driver->transactionsSupported()
+ && !(m_driver->d->features & Driver::IgnoreTransactions))
+ {
+ SET_ERR_TRANS_NOT_SUPP;
+ return false;
+ }
+ Transaction t = trans;
+ if (!t.active()) { //try default tr.
+ if (!d->default_trans.active()) {
+ if (ignore_inactive)
+ return true;
+ clearError();
+ setError(ERR_NO_TRANSACTION_ACTIVE, i18n("Transaction not started.") );
+ return false;
+ }
+ t = d->default_trans;
+ d->default_trans = Transaction::null; //now: no default tr.
+ }
+ bool ret = true;
+ if (! (m_driver->d->features & Driver::IgnoreTransactions) )
+ ret = drv_commitTransaction(t.m_data);
+ if (t.m_data)
+ t.m_data->m_active = false; //now this transaction if inactive
+ if (!d->dont_remove_transactions) //true=transaction obj will be later removed from list
+ d->transactions.remove(t);
+ if (!ret && !error())
+ setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Error on commit transaction"));
+ return ret;
+}
+
+bool Connection::rollbackTransaction(const Transaction trans, bool ignore_inactive)
+{
+ if (!isDatabaseUsed())
+ return false;
+// if (!checkIsDatabaseUsed())
+// return false;
+ if ( !m_driver->transactionsSupported()
+ && !(m_driver->d->features & Driver::IgnoreTransactions))
+ {
+ SET_ERR_TRANS_NOT_SUPP;
+ return false;
+ }
+ Transaction t = trans;
+ if (!t.active()) { //try default tr.
+ if (!d->default_trans.active()) {
+ if (ignore_inactive)
+ return true;
+ clearError();
+ setError(ERR_NO_TRANSACTION_ACTIVE, i18n("Transaction not started.") );
+ return false;
+ }
+ t = d->default_trans;
+ d->default_trans = Transaction::null; //now: no default tr.
+ }
+ bool ret = true;
+ if (! (m_driver->d->features & Driver::IgnoreTransactions) )
+ ret = drv_rollbackTransaction(t.m_data);
+ if (t.m_data)
+ t.m_data->m_active = false; //now this transaction if inactive
+ if (!d->dont_remove_transactions) //true=transaction obj will be later removed from list
+ d->transactions.remove(t);
+ if (!ret && !error())
+ setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Error on rollback transaction"));
+ return ret;
+}
+
+#undef SET_ERR_TRANS_NOT_SUPP
+#undef SET_BEGIN_TR_ERROR
+
+/*bool Connection::duringTransaction()
+{
+ return drv_duringTransaction();
+}*/
+
+Transaction& Connection::defaultTransaction() const
+{
+ return d->default_trans;
+}
+
+void Connection::setDefaultTransaction(const Transaction& trans)
+{
+ if (!isDatabaseUsed())
+ return;
+// if (!checkIsDatabaseUsed())
+ // return;
+ if ( !(m_driver->d->features & Driver::IgnoreTransactions)
+ && (!trans.active() || !m_driver->transactionsSupported()) )
+ {
+ return;
+ }
+ d->default_trans = trans;
+}
+
+const QValueList<Transaction>& Connection::transactions()
+{
+ return d->transactions;
+}
+
+bool Connection::autoCommit() const
+{
+ return d->autoCommit;
+}
+
+bool Connection::setAutoCommit(bool on)
+{
+ if (d->autoCommit == on || m_driver->d->features & Driver::IgnoreTransactions)
+ return true;
+ if (!drv_setAutoCommit(on))
+ return false;
+ d->autoCommit = on;
+ return true;
+}
+
+TransactionData* Connection::drv_beginTransaction()
+{
+ QString old_sql = m_sql; //don't
+ if (!executeSQL( "BEGIN" ))
+ return 0;
+ return new TransactionData(this);
+}
+
+bool Connection::drv_commitTransaction(TransactionData *)
+{
+ return executeSQL( "COMMIT" );
+}
+
+bool Connection::drv_rollbackTransaction(TransactionData *)
+{
+ return executeSQL( "ROLLBACK" );
+}
+
+bool Connection::drv_setAutoCommit(bool /*on*/)
+{
+ return true;
+}
+
+Cursor* Connection::executeQuery( const QString& statement, uint cursor_options )
+{
+ if (statement.isEmpty())
+ return 0;
+ Cursor *c = prepareQuery( statement, cursor_options );
+ if (!c)
+ return 0;
+ if (!c->open()) {//err - kill that
+ setError(c);
+ delete c;
+ return 0;
+ }
+ return c;
+}
+
+Cursor* Connection::executeQuery( QuerySchema& query, const QValueList<QVariant>& params,
+ uint cursor_options )
+{
+ Cursor *c = prepareQuery( query, params, cursor_options );
+ if (!c)
+ return 0;
+ if (!c->open()) {//err - kill that
+ setError(c);
+ delete c;
+ return 0;
+ }
+ return c;
+}
+
+Cursor* Connection::executeQuery( QuerySchema& query, uint cursor_options )
+{
+ return executeQuery(query, QValueList<QVariant>(), cursor_options);
+}
+
+Cursor* Connection::executeQuery( TableSchema& table, uint cursor_options )
+{
+ return executeQuery( *table.query(), cursor_options );
+}
+
+Cursor* Connection::prepareQuery( TableSchema& table, uint cursor_options )
+{
+ return prepareQuery( *table.query(), cursor_options );
+}
+
+Cursor* Connection::prepareQuery( QuerySchema& query, const QValueList<QVariant>& params,
+ uint cursor_options )
+{
+ Cursor* cursor = prepareQuery(query, cursor_options);
+ if (cursor)
+ cursor->setQueryParameters(params);
+ return cursor;
+}
+
+bool Connection::deleteCursor(Cursor *cursor)
+{
+ if (!cursor)
+ return false;
+ if (cursor->connection()!=this) {//illegal call
+ KexiDBWarn << "Connection::deleteCursor(): Cannot delete the cursor not owned by the same connection!" << endl;
+ return false;
+ }
+ const bool ret = cursor->close();
+ delete cursor;
+ return ret;
+}
+
+bool Connection::setupObjectSchemaData( const RowData &data, SchemaData &sdata )
+{
+ //not found: retrieve schema
+/* KexiDB::Cursor *cursor;
+ if (!(cursor = executeQuery( QString("select * from kexi__objects where o_id='%1'").arg(objId) )))
+ return false;
+ if (!cursor->moveFirst()) {
+ deleteCursor(cursor);
+ return false;
+ }*/
+ //if (!ok) {
+ //deleteCursor(cursor);
+ //return 0;
+// }
+ bool ok;
+ sdata.m_id = data[0].toInt(&ok);
+ if (!ok) {
+ return false;
+ }
+ sdata.m_name = data[2].toString();
+ if (!KexiUtils::isIdentifier( sdata.m_name )) {
+ setError(ERR_INVALID_IDENTIFIER, i18n("Invalid object name \"%1\"").arg(sdata.m_name));
+ return false;
+ }
+ sdata.m_caption = data[3].toString();
+ sdata.m_desc = data[4].toString();
+
+// KexiDBDbg<<"@@@ Connection::setupObjectSchemaData() == " << sdata.schemaDataDebugString() << endl;
+ return true;
+}
+
+tristate Connection::loadObjectSchemaData( int objectID, SchemaData &sdata )
+{
+ RowData data;
+ if (true!=querySingleRecord(QString::fromLatin1(
+ "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1")
+ .arg(objectID), data))
+ return cancelled;
+ return setupObjectSchemaData( data, sdata );
+}
+
+tristate Connection::loadObjectSchemaData( int objectType, const QString& objectName, SchemaData &sdata )
+{
+ RowData data;
+ if (true!=querySingleRecord(QString::fromLatin1("SELECT o_id, o_type, o_name, o_caption, o_desc "
+ "FROM kexi__objects WHERE o_type=%1 AND lower(o_name)=%2")
+ .arg(objectType).arg(m_driver->valueToSQL(Field::Text, objectName.lower())), data))
+ return cancelled;
+ return setupObjectSchemaData( data, sdata );
+}
+
+bool Connection::storeObjectSchemaData( SchemaData &sdata, bool newObject )
+{
+ TableSchema *ts = d->tables_byname["kexi__objects"];
+ if (!ts)
+ return false;
+ if (newObject) {
+ int existingID;
+ if (true == querySingleNumber(QString::fromLatin1(
+ "SELECT o_id FROM kexi__objects WHERE o_type=%1 AND lower(o_name)=%2")
+ .arg(sdata.type()).arg(m_driver->valueToSQL(Field::Text, sdata.name().lower())), existingID))
+ {
+ //we already have stored a schema data with the same name and type:
+ //just update it's properties as it would be existing object
+ sdata.m_id = existingID;
+ newObject = false;
+ }
+ }
+ if (newObject) {
+ FieldList *fl;
+ bool ok;
+ if (sdata.id()<=0) {//get new ID
+ fl = ts->subList("o_type", "o_name", "o_caption", "o_desc");
+ ok = fl!=0;
+ if (ok && !insertRecord(*fl, QVariant(sdata.type()), QVariant(sdata.name()),
+ QVariant(sdata.caption()), QVariant(sdata.description()) ))
+ ok = false;
+ delete fl;
+ if (!ok)
+ return false;
+ //fetch newly assigned ID
+//! @todo safe to cast it?
+ int obj_id = (int)lastInsertedAutoIncValue("o_id",*ts);
+ KexiDBDbg << "######## NEW obj_id == " << obj_id << endl;
+ if (obj_id<=0)
+ return false;
+ sdata.m_id = obj_id;
+ return true;
+ } else {
+ fl = ts->subList("o_id", "o_type", "o_name", "o_caption", "o_desc");
+ ok = fl!=0;
+ if (ok && !insertRecord(*fl, QVariant(sdata.id()), QVariant(sdata.type()), QVariant(sdata.name()),
+ QVariant(sdata.caption()), QVariant(sdata.description()) ))
+ ok = false;
+ delete fl;
+ return ok;
+ }
+ }
+ //existing object:
+ return executeSQL(QString("UPDATE kexi__objects SET o_type=%2, o_caption=%3, o_desc=%4 WHERE o_id=%1")
+ .arg(sdata.id()).arg(sdata.type())
+ .arg(m_driver->valueToSQL(KexiDB::Field::Text, sdata.caption()))
+ .arg(m_driver->valueToSQL(KexiDB::Field::Text, sdata.description())) );
+}
+
+tristate Connection::querySingleRecordInternal(RowData &data, const QString* sql, QuerySchema* query,
+ bool addLimitTo1)
+{
+ Q_ASSERT(sql || query);
+//! @todo does not work with non-SQL data sources
+ if (sql)
+ m_sql = addLimitTo1 ? (*sql + " LIMIT 1") : *sql; // is this safe?
+ KexiDB::Cursor *cursor;
+ if (!(cursor = sql ? executeQuery( m_sql ) : executeQuery( *query ))) {
+ KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl;
+ return false;
+ }
+ if (!cursor->moveFirst() || cursor->eof()) {
+ const tristate result = cursor->error() ? false : cancelled;
+ KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() m_sql=" << m_sql << endl;
+ setError(cursor);
+ deleteCursor(cursor);
+ return result;
+ }
+ cursor->storeCurrentRow(data);
+ return deleteCursor(cursor);
+}
+
+tristate Connection::querySingleRecord(const QString& sql, RowData &data, bool addLimitTo1)
+{
+ return querySingleRecordInternal(data, &sql, 0, addLimitTo1);
+}
+
+tristate Connection::querySingleRecord(QuerySchema& query, RowData &data, bool addLimitTo1)
+{
+ return querySingleRecordInternal(data, 0, &query, addLimitTo1);
+}
+
+bool Connection::checkIfColumnExists(Cursor *cursor, uint column)
+{
+ if (column >= cursor->fieldCount()) {
+ setError(ERR_CURSOR_RECORD_FETCHING, i18n("Column %1 does not exist for the query.").arg(column));
+ return false;
+ }
+ return true;
+}
+
+tristate Connection::querySingleString(const QString& sql, QString &value, uint column, bool addLimitTo1)
+{
+ KexiDB::Cursor *cursor;
+ m_sql = addLimitTo1 ? (sql + " LIMIT 1") : sql; // is this safe?;
+ if (!(cursor = executeQuery( m_sql ))) {
+ KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl;
+ return false;
+ }
+ if (!cursor->moveFirst() || cursor->eof()) {
+ const tristate result = cursor->error() ? false : cancelled;
+ KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() " << m_sql << endl;
+ deleteCursor(cursor);
+ return result;
+ }
+ if (!checkIfColumnExists(cursor, column)) {
+ deleteCursor(cursor);
+ return false;
+ }
+ value = cursor->value(column).toString();
+ return deleteCursor(cursor);
+}
+
+tristate Connection::querySingleNumber(const QString& sql, int &number, uint column, bool addLimitTo1)
+{
+ static QString str;
+ static bool ok;
+ const tristate result = querySingleString(sql, str, column, addLimitTo1);
+ if (result!=true)
+ return result;
+ number = str.toInt(&ok);
+ return ok;
+}
+
+bool Connection::queryStringList(const QString& sql, QStringList& list, uint column)
+{
+ KexiDB::Cursor *cursor;
+ clearError();
+ m_sql = sql;
+ if (!(cursor = executeQuery( m_sql ))) {
+ KexiDBWarn << "Connection::queryStringList(): !executeQuery() " << m_sql << endl;
+ return false;
+ }
+ cursor->moveFirst();
+ if (cursor->error()) {
+ setError(cursor);
+ deleteCursor(cursor);
+ return false;
+ }
+ if (!cursor->eof() && !checkIfColumnExists(cursor, column)) {
+ deleteCursor(cursor);
+ return false;
+ }
+ list.clear();
+ while (!cursor->eof()) {
+ list.append( cursor->value(column).toString() );
+ if (!cursor->moveNext() && cursor->error()) {
+ setError(cursor);
+ deleteCursor(cursor);
+ return false;
+ }
+ }
+ return deleteCursor(cursor);
+}
+
+bool Connection::resultExists(const QString& sql, bool &success, bool addLimitTo1)
+{
+ KexiDB::Cursor *cursor;
+ //optimization
+ if (m_driver->beh->SELECT_1_SUBQUERY_SUPPORTED) {
+ //this is at least for sqlite
+ if (addLimitTo1 && sql.left(6).upper() == "SELECT")
+ m_sql = QString("SELECT 1 FROM (") + sql + ") LIMIT 1"; // is this safe?;
+ else
+ m_sql = sql;
+ }
+ else {
+ if (addLimitTo1 && sql.left(6).upper() == "SELECT")
+ m_sql = sql + " LIMIT 1"; //not always safe!
+ else
+ m_sql = sql;
+ }
+ if (!(cursor = executeQuery( m_sql ))) {
+ KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl;
+ success = false;
+ return false;
+ }
+ if (!cursor->moveFirst() || cursor->eof()) {
+ success = !cursor->error();
+ KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() " << m_sql << endl;
+ setError(cursor);
+ deleteCursor(cursor);
+ return false;
+ }
+ success = deleteCursor(cursor);
+ return true;
+}
+
+bool Connection::isEmpty( TableSchema& table, bool &success )
+{
+ return !resultExists( selectStatement( *table.query() ), success );
+}
+
+//! Used by addFieldPropertyToExtendedTableSchemaData()
+static void createExtendedTableSchemaMainElementIfNeeded(
+ QDomDocument& doc, QDomElement& extendedTableSchemaMainEl,
+ bool& extendedTableSchemaStringIsEmpty)
+{
+ if (!extendedTableSchemaStringIsEmpty)
+ return;
+ //init document
+ extendedTableSchemaMainEl = doc.createElement("EXTENDED_TABLE_SCHEMA");
+ doc.appendChild( extendedTableSchemaMainEl );
+ extendedTableSchemaMainEl.setAttribute("version", QString::number(KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION));
+ extendedTableSchemaStringIsEmpty = false;
+}
+
+//! Used by addFieldPropertyToExtendedTableSchemaData()
+static void createExtendedTableSchemaFieldElementIfNeeded(QDomDocument& doc,
+ QDomElement& extendedTableSchemaMainEl, const QString& fieldName, QDomElement& extendedTableSchemaFieldEl,
+ bool append = true)
+{
+ if (!extendedTableSchemaFieldEl.isNull())
+ return;
+ extendedTableSchemaFieldEl = doc.createElement("field");
+ if (append)
+ extendedTableSchemaMainEl.appendChild( extendedTableSchemaFieldEl );
+ extendedTableSchemaFieldEl.setAttribute("name", fieldName);
+}
+
+/*! @internal used by storeExtendedTableSchemaData()
+ Creates DOM node for \a propertyName and \a propertyValue.
+ Creates enclosing EXTENDED_TABLE_SCHEMA element if EXTENDED_TABLE_SCHEMA is true.
+ Updates extendedTableSchemaStringIsEmpty and extendedTableSchemaMainEl afterwards.
+ If extendedTableSchemaFieldEl is null, creates <field> element (with optional
+ "custom" attribute is \a custom is false). */
+static void addFieldPropertyToExtendedTableSchemaData(
+ Field *f, const char* propertyName, const QVariant& propertyValue,
+ QDomDocument& doc, QDomElement& extendedTableSchemaMainEl,
+ QDomElement& extendedTableSchemaFieldEl,
+ bool& extendedTableSchemaStringIsEmpty,
+ bool custom = false )
+{
+ createExtendedTableSchemaMainElementIfNeeded(doc,
+ extendedTableSchemaMainEl, extendedTableSchemaStringIsEmpty);
+ createExtendedTableSchemaFieldElementIfNeeded(
+ doc, extendedTableSchemaMainEl, f->name(), extendedTableSchemaFieldEl);
+
+ //create <property>
+ QDomElement extendedTableSchemaFieldPropertyEl = doc.createElement("property");
+ extendedTableSchemaFieldEl.appendChild( extendedTableSchemaFieldPropertyEl );
+ if (custom)
+ extendedTableSchemaFieldPropertyEl.setAttribute("custom", "true");
+ extendedTableSchemaFieldPropertyEl.setAttribute("name", propertyName);
+ QDomElement extendedTableSchemaFieldPropertyValueEl;
+ switch (propertyValue.type()) {
+ case QVariant::String:
+ extendedTableSchemaFieldPropertyValueEl = doc.createElement("string");
+ break;
+ case QVariant::CString:
+ extendedTableSchemaFieldPropertyValueEl = doc.createElement("cstring");
+ break;
+ case QVariant::Int:
+ case QVariant::Double:
+ case QVariant::UInt:
+ case QVariant::LongLong:
+ case QVariant::ULongLong:
+ extendedTableSchemaFieldPropertyValueEl = doc.createElement("number");
+ break;
+ case QVariant::Bool:
+ extendedTableSchemaFieldPropertyValueEl = doc.createElement("bool");
+ break;
+ default:
+//! @todo add more QVariant types
+ KexiDBFatal << "addFieldPropertyToExtendedTableSchemaData(): impl. error" << endl;
+ }
+ extendedTableSchemaFieldPropertyEl.appendChild( extendedTableSchemaFieldPropertyValueEl );
+ extendedTableSchemaFieldPropertyValueEl.appendChild(
+ doc.createTextNode( propertyValue.toString() ) );
+}
+
+bool Connection::storeExtendedTableSchemaData(TableSchema& tableSchema)
+{
+//! @todo future: save in older versions if neeed
+ QDomDocument doc("EXTENDED_TABLE_SCHEMA");
+ QDomElement extendedTableSchemaMainEl;
+ bool extendedTableSchemaStringIsEmpty = true;
+
+ //for each field:
+ Field *f;
+ for (Field::ListIterator it( *tableSchema.fields() ); (f = it.current()); ++it) {
+ QDomElement extendedTableSchemaFieldEl;
+ if (f->visibleDecimalPlaces()>=0/*nondefault*/ && KexiDB::supportsVisibleDecimalPlacesProperty(f->type())) {
+ addFieldPropertyToExtendedTableSchemaData(
+ f, "visibleDecimalPlaces", f->visibleDecimalPlaces(), doc,
+ extendedTableSchemaMainEl, extendedTableSchemaFieldEl,
+ extendedTableSchemaStringIsEmpty );
+ }
+ // boolean field with "not null"
+
+ // add custom properties
+ const Field::CustomPropertiesMap customProperties(f->customProperties());
+ foreach( Field::CustomPropertiesMap::ConstIterator, itCustom, customProperties ) {
+ addFieldPropertyToExtendedTableSchemaData(
+ f, itCustom.key(), itCustom.data(), doc,
+ extendedTableSchemaMainEl, extendedTableSchemaFieldEl, extendedTableSchemaStringIsEmpty,
+ /*custom*/true );
+ }
+ // save lookup table specification, if present
+ LookupFieldSchema *lookupFieldSchema = tableSchema.lookupFieldSchema( *f );
+ if (lookupFieldSchema) {
+ createExtendedTableSchemaFieldElementIfNeeded(
+ doc, extendedTableSchemaMainEl, f->name(), extendedTableSchemaFieldEl, false/* !append */);
+ LookupFieldSchema::saveToDom(*lookupFieldSchema, doc, extendedTableSchemaFieldEl);
+
+ if (extendedTableSchemaFieldEl.hasChildNodes()) {
+ // this element provides the definition, so let's append it now
+ createExtendedTableSchemaMainElementIfNeeded(doc, extendedTableSchemaMainEl,
+ extendedTableSchemaStringIsEmpty);
+ extendedTableSchemaMainEl.appendChild( extendedTableSchemaFieldEl );
+ }
+ }
+ }
+
+ // Store extended schema information (see ExtendedTableSchemaInformation in Kexi Wiki)
+ if (extendedTableSchemaStringIsEmpty) {
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(QString("** Extended table schema REMOVED."));
+#endif
+ if (!removeDataBlock( tableSchema.id(), "extended_schema"))
+ return false;
+ }
+ else {
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(QString("** Extended table schema set to:\n")+doc.toString(4));
+#endif
+ if (!storeDataBlock( tableSchema.id(), doc.toString(1), "extended_schema" ))
+ return false;
+ }
+ return true;
+}
+
+bool Connection::loadExtendedTableSchemaData(TableSchema& tableSchema)
+{
+#define loadExtendedTableSchemaData_ERR \
+ { setError(i18n("Error while loading extended table schema information.")); \
+ return false; }
+#define loadExtendedTableSchemaData_ERR2(details) \
+ { setError(i18n("Error while loading extended table schema information."), details); \
+ return false; }
+#define loadExtendedTableSchemaData_ERR3(data) \
+ { setError(i18n("Error while loading extended table schema information."), \
+ i18n("Invalid XML data: ") + data.left(1024) ); \
+ return false; }
+
+ // Load extended schema information, if present (see ExtendedTableSchemaInformation in Kexi Wiki)
+ QString extendedTableSchemaString;
+ tristate res = loadDataBlock( tableSchema.id(), extendedTableSchemaString, "extended_schema" );
+ if (!res)
+ loadExtendedTableSchemaData_ERR;
+ // extendedTableSchemaString will be just empty if there is no such data block
+
+#ifdef KEXIDB_LOOKUP_FIELD_TEST
+//<temp. for LookupFieldSchema tests>
+ if (tableSchema.name()=="cars") {
+ LookupFieldSchema *lookupFieldSchema = new LookupFieldSchema();
+ lookupFieldSchema->rowSource().setType(LookupFieldSchema::RowSource::Table);
+ lookupFieldSchema->rowSource().setName("persons");
+ lookupFieldSchema->setBoundColumn(0); //id
+ lookupFieldSchema->setVisibleColumn(3); //surname
+ tableSchema.setLookupFieldSchema( "owner", lookupFieldSchema );
+ }
+//</temp. for LookupFieldSchema tests>
+#endif
+
+ if (extendedTableSchemaString.isEmpty())
+ return true;
+
+ QDomDocument doc;
+ QString errorMsg;
+ int errorLine, errorColumn;
+ if (!doc.setContent( extendedTableSchemaString, &errorMsg, &errorLine, &errorColumn ))
+ loadExtendedTableSchemaData_ERR2( i18n("Error in XML data: \"%1\" in line %2, column %3.\nXML data: ")
+ .arg(errorMsg).arg(errorLine).arg(errorColumn) + extendedTableSchemaString.left(1024));
+
+//! @todo look at the current format version (KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION)
+
+ if (doc.doctype().name()!="EXTENDED_TABLE_SCHEMA")
+ loadExtendedTableSchemaData_ERR3( extendedTableSchemaString );
+
+ QDomElement docEl = doc.documentElement();
+ if (docEl.tagName()!="EXTENDED_TABLE_SCHEMA")
+ loadExtendedTableSchemaData_ERR3( extendedTableSchemaString );
+
+ for (QDomNode n = docEl.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ QDomElement fieldEl = n.toElement();
+ if (fieldEl.tagName()=="field") {
+ Field *f = tableSchema.field( fieldEl.attribute("name") );
+ if (f) {
+ //set properties of the field:
+//! @todo more properties
+ for (QDomNode propNode = fieldEl.firstChild();
+ !propNode.isNull(); propNode = propNode.nextSibling())
+ {
+ QDomElement propEl = propNode.toElement();
+ bool ok;
+ int intValue;
+ if (propEl.tagName()=="property") {
+ QCString propertyName = propEl.attribute("name").latin1();
+ if (propEl.attribute("custom")=="true") {
+ //custom property
+ f->setCustomProperty(propertyName,
+ KexiDB::loadPropertyValueFromDom( propEl.firstChild() ));
+ }
+ else if (propertyName == "visibleDecimalPlaces"
+ && KexiDB::supportsVisibleDecimalPlacesProperty(f->type()))
+ {
+ intValue = KexiDB::loadIntPropertyValueFromDom( propEl.firstChild(), &ok );
+ if (ok)
+ f->setVisibleDecimalPlaces(intValue);
+ }
+//! @todo more properties...
+ }
+ else if (propEl.tagName()=="lookup-column") {
+ LookupFieldSchema *lookupFieldSchema = LookupFieldSchema::loadFromDom(propEl);
+ if (lookupFieldSchema)
+ lookupFieldSchema->debug();
+ tableSchema.setLookupFieldSchema( f->name(), lookupFieldSchema );
+ }
+ }
+ }
+ else {
+ KexiDBWarn << "Connection::loadExtendedTableSchemaData(): no such field \""
+ << fieldEl.attribute("name") << "\" in table \"" << tableSchema.name() << "\"" << endl;
+ }
+ }
+ }
+
+ return true;
+}
+
+KexiDB::Field* Connection::setupField( const RowData &data )
+{
+ bool ok = true;
+ int f_int_type = data.at(1).toInt(&ok);
+ if (f_int_type<=Field::InvalidType || f_int_type>Field::LastType)
+ ok = false;
+ if (!ok)
+ return 0;
+ Field::Type f_type = (Field::Type)f_int_type;
+ int f_len = QMAX( 0, data.at(3).toInt(&ok) );
+ if (!ok)
+ return 0;
+ int f_prec = data.at(4).toInt(&ok);
+ if (!ok)
+ return 0;
+ int f_constr = data.at(5).toInt(&ok);
+ if (!ok)
+ return 0;
+ int f_opts = data.at(6).toInt(&ok);
+ if (!ok)
+ return 0;
+
+ if (!KexiUtils::isIdentifier( data.at(2).toString() )) {
+ setError(ERR_INVALID_IDENTIFIER, i18n("Invalid object name \"%1\"")
+ .arg( data.at(2).toString() ));
+ ok = false;
+ return 0;
+ }
+
+ Field *f = new Field(
+ data.at(2).toString(), f_type, f_constr, f_opts, f_len, f_prec );
+
+ f->setDefaultValue( KexiDB::stringToVariant(data.at(7).toString(), Field::variantType( f_type ), ok) );
+ if (!ok) {
+ KexiDBWarn << "Connection::setupTableSchema() problem with KexiDB::stringToVariant("
+ << data.at(7).toString() << ")" << endl;
+ }
+ ok = true; //problem with defaultValue is not critical
+
+ f->m_caption = data.at(9).toString();
+ f->m_desc = data.at(10).toString();
+ return f;
+}
+
+KexiDB::TableSchema* Connection::setupTableSchema( const RowData &data )
+{
+ TableSchema *t = new TableSchema( this );
+ if (!setupObjectSchemaData( data, *t )) {
+ delete t;
+ return 0;
+ }
+
+ KexiDB::Cursor *cursor;
+ if (!(cursor = executeQuery(
+ QString::fromLatin1("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, "
+ "f_options, f_default, f_order, f_caption, f_help"
+ " FROM kexi__fields WHERE t_id=%1 ORDER BY f_order").arg(t->m_id) )))
+ {
+ delete t;
+ return 0;
+ }
+ if (!cursor->moveFirst()) {
+ if (!cursor->error() && cursor->eof()) {
+ setError(i18n("Table has no fields defined."));
+ }
+ deleteCursor(cursor);
+ delete t;
+ return 0;
+ }
+
+ // For each field: load its schema
+ RowData fieldData;
+ bool ok = true;
+ while (!cursor->eof()) {
+// KexiDBDbg<<"@@@ f_name=="<<cursor->value(2).asCString()<<endl;
+ cursor->storeCurrentRow(fieldData);
+ Field *f = setupField(fieldData);
+ if (!f) {
+ ok = false;
+ break;
+ }
+ t->addField(f);
+ cursor->moveNext();
+ }
+
+ if (!ok) {//error:
+ deleteCursor(cursor);
+ delete t;
+ return 0;
+ }
+
+ if (!deleteCursor(cursor)) {
+ delete t;
+ return 0;
+ }
+
+ if (!loadExtendedTableSchemaData(*t)) {
+ delete t;
+ return 0;
+ }
+ //store locally:
+ d->tables.insert(t->m_id, t);
+ d->tables_byname.insert(t->m_name.lower(), t);
+ return t;
+}
+
+TableSchema* Connection::tableSchema( const QString& tableName )
+{
+ QString m_tableName = tableName.lower();
+ TableSchema *t = d->tables_byname[m_tableName];
+ if (t)
+ return t;
+ //not found: retrieve schema
+ RowData data;
+ if (true!=querySingleRecord(QString::fromLatin1(
+ "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE lower(o_name)='%1' AND o_type=%2")
+ .arg(m_tableName).arg(KexiDB::TableObjectType), data))
+ return 0;
+
+ return setupTableSchema(data);
+}
+
+TableSchema* Connection::tableSchema( int tableId )
+{
+ TableSchema *t = d->tables[tableId];
+ if (t)
+ return t;
+ //not found: retrieve schema
+ RowData data;
+ if (true!=querySingleRecord(QString::fromLatin1(
+ "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1")
+ .arg(tableId), data))
+ return 0;
+
+ return setupTableSchema(data);
+}
+
+tristate Connection::loadDataBlock( int objectID, QString &dataString, const QString& dataID )
+{
+ if (objectID<=0)
+ return false;
+ return querySingleString(
+ QString("SELECT o_data FROM kexi__objectdata WHERE o_id=") + QString::number(objectID)
+ + " AND " + KexiDB::sqlWhere(m_driver, KexiDB::Field::Text, "o_sub_id", dataID),
+ dataString );
+}
+
+bool Connection::storeDataBlock( int objectID, const QString &dataString, const QString& dataID )
+{
+ if (objectID<=0)
+ return false;
+ QString sql(QString::fromLatin1("SELECT kexi__objectdata.o_id FROM kexi__objectdata WHERE o_id=%1").arg(objectID));
+ QString sql_sub( KexiDB::sqlWhere(m_driver, KexiDB::Field::Text, "o_sub_id", dataID) );
+
+ bool ok, exists;
+ exists = resultExists(sql + " and " + sql_sub, ok);
+ if (!ok)
+ return false;
+ if (exists) {
+ return executeSQL( "UPDATE kexi__objectdata SET o_data="
+ + m_driver->valueToSQL( KexiDB::Field::LongText, dataString )
+ + " WHERE o_id=" + QString::number(objectID) + " AND " + sql_sub );
+ }
+ return executeSQL(
+ QString::fromLatin1("INSERT INTO kexi__objectdata (o_id, o_data, o_sub_id) VALUES (")
+ + QString::number(objectID) +"," + m_driver->valueToSQL( KexiDB::Field::LongText, dataString )
+ + "," + m_driver->valueToSQL( KexiDB::Field::Text, dataID ) + ")" );
+}
+
+bool Connection::removeDataBlock( int objectID, const QString& dataID)
+{
+ if (objectID<=0)
+ return false;
+ if (dataID.isEmpty())
+ return KexiDB::deleteRow(*this, "kexi__objectdata", "o_id", QString::number(objectID));
+ else
+ return KexiDB::deleteRow(*this, "kexi__objectdata",
+ "o_id", KexiDB::Field::Integer, objectID, "o_sub_id", KexiDB::Field::Text, dataID);
+}
+
+KexiDB::QuerySchema* Connection::setupQuerySchema( const RowData &data )
+{
+ bool ok = true;
+ const int objID = data[0].toInt(&ok);
+ if (!ok)
+ return false;
+ QString sqlText;
+ if (!loadDataBlock( objID, sqlText, "sql" )) {
+ setError(ERR_OBJECT_NOT_FOUND,
+ i18n("Could not find definition for query \"%1\". Removing this query is recommended.")
+ .arg(data[2].toString()));
+ return 0;
+ }
+ d->parser()->parse( sqlText );
+ KexiDB::QuerySchema *query = d->parser()->query();
+ //error?
+ if (!query) {
+ setError(ERR_SQL_PARSE_ERROR,
+ i18n("<p>Could not load definition for query \"%1\". "
+ "SQL statement for this query is invalid:<br><tt>%2</tt></p>\n"
+ "<p>You can open this query in Text View and correct it.</p>").arg(data[2].toString())
+ .arg(d->parser()->statement()));
+ return 0;
+ }
+ if (!setupObjectSchemaData( data, *query )) {
+ delete query;
+ return 0;
+ }
+ d->queries.insert(query->m_id, query);
+ d->queries_byname.insert(query->m_name, query);
+ return query;
+}
+
+QuerySchema* Connection::querySchema( const QString& queryName )
+{
+ QString m_queryName = queryName.lower();
+ QuerySchema *q = d->queries_byname[m_queryName];
+ if (q)
+ return q;
+ //not found: retrieve schema
+ RowData data;
+ if (true!=querySingleRecord(QString::fromLatin1(
+ "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE lower(o_name)='%1' AND o_type=%2")
+ .arg(m_queryName).arg(KexiDB::QueryObjectType), data))
+ return 0;
+
+ return setupQuerySchema(data);
+}
+
+QuerySchema* Connection::querySchema( int queryId )
+{
+ QuerySchema *q = d->queries[queryId];
+ if (q)
+ return q;
+ //not found: retrieve schema
+ clearError();
+ RowData data;
+ if (true!=querySingleRecord(QString::fromLatin1(
+ "SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1").arg(queryId), data))
+ return 0;
+
+ return setupQuerySchema(data);
+}
+
+bool Connection::setQuerySchemaObsolete( const QString& queryName )
+{
+ QuerySchema* oldQuery = querySchema( queryName );
+ if (!oldQuery)
+ return false;
+ d->obsoleteQueries.append(oldQuery);
+ d->queries_byname.take(queryName);
+ d->queries.take(oldQuery->id());
+ return true;
+}
+
+TableSchema* Connection::newKexiDBSystemTableSchema(const QString& tsname)
+{
+ TableSchema *ts = new TableSchema(tsname.lower());
+ insertInternalTableSchema( ts );
+ return ts;
+}
+
+bool Connection::isInternalTableSchema(const QString& tableName)
+{
+ return (d->kexiDBSystemTables[ d->tables_byname[tableName] ])
+ // these are here for compatiblility because we're no longer instantiate
+ // them but can exist in projects created with previous Kexi versions:
+ || tableName=="kexi__final" || tableName=="kexi__useractions";
+}
+
+void Connection::insertInternalTableSchema(TableSchema *tableSchema)
+{
+ tableSchema->setKexiDBSystem(true);
+ d->kexiDBSystemTables.insert(tableSchema, tableSchema);
+ d->tables_byname.insert(tableSchema->name(), tableSchema);
+}
+
+//! Creates kexi__* tables.
+bool Connection::setupKexiDBSystemSchema()
+{
+ if (!d->kexiDBSystemTables.isEmpty())
+ return true; //already set up
+
+ TableSchema *t_objects = newKexiDBSystemTableSchema("kexi__objects");
+ t_objects->addField( new Field("o_id", Field::Integer, Field::PrimaryKey | Field::AutoInc, Field::Unsigned) )
+ .addField( new Field("o_type", Field::Byte, 0, Field::Unsigned) )
+ .addField( new Field("o_name", Field::Text) )
+ .addField( new Field("o_caption", Field::Text ) )
+ .addField( new Field("o_desc", Field::LongText ) );
+
+ t_objects->debug();
+
+ TableSchema *t_objectdata = newKexiDBSystemTableSchema("kexi__objectdata");
+ t_objectdata->addField( new Field("o_id", Field::Integer, Field::NotNull, Field::Unsigned) )
+ .addField( new Field("o_data", Field::LongText) )
+ .addField( new Field("o_sub_id", Field::Text) );
+
+ TableSchema *t_fields = newKexiDBSystemTableSchema("kexi__fields");
+ t_fields->addField( new Field("t_id", Field::Integer, 0, Field::Unsigned) )
+ .addField( new Field("f_type", Field::Byte, 0, Field::Unsigned) )
+ .addField( new Field("f_name", Field::Text ) )
+ .addField( new Field("f_length", Field::Integer ) )
+ .addField( new Field("f_precision", Field::Integer ) )
+ .addField( new Field("f_constraints", Field::Integer ) )
+ .addField( new Field("f_options", Field::Integer ) )
+ .addField( new Field("f_default", Field::Text ) )
+ //these are additional properties:
+ .addField( new Field("f_order", Field::Integer ) )
+ .addField( new Field("f_caption", Field::Text ) )
+ .addField( new Field("f_help", Field::LongText ) );
+
+/* TableSchema *t_querydata = newKexiDBSystemTableSchema("kexi__querydata");
+ t_querydata->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) )
+ .addField( new Field("q_sql", Field::LongText ) )
+ .addField( new Field("q_valid", Field::Boolean ) );
+
+ TableSchema *t_queryfields = newKexiDBSystemTableSchema("kexi__queryfields");
+ t_queryfields->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) )
+ .addField( new Field("f_order", Field::Integer ) )
+ .addField( new Field("f_id", Field::Integer ) )
+ .addField( new Field("f_tab_asterisk", Field::Integer, 0, Field::Unsigned) )
+ .addField( new Field("f_alltab_asterisk", Field::Boolean) );
+
+ TableSchema *t_querytables = newKexiDBSystemTableSchema("kexi__querytables");
+ t_querytables->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) )
+ .addField( new Field("t_id", Field::Integer, 0, Field::Unsigned) )
+ .addField( new Field("t_order", Field::Integer, 0, Field::Unsigned) );*/
+
+ TableSchema *t_db = newKexiDBSystemTableSchema("kexi__db");
+ t_db->addField( new Field("db_property", Field::Text, Field::NoConstraints, Field::NoOptions, 32 ) )
+ .addField( new Field("db_value", Field::LongText ) );
+
+/* moved to KexiProject...
+ TableSchema *t_parts = newKexiDBSystemTableSchema("kexi__parts");
+ t_parts->addField( new Field("p_id", Field::Integer, Field::PrimaryKey | Field::AutoInc, Field::Unsigned) )
+ .addField( new Field("p_name", Field::Text) )
+ .addField( new Field("p_mime", Field::Text ) )
+ .addField( new Field("p_url", Field::Text ) );
+*/
+
+/*UNUSED
+ TableSchema *t_final = newKexiDBSystemTableSchema("kexi__final");
+ t_final->addField( new Field("p_id", Field::Integer, 0, Field::Unsigned) )
+ .addField( new Field("property", Field::LongText ) )
+ .addField( new Field("value", Field::BLOB) );
+
+ TableSchema *t_useractions = newKexiDBSystemTableSchema("kexi__useractions");
+ t_useractions->addField( new Field("p_id", Field::Integer, 0, Field::Unsigned) )
+ .addField( new Field("scope", Field::Integer ) )
+ .addField( new Field("name", Field::LongText ) )
+ .addField( new Field("text", Field::LongText ) )
+ .addField( new Field("icon", Field::LongText ) )
+ .addField( new Field("method", Field::Integer ) )
+ .addField( new Field("arguments", Field::LongText) );
+*/
+ return true;
+}
+
+void Connection::removeMe(TableSchema *ts)
+{
+ if (ts && !m_destructor_started) {
+ d->tables.take(ts->id());
+ d->tables_byname.take(ts->name());
+ }
+}
+
+QString Connection::anyAvailableDatabaseName()
+{
+ if (!d->availableDatabaseName.isEmpty()) {
+ return d->availableDatabaseName;
+ }
+ return m_driver->beh->ALWAYS_AVAILABLE_DATABASE_NAME;
+}
+
+void Connection::setAvailableDatabaseName(const QString& dbName)
+{
+ d->availableDatabaseName = dbName;
+}
+
+//! @internal used in updateRow(), insertRow(),
+inline void updateRowDataWithNewValues(QuerySchema &query, RowData& data, KexiDB::RowEditBuffer::DBMap& b,
+ QMap<QueryColumnInfo*,int>& columnsOrderExpanded)
+{
+ columnsOrderExpanded = query.columnsOrder(QuerySchema::ExpandedList);
+ QMap<QueryColumnInfo*,int>::ConstIterator columnsOrderExpandedIt;
+ for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) {
+ columnsOrderExpandedIt = columnsOrderExpanded.find( it.key() );
+ if (columnsOrderExpandedIt == columnsOrderExpanded.constEnd()) {
+ KexiDBWarn << "(Connection) updateRowDataWithNewValues(): \"now also assign new value in memory\" step "
+ "- could not find item '" << it.key()->aliasOrName() << "'" << endl;
+ continue;
+ }
+ data[ columnsOrderExpandedIt.data() ] = it.data();
+ }
+}
+
+bool Connection::updateRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool useROWID)
+{
+// Each SQL identifier needs to be escaped in the generated query.
+// query.debug();
+
+ KexiDBDbg << "Connection::updateRow.." << endl;
+ clearError();
+ //--get PKEY
+ if (buf.dbBuffer().isEmpty()) {
+ KexiDBDbg << " -- NO CHANGES DATA!" << endl;
+ return true;
+ }
+ TableSchema *mt = query.masterTable();
+ if (!mt) {
+ KexiDBWarn << " -- NO MASTER TABLE!" << endl;
+ setError(ERR_UPDATE_NO_MASTER_TABLE,
+ i18n("Could not update row because there is no master table defined."));
+ return false;
+ }
+ IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0;
+ if (!useROWID && !pkey) {
+ KexiDBWarn << " -- NO MASTER TABLE's PKEY!" << endl;
+ setError(ERR_UPDATE_NO_MASTER_TABLES_PKEY,
+ i18n("Could not update row because master table has no primary key defined."));
+//! @todo perhaps we can try to update without using PKEY?
+ return false;
+ }
+ //update the record:
+ m_sql = "UPDATE " + escapeIdentifier(mt->name()) + " SET ";
+ QString sqlset, sqlwhere;
+ sqlset.reserve(1024);
+ sqlwhere.reserve(1024);
+ KexiDB::RowEditBuffer::DBMap b = buf.dbBuffer();
+ for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) {
+ if (it.key()->field->table()!=mt)
+ continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field)
+ if (!sqlset.isEmpty())
+ sqlset+=",";
+ sqlset += (escapeIdentifier(it.key()->field->name()) + "=" +
+ m_driver->valueToSQL(it.key()->field,it.data()));
+ }
+ if (pkey) {
+ const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() );
+ KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl;
+ if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check
+ KexiDBWarn << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl;
+ setError(ERR_UPDATE_NO_ENTIRE_MASTER_TABLES_PKEY,
+ i18n("Could not update row because it does not contain entire master table's primary key."));
+ return false;
+ }
+ if (!pkey->fields()->isEmpty()) {
+ uint i=0;
+ for (Field::ListIterator it = pkey->fieldsIterator(); it.current(); i++, ++it) {
+ if (!sqlwhere.isEmpty())
+ sqlwhere+=" AND ";
+ QVariant val = data[ pkeyFieldsOrder[i] ];
+ if (val.isNull() || !val.isValid()) {
+ setError(ERR_UPDATE_NULL_PKEY_FIELD,
+ i18n("Primary key's field \"%1\" cannot be empty.").arg(it.current()->name()));
+ //js todo: pass the field's name somewhere!
+ return false;
+ }
+ sqlwhere += ( escapeIdentifier(it.current()->name()) + "=" +
+ m_driver->valueToSQL( it.current(), val ) );
+ }
+ }
+ }
+ else {//use ROWID
+ sqlwhere = ( escapeIdentifier(m_driver->beh->ROW_ID_FIELD_NAME) + "="
+ + m_driver->valueToSQL(Field::BigInteger, data[data.size()-1]));
+ }
+ m_sql += (sqlset + " WHERE " + sqlwhere);
+ KexiDBDbg << " -- SQL == " << ((m_sql.length() > 400) ? (m_sql.left(400)+"[.....]") : m_sql) << endl;
+
+ if (!executeSQL(m_sql)) {
+ setError(ERR_UPDATE_SERVER_ERROR, i18n("Row updating on the server failed."));
+ return false;
+ }
+ //success: now also assign new values in memory:
+ QMap<QueryColumnInfo*,int> columnsOrderExpanded;
+ updateRowDataWithNewValues(query, data, b, columnsOrderExpanded);
+ return true;
+}
+
+bool Connection::insertRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool getROWID)
+{
+// Each SQL identifier needs to be escaped in the generated query.
+ KexiDBDbg << "Connection::updateRow.." << endl;
+ clearError();
+ //--get PKEY
+ /*disabled: there may be empty rows (with autoinc)
+ if (buf.dbBuffer().isEmpty()) {
+ KexiDBDbg << " -- NO CHANGES DATA!" << endl;
+ return true; }*/
+ TableSchema *mt = query.masterTable();
+ if (!mt) {
+ KexiDBWarn << " -- NO MASTER TABLE!" << endl;
+ setError(ERR_INSERT_NO_MASTER_TABLE,
+ i18n("Could not insert row because there is no master table defined."));
+ return false;
+ }
+ IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0;
+ if (!getROWID && !pkey)
+ KexiDBWarn << " -- WARNING: NO MASTER TABLE's PKEY" << endl;
+
+ QString sqlcols, sqlvals;
+ sqlcols.reserve(1024);
+ sqlvals.reserve(1024);
+
+ //insert the record:
+ m_sql = "INSERT INTO " + escapeIdentifier(mt->name()) + " (";
+ KexiDB::RowEditBuffer::DBMap b = buf.dbBuffer();
+
+ // add default values, if available (for any column without value explicitly set)
+ const QueryColumnInfo::Vector fieldsExpanded( query.fieldsExpanded( QuerySchema::Unique ) );
+ for (uint i=0; i<fieldsExpanded.count(); i++) {
+ QueryColumnInfo *ci = fieldsExpanded.at(i);
+ if (ci->field && KexiDB::isDefaultValueAllowed(ci->field)
+ && !ci->field->defaultValue().isNull()
+ && !b.contains( ci ))
+ {
+ KexiDBDbg << "Connection::insertRow(): adding default value '" << ci->field->defaultValue().toString()
+ << "' for column '" << ci->field->name() << "'" << endl;
+ b.insert( ci, ci->field->defaultValue() );
+ }
+ }
+
+ if (b.isEmpty()) {
+ // empty row inserting requested:
+ if (!getROWID && !pkey) {
+ KexiDBWarn << "MASTER TABLE's PKEY REQUIRED FOR INSERTING EMPTY ROWS: INSERT CANCELLED" << endl;
+ setError(ERR_INSERT_NO_MASTER_TABLES_PKEY,
+ i18n("Could not insert row because master table has no primary key defined."));
+ return false;
+ }
+ if (pkey) {
+ const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() );
+// KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl;
+ if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check
+ KexiDBWarn << "NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl;
+ setError(ERR_INSERT_NO_ENTIRE_MASTER_TABLES_PKEY,
+ i18n("Could not insert row because it does not contain entire master table's primary key.")
+ .arg(query.name()));
+ return false;
+ }
+ }
+ //at least one value is needed for VALUES section: find it and set to NULL:
+ Field *anyField = mt->anyNonPKField();
+ if (!anyField) {
+ if (!pkey) {
+ KexiDBWarn << "WARNING: NO FIELD AVAILABLE TO SET IT TO NULL" << endl;
+ return false;
+ }
+ else {
+ //try to set NULL in pkey field (could not work for every SQL engine!)
+ anyField = pkey->fields()->first();
+ }
+ }
+ sqlcols += escapeIdentifier(anyField->name());
+ sqlvals += m_driver->valueToSQL(anyField,QVariant()/*NULL*/);
+ }
+ else {
+ // non-empty row inserting requested:
+ for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) {
+ if (it.key()->field->table()!=mt)
+ continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field)
+ if (!sqlcols.isEmpty()) {
+ sqlcols+=",";
+ sqlvals+=",";
+ }
+ sqlcols += escapeIdentifier(it.key()->field->name());
+ sqlvals += m_driver->valueToSQL(it.key()->field,it.data());
+ }
+ }
+ m_sql += (sqlcols + ") VALUES (" + sqlvals + ")");
+// KexiDBDbg << " -- SQL == " << m_sql << endl;
+
+ bool res = executeSQL(m_sql);
+
+ if (!res) {
+ setError(ERR_INSERT_SERVER_ERROR, i18n("Row inserting on the server failed."));
+ return false;
+ }
+ //success: now also assign a new value in memory:
+ QMap<QueryColumnInfo*,int> columnsOrderExpanded;
+ updateRowDataWithNewValues(query, data, b, columnsOrderExpanded);
+
+ //fetch autoincremented values
+ QueryColumnInfo::List *aif_list = query.autoIncrementFields();
+ Q_ULLONG ROWID = 0;
+ if (pkey && !aif_list->isEmpty()) {
+ //! @todo now only if PKEY is present, this should also work when there's no PKEY
+ QueryColumnInfo *id_columnInfo = aif_list->first();
+//! @todo safe to cast it?
+ Q_ULLONG last_id = lastInsertedAutoIncValue(
+ id_columnInfo->field->name(), id_columnInfo->field->table()->name(), &ROWID);
+ if (last_id==(Q_ULLONG)-1 || last_id<=0) {
+ //! @todo show error
+//! @todo remove just inserted row. How? Using ROLLBACK?
+ return false;
+ }
+ RowData aif_data;
+ QString getAutoIncForInsertedValue = QString::fromLatin1("SELECT ")
+ + query.autoIncrementSQLFieldsList(m_driver)
+ + QString::fromLatin1(" FROM ")
+ + escapeIdentifier(id_columnInfo->field->table()->name())
+ + QString::fromLatin1(" WHERE ")
+ + escapeIdentifier(id_columnInfo->field->name()) + "="
+ + QString::number(last_id);
+ if (true!=querySingleRecord(getAutoIncForInsertedValue, aif_data)) {
+ //! @todo show error
+ return false;
+ }
+ QueryColumnInfo::ListIterator ci_it(*aif_list);
+ QueryColumnInfo *ci;
+ for (uint i=0; (ci = ci_it.current()); ++ci_it, i++) {
+// KexiDBDbg << "Connection::insertRow(): AUTOINCREMENTED FIELD " << fi->field->name() << " == "
+// << aif_data[i].toInt() << endl;
+ ( data[ columnsOrderExpanded[ ci ] ] = aif_data[i] ).cast( ci->field->variantType() ); //cast to get proper type
+ }
+ }
+ else {
+ ROWID = drv_lastInsertRowID();
+// KexiDBDbg << "Connection::insertRow(): new ROWID == " << (uint)ROWID << endl;
+ if (m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) {
+ KexiDBWarn << "Connection::insertRow(): m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE" << endl;
+ return false;
+ }
+ }
+ if (getROWID && /*sanity check*/data.size() > fieldsExpanded.size()) {
+// KexiDBDbg << "Connection::insertRow(): new ROWID == " << (uint)ROWID << endl;
+ data[data.size()-1] = ROWID;
+ }
+ return true;
+}
+
+bool Connection::deleteRow(QuerySchema &query, RowData& data, bool useROWID)
+{
+// Each SQL identifier needs to be escaped in the generated query.
+ KexiDBWarn << "Connection::deleteRow.." << endl;
+ clearError();
+ TableSchema *mt = query.masterTable();
+ if (!mt) {
+ KexiDBWarn << " -- NO MASTER TABLE!" << endl;
+ setError(ERR_DELETE_NO_MASTER_TABLE,
+ i18n("Could not delete row because there is no master table defined.")
+ .arg(query.name()));
+ return false;
+ }
+ IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0;
+
+//! @todo allow to delete from a table without pkey
+ if (!useROWID && !pkey) {
+ KexiDBWarn << " -- WARNING: NO MASTER TABLE's PKEY" << endl;
+ setError(ERR_DELETE_NO_MASTER_TABLES_PKEY,
+ i18n("Could not delete row because there is no primary key for master table defined."));
+ return false;
+ }
+
+ //update the record:
+ m_sql = "DELETE FROM " + escapeIdentifier(mt->name()) + " WHERE ";
+ QString sqlwhere;
+ sqlwhere.reserve(1024);
+
+ if (pkey) {
+ const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() );
+ KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl;
+ if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check
+ KexiDBWarn << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl;
+ setError(ERR_DELETE_NO_ENTIRE_MASTER_TABLES_PKEY,
+ i18n("Could not delete row because it does not contain entire master table's primary key."));
+ return false;
+ }
+ uint i=0;
+ for (Field::ListIterator it = pkey->fieldsIterator(); it.current(); i++, ++it) {
+ if (!sqlwhere.isEmpty())
+ sqlwhere+=" AND ";
+ QVariant val = data[ pkeyFieldsOrder[i] ];
+ if (val.isNull() || !val.isValid()) {
+ setError(ERR_DELETE_NULL_PKEY_FIELD, i18n("Primary key's field \"%1\" cannot be empty.")
+ .arg(it.current()->name()));
+//js todo: pass the field's name somewhere!
+ return false;
+ }
+ sqlwhere += ( escapeIdentifier(it.current()->name()) + "=" +
+ m_driver->valueToSQL( it.current(), val ) );
+ }
+ }
+ else {//use ROWID
+ sqlwhere = ( escapeIdentifier(m_driver->beh->ROW_ID_FIELD_NAME) + "="
+ + m_driver->valueToSQL(Field::BigInteger, data[data.size()-1]));
+ }
+ m_sql += sqlwhere;
+ KexiDBDbg << " -- SQL == " << m_sql << endl;
+
+ if (!executeSQL(m_sql)) {
+ setError(ERR_DELETE_SERVER_ERROR, i18n("Row deletion on the server failed."));
+ return false;
+ }
+ return true;
+}
+
+bool Connection::deleteAllRows(QuerySchema &query)
+{
+ clearError();
+ TableSchema *mt = query.masterTable();
+ if (!mt) {
+ KexiDBWarn << " -- NO MASTER TABLE!" << endl;
+ return false;
+ }
+ IndexSchema *pkey = mt->primaryKey();
+ if (!pkey || pkey->fields()->isEmpty())
+ KexiDBWarn << "Connection::deleteAllRows -- WARNING: NO MASTER TABLE's PKEY" << endl;
+
+ m_sql = "DELETE FROM " + escapeIdentifier(mt->name());
+
+ KexiDBDbg << " -- SQL == " << m_sql << endl;
+
+ if (!executeSQL(m_sql)) {
+ setError(ERR_DELETE_SERVER_ERROR, i18n("Row deletion on the server failed."));
+ return false;
+ }
+ return true;
+}
+
+void Connection::registerForTableSchemaChanges(TableSchemaChangeListenerInterface& listener,
+ TableSchema &schema)
+{
+ QPtrList<TableSchemaChangeListenerInterface>* listeners = d->tableSchemaChangeListeners[&schema];
+ if (!listeners) {
+ listeners = new QPtrList<TableSchemaChangeListenerInterface>();
+ d->tableSchemaChangeListeners.insert(&schema, listeners);
+ }
+//TODO: inefficient
+ if (listeners->findRef( &listener )==-1)
+ listeners->append( &listener );
+}
+
+void Connection::unregisterForTableSchemaChanges(TableSchemaChangeListenerInterface& listener,
+ TableSchema &schema)
+{
+ QPtrList<TableSchemaChangeListenerInterface>* listeners = d->tableSchemaChangeListeners[&schema];
+ if (!listeners)
+ return;
+//TODO: inefficient
+ listeners->remove( &listener );
+}
+
+void Connection::unregisterForTablesSchemaChanges(TableSchemaChangeListenerInterface& listener)
+{
+ for (QPtrDictIterator< QPtrList<TableSchemaChangeListenerInterface> > it(d->tableSchemaChangeListeners);
+ it.current(); ++it)
+ {
+ if (-1!=it.current()->find(&listener))
+ it.current()->take();
+ }
+}
+
+QPtrList<Connection::TableSchemaChangeListenerInterface>*
+Connection::tableSchemaChangeListeners(TableSchema& tableSchema) const
+{
+ KexiDBDbg << d->tableSchemaChangeListeners.count() << endl;
+ return d->tableSchemaChangeListeners[&tableSchema];
+}
+
+tristate Connection::closeAllTableSchemaChangeListeners(TableSchema& tableSchema)
+{
+ QPtrList<Connection::TableSchemaChangeListenerInterface> *listeners = d->tableSchemaChangeListeners[&tableSchema];
+ if (!listeners)
+ return true;
+ QPtrListIterator<KexiDB::Connection::TableSchemaChangeListenerInterface> tmpListeners(*listeners); //safer copy
+ tristate res = true;
+ //try to close every window
+ for (QPtrListIterator<KexiDB::Connection::TableSchemaChangeListenerInterface> it(tmpListeners);
+ it.current() && res==true; ++it)
+ {
+ res = it.current()->closeListener();
+ }
+ return res;
+}
+
+/*PreparedStatement::Ptr Connection::prepareStatement(PreparedStatement::StatementType,
+ TableSchema&)
+{
+ //safe?
+ return 0;
+}*/
+
+void Connection::setReadOnly(bool set)
+{
+ if (d->isConnected)
+ return; //sanity
+ d->readOnly = set;
+}
+
+bool Connection::isReadOnly() const
+{
+ return d->readOnly;
+}
+
+#include "connection.moc"
diff --git a/kexi/kexidb/connection.h b/kexi/kexidb/connection.h
new file mode 100644
index 000000000..b72e01d41
--- /dev/null
+++ b/kexi/kexidb/connection.h
@@ -0,0 +1,1198 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_CONNECTION_H
+#define KEXIDB_CONNECTION_H
+
+#include <qobject.h>
+#include <qstringlist.h>
+#include <qintdict.h>
+#include <qdict.h>
+#include <qptrdict.h>
+#include <qvaluevector.h>
+#include <qvaluelist.h>
+#include <qvariant.h>
+#include <qguardedptr.h>
+
+#include <kexidb/object.h>
+#include <kexidb/connectiondata.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/queryschema.h>
+#include <kexidb/queryschemaparameter.h>
+#include <kexidb/transaction.h>
+#include <kexidb/driver.h>
+#include <kexidb/preparedstatement.h>
+
+#include <kexiutils/tristate.h>
+
+namespace KexiDB {
+
+//! structure for storing single record with type information
+typedef QValueVector<QVariant> RowData;
+
+class Cursor;
+class ConnectionPrivate;
+class RowEditBuffer;
+class DatabaseProperties;
+class AlterTableHandler;
+
+/*! @short Provides database connection, allowing queries and data modification.
+
+ This class represents a database connection established within a data source.
+ It supports data queries and modification by creating client-side database cursors.
+ Database transactions are supported.
+*/
+class KEXI_DB_EXPORT Connection : public QObject, public KexiDB::Object
+{
+ Q_OBJECT
+
+ public:
+
+ /*! Opened connection is automatically disconnected and removed
+ from driver's connections list.
+ Note for driver developers: you should call destroy()
+ from you Connection's subclass destructor. */
+ virtual ~Connection();
+
+ /*! \return parameters that were used to create this connection. */
+ ConnectionData* data() const;
+
+ /*! \return the driver used for this connection. */
+ inline Driver* driver() const { return m_driver; }
+
+ /*!
+ \brief Connects to driver with given parameters.
+ \return true if successful. */
+ bool connect();
+
+ /*! \return true, if connection is properly established. */
+ bool isConnected() const;
+
+ /*! \return true, both if connection is properly established
+ and any database within this connection is properly used
+ with useDatabase(). */
+ bool isDatabaseUsed() const;
+
+ /*! \return true for read only connection. Used especially for file-based drivers.
+ Can be reimplemented in a driver to provide real read-only flag of the connection
+ (SQlite3 dirver does this). */
+ virtual bool isReadOnly() const;
+
+ /*! Reimplemented from Object: also clears sql string.
+ @sa recentSQLString() */
+ virtual void clearError();
+
+ /*! \brief Disconnects from driver with given parameters.
+
+ The database (if used) is closed, and any active transactions
+ (if supported) are rolled back, so commit these before disconnecting,
+ if you'd like to save your changes. */
+ bool disconnect();
+
+ /*! \return list of database names for opened connection.
+ If \a also_system_db is true, the system database names are also returned. */
+ QStringList databaseNames(bool also_system_db = false);
+
+ /*! \return true if database \a dbName exists.
+ If \a ignoreErrors is true, error flag of connection
+ won't be modified for any errors (it will quietly return),
+ else (ignoreErrors == false) we can check why the database does
+ not exist using error(), errorNum() and/or errorMsg(). */
+ bool databaseExists( const QString &dbName, bool ignoreErrors = true );
+
+ /*! \brief Creates new database with name \a dbName, using this connection.
+
+ If database with \a dbName already exists, or other error occurred,
+ false is returned.
+ For file-based drivers, \a dbName should be equal to the database
+ filename (the same as specified for ConnectionData).
+
+ See doc/dev/kexidb_issues.txt document, chapter "Table schema, query schema, etc. storage"
+ for database schema documentation (detailed description of kexi__* 'system' tables).
+
+ \sa useDatabase() */
+ bool createDatabase( const QString &dbName );
+
+ /*!
+ \brief Opens an existing database specified by \a dbName.
+
+ If \a kexiCompatible is true (the default) initial checks will be performed
+ to recognize database Kexi-specific format. Set \a kexiCompatible to false
+ if you're using native database (one that have no Kexi System tables).
+ For file-based drivers, \a dbName should be equal to filename
+ (the same as specified for ConnectionData).
+ \return true on success, false on failure.
+ If user has cancelled this action and \a cancelled is not 0, *cancelled is set to true. */
+ bool useDatabase( const QString &dbName, bool kexiCompatible = true, bool *cancelled = 0,
+ MessageHandler* msgHandler = 0 );
+
+ /*!
+ \brief Closes currently used database for this connection.
+
+ Any active transactions (if supported) are rolled back,
+ so commit these before closing, if you'd like to save your changes. */
+ bool closeDatabase();
+
+ /*! \brief Get the name of the current database
+
+ \return name of currently used database for this connection or empty string
+ if there is no used database */
+ QString currentDatabase() const;
+
+ /*! \brief Drops database with name \a dbName.
+
+ if dbName is not specified, currently used database name is used
+ (it is closed before dropping).
+ */
+ bool dropDatabase( const QString &dbName = QString::null );
+
+ /*! \return names of all the \a objecttype (see \a ObjectTypes in global.h)
+ schemas stored in currently used database. KexiDB::AnyObjectType can be passed
+ as \a objType to get names of objects of any type.
+ If \a ok is not null then variable pointed by it will be set to the result.
+ On error, the functions can return incomplete list. */
+ QStringList objectNames(int objType = KexiDB::AnyObjectType, bool* ok = 0);
+
+ /*! \return names of all table schemas stored in currently
+ used database. If \a also_system_tables is true,
+ internal KexiDB system table names (kexi__*) are also returned.
+ \sa kexiDBSystemTableNames() */
+ QStringList tableNames(bool also_system_tables = false);
+
+ /*! \return list of internal KexiDB system table names
+ (kexi__*). This does not mean that these tables can be found
+ in currently opened database. Just static list of table
+ names is returned.
+
+ The list contents may depend on KexiDB library version;
+ opened database can contain fewer 'system' tables than in current
+ KexiDB implementation, if the current one is newer than the one used
+ to build the database. */
+ static const QStringList& kexiDBSystemTableNames();
+
+ /*! \return server version information for this connection.
+ If database is not connected (i.e. isConnected() is false) 0 is returned. */
+ KexiDB::ServerVersionInfo* serverVersion() const;
+
+ /*! \return version information for this connection.
+ If database is not used (i.e. isDatabaseUsed() is false) 0 is returned.
+ It can be compared to drivers' and KexiDB library version to maintain
+ backward/upward compatiblility. */
+ KexiDB::DatabaseVersionInfo* databaseVersion() const;
+
+ /*! \return DatabaseProperties object allowing to read and write global database properties
+ for this connection. */
+ DatabaseProperties& databaseProperties();
+
+ /*! \return ids of all table schema names stored in currently
+ used database. These ids can be later used as argument for tableSchema().
+ This is a shortcut for objectIds(TableObjectType).
+ If \a also_system_tables is true,
+ Internal KexiDB system tables (kexi__*) are not available here
+ because these have no identifiers assigned (more formally: id=-1). */
+ QValueList<int> tableIds();
+
+ /*! \return ids of all database query schemas stored in currently
+ used database. These ids can be later used as argument for querySchema().
+ This is a shortcut for objectIds(TableObjectType). */
+ QValueList<int> queryIds();
+
+ /*! \return names of all schemas of object with \a objType type
+ that are stored in currently used database. */
+ QValueList<int> objectIds(int objType);
+
+ /*! \brief Creates new transaction handle and starts a new transaction.
+ \return KexiDB::Transaction object if transaction has been started
+ successfully, otherwise null transaction.
+ For drivers that allow single transaction per connection
+ (Driver::features() && SingleTransactions) this method can be called one time,
+ and then this single transaction will be default ( setDefaultTransaction() will
+ be called).
+ For drivers that allow multiple transactions per connection, no default transaction is
+ set automatically in beginTransaction() method, you could do this by hand.
+ \sa setDefaultTransaction(), defaultTransaction().
+ */
+ Transaction beginTransaction();
+
+/*! \todo for nested transactions:
+ Tansaction* beginTransaction(transaction *parent_transaction);
+*/
+ /*! Commits transaction \a trans.
+ If there is not \a trans argument passed, and there is default transaction
+ (obtained from defaultTransaction()) defined, this one will be committed.
+ If default is not present, false is returned (when ignore_inactive is
+ false, the default), or true is returned (when ignore_inactive is true).
+
+ On successful commit, \a trans object will be destroyed.
+ If this was default transaction, there is no default transaction for now.
+ */
+ bool commitTransaction( Transaction trans = Transaction::null,
+ bool ignore_inactive = false );
+
+ /*! Rollbacks transaction \a trans.
+ If there is not \a trans argument passed, and there is default transaction
+ (obtained from defaultTransaction()) defined, this one will be rolled back.
+ If default is not present, false is returned (when ignore_inactive is
+ false, the default), or true is returned (when ignore_inactive is true).
+
+ or any error occurred, false is returned.
+
+ On successful rollback, \a trans object will be destroyed.
+ If this was default transaction, there is no default transaction for now.
+ */
+ bool rollbackTransaction( Transaction trans = Transaction::null,
+ bool ignore_inactive = false );
+
+ /*! \return handle for default transaction for this connection
+ or null transaction if there is no such a transaction defined.
+ If transactions are supported: Any operation on database (e.g. inserts)
+ that is started without specifying transaction context, will be performed
+ in the context of this transaction.
+
+ Returned null transaction doesn't mean that there is no transactions
+ started at all.
+ Default transaction can be defined automatically for some drivers --
+ see beginTransaction().
+ \sa KexiDB::Driver::transactionsSupported()
+ */
+ Transaction& defaultTransaction() const;
+
+ /*! Sets default transaction that will be used as context for operations
+ on data in opened database for this connection. */
+ void setDefaultTransaction(const Transaction& trans);
+
+ /*! \return set of handles of currently active transactions.
+ Note that in multithreading environment some of these
+ transactions can be already inactive after calling this method.
+ Use Transaction::active() to check that. Inactive transaction
+ handle is useless and can be safely dropped.
+ */
+ const QValueList<Transaction>& transactions();
+
+ /*! \return true if "auto commit" option is on.
+
+ When auto commit is on (the default on for any new Connection object),
+ every sql functional statement (statement that changes
+ data in the database implicitly starts a new transaction.
+ This transaction is automatically committed
+ after successful statement execution or rolled back on error.
+
+ For drivers that do not support transactions (see Driver::features())
+ this method shouldn't be called because it does nothing ans always returns false.
+
+ No internal KexiDB object should changes this option, although auto commit's
+ behaviour depends on database engine's specifics. Engines that support only single
+ transaction per connection (see Driver::SingleTransactions),
+ use this single connection for autocommiting, so if there is already transaction
+ started by the KexiDB user program (with beginTransaction()), this transaction
+ is committed before any sql functional statement execution. In this situation
+ default transaction is also affected (see defaultTransaction()).
+
+ Only for drivers that support nested transactions (Driver::NestedTransactions),
+ autocommiting works independently from previously started transaction,
+
+ For other drivers set this option off if you need use transaction
+ for grouping more statements together.
+
+ NOTE: nested transactions are not yet implemented in KexiDB API.
+ */
+ bool autoCommit() const;
+
+ /*! Changes auto commit option. This does not affect currently started transactions.
+ This option can be changed even when connection is not established.
+ \sa autoCommit() */
+ bool setAutoCommit(bool on);
+
+ /*! driver-specific string escaping */
+//js: MOVED TO Driver virtual QString escapeString(const QString& str) const = 0;
+// virtual QCString escapeString(const QCString& str) const = 0;
+
+ /*! Prepares SELECT query described by raw \a statement.
+ \return opened cursor created for results of this query
+ or NULL if there was any error. Cursor can have optionally applied \a cursor_options
+ (one of more selected from KexiDB::Cursor::Options).
+ Preparation means that returned cursor is created but not opened.
+ Open this when you would like to do it with Cursor::open().
+
+ Note for driver developers: you should initialize cursor engine-specific
+ resources and return Cursor subclass' object
+ (passing \a statement and \a cursor_options to it's constructor).
+ */
+ virtual Cursor* prepareQuery( const QString& statement, uint cursor_options = 0) = 0;
+
+ /*! \overload prepareQuery( const QString& statement = QString::null, uint cursor_options = 0)
+ Prepares query described by \a query schema. \a params are values of parameters that
+ will be inserted into places marked with [] before execution of the query.
+
+ Note for driver developers: you should initialize cursor engine-specific
+ resources and return Cursor subclass' object
+ (passing \a query and \a cursor_options to it's constructor).
+ Kexi SQL and driver-specific escaping is performed on table names.
+ */
+ Cursor* prepareQuery( QuerySchema& query, const QValueList<QVariant>& params,
+ uint cursor_options = 0 );
+
+ /*! \overload prepareQuery( QuerySchema& query, const QValueList<QVariant>& params,
+ uint cursor_options = 0 )
+ Prepares query described by \a query schema without parameters.
+ */
+ virtual Cursor* prepareQuery( QuerySchema& query, uint cursor_options = 0 ) = 0;
+
+ /*! \overload prepareQuery( const QString& statement = QString::null, uint cursor_options = 0)
+ Statement is build from data provided by \a table schema,
+ it is like "select * from table_name".*/
+ Cursor* prepareQuery( TableSchema& table, uint cursor_options = 0);
+
+ /*! Executes SELECT query described by \a statement.
+ \return opened cursor created for results of this query
+ or NULL if there was any error on the cursor creation or opening.
+ Cursor can have optionally applied \a cursor_options
+ (one of more selected from KexiDB::Cursor::Options).
+ Identifiers in \a statement that are the same as keywords in Kexi
+ SQL or the backend's SQL need to have been escaped.
+ */
+ Cursor* executeQuery( const QString& statement, uint cursor_options = 0 );
+
+ /*! \overload executeQuery( const QString& statement, uint cursor_options = 0 )
+ \a params are values of parameters that
+ will be inserted into places marked with [] before execution of the query.
+
+ Statement is build from data provided by \a query schema.
+ Kexi SQL and driver-specific escaping is performed on table names. */
+ Cursor* executeQuery( QuerySchema& query, const QValueList<QVariant>& params,
+ uint cursor_options = 0 );
+
+ /*! \overload executeQuery( QuerySchema& query, const QValueList<QVariant>& params,
+ uint cursor_options = 0 ) */
+ Cursor* executeQuery( QuerySchema& query, uint cursor_options = 0 );
+
+ /*! \overload executeQuery( const QString& statement, uint cursor_options = 0 )
+ Executes query described by \a query schema without parameters.
+ Statement is build from data provided by \a table schema,
+ it is like "select * from table_name".*/
+ Cursor* executeQuery( TableSchema& table, uint cursor_options = 0 );
+
+ /*! Deletes cursor \a cursor previously created by functions like executeQuery()
+ for this connection.
+ There is an attempt to close the cursor with Cursor::close() if it was opened.
+ Anyway, at last cursor is deleted.
+ \return true if cursor is properly closed before deletion. */
+ bool deleteCursor(Cursor *cursor);
+
+ /*! \return schema of a table pointed by \a tableId, retrieved from currently
+ used database. The schema is cached inside connection,
+ so retrieval is performed only once, on demand. */
+ TableSchema* tableSchema( int tableId );
+
+ /*! \return schema of a table pointed by \a tableName, retrieved from currently
+ used database. KexiDB system table schema can be also retrieved.
+ \sa tableSchema( int tableId ) */
+ TableSchema* tableSchema( const QString& tableName );
+
+ /*! \return schema of a query pointed by \a queryId, retrieved from currently
+ used database. The schema is cached inside connection,
+ so retrieval is performed only once, on demand. */
+ QuerySchema* querySchema( int queryId );
+
+ /*! \return schema of a query pointed by \a queryName, retrieved from currently
+ used database. \sa querySchema( int queryId ) */
+ QuerySchema* querySchema( const QString& queryName );
+
+ /*! Sets \a queryName query obsolete by moving it out of the query sets, so it will not be
+ accessible by querySchema( const QString& queryName ). The existing query object is not
+ destroyed, to avoid problems when it's referenced. In this case,
+ a new query schema will be retrieved directly from the backend.
+
+ For now it's used in KexiQueryDesignerGuiEditor::storeLayout().
+ This solves the problem when user has changed a query schema but already form still uses
+ previously instantiated query schema.
+ \return true if there is such query. Otherwise the method does nothing. */
+ bool setQuerySchemaObsolete( const QString& queryName );
+
+//js: MOVED TO Driver QString valueToSQL( const Field::Type ftype, const QVariant& v ) const;
+// QString valueToSQL( const Field *field, const QVariant& v ) const;
+
+ /*! Executes \a sql query and stores first record's data inside \a data.
+ This is convenient method when we need only first record from query result,
+ or when we know that query result has only one record.
+ If \a addLimitTo1 is true (the default), adds a LIMIT clause to the query,
+ so \a sql should not include one already.
+ \return true if query was successfully executed and first record has been found,
+ false on data retrieving failure, and cancelled if there's no single record available. */
+ tristate querySingleRecord(const QString& sql, RowData &data, bool addLimitTo1 = true);
+
+ /*! Like tristate querySingleRecord(const QString& sql, RowData &data)
+ but uses QuerySchema object.
+ If \a addLimitTo1 is true (the default), adds a LIMIT clause to the query. */
+ tristate querySingleRecord(QuerySchema& query, RowData &data, bool addLimitTo1 = true);
+
+ /*! Executes \a sql query and stores first record's field's (number \a column) string value
+ inside \a value. For efficiency it's recommended that a query defined by \a sql
+ should have just one field (SELECT one_field FROM ....).
+ If \a addLimitTo1 is true (the default), adds a LIMIT clause to the query,
+ so \a sql should not include one already.
+ \return true if query was successfully executed and first record has been found,
+ false on data retrieving failure, and cancelled if there's no single record available.
+ \sa queryStringList() */
+ tristate querySingleString(const QString& sql, QString &value, uint column = 0,
+ bool addLimitTo1 = true);
+
+ /*! Convenience function: executes \a sql query and stores first
+ record's field's (number \a column) value inside \a number. \sa querySingleString().
+ Note: "LIMIT 1" is appended to \a sql statement if \a addLimitTo1 is true (the default).
+ \return true if query was successfully executed and first record has been found,
+ false on data retrieving failure, and cancelled if there's no single record available. */
+ tristate querySingleNumber(const QString& sql, int &number, uint column = 0,
+ bool addLimitTo1 = true);
+
+ /*! Executes \a sql query and stores Nth field's string value of every record
+ inside \a list, where N is equal to \a column. The list is initially cleared.
+ For efficiency it's recommended that a query defined by \a sql
+ should have just one field (SELECT one_field FROM ....).
+ \return true if all values were fetched successfuly,
+ false on data retrieving failure. Returning empty list can be still a valid result.
+ On errors, the list is not cleared, it may contain a few retrieved values. */
+ bool queryStringList(const QString& sql, QStringList& list, uint column = 0);
+
+ /*! \return true if there is at least one record returned in \a sql query.
+ Does not fetch any records. \a success will be set to false
+ on query execution errors (true otherwise), so you can see a difference between
+ "no results" and "query execution error" states.
+ Note: real executed query is: "SELECT 1 FROM (\a sql) LIMIT 1"
+ if \a addLimitTo1 is true (the default). */
+ bool resultExists(const QString& sql, bool &success, bool addLimitTo1 = true);
+
+ /*! \return true if there is at least one record in \a table. */
+ bool isEmpty( TableSchema& table, bool &success );
+
+//! @todo perhaps use Q_ULLONG here?
+ /*! \return number of records in \a sql query.
+ Does not fetch any records. -1 is returned on query execution errors (>0 otherwise).
+ Note: real executed query is: "SELECT COUNT() FROM (\a sql) LIMIT 1"
+ (using querySingleNumber()) */
+ int resultCount(const QString& sql);
+
+ //PROTOTYPE:
+ #define A , const QVariant&
+ #define H_INS_REC(args) bool insertRecord(TableSchema &tableSchema args)
+ #define H_INS_REC_ALL \
+ H_INS_REC(A); \
+ H_INS_REC(A A); \
+ H_INS_REC(A A A); \
+ H_INS_REC(A A A A); \
+ H_INS_REC(A A A A A); \
+ H_INS_REC(A A A A A A); \
+ H_INS_REC(A A A A A A A); \
+ H_INS_REC(A A A A A A A A)
+ H_INS_REC_ALL;
+
+ #undef H_INS_REC
+ #define H_INS_REC(args) bool insertRecord(FieldList& fields args)
+
+ H_INS_REC_ALL;
+ #undef H_INS_REC_ALL
+ #undef H_INS_REC
+ #undef A
+
+ bool insertRecord(TableSchema &tableSchema, QValueList<QVariant>& values);
+
+ bool insertRecord(FieldList& fields, QValueList<QVariant>& values);
+
+ /*! Creates table defined by \a tableSchema.
+ Schema information is also added into kexi system tables, for later reuse.
+ \return true on success - \a tableSchema object is then
+ inserted to Connection structures - it is owned by Connection object now,
+ so you shouldn't destroy the tableSchema object by hand
+ (or declare it as local-scope variable).
+
+ If \a replaceExisting is false (the default) and table with the same name
+ (as tableSchema->name()) exists, false is returned.
+ If \a replaceExisting is true, a table schema with the same name (if exists)
+ is overwritten, then a new table schema gets the same identifier
+ as existing table schema's identifier.
+
+ Note that on error:
+ - \a tableSchema is not inserted into Connection's structures,
+ so you are still owner of this object
+ - existing table schema object is not destroyed (i.e. it is still available
+ e.g. using Connection::tableSchema(const QString& ), even if the table
+ was physically dropped.
+ */
+ bool createTable( TableSchema* tableSchema, bool replaceExisting = false );
+
+ /*! Drops a table defined by \a tableSchema (both table object as well as physically).
+ If true is returned, schema information \a tableSchema is destoyed
+ (because it's owned), so don't keep this anymore!
+ No error is raised if the table does not exist physically
+ - its schema is removed even in this case.
+ */
+//! @todo (js): update any structure (e.g. query) that depend on this table!
+ tristate dropTable( TableSchema* tableSchema );
+
+ /*! It is a convenience function, does exactly the same as
+ bool dropTable( KexiDB::TableSchema* tableSchema ) */
+ tristate dropTable( const QString& table );
+
+ /*! Alters \a tableSchema using \a newTableSchema in memory and on the db backend.
+ \return true on success, cancelled if altering was cancelled. */
+//! @todo (js): implement real altering
+//! @todo (js): update any structure (e.g. query) that depend on this table!
+ tristate alterTable( TableSchema& tableSchema, TableSchema& newTableSchema);
+
+ /*! Alters name of table described by \a tableSchema to \a newName.
+ If \a replace is true, destination table is completely dropped and replaced
+ by \a tableSchema, if present. In this case, identifier of
+ \a tableSchema becomes equal to the dropped table's id, what can be useful
+ if \a tableSchema was created with a temporary name and ID (used in AlterTableHandler).
+
+ If \a replace is false (the default) and destination table is present
+ -- false is returned and ERR_OBJECT_EXISTS error is set.
+ The schema of \a tableSchema is updated on success.
+ \return true on success. */
+ bool alterTableName(TableSchema& tableSchema, const QString& newName, bool replace = false);
+
+ /*! Drops a query defined by \a querySchema.
+ If true is returned, schema information \a querySchema is destoyed
+ (because it's owned), so don't keep this anymore!
+ */
+ bool dropQuery( QuerySchema* querySchema );
+
+ /*! It is a convenience function, does exactly the same as
+ bool dropQuery( KexiDB::QuerySchema* querySchema ) */
+ bool dropQuery( const QString& query );
+
+ /*! Removes information about object with \a objId
+ from internal "kexi__object" and "kexi__objectdata" tables.
+ \return true on success. */
+ bool removeObject( uint objId );
+
+ /*! \return first field from \a fieldlist that has system name,
+ null if there are no such field.
+ For checking Driver::isSystemFieldName() is used, so this check can
+ be driver-dependent. */
+ Field* findSystemFieldName(FieldList *fieldlist);
+
+ /*! \return name of any (e.g. first found) database for this connection.
+ This method does not close or open this connection. The method can be used
+ (it is also internally used, e.g. for database dropping) when we need
+ a database name before we can connect and execute any SQL statement
+ (e.g. DROP DATABASE).
+
+ The method can return nul lstring, but in this situation no automatic (implicit)
+ connections could be made, what is useful by e.g. dropDatabase().
+
+ Note for driver developers: return here a name of database which you are sure
+ is existing.
+ Default implementation returns:
+ - value that previously had been set using setAvailableDatabaseName() for
+ this connection, if it is not empty
+ - else (2nd priority): value of DriverBehaviour::ALWAYS_AVAILABLE_DATABASE_NAME
+ if it is not empty.
+
+ See decription of DriverBehaviour::ALWAYS_AVAILABLE_DATABASE_NAME member.
+ You may want to reimplement this method only when you need to depend on
+ this connection specifics
+ (e.g. you need to check something remotely).
+ */
+ virtual QString anyAvailableDatabaseName();
+
+ /*! Sets \a dbName as name of a database that can be accessible.
+ This is option that e.g. application that make use of KexiDB library can set
+ to tune connection's behaviour when it needs to temporary connect to any database
+ in the server to do some work.
+ You can pass empty dbName - then anyAvailableDatabaseName() will try return
+ DriverBehaviour::ALWAYS_AVAILABLE_DATABASE_NAME (the default) value
+ instead of the one previously set with setAvailableDatabaseName().
+
+ \sa anyAvailableDatabaseName()
+ */
+ void setAvailableDatabaseName(const QString& dbName);
+
+ /*! Because some engines need to have opened any database before
+ executing administrative sql statements like "create database" or "drop database",
+ this method is used to use appropriate, existing database for this connection.
+ For file-based db drivers this always return true and does not set tmpdbName
+ to any value. For other db drivers: this sets tmpdbName to db name computed
+ using anyAvailableDatabaseName(), and if the name computed is empty, false
+ is returned; if it is not empty, useDatabase() is called.
+ False is returned also when useDatabase() fails.
+ You can call this method from your application's level if you really want to perform
+ tasks that require any used database. In such a case don't forget
+ to closeDatabase() if returned tmpdbName is not empty.
+
+ Note: This method has nothing to do with creating or using temporary databases
+ in such meaning that these database are not persistent
+ */
+ bool useTemporaryDatabaseIfNeeded(QString &tmpdbName);
+
+ /*! \return autoincrement field's \a aiFieldName value
+ of last inserted record. This refers \a tableName table.
+
+ Simply, method internally fetches last inserted record and returns selected
+ field's value. Requirements: field must be of integer type, there must be a
+ record inserted in current database session (whatever this means).
+ On error (Q_ULLONG)-1 is returned.
+ Last inserted record is identified by magical row identifier, usually called
+ ROWID (PostgreSQL has it as well as SQLite;
+ see DriverBehaviour::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE).
+ ROWID's value will be assigned back to \a ROWID if this pointer is not null.
+ */
+ Q_ULLONG lastInsertedAutoIncValue(const QString& aiFieldName, const QString& tableName,
+ Q_ULLONG* ROWID = 0);
+
+ /*! \overload int lastInsertedAutoIncValue(const QString&, const QString&, Q_ULLONG*)
+ */
+ Q_ULLONG lastInsertedAutoIncValue(const QString& aiFieldName,
+ const TableSchema& table, Q_ULLONG* ROWID = 0);
+
+ /*! Executes query \a statement, but without returning resulting
+ rows (used mostly for functional queries).
+ Only use this method if you really need. */
+ bool executeSQL( const QString& statement );
+
+ //! @short options used in selectStatement()
+ class KEXI_DB_EXPORT SelectStatementOptions
+ {
+ public:
+ SelectStatementOptions();
+ ~SelectStatementOptions();
+
+ //! A mode for escaping identifier, Driver::EscapeDriver|Driver::EscapeAsNecessary by default
+ int identifierEscaping;
+
+ //! True if ROWID should be also retrieved. False by default.
+ bool alsoRetrieveROWID : 1;
+
+ /*! True if relations (LEFT OUTER JOIN) for visible lookup columns should be added.
+ True by default. This is set to false when user-visible statement is generated
+ e.g. for the Query Designer. */
+ bool addVisibleLookupColumns : 1;
+ };
+
+ /*! \return "SELECT ..." statement's string needed for executing query
+ defined by \a querySchema and \a params. */
+ QString selectStatement( QuerySchema& querySchema,
+ const QValueList<QVariant>& params,
+ const SelectStatementOptions& options = SelectStatementOptions() ) const;
+
+ /*! \overload QString selectStatement( QuerySchema& querySchema,
+ QValueList<QVariant> params = QValueList<QVariant>(),
+ const SelectStatementOptions& options = SelectStatementOptions() ) const;
+ \return "SELECT ..." statement's string needed for executing query
+ defined by \a querySchema. */
+ inline QString selectStatement( QuerySchema& querySchema,
+ const SelectStatementOptions& options = SelectStatementOptions() ) const
+ {
+ return selectStatement(querySchema, QValueList<QVariant>(), options);
+ }
+
+ /*! Stores object's schema data (id, name, caption, help text)
+ described by \a sdata on the backend.
+ If \a newObject is true, new entry is created,
+ and (when sdata.id() was <=0), new, unique object identifier
+ is obtained and assigned to \a sdata (see SchemaData::id()).
+
+ If \a newObject is false, it's expected that entry on the
+ backend already exists, so it's updated (changes to identifier are not allowed).
+ \return true on success. */
+ bool storeObjectSchemaData( SchemaData &sdata, bool newObject );
+
+ /*! Added for convenience.
+ \sa setupObjectSchemaData( const KexiDB::RowData &data, SchemaData &sdata ).
+ \return true on success, false on failure and cancelled when such object couldn't */
+ tristate loadObjectSchemaData( int objectID, SchemaData &sdata );
+
+ /*! Finds object schema data for object of type \a objectType and name \a objectName.
+ If the object is found, resulted schema is stored in \a sdata and true is returned,
+ otherwise false is returned. */
+ tristate loadObjectSchemaData( int objectType, const QString& objectName, SchemaData &sdata );
+
+ /*! Loads (potentially large) data block (e.g. xml form's representation), referenced by objectID
+ and puts it to \a dataString. The can be block indexed with optional \a dataID.
+ \return true on success, false on failure and cancelled when there is no such data block
+ \sa storeDataBlock(). */
+ tristate loadDataBlock( int objectID, QString &dataString, const QString& dataID );
+
+ /*! Stores (potentially large) data block \a dataString (e.g. xml form's representation),
+ referenced by objectID. Block will be stored in "kexi__objectdata" table and
+ an optional \a dataID identifier.
+ If there is already such record in the table, it's simply overwritten.
+ \return true on success
+ \sa loadDataBlock(). */
+ bool storeDataBlock( int objectID, const QString &dataString, const QString& dataID = QString::null );
+
+ /*! Removes (potentially large) string data (e.g. xml form's representation),
+ referenced by objectID, and pointed by optional \a dataID.
+ \return true on success. Does not fail if the block does not exist.
+ Note that if \a dataID is not specified, all data blocks for this dialog will be removed.
+ \sa loadDataBlock() storeDataBlock(). */
+ bool removeDataBlock( int objectID, const QString& dataID = QString::null);
+
+ class KEXI_DB_EXPORT TableSchemaChangeListenerInterface
+ {
+ public:
+ TableSchemaChangeListenerInterface() {}
+ virtual ~TableSchemaChangeListenerInterface() {}
+ /*! Closes listening object so it will be deleted and thus no longer use
+ a conflicting table schema. */
+ virtual tristate closeListener() = 0;
+
+ /*! i18n'd string that can be displayed for user to inform about
+ e.g. conflicting listeners. */
+ QString listenerInfoString;
+ };
+//TMP// TODO: will be more generic
+ /** Register \a listener for receiving (listening) information about changes
+ in TableSchema object. Changes could be: altering and removing. */
+ void registerForTableSchemaChanges(TableSchemaChangeListenerInterface& listener,
+ TableSchema& schema);
+
+ void unregisterForTableSchemaChanges(TableSchemaChangeListenerInterface& listener,
+ TableSchema &schema);
+
+ void unregisterForTablesSchemaChanges(TableSchemaChangeListenerInterface& listener);
+
+ QPtrList<Connection::TableSchemaChangeListenerInterface>*
+ tableSchemaChangeListeners(TableSchema& tableSchema) const;
+
+ tristate closeAllTableSchemaChangeListeners(TableSchema& tableSchema);
+
+ /*! @internal Removes \a tableSchema from internal structures and
+ destroys it. Does not make any change at the backend. */
+ void removeTableSchemaInternal(KexiDB::TableSchema *tableSchema);
+
+ /*! @internal. Inserts internal table to Connection's structures, so it can be found by name.
+ This method is used for example in KexiProject to insert information about "kexi__blobs"
+ table schema. Use createTable() to physically create table. After createTable()
+ calling insertInternalTableSchema() is not required.
+ Also used internally by newKexiDBSystemTableSchema(const QString& tsname) */
+ void insertInternalTableSchema(TableSchema *tableSchema);
+
+//! @todo move this somewhere to low level class (MIGRATION?)
+ /*! LOW LEVEL METHOD. For reimplemenation: returns true if table
+ with name \a tableName exists in the database.
+ \return false if it does not exist or error occurred.
+ The lookup is case insensitive. */
+ virtual bool drv_containsTable( const QString &tableName ) = 0;
+
+ /*! Creates table using \a tableSchema information.
+ \return true on success. Default implementation
+ builds a statement using createTableStatement() and calls drv_executeSQL()
+ Note for driver developers: reimplement this only if you want do to
+ this in other way.
+
+ Moved to public for KexiMigrate.
+ @todo fix this after refactoring
+ */
+ virtual bool drv_createTable( const TableSchema& tableSchema );
+
+ /*! Alters table's described \a tableSchema name to \a newName.
+ This is the default implementation, using "ALTER TABLE <oldname> RENAME TO <newname>",
+ what's supported by SQLite >= 3.2, PostgreSQL, MySQL.
+ Backends lacking ALTER TABLE, for example SQLite2, reimplement this with by an inefficient
+ data copying to a new table. In any case, renaming is performed at the backend.
+ It's good idea to keep the operation within a transaction.
+ \return true on success.
+
+ Moved to public for KexiProject.
+ @todo fix this after refactoring
+ */
+ virtual bool drv_alterTableName(TableSchema& tableSchema, const QString& newName);
+
+ /*! Physically drops table named with \a name.
+ Default impelmentation executes "DROP TABLE.." command,
+ so you rarely want to change this.
+
+ Moved to public for KexiMigrate
+ @todo fix this after refatoring
+ */
+ virtual bool drv_dropTable( const QString& name );
+
+ /*! Prepare a SQL statement and return a \a PreparedStatement instance. */
+ virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type,
+ FieldList& fields) = 0;
+
+ bool isInternalTableSchema(const QString& tableName);
+
+ /*! Setups schema data for object that owns sdata (e.g. table, query)
+ using \a cursor opened on 'kexi__objects' table, pointing to a record
+ corresponding to given object.
+
+ Moved to public for KexiMigrate
+ @todo fix this after refatoring
+ */
+ bool setupObjectSchemaData( const RowData &data, SchemaData &sdata );
+
+ /*! \return a new field table schema for a table retrieved from \a data.
+ Used internally by tableSchema().
+
+ Moved to public for KexiMigrate
+ @todo fix this after refatoring
+ */
+ KexiDB::Field* setupField( const RowData &data );
+
+ protected:
+ /*! Used by Driver */
+ Connection( Driver *driver, ConnectionData &conn_data );
+
+ /*! Method to be called form Connection's subclass destructor.
+ \sa ~Connection() */
+ void destroy();
+
+ /*! @internal drops table \a tableSchema physically, but destroys
+ \a tableSchema object only if \a alsoRemoveSchema is true.
+ Used (alsoRemoveSchema==false) on table altering:
+ if recreating table can failed we're giving up and keeping
+ the original table schema (even if it is no longer points to any real data). */
+ tristate dropTable( KexiDB::TableSchema* tableSchema, bool alsoRemoveSchema);
+
+ /*! For reimplemenation: connects to database. \a version should be set to real
+ server's version.
+ \return true on success. */
+ virtual bool drv_connect(KexiDB::ServerVersionInfo& version) = 0;
+
+ /*! For reimplemenation: disconnects database
+ \return true on success. */
+ virtual bool drv_disconnect() = 0;
+
+ /*! Executes query \a statement, but without returning resulting
+ rows (used mostly for functional queries).
+ Only use this method if you really need. */
+ virtual bool drv_executeSQL( const QString& statement ) = 0;
+
+ /*! For reimplemenation: loads list of databases' names available for this connection
+ and adds these names to \a list. If your server is not able to offer such a list,
+ consider reimplementing drv_databaseExists() instead.
+ The method should return true only if there was no error on getting database names
+ list from the server.
+ Default implementation puts empty list into \a list and returns true. */
+ virtual bool drv_getDatabasesList( QStringList &list );
+
+//! @todo move this somewhere to low level class (MIGRATION?)
+ /*! LOW LEVEL METHOD. For reimplemenation: loads low-level list of table names
+ available for this connection. The names are in lower case.
+ The method should return true only if there was no error on getting database names
+ list from the server. */
+ virtual bool drv_getTablesList( QStringList &list ) = 0;
+
+ /*! For optional reimplemenation: asks server if database \a dbName exists.
+ This method is used internally in databaseExists(). The default implementation
+ calls databaseNames and checks if that list contains \a dbName. If you need to
+ ask the server specifically if a database exists, eg. if you can't retrieve a list
+ of all available database names, please reimplement this method and do all
+ needed checks.
+
+ See databaseExists() description for details about ignoreErrors argument.
+ You should use this appropriately in your implementation.
+
+ Note: This method should also work if there is already database used (with useDatabase());
+ in this situation no changes should be made in current database selection. */
+ virtual bool drv_databaseExists( const QString &dbName, bool ignoreErrors = true );
+
+ /*! For reimplemenation: creates new database using connection */
+ virtual bool drv_createDatabase( const QString &dbName = QString::null ) = 0;
+
+ /*! For reimplemenation: opens existing database using connection
+ \return true on success, false on failure and cancelled if user has cancelled this action. */
+ virtual bool drv_useDatabase( const QString &dbName = QString::null, bool *cancelled = 0,
+ MessageHandler* msgHandler = 0 ) = 0;
+
+ /*! For reimplemenation: closes previously opened database
+ using connection. */
+ virtual bool drv_closeDatabase() = 0;
+
+ /*! \return true if internal driver's structure is still in opened/connected
+ state and database is used.
+ Note for driver developers: Put here every test that you can do using your
+ internal engine's database API,
+ eg (a bit schematic): my_connection_struct->isConnected()==true.
+ Do not check things like Connection::isDatabaseUsed() here or other things
+ that "KexiDB already knows" at its level.
+ If you cannot test anything, just leave default implementation (that returns true).
+
+ Result of this method is used as an addtional chance to check for isDatabaseUsed().
+ Do not call this method from your driver's code, it should be used at KexiDB
+ level only.
+ */
+ virtual bool drv_isDatabaseUsed() const { return true; }
+
+ /*! For reimplemenation: drops database from the server
+ using connection. After drop, database shouldn't be accessible
+ anymore. */
+ virtual bool drv_dropDatabase( const QString &dbName = QString::null ) = 0;
+
+ /*! \return "CREATE TABLE ..." statement string needed for \a tableSchema
+ creation in the database.
+
+ Note: The statement string can be specific for this connection's driver database,
+ and thus not reusable in general.
+ */
+ QString createTableStatement( const TableSchema& tableSchema ) const;
+
+
+ /*! \return "SELECT ..." statement's string needed for executing query
+ defined by "select * from table_name" where <i>table_name</i> is \a tableSchema's name.
+ This method's variant can be useful when there is no appropriate QuerySchema defined.
+
+ Note: The statement string can be specific for this connection's driver database,
+ and thus not reusable in general.
+ */
+ QString selectStatement( TableSchema& tableSchema,
+ const SelectStatementOptions& options = SelectStatementOptions() ) const;
+
+ /*!
+ Creates table named by \a tableSchemaName. Schema object must be on
+ schema tables' list before calling this method (otherwise false if returned).
+ Just uses drv_createTable( const KexiDB::TableSchema& tableSchema ).
+ Used internally, e.g. in createDatabase().
+ \return true on success
+ */
+ virtual bool drv_createTable( const QString& tableSchemaName );
+
+// /*! Executes query \a statement and returns resulting rows
+// (used mostly for SELECT query). */
+// virtual bool drv_executeQuery( const QString& statement ) = 0;
+
+ /*! \return unique identifier of last inserted row.
+ Typically this is just primary key value.
+ This identifier could be reused when we want to reference
+ just inserted row.
+ Note for driver developers: contact js (at) iidea.pl
+ if your engine do not offers this information. */
+ virtual Q_ULLONG drv_lastInsertRowID() = 0;
+
+ /*! Note for driver developers: begins new transaction
+ and returns handle to it. Default implementation just
+ executes "BEGIN" sql statement and returns just empty data (TransactionData object).
+
+ Drivers that do not support transactions (see Driver::features())
+ do never call this method.
+ Reimplement this method if you need to do something more
+ (e.g. if you driver will support multiple transactions per connection).
+ Make subclass of TransactionData (declared in transaction.h)
+ and return object of this subclass.
+ You should return NULL if any error occurred.
+ Do not check anything in connection (isConnected(), etc.) - all is already done.
+ */
+ virtual TransactionData* drv_beginTransaction();
+
+ /*! Note for driver developers: begins new transaction
+ and returns handle to it. Default implementation just
+ executes "COMMIT" sql statement and returns true on success.
+
+ \sa drv_beginTransaction()
+ */
+ virtual bool drv_commitTransaction(TransactionData* trans);
+
+ /*! Note for driver developers: begins new transaction
+ and returns handle to it. Default implementation just
+ executes "ROLLBACK" sql statement and returns true on success.
+
+ \sa drv_beginTransaction()
+ */
+ virtual bool drv_rollbackTransaction(TransactionData* trans);
+
+ /*! Changes autocommiting option for established connection.
+ \return true on success.
+
+ Note for driver developers: reimplement this only if your engine
+ allows to set special auto commit option (like "SET AUTOCOMMIT=.." in MySQL).
+ If not, auto commit behaviour will be simulated if at least single
+ transactions per connection are supported by the engine.
+ Do not set any internal flags for autocommiting -- it is already done inside
+ setAutoCommit().
+
+ Default implementation does nothing with connection, just returns true.
+
+ \sa drv_beginTransaction(), autoCommit(), setAutoCommit()
+ */
+ virtual bool drv_setAutoCommit(bool on);
+
+ /*! Internal, for handling autocommited transactions:
+ begins transaction if one is supported.
+ \return true if new transaction started
+ successfully or no transactions are supported at all by the driver
+ or if autocommit option is turned off.
+ A handle to a newly created transaction (or null on error) is passed
+ to \a tg parameter.
+
+ Special case when used database driver has only single transaction support
+ (Driver::SingleTransactions):
+ and there is already transaction started, it is committed before
+ starting a new one, but only if this transaction has been started inside Connection object.
+ (i.e. by beginAutoCommitTransaction()). Otherwise, a new transaction will not be started,
+ but true will be returned immediately.
+ */
+ bool beginAutoCommitTransaction(TransactionGuard& tg);
+
+ /*! Internal, for handling autocommited transactions:
+ Commits transaction prevoiusly started with beginAutoCommitTransaction().
+ \return true on success or when no transactions are supported
+ at all by the driver.
+
+ Special case when used database driver has only single transaction support
+ (Driver::SingleTransactions): if \a trans has been started outside Connection object
+ (i.e. not by beginAutoCommitTransaction()), the transaction will not be committed.
+ */
+ bool commitAutoCommitTransaction(const Transaction& trans);
+
+ /*! Internal, for handling autocommited transactions:
+ Rollbacks transaction prevoiusly started with beginAutoCommitTransaction().
+ \return true on success or when no transactions are supported
+ at all by the driver.
+
+ Special case when used database driver has only single transaction support
+ (Driver::SingleTransactions): \a trans will not be rolled back
+ if it has been started outside this Connection object.
+ */
+ bool rollbackAutoCommitTransaction(const Transaction& trans);
+
+ /*! Creates cursor data and initializes cursor
+ using \a statement for later data retrieval. */
+// virtual CursorData* drv_createCursor( const QString& statement ) = 0;
+ /*! Closes and deletes cursor data. */
+// virtual bool drv_deleteCursor( CursorData *data ) = 0;
+
+ /*! Helper: checks if connection is established;
+ if not: error message is set up and false returned */
+ bool checkConnected();
+
+ /*! Helper: checks both if connection is established and database any is used;
+ if not: error message is set up and false returned */
+ bool checkIsDatabaseUsed();
+
+ /*! \return a full table schema for a table retrieved using 'kexi__*' system tables.
+ Used internally by tableSchema() methods. */
+ TableSchema* setupTableSchema( const RowData &data );
+
+ /*! \return a full query schema for a query using 'kexi__*' system tables.
+ Used internally by querySchema() methods. */
+ QuerySchema* setupQuerySchema( const RowData &data );
+
+ /*! Update a row. */
+ bool updateRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool useROWID = false);
+ /*! Insert a new row. */
+ bool insertRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool getROWID = false);
+ /*! Delete an existing row. */
+ bool deleteRow(QuerySchema &query, RowData& data, bool useROWID = false);
+ /*! Delete all existing rows. */
+ bool deleteAllRows(QuerySchema &query);
+
+ /*! Allocates all needed table KexiDB system objects for kexi__* KexiDB liblary's
+ system tables schema.
+ These objects are used internally in this connection
+ and are added to list of tables (by name,
+ not by id because these have no ids).
+ */
+ bool setupKexiDBSystemSchema();
+
+ /*! used internally by setupKexiDBSystemSchema():
+ Allocates single table KexiDB system object named \a tsname
+ and adds this to list of such objects (for later removal on closeDatabase()).
+ */
+ TableSchema* newKexiDBSystemTableSchema(const QString& tsname);
+
+ //! Identifier escaping function in the associated Driver.
+ /*! Calls the identifier escaping function in the associated Driver to
+ escape table and column names. This should be used when explicitly
+ constructing SQL strings (e.g. "FROM " + escapeIdentifier(tablename)).
+ It should not be used for other functions (e.g. don't do
+ useDatabase(escapeIdentifier(database))), because the identifier will
+ be escaped when the called function generates, for example, "USE " +
+ escapeIdentifier(database).
+
+ For efficiency, kexi__* system tables and columns therein are not escaped
+ - we assume these are valid identifiers for all drivers.
+ */
+ inline QString escapeIdentifier(const QString& id,
+ int escaping = Driver::EscapeDriver|Driver::EscapeAsNecessary ) const {
+ return m_driver->escapeIdentifier(id, escaping);
+ }
+
+ /*! Called by TableSchema -- signals destruction to Connection object
+ To avoid having deleted table object on its list. */
+ void removeMe(TableSchema *ts);
+
+ /*! @internal
+ \return true if the cursor \a cursor contains column \a column,
+ else, sets appropriate error with a message and returns false. */
+ bool checkIfColumnExists(Cursor *cursor, uint column);
+
+ /*! @internal used by querySingleRecord() methods.
+ Note: "LIMIT 1" is appended to \a sql statement if \a addLimitTo1 is true (the default). */
+ tristate querySingleRecordInternal(RowData &data, const QString* sql,
+ QuerySchema* query, bool addLimitTo1 = true);
+
+ /*! @internal used by Driver::createConnection().
+ Only works if connection is not yet established. */
+ void setReadOnly(bool set);
+
+ /*! Loads extended schema information for table \a tableSchema,
+ if present (see ExtendedTableSchemaInformation in Kexi Wiki).
+ \return true on success */
+ bool loadExtendedTableSchemaData(TableSchema& tableSchema);
+
+ /*! Stores extended schema information for table \a tableSchema,
+ (see ExtendedTableSchemaInformation in Kexi Wiki).
+ The action is performed within the current transaction,
+ so it's up to you to commit.
+ Used, e.g. by createTable(), within its transaction.
+ \return true on success */
+ bool storeExtendedTableSchemaData(TableSchema& tableSchema);
+
+ /*! @internal
+ Stores main field's schema information for field \a field.
+ Used in table altering code when information in kexi__fields has to be updated.
+ \return true on success and false on failure. */
+ bool storeMainFieldSchema(Field *field);
+
+ //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /*! This is a part of alter table interface implementing lower-level operations
+ used to perform table schema altering. Used by AlterTableHandler.
+
+ Changes value of field property.
+ \return true on success, false on failure, cancelled if the action has been cancelled.
+
+ Note for driver developers: implement this if the driver has to support the altering. */
+ virtual tristate drv_changeFieldProperty(TableSchema &table, Field& field,
+ const QString& propertyName, const QVariant& value) {
+ Q_UNUSED(table); Q_UNUSED(field); Q_UNUSED(propertyName); Q_UNUSED(value);
+ return cancelled; }
+
+ //! cursors created for this connection
+ QPtrDict<KexiDB::Cursor> m_cursors;
+
+ private:
+ ConnectionPrivate* d; //!< @internal d-pointer class.
+ Driver* const m_driver; //!< The driver this \a Connection instance uses.
+ bool m_destructor_started : 1; //!< helper: true if destructor is started.
+
+ friend class KexiDB::Driver;
+ friend class KexiDB::Cursor;
+ friend class KexiDB::TableSchema; //!< for removeMe()
+ friend class KexiDB::DatabaseProperties; //!< for setError()
+ friend class ConnectionPrivate;
+ friend class KexiDB::AlterTableHandler;
+};
+
+} //namespace KexiDB
+
+#endif
+
diff --git a/kexi/kexidb/connection_p.h b/kexi/kexidb/connection_p.h
new file mode 100644
index 000000000..f3b80fce4
--- /dev/null
+++ b/kexi/kexidb/connection_p.h
@@ -0,0 +1,40 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_CONNECTION_P_H
+#define KEXIDB_CONNECTION_P_H
+
+#include "connection.h"
+
+namespace KexiDB {
+
+//! Interface for connection's internals, implemented within drivers
+class KEXI_DB_EXPORT ConnectionInternal
+{
+ public:
+ ConnectionInternal(Connection *conn);
+ virtual ~ConnectionInternal();
+ virtual void storeResult() = 0;
+
+ Connection* connection;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/connectiondata.cpp b/kexi/kexidb/connectiondata.cpp
new file mode 100644
index 000000000..a74237cce
--- /dev/null
+++ b/kexi/kexidb/connectiondata.cpp
@@ -0,0 +1,114 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/connectiondata.h>
+
+#include <kexidb/drivermanager.h>
+
+#include <qfileinfo.h>
+#include <qdir.h>
+
+#include <klocale.h>
+
+using namespace KexiDB;
+
+namespace KexiDB {
+//! @internal
+class ConnectionData::Private {
+public:
+ Private() {
+ dummy=false;
+ }
+ ~Private() {}
+ bool dummy;
+};
+}
+
+/*================================================================*/
+
+ConnectionDataBase::ConnectionDataBase()
+ : id(-1), port(0), useLocalSocketFile(true), savePassword(false)
+{
+}
+
+/*================================================================*/
+
+ConnectionData::ConnectionData()
+: QObject()
+, ConnectionDataBase()
+, formatVersion(0)
+, priv(new ConnectionData::Private())
+{
+}
+
+ConnectionData::ConnectionData(const ConnectionData& cd)
+: QObject()
+, ConnectionDataBase()
+, priv(0)
+{
+ static_cast<ConnectionData&>(*this) = static_cast<const ConnectionData&>(cd);//copy data members
+}
+
+ConnectionData::~ConnectionData()
+{
+ delete priv;
+ priv = 0;
+}
+
+ConnectionData& ConnectionData::operator=(const ConnectionData& cd)
+{
+ if (this != &cd) {
+ delete priv; //this is old
+ static_cast<ConnectionDataBase&>(*this) = static_cast<const ConnectionDataBase&>(cd);//copy data members
+ priv = new ConnectionData::Private();
+ *priv = *cd.priv;
+ }
+ return *this;
+}
+
+void ConnectionData::setFileName( const QString& fn )
+{
+ QFileInfo file(fn);
+ if (!fn.isEmpty() && m_fileName != file.absFilePath()) {
+ m_fileName = QDir::convertSeparators(file.absFilePath());
+ m_dbPath = QDir::convertSeparators(file.dirPath(true));
+ m_dbFileName = file.fileName();
+ }
+}
+
+QString ConnectionData::serverInfoString(bool addUser) const
+{
+ const QString& i18nFile = i18n("file");
+
+ if (!m_dbFileName.isEmpty())
+ return i18nFile+": "+(m_dbPath.isEmpty() ? "" : m_dbPath
+ + QDir::separator()) + m_dbFileName;
+
+ DriverManager man;
+ if (!driverName.isEmpty()) {
+ Driver::Info info = man.driverInfo(driverName);
+ if (!info.name.isEmpty() && info.fileBased)
+ return QString("<")+i18nFile+">";
+ }
+
+ return ( (userName.isEmpty() || !addUser) ? QString("") : (userName+"@"))
+ + (hostName.isEmpty() ? QString("localhost") : hostName)
+ + (port!=0 ? (QString(":")+QString::number(port)) : QString::null);
+}
+
diff --git a/kexi/kexidb/connectiondata.h b/kexi/kexidb/connectiondata.h
new file mode 100644
index 000000000..cd3c15375
--- /dev/null
+++ b/kexi/kexidb/connectiondata.h
@@ -0,0 +1,239 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_CONNECTION_DATA_H
+#define KEXIDB_CONNECTION_DATA_H
+
+#include <kexidb/kexidb_export.h>
+
+#include <qobject.h>
+#include <qstring.h>
+#include <qptrlist.h>
+
+namespace KexiDB {
+
+/*! ConnectionDataBase is a helper class for ConnectionData. It
+ is not intended to be instantiated explicitly. Instead, use the
+ ConnectionData class. */
+/*! @internal
+ Used by ConnectionData.
+ It is easier to internally operate on non-QObject-derived object,
+ e.g.: to copy data members in ConnectionData ctor. */
+class ConnectionDataBase
+{
+ public:
+ ConnectionDataBase();
+
+ /*!
+ \brief The caption of the connection.
+
+ Captions are optional for identyfying given connection
+ by name eg. for users.
+ */
+ QString caption;
+
+ /*!
+ \brief The additional description for the connection
+ */
+ QString description;
+
+ /*!
+ \brief Used for identifying a single piece of data in a set
+
+ Optional ID used for identifying a single piece data in a set.
+ ConnectionData::ConstList for example) This is set automatically
+ when needed. By default: -1.
+ */
+ int id;
+
+ /*!
+ \brief the name of the driver that should be used to create a connection
+
+ Name (unique, not i18n'd) of driver that is used (or should be used) to
+ create a connection. If you pass this ConnectionData object to
+ KexiDB::Driver::createConnection() to create connection, the @a driverName member
+ will be updated with a valid KexiDB driver name.
+ In other situations the @a driverName member may be used to store information what
+ driver should be used to perform connection, before we get an appropriate
+ driver object from DriverManager.
+ */
+ QString driverName;
+
+ /*!
+ \brief Host name used for the remote connection.
+
+ Can be empty if the connection is not remote. If it is empty "localhost" is used.
+ */
+ QString hostName;
+
+ /*!
+ \brief Port used for the remote connection.
+
+ The default is 0, what means we use don't change the database engine's default port.
+ */
+ unsigned short int port;
+
+ /*!
+ \brief True if local socket file should be used instead of TCP/IP port.
+
+ Only meaningful for connections with localhost as server.
+ True by default, so local communication can be optimized, and users can avoid problems
+ with TCP/IP connections disabled by firewalls.
+
+ If true, @a hostName and @a port will be ignored and @a localSocketFileName will be used.
+ On MS Windows this option is often ignored and TCP/IP connection to the localhost is performed.
+ */
+ bool useLocalSocketFile;
+
+ /*!
+ \brief Name of local (named) socket file.
+
+ For local connections only. If empty, it's driver will try to locate existing local socket
+ file. Empty by default.
+ */
+ QString localSocketFileName;
+
+ /*!
+ \brief Password used for the connection.
+
+ Can be empty string or null. If it is empty (equal to ""), empty password is passed to the driver.
+ If it is null (QString::null), no password is passed to the driver.
+ In this case, applications using KexiDB should ask for the password. */
+ QString password;
+
+ /*!
+ \brief True if password should be saved to a file for the connection.
+
+ False by default, in most cases can be set to true when nonempty
+ password has been loaded from a file.
+ For instance, this flag can be then shown for a user as a checkbox.
+ */
+ bool savePassword;
+
+ /*!
+ \brief Username used for the connection.
+
+ Can be empty. */
+ QString userName;
+
+ protected:
+ /*!
+ \brief The filename for file-based databases
+
+ For file-based database engines like SQLite, \a fileName is used
+ instead hostName and port
+ */
+ QString m_fileName;
+
+ /*!
+ \brief Absolute path to the database file
+
+ Will be empty if database is not file-based
+ */
+ QString m_dbPath;
+
+ /*!
+ \brief Filename of the database file
+
+ Will be empty if database is not file-based
+ */
+ QString m_dbFileName;
+};
+
+//! Database specific connection data, e.g. host, port.
+/*! Connection data, once configured, can be later stored for reuse.
+*/
+class KEXI_DB_EXPORT ConnectionData : public QObject, public ConnectionDataBase
+{
+ public:
+ typedef QPtrList<ConnectionData> List;
+ typedef QPtrListIterator<ConnectionData> ListIterator;
+
+ ConnectionData();
+
+ ConnectionData(const ConnectionData&);
+
+ ~ConnectionData();
+
+ ConnectionData& operator=(const ConnectionData& cd);
+
+ /*!
+ \brief Set the filename used by the connection
+
+ For file-based database engines, like SQLite, you should use this
+ function to set the file name of the database to use.
+ \a fn can be either absolute or relative path to the file.
+ */
+ void setFileName( const QString& fn );
+
+ /*!
+ \brief Get the filename used by the connection
+
+ For file-based database engines like SQLite, \a fileName is used
+ instead hostName and port.
+ @return An absolute path to the database file being used
+ */
+ QString fileName() const { return m_fileName; }
+
+ /*!
+ \brief The directory the database file is in
+
+ \return file path (for file-based engines) but without a file name
+ */
+ QString dbPath() const { return m_dbPath; }
+
+ /*!
+ \brief The file name (without path) of the database file
+
+ \return The file name (for file-based engines) but without a full path
+ */
+ QString dbFileName() const { return m_dbFileName; }
+
+ /*!
+ \brief A user-friendly string for the server
+
+ \return a user-friendly string like:
+ - "myhost.org:12345" if a host and port is specified;
+ - "localhost:12345" of only port is specified;
+ - "user@myhost.org:12345" if also user is specified
+ - "<file>" if file-based driver is assigned but no filename is assigned
+ - "file: pathto/mydb.kexi" if file-based driver is assigned and
+ filename is assigned
+
+ User's name is added if \a addUser is true (the default).
+ */
+ QString serverInfoString(bool addUser = true) const;
+
+ /*! @internal
+ Format version used when saving the data to a shortcut file.
+ This is set to 0 by default what means KexiDBShortcutFile_version should be used on saving.
+ If KexiDBConnShortcutFile was used to create this KexiProjectData object,
+ the version information is be retrieved from the file. */
+ uint formatVersion;
+
+ protected:
+ class Private;
+ Private *priv;
+
+ friend class Connection;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/cursor.cpp b/kexi/kexidb/cursor.cpp
new file mode 100644
index 000000000..4b9cdea31
--- /dev/null
+++ b/kexi/kexidb/cursor.cpp
@@ -0,0 +1,571 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/cursor.h>
+
+#include <kexidb/driver.h>
+#include <kexidb/driver_p.h>
+#include <kexidb/error.h>
+#include <kexidb/roweditbuffer.h>
+#include <kexiutils/utils.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+using namespace KexiDB;
+
+#ifdef KEXI_DEBUG_GUI
+
+#endif
+
+Cursor::Cursor(Connection* conn, const QString& statement, uint options)
+ : QObject()
+ , m_conn(conn)
+ , m_query(0)
+ , m_rawStatement(statement)
+ , m_options(options)
+{
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addKexiDBDebug(QString("Create cursor: ")+statement);
+#endif
+ init();
+}
+
+Cursor::Cursor(Connection* conn, QuerySchema& query, uint options )
+ : QObject()
+ , m_conn(conn)
+ , m_query(&query)
+ , m_options(options)
+{
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addKexiDBDebug(QString("Create cursor for query \"%1\": ").arg(query.name())+query.debugString());
+#endif
+ init();
+}
+
+void Cursor::init()
+{
+ assert(m_conn);
+ m_conn->m_cursors.insert(this,this);
+ m_opened = false;
+// , m_atFirst(false)
+// , m_atLast(false)
+// , m_beforeFirst(false)
+ m_atLast = false;
+ m_afterLast = false;
+ m_readAhead = false;
+ m_at = 0;
+//js:todo: if (m_query)
+// m_fieldCount = m_query->fieldsCount();
+// m_fieldCount = m_query ? m_query->fieldCount() : 0; //do not know
+ //<members related to buffering>
+// m_cols_pointers_mem_size = 0;
+ m_records_in_buf = 0;
+ m_buffering_completed = false;
+ m_at_buffer = false;
+ m_result = -1;
+
+ m_containsROWIDInfo = (m_query && m_query->masterTable())
+ && m_conn->driver()->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false;
+
+ if (m_query) {
+ //get list of all fields
+ m_fieldsExpanded = new QueryColumnInfo::Vector();
+ *m_fieldsExpanded = m_query->fieldsExpanded(
+ m_containsROWIDInfo ? QuerySchema::WithInternalFieldsAndRowID : QuerySchema::WithInternalFields);
+ m_logicalFieldCount = m_fieldsExpanded->count()
+ - m_query->internalFields().count() - (m_containsROWIDInfo?1:0);
+ m_fieldCount = m_fieldsExpanded->count();
+ } else {
+ m_fieldsExpanded = 0;
+ m_logicalFieldCount = 0;
+ m_fieldCount = 0;
+ }
+ m_orderByColumnList = 0;
+ m_queryParameters = 0;
+}
+
+Cursor::~Cursor()
+{
+#ifdef KEXI_DEBUG_GUI
+ if (m_query)
+ KexiUtils::addKexiDBDebug(QString("~ Delete cursor for query"));
+ else
+ KexiUtils::addKexiDBDebug(QString("~ Delete cursor: ")+m_rawStatement);
+#endif
+/* if (!m_query)
+ KexiDBDbg << "Cursor::~Cursor() '" << m_rawStatement.latin1() << "'" << endl;
+ else
+ KexiDBDbg << "Cursor::~Cursor() " << endl;*/
+
+ //take me if delete was
+ if (!m_conn->m_destructor_started)
+ m_conn->m_cursors.take(this);
+ else {
+ KexiDBDbg << "Cursor::~Cursor() can be destroyed with Conenction::deleteCursor(), not with delete operator !"<< endl;
+ exit(1);
+ }
+ delete m_fieldsExpanded;
+ delete m_queryParameters;
+}
+
+bool Cursor::open()
+{
+ if (m_opened) {
+ if (!close())
+ return false;
+ }
+ if (!m_rawStatement.isEmpty())
+ m_conn->m_sql = m_rawStatement;
+ else {
+ if (!m_query) {
+ KexiDBDbg << "Cursor::open(): no query statement (or schema) defined!" << endl;
+ setError(ERR_SQL_EXECUTION_ERROR, i18n("No query statement or schema defined."));
+ return false;
+ }
+ Connection::SelectStatementOptions options;
+ options.alsoRetrieveROWID = m_containsROWIDInfo; /*get ROWID if needed*/
+ m_conn->m_sql = m_queryParameters
+ ? m_conn->selectStatement( *m_query, *m_queryParameters, options )
+ : m_conn->selectStatement( *m_query, options );
+ if (m_conn->m_sql.isEmpty()) {
+ KexiDBDbg << "Cursor::open(): empty statement!" << endl;
+ setError(ERR_SQL_EXECUTION_ERROR, i18n("Query statement is empty."));
+ return false;
+ }
+ }
+ m_sql = m_conn->m_sql;
+ m_opened = drv_open();
+// m_beforeFirst = true;
+ m_afterLast = false; //we are not @ the end
+ m_at = 0; //we are before 1st rec
+ if (!m_opened) {
+ setError(ERR_SQL_EXECUTION_ERROR, i18n("Error opening database cursor."));
+ return false;
+ }
+ m_validRecord = false;
+
+//luci: WHAT_EXACTLY_SHOULD_THAT_BE?
+// if (!m_readAhead) // jowenn: to ensure before first state, without cluttering implementation code
+ if (m_conn->driver()->beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY) {
+// KexiDBDbg << "READ AHEAD:" << endl;
+ m_readAhead = getNextRecord(); //true if any record in this query
+// KexiDBDbg << "READ AHEAD = " << m_readAhead << endl;
+ }
+ m_at = 0; //we are still before 1st rec
+ return !error();
+}
+
+bool Cursor::close()
+{
+ if (!m_opened)
+ return true;
+ bool ret = drv_close();
+
+ clearBuffer();
+
+ m_opened = false;
+// m_beforeFirst = false;
+ m_afterLast = false;
+ m_readAhead = false;
+ m_fieldCount = 0;
+ m_logicalFieldCount = 0;
+ m_at = -1;
+
+// KexiDBDbg<<"Cursor::close() == "<<ret<<endl;
+ return ret;
+}
+
+bool Cursor::reopen()
+{
+ if (!m_opened)
+ return open();
+ return close() && open();
+}
+
+bool Cursor::moveFirst()
+{
+ if (!m_opened)
+ return false;
+// if (!m_beforeFirst) { //cursor isn't @ first record now: reopen
+ if (!m_readAhead) {
+ if (m_options & Buffered) {
+ if (m_records_in_buf==0 && m_buffering_completed) {
+ //eof and bof should now return true:
+ m_afterLast = true;
+ m_at = 0;
+ return false; //buffering completed and there is no records!
+ }
+ if (m_records_in_buf>0) {
+ //set state as we would be before first rec:
+ m_at_buffer = false;
+ m_at = 0;
+ //..and move to next, ie. 1st record
+// m_afterLast = m_afterLast = !getNextRecord();
+ m_afterLast = !getNextRecord();
+ return !m_afterLast;
+ }
+ }
+ if (m_afterLast && m_at==0) //failure if already no records
+ return false;
+ if (!reopen()) //try reopen
+ return false;
+ if (m_afterLast) //eof
+ return false;
+ }
+ else {
+ //we have a record already read-ahead: we now point @ that:
+ m_at = 1;
+ }
+// if (!m_atFirst) { //cursor isn't @ first record now: reopen
+// reopen();
+// }
+// if (m_validRecord) {
+// return true; //there is already valid record retrieved
+// }
+ //get first record
+// if (drv_moveFirst() && drv_getRecord()) {
+// m_beforeFirst = false;
+ m_afterLast = false;
+ m_readAhead = false; //1st record had been read
+// }
+ return m_validRecord;
+}
+
+bool Cursor::moveLast()
+{
+ if (!m_opened)
+ return false;
+ if (m_afterLast || m_atLast) {
+ return m_validRecord; //we already have valid last record retrieved
+ }
+ if (!getNextRecord()) { //at least next record must be retrieved
+// m_beforeFirst = false;
+ m_afterLast = true;
+ m_validRecord = false;
+ m_atLast = false;
+ return false; //no records
+ }
+ while (getNextRecord()) //move after last rec.
+ ;
+// m_beforeFirst = false;
+ m_afterLast = false;
+ //cursor shows last record data
+ m_atLast = true;
+// m_validRecord = true;
+
+/*
+ //we are before or @ last record:
+// if (m_atLast && m_validRecord) //we're already @ last rec.
+// return true;
+ if (m_validRecord) {
+ if (drv_getRecord())
+ }
+ if (!m_validRecord) {
+ if (drv_getRecord() && m_validRecord)
+ return true;
+ reopen();
+ }
+ */
+ return true;
+}
+
+bool Cursor::moveNext()
+{
+ if (!m_opened || m_afterLast)
+ return false;
+ if (getNextRecord()) {
+// m_validRecord = true;
+ return true;
+ }
+ return false;
+}
+
+bool Cursor::movePrev()
+{
+ if (!m_opened /*|| m_beforeFirst*/ || !(m_options & Buffered))
+ return false;
+
+ //we're after last record and there are records in the buffer
+ //--let's move to last record
+ if (m_afterLast && (m_records_in_buf>0)) {
+ drv_bufferMovePointerTo(m_records_in_buf-1);
+ m_at=m_records_in_buf;
+ m_at_buffer = true; //now current record is stored in the buffer
+ m_validRecord=true;
+ m_afterLast=false;
+ return true;
+ }
+ //we're at first record: go BOF
+ if ((m_at <= 1) || (m_records_in_buf <= 1/*sanity*/)) {
+ m_at=0;
+ m_at_buffer = false;
+ m_validRecord=false;
+ return false;
+ }
+
+ m_at--;
+ if (m_at_buffer) {//we already have got a pointer to buffer
+ drv_bufferMovePointerPrev(); //just move to prev record in the buffer
+ } else {//we have no pointer
+ //compute a place in the buffer that contain next record's data
+ drv_bufferMovePointerTo(m_at-1);
+ m_at_buffer = true; //now current record is stored in the buffer
+ }
+ m_validRecord=true;
+ m_afterLast=false;
+ return true;
+}
+
+bool Cursor::eof() const
+{
+ return m_afterLast;
+}
+
+bool Cursor::bof() const
+{
+ return m_at==0;
+}
+
+Q_LLONG Cursor::at() const
+{
+ if (m_readAhead)
+ return 0;
+ return m_at - 1;
+}
+
+bool Cursor::isBuffered() const
+{
+ return m_options & Buffered;
+}
+
+void Cursor::setBuffered(bool buffered)
+{
+ if (!m_opened)
+ return;
+ if (isBuffered()==buffered)
+ return;
+ m_options ^= Buffered;
+}
+
+void Cursor::clearBuffer()
+{
+ if ( !isBuffered() || m_fieldCount==0)
+ return;
+
+ drv_clearBuffer();
+
+ m_records_in_buf=0;
+ m_at_buffer=false;
+}
+
+bool Cursor::getNextRecord()
+{
+ m_result = -1; //by default: invalid result of row fetching
+
+ if ((m_options & Buffered)) {//this cursor is buffered:
+// KexiDBDbg << "m_at < m_records_in_buf :: " << (long)m_at << " < " << m_records_in_buf << endl;
+//js if (m_at==-1) m_at=0;
+ if (m_at < m_records_in_buf) {//we have next record already buffered:
+/// if (m_at < (m_records_in_buf-1)) {//we have next record already buffered:
+//js if (m_at_buffer && (m_at!=0)) {//we already have got a pointer to buffer
+ if (m_at_buffer) {//we already have got a pointer to buffer
+ drv_bufferMovePointerNext(); //just move to next record in the buffer
+ } else {//we have no pointer
+ //compute a place in the buffer that contain next record's data
+ drv_bufferMovePointerTo(m_at-1+1);
+// drv_bufferMovePointerTo(m_at+1);
+ m_at_buffer = true; //now current record is stored in the buffer
+ }
+ }
+ else {//we are after last retrieved record: we need to physically fetch next record:
+ if (!m_readAhead) {//we have no record that was read ahead
+ if (!m_buffering_completed) {
+ //retrieve record only if we are not after
+ //the last buffer's item (i.e. when buffer is not fully filled):
+// KexiDBDbg<<"==== buffering: drv_getNextRecord() ===="<<endl;
+ drv_getNextRecord();
+ }
+ if ((FetchResult) m_result != FetchOK) {//there is no record
+ m_buffering_completed = true; //no more records for buffer
+// KexiDBDbg<<"m_result != FetchOK ********"<<endl;
+ m_validRecord = false;
+ m_afterLast = true;
+//js m_at = m_records_in_buf;
+ m_at = -1; //position is invalid now and will not be used
+ if ((FetchResult) m_result == FetchEnd) {
+ return false;
+ }
+ setError(ERR_CURSOR_RECORD_FETCHING, i18n("Cannot fetch next record."));
+ return false;
+ }
+ //we have a record: store this record's values in the buffer
+ drv_appendCurrentRecordToBuffer();
+ m_records_in_buf++;
+ }
+ else //we have a record that was read ahead: eat this
+ m_readAhead = false;
+ }
+ }
+ else {//we are after last retrieved record: we need to physically fetch next record:
+ if (!m_readAhead) {//we have no record that was read ahead
+// KexiDBDbg<<"==== no prefetched record ===="<<endl;
+ drv_getNextRecord();
+ if ((FetchResult)m_result != FetchOK) {//there is no record
+// KexiDBDbg<<"m_result != FetchOK ********"<<endl;
+ m_validRecord = false;
+ m_afterLast = true;
+ m_at = -1;
+ if ((FetchResult) m_result == FetchEnd) {
+ return false;
+ }
+ setError(ERR_CURSOR_RECORD_FETCHING, i18n("Cannot fetch next record."));
+ return false;
+ }
+ }
+ else //we have a record that was read ahead: eat this
+ m_readAhead = false;
+ }
+
+ m_at++;
+
+// if (m_data->curr_colname && m_data->curr_coldata)
+// for (int i=0;i<m_data->curr_cols;i++) {
+// KexiDBDbg<<i<<": "<< m_data->curr_colname[i]<<" == "<< m_data->curr_coldata[i]<<endl;
+// }
+// KexiDBDbg<<"m_at == "<<(long)m_at<<endl;
+
+ m_validRecord = true;
+ return true;
+}
+
+bool Cursor::updateRow(RowData& data, RowEditBuffer& buf, bool useROWID)
+{
+//! @todo doesn't update cursor's buffer YET!
+ clearError();
+ if (!m_query)
+ return false;
+ return m_conn->updateRow(*m_query, data, buf, useROWID);
+}
+
+bool Cursor::insertRow(RowData& data, RowEditBuffer& buf, bool getROWID)
+{
+//! @todo doesn't update cursor's buffer YET!
+ clearError();
+ if (!m_query)
+ return false;
+ return m_conn->insertRow(*m_query, data, buf, getROWID);
+}
+
+bool Cursor::deleteRow(RowData& data, bool useROWID)
+{
+//! @todo doesn't update cursor's buffer YET!
+ clearError();
+ if (!m_query)
+ return false;
+ return m_conn->deleteRow(*m_query, data, useROWID);
+}
+
+bool Cursor::deleteAllRows()
+{
+//! @todo doesn't update cursor's buffer YET!
+ clearError();
+ if (!m_query)
+ return false;
+ return m_conn->deleteAllRows(*m_query);
+}
+
+QString Cursor::debugString() const
+{
+ QString dbg = "CURSOR( ";
+ if (!m_query) {
+ dbg += "RAW STATEMENT: '";
+ dbg += m_rawStatement;
+ dbg += "'\n";
+ }
+ else {
+ dbg += "QuerySchema: '";
+ dbg += m_conn->selectStatement( *m_query );
+ dbg += "'\n";
+ }
+ if (isOpened())
+ dbg += " OPENED";
+ else
+ dbg += " NOT_OPENED";
+ if (isBuffered())
+ dbg += " BUFFERED";
+ else
+ dbg += " NOT_BUFFERED";
+ dbg += " AT=";
+ dbg += QString::number((unsigned long)at());
+ dbg += " )";
+ return dbg;
+}
+
+void Cursor::debug() const
+{
+ KexiDBDbg << debugString() << endl;
+}
+
+void Cursor::setOrderByColumnList(const QStringList& columnNames)
+{
+ Q_UNUSED(columnNames);
+//! @todo implement this:
+// all field names should be fooun, exit otherwise ..........
+
+ // OK
+//TODO if (!m_orderByColumnList)
+//TODO
+}
+
+/*! Convenience method, similar to setOrderBy(const QStringList&). */
+void Cursor::setOrderByColumnList(const QString& column1, const QString& column2,
+ const QString& column3, const QString& column4, const QString& column5)
+{
+ Q_UNUSED(column1);
+ Q_UNUSED(column2);
+ Q_UNUSED(column3);
+ Q_UNUSED(column4);
+ Q_UNUSED(column5);
+//! @todo implement this, like above
+//! @todo add ORDER BY info to debugString()
+}
+
+QueryColumnInfo::Vector Cursor::orderByColumnList() const
+{
+ return m_orderByColumnList ? *m_orderByColumnList: QueryColumnInfo::Vector();
+}
+
+QValueList<QVariant> Cursor::queryParameters() const
+{
+ return m_queryParameters ? *m_queryParameters : QValueList<QVariant>();
+}
+
+void Cursor::setQueryParameters(const QValueList<QVariant>& params)
+{
+ if (!m_queryParameters)
+ m_queryParameters = new QValueList<QVariant>(params);
+ else
+ *m_queryParameters = params;
+}
+
+#include "cursor.moc"
diff --git a/kexi/kexidb/cursor.h b/kexi/kexidb/cursor.h
new file mode 100644
index 000000000..6ea64dd9f
--- /dev/null
+++ b/kexi/kexidb/cursor.h
@@ -0,0 +1,365 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_CURSOR_H
+#define KEXIDB_CURSOR_H
+
+#include <qstring.h>
+#include <qvariant.h>
+#include <qptrvector.h>
+#include <qvaluevector.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/object.h>
+
+namespace KexiDB {
+
+class RowEditBuffer;
+
+//! Provides database cursor functionality.
+/*!
+ Cursor can be defined in two ways:
+
+ -# by passing QuerySchema object to Connection::executeQuery() or Connection::prepareQuery();
+ then query is defined for in engine-independent way -- this is recommended usage
+
+ -# by passing raw query statement string to Connection::executeQuery() or Connection::prepareQuery();
+ then query may be defined for in engine-dependent way -- this is not recommended usage,
+ but convenient when we can't or do not want to allocate QuerySchema object, while we
+ know that the query statement is syntactically and logically ok in our context.
+
+ You can move cursor to next record with moveNext() and move back with movePrev().
+ The cursor is always positioned on record, not between records, with exception that
+ ofter open() it is positioned before first record (if any) -- then bof() equals true,
+ and can be positioned after the last record (if any) with moveNext() -- then eof() equals true,
+ For example, if you have four records 1, 2, 3, 4, then after calling moveNext(),
+ moveNext(), moveNext(), movePrev() you are going through records: 1, 2, 3, 2.
+
+ Cursor can be buffered or unbuferred.
+ Buffering in this class is not related to any SQL engine capatibilities for server-side cursors
+ (eg. like 'DECLARE CURSOR' statement) - buffered data is at client (application) side.
+ Any record retrieved in buffered cursor will be stored inside an internal buffer
+ and reused when needed. Unbuffered cursor always requires one record fetching from
+ db connection at every step done with moveNext(), movePrev(), etc.
+
+ Notes:
+ - Do not use delete operator for Cursor objects - this will fail; use Connection::deleteCursor()
+ instead.
+ - QuerySchema object is not owned by Cursor object that uses it.
+*/
+class KEXI_DB_EXPORT Cursor: public QObject, public Object
+{
+ Q_OBJECT
+
+ public:
+ //! Cursor options that describes its behaviour
+ enum Options {
+ NoOptions = 0,
+ Buffered = 1
+ };
+
+ virtual ~Cursor();
+
+ /*! \return connection used for the cursor */
+ inline Connection* connection() const { return m_conn; }
+
+ /*! Opens the cursor using data provided on creation.
+ The data might be either QuerySchema or raw sql statement. */
+ bool open();
+
+ /*! Closes and then opens again the same cursor.
+ If the cursor is not opened it is just opened and result of this open is returned.
+ Otherwise, true is returned if cursor is successfully closed and then opened. */
+ bool reopen();
+
+// /*! Opens the cursor using \a statement.
+// Omit \a statement if cursor is already initialized with statement
+// at creation time. If \a statement is not empty, existing statement
+// (if any) is overwritten. */
+// bool open( const QString& statement = QString::null );
+
+ /*! Closes previously opened cursor.
+ If the cursor is closed, nothing happens. */
+ virtual bool close();
+
+ /*! \return query schema used to define this cursor
+ or NULL if the cursor is not defined by a query schema but by a raw statement. */
+ inline QuerySchema *query() const { return m_query; }
+
+ //! \return query parameters assigned to this cursor
+ QValueList<QVariant> queryParameters() const;
+
+ //! Sets query parameters \a params for this cursor.
+ void setQueryParameters(const QValueList<QVariant>& params);
+
+ /*! \return raw query statement used to define this cursor
+ or null string if raw statement instead (but QuerySchema is defined instead). */
+ inline QString rawStatement() const { return m_rawStatement; }
+
+ /*! \return logically or'd cursor's options,
+ selected from Cursor::Options enum. */
+ inline uint options() const { return m_options; }
+
+ /*! \return true if the cursor is opened. */
+ inline bool isOpened() const { return m_opened; }
+
+ /*! \return true if the cursor is buffered. */
+ bool isBuffered() const;
+
+ /*! Sets this cursor to buffered type or not. See description
+ of buffered and nonbuffered cursors in class description.
+ This method only works if cursor is not opened (isOpened()==false).
+ You can close already opened cursor and then switch this option on/off.
+ */
+ void setBuffered(bool buffered);
+
+ /*! Moves current position to the first record and retrieves it.
+ \return true if the first record was retrieved.
+ False could mean that there was an error or there is no record available. */
+ bool moveFirst();
+
+ /*! Moves current position to the last record and retrieves it.
+ \return true if the last record was retrieved.
+ False could mean that there was an error or there is no record available. */
+ virtual bool moveLast();
+
+ /*! Moves current position to the next record and retrieves it. */
+ virtual bool moveNext();
+
+ /*! Moves current position to the next record and retrieves it.
+ Currently it's only supported for buffered cursors. */
+ virtual bool movePrev();
+
+ /*! \return true if current position is after last record. */
+ bool eof() const;
+
+ /*! \return true if current position is before first record. */
+ bool bof() const;
+
+ /*! \return current internal position of the cursor's query.
+ We are counting records from 0.
+ Value -1 means that cursor does not point to any valid record
+ (this happens eg. after open(), close(),
+ and after moving after last record or before first one. */
+ Q_LLONG at() const;
+
+ /*! \return number of fields available for this cursor.
+ This never includes ROWID column or other internal coluns (e.g. lookup). */
+ inline uint fieldCount() const { return m_query ? m_logicalFieldCount : m_fieldCount; }
+
+ /*! \return true if ROWID information is appended with every row.
+ ROWID information is available
+ if DriverBehaviour::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false
+ for a KexiDB database driver and the master table has no primary key defined.
+ Phisically, ROWID value is returned after last returned field,
+ so data vector's length is expanded by one. */
+ inline bool containsROWIDInfo() const { return m_containsROWIDInfo; }
+
+ /*! \return a value stored in column number \a i (counting from 0).
+ Is has unspecified behaviour if the cursor is not at valid record.
+ Note for driver developers:
+ If \a i is >= than m_fieldCount, null QVariant value should be returned.
+ To return a value typically you can use a pointer to internal structure
+ that contain current row data (buffered or unbuffered). */
+ virtual QVariant value(uint i) = 0;
+
+ /*! [PROTOTYPE] \return current record data or NULL if there is no current records. */
+ virtual const char ** rowData() const = 0;
+
+ /*! Sets a list of columns for ORDER BY section of the query.
+ Only works when the cursor has been created using QuerySchema object
+ (i.e. when query()!=0; does not work with raw statements).
+ Each name on the list must be a field or alias present within the query
+ and must not be covered by aliases. If one or more names cannot be found within
+ the query, the method will have no effect. Any previous ORDER BY settings will be removed.
+
+ The order list provided here has priority over a list defined in the QuerySchema
+ object itseld (using QuerySchema::setOrderByColumnList()).
+ The QuerySchema object itself is not modifed by this method: only order of records retrieved
+ by this cursor is affected.
+
+ Use this method before calling open(). You can also call reopen() after calling this method
+ to see effects of applying records order. */
+ void setOrderByColumnList(const QStringList& columnNames);
+
+ /*! Convenience method, similar to setOrderByColumnList(const QStringList&). */
+ void setOrderByColumnList(const QString& column1, const QString& column2 = QString::null,
+ const QString& column3 = QString::null, const QString& column4 = QString::null,
+ const QString& column5 = QString::null);
+
+ /*! \return a list of fields contained in ORDER BY section of the query.
+ @see setOrderBy(const QStringList&) */
+ QueryColumnInfo::Vector orderByColumnList() const;
+
+ /*! Puts current record's data into \a data (makes a deep copy).
+ This have unspecified behaviour if the cursor is not at valid record.
+ Note: For reimplementation in driver's code. Shortly, this method translates
+ a row data from internal representation (probably also used in buffer)
+ to simple public RecordData representation. */
+ virtual void storeCurrentRow(RowData &data) const = 0;
+
+ bool updateRow(RowData& data, RowEditBuffer& buf, bool useROWID = false);
+
+ bool insertRow(RowData& data, RowEditBuffer& buf, bool getROWID = false);
+
+ bool deleteRow(RowData& data, bool useROWID = false);
+
+ bool deleteAllRows();
+
+ /*! \return a code of last executed operation's result at the server side.
+ This code is engine dependent and may be even engine-version dependent.
+ It can be visible in applications mainly after clicking a "Details>>" button
+ or something like that -- this just can be useful for advanced users and
+ for testing.
+ Note for driver developers: Return here the value you usually store as result
+ of most lower-level operations. By default this method returns 0. */
+ virtual int serverResult() { return 0; }
+
+ /*! \return (not i18n'd) name of last executed operation's result at the server side.
+ Sometimes engines have predefined its result names that can be used e.g.
+ to refer a documentation. SQLite is one of such engines.
+ Note for driver developers: Leave the default implementation (null
+ string is returned ) if your engine has no such capability. */
+ virtual QString serverResultName() { return QString::null; }
+
+ /*! \return (not i18n'd) description text (message) of last operation's error/result.
+ In most cases engines do return such a messages, any user can then use this
+ to refer a documentation.
+ Note for driver developers: Leave the default implementation (null
+ string is returned ) if your engine has no such capability. */
+ virtual QString serverErrorMsg() { return QString::null; }
+
+ /*! \return Debug information. */
+ QString debugString() const;
+
+ //! Outputs debug information.
+ void debug() const;
+
+ protected:
+ //! possible results of row fetching, used for m_result
+ typedef enum FetchResult { FetchError=0, FetchOK=1, FetchEnd=2 };
+
+ /*! Cursor will operate on \a conn, raw \a statement will be used to execute query. */
+ Cursor(Connection* conn, const QString& statement, uint options = NoOptions );
+
+ /*! Cursor will operate on \a conn, \a query schema will be used to execute query. */
+ Cursor(Connection* conn, QuerySchema& query, uint options = NoOptions );
+
+ void init();
+
+ /*! Internal: cares about proper flag setting depending on result of drv_getNextRecord()
+ and depending on wherher a cursor is buffered. */
+ bool getNextRecord();
+
+ /* Note for driver developers: this method should initialize engine-specific cursor's
+ resources using m_sql statement. It is not required to store \a statement somewhere
+ in your Cursor subclass (it is already stored in m_query or m_rawStatement,
+ depending query type) - only pass it to proper engine's function. */
+ virtual bool drv_open() = 0;
+
+ virtual bool drv_close() = 0;
+// virtual bool drv_moveFirst() = 0;
+ virtual void drv_getNextRecord() = 0;
+//unused virtual bool drv_getPrevRecord() = 0;
+
+ /*! Stores currently fetched record's values in appropriate place of the buffer.
+ Note for driver developers:
+ This place can be computed using m_at. Do not change value of m_at or any other
+ Cursor members, only change your internal structures like pointer to current
+ row, etc. If your database engine's API function (for record fetching)
+ do not allocates such a space, you want to allocate a space for current
+ record. Otherwise, reuse existing structure, what could be more efficient.
+ All functions like drv_appendCurrentRecordToBuffer() operates on the buffer,
+ i.e. array of stored rows. You are not forced to have any particular
+ fixed structure for buffer item or buffer itself - the structure is internal and
+ only methods like storeCurrentRecord() visible to public.
+ */
+ virtual void drv_appendCurrentRecordToBuffer() = 0;
+ /*! Moves pointer (that points to the buffer) -- to next item in this buffer.
+ Note for driver developers: probably just execute "your_pointer++" is enough.
+ */
+ virtual void drv_bufferMovePointerNext() = 0;
+ /*! Like drv_bufferMovePointerNext() but execute "your_pointer--". */
+ virtual void drv_bufferMovePointerPrev() = 0;
+ /*! Moves pointer (that points to the buffer) to a new place: \a at.
+ */
+ virtual void drv_bufferMovePointerTo(Q_LLONG at) = 0;
+
+ /*DISABLED: ! This is called only once in open(), after successful drv_open().
+ Reimplement this if you need (or not) to do get the first record after drv_open(),
+ eg. to know if there are any records in table. Value returned by this method
+ will be assigned to m_readAhead.
+ Default implementation just calls drv_getNextRecord(). */
+
+ /*! Clears cursor's buffer if this was allocated (only for buffered cursor type).
+ Otherwise do nothing. For reimplementing. Default implementation does nothing. */
+ virtual void drv_clearBuffer() {}
+
+ //! @internal clears buffer with reimplemented drv_clearBuffer(). */
+ void clearBuffer();
+
+ /*! Clears an internal member that is used to storing last result code,
+ the same that is returend by serverResult(). */
+ virtual void drv_clearServerResult() = 0;
+
+ QGuardedPtr<Connection> m_conn;
+ QuerySchema *m_query;
+// CursorData *m_data;
+ QString m_rawStatement;
+ bool m_opened : 1;
+//js (m_at==0 is enough) bool m_beforeFirst : 1;
+ bool m_atLast : 1;
+ bool m_afterLast : 1;
+// bool m_atLast;
+ bool m_validRecord : 1; //!< true if valid record is currently retrieved @ current position
+ bool m_containsROWIDInfo : 1;
+ Q_LLONG m_at;
+ uint m_fieldCount; //!< cached field count information
+ uint m_logicalFieldCount; //!< logical field count, i.e. without intrernal values like ROWID or lookup
+ uint m_options; //!< cursor options that describes its behaviour
+ char m_result; //!< result of a row fetching
+
+ //<members related to buffering>
+ int m_records_in_buf; //!< number of records currently stored in the buffer
+ bool m_buffering_completed : 1; //!< true if we already have all records stored in the buffer
+ //</members related to buffering>
+
+ //! Useful e.g. for value(int) method when we need access to schema def.
+ QueryColumnInfo::Vector* m_fieldsExpanded;
+
+ //! Used by setOrderByColumnList()
+ QueryColumnInfo::Vector* m_orderByColumnList;
+
+ QValueList<QVariant>* m_queryParameters;
+
+ private:
+ bool m_readAhead : 1;
+
+ //<members related to buffering>
+ bool m_at_buffer : 1; //!< true if we already point to the buffer with curr_coldata
+ //</members related to buffering>
+
+
+ class Private;
+ Private *d;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/cursor_p.h b/kexi/kexidb/cursor_p.h
new file mode 100644
index 000000000..c03eba66a
--- /dev/null
+++ b/kexi/kexidb/cursor_p.h
@@ -0,0 +1,40 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_CURSOR_P_H
+#define KEXIDB_CURSOR_P_H
+
+#include <qstring.h>
+
+#include "connection.h"
+
+namespace KexiDB {
+
+#if 0
+/*PRIVATE*/ class /*KEXI_DB_EXPORT*/ CursorData
+{
+ public:
+ CursorData() {};
+ ~CursorData() {};
+};
+#endif
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/dbobjectnamevalidator.cpp b/kexi/kexidb/dbobjectnamevalidator.cpp
new file mode 100644
index 000000000..77ed0e557
--- /dev/null
+++ b/kexi/kexidb/dbobjectnamevalidator.cpp
@@ -0,0 +1,51 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "dbobjectnamevalidator.h"
+
+#include "driver.h"
+
+using namespace KexiDB;
+using namespace KexiUtils;
+
+ObjectNameValidator::ObjectNameValidator(
+ KexiDB::Driver *drv, QObject * parent, const char * name)
+: Validator(parent,name)
+{
+ m_drv = drv;
+}
+
+ObjectNameValidator::~ObjectNameValidator()
+{
+}
+
+Validator::Result ObjectNameValidator::internalCheck(
+ const QString & /*valueName*/, const QVariant& v,
+ QString &message, QString &details)
+{
+
+ if (m_drv.isNull() ? !KexiDB::Driver::isKexiDBSystemObjectName(v.toString())
+ : !m_drv->isSystemObjectName(v.toString()))
+ return Validator::Ok;
+ message = i18n("You cannot use name \"%1\" for your object.\n"
+ "It is reserved for internal Kexi objects. Please choose another name.")
+ .arg(v.toString());
+ details = i18n("Names of internal Kexi objects are starting with \"kexi__\".");
+ return Validator::Error;
+}
diff --git a/kexi/kexidb/dbobjectnamevalidator.h b/kexi/kexidb/dbobjectnamevalidator.h
new file mode 100644
index 000000000..9b8ac617f
--- /dev/null
+++ b/kexi/kexidb/dbobjectnamevalidator.h
@@ -0,0 +1,49 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDBOBJECTNAMEVALIDATOR_H
+#define KEXIDBOBJECTNAMEVALIDATOR_H
+
+#include <kexiutils/validator.h>
+#include <qstring.h>
+#include <qguardedptr.h>
+
+namespace KexiDB {
+
+ class Driver;
+
+ /*! Validates input:
+ accepts if the name is not reserved for internal kexi objects. */
+ class KEXI_DB_EXPORT ObjectNameValidator : public KexiUtils::Validator
+ {
+ public:
+ /*! \a drv is a KexiDB driver on which isSystemObjectName() will be
+ called inside check(). If \a drv is 0, KexiDB::Driver::isKexiDBSystemObjectName()
+ static function is called instead. */
+ ObjectNameValidator(KexiDB::Driver *drv, QObject * parent = 0, const char * name = 0);
+ virtual ~ObjectNameValidator();
+
+ protected:
+ virtual KexiUtils::Validator::Result internalCheck(const QString &valueName, const QVariant& v,
+ QString &message, QString &details);
+ QGuardedPtr<KexiDB::Driver> m_drv;
+ };
+}
+
+#endif
diff --git a/kexi/kexidb/dbproperties.cpp b/kexi/kexidb/dbproperties.cpp
new file mode 100644
index 000000000..c5780542d
--- /dev/null
+++ b/kexi/kexidb/dbproperties.cpp
@@ -0,0 +1,148 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "dbproperties.h"
+#include <klocale.h>
+
+using namespace KexiDB;
+
+DatabaseProperties::DatabaseProperties(Connection *conn)
+ : KexiDB::Object()
+ , m_conn(conn)
+{
+}
+
+DatabaseProperties::~DatabaseProperties()
+{
+}
+
+bool DatabaseProperties::setValue( const QString& _name, const QVariant& value )
+{
+ QString name(_name.stripWhiteSpace());
+ bool ok;
+ //we need to know whether update or insert
+ bool exists = m_conn->resultExists(
+ QString::fromLatin1("SELECT 1 FROM kexi__db WHERE db_property=%1")
+ .arg(m_conn->driver()->escapeString(name)), ok);
+ if (!ok) {
+ setError(m_conn, i18n("Could not set value of database property \"%1\".").arg(name));
+ return false;
+ }
+
+ if (exists) {
+ if (!m_conn->executeSQL(
+ QString::fromLatin1("UPDATE kexi__db SET db_value=%1 WHERE db_property=%2")
+ .arg(m_conn->driver()->escapeString(value.toString()))
+ .arg(m_conn->driver()->escapeString(name))))
+ {
+ setError(m_conn, i18n("Could not set value of database property \"%1\".").arg(name));
+ return false;
+ }
+ return true;
+ }
+
+ if (!m_conn->executeSQL(
+ QString::fromLatin1("INSERT INTO kexi__db (db_property, db_value) VALUES (%1, %2)")
+ .arg(m_conn->driver()->escapeString(name))
+ .arg(m_conn->driver()->escapeString(value.toString()))))
+ {
+ setError(m_conn, i18n("Could not set value of database property \"%1\".").arg(name));
+ return false;
+ }
+ return true;
+}
+
+bool DatabaseProperties::setCaption( const QString& _name, const QString& caption )
+{
+ QString name(_name.stripWhiteSpace());
+ //captions have ' ' prefix
+ name.prepend(" ");
+ bool ok;
+ //we need to know whether update or insert
+ bool exists = m_conn->resultExists(
+ QString::fromLatin1("SELECT 1 FROM kexi__db WHERE db_property=%1")
+ .arg(m_conn->driver()->escapeString(name)), ok);
+ if (!ok) {
+ setError(m_conn, i18n("Could not set caption for database property \"%1\".").arg(name));
+ return false;
+ }
+
+ if (exists) {
+ if (!m_conn->executeSQL(
+ QString::fromLatin1("UPDATE kexi__db SET db_value=%1 WHERE db_property=%2")
+ .arg(m_conn->driver()->escapeString(caption))
+ .arg(m_conn->driver()->escapeString(name))))
+ {
+ setError(m_conn, i18n("Could not set caption for database property \"%1\".").arg(name));
+ return false;
+ }
+ return true;
+ }
+
+ if (!m_conn->executeSQL(
+ QString::fromLatin1("INSERT INTO kexi__db (db_property, db_value) VALUES (%1, %2)")
+ .arg(m_conn->driver()->escapeString(name))
+ .arg(m_conn->driver()->escapeString(caption))))
+ {
+ setError(m_conn, i18n("Could not set caption for database property \"%1\".").arg(name));
+ return false;
+ }
+ return true;
+}
+
+QVariant DatabaseProperties::value( const QString& _name )
+{
+ QString result;
+ QString name(_name.stripWhiteSpace());
+ if (true!=m_conn->querySingleString(
+ QString::fromLatin1("SELECT db_value FROM kexi__db WHERE db_property=")
+ + m_conn->driver()->escapeString(name), result)) {
+ m_conn->setError(ERR_NO_DB_PROPERTY, i18n("Could not read database property \"%1\".").arg(name));
+ return QVariant();
+ }
+ return result;
+}
+
+QString DatabaseProperties::caption( const QString& _name )
+{
+ QString result;
+ QString name(_name.stripWhiteSpace());
+ //captions have ' ' prefix
+ name.prepend(" ");
+ if (true!=m_conn->querySingleString(
+ QString::fromLatin1("SELECT db_value FROM kexi__db WHERE db_property=")
+ + m_conn->driver()->escapeString(name), result)) {
+ setError(m_conn, i18n("Could not read database property \"%1\".").arg(name));
+ return QString::null;
+ }
+ return result;
+}
+
+QStringList DatabaseProperties::names()
+{
+ QStringList result;
+ if (true!=m_conn->queryStringList(
+ QString::fromLatin1("SELECT db_value FROM kexi__db WHERE db_property NOT LIKE ")
+ + m_conn->driver()->escapeString(QString::fromLatin1(" %%")), result, 0 /*0-th*/)) {
+ // ^^ exclude captions
+ setError(m_conn, i18n("Could not read database properties."));
+ return QStringList();
+ }
+ return result;
+}
diff --git a/kexi/kexidb/dbproperties.h b/kexi/kexidb/dbproperties.h
new file mode 100644
index 000000000..91aed7e27
--- /dev/null
+++ b/kexi/kexidb/dbproperties.h
@@ -0,0 +1,67 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_DBPROPERTIES_H
+#define KEXIDB_DBPROPERTIES_H
+
+#include "connection.h"
+
+namespace KexiDB {
+
+//! @todo implement KConfigBase interface here?
+
+//! A set of storable database properties.
+/*! This is a convenience class that allows to store global dabatase properties without a need
+ for creating and maintain custom table.
+ DatabaseProperties object is accessible only using KexiDB::Connection::databaseProperties() method.
+ */
+class KEXI_DB_EXPORT DatabaseProperties : public KexiDB::Object
+{
+ public:
+ /*! Sets \a value for property \a name. Optional caption can be also set.
+ If there's no such property defined, it will be added. Existing value will be overwritten.
+ Note that to execute this method, database must be opened in read-write mode.
+ \return true on successful data. Connection */
+ bool setValue( const QString& name, const QVariant& value );
+
+ /*! Sets \a caption for for property \a name.
+ Usually it shouldn't be translated: trnaslation can be performed before displaying. */
+ bool setCaption( const QString& name, const QString& caption );
+
+ //! \return property value for \a propeName available for this driver.
+ //! If there's no such property defined for driver, Null QVariant value is returned.
+ QVariant value( const QString& name );
+
+ //! \return translated property caption for \a name.
+ //! If there's no such property defined for driver, empty string value is returned.
+ QString caption( const QString& name );
+
+ //! \return a list of available property names.
+ QStringList names();
+
+ protected:
+ DatabaseProperties(Connection *conn);
+ ~DatabaseProperties();
+
+ QGuardedPtr<Connection> m_conn;
+ friend class Connection;
+};
+}
+
+#endif
diff --git a/kexi/kexidb/driver.cpp b/kexi/kexidb/driver.cpp
new file mode 100644
index 000000000..6e82c0809
--- /dev/null
+++ b/kexi/kexidb/driver.cpp
@@ -0,0 +1,367 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/driver.h>
+#include <kexidb/driver_p.h>
+#include <kexidb/drivermanager.h>
+#include <kexidb/drivermanager_p.h>
+#include "error.h"
+#include "drivermanager.h"
+#include "connection.h"
+#include "connectiondata.h"
+#include "admin.h"
+
+#include <qfileinfo.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <assert.h>
+
+using namespace KexiDB;
+
+/*! used when we do not have Driver instance yet,
+ or when we cannot get one */
+QValueVector<QString> dflt_typeNames;
+
+
+//---------------------------------------------
+
+
+DriverBehaviour::DriverBehaviour()
+ : UNSIGNED_TYPE_KEYWORD("UNSIGNED")
+ , AUTO_INCREMENT_FIELD_OPTION("AUTO_INCREMENT")
+ , AUTO_INCREMENT_PK_FIELD_OPTION("AUTO_INCREMENT PRIMARY KEY")
+ , SPECIAL_AUTO_INCREMENT_DEF(false)
+ , AUTO_INCREMENT_REQUIRES_PK(false)
+ , ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE(false)
+ , QUOTATION_MARKS_FOR_IDENTIFIER('"')
+ , USING_DATABASE_REQUIRED_TO_CONNECT(true)
+ , _1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY(false)
+ , SELECT_1_SUBQUERY_SUPPORTED(false)
+ , SQL_KEYWORDS(0)
+{
+}
+
+//---------------------------------------------
+
+Driver::Info::Info()
+ : fileBased(false)
+ , allowImportingTo(true)
+{
+}
+
+//---------------------------------------------
+
+Driver::Driver( QObject *parent, const char *name, const QStringList & )
+ : QObject( parent, name )
+ , Object()
+ , beh( new DriverBehaviour() )
+ , d( new DriverPrivate() )
+{
+ d->connections.setAutoDelete(false);
+ //TODO: reasonable size
+ d->connections.resize(101);
+ d->typeNames.resize(Field::LastType + 1);
+
+ d->initKexiKeywords();
+}
+
+
+Driver::~Driver()
+{
+ DriverManagerInternal::self()->aboutDelete( this );
+// KexiDBDbg << "Driver::~Driver()" << endl;
+ QPtrDictIterator<Connection> it( d->connections );
+ Connection *conn;
+ while ( (conn = it.toFirst()) ) {
+ delete conn;
+ }
+ delete beh;
+ delete d;
+// KexiDBDbg << "Driver::~Driver() ok" << endl;
+}
+
+bool Driver::isValid()
+{
+ clearError();
+ if (KexiDB::version().major != version().major
+ || KexiDB::version().minor != version().minor)
+ {
+ setError(ERR_INCOMPAT_DRIVER_VERSION,
+ i18n("Incompatible database driver's \"%1\" version: found version %2, expected version %3.")
+ .arg(name())
+ .arg(QString("%1.%2").arg(version().major).arg(version().minor))
+ .arg(QString("%1.%2").arg(KexiDB::version().major).arg(KexiDB::version().minor)));
+ return false;
+ }
+
+ QString inv_impl = i18n("Invalid database driver's \"%1\" implementation:\n").arg(name());
+ QString not_init = i18n("Value of \"%1\" is not initialized for the driver.");
+ if (beh->ROW_ID_FIELD_NAME.isEmpty()) {
+ setError(ERR_INVALID_DRIVER_IMPL, inv_impl + not_init.arg("DriverBehaviour::ROW_ID_FIELD_NAME"));
+ return false;
+ }
+
+ return true;
+}
+
+const QPtrList<Connection> Driver::connectionsList() const
+{
+ QPtrList<Connection> clist;
+ QPtrDictIterator<Connection> it( d->connections );
+ for( ; it.current(); ++it )
+ clist.append( &(*it) );
+ return clist;
+}
+
+QString Driver::fileDBDriverMimeType() const
+{ return d->fileDBDriverMimeType; }
+
+QString Driver::defaultFileBasedDriverMimeType()
+{ return QString::fromLatin1("application/x-kexiproject-sqlite3"); }
+
+QString Driver::defaultFileBasedDriverName()
+{
+ DriverManager dm;
+ return dm.lookupByMime(Driver::defaultFileBasedDriverMimeType()).lower();
+}
+
+const KService* Driver::service() const
+{ return d->service; }
+
+bool Driver::isFileDriver() const
+{ return d->isFileDriver; }
+
+int Driver::features() const
+{ return d->features; }
+
+bool Driver::transactionsSupported() const
+{ return d->features & (SingleTransactions | MultipleTransactions); }
+
+AdminTools& Driver::adminTools() const
+{
+ if (!d->adminTools)
+ d->adminTools = drv_createAdminTools();
+ return *d->adminTools;
+}
+
+AdminTools* Driver::drv_createAdminTools() const
+{
+ return new AdminTools(); //empty impl.
+}
+
+QString Driver::sqlTypeName(int id_t, int /*p*/) const
+{
+ if (id_t > Field::InvalidType && id_t <= Field::LastType)
+ return d->typeNames[(id_t>0 && id_t<=Field::LastType) ? id_t : Field::InvalidType /*sanity*/];
+
+ return d->typeNames[Field::InvalidType];
+}
+
+Connection *Driver::createConnection( ConnectionData &conn_data, int options )
+{
+ clearError();
+ if (!isValid())
+ return 0;
+
+ if (d->isFileDriver) {
+ if (conn_data.fileName().isEmpty()) {
+ setError(ERR_MISSING_DB_LOCATION, i18n("File name expected for file-based database driver.") );
+ return 0;
+ }
+ }
+// Connection *conn = new Connection( this, conn_data );
+ Connection *conn = drv_createConnection( conn_data );
+
+ conn->setReadOnly(options & ReadOnlyConnection);
+
+ conn_data.driverName = name();
+ d->connections.insert( conn, conn );
+ return conn;
+}
+
+Connection* Driver::removeConnection( Connection *conn )
+{
+ clearError();
+ return d->connections.take( conn );
+}
+
+QString Driver::defaultSQLTypeName(int id_t)
+{
+ if (id_t>=Field::Null)
+ return "Null";
+ if (dflt_typeNames.isEmpty()) {
+ dflt_typeNames.resize(Field::LastType + 1);
+ dflt_typeNames[Field::InvalidType]="InvalidType";
+ dflt_typeNames[Field::Byte]="Byte";
+ dflt_typeNames[Field::ShortInteger]="ShortInteger";
+ dflt_typeNames[Field::Integer]="Integer";
+ dflt_typeNames[Field::BigInteger]="BigInteger";
+ dflt_typeNames[Field::Boolean]="Boolean";
+ dflt_typeNames[Field::Date]="Date";
+ dflt_typeNames[Field::DateTime]="DateTime";
+ dflt_typeNames[Field::Time]="Time";
+ dflt_typeNames[Field::Float]="Float";
+ dflt_typeNames[Field::Double]="Double";
+ dflt_typeNames[Field::Text]="Text";
+ dflt_typeNames[Field::LongText]="LongText";
+ dflt_typeNames[Field::BLOB]="BLOB";
+ }
+ return dflt_typeNames[id_t];
+}
+
+bool Driver::isSystemObjectName( const QString& n ) const
+{
+ return Driver::isKexiDBSystemObjectName(n);
+}
+
+bool Driver::isKexiDBSystemObjectName( const QString& n )
+{
+ if (!n.lower().startsWith("kexi__"))
+ return false;
+ const QStringList list( Connection::kexiDBSystemTableNames() );
+ return list.find(n.lower())!=list.constEnd();
+}
+
+bool Driver::isSystemFieldName( const QString& n ) const
+{
+ if (!beh->ROW_ID_FIELD_NAME.isEmpty() && n.lower()==beh->ROW_ID_FIELD_NAME.lower())
+ return true;
+ return drv_isSystemFieldName(n);
+}
+
+QString Driver::valueToSQL( uint ftype, const QVariant& v ) const
+{
+ if (v.isNull())
+ return "NULL";
+ switch (ftype) {
+ case Field::Text:
+ case Field::LongText: {
+ QString s = v.toString();
+ return escapeString(s); //QString("'")+s.replace( '"', "\\\"" ) + "'";
+ }
+ case Field::Byte:
+ case Field::ShortInteger:
+ case Field::Integer:
+ case Field::BigInteger:
+ return v.toString();
+ case Field::Float:
+ case Field::Double: {
+ if (v.type()==QVariant::String) {
+ //workaround for values stored as string that should be casted to floating-point
+ QString s(v.toString());
+ return s.replace(',', ".");
+ }
+ return v.toString();
+ }
+//TODO: here special encoding method needed
+ case Field::Boolean:
+ return QString::number(v.toInt()?1:0); //0 or 1
+ case Field::Time:
+ return QString("\'")+v.toTime().toString(Qt::ISODate)+"\'";
+ case Field::Date:
+ return QString("\'")+v.toDate().toString(Qt::ISODate)+"\'";
+ case Field::DateTime:
+ return dateTimeToSQL( v.toDateTime() );
+ case Field::BLOB: {
+ if (v.toByteArray().isEmpty())
+ return QString::fromLatin1("NULL");
+ if (v.type()==QVariant::String)
+ return escapeBLOB(v.toString().utf8());
+ return escapeBLOB(v.toByteArray());
+ }
+ case Field::InvalidType:
+ return "!INVALIDTYPE!";
+ default:
+ KexiDBDbg << "Driver::valueToSQL(): UNKNOWN!" << endl;
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QVariant Driver::propertyValue( const QCString& propName ) const
+{
+ return d->properties[propName.lower()];
+}
+
+QString Driver::propertyCaption( const QCString& propName ) const
+{
+ return d->propertyCaptions[propName.lower()];
+}
+
+QValueList<QCString> Driver::propertyNames() const
+{
+ QValueList<QCString> names = d->properties.keys();
+ qHeapSort(names);
+ return names;
+}
+
+QString Driver::escapeIdentifier(const QString& str, int options) const
+{
+ QCString cstr = str.latin1();
+ return QString(escapeIdentifier(cstr, options));
+}
+
+QCString Driver::escapeIdentifier(const QCString& str, int options) const
+{
+ bool needOuterQuotes = false;
+
+// Need to use quotes if ...
+// ... we have been told to, or ...
+ if(options & EscapeAlways)
+ needOuterQuotes = true;
+
+// ... or if the driver does not have a list of keywords,
+ else if(!d->driverSQLDict)
+ needOuterQuotes = true;
+
+// ... or if it's a keyword in Kexi's SQL dialect,
+ else if(d->kexiSQLDict->find(str))
+ needOuterQuotes = true;
+
+// ... or if it's a keyword in the backends SQL dialect,
+// (have already checked !d->driverSQLDict)
+ else if((options & EscapeDriver) && d->driverSQLDict->find(str))
+ needOuterQuotes = true;
+
+// ... or if the identifier has a space in it...
+ else if(str.find(' ') != -1)
+ needOuterQuotes = true;
+
+ if(needOuterQuotes && (options & EscapeKexi)) {
+ const char quote = '"';
+ return quote + QCString(str).replace( quote, "\"\"" ) + quote;
+ }
+ else if (needOuterQuotes) {
+ const char quote = beh->QUOTATION_MARKS_FOR_IDENTIFIER.latin1();
+ return quote + drv_escapeIdentifier(str) + quote;
+ } else {
+ return drv_escapeIdentifier(str);
+ }
+}
+
+void Driver::initSQLKeywords(int hashSize) {
+
+ if(!d->driverSQLDict && beh->SQL_KEYWORDS != 0) {
+ d->initDriverKeywords(beh->SQL_KEYWORDS, hashSize);
+ }
+}
+
+#include "driver.moc"
diff --git a/kexi/kexidb/driver.h b/kexi/kexidb/driver.h
new file mode 100644
index 000000000..ef946e65b
--- /dev/null
+++ b/kexi/kexidb/driver.h
@@ -0,0 +1,375 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_DRIVER_H
+#define KEXIDB_DRIVER_H
+
+#include <qobject.h>
+#include <qdatetime.h>
+#include <qdict.h>
+
+#include <kexidb/global.h>
+#include <kexidb/object.h>
+#include <kexidb/field.h>
+
+class KService;
+
+namespace KexiDB {
+
+class AdminTools;
+class Connection;
+class ConnectionData;
+class ConnectionInternal;
+class DriverManager;
+class DriverBehaviour;
+class DriverPrivate;
+
+//! Generic database abstraction.
+/*! This class is a prototype of the database driver for implementations.
+ Driver allows new connections to be created, and groups
+ these as a parent.
+ Before destruction, all connections are destructed.
+
+ Notes:
+ - driver must be provided within KDE module file named with "kexidb_" prefix
+ - following line should be placed in driver's implementation:
+ \code
+ KEXIDB_DRIVER_INFO( CLASS_NAME, INTERNAL_NAME );
+ \endcode
+ where:
+ - CLASS_NAME is actual driver's class name, e.g. MySqlDriver
+ - INTERNAL_NAME is driver name's most significant part (without quotation marks), e.g. mysql
+ Above information uses K_EXPORT_COMPONENT_FACTORY macro for KTrader to find the module's entry point.
+ For example, this line declares kexidb_mysqldriver.so module's entry point:
+ \code
+ KEXIDB_DRIVER_INFO( MySqlDriver, mysql );
+ \endcode
+
+ \sa SQLiteDriver MySqlDriver, pqxxSqlDriver
+*/
+class KEXI_DB_EXPORT Driver : public QObject, public KexiDB::Object
+{
+ Q_OBJECT
+ public:
+ /*! Helpful for retrieving info about driver from using
+ KexiDB::DriverManager::driversInfo() without loading driver libraries. */
+ class Info {
+ public:
+ Info();
+ QString name, caption, comment, fileDBMimeType;
+ //! true is the driver is for file-based database backend
+ bool fileBased : 1;
+ /*! true is the driver is for a backend that allows importing.
+ Defined by X-Kexi-DoNotAllowProjectImportingTo in "kexidb_driver" service type.
+ Used for migration. */
+ bool allowImportingTo : 1;
+ };
+ typedef QMap<QString,Info> InfoMap;
+
+ /*! Features supported by driver (sum of few Features enum items). */
+ enum Features {
+ NoFeatures = 0,
+ //! single trasactions are only supported
+ SingleTransactions = 1,
+ //! multiple concurrent trasactions are supported
+ //! (this implies !SingleTransactions)
+ MultipleTransactions = 2,
+//(js) NOT YET IN USE:
+ /*! nested trasactions are supported
+ (this should imply !SingleTransactions and MultipleTransactions) */
+ NestedTransactions = 4,
+ /*! forward moving is supported for cursors
+ (if not available, no cursors available at all) */
+ CursorForward = 8,
+ /*! backward moving is supported for cursors (this implies CursorForward) */
+ CursorBackward = (CursorForward+16),
+ /*! compacting database supported (aka VACUUM) */
+ CompactingDatabaseSupported = 32,
+ //-- temporary options: can be removed later, use at your own risk --
+ /*! If set, actions related to transactions will be silently bypassed
+ with success. Set this if your driver does not support transactions at all
+ Currently, this is only way to get it working with KexiDB.
+ Keep in mind that this hack do not provide data integrity!
+ This flag is currently used for MySQL driver. */
+ IgnoreTransactions = 1024
+ };
+
+ //! Options used for createConnection()
+ enum CreateConnectionOptions {
+ ReadOnlyConnection = 1 //!< set to perform read only connection
+ };
+
+ virtual ~Driver();
+
+ /*! Creates connection using \a conn_data as parameters.
+ \return 0 and sets error message on error.
+ driverName member of \a conn_data will be updated with this driver name.
+ \a options can be a combination of CreateConnectionOptions enum values.
+ */
+ Connection *createConnection( ConnectionData &conn_data, int options = 0 );
+
+ /*! \return List of created connections. */
+ const QPtrList<Connection> connectionsList() const;
+
+// /*! \return a name equal to the service name (X-Kexi-DriverName)
+// stored in given service .desktop file. */
+// QString driverName() { return m_driverName; }
+
+ /*! \return a name of MIME type of files handled by this driver
+ if it is a file-based database's driver
+ (equal X-Kexi-FileDBDriverMime service property)
+ otherwise returns null string. \sa isFileDriver()
+ */
+ QString fileDBDriverMimeType() const;
+
+ /*! \return default file-based driver mime type
+ (typically something like "application/x-kexiproject-sqlite") */
+ static QString defaultFileBasedDriverMimeType();
+
+ /*! \return default file-based driver name (currently, "sqlite3"). */
+ static QString defaultFileBasedDriverName();
+
+ /*! Info about the driver as a service. */
+ const KService* service() const;
+
+ /*! \return true if this driver is file-based */
+ bool isFileDriver() const;
+
+ /*! \return true if \a n is a system object's name,
+ eg. name of build-in system table that cannot be used or created by a user,
+ and in most cases user even shouldn't see this. The list is specific for
+ a given driver implementation.
+ By default calls Driver::isKexiDBSystemObjectName() static method.
+ Note for driver developers: Also call Driver::isSystemObjectName()
+ from your reimplementation.
+ \sa isSystemFieldName().
+ */
+ virtual bool isSystemObjectName( const QString& n ) const;
+
+ /*! \return true if \a n is a kexibd-related 'system' object's
+ name, i.e. when \a n starts with "kexi__" prefix.
+ */
+ static bool isKexiDBSystemObjectName( const QString& n );
+
+ /*! \return true if \a n is a system database's name,
+ eg. name of build-in, system database that cannot be used or created by a user,
+ and in most cases user even shouldn't see this. The list is specific for
+ a given driver implementation. For implementation.
+ \sa isSystemObjectName().
+ */
+ virtual bool isSystemDatabaseName( const QString& n ) const = 0;
+
+ /*! \return true if \a n is a system field's name, build-in system
+ field that cannot be used or created by a user,
+ and in most cases user even shouldn't see this. The list is specific for
+ a given driver implementation.
+ \sa isSystemObjectName().
+ */
+ bool isSystemFieldName( const QString& n ) const;
+
+ /*! \return Driver's features that are combination of Driver::Features
+ enum. */
+ int features() const;
+
+ /*! \return true if transaction are supported (single or
+ multiple). */
+ bool transactionsSupported() const;
+
+ /*! \return admin tools object providing a number of database administration
+ tools for the driver. Tools availablility varies from driver to driver.
+ You can check it using features(). */
+ AdminTools& adminTools() const;
+
+ /*! SQL-implementation-dependent name of given type */
+ virtual QString sqlTypeName(int id_t, int p=0) const;
+
+ /*! used when we do not have Driver instance yet */
+ static QString defaultSQLTypeName(int id_t);
+
+ /*! \return true if this driver's implementation is valid.
+ Just few constriants are checked to ensure that driver
+ developer didn't forget about something.
+ This method is called automatically on createConnection(),
+ and proper error message is set properly on any error. */
+ virtual bool isValid();
+
+ /*! Driver's static version information (major part), it is automatically defined
+ in implementation by KEXIDB_DRIVER macro (see driver_p.h)
+ It's usually compared to drivers' and KexiDB library version. */
+ virtual DatabaseVersionInfo version() const = 0;
+
+ /*! Escapes and converts value \a v (for type \a ftype)
+ to string representation required by SQL commands.
+ Reimplement this if you need other behaviour (eg. for 'date' type handling)
+ This implementation return date, datetime and time values in ISO format,
+ what seems to be accepted by SQL servers.
+ @see Qt::DateFormat */
+ virtual QString valueToSQL( uint ftype, const QVariant& v ) const;
+
+ //! Like above but with the fildtype as string.
+ inline QString valueToSQL( const QString& ftype, const QVariant& v ) const {
+ return valueToSQL(Field::typeForString(ftype), v);
+ }
+
+ //! Like above method, for \a field.
+ inline QString valueToSQL( const Field *field, const QVariant& v ) const {
+ return valueToSQL( (field ? field->type() : Field::InvalidType), v );
+ }
+
+ /*! not compatible with all drivers - reimplement */
+ inline virtual QString dateTimeToSQL(const QDateTime& v) const {
+
+ /*! (was compatible with SQLite: http://www.sqlite.org/cvstrac/wiki?p=DateAndTimeFunctions)
+ Now it's ISO 8601 DateTime format - with "T" delimiter:
+ http://www.w3.org/TR/NOTE-datetime
+ (e.g. "1994-11-05T13:15:30" not "1994-11-05 13:15:30")
+ @todo add support for time zones?
+ */
+//old const QDateTime dt( v.toDateTime() );
+//old return QString("\'")+dt.date().toString(Qt::ISODate)+" "+dt.time().toString(Qt::ISODate)+"\'";
+ return QString("\'")+v.toString(Qt::ISODate)+"\'";
+ }
+
+ /*! Driver-specific SQL string escaping.
+ Implement escaping for any character like " or ' as your
+ database engine requires. Prepend and append quotation marks.
+ */
+ virtual QString escapeString( const QString& str ) const = 0;
+
+ /*! This is overloaded version of escapeString( const QString& str )
+ to be implemented in the same way.
+ */
+ virtual QCString escapeString( const QCString& str ) const = 0;
+
+ /*! Driver-specific SQL BLOB value escaping.
+ Implement escaping for any character like " or ' and \\0 as your
+ database engine requires. Prepend and append quotation marks.
+ */
+ virtual QString escapeBLOB(const QByteArray& array) const = 0;
+
+//todo enum EscapeType { EscapeDriver = 0x00, EscapeKexi = 0x01};
+//todo enum EscapePolicy { EscapeAsNecessary = 0x00, EscapeAlways = 0x02 };
+
+ enum EscapeType { EscapeDriver = 0x01, EscapeKexi = 0x02};
+
+ enum EscapePolicy { EscapeAsNecessary = 0x04, EscapeAlways = 0x08 };
+
+ //! Driver-specific identifier escaping (e.g. for a table name, db name, etc.)
+ /*! Escape database identifier (\a str) in order that keywords
+ can be used as table names, column names, etc.
+ \a options is the union of the EscapeType and EscapePolicy types.
+ If no escaping options are given, defaults to driver escaping as
+ necessary. */
+ QString escapeIdentifier( const QString& str,
+ int options = EscapeDriver|EscapeAsNecessary) const;
+
+ QCString escapeIdentifier( const QCString& str,
+ int options = EscapeDriver|EscapeAsNecessary) const;
+
+ //! \return property value for \a propeName available for this driver.
+ //! If there's no such property defined for driver, Null QVariant value is returned.
+ QVariant propertyValue( const QCString& propName ) const;
+
+ //! \return translated property caption for \a propeName.
+ //! If there's no such property defined for driver, empty string value is returned.
+ QString propertyCaption( const QCString& propName ) const;
+
+ //! \return a list of property names available for this driver.
+ QValueList<QCString> propertyNames() const;
+
+ protected:
+ /*! Used by DriverManager.
+ Note for driver developers: Reimplement this.
+ In your reimplementation you should initialize:
+ - d->typeNames - to types accepted by your engine
+ - d->isFileDriver - to true or false depending if your driver is file-based
+ - d->features - to combination of selected values from Features enum
+
+ You may also want to change options in DriverBehaviour *beh member.
+ See drivers/mySQL/mysqldriver.cpp for usage example.
+ */
+ Driver( QObject *parent, const char *name, const QStringList &args = QStringList() );
+
+ /*! For reimplemenation: creates and returns connection object
+ with additional structures specific for a given driver.
+ Connection object should inherit Connection and have a destructor
+ that descructs all allocated driver-dependent connection structures. */
+ virtual Connection *drv_createConnection( ConnectionData &conn_data ) = 0;
+//virtual ConnectionInternal* createConnectionInternalObject( Connection& conn ) = 0;
+
+ /*! Driver-specific SQL string escaping.
+ This method is used by escapeIdentifier().
+ Implement escaping for any character like " or ' as your
+ database engine requires. Do not append or prepend any quotation
+ marks characters - it is automatically done by escapeIdentifier() using
+ DriverBehaviour::QUOTATION_MARKS_FOR_IDENTIFIER.
+ */
+ virtual QString drv_escapeIdentifier( const QString& str ) const = 0;
+
+ /*! This is overloaded version of drv_escapeIdentifier( const QString& str )
+ to be implemented in the same way.
+ */
+ virtual QCString drv_escapeIdentifier( const QCString& str ) const = 0;
+
+ /*! \return true if \a n is a system field's name, build-in system
+ field that cannot be used or created by a user,
+ and in most cases user even shouldn't see this. The list is specific for
+ a given driver implementation. For implementation.*/
+ virtual bool drv_isSystemFieldName( const QString& n ) const = 0;
+
+ /* Creates admin tools object providing a number of database administration
+ tools for the driver. This is called once per driver.
+
+ Note for driver developers: Reimplement this method by returning
+ KexiDB::AdminTools-derived object. Default implementation creates
+ empty admin tools.
+ @see adminTools() */
+ virtual AdminTools* drv_createAdminTools() const;
+
+ /*! \return connection \a conn , do not deletes it nor affect.
+ Returns 0 if \a conn is not owned by this driver.
+ After this, you are owner of \a conn object, so you should
+ eventually delete it. Better use Connection destructor. */
+ Connection* removeConnection( Connection *conn );
+
+ friend class Connection;
+ friend class Cursor;
+ friend class DriverManagerInternal;
+
+
+ /*! Used to initialise the dictionary of driver-specific keywords.
+ Should be called by the Driver's constructor.
+ \a hashSize is the number of buckets to use in the dictionary.
+ \sa DriverPrivate::SQL_KEYWORDS. */
+ void initSQLKeywords(int hashSize = 17);
+
+ DriverBehaviour *beh;
+ DriverPrivate *d;
+};
+
+} //namespace KexiDB
+
+/*! Driver's static version information, automatically impemented for KexiDB drivers.
+ Put this into driver class declaration just like Q_OBJECT macro. */
+#define KEXIDB_DRIVER \
+ public: \
+ virtual DatabaseVersionInfo version() const;
+
+#endif
+
diff --git a/kexi/kexidb/driver_p.cpp b/kexi/kexidb/driver_p.cpp
new file mode 100644
index 000000000..2ad5f9ce1
--- /dev/null
+++ b/kexi/kexidb/driver_p.cpp
@@ -0,0 +1,129 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2004 Martin Ellis <martin.ellis@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+#include <qdict.h>
+#include <qvaluevector.h>
+#include "driver_p.h"
+
+using namespace KexiDB;
+
+namespace KexiDB {
+ QAsciiDict<bool>* DriverPrivate::kexiSQLDict = 0;
+
+ /*! QAsciiDict keys need to be a pointer to *something*. Used
+ for SQL keyword dictionaries
+ */
+ static bool _dummy;
+}
+
+
+DriverPrivate::DriverPrivate()
+ : isFileDriver(false)
+ , isDBOpenedAfterCreate(false)
+ , features(Driver::NoFeatures)
+{
+ kexiSQLDict = 0;
+ driverSQLDict = 0;
+ adminTools = 0;
+
+ properties["client_library_version"] = "";
+ propertyCaptions["client_library_version"] =
+ i18n("Client library version");
+
+ properties["default_server_encoding"] = "";
+ propertyCaptions["default_server_encoding"] =
+ i18n("Default character encoding on server");
+}
+
+void DriverPrivate::initInternalProperties()
+{
+ properties["is_file_database"] = QVariant(isFileDriver, 1);
+ propertyCaptions["is_file_database"] = i18n("File-based database driver");
+ if (isFileDriver) {
+ properties["file_database_mimetype"] = fileDBDriverMimeType;
+ propertyCaptions["file_database_mimetype"] = i18n("File-based database's MIME type");
+ }
+
+#if 0
+ QString str;
+ if (features & Driver::SingleTransactions)
+ str = i18n("Single transactions");
+ else if (features & Driver::MultipleTransactions)
+ str = i18n("Multiple transactions");
+ else if (features & Driver::NestedTransactions)
+ str = i18n("Nested transactions");
+ else if (features & Driver::IgnoreTransactions)
+ str = i18n("Ignored");
+ else
+ str = i18n("None");
+#endif
+// properties["transaction_support"] = features & Driver::TransactionsMask;
+// propertyCaptions["transaction_support"] = i18n("Transaction support");
+ properties["transaction_single"] = QVariant(features & Driver::SingleTransactions, 1);
+ propertyCaptions["transaction_single"] = i18n("Single transactions support");
+ properties["transaction_multiple"] = QVariant(features & Driver::MultipleTransactions, 1);
+ propertyCaptions["transaction_multiple"] = i18n("Multiple transactions support");
+ properties["transaction_nested"] = QVariant(features & Driver::NestedTransactions, 1);
+ propertyCaptions["transaction_nested"] = i18n("Nested transactions support");
+
+ properties["kexidb_driver_version"] =
+ QString("%1.%2").arg(version().major).arg(version().minor);
+ propertyCaptions["kexidb_driver_version"] =
+ i18n("KexiDB driver version");
+}
+
+DriverPrivate::~DriverPrivate()
+{
+ delete driverSQLDict;
+ delete adminTools;
+}
+
+
+void DriverPrivate::initKexiKeywords() {
+ // QAsciiDict constructor args:
+ // size (preferable prime)
+ // case sensitive flag (false)
+ // copy strings (false)
+ if(!kexiSQLDict) {
+ kexiSQLDict = new QAsciiDict<bool>(79, false, false);
+ initKeywords(kexiSQLKeywords, *kexiSQLDict);
+ }
+}
+
+void DriverPrivate::initDriverKeywords(const char* keywords[], int hashSize) {
+ driverSQLDict = new QAsciiDict<bool>(hashSize, false, false);
+ initKeywords(keywords, *driverSQLDict);
+}
+
+void DriverPrivate::initKeywords(const char* keywords[],
+ QAsciiDict<bool>& dict) {
+ for(int i = 0; keywords[i] != 0; i++) {
+ dict.insert(keywords[i], &_dummy);
+ }
+}
+
+AdminTools::Private::Private()
+{
+}
+
+AdminTools::Private::~Private()
+{
+}
diff --git a/kexi/kexidb/driver_p.h b/kexi/kexidb/driver_p.h
new file mode 100644
index 000000000..44ecd6178
--- /dev/null
+++ b/kexi/kexidb/driver_p.h
@@ -0,0 +1,262 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_DRIVER_P_H
+#define KEXIDB_DRIVER_P_H
+
+#ifndef __KEXIDB__
+# error "Do not include: this is KexiDB internal file"
+#endif
+
+#include <qstring.h>
+#include <qvariant.h>
+#include <qmap.h>
+#include <qptrdict.h>
+#include <qasciidict.h>
+#include <qvaluevector.h>
+#include <kgenericfactory.h>
+
+#include "connection.h"
+#include "admin.h"
+
+class KService;
+
+namespace KexiDB {
+
+/*! Detailed definition of driver's default behaviour.
+ Note for driver developers:
+ Change these defaults in you Driver subclass
+ constructor, if needed.
+*/
+class KEXI_DB_EXPORT DriverBehaviour
+{
+ public:
+ DriverBehaviour();
+
+ //! "UNSIGNED" by default
+ QString UNSIGNED_TYPE_KEYWORD;
+
+ //! "AUTO_INCREMENT" by default, used as add-in word to field definition
+ //! May be also used as full definition if SPECIAL_AUTO_INCREMENT_DEF is true.
+ QString AUTO_INCREMENT_FIELD_OPTION;
+
+ //! "AUTO_INCREMENT PRIMARY KEY" by default, used as add-in word to field definition
+ //! May be also used as full definition if SPECIAL_AUTO_INCREMENT_DEF is true.
+ QString AUTO_INCREMENT_PK_FIELD_OPTION;
+
+ //! "" by default, used as type string for autoinc. field definition
+ //! pgsql defines it as "SERIAL", sqlite defines it as "INTEGER"
+ QString AUTO_INCREMENT_TYPE;
+
+ /*! True if autoincrement field has special definition
+ e.g. like "INTEGER PRIMARY KEY" for SQLite.
+ Special definition string should be stored in AUTO_INCREMENT_FIELD_OPTION.
+ False by default. */
+ bool SPECIAL_AUTO_INCREMENT_DEF : 1;
+
+ /*! True if autoincrement requires field to be declared as primary key.
+ This is true for SQLite. False by default. */
+ bool AUTO_INCREMENT_REQUIRES_PK : 1;
+
+ /*! Name of a field (or built-in function) with autoincremented unique value,
+ typically returned by Connection::drv_lastInsertRowID().
+
+ Examples:
+ - PostgreSQL and SQLite engines use 'OID' field
+ - MySQL uses LAST_INSERT_ID() built-in function
+ */
+ QString ROW_ID_FIELD_NAME;
+
+ /*! True if the value (fetched from field or function,
+ defined by ROW_ID_FIELD_NAME member) is EXACTLY the value of autoincremented field,
+ not an implicit (internal) row number. Default value is false.
+
+ Examples:
+ - PostgreSQL and SQLite engines have this flag set to false ('OID' field has
+ it's own implicit value)
+ - MySQL engine has this flag set to true (LAST_INSERT_ID() returns real value
+ of last autoincremented field).
+
+ Notes:
+ If it's false, we have a convenient way for identifying row even when there's
+ no primary key defined. So, as '_ROWID' column in MySQL is really
+ just a synonym for the primary key, this engine needs to have primary keys always
+ defined if we want to use interactive editing features like row updating and deleting.
+ */
+ bool ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE : 1;
+
+ /*! Name of any (e.g. first found) database for this connection that
+ typically always exists. This can be not set if we want to do some magic checking
+ what database name is availabe by reimplementing
+ Connection::anyAvailableDatabaseName().
+ Example: for PostgreSQL this is "template1".
+
+ \sa Connection::SetAvailableDatabaseName()
+ */
+ QString ALWAYS_AVAILABLE_DATABASE_NAME;
+
+ /*! Quotation marks used for escaping identifier (see Driver::escapeIdentifier()).
+ Default value is '"'. Change it for your driver.
+ */
+ QChar QUOTATION_MARKS_FOR_IDENTIFIER;
+
+ /*! True if using database is requied to perform real connection.
+ This is true for may engines, e.g. for PostgreSQL, where connections
+ string should contain a database name.
+ This flag is unused for file-based db drivers,
+ by default set to true and used for all other db drivers.
+ */
+ bool USING_DATABASE_REQUIRED_TO_CONNECT : 1;
+
+ /*! True if before we know whether the fetched result of executed query
+ is empty or not, we need to fetch first record. Particularly, it's true for SQLite.
+ The flag is used in Cursor::open(). By default this flag is false. */
+ bool _1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY : 1;
+
+ /*! True if "SELECT 1 from (subquery)" is supported. False by default.
+ Used in Connection::resultExists() for optimization. It's set to true for SQLite driver. */
+ bool SELECT_1_SUBQUERY_SUPPORTED : 1;
+
+ /*! Keywords that need to be escaped for the driver. Set this before calling
+ Driver::initSQLKeywords. */
+ const char** SQL_KEYWORDS;
+};
+
+/*! Private driver's data members. Available for implementation. */
+class DriverPrivate
+{
+ public:
+ DriverPrivate();
+ virtual ~DriverPrivate();
+
+ QPtrDict<Connection> connections;
+
+//(js)now QObject::name() is reused:
+// /*! The name equal to the service name (X-Kexi-DriverName)
+// stored in given service .desktop file. Set this in subclasses. */
+// QString m_driverName;
+
+ /*! Name of MIME type of files handled by this driver
+ if it is a file-based database's driver
+ (equal X-Kexi-FileDBDriverMime service property) */
+ QString fileDBDriverMimeType;
+
+ /*! Info about the driver as a service. */
+ KService *service;
+
+ /*! Internal constant flag: Set this in subclass if driver is a file driver */
+ bool isFileDriver : 1;
+
+ /*! Internal constant flag: Set this in subclass if after successful
+ drv_createDatabased() database is in opened state (as after useDatabase()).
+ For most engines this is not true. */
+ bool isDBOpenedAfterCreate : 1;
+
+ /*! List of system objects names, eg. build-in system tables that
+ cannot be used by user, and in most cases user even shouldn't see these.
+ The list contents is driver dependent (by default is empty)
+ - fill this in subclass ctor. */
+// QStringList m_systemObjectNames;
+
+ /*! List of system fields names, build-in system fields that cannot be used by user,
+ and in most cases user even shouldn't see these.
+ The list contents is driver dependent (by default is empty) - fill this in subclass ctor. */
+// QStringList m_systemFieldNames;
+
+ /*! Features (like transactions, etc.) supported by this driver
+ (sum of selected Features enum items).
+ This member should be filled in driver implementation's constructor
+ (by default m_features==NoFeatures). */
+ int features;
+
+ //! real type names for this engine
+ QValueVector<QString> typeNames;
+
+ /*! Driver properties dictionary (indexed by name),
+ useful for presenting properties to the user.
+ Set available properties here in driver implementation. */
+ QMap<QCString,QVariant> properties;
+
+ /*! i18n'd captions for properties. You do not need
+ to set predefined properties' caption in driver implementation
+ -it's done automatically. */
+ QMap<QCString,QString> propertyCaptions;
+
+ /*! Provides a number of database administration tools for the driver. */
+ AdminTools *adminTools;
+
+ /*! Kexi SQL keywords that need to be escaped if used as an identifier (e.g.
+ for a table or column name). These keywords will be escaped by the
+ front-end, even if they are not recognised by the backend to provide
+ UI consistency and to allow DB migration without changing the queries.
+ \sa DriverPrivate::initKexiKeywords(), KexiDB::kexiSQLKeywords.
+ */
+ static QAsciiDict<bool>* kexiSQLDict;
+ static const char *kexiSQLKeywords[];
+
+ /*! Driver-specific SQL keywords that need to be escaped if used as an
+ identifier (e.g. for a table or column name) that aren't also Kexi SQL
+ keywords. These don't neccesarily need to be escaped when displayed by
+ the front-end, because they won't confuse the parser. However, they do
+ need to be escaped before sending to the DB-backend which will have
+ it's own parser.
+
+ \sa DriverBehaviour::SQL_KEYWORDS.
+ */
+ QAsciiDict<bool>* driverSQLDict;
+
+ /*! Initialise the dictionary of Kexi SQL keywords used for escaping. */
+ void initKexiKeywords();
+ /*! Initialise the dictionary of driver-specific keywords used for escaping.
+ \a hashSize is the number of buckets to use in the dictionary.
+ \sa Driver::initSQLKeywords(). */
+ void initDriverKeywords(const char* keywords[], int hashSize);
+
+ protected:
+ /*! Used by driver manager to initialize properties taken using internal
+ driver flags. */
+ void initInternalProperties();
+
+ private:
+ void initKeywords(const char* keywords[], QAsciiDict<bool>& dict);
+
+ friend class DriverManagerInternal;
+};
+
+// escaping types for Driver::escapeBLOBInternal()
+#define BLOB_ESCAPING_TYPE_USE_X 0 //!< escaping like X'abcd0', used by sqlite
+#define BLOB_ESCAPING_TYPE_USE_0x 1 //!< escaping like 0xabcd0, used by mysql
+#define BLOB_ESCAPING_TYPE_USE_OCTAL 2 //!< escaping like 'abcd\\000', used by pgsql
+
+class KEXI_DB_EXPORT AdminTools::Private
+{
+ public:
+ Private();
+ ~Private();
+};
+
+}
+
+//! Driver's static version information (implementation),
+//! with KLibFactory symbol declaration.
+#define KEXIDB_DRIVER_INFO( class_name, internal_name ) \
+ DatabaseVersionInfo class_name::version() const { return KEXIDB_VERSION; } \
+ K_EXPORT_COMPONENT_FACTORY(kexidb_ ## internal_name ## driver, KGenericFactory<KexiDB::class_name>( "kexidb_" #internal_name ))
+
+#endif
diff --git a/kexi/kexidb/drivermanager.cpp b/kexi/kexidb/drivermanager.cpp
new file mode 100644
index 000000000..e7e5b2bbc
--- /dev/null
+++ b/kexi/kexidb/drivermanager.cpp
@@ -0,0 +1,435 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/drivermanager_p.h>
+
+#include <kexidb/driver.h>
+#include <kexidb/driver_p.h>
+#include <kexidb/error.h>
+
+#include <klibloader.h>
+#include <kparts/componentfactory.h>
+#include <ktrader.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kservice.h>
+
+#include <assert.h>
+
+#include <qapplication.h>
+
+//remove debug
+#undef KexiDBDbg
+#define KexiDBDbg if (0) kdDebug()
+
+using namespace KexiDB;
+
+DriverManagerInternal* DriverManagerInternal::s_self = 0L;
+
+
+DriverManagerInternal::DriverManagerInternal() /* protected */
+ : QObject( 0, "KexiDB::DriverManager" )
+ , Object()
+ , m_drivers(17, false)
+ , m_refCount(0)
+ , lookupDriversNeeded(true)
+{
+ m_drivers.setAutoDelete(true);
+ m_serverResultNum=0;
+
+}
+
+DriverManagerInternal::~DriverManagerInternal()
+{
+ KexiDBDbg << "DriverManagerInternal::~DriverManagerInternal()" << endl;
+ m_drivers.clear();
+ if ( s_self == this )
+ s_self = 0;
+ KexiDBDbg << "DriverManagerInternal::~DriverManagerInternal() ok" << endl;
+}
+
+void DriverManagerInternal::slotAppQuits()
+{
+ if (qApp->mainWidget() && qApp->mainWidget()->isVisible())
+ return; //what a hack! - we give up when app is still there
+ KexiDBDbg << "DriverManagerInternal::slotAppQuits(): let's clear drivers..." << endl;
+ m_drivers.clear();
+}
+
+DriverManagerInternal *DriverManagerInternal::self()
+{
+ if (!s_self)
+ s_self = new DriverManagerInternal();
+
+ return s_self;
+}
+
+bool DriverManagerInternal::lookupDrivers()
+{
+ if (!lookupDriversNeeded)
+ return true;
+
+ if (qApp) {
+ connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(slotAppQuits()));
+ }
+//TODO: for QT-only version check for KInstance wrapper
+// KexiDBWarn << "DriverManagerInternal::lookupDrivers(): cannot work without KInstance (KGlobal::instance()==0)!" << endl;
+// setError("Driver Manager cannot work without KInstance (KGlobal::instance()==0)!");
+
+ lookupDriversNeeded = false;
+ clearError();
+ KTrader::OfferList tlist = KTrader::self()->query("Kexi/DBDriver");
+ KTrader::OfferList::ConstIterator it(tlist.constBegin());
+ for(; it != tlist.constEnd(); ++it)
+ {
+ KService::Ptr ptr = (*it);
+ if (!ptr->property("Library").toString().startsWith("kexidb_")) {
+ KexiDBWarn << "DriverManagerInternal::lookupDrivers():"
+ " X-KDE-Library == " << ptr->property("Library").toString()
+ << ": no \"kexidb_\" prefix -- skipped to avoid potential conflicts!" << endl;
+ continue;
+ }
+ QString srv_name = ptr->property("X-Kexi-DriverName").toString();
+ if (srv_name.isEmpty()) {
+ KexiDBWarn << "DriverManagerInternal::lookupDrivers():"
+ " X-Kexi-DriverName must be set for KexiDB driver \""
+ << ptr->property("Name").toString() << "\" service!\n -- skipped!" << endl;
+ continue;
+ }
+ if (m_services_lcase.contains(srv_name.lower())) {
+ KexiDBWarn << "DriverManagerInternal::lookupDrivers(): more than one driver named '"
+ << srv_name.lower() << "'\n -- skipping this one!" << endl;
+ continue;
+ }
+
+ QString srv_ver_str = ptr->property("X-Kexi-KexiDBVersion").toString();
+ QStringList lst( QStringList::split(".", srv_ver_str) );
+ uint minor_ver, major_ver;
+ bool ok = (lst.count() == 2);
+ if (ok)
+ major_ver = lst[0].toUInt(&ok);
+ if (ok)
+ minor_ver = lst[1].toUInt(&ok);
+ if (!ok) {
+ KexiDBWarn << "DriverManagerInternal::lookupDrivers(): problem with detecting '"
+ << srv_name.lower() << "' driver's version -- skipping it!" << endl;
+ continue;
+ }
+ if (major_ver != KexiDB::version().major || minor_ver != KexiDB::version().minor) {
+ KexiDBWarn << QString("DriverManagerInternal::lookupDrivers(): '%1' driver"
+ " has version '%2' but required KexiDB driver version is '%3.%4'\n"
+ " -- skipping this driver!").arg(srv_name.lower()).arg(srv_ver_str)
+ .arg(KexiDB::version().major).arg(KexiDB::version().minor) << endl;
+ possibleProblems += QString("\"%1\" database driver has version \"%2\" "
+ "but required driver version is \"%3.%4\"")
+ .arg(srv_name.lower()).arg(srv_ver_str)
+ .arg(KexiDB::version().major).arg(KexiDB::version().minor);
+ continue;
+ }
+
+ QString drvType = ptr->property("X-Kexi-DriverType").toString().lower();
+ if (drvType=="file") {
+ //new property: a list of supported mime types
+ QStringList mimes( ptr->property("X-Kexi-FileDBDriverMimeList").toStringList() );
+ //single mime is obsolete, but we're handling it:
+ {
+ QString mime( ptr->property("X-Kexi-FileDBDriverMime").toString().lower() );
+ if (!mime.isEmpty())
+ mimes.append( mime );
+ }
+
+ //store association of this driver with all listed mime types
+ for (QStringList::ConstIterator mime_it = mimes.constBegin(); mime_it!=mimes.constEnd(); ++mime_it) {
+ QString mime( (*mime_it).lower() );
+ if (!m_services_by_mimetype.contains(mime)) {
+ m_services_by_mimetype.insert(mime, ptr);
+ }
+ else {
+ KexiDBWarn << "DriverManagerInternal::lookupDrivers(): more than one driver for '"
+ << mime << "' mime type!" << endl;
+ }
+ }
+ }
+ m_services.insert(srv_name, ptr);
+ m_services_lcase.insert(srv_name.lower(), ptr);
+ KexiDBDbg << "KexiDB::DriverManager::lookupDrivers(): registered driver: " << ptr->name() << "(" << ptr->library() << ")" << endl;
+ }
+
+ if (tlist.isEmpty())
+ {
+ setError(ERR_DRIVERMANAGER, i18n("Could not find any database drivers.") );
+ return false;
+ }
+ return true;
+}
+
+KexiDB::Driver::Info DriverManagerInternal::driverInfo(const QString &name)
+{
+ KexiDB::Driver::Info i = m_driversInfo[name.lower()];
+ if (!error() && i.name.isEmpty())
+ setError(ERR_DRIVERMANAGER, i18n("Could not find database driver \"%1\".").arg(name) );
+ return i;
+}
+
+Driver* DriverManagerInternal::driver(const QString& name)
+{
+ if (!lookupDrivers())
+ return 0;
+
+ clearError();
+ KexiDBDbg << "DriverManager::driver(): loading " << name << endl;
+
+ Driver *drv = name.isEmpty() ? 0 : m_drivers.find(name.latin1());
+ if (drv)
+ return drv; //cached
+
+ if (!m_services_lcase.contains(name.lower())) {
+ setError(ERR_DRIVERMANAGER, i18n("Could not find database driver \"%1\".").arg(name) );
+ return 0;
+ }
+
+ KService::Ptr ptr= *(m_services_lcase.find(name.lower()));
+ QString srv_name = ptr->property("X-Kexi-DriverName").toString();
+
+ KexiDBDbg << "KexiDBInterfaceManager::load(): library: "<<ptr->library()<<endl;
+ drv = KParts::ComponentFactory::createInstanceFromService<KexiDB::Driver>(ptr,
+ this, srv_name.latin1(), QStringList(),&m_serverResultNum);
+
+ if (!drv) {
+ setError(ERR_DRIVERMANAGER, i18n("Could not load database driver \"%1\".")
+ .arg(name) );
+ if (m_componentLoadingErrors.isEmpty()) {//fill errtable on demand
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrNoServiceFound]="ErrNoServiceFound";
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrServiceProvidesNoLibrary]="ErrServiceProvidesNoLibrary";
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrNoLibrary]="ErrNoLibrary";
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrNoFactory]="ErrNoFactory";
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrNoComponent]="ErrNoComponent";
+ }
+ m_serverResultName=m_componentLoadingErrors[m_serverResultNum];
+ return 0;
+ }
+ KexiDBDbg << "KexiDBInterfaceManager::load(): loading succeed: " << name <<endl;
+// KexiDBDbg << "drv="<<(long)drv <<endl;
+
+// drv->setName(srv_name.latin1());
+ drv->d->service = ptr.data(); //store info
+ drv->d->fileDBDriverMimeType = ptr->property("X-Kexi-FileDBDriverMime").toString();
+ drv->d->initInternalProperties();
+
+ if (!drv->isValid()) {
+ setError(drv);
+ delete drv;
+ return 0;
+ }
+ m_drivers.insert(name.latin1(), drv); //cache it
+ return drv;
+}
+
+void DriverManagerInternal::incRefCount()
+{
+ m_refCount++;
+ KexiDBDbg << "DriverManagerInternal::incRefCount(): " << m_refCount << endl;
+}
+
+void DriverManagerInternal::decRefCount()
+{
+ m_refCount--;
+ KexiDBDbg << "DriverManagerInternal::decRefCount(): " << m_refCount << endl;
+// if (m_refCount<1) {
+// KexiDBDbg<<"KexiDB::DriverManagerInternal::decRefCount(): reached m_refCount<1 -->deletelater()"<<endl;
+// s_self=0;
+// deleteLater();
+// }
+}
+
+void DriverManagerInternal::aboutDelete( Driver* drv )
+{
+ m_drivers.take(drv->name());
+}
+
+
+
+// ---------------------------
+// --- DriverManager impl. ---
+// ---------------------------
+
+DriverManager::DriverManager()
+ : QObject( 0, "KexiDB::DriverManager" )
+ , Object()
+ , d_int( DriverManagerInternal::self() )
+{
+ d_int->incRefCount();
+// if ( !s_self )
+// s_self = this;
+// lookupDrivers();
+}
+
+DriverManager::~DriverManager()
+{
+ KexiDBDbg << "DriverManager::~DriverManager()" << endl;
+/* Connection *conn;
+ for ( conn = m_connections.first(); conn ; conn = m_connections.next() ) {
+ conn->disconnect();
+ conn->m_driver = 0; //don't let the connection touch our driver now
+ m_connections.remove();
+ delete conn;
+ }*/
+
+ d_int->decRefCount();
+ if (d_int->m_refCount==0) {
+ //delete internal drv manager!
+ delete d_int;
+ }
+// if ( s_self == this )
+ //s_self = 0;
+ KexiDBDbg << "DriverManager::~DriverManager() ok" << endl;
+}
+
+const KexiDB::Driver::InfoMap DriverManager::driversInfo()
+{
+ if (!d_int->lookupDrivers())
+ return KexiDB::Driver::InfoMap();
+
+ if (!d_int->m_driversInfo.isEmpty())
+ return d_int->m_driversInfo;
+ ServicesMap::ConstIterator it;
+ for ( it=d_int->m_services.constBegin() ; it != d_int->m_services.constEnd(); ++it ) {
+ Driver::Info info;
+ KService::Ptr ptr = it.data();
+ info.name = ptr->property("X-Kexi-DriverName").toString();
+ info.caption = ptr->property("Name").toString();
+ info.comment = ptr->property("Comment").toString();
+ if (info.caption.isEmpty())
+ info.caption = info.name;
+ info.fileBased = (ptr->property("X-Kexi-DriverType").toString().lower()=="file");
+ if (info.fileBased)
+ info.fileDBMimeType = ptr->property("X-Kexi-FileDBDriverMime").toString().lower();
+ QVariant v = ptr->property("X-Kexi-DoNotAllowProjectImportingTo");
+ info.allowImportingTo = v.isNull() ? true : !v.toBool();
+ d_int->m_driversInfo.insert(info.name.lower(), info);
+ }
+ return d_int->m_driversInfo;
+}
+
+const QStringList DriverManager::driverNames()
+{
+ if (!d_int->lookupDrivers())
+ return QStringList();
+
+ if (d_int->m_services.isEmpty() && d_int->error())
+ return QStringList();
+ return d_int->m_services.keys();
+}
+
+KexiDB::Driver::Info DriverManager::driverInfo(const QString &name)
+{
+ driversInfo();
+ KexiDB::Driver::Info i = d_int->driverInfo(name);
+ if (d_int->error())
+ setError(d_int);
+ return i;
+}
+
+KService::Ptr DriverManager::serviceInfo(const QString &name)
+{
+ if (!d_int->lookupDrivers()) {
+ setError(d_int);
+ return KService::Ptr();
+ }
+
+ clearError();
+ if (d_int->m_services_lcase.contains(name.lower())) {
+ return *d_int->m_services_lcase.find(name.lower());
+ } else {
+ setError(ERR_DRIVERMANAGER, i18n("No such driver service: \"%1\".").arg(name) );
+ return KService::Ptr();
+ }
+}
+
+const DriverManager::ServicesMap& DriverManager::services()
+{
+ d_int->lookupDrivers();
+
+ return d_int->m_services;
+}
+
+QString DriverManager::lookupByMime(const QString &mimeType)
+{
+ if (!d_int->lookupDrivers()) {
+ setError(d_int);
+ return 0;
+ }
+
+ KService::Ptr ptr = d_int->m_services_by_mimetype[mimeType.lower()];
+ if (!ptr)
+ return QString::null;
+ return ptr->property("X-Kexi-DriverName").toString();
+}
+
+Driver* DriverManager::driver(const QString& name)
+{
+ Driver *drv = d_int->driver(name);
+ if (d_int->error())
+ setError(d_int);
+ return drv;
+}
+
+QString DriverManager::serverErrorMsg()
+{
+ return d_int->m_serverErrMsg;
+}
+
+int DriverManager::serverResult()
+{
+ return d_int->m_serverResultNum;
+}
+
+QString DriverManager::serverResultName()
+{
+ return d_int->m_serverResultName;
+}
+
+void DriverManager::drv_clearServerResult()
+{
+ d_int->m_serverErrMsg=QString::null;
+ d_int->m_serverResultNum=0;
+ d_int->m_serverResultName=QString::null;
+}
+
+QString DriverManager::possibleProblemsInfoMsg() const
+{
+ if (d_int->possibleProblems.isEmpty())
+ return QString::null;
+ QString str;
+ str.reserve(1024);
+ str = "<ul>";
+ for (QStringList::ConstIterator it = d_int->possibleProblems.constBegin();
+ it!=d_int->possibleProblems.constEnd(); ++it)
+ {
+ str += (QString::fromLatin1("<li>") + *it + QString::fromLatin1("</li>"));
+ }
+ str += "</ul>";
+ return str;
+}
+
+#include "drivermanager_p.moc"
+
diff --git a/kexi/kexidb/drivermanager.h b/kexi/kexidb/drivermanager.h
new file mode 100644
index 000000000..fa78c8417
--- /dev/null
+++ b/kexi/kexidb/drivermanager.h
@@ -0,0 +1,104 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_DRIVER_MNGR_H
+#define KEXIDB_DRIVER_MNGR_H
+
+#include <qobject.h>
+#include <qcstring.h>
+#include <qmap.h>
+#include <qdict.h>
+
+#include <klibloader.h>
+#include <kservice.h>
+
+#include <kexidb/driver.h>
+
+namespace KexiDB {
+
+class DriverManagerInternal;
+class Connection;
+class ConnectionData;
+
+//! Database driver management, e.g. finding and loading drivers.
+class KEXI_DB_EXPORT DriverManager : public QObject, public KexiDB::Object
+{
+ public:
+ typedef QMap<QString, KService::Ptr> ServicesMap;
+
+ DriverManager();
+ virtual ~DriverManager();
+
+ /*! Tries to load db driver with named name \a name.
+ The name is case insensitive.
+ \return db driver, or 0 if error (then error message is also set) */
+ Driver* driver(const QString& name);
+
+ /*! returns list of available drivers names.
+ That drivers can be loaded by first use of driver() method. */
+ const QStringList driverNames();
+
+ /*! returns information list of available drivers.
+ That drivers can be loaded by first use of driver() method. */
+ const KexiDB::Driver::InfoMap driversInfo();
+
+ /*! \return information about driver's named with \a name.
+ The name is case insensitive.
+ You can check if driver information is not found calling
+ Info::name.isEmpty() (then error message is also set). */
+ KexiDB::Driver::Info driverInfo(const QString &name);
+
+ /*! \return service information about driver's named with \a name.
+ The name is case insensitive.
+ In most cases you can use driverInfo() instead. */
+ KService::Ptr serviceInfo(const QString &name);
+
+ /*! \return a map structure of the services. Not necessary for everyday use. */
+ const ServicesMap& services();
+
+ /*! Looks up a drivers list by MIME type of database file.
+ Only file-based database drivers are checked.
+ The lookup is case insensitive.
+ \return driver name or null string if no driver found.
+ */
+ QString lookupByMime(const QString &mimeType);
+
+ //! server error is set if there is error at KService level (useful for debugging)
+ virtual QString serverErrorMsg();
+ virtual int serverResult();
+ virtual QString serverResultName();
+
+ /*! HTML information about possible problems encountered.
+ It's displayed in 'details' section, if an error encountered.
+ Currently it contains a list of incompatible db drivers.
+ Used in KexiStartupHandler::detectDriverForFile(). */
+ QString possibleProblemsInfoMsg() const;
+
+ protected:
+ virtual void drv_clearServerResult();
+
+ private:
+ DriverManagerInternal *d_int;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/drivermanager_p.h b/kexi/kexidb/drivermanager_p.h
new file mode 100644
index 000000000..ce92dd039
--- /dev/null
+++ b/kexi/kexidb/drivermanager_p.h
@@ -0,0 +1,94 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_DRIVER_MNGR_P_H
+#define KEXIDB_DRIVER_MNGR_P_H
+
+#include <kexidb/object.h>
+
+#include <qobject.h>
+#include <qasciidict.h>
+
+namespace KexiDB {
+
+/*! Internal class of driver manager.
+*/
+class KEXI_DB_EXPORT DriverManagerInternal : public QObject, public KexiDB::Object
+{
+ Q_OBJECT
+ public:
+ ~DriverManagerInternal();
+
+ /*! Tries to load db driver \a name.
+ \return db driver, or 0 if error (then error message is also set) */
+ KexiDB::Driver* driver(const QString& name);
+
+ KexiDB::Driver::Info driverInfo(const QString &name);
+
+ static DriverManagerInternal *self();
+
+ /*! increments the refcount for the manager */
+ void incRefCount();
+
+ /*! decrements the refcount for the manager
+ if the refcount reaches a value less than 1 the manager is freed */
+ void decRefCount();
+
+ /*! Called from Driver dtor (because sometimes KLibrary (used by Driver)
+ is destroyed before DriverManagerInternal) */
+ void aboutDelete( Driver* drv );
+
+ protected slots:
+ /*! Used to destroy all drivers on QApplication quit, so even if there are
+ DriverManager's static instances that are destroyed on program
+ "static destruction", drivers are not kept after QApplication death.
+ */
+ void slotAppQuits();
+
+ protected:
+ /*! Used by self() */
+ DriverManagerInternal();
+
+ bool lookupDrivers();
+
+ static KexiDB::DriverManagerInternal* s_self;
+
+ DriverManager::ServicesMap m_services; //! services map
+ DriverManager::ServicesMap m_services_lcase; //! as above but service names in lowercase
+ DriverManager::ServicesMap m_services_by_mimetype;
+ Driver::InfoMap m_driversInfo; //! used to store drivers information
+ QAsciiDict<KexiDB::Driver> m_drivers;
+ ulong m_refCount;
+
+ QString m_serverErrMsg;
+ int m_serverResultNum;
+ QString m_serverResultName;
+ //! result names for KParts::ComponentFactory::ComponentLoadingError
+ QMap<int,QString> m_componentLoadingErrors;
+
+ QStringList possibleProblems;
+
+ bool lookupDriversNeeded : 1;
+
+ friend class DriverManager;
+};
+}
+
+#endif
+
diff --git a/kexi/kexidb/drivers/Makefile.am b/kexi/kexidb/drivers/Makefile.am
new file mode 100644
index 000000000..642a80f30
--- /dev/null
+++ b/kexi/kexidb/drivers/Makefile.am
@@ -0,0 +1,11 @@
+if compile_mysql_plugin
+mysql_dir=mySQL
+endif
+
+
+if compile_pgsql_plugin
+pgsql_dir=pqxx
+endif
+
+SUBDIRS = sqlite sqlite2 $(pgsql_dir) $(mysql_dir)
+
diff --git a/kexi/kexidb/drivers/common.pro b/kexi/kexidb/drivers/common.pro
new file mode 100644
index 000000000..4795c4540
--- /dev/null
+++ b/kexi/kexidb/drivers/common.pro
@@ -0,0 +1,11 @@
+CONFIG += kde3lib
+
+TEMPLATE = lib
+include( $(KEXI)/kexidb/common.pro )
+
+win32:LIBS += \
+$$KDELIBDESTDIR/kexidb$$KEXILIB_SUFFIX
+
+#$$KDELIBDESTDIR/kexicore$$KEXILIB_SUFFIX \
+#$$KDELIBDESTDIR/kexidatatable$$KEXILIB_SUFFIX \
+
diff --git a/kexi/kexidb/drivers/configure.in.bot b/kexi/kexidb/drivers/configure.in.bot
new file mode 100644
index 000000000..02707c446
--- /dev/null
+++ b/kexi/kexidb/drivers/configure.in.bot
@@ -0,0 +1,99 @@
+if test -z "$MYSQL_INC" -o -z "$MYSQL_LIBS"; then
+
+ echo "----------------------------------------------------------------------"
+
+ echo " + The MySQL development files were not found."
+ cat <<EOS
+ These are required for MySQL support in Kexi.
+
+ If you want MySQL support in Kexi, you need to install the MySQL development
+ files, ensure that mysql-config is in your path, and run this configure script
+ again, and finally run make; make install.
+ If you don't need MySQL support, you can simply run make; make install now.
+EOS
+ all_tests=bad
+fi
+
+if test -z "$PG_INCDIR" -o -z "$PG_LIBDIR" -o \
+ -z "$PQXX_INCDIR" -o -z "$PQXX_LIBDIR"; then
+
+ echo "----------------------------------------------------------------------"
+
+# LIBPQ messages
+ if test -z "$PG_INCDIR"; then
+ echo " + The PostgreSQL C-API (libpq) headers were not found."
+ fi
+
+ if test -z "$PG_LIBDIR"; then
+ echo " + The PostgreSQL C-API (libpq) libraries were not found."
+ fi
+
+ if test -z "$PG_INCDIR" -a -z "$PG_LIBDIR" ; then
+ pglib_parts_missing="HEADER or the libpq LIBRARY"
+ elif test -z "$PG_INCDIR" ; then
+ pglib_parts_missing="HEADER"
+ elif test -z "$PG_LIBDIR" ; then
+ pglib_parts_missing="LIBRARY"
+ fi
+
+ if test -z "$PG_INCDIR" -o -z "$PG_LIBDIR" ; then
+ cat <<EOS
+ Could not find the libpq $pglib_parts_missing files.
+ These are required by the libpqxx C++ library, which is used by
+ Kexi's PostgreSQL drivers.
+
+ The PostgreSQL C-API usually ship with PostgreSQL, but if you've
+ installed from a distros package then these files may be part of
+ a package called postgresql-devel or libpq-devel"
+
+EOS
+ fi
+
+# LIBPQXX messages
+ if test -z "$PQXX_INCDIR"; then
+ echo " + The PostgreSQL C++ API (libpqxx) headers were not found."
+ fi
+
+ if test -z "$PQXX_LIBDIR"; then
+ echo " + The PostgreSQL C++ API (libpqxx) shared libraries were not found."
+ fi
+
+ if test -z "$PQXX_INCDIR" -a -z "$PQXX_LIBDIR" ; then
+ pqxx_parts_missing="HEADER or the libpqxx LIBRARY"
+ elif test -z "$PQXX_INCDIR" ; then
+ pqxx_parts_missing="HEADER"
+ elif test -z "$PQXX_LIBDIR" ; then
+ pqxx_parts_missing="LIBRARY"
+ fi
+
+ if test -z "$PQXX_INCDIR" -o -z "$PQXX_LIBDIR" ; then
+ cat <<EOS
+ Could not find the libpqxx $pqxx_parts_missing files.
+ These are required by Kexi's PostgreSQL drivers.
+
+ Note: Kexi requires the SHARED libpqxx.so library files.
+ If you build pqxx library on your own, don't forget to use the
+ --enable-shared option when you run libpqxx's configure script.
+ This is necessary to compile the SHARED .so library, and
+ not the STATIC libpqxx.a.
+
+ The PostgreSQL C++ API can be downloaded from pqxx.tk or
+ http://gborg.postgresql.org/project/libpqxx/projdisplay.php
+ Grab the latest version (>=2)
+
+EOS
+ fi
+
+# SUMMARY messages
+ cat <<EOS
+ These warnings are not critical, but without installing the files
+ listed above Kexi will be compiled without PostgreSQL support.
+
+ If you want PostgreSQL support in Kexi, you need to install the files
+ listed above, then run this configure script again, and finally run
+ make; make install. If you don't, simply run make; make install now.
+EOS
+
+ all_tests=bad
+ echo "----------------------------------------------------------------------"
+fi
diff --git a/kexi/kexidb/drivers/configure.in.in b/kexi/kexidb/drivers/configure.in.in
new file mode 100644
index 000000000..01d426b6d
--- /dev/null
+++ b/kexi/kexidb/drivers/configure.in.in
@@ -0,0 +1,244 @@
+dnl ========================================
+dnl checks for MySQL
+dnl taken form KDEDB
+dnl ========================================
+
+AC_ARG_ENABLE(mysql,
+ AC_HELP_STRING([--enable-mysql],[build MySQL-plugin [default=yes]]),
+ mysql_plugin=$enableval, mysql_plugin=yes)
+
+if test "x$mysql_plugin" = "xyes"; then
+ compile_mysql_plugin="yes"
+else
+ compile_mysql_plugin="no"
+fi
+
+AC_ARG_WITH(mysql_includes,
+AC_HELP_STRING([--with-mysql-includes=DIR],[use MySQL-includes installed in this directory]),
+[
+ ac_mysql_incdir=$withval
+], ac_mysql_incdir=
+)
+
+AC_ARG_WITH(mysql_libraries,
+AC_HELP_STRING([--with-mysql-libraries=DIR],[use MySQL-libs installed in this directory ]),
+[
+ ac_mysql_libdir=$withval
+], ac_mysql_libdir=
+)
+
+dnl ==============================================
+dnl check whether MySQL should be compiled
+dnl and where headers and libraries are installed
+dnl if present compile mysql-plugin
+dnl ==============================================
+
+AC_MSG_CHECKING([for MySQL])
+
+if test "$compile_mysql_plugin" = "yes"; then
+ if test -n "$ac_mysql_incdir" -o -n "$ac_mysql_libdir"; then
+dnl *** Configure arguments for includes or libs given ***
+dnl *** and MySQL not explicitly disabled. ***
+dnl *** Check that the paths given to configure are valid ***
+ AC_MSG_CHECKING([for MySQL headers])
+ mysql_incdirs="$ac_mysql_incdir /usr/local/include /usr/include"
+ AC_FIND_FILE(mysql/mysql.h, $mysql_incdirs, mysql_incdir)
+ if test -r $mysql_incdir/mysql/mysql.h; then
+ MYSQL_INC=$mysql_incdir
+ AC_MSG_RESULT([$MYSQL_INC])
+ AC_SUBST(MYSQL_INC)
+ else
+ compile_mysql_plugin="no"
+ AC_MSG_RESULT([not found])
+ fi
+
+ AC_MSG_CHECKING([for MySQL libraries])
+ mysql_libdirs="$ac_mysql_libdir /usr/local/lib$kdelibsuff /usr/lib$kdelibsuff"
+ AC_FIND_FILE(mysql/libmysqlclient.so, $mysql_libdirs, mysql_libdir)
+ if test -r $mysql_libdir/mysql/libmysqlclient.so; then
+ MYSQL_LIBS=$mysql_libdir
+ AC_MSG_RESULT([$MYSQL_LIBS])
+ AC_SUBST(MYSQL_LIBS)
+ else
+ compile_mysql_plugin="no"
+ AC_MSG_RESULT([not found])
+ fi
+ else
+dnl *** No configure arguments for includes or libs given ***
+dnl *** and MySQL not explicitly disabled. ***
+ KDE_FIND_PATH(mysql_config, MYSQL_CONFIG,
+ [${prefix}/bin ${exec_prefix}/bin /usr/local/bin /usr/bin ], [
+ AC_MSG_RESULT([not found])
+ ])
+
+ if test -n "$MYSQL_CONFIG"; then
+ mysql_incdir=`$MYSQL_CONFIG --cflags| $SED -e "s,-I,,g" | cut -d " " -f 1`
+ mysql_libdir=`$MYSQL_CONFIG --libs| $SED -e "s,',,g"`
+ MYSQL_INC=$mysql_incdir
+ MYSQL_LIBS=$mysql_libdir
+ AC_SUBST(MYSQL_INC)
+ AC_SUBST(MYSQL_LIBS)
+ compile_mypsql_plugin="yes"
+ AC_MSG_RESULT([headers $mysql_incdir, libraries $mysql_libdir])
+ else
+ compile_mysql_plugin="no"
+ fi
+ fi
+else
+dnl *** MySQL plugin explicitly disabled. ***
+dnl *** Show that we are doing as requested. ***
+ AC_MSG_NOTICE([Not attempting to configure MySQL as requested])
+fi
+
+AM_CONDITIONAL(compile_mysql_plugin, test "$compile_mysql_plugin" = "yes")
+
+dnl ========================================
+dnl Checks for PostgreSQL
+dnl ========================================
+
+dnl ========================================
+dnl libpq
+dnl Add configure-args
+dnl ========================================
+
+dnl Assume we're building until something fails, unless explicitly disabled
+AC_ARG_ENABLE(pgsql,
+AC_HELP_STRING([--enable-pgsql],[build PostgreSQL-plugin [default=yes]]),
+ pgsql_plugin=$enableval, pgsql_plugin=yes)
+
+if test "x$pgsql_plugin" = "xyes"; then
+ compile_pgsql_plugin="yes"
+else
+ compile_pgsql_plugin="no"
+fi
+
+AC_ARG_WITH(pgsql-includes,
+AC_HELP_STRING([--with-pgsql-includes=DIR],[use PostgreSQL(libpq)-includes installed in this directory ]),
+[
+ ac_pgsql_incdir=$withval
+], ac_pgsql_incdir=
+)
+
+AC_ARG_WITH(pgsql-libraries,
+AC_HELP_STRING([--with-pgsql-libraries=DIR],[use PostgreSQL(libpq)-libraries installed in this directory ]),
+[
+ ac_pgsql_libdir=$withval
+], ac_pgsql_libdir=
+)
+
+
+dnl ========================================
+dnl header/library directories
+dnl ========================================
+
+if test "$compile_pgsql_plugin" = "yes"; then
+ if test -n "$ac_pgsql_incdir" -o -n "$ac_pgsql_libdir"; then
+dnl *** Configure arguments for includes or libs given ***
+dnl *** and PostgreSQL not explicitly disabled. ***
+dnl *** Check that the paths given to configure are valid ***
+ AC_MSG_CHECKING([for PostgreSQL C API headers])
+ pgsql_incdirs="$ac_pgsql_incdir /usr/local/include /usr/include"
+ AC_FIND_FILE(libpq-fe.h, $pgsql_incdirs, pgsql_incdir)
+ if test -r $pgsql_incdir/libpq-fe.h; then
+ PG_INCDIR=$pgsql_incdir
+ AC_MSG_RESULT([$PG_INCDIR])
+ AC_SUBST(PG_INCDIR)
+ else
+ compile_pgsql_plugin="no"
+ AC_MSG_RESULT([not found])
+ fi
+
+ AC_MSG_CHECKING([for PostgreSQL C API libraries])
+ pgsql_libdirs="$ac_pgsql_libdir /usr/local/lib$kdelibsuff /usr/lib$kdelibsuff"
+ AC_FIND_FILE(libpq.so, $pgsql_libdirs, pgsql_libdir)
+ if test -r $pgsql_libdir/libpq.so; then
+ PG_LIBDIR=$pgsql_libdir
+ AC_MSG_RESULT([$PG_LIBDIR])
+ AC_SUBST(PG_LIBDIR)
+ else
+ compile_pgsql_plugin="no"
+ AC_MSG_RESULT([not found])
+ fi
+ else
+dnl *** No configure arguments for includes or libs given ***
+dnl *** and PostgreSQL not explicitly disabled. ***
+ KDE_FIND_PATH(pg_config, PG_CONFIG,
+ [${prefix}/bin ${exec_prefix}/bin /usr/local/bin /usr/bin ], [
+ AC_MSG_RESULT([not found])
+ ])
+
+ if test -n "$PG_CONFIG"; then
+ pgsql_incdir=`$PG_CONFIG --includedir`
+ pgsql_libdir=`$PG_CONFIG --libdir`
+ PG_INCDIR=$pgsql_incdir
+ PG_LIBDIR=$pgsql_libdir
+ AC_SUBST(PG_LIBDIR)
+ compile_pgsql_plugin="yes"
+ AC_MSG_RESULT([headers $pgsql_incdir, libraries $pgsql_libdir])
+ else
+ compile_pgsql_plugin="no"
+ fi
+ fi
+else
+dnl *** PostgreSQL plugin explicitly disabled. ***
+dnl *** Show that we are doing as requested. ***
+ AC_MSG_NOTICE([Not attempting to configure PostgreSQL as requested])
+fi
+
+AM_CONDITIONAL(compile_pgsql_plugin, test "$compile_pgsql_plugin" = "yes")
+
+
+dnl ========================================
+dnl libpqxx checks
+dnl ========================================
+
+AC_ARG_WITH(pqxx-includes,
+AC_HELP_STRING([--with-pqxx-includes=DIR],[use PostgreSQL(libpqxx)-includes installed in this directory ]),
+[
+ ac_pqxx_incdir=$withval
+], ac_pqxx_incdir=
+)
+
+AC_ARG_WITH(pqxx-libraries,
+AC_HELP_STRING([--with-pqxx-libraries=DIR],[use PostgreSQL(libpqxx)-libraries installed in this directory ]),
+[
+ ac_pqxx_libdir=$withval
+], ac_pqxx_libdir=
+)
+
+
+dnl ========================================
+dnl libpqxx headers
+dnl ========================================
+if test "$compile_pgsql_plugin" = "yes"; then
+ AC_MSG_CHECKING([for PostgreSQL C++ includes])
+ pqxx_incdirs="$ac_pqxx_incdir /usr/local/include /usr/include"
+ AC_FIND_FILE(pqxx/pqxx, $pqxx_incdirs, pqxx_incdir)
+ if test -r $pqxx_incdir/pqxx/pqxx; then
+ PQXX_INCDIR=$pqxx_incdir
+ AC_MSG_RESULT([$PQXX_INCDIR])
+ AC_SUBST(PQXX_INCDIR)
+ else
+ compile_pgsql_plugin="no"
+ AC_MSG_RESULT([not found])
+ fi
+fi
+
+dnl ========================================
+dnl libpqxx libraries
+dnl ========================================
+if test "$compile_pgsql_plugin" = "yes"; then
+ AC_MSG_CHECKING([for PostgreSQL C++ libraries])
+ pqxx_libdirs="$ac_pqxx_libdir /usr/local/lib$kdelibsuff /usr/lib$kdelibsuff"
+ AC_FIND_FILE(libpqxx.so, $pqxx_libdirs, pqxx_libdir)
+ if test -r $pqxx_libdir/libpqxx.so; then
+ PQXX_LIBDIR=$pqxx_libdir
+ AC_MSG_RESULT([$PQXX_LIBDIR])
+ AC_SUBST(PQXX_LIBDIR)
+ else
+ compile_pgsql_plugin="no"
+ AC_MSG_RESULT([not found])
+ fi
+fi
+
+AM_CONDITIONAL(compile_pgsql_plugin, test "$compile_pgsql_plugin" = "yes")
diff --git a/kexi/kexidb/drivers/drivers.pro b/kexi/kexidb/drivers/drivers.pro
new file mode 100644
index 000000000..1c8b5c344
--- /dev/null
+++ b/kexi/kexidb/drivers/drivers.pro
@@ -0,0 +1,7 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+sqlite2 \
+sqlite \
+mySQL \
+pqxx
diff --git a/kexi/kexidb/drivers/mySQL/Makefile.am b/kexi/kexidb/drivers/mySQL/Makefile.am
new file mode 100644
index 000000000..dca6c26e3
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/Makefile.am
@@ -0,0 +1,33 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexidb_mysqldriver.la
+
+INCLUDES = -I$(MYSQL_INC) -I$(srcdir)/../../.. \
+ -I$(srcdir)/../.. \
+ -I$(top_srcdir)/kexi $(all_includes)
+
+kexidb_mysqldriver_la_METASOURCES = AUTO
+
+kexidb_mysqldriver_la_SOURCES = \
+ mysqldriver.cpp \
+ mysqlconnection.cpp \
+ mysqlconnection_p.cpp \
+ mysqlcursor.cpp \
+ mysqlkeywords.cpp \
+ mysqlpreparedstatement.cpp
+
+kexidb_mysqldriver_la_LIBADD = $(LIB_KPARTS) \
+ $(LIB_QT) \
+ $(MYSQL_LIBS) \
+ -lmysqlclient \
+ ../../libkexidb.la
+
+kexidb_mysqldriver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) -no-undefined
+
+
+kde_services_DATA = kexidb_mysqldriver.desktop
+
+
+KDE_CXXFLAGS += -DKEXIDB_MYSQL_DRIVER_EXPORT= -D__KEXIDB__= \
+ -include $(top_srcdir)/kexi/kexidb/global.h
+
diff --git a/kexi/kexidb/drivers/mySQL/kexidb_mysqldriver.desktop b/kexi/kexidb/drivers/mySQL/kexidb_mysqldriver.desktop
new file mode 100644
index 000000000..066782f12
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/kexidb_mysqldriver.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=MySQL
+Name[ne]=मेरो एसक्यूएल
+Name[sk]=mySQL
+X-KDE-Library=kexidb_mysqldriver
+ServiceTypes=Kexi/DBDriver
+Type=Service
+InitialPreference=8
+X-Kexi-DriverName=MySQL
+X-Kexi-DriverType=Network
+X-Kexi-KexiDBVersion=1.8
diff --git a/kexi/kexidb/drivers/mySQL/mySQL.pro b/kexi/kexidb/drivers/mySQL/mySQL.pro
new file mode 100644
index 000000000..cc6eddd69
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mySQL.pro
@@ -0,0 +1,28 @@
+include( ../common.pro )
+
+INCLUDEPATH += $(MYSQL_INC) $(MYSQL_INC)/mysql
+
+contains(CONFIG,debug) {
+ win32:LIBS += $(MYSQL_LIB)/debug/libmysql.lib
+ win32:QMAKE_LFLAGS += /NODEFAULTLIB:LIBCMTD.LIB
+}
+!contains(CONFIG,debug) {
+# win32:LIBS += $(MYSQL_LIB)/opt/mysqlclient.lib
+ win32:LIBS += $(MYSQL_LIB)/opt/libmysql.lib
+# win32:QMAKE_LFLAGS += /NODEFAULTLIB:MSVCRT.LIB
+}
+
+TARGET = kexidb_mysqldriver$$KDELIBDEBUG
+
+system( bash kmoc )
+
+SOURCES = \
+mysqlconnection_p.cpp \
+mysqlconnection.cpp \
+mysqldriver.cpp \
+mysqlcursor.cpp \
+mysqlkeywords.cpp \
+mysqlpreparedstatement.cpp
+
+HEADERS =
+
diff --git a/kexi/kexidb/drivers/mySQL/mysqlconnection.cpp b/kexi/kexidb/drivers/mySQL/mysqlconnection.cpp
new file mode 100644
index 000000000..7417b6980
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlconnection.cpp
@@ -0,0 +1,208 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qvariant.h>
+#include <qfile.h>
+#include <qdict.h>
+#include <qregexp.h>
+
+#include <kgenericfactory.h>
+#include <kdebug.h>
+
+#include "mysqldriver.h"
+#include "mysqlconnection.h"
+#include "mysqlconnection_p.h"
+#include "mysqlcursor.h"
+#include "mysqlpreparedstatement.h"
+#include <kexidb/error.h>
+
+
+using namespace KexiDB;
+
+//--------------------------------------------------------------------------
+
+MySqlConnection::MySqlConnection( Driver *driver, ConnectionData &conn_data )
+ :Connection(driver,conn_data)
+ ,d(new MySqlConnectionInternal(this))
+{
+}
+
+MySqlConnection::~MySqlConnection() {
+ destroy();
+}
+
+bool MySqlConnection::drv_connect(KexiDB::ServerVersionInfo& version)
+{
+ const bool ok = d->db_connect(*data());
+ if (!ok)
+ return false;
+
+ version.string = mysql_get_host_info(d->mysql);
+
+ //retrieve server version info
+#if 0 //this only works for client version >= 4.1 :(
+ unsigned long v = mysql_get_server_version(d->mysql);
+ // v - a number that represents the MySQL server version in this format
+ // = major_version*10000 + minor_version *100 + sub_version
+ version.major = v/10000;
+ version.minor = (v - version.major*10000)/100;
+ version.release = v - version.major*10000 - version.minor*100;
+#else //better way to get the version info: use 'version' built-in variable:
+//! @todo this is hardcoded for now; define api for retrieving variables and use this API...
+ QString versionString;
+ const tristate res = querySingleString("SELECT @@version", versionString, /*column*/0, false /*!addLimitTo1*/);
+ QRegExp versionRe("(\\d+)\\.(\\d+)\\.(\\d+)");
+ if (res==true && versionRe.exactMatch(versionString)) { // (if querySingleString failed, the version will be 0.0.0...
+ version.major = versionRe.cap(1).toInt();
+ version.minor = versionRe.cap(2).toInt();
+ version.release = versionRe.cap(3).toInt();
+ }
+#endif
+ return true;
+}
+
+bool MySqlConnection::drv_disconnect() {
+ return d->db_disconnect();
+}
+
+Cursor* MySqlConnection::prepareQuery(const QString& statement, uint cursor_options) {
+ return new MySqlCursor(this,statement,cursor_options);
+}
+
+Cursor* MySqlConnection::prepareQuery( QuerySchema& query, uint cursor_options ) {
+ return new MySqlCursor( this, query, cursor_options );
+}
+
+bool MySqlConnection::drv_getDatabasesList( QStringList &list ) {
+ KexiDBDrvDbg << "MySqlConnection::drv_getDatabasesList()" << endl;
+ list.clear();
+ MYSQL_RES *res;
+
+ if((res=mysql_list_dbs(d->mysql,0)) != 0) {
+ MYSQL_ROW row;
+ while ( (row = mysql_fetch_row(res))!=0) {
+ list<<QString(row[0]);
+ }
+ mysql_free_result(res);
+ return true;
+ }
+
+ d->storeResult();
+// setError(ERR_DB_SPECIFIC,mysql_error(d->mysql));
+ return false;
+}
+
+bool MySqlConnection::drv_createDatabase( const QString &dbName) {
+ KexiDBDrvDbg << "MySqlConnection::drv_createDatabase: " << dbName << endl;
+ // mysql_create_db deprecated, use SQL here.
+ if (drv_executeSQL("CREATE DATABASE " + (dbName)))
+ return true;
+ d->storeResult();
+ return false;
+}
+
+bool MySqlConnection::drv_useDatabase(const QString &dbName, bool *cancelled, MessageHandler* msgHandler)
+{
+ Q_UNUSED(cancelled);
+ Q_UNUSED(msgHandler);
+//TODO is here escaping needed?
+ return d->useDatabase(dbName);
+}
+
+bool MySqlConnection::drv_closeDatabase() {
+//TODO free resources
+//As far as I know, mysql doesn't support that
+ return true;
+}
+
+bool MySqlConnection::drv_dropDatabase( const QString &dbName) {
+//TODO is here escaping needed
+ return drv_executeSQL("drop database "+dbName);
+}
+
+bool MySqlConnection::drv_executeSQL( const QString& statement ) {
+ return d->executeSQL(statement);
+}
+
+Q_ULLONG MySqlConnection::drv_lastInsertRowID()
+{
+ //! @todo
+ return (Q_ULLONG)mysql_insert_id(d->mysql);
+}
+
+int MySqlConnection::serverResult()
+{
+ return d->res;
+}
+
+QString MySqlConnection::serverResultName()
+{
+ return QString::null;
+}
+
+void MySqlConnection::drv_clearServerResult()
+{
+ if (!d)
+ return;
+ d->res = 0;
+}
+
+QString MySqlConnection::serverErrorMsg()
+{
+ return d->errmsg;
+}
+
+bool MySqlConnection::drv_containsTable( const QString &tableName )
+{
+ bool success;
+ return resultExists(QString("show tables like %1")
+ .arg(driver()->escapeString(tableName)), success) && success;
+}
+
+bool MySqlConnection::drv_getTablesList( QStringList &list )
+{
+ KexiDB::Cursor *cursor;
+ m_sql = "show tables";
+ if (!(cursor = executeQuery( m_sql ))) {
+ KexiDBDbg << "Connection::drv_getTablesList(): !executeQuery()" << endl;
+ return false;
+ }
+ list.clear();
+ cursor->moveFirst();
+ while (!cursor->eof() && !cursor->error()) {
+ list += cursor->value(0).toString();
+ cursor->moveNext();
+ }
+ if (cursor->error()) {
+ deleteCursor(cursor);
+ return false;
+ }
+ return deleteCursor(cursor);
+}
+
+PreparedStatement::Ptr MySqlConnection::prepareStatement(PreparedStatement::StatementType type,
+ FieldList& fields)
+{
+ return new MySqlPreparedStatement(type, *d, fields);
+}
+
+#include "mysqlconnection.moc"
diff --git a/kexi/kexidb/drivers/mySQL/mysqlconnection.h b/kexi/kexidb/drivers/mySQL/mysqlconnection.h
new file mode 100644
index 000000000..bafb889de
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlconnection.h
@@ -0,0 +1,87 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef MYSQLCONNECTION_H
+#define MYSQLCONNECTION_H
+
+#include <qstringlist.h>
+
+#include <kexidb/connection.h>
+#include "mysqlcursor.h"
+#include <qdict.h>
+
+namespace KexiDB {
+
+class MySqlConnectionInternal;
+
+/*!
+ * Should override kexiDB/kexiDB
+ * all other members are done by the
+ * base class.
+ */
+class MySqlConnection : public Connection
+{
+ Q_OBJECT
+
+ public:
+ virtual ~MySqlConnection();
+
+ virtual Cursor* prepareQuery( const QString& statement = QString::null, uint cursor_options = 0 );
+ virtual Cursor* prepareQuery( QuerySchema& query, uint cursor_options = 0 );
+
+ virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type,
+ FieldList& fields);
+
+ protected:
+
+ /*! Used by driver */
+ MySqlConnection( Driver *driver, ConnectionData &conn_data );
+
+ virtual bool drv_connect(KexiDB::ServerVersionInfo& version);
+ virtual bool drv_disconnect();
+ virtual bool drv_getDatabasesList( QStringList &list );
+ virtual bool drv_createDatabase( const QString &dbName = QString::null );
+ virtual bool drv_useDatabase( const QString &dbName = QString::null, bool *cancelled = 0,
+ MessageHandler* msgHandler = 0 );
+ virtual bool drv_closeDatabase();
+ virtual bool drv_dropDatabase( const QString &dbName = QString::null );
+ virtual bool drv_executeSQL( const QString& statement );
+ virtual Q_ULLONG drv_lastInsertRowID();
+
+ virtual int serverResult();
+ virtual QString serverResultName();
+ virtual QString serverErrorMsg();
+ virtual void drv_clearServerResult();
+
+//TODO: move this somewhere to low level class (MIGRATION?)
+ virtual bool drv_getTablesList( QStringList &list );
+//TODO: move this somewhere to low level class (MIGRATION?)
+ virtual bool drv_containsTable( const QString &tableName );
+
+ MySqlConnectionInternal* d;
+
+ friend class MySqlDriver;
+ friend class MySqlCursor;
+};
+
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/mySQL/mysqlconnection_p.cpp b/kexi/kexidb/drivers/mySQL/mysqlconnection_p.cpp
new file mode 100644
index 000000000..9797ca967
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlconnection_p.cpp
@@ -0,0 +1,175 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2004 Martin Ellis <martin.ellis@kdemail.net>
+
+This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qcstring.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qfile.h>
+
+#include <kdebug.h>
+
+#include "mysqlconnection_p.h"
+
+#include <kexidb/connectiondata.h>
+
+#ifdef MYSQLMIGRATE_H
+#define NAMESPACE KexiMigration
+#else
+#define NAMESPACE KexiDB
+#endif
+
+using namespace NAMESPACE;
+
+/* ************************************************************************** */
+MySqlConnectionInternal::MySqlConnectionInternal(KexiDB::Connection* connection)
+ : ConnectionInternal(connection)
+ , mysql(0)
+ , mysql_owned(true)
+ , res(0)
+{
+}
+
+MySqlConnectionInternal::~MySqlConnectionInternal()
+{
+ if (mysql_owned && mysql) {
+ mysql_close(mysql);
+ mysql = 0;
+ }
+}
+
+void MySqlConnectionInternal::storeResult()
+{
+ res = mysql_errno(mysql);
+ errmsg = mysql_error(mysql);
+}
+
+/* ************************************************************************** */
+/*! Connects to the MySQL server on host as the given user using the specified
+ password. If host is "localhost", then a socket on the local file system
+ can be specified to connect to the server (several defaults will be tried if
+ none is specified). If the server is on a remote machine, then a port is
+ the port that the remote server is listening on.
+ */
+//bool MySqlConnectionInternal::db_connect(QCString host, QCString user,
+// QCString password, unsigned short int port, QString socket)
+bool MySqlConnectionInternal::db_connect(const KexiDB::ConnectionData& data)
+{
+ if (!(mysql = mysql_init(mysql)))
+ return false;
+
+ KexiDBDrvDbg << "MySqlConnectionInternal::connect()" << endl;
+ QCString localSocket;
+ QString hostName = data.hostName;
+ if (hostName.isEmpty() || hostName.lower()=="localhost") {
+ if (data.useLocalSocketFile) {
+ if (data.localSocketFileName.isEmpty()) {
+ //! @todo move the list of default sockets to a generic method
+ QStringList sockets;
+ #ifndef Q_WS_WIN
+ sockets.append("/var/lib/mysql/mysql.sock");
+ sockets.append("/var/run/mysqld/mysqld.sock");
+ sockets.append("/tmp/mysql.sock");
+
+ for(QStringList::ConstIterator it = sockets.constBegin(); it != sockets.constEnd(); it++)
+ {
+ if(QFile(*it).exists()) {
+ localSocket = ((QString)(*it)).local8Bit();
+ break;
+ }
+ }
+ #endif
+ }
+ else
+ localSocket = QFile::encodeName(data.localSocketFileName);
+ }
+ else {
+ //we're not using local socket
+ hostName = "127.0.0.1"; //this will force mysql to connect to localhost
+ }
+ }
+
+/*! @todo is latin1() encoding here valid? what about using UTF for passwords? */
+ const char *pwd = data.password.isNull() ? 0 : data.password.latin1();
+ mysql_real_connect(mysql, hostName.latin1(), data.userName.latin1(),
+ pwd, 0, data.port, localSocket, 0);
+ if(mysql_errno(mysql) == 0)
+ return true;
+
+ storeResult(); //store error msg, if any - can be destroyed after disconnect()
+ db_disconnect();
+// setError(ERR_DB_SPECIFIC,err);
+ return false;
+}
+
+/*! Disconnects from the database.
+ */
+bool MySqlConnectionInternal::db_disconnect()
+{
+ mysql_close(mysql);
+ mysql = 0;
+ KexiDBDrvDbg << "MySqlConnection::disconnect()" << endl;
+ return true;
+}
+
+/* ************************************************************************** */
+/*! Selects dbName as the active database so it can be used.
+ */
+bool MySqlConnectionInternal::useDatabase(const QString &dbName) {
+//TODO is here escaping needed?
+ return executeSQL("USE " + dbName);
+}
+
+/*! Executes the given SQL statement on the server.
+ */
+bool MySqlConnectionInternal::executeSQL(const QString& statement) {
+// KexiDBDrvDbg << "MySqlConnectionInternal::executeSQL: "
+// << statement << endl;
+ QCString queryStr=statement.utf8();
+ const char *query=queryStr;
+ if(mysql_real_query(mysql, query, strlen(query)) == 0)
+ {
+ return true;
+ }
+
+ storeResult();
+// setError(ERR_DB_SPECIFIC,mysql_error(m_mysql));
+ return false;
+}
+
+QString MySqlConnectionInternal::escapeIdentifier(const QString& str) const {
+ return QString(str).replace('`', "'");
+}
+
+//--------------------------------------
+
+MySqlCursorData::MySqlCursorData(KexiDB::Connection* connection)
+: MySqlConnectionInternal(connection)
+, mysqlres(0)
+, mysqlrow(0)
+, lengths(0)
+, numRows(0)
+{
+ mysql_owned = false;
+}
+
+MySqlCursorData::~MySqlCursorData()
+{
+}
+
diff --git a/kexi/kexidb/drivers/mySQL/mysqlconnection_p.h b/kexi/kexidb/drivers/mySQL/mysqlconnection_p.h
new file mode 100644
index 000000000..5bb77487d
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlconnection_p.h
@@ -0,0 +1,101 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Martin Ellis <martin.ellis@kdemail.net>
+
+This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_MYSQLCLIENT_P_H
+#define KEXIDB_MYSQLCLIENT_P_H
+
+#include <kexidb/connection_p.h>
+
+#ifdef Q_WS_WIN
+#include <my_global.h>
+#endif
+#include <mysql_version.h>
+#include <mysql.h>
+
+typedef struct st_mysql MYSQL;
+#undef bool
+
+class QCString;
+class QString;
+
+#ifdef MYSQLMIGRATE_H
+#define NAMESPACE KexiMigration
+#else
+#define NAMESPACE KexiDB
+#endif
+
+namespace KexiDB {
+ class ConnectionData;
+}
+
+namespace NAMESPACE {
+
+//! Internal MySQL connection data.
+/*! Provides a low-level API for accessing MySQL databases, that can
+ be shared by any module that needs direct access to the underlying
+ database. Used by the KexiDB and KexiMigration drivers.
+ */
+class MySqlConnectionInternal : public KexiDB::ConnectionInternal
+{
+ public:
+ MySqlConnectionInternal(KexiDB::Connection* connection);
+ virtual ~MySqlConnectionInternal();
+
+ //! Connects to a MySQL database
+ bool db_connect(const KexiDB::ConnectionData& data);
+
+ //! Disconnects from the database
+ bool db_disconnect();
+
+ //! Selects a database that is about to be used
+ bool useDatabase(const QString &dbName = QString::null);
+
+ //! Execute SQL statement on the database
+ bool executeSQL( const QString& statement );
+
+ //! Stores last operation's result
+ virtual void storeResult();
+
+ //! Escapes a table, database or column name
+ QString escapeIdentifier(const QString& str) const;
+
+ MYSQL *mysql;
+ bool mysql_owned; //!< true if mysql pointer should be freed on destruction
+ QString errmsg; //!< server-specific message of last operation
+ int res; //!< result code of last operation on server
+};
+
+
+//! Internal MySQL cursor data.
+/*! Provides a low-level abstraction for iterating over MySql result sets. */
+class MySqlCursorData : public MySqlConnectionInternal
+{
+ public:
+ MySqlCursorData(KexiDB::Connection* connection);
+ virtual ~MySqlCursorData();
+
+ MYSQL_RES *mysqlres;
+ MYSQL_ROW mysqlrow;
+ unsigned long *lengths;
+ unsigned long numRows;
+};
+
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/mySQL/mysqlcursor.cpp b/kexi/kexidb/drivers/mySQL/mysqlcursor.cpp
new file mode 100644
index 000000000..7897fa972
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlcursor.cpp
@@ -0,0 +1,218 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "mysqlcursor.h"
+#include "mysqlconnection.h"
+#include "mysqlconnection_p.h"
+#include <kexidb/error.h>
+#include <kexidb/utils.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <limits.h>
+
+#define BOOL bool
+
+using namespace KexiDB;
+
+MySqlCursor::MySqlCursor(KexiDB::Connection* conn, const QString& statement, uint cursor_options)
+ : Cursor(conn,statement,cursor_options)
+ , d( new MySqlCursorData(conn) )
+{
+ m_options |= Buffered;
+ d->mysql = static_cast<MySqlConnection*>(conn)->d->mysql;
+// KexiDBDrvDbg << "MySqlCursor: constructor for query statement" << endl;
+}
+
+MySqlCursor::MySqlCursor(Connection* conn, QuerySchema& query, uint options )
+ : Cursor( conn, query, options )
+ , d( new MySqlCursorData(conn) )
+{
+ m_options |= Buffered;
+ d->mysql = static_cast<MySqlConnection*>(conn)->d->mysql;
+// KexiDBDrvDbg << "MySqlCursor: constructor for query statement" << endl;
+}
+
+MySqlCursor::~MySqlCursor() {
+ close();
+}
+
+bool MySqlCursor::drv_open() {
+// KexiDBDrvDbg << "MySqlCursor::drv_open:" << m_sql << endl;
+ // This can't be right? mysql_real_query takes a length in order that
+ // queries can have binary data - but strlen does not allow binary data.
+ if(mysql_real_query(d->mysql, m_sql.utf8(), strlen(m_sql.utf8())) == 0) {
+ if(mysql_errno(d->mysql) == 0) {
+ d->mysqlres= mysql_store_result(d->mysql);
+ m_fieldCount=mysql_num_fields(d->mysqlres);
+ d->numRows=mysql_num_rows(d->mysqlres);
+ m_at=0;
+
+ m_opened=true;
+ m_records_in_buf = d->numRows;
+ m_buffering_completed = true;
+ m_afterLast=false;
+ return true;
+ }
+ }
+
+ setError(ERR_DB_SPECIFIC,QString::fromUtf8(mysql_error(d->mysql)));
+ return false;
+}
+
+bool MySqlCursor::drv_close() {
+ mysql_free_result(d->mysqlres);
+ d->mysqlres=0;
+ d->mysqlrow=0;
+//js: done in superclass: m_numFields=0;
+ d->lengths=0;
+ m_opened=false;
+ d->numRows=0;
+ return true;
+}
+
+/*bool MySqlCursor::drv_moveFirst() {
+ return true; //TODO
+}*/
+
+void MySqlCursor::drv_getNextRecord() {
+// KexiDBDrvDbg << "MySqlCursor::drv_getNextRecord" << endl;
+ if (at() < d->numRows && at() >=0) {
+ d->lengths=mysql_fetch_lengths(d->mysqlres);
+ m_result=FetchOK;
+ }
+ else if (at() >= d->numRows) {
+ m_result = FetchEnd;
+ }
+ else {
+ m_result = FetchError;
+ }
+}
+
+// This isn't going to work right now as it uses d->mysqlrow
+QVariant MySqlCursor::value(uint pos) {
+ if (!d->mysqlrow || pos>=m_fieldCount || d->mysqlrow[pos]==0)
+ return QVariant();
+
+ KexiDB::Field *f = (m_fieldsExpanded && pos<m_fieldsExpanded->count())
+ ? m_fieldsExpanded->at(pos)->field : 0;
+
+//! @todo js: use MYSQL_FIELD::type here!
+
+ return KexiDB::cstringToVariant(d->mysqlrow[pos], f, d->lengths[pos]);
+/* moved to cstringToVariant()
+ //from most to least frequently used types:
+ if (!f || f->isTextType())
+ return QVariant( QString::fromUtf8((const char*)d->mysqlrow[pos], d->lengths[pos]) );
+ else if (f->isIntegerType())
+//! @todo support BigInteger
+ return QVariant( QCString((const char*)d->mysqlrow[pos], d->lengths[pos]).toInt() );
+ else if (f->isFPNumericType())
+ return QVariant( QCString((const char*)d->mysqlrow[pos], d->lengths[pos]).toDouble() );
+
+ //default
+ return QVariant(QString::fromUtf8((const char*)d->mysqlrow[pos], d->lengths[pos]));*/
+}
+
+
+/* As with sqlite, the DB library returns all values (including numbers) as
+ strings. So just put that string in a QVariant and let KexiDB deal with it.
+ */
+void MySqlCursor::storeCurrentRow(RowData &data) const
+{
+// KexiDBDrvDbg << "MySqlCursor::storeCurrentRow: Position is " << (long)m_at<< endl;
+ if (d->numRows<=0)
+ return;
+
+//! @todo js: use MYSQL_FIELD::type here!
+//! see SQLiteCursor::storeCurrentRow()
+
+ data.resize(m_fieldCount);
+ const uint fieldsExpandedCount = m_fieldsExpanded ? m_fieldsExpanded->count() : UINT_MAX;
+ const uint realCount = QMIN(fieldsExpandedCount, m_fieldCount);
+ for( uint i=0; i<realCount; i++) {
+ Field *f = m_fieldsExpanded ? m_fieldsExpanded->at(i)->field : 0;
+ if (m_fieldsExpanded && !f)
+ continue;
+ data[i] = KexiDB::cstringToVariant(d->mysqlrow[i], f, d->lengths[i]);
+/* moved to cstringToVariant()
+ if (f && f->type()==Field::BLOB) {
+ QByteArray ba;
+ ba.duplicate(d->mysqlrow[i], d->lengths[i]);
+ data[i] = ba;
+ KexiDBDbg << data[i].toByteArray().size() << endl;
+ }
+//! @todo more types!
+//! @todo look at what type mysql declares!
+ else {
+ data[i] = QVariant(QString::fromUtf8((const char*)d->mysqlrow[i], d->lengths[i]));
+ }*/
+ }
+}
+
+void MySqlCursor::drv_appendCurrentRecordToBuffer() {
+}
+
+
+void MySqlCursor::drv_bufferMovePointerNext() {
+ d->mysqlrow=mysql_fetch_row(d->mysqlres);
+ d->lengths=mysql_fetch_lengths(d->mysqlres);
+}
+
+void MySqlCursor::drv_bufferMovePointerPrev() {
+ //MYSQL_ROW_OFFSET ro=mysql_row_tell(d->mysqlres);
+ mysql_data_seek(d->mysqlres,m_at-1);
+ d->mysqlrow=mysql_fetch_row(d->mysqlres);
+ d->lengths=mysql_fetch_lengths(d->mysqlres);
+}
+
+
+void MySqlCursor::drv_bufferMovePointerTo(Q_LLONG to) {
+ //MYSQL_ROW_OFFSET ro=mysql_row_tell(d->mysqlres);
+ mysql_data_seek(d->mysqlres, to);
+ d->mysqlrow=mysql_fetch_row(d->mysqlres);
+ d->lengths=mysql_fetch_lengths(d->mysqlres);
+}
+
+const char** MySqlCursor::rowData() const {
+ //! @todo
+ return 0;
+}
+
+int MySqlCursor::serverResult()
+{
+ return d->res;
+}
+
+QString MySqlCursor::serverResultName()
+{
+ return QString::null;
+}
+
+void MySqlCursor::drv_clearServerResult()
+{
+ if (!d)
+ return;
+ d->res = 0;
+}
+
+QString MySqlCursor::serverErrorMsg()
+{
+ return d->errmsg;
+}
diff --git a/kexi/kexidb/drivers/mySQL/mysqlcursor.h b/kexi/kexidb/drivers/mySQL/mysqlcursor.h
new file mode 100644
index 000000000..09ace22b2
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlcursor.h
@@ -0,0 +1,68 @@
+/* This file is part of the KDE project
+Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
+
+This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _MYSQLCURSOR_H_
+#define _MYSQLCURSOR_H_
+
+#include <kexidb/cursor.h>
+#include <kexidb/connection.h>
+
+namespace KexiDB {
+
+class MySqlCursorData;
+
+class MySqlCursor: public Cursor {
+public:
+ MySqlCursor(Connection* conn, const QString& statement = QString::null, uint cursor_options = NoOptions );
+ MySqlCursor(Connection* conn, QuerySchema& query, uint options = NoOptions );
+ virtual ~MySqlCursor();
+ virtual bool drv_open();
+ virtual bool drv_close();
+// virtual bool drv_moveFirst();
+ virtual void drv_getNextRecord();
+ //virtual bool drv_getPrevRecord();
+ virtual QVariant value(uint);
+
+ virtual void drv_clearServerResult();
+ virtual void drv_appendCurrentRecordToBuffer();
+ virtual void drv_bufferMovePointerNext();
+ virtual void drv_bufferMovePointerPrev();
+ virtual void drv_bufferMovePointerTo(Q_LLONG to);
+ virtual const char** rowData() const;
+ virtual void storeCurrentRow(RowData &data) const;
+// virtual bool save(RowData& data, RowEditBuffer& buf);
+
+ virtual int serverResult();
+ virtual QString serverResultName();
+ virtual QString serverErrorMsg();
+
+protected:
+ QVariant pValue(uint pos) const;
+// MYSQL_RES *m_res;
+// MYSQL_ROW m_row;
+// MYSQL *my_conn;
+// unsigned long *m_lengths;
+//js: int m_numFields;
+// unsigned long m_numRows;
+ MySqlCursorData *d;
+};
+
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/mySQL/mysqldriver.cpp b/kexi/kexidb/drivers/mySQL/mysqldriver.cpp
new file mode 100644
index 000000000..c27681c0b
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqldriver.cpp
@@ -0,0 +1,212 @@
+/* This file is part of the KDE project
+Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+Daniel Molkentin <molkentin@kde.org>
+Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
+
+This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifdef Q_WS_WIN
+# include <mysql/config-win.h>
+#endif
+#include <mysql_version.h>
+#include <mysql.h>
+#define BOOL bool
+
+#include <qvariant.h>
+#include <qfile.h>
+#include <qdict.h>
+
+#include <kgenericfactory.h>
+#include <kdebug.h>
+
+#include "mysqldriver.h"
+#include "mysqlconnection.h"
+#include <kexidb/field.h>
+#include <kexidb/driver_p.h>
+#include <kexidb/utils.h>
+
+using namespace KexiDB;
+
+KEXIDB_DRIVER_INFO( MySqlDriver, mysql )
+
+/* TODO: Implement buffered/unbuffered, rather than buffer everything.
+ Each MYSQL connection can only handle at most one unbuffered cursor,
+ so MySqlConnection should keep count?
+ */
+
+/*!
+ * Constructor sets database features and
+ * maps the types in KexiDB::Field::Type to the MySQL types.
+ *
+ * See: http://dev.mysql.com/doc/mysql/en/Column_types.html
+ */
+MySqlDriver::MySqlDriver(QObject *parent, const char *name, const QStringList &args) : Driver(parent, name,args)
+{
+// KexiDBDrvDbg << "MySqlDriver::MySqlDriver()" << endl;
+
+ d->isFileDriver=false;
+ d->features=IgnoreTransactions | CursorForward;
+
+ beh->ROW_ID_FIELD_NAME="LAST_INSERT_ID()";
+ beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE=true;
+ beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY=false;
+ beh->USING_DATABASE_REQUIRED_TO_CONNECT=false;
+ beh->QUOTATION_MARKS_FOR_IDENTIFIER='`';
+ beh->SQL_KEYWORDS = keywords;
+ initSQLKeywords(331);
+
+ //predefined properties
+#if MYSQL_VERSION_ID < 40000
+ d->properties["client_library_version"] = MYSQL_SERVER_VERSION; //nothing better
+ d->properties["default_server_encoding"] = MYSQL_CHARSET; //nothing better
+#elif MYSQL_VERSION_ID < 50000
+//OK? d->properties["client_library_version"] = mysql_get_client_version();
+#endif
+
+ d->typeNames[Field::Byte]="TINYINT";
+ d->typeNames[Field::ShortInteger]="SMALLINT";
+ d->typeNames[Field::Integer]="INT";
+ d->typeNames[Field::BigInteger]="BIGINT";
+ // Can use BOOLEAN here, but BOOL has been in MySQL longer
+ d->typeNames[Field::Boolean]="BOOL";
+ d->typeNames[Field::Date]="DATE";
+ d->typeNames[Field::DateTime]="DATETIME";
+ d->typeNames[Field::Time]="TIME";
+ d->typeNames[Field::Float]="FLOAT";
+ d->typeNames[Field::Double]="DOUBLE";
+ d->typeNames[Field::Text]="VARCHAR";
+ d->typeNames[Field::LongText]="LONGTEXT";
+ d->typeNames[Field::BLOB]="BLOB";
+}
+
+MySqlDriver::~MySqlDriver()
+{
+}
+
+KexiDB::Connection*
+MySqlDriver::drv_createConnection( ConnectionData &conn_data )
+{
+ return new MySqlConnection( this, conn_data );
+}
+
+bool MySqlDriver::isSystemDatabaseName(const QString &n) const
+{
+ return n.lower()=="mysql" || Driver::isSystemObjectName(n);
+}
+
+bool MySqlDriver::drv_isSystemFieldName(const QString&) const {
+ return false;
+}
+
+QString MySqlDriver::escapeString(const QString& str) const
+{
+ //escape as in http://dev.mysql.com/doc/refman/5.0/en/string-syntax.html
+//! @todo support more characters, like %, _
+
+ const int old_length = str.length();
+ int i;
+ for ( i = 0; i < old_length; i++ ) { //anything to escape?
+ const unsigned int ch = str[i].unicode();
+ if (ch == '\\' || ch == '\'' || ch == '"' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\b' || ch == '\0')
+ break;
+ }
+ if (i >= old_length) { //no characters to escape
+ return QString::fromLatin1("'") + str + QString::fromLatin1("'");
+ }
+
+ QChar *new_string = new QChar[ old_length * 3 + 1 ]; // a worst case approximation
+//! @todo move new_string to Driver::m_new_string or so...
+ int new_length = 0;
+ new_string[new_length++] = '\''; //prepend '
+ for ( i = 0; i < old_length; i++, new_length++ ) {
+ const unsigned int ch = str[i].unicode();
+ if (ch == '\\') {
+ new_string[new_length++] = '\\';
+ new_string[new_length] = '\\';
+ }
+ else if (ch <= '\'') {//check for speedup
+ if (ch == '\'') {
+ new_string[new_length++] = '\\';
+ new_string[new_length] = '\'';
+ }
+ else if (ch == '"') {
+ new_string[new_length++] = '\\';
+ new_string[new_length] = '"';
+ }
+ else if (ch == '\n') {
+ new_string[new_length++] = '\\';
+ new_string[new_length] = 'n';
+ }
+ else if (ch == '\r') {
+ new_string[new_length++] = '\\';
+ new_string[new_length] = 'r';
+ }
+ else if (ch == '\t') {
+ new_string[new_length++] = '\\';
+ new_string[new_length] = 't';
+ }
+ else if (ch == '\b') {
+ new_string[new_length++] = '\\';
+ new_string[new_length] = 'b';
+ }
+ else if (ch == '\0') {
+ new_string[new_length++] = '\\';
+ new_string[new_length] = '0';
+ }
+ else
+ new_string[new_length] = str[i];
+ }
+ else
+ new_string[new_length] = str[i];
+ }
+
+ new_string[new_length++] = '\''; //append '
+ QString result(new_string, new_length);
+ delete [] new_string;
+ return result;
+}
+
+QString MySqlDriver::escapeBLOB(const QByteArray& array) const
+{
+ return KexiDB::escapeBLOB(array, KexiDB::BLOBEscape0xHex);
+}
+
+QCString MySqlDriver::escapeString(const QCString& str) const
+{
+//! @todo optimize using mysql_real_escape_string()?
+//! see http://dev.mysql.com/doc/refman/5.0/en/string-syntax.html
+
+ return QCString("'")+QCString(str)
+ .replace( '\\', "\\\\" )
+ .replace( '\'', "\\''" )
+ .replace( '"', "\\\"" )
+ + QCString("'");
+}
+
+/*! Add back-ticks to an identifier, and replace any back-ticks within
+ * the name with single quotes.
+ */
+QString MySqlDriver::drv_escapeIdentifier( const QString& str) const {
+ return QString(str).replace('`', "'");
+}
+
+QCString MySqlDriver::drv_escapeIdentifier( const QCString& str) const {
+ return QCString(str).replace('`', "'");
+}
+
+#include "mysqldriver.moc"
+
diff --git a/kexi/kexidb/drivers/mySQL/mysqldriver.h b/kexi/kexidb/drivers/mySQL/mysqldriver.h
new file mode 100644
index 000000000..8282e2154
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqldriver.h
@@ -0,0 +1,59 @@
+/* This file is part of the KDE project
+Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+Daniel Molkentin <molkentin@kde.org>
+Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
+
+This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef MYSQLDB_H
+#define MYSQLDB_H
+
+#include <kexidb/driver.h>
+
+namespace KexiDB {
+
+//! MySQL database driver.
+class MySqlDriver : public Driver
+{
+ Q_OBJECT
+ KEXIDB_DRIVER
+
+ public:
+ MySqlDriver(QObject *parent, const char *name, const QStringList &args=QStringList());
+ virtual ~MySqlDriver();
+
+ virtual bool isSystemDatabaseName( const QString &n ) const;
+
+ //! Escape a string for use as a value
+ virtual QString escapeString(const QString& str) const;
+ virtual QCString escapeString(const QCString& str) const;
+
+ //! Escape BLOB value \a array
+ virtual QString escapeBLOB(const QByteArray& array) const;
+
+ protected:
+ virtual QString drv_escapeIdentifier( const QString& str) const;
+ virtual QCString drv_escapeIdentifier( const QCString& str) const;
+ virtual Connection *drv_createConnection( ConnectionData &conn_data );
+ virtual bool drv_isSystemFieldName( const QString& n ) const;
+
+ private:
+ static const char *keywords[];
+};
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/mySQL/mysqlkeywords.cpp b/kexi/kexidb/drivers/mySQL/mysqlkeywords.cpp
new file mode 100644
index 000000000..e06adb5eb
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlkeywords.cpp
@@ -0,0 +1,338 @@
+ /*
+ * This file has been automatically generated from
+ * koffice/kexi/tools/sql_keywords/sql_keywords.sh and
+ * mysql-4.1.7/sql/lex.h.
+ *
+ * Please edit the sql_keywords.sh, not this file!
+ */
+#include <mysqldriver.h>
+
+namespace KexiDB {
+ const char* MySqlDriver::keywords[] = {
+ "ACTION",
+ "ADD",
+ "AGAINST",
+ "AGGREGATE",
+ "ALTER",
+ "ANALYZE",
+ "ANY",
+ "ASCII",
+ "AUTO_INCREMENT",
+ "AVG",
+ "AVG_ROW_LENGTH",
+ "BACKUP",
+ "BDB",
+ "BERKELEYDB",
+ "BIGINT",
+ "BINARY",
+ "BINLOG",
+ "BIT",
+ "BLOB",
+ "BOOL",
+ "BOOLEAN",
+ "BOTH",
+ "BTREE",
+ "BYTE",
+ "CACHE",
+ "CHANGE",
+ "CHANGED",
+ "CHAR",
+ "CHARACTER",
+ "CHARSET",
+ "CHECKSUM",
+ "CIPHER",
+ "CLIENT",
+ "CLOSE",
+ "COLLATION",
+ "COLUMN",
+ "COLUMNS",
+ "COMMENT",
+ "COMMITTED",
+ "COMPRESSED",
+ "CONCURRENT",
+ "CONVERT",
+ "CUBE",
+ "CURRENT_DATE",
+ "CURRENT_TIME",
+ "CURRENT_TIMESTAMP",
+ "CURRENT_USER",
+ "DATA",
+ "DATABASES",
+ "DATE",
+ "DATETIME",
+ "DAY",
+ "DAY_HOUR",
+ "DAY_MICROSECOND",
+ "DAY_MINUTE",
+ "DAY_SECOND",
+ "DEALLOCATE",
+ "DEC",
+ "DECIMAL",
+ "DELAYED",
+ "DELAY_KEY_WRITE",
+ "DESCRIBE",
+ "DES_KEY_FILE",
+ "DIRECTORY",
+ "DISABLE",
+ "DISCARD",
+ "DISTINCTROW",
+ "DIV",
+ "DO",
+ "DOUBLE",
+ "DUAL",
+ "DUMPFILE",
+ "DUPLICATE",
+ "DYNAMIC",
+ "ENABLE",
+ "ENCLOSED",
+ "ENGINE",
+ "ENGINES",
+ "ENUM",
+ "ERRORS",
+ "ESCAPE",
+ "ESCAPED",
+ "EVENTS",
+ "EXECUTE",
+ "EXISTS",
+ "EXPANSION",
+ "EXTENDED",
+ "FALSE",
+ "FAST",
+ "FIELDS",
+ "FILE",
+ "FIRST",
+ "FIXED",
+ "FLOAT",
+ "FLOAT4",
+ "FLOAT8",
+ "FLUSH",
+ "FORCE",
+ "FULLTEXT",
+ "FUNCTION",
+ "GEOMETRY",
+ "GEOMETRYCOLLECTION",
+ "GET_FORMAT",
+ "GLOBAL",
+ "GRANT",
+ "GRANTS",
+ "HANDLER",
+ "HASH",
+ "HELP",
+ "HIGH_PRIORITY",
+ "HOSTS",
+ "HOUR",
+ "HOUR_MICROSECOND",
+ "HOUR_MINUTE",
+ "HOUR_SECOND",
+ "IDENTIFIED",
+ "IF",
+ "IMPORT",
+ "INDEXES",
+ "INFILE",
+ "INNOBASE",
+ "INNODB",
+ "INSERT_METHOD",
+ "INT",
+ "INT1",
+ "INT2",
+ "INT3",
+ "INT4",
+ "INT8",
+ "INTERVAL",
+ "IO_THREAD",
+ "ISOLATION",
+ "ISSUER",
+ "KEYS",
+ "KILL",
+ "LAST",
+ "LEADING",
+ "LEAVES",
+ "LEVEL",
+ "LINES",
+ "LINESTRING",
+ "LOAD",
+ "LOCAL",
+ "LOCALTIME",
+ "LOCALTIMESTAMP",
+ "LOCK",
+ "LOCKS",
+ "LOGS",
+ "LONG",
+ "LONGBLOB",
+ "LONGTEXT",
+ "LOW_PRIORITY",
+ "MASTER",
+ "MASTER_CONNECT_RETRY",
+ "MASTER_HOST",
+ "MASTER_LOG_FILE",
+ "MASTER_LOG_POS",
+ "MASTER_PASSWORD",
+ "MASTER_PORT",
+ "MASTER_SERVER_ID",
+ "MASTER_SSL",
+ "MASTER_SSL_CA",
+ "MASTER_SSL_CAPATH",
+ "MASTER_SSL_CERT",
+ "MASTER_SSL_CIPHER",
+ "MASTER_SSL_KEY",
+ "MASTER_USER",
+ "MAX_CONNECTIONS_PER_HOUR",
+ "MAX_QUERIES_PER_HOUR",
+ "MAX_ROWS",
+ "MAX_UPDATES_PER_HOUR",
+ "MEDIUM",
+ "MEDIUMBLOB",
+ "MEDIUMINT",
+ "MEDIUMTEXT",
+ "MICROSECOND",
+ "MIDDLEINT",
+ "MINUTE",
+ "MINUTE_MICROSECOND",
+ "MINUTE_SECOND",
+ "MIN_ROWS",
+ "MOD",
+ "MODE",
+ "MODIFY",
+ "MONTH",
+ "MULTILINESTRING",
+ "MULTIPOINT",
+ "MULTIPOLYGON",
+ "NAMES",
+ "NATIONAL",
+ "NDB",
+ "NDBCLUSTER",
+ "NCHAR",
+ "NEW",
+ "NEXT",
+ "NO",
+ "NONE",
+ "NO_WRITE_TO_BINLOG",
+ "NUMERIC",
+ "NVARCHAR",
+ "OLD_PASSWORD",
+ "ONE_SHOT",
+ "OPEN",
+ "OPTIMIZE",
+ "OPTION",
+ "OPTIONALLY",
+ "OUTFILE",
+ "PACK_KEYS",
+ "PARTIAL",
+ "PASSWORD",
+ "POINT",
+ "POLYGON",
+ "PRECISION",
+ "PREPARE",
+ "PREV",
+ "PRIVILEGES",
+ "PROCEDURE",
+ "PROCESS",
+ "PROCESSLIST",
+ "PURGE",
+ "QUERY",
+ "QUICK",
+ "RAID0",
+ "RAID_CHUNKS",
+ "RAID_CHUNKSIZE",
+ "RAID_TYPE",
+ "READ",
+ "REAL",
+ "REGEXP",
+ "RELAY_LOG_FILE",
+ "RELAY_LOG_POS",
+ "RELAY_THREAD",
+ "RELOAD",
+ "RENAME",
+ "REPAIR",
+ "REPEATABLE",
+ "REPLICATION",
+ "REQUIRE",
+ "RESET",
+ "RESTORE",
+ "RETURNS",
+ "REVOKE",
+ "RLIKE",
+ "ROLLUP",
+ "ROWS",
+ "ROW_FORMAT",
+ "RTREE",
+ "SAVEPOINT",
+ "SECOND",
+ "SECOND_MICROSECOND",
+ "SEPARATOR",
+ "SERIAL",
+ "SERIALIZABLE",
+ "SESSION",
+ "SHARE",
+ "SHOW",
+ "SHUTDOWN",
+ "SIGNED",
+ "SIMPLE",
+ "SLAVE",
+ "SMALLINT",
+ "SOME",
+ "SONAME",
+ "SOUNDS",
+ "SPATIAL",
+ "SQL_BIG_RESULT",
+ "SQL_BUFFER_RESULT",
+ "SQL_CACHE",
+ "SQL_CALC_FOUND_ROWS",
+ "SQL_NO_CACHE",
+ "SQL_SMALL_RESULT",
+ "SQL_THREAD",
+ "SSL",
+ "START",
+ "STARTING",
+ "STATUS",
+ "STOP",
+ "STORAGE",
+ "STRAIGHT_JOIN",
+ "STRING",
+ "STRIPED",
+ "SUBJECT",
+ "SUPER",
+ "TABLES",
+ "TABLESPACE",
+ "TERMINATED",
+ "TEXT",
+ "TIME",
+ "TIMESTAMP",
+ "TINYBLOB",
+ "TINYINT",
+ "TINYTEXT",
+ "TRAILING",
+ "TRUE",
+ "TRUNCATE",
+ "TYPE",
+ "TYPES",
+ "UNCOMMITTED",
+ "UNICODE",
+ "UNLOCK",
+ "UNSIGNED",
+ "UNTIL",
+ "USAGE",
+ "USE",
+ "USER",
+ "USER_RESOURCES",
+ "USE_FRM",
+ "UTC_DATE",
+ "UTC_TIME",
+ "UTC_TIMESTAMP",
+ "VALUE",
+ "VARBINARY",
+ "VARCHAR",
+ "VARCHARACTER",
+ "VARIABLES",
+ "VARYING",
+ "WARNINGS",
+ "WITH",
+ "WORK",
+ "WRITE",
+ "X509",
+ "YEAR",
+ "YEAR_MONTH",
+ "ZEROFILL",
+ 0
+ };
+}
diff --git a/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.cpp b/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.cpp
new file mode 100644
index 000000000..2702626a1
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.cpp
@@ -0,0 +1,298 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "mysqlpreparedstatement.h"
+#include <kdebug.h>
+#include <errmsg.h>
+
+using namespace KexiDB;
+
+// For example prepared MySQL statement code see:
+// http://dev.mysql.com/doc/refman/4.1/en/mysql-stmt-execute.html
+
+MySqlPreparedStatement::MySqlPreparedStatement(StatementType type, ConnectionInternal& conn,
+ FieldList& fields)
+ : KexiDB::PreparedStatement(type, conn, fields)
+ , MySqlConnectionInternal(conn.connection)
+#ifdef KEXI_USE_MYSQL_STMT
+ , m_statement(0)
+ , m_mysqlBind(0)
+#endif
+ , m_resetRequired(false)
+{
+// KexiDBDrvDbg << "MySqlPreparedStatement: Construction" << endl;
+
+ mysql_owned = false;
+ mysql = dynamic_cast<KexiDB::MySqlConnectionInternal&>(conn).mysql; //copy
+ m_tempStatementString = generateStatementString();
+
+ if (!init())
+ done();
+}
+
+bool MySqlPreparedStatement::init()
+{
+ if (m_tempStatementString.isEmpty())
+ return false;
+#ifdef KEXI_USE_MYSQL_STMT
+ m_statement = mysql_stmt_init(mysql);
+ if (!m_statement) {
+//! @todo err 'out of memory'
+ return false;
+ }
+ res = mysql_stmt_prepare(m_statement,
+ (const char*)m_tempStatementString, m_tempStatementString.length());
+ if (0 != res) {
+//! @todo use mysql_stmt_error(stmt); to show error
+ return false;
+ }
+
+ m_realParamCount = mysql_stmt_param_count(m_statement);
+ if (m_realParamCount<=0) {
+//! @todo err
+ return false;
+ }
+ m_mysqlBind = new MYSQL_BIND[ m_realParamCount ];
+ memset(m_mysqlBind, 0, sizeof(MYSQL_BIND)*m_realParamCount); //safe?
+#endif
+ return true;
+}
+
+
+MySqlPreparedStatement::~MySqlPreparedStatement()
+{
+ done();
+}
+
+void MySqlPreparedStatement::done()
+{
+#ifdef KEXI_USE_MYSQL_STMT
+ if (m_statement) {
+//! @todo handle errors of mysql_stmt_close()?
+ mysql_stmt_close(m_statement);
+ m_statement = 0;
+ }
+ delete m_mysqlBind;
+ m_mysqlBind = 0;
+#endif
+}
+
+#ifdef KEXI_USE_MYSQL_STMT
+#define BIND_NULL { \
+ m_mysqlBind[arg].buffer_type = MYSQL_TYPE_NULL; \
+ m_mysqlBind[arg].buffer = 0; \
+ m_mysqlBind[arg].buffer_length = 0; \
+ m_mysqlBind[arg].is_null = &dummyNull; \
+ m_mysqlBind[arg].length = &str_length; }
+#endif
+
+bool MySqlPreparedStatement::execute()
+{
+#ifdef KEXI_USE_MYSQL_STMT
+ if (!m_statement || m_realParamCount<=0)
+ return false;
+ if ( mysql_stmt_errno(m_statement) == CR_SERVER_LOST ) {
+ //sanity: connection lost: reconnect
+//! @todo KexiDB::Connection should be reconnected as well!
+ done();
+ if (!init()) {
+ done();
+ return false;
+ }
+ }
+
+ if (m_resetRequired) {
+ mysql_stmt_reset(m_statement);
+ res = sqlite3_reset(prepared_st_handle);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ m_resetRequired = false;
+ }
+
+ int arg = 0;
+ bool dummyNull = true;
+ unsigned long str_length;
+ KexiDB::Field *field;
+
+ Field::List _dummy;
+ Field::ListIterator itFields(_dummy);
+ //for INSERT, we're iterating over inserting values
+ //for SELECT, we're iterating over WHERE conditions
+ if (m_type == SelectStatement)
+ itFields = *m_whereFields;
+ else if (m_type == InsertStatement)
+ itFields = m_fields->fieldsIterator();
+ else
+ assert(0); //impl. error
+
+ for (QValueListConstIterator<QVariant> it = m_args.constBegin();
+ (field = itFields.current()) && arg < m_realParamCount; ++it, ++itFields, arg++)
+ {
+ if (it==m_args.constEnd() || (*it).isNull()) {//no value to bind or the value is null: bind NULL
+ BIND_NULL;
+ continue;
+ }
+ if (field->isTextType()) {
+//! @todo optimize
+m_stringBuffer[ 1024 ]; ???
+ char *str = qstrncpy(m_stringBuffer, (const char*)(*it).toString().utf8(), 1024);
+ m_mysqlBind[arg].buffer_type = MYSQL_TYPE_STRING;
+ m_mysqlBind[arg].buffer = m_stringBuffer;
+ m_mysqlBind[arg].is_null = (my_bool*)0;
+ m_mysqlBind[arg].buffer_length = 1024; //?
+ m_mysqlBind[arg].length = &str_length;
+ }
+ else switch (field->type()) {
+ case KexiDB::Field::Byte:
+ case KexiDB::Field::ShortInteger:
+ case KexiDB::Field::Integer:
+ {
+//! @todo what about unsigned > INT_MAX ?
+ bool ok;
+ const int value = (*it).toInt(&ok);
+ if (ok) {
+ if (field->type()==KexiDB::Field::Byte)
+ m_mysqlBind[arg].buffer_type = MYSQL_TYPE_TINY;
+ else if (field->type()==KexiDB::Field::ShortInteger)
+ m_mysqlBind[arg].buffer_type = MYSQL_TYPE_SHORT;
+ else if (field->type()==KexiDB::Field::Integer)
+ m_mysqlBind[arg].buffer_type = MYSQL_TYPE_LONG;
+
+ m_mysqlBind[arg].is_null = (my_bool*)0;
+ m_mysqlBind[arg].length = 0;
+
+ res = sqlite3_bind_int(prepared_st_handle, arg, value);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ }
+ else
+ BIND_NULL;
+ break;
+ }
+ case KexiDB::Field::Float:
+ case KexiDB::Field::Double:
+ res = sqlite3_bind_double(prepared_st_handle, arg, (*it).toDouble());
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::BigInteger:
+ {
+//! @todo what about unsigned > LLONG_MAX ?
+ bool ok;
+ Q_LLONG value = (*it).toLongLong(&ok);
+ if (ok) {
+ res = sqlite3_bind_int64(prepared_st_handle, arg, value);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ }
+ else {
+ res = sqlite3_bind_null(prepared_st_handle, arg);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ }
+ break;
+ }
+ case KexiDB::Field::Boolean:
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ QString::number((*it).toBool() ? 1 : 0).latin1(),
+ 1, SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::Time:
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ (*it).toTime().toString(Qt::ISODate).latin1(),
+ sizeof("HH:MM:SS"), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::Date:
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ (*it).toDate().toString(Qt::ISODate).latin1(),
+ sizeof("YYYY-MM-DD"), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::DateTime:
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ (*it).toDateTime().toString(Qt::ISODate).latin1(),
+ sizeof("YYYY-MM-DDTHH:MM:SS"), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::BLOB:
+ {
+ const QByteArray byteArray((*it).toByteArray());
+ res = sqlite3_bind_blob(prepared_st_handle, arg,
+ (const char*)byteArray, byteArray.size(), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ }
+ default:
+ KexiDBWarn << "PreparedStatement::execute(): unsupported field type: "
+ << field->type() << " - NULL value bound to column #" << arg << endl;
+ res = sqlite3_bind_null(prepared_st_handle, arg);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ } //switch
+ }
+
+ //real execution
+ res = sqlite3_step(prepared_st_handle);
+ m_resetRequired = true;
+ if (m_type == InsertStatement && res == SQLITE_DONE) {
+ return true;
+ }
+ if (m_type == SelectStatement) {
+ //fetch result
+
+ //todo
+ }
+#else
+ m_resetRequired = true;
+ if (connection->insertRecord(*m_fields, m_args)) {
+ return true;
+ }
+
+#endif //KEXI_USE_MYSQL_STMT
+ return false;
+}
diff --git a/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.h b/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.h
new file mode 100644
index 000000000..01478e9e7
--- /dev/null
+++ b/kexi/kexidb/drivers/mySQL/mysqlpreparedstatement.h
@@ -0,0 +1,56 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef MYSQLPREPAREDSTATEMENT_H
+#define MYSQLPREPAREDSTATEMENT_H
+
+#include <kexidb/preparedstatement.h>
+#include "mysqlconnection_p.h"
+
+//todo 1.1 - unfinished: #define KEXI_USE_MYSQL_STMT
+// for 1.0 we're using unoptimized version
+
+namespace KexiDB
+{
+
+/*! Implementation of prepared statements for MySQL driver. */
+class MySqlPreparedStatement : public PreparedStatement, public MySqlConnectionInternal
+{
+ public:
+ MySqlPreparedStatement(StatementType type, ConnectionInternal& conn, FieldList& fields);
+
+ virtual ~MySqlPreparedStatement();
+
+ virtual bool execute();
+
+ QCString m_tempStatementString;
+
+#ifdef KEXI_USE_MYSQL_STMT
+ int m_realParamCount;
+ MYSQL_STMT *m_statement;
+ MYSQL_BIND *m_mysqlBind;
+#endif
+ bool m_resetRequired : 1;
+
+ protected:
+ bool init();
+ void done();
+};
+}
+#endif
diff --git a/kexi/kexidb/drivers/odbc/Makefile.am b/kexi/kexidb/drivers/odbc/Makefile.am
new file mode 100644
index 000000000..2570181a8
--- /dev/null
+++ b/kexi/kexidb/drivers/odbc/Makefile.am
@@ -0,0 +1,21 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexidb_odbcdriver.la
+
+INCLUDES = -I$(srcdir)/../.. -I$(top_srcdir)/kexi $(all_includes)
+
+kexidb_odbcdriver_la_METASOURCES = AUTO
+
+kexidb_odbcdriver_la_SOURCES = odbcdriver.cpp odbcconnection.cpp
+
+kexidb_odbcdriver_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) -lodbc $(top_builddir)/kexi/kexidb/libkexidb.la
+
+kexidb_odbcdriver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) -no-undefined
+
+
+kde_services_DATA = kexidb_odbcdriver.desktop
+
+
+KDE_CXXFLAGS += -DKEXIDB_ODBC_DRIVER_EXPORT= -D__KEXIDB__= \
+ -include $(top_srcdir)/kexi/kexidb/global.h
+
diff --git a/kexi/kexidb/drivers/odbc/kexidb_odbcdriver.desktop b/kexi/kexidb/drivers/odbc/kexidb_odbcdriver.desktop
new file mode 100644
index 000000000..acad3c640
--- /dev/null
+++ b/kexi/kexidb/drivers/odbc/kexidb_odbcdriver.desktop
@@ -0,0 +1,54 @@
+[Desktop Entry]
+Name=ODBC
+Name[ta]=B0
+Comment=Kexi Open Database Connectivity Driver
+Comment[bg]=Драйвер ODBC за Kexi
+Comment[ca]=Controlador ODBC per Kexi
+Comment[cy]=Gyrrydd Cysylltedd Cronfa Ddata Agored Kexi
+Comment[da]=Kexi-driver for åben databaseforbindelse
+Comment[de]=Kexi ODBC-Treiber
+Comment[el]=Οδηγός σύνδεσης Open Database με το Kexi
+Comment[eo]=Kexi "Open Database Connectivity"-pelilo
+Comment[es]=Controlador «Open Database Connectivity» para Kexi
+Comment[et]=Kexi ODBC (Open Database Connectivity) draiver
+Comment[eu]=Kexi-ren ODBC kontrolatzailea
+Comment[fa]=گردانندۀ اتصال دادگان Kexi Open
+Comment[fi]=Kexin ODBC-ajuri
+Comment[fr]=Pilote ODBC (connectivité de bases de données ouvertes) de Kexi
+Comment[fy]=Kexi Open Databank Connectivity-stjoerprogramma
+Comment[gl]=Controlador de ODBC de Kexi
+Comment[he]=מנהל התקן של Kexi לחיבור למסד נתונים פתוח (ODBC)
+Comment[hr]=Upravljački program za povezivanje s Kexi otvorenom bazom podataka
+Comment[hu]=Kexi ODBC-meghajtó
+Comment[is]=Kexi Open Database tengirekill
+Comment[it]=Scorciatoia verso un progetto Kexi sul server della banca dati
+Comment[ja]=Kexi ODBC (Open Database Connectivity) ドライバ
+Comment[km]=កម្មវិធី​បញ្ជា​សម្រាប់​តភ្ជាប់​មូលដ្ឋាន​ទិន្នន័យ​បើក​ចំហ​របស់ Kexi
+Comment[lv]=Kexi atvērtā datu bāzu savienojamības (ODBC) draiveris
+Comment[ms]=Pemacu Kesambungan Pangkalan Data Terbuka Kexi
+Comment[nb]=Tilkoblingsdriver for Kexis åpne database
+Comment[nds]=ODBC-Driever för Kexi
+Comment[ne]=केक्सी खुला डाटाबेस जडित ड्राइभर
+Comment[nl]=Kexi Open Database Connectivity-stuurprogramma
+Comment[pl]=Sterownik ODBC dla Kexi
+Comment[pt]=Controlador de ODBC do Kexi
+Comment[pt_BR]=Driver para ODBC do Kexi
+Comment[ru]=Драйвер Kexi Open Database
+Comment[sk]=Ovládač Kexi Open Database Connectivity
+Comment[sl]=Gonilnik povezovanja odprte zbirke podatkov za Kexi
+Comment[sr]=Kexi-јев управљачки програм за Open Database Connectivity (ODBC)
+Comment[sr@Latn]=Kexi-jev upravljački program za Open Database Connectivity (ODBC)
+Comment[sv]=Kexi-drivrutin för öppen databasanslutning
+Comment[ta]=குறுவழி kexi திட்டப்பணிக் தரவுத்தள சேவையகம்
+Comment[tg]=Драйвери Kexi Open Database
+Comment[tr]=Kexi Açık Veritabanı Bağlanabilirlik Sürücüsü
+Comment[uk]=Драйвер з'єднання Kexi Open Database
+Comment[zh_CN]=Kexi 开放数据库连接驱动
+Comment[zh_TW]=Kexi 開放資料庫連線驅動程式
+X-KDE-Library=kexidb_odbcdriver
+ServiceTypes=Kexi/DBDriver
+Type=Service
+InitialPreference=8
+MimeType=text/plain
+X-Kexi-DriverType=Network
+X-Kexi-DriverName=ODBC
diff --git a/kexi/kexidb/drivers/odbc/odbcconnection.cpp b/kexi/kexidb/drivers/odbc/odbcconnection.cpp
new file mode 100644
index 000000000..ec5a7cdf6
--- /dev/null
+++ b/kexi/kexidb/drivers/odbc/odbcconnection.cpp
@@ -0,0 +1,153 @@
+/*
+ This file is part of the KDE project
+ Copyright (C) 2004 Matt Rogers <matt.rogers@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+//unixODBC Includes
+#include <sql.h>
+#include <sqlext.h>
+#include <sqltypes.h>
+
+//QT Includes
+#include <qfile.h>
+#include <qdir.h>
+
+//KDE Includes
+#include <kgenericfactory.h>
+#include <kdebug.h>
+
+//Kexi Includes
+#include <kexidb/driver.h>
+#include <kexidb/cursor.h>
+#include <kexidb/error.h>
+
+//Local Includes
+#include "odbcconnection.h"
+
+using namespace KexiDB;
+
+//! @internal
+class ODBCConnectionPrivate
+{
+ public:
+ ConnectionData connData;
+ QString currentDB;
+ SQLHENV envHandle;
+ SQLHDBC connectionHandle;
+};
+
+ODBCConnection::ODBCConnection( Driver *driver, ConnectionData &conn_data )
+ : Connection( driver, conn_data )
+{
+ d = new ODBCConnectionPrivate;
+ //d->connData = conn_data;
+}
+
+Cursor* ODBCConnection::prepareQuery(const QString& statement, uint cursor_options)
+{
+ Q_UNUSED( statement );
+ Q_UNUSED( cursor_options );
+ return 0;
+}
+
+QString ODBCConnection::escapeString(const QString& str) const
+{
+ return str;
+}
+
+QCString ODBCConnection::escapeString(const QCString& str) const
+{
+ return str;
+}
+
+bool ODBCConnection::drv_connect()
+{
+ long result;
+
+ result = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &d->envHandle );
+ if ( result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO )
+ return false;
+
+ //We'll use ODBC 3.5 by default, so just get connection handle
+ result = SQLAllocHandle( SQL_HANDLE_DBC, d->envHandle, &d->connectionHandle );
+ if ( result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO )
+ {
+ SQLFreeHandle( SQL_HANDLE_ENV, d->envHandle );
+ return false;
+ }
+
+ result = SQLConnect( d->connectionHandle, (unsigned char*) d->connData.hostName.latin1(),
+ d->connData.hostName.length(), (unsigned char*) d->connData.userName.latin1(),
+ d->connData.userName.length(), (unsigned char*) d->connData.password.latin1(),
+ d->connData.password.length() );
+ if ( result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO )
+ {
+ SQLFreeHandle( SQL_HANDLE_DBC, d->connectionHandle );
+ SQLFreeHandle( SQL_HANDLE_ENV, d->envHandle );
+ return false;
+ }
+
+ return true;
+}
+
+bool ODBCConnection::drv_disconnect()
+{
+ SQLDisconnect( d->connectionHandle );
+ SQLFreeHandle( SQL_HANDLE_DBC, d->connectionHandle );
+ SQLFreeHandle( SQL_HANDLE_ENV, d->envHandle );
+ return true;
+}
+
+bool ODBCConnection::drv_getDatabasesList(QStringList &)
+{
+ return false;
+}
+
+bool ODBCConnection::drv_createDatabase(const QString &)
+{
+ return false;
+}
+
+bool ODBCConnection::drv_useDatabase(const QString &)
+{
+ return false;
+}
+
+bool ODBCConnection::drv_closeDatabase()
+{
+ return false;
+}
+
+bool ODBCConnection::drv_dropDatabase(const QString &)
+{
+ return false;
+}
+
+bool ODBCConnection::drv_executeSQL(const QString &)
+{
+ return false;
+}
+
+ODBCConnection::~ODBCConnection()
+{
+ drv_disconnect();
+ destroy();
+ delete d;
+}
+
+#include "odbcconnection.moc"
+
diff --git a/kexi/kexidb/drivers/odbc/odbcconnection.h b/kexi/kexidb/drivers/odbc/odbcconnection.h
new file mode 100644
index 000000000..8f5519050
--- /dev/null
+++ b/kexi/kexidb/drivers/odbc/odbcconnection.h
@@ -0,0 +1,92 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Matt Rogers <matt.rogers@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_CONN_ODBC_H
+#define KEXIDB_CONN_ODBC_H
+
+#include <qstringlist.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/tableschema.h>
+
+#include <sql.h>
+#include <sqlext.h>
+#include <sqltypes.h>
+
+class ODBCConnectionPrivate;
+
+namespace KexiDB
+{
+class Driver;
+
+
+class ODBCConnection : public Connection
+{
+ Q_OBJECT
+
+ public:
+ ~ODBCConnection();
+
+ virtual Cursor* prepareQuery( const QString& statement = QString::null, uint cursor_options = 0 );
+ virtual QString escapeString(const QString& str) const;
+ virtual QCString escapeString(const QCString& str) const;
+
+ protected:
+ /*! Used by driver */
+ ODBCConnection( Driver *driver, ConnectionData &conn_data );
+
+ virtual bool drv_connect();
+
+ virtual bool drv_disconnect();
+
+ virtual bool drv_getDatabasesList( QStringList &list );
+
+ /*! Creates new database using connection. Note: Do not pass \a dbName
+ arg because for file-based engine (that has one database per connection)
+ it is defined during connection. */
+ virtual bool drv_createDatabase( const QString &dbName = QString::null );
+
+ /*! Opens existing database using connection. Do not pass \a dbName
+ arg because for file-based engine (that has one database per connection)
+ it is defined during connection. If you pass it,
+ database file name will be changed. */
+ virtual bool drv_useDatabase( const QString &dbName = QString::null );
+
+ virtual bool drv_closeDatabase();
+
+ /*! Drops database from the server using connection.
+ After drop, database shouldn't be accessible
+ anymore, so database file is just removed. See note from drv_useDatabase(). */
+ virtual bool drv_dropDatabase( const QString &dbName = QString::null );
+
+ //virtual bool drv_createTable( const KexiDB::Table& table );
+
+ virtual bool drv_executeSQL( const QString& statement );
+
+ friend class ODBCDriver;
+
+ private:
+ ODBCConnectionPrivate *d;
+};
+
+
+
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/odbc/odbcdriver.cpp b/kexi/kexidb/drivers/odbc/odbcdriver.cpp
new file mode 100644
index 000000000..aac6a6c95
--- /dev/null
+++ b/kexi/kexidb/drivers/odbc/odbcdriver.cpp
@@ -0,0 +1,108 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Matt Rogers <matt.rogers@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+//QT Includes
+#include <qfile.h>
+#include <qdir.h>
+#include <qstring.h>
+#include <qcstring.h>
+
+//KDE Includes
+#include <kdebug.h>
+
+//Kexi Includes
+#include <kexidb/connection.h>
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver_p.h>
+
+//ODBC Includes
+#include "odbcdriver.h"
+#include "odbcconnection.h"
+
+using namespace KexiDB;
+
+KEXIDB_DRIVER_INFO( ODBCDriver, odbc )
+
+ODBCDriver::ODBCDriver( QObject *parent, const char *name, const QStringList &args )
+ : Driver( parent, name, args )
+{
+ d->isFileDriver = false;
+ d->isDBOpenedAfterCreate = true;
+ d->features = SingleTransactions | CursorForward;
+
+ //predefined properties
+ d->properties["client_library_version"] = "";//TODO
+ d->properties["default_server_encoding"] = ""; //TODO
+
+ d->typeNames[ Field::Byte ] = "Byte";
+ d->typeNames[ Field::ShortInteger ] = "ShortInteger";
+ d->typeNames[ Field::Integer ] = "Integer";
+ d->typeNames[ Field::BigInteger ] = "BigInteger";
+ d->typeNames[ Field::Boolean ] = "Boolean";
+ d->typeNames[ Field::Date ] = "Date";
+ d->typeNames[ Field::DateTime ] = "DateTime";
+ d->typeNames[ Field::Time ] = "Time";
+ d->typeNames[ Field::Float ] = "Float";
+ d->typeNames[ Field::Double ] = "Double";
+ d->typeNames[ Field::Text ] = "Text";
+ d->typeNames[ Field::LongText ] = "CLOB";
+ d->typeNames[ Field::BLOB ] = "BLOB";
+}
+
+ODBCDriver::~ODBCDriver()
+{
+}
+
+KexiDB::Connection* ODBCDriver::drv_createConnection( ConnectionData &conn_data )
+{
+ Q_UNUSED( conn_data );
+ return 0L;
+ //return new ODBCConnection( this, conn_data );
+}
+
+bool ODBCDriver::isSystemDatabaseName( const QString& name ) const
+{
+ Q_UNUSED( name );
+ return false;
+}
+
+bool ODBCDriver::isSystemObjectName( const QString& name )
+{
+ Q_UNUSED( name );
+ return false;
+}
+
+bool ODBCDriver::isSystemFieldName( const QString& name ) const
+{
+ Q_UNUSED( name );
+ return false;
+}
+
+QString ODBCDriver::escapeString( const QString& str ) const
+{
+ return str;
+}
+
+QCString ODBCDriver::escapeString( const QCString& str ) const
+{
+ return str;
+}
+
+#include "odbcdriver.moc"
+
diff --git a/kexi/kexidb/drivers/odbc/odbcdriver.h b/kexi/kexidb/drivers/odbc/odbcdriver.h
new file mode 100644
index 000000000..60681f218
--- /dev/null
+++ b/kexi/kexidb/drivers/odbc/odbcdriver.h
@@ -0,0 +1,73 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Matt Rogers <matt.rogers@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_DRIVER_ODBC_H
+#define KEXIDB_DRIVER_ODBC_H
+
+//Kexi Includes
+#include <kexidb/driver.h>
+
+class QCString;
+class QString;
+
+namespace KexiDB
+{
+
+class Connection;
+class DriverManager;
+class ODBCDriverPrivate;
+
+//! ODBC database driver.
+/*!
+ * This is the ODBC Driver for Kexi.
+ * @author Matt Rogers <matt.rogers@kdemail.net>
+ */
+class ODBCDriver : public Driver
+{
+ Q_OBJECT
+ KEXIDB_DRIVER
+
+ public:
+ ODBCDriver( QObject *parent, const char *name, const QStringList &args = QStringList() );
+ ~ODBCDriver();
+
+ virtual bool isSystemDatabaseName( const QString& name ) const;
+ /** \return true if n is a system object name;
+ * \todo Find out what is a system object name and what isn't
+ */
+ virtual bool isSystemObjectName( const QString& name );
+
+ /**
+ * \return true if \a n is a system field name;
+ * There aren't any system fields per tables, unless the table
+ * is a system table
+ */
+ virtual bool isSystemFieldName( const QString& name ) const;
+
+ virtual QString escapeString( const QString& str ) const;
+ virtual QCString escapeString( const QCString& str ) const;
+
+ protected:
+ virtual Connection *drv_createConnection( ConnectionData &conn_data );
+};
+
+}
+
+#endif
+
diff --git a/kexi/kexidb/drivers/pqxx/Makefile.am b/kexi/kexidb/drivers/pqxx/Makefile.am
new file mode 100644
index 000000000..5129c84fc
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/Makefile.am
@@ -0,0 +1,22 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexidb_pqxxsqldriver.la
+
+INCLUDES = -I$(srcdir)/../../.. $(all_includes) -I$(PG_INCDIR) -I$(PQXX_INCDIR)
+
+kexidb_pqxxsqldriver_la_METASOURCES = AUTO
+
+kexidb_pqxxsqldriver_la_SOURCES = pqxxdriver.cpp pqxxcursor.cpp pqxxconnection.cpp \
+ pqxxkeywords.cpp pqxxconnection_p.cpp pqxxpreparedstatement.cpp
+
+kexidb_pqxxsqldriver_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) -lpqxx ../../libkexidb.la
+
+kexidb_pqxxsqldriver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) \
+ -L$(PQXX_LIBDIR) -L$(PG_LIBDIR) $(VER_INFO) -no-undefined
+
+kde_services_DATA = kexidb_pqxxsqldriver.desktop
+
+noinst_HEADERS = pqxxconnection.h pqxxconnection_p.h
+
+KDE_CXXFLAGS += -DKEXIDB_PGSQL_DRIVER_EXPORT= -D__KEXIDB__= \
+ -include $(top_srcdir)/kexi/kexidb/global.h
diff --git a/kexi/kexidb/drivers/pqxx/README b/kexi/kexidb/drivers/pqxx/README
new file mode 100644
index 000000000..6a1e6c3ed
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/README
@@ -0,0 +1,18 @@
+ReadMe For Kexi pqkexidb_pqxxslqdriver.desktop~xx PostgreSQL Driver
+
+This driver requires libpqxx available from pqxx.tk or gborg.postgresql.org.
+
+Currently the driver builds against 1.9.4 of libpqxx, but it should always work with the latest version.
+When 2.0.0 comes out then that will be the version to use.
+
+The driver may require PostgreSQL >=7.4. Using the old api this was a requirement, but the rewrite
+isnt far enough in to get into those kinds of details, so at the mement it should be happy with earlier versions.
+Im using PostgreSQL from CVS so i cant say for sure.
+
+To build the driver you may need to add 'pqxx' to the list of subdirs in Makefile.am in kexi/drivers/
+
+Thats it for now
+
+Adam Pigg
+adam@piggz.fsnet.co.uk
+adampigg.9p.org.uk \ No newline at end of file
diff --git a/kexi/kexidb/drivers/pqxx/kexidb_pqxxsqldriver.desktop b/kexi/kexidb/drivers/pqxx/kexidb_pqxxsqldriver.desktop
new file mode 100644
index 000000000..1f38241b1
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/kexidb_pqxxsqldriver.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=PostgreSQL
+Name[hi]=पोस्टग्रे-एसक्यूएल
+Name[ne]=पोस्ट ग्रे एसक्यूएल
+X-KDE-Library=kexidb_pqxxsqldriver
+ServiceTypes=Kexi/DBDriver
+Type=Service
+InitialPreference=8
+X-Kexi-DriverName=PostgreSQL
+X-Kexi-DriverType=Network
+X-Kexi-KexiDBVersion=1.8
diff --git a/kexi/kexidb/drivers/pqxx/pqxxconnection.cpp b/kexi/kexidb/drivers/pqxx/pqxxconnection.cpp
new file mode 100644
index 000000000..8465bcf43
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxconnection.cpp
@@ -0,0 +1,448 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "pqxxconnection.h"
+#include <qvariant.h>
+#include <qfile.h>
+#include <kdebug.h>
+#include <kexidb/error.h>
+#include <kexidb/global.h>
+#include <klocale.h>
+#include <string>
+#include "pqxxpreparedstatement.h"
+#include "pqxxconnection_p.h"
+using namespace KexiDB;
+
+pqxxTransactionData::pqxxTransactionData(Connection *conn, bool nontransaction)
+ : TransactionData(conn)
+{
+ if (nontransaction)
+ data = new pqxx::nontransaction(*static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql /* todo: add name? */);
+ else
+ data = new pqxx::transaction<>(*static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql /* todo: add name? */);
+ if (!static_cast<pqxxSqlConnection*>(conn)->m_trans) {
+ static_cast<pqxxSqlConnection*>(conn)->m_trans = this;
+ }
+}
+
+pqxxTransactionData::~pqxxTransactionData()
+{
+ if (static_cast<pqxxSqlConnection*>(m_conn)->m_trans == this) {
+ static_cast<pqxxSqlConnection*>(m_conn)->m_trans = 0;
+ }
+ delete data;
+ data = 0;
+}
+
+//==================================================================================
+
+pqxxSqlConnection::pqxxSqlConnection(Driver *driver, ConnectionData &conn_data)
+ : Connection(driver,conn_data)
+ , d( new pqxxSqlConnectionInternal(this) )
+ , m_trans(0)
+{
+}
+
+//==================================================================================
+//Do any tidying up before the object is deleted
+pqxxSqlConnection::~pqxxSqlConnection()
+{
+ //delete m_trans;
+ destroy();
+ delete d;
+}
+
+//==================================================================================
+//Return a new query based on a query statment
+Cursor* pqxxSqlConnection::prepareQuery( const QString& statement, uint cursor_options)
+{
+ Q_UNUSED(cursor_options);
+ return new pqxxSqlCursor(this, statement, 1); //Always used buffered cursor
+}
+
+//==================================================================================
+//Return a new query based on a query object
+Cursor* pqxxSqlConnection::prepareQuery( QuerySchema& query, uint cursor_options)
+{
+ Q_UNUSED(cursor_options);
+ return new pqxxSqlCursor(this, query, 1);//Always used buffered cursor
+}
+
+//==================================================================================
+//Properly escaped a database object name
+QString pqxxSqlConnection::escapeName(const QString &name) const
+{
+ return QString("\"" + name + "\"");
+}
+
+//==================================================================================
+//Made this a noop
+//We tell kexi we are connected, but we wont actually connect until we use a database!
+bool pqxxSqlConnection::drv_connect(KexiDB::ServerVersionInfo& version)
+{
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_connect" << endl;
+ version.clear();
+ d->version = &version; //remember for later...
+#ifdef __GNUC__
+#warning pqxxSqlConnection::drv_connect implement setting version info when we drop libpqxx for libpq
+#endif
+ return true;
+}
+
+//==================================================================================
+//Made this a noop
+//We tell kexi wehave disconnected, but it is actually handled by closeDatabse
+bool pqxxSqlConnection::drv_disconnect()
+{
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_disconnect: " << endl;
+ return true;
+}
+
+//==================================================================================
+//Return a list of database names
+bool pqxxSqlConnection::drv_getDatabasesList( QStringList &list )
+{
+// KexiDBDrvDbg << "pqxxSqlConnection::drv_getDatabaseList" << endl;
+
+ if (executeSQL("SELECT datname FROM pg_database WHERE datallowconn = TRUE"))
+ {
+ std::string N;
+ for (pqxx::result::const_iterator c = d->res->begin(); c != d->res->end(); ++c)
+ {
+ // Read value of column 0 into a string N
+ c[0].to(N);
+ // Copy the result into the return list
+ list << QString::fromLatin1 (N.c_str());
+ }
+ return true;
+ }
+
+ return false;
+}
+
+//==================================================================================
+//Create a new database
+bool pqxxSqlConnection::drv_createDatabase( const QString &dbName )
+{
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_createDatabase: " << dbName << endl;
+
+ if (executeSQL("CREATE DATABASE " + escapeName(dbName)))
+ return true;
+
+ return false;
+}
+
+//==================================================================================
+//Use this as our connection instead of connect
+bool pqxxSqlConnection::drv_useDatabase( const QString &dbName, bool *cancelled,
+ MessageHandler* msgHandler )
+{
+ Q_UNUSED(cancelled);
+ Q_UNUSED(msgHandler);
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_useDatabase: " << dbName << endl;
+
+ QString conninfo;
+ QString socket;
+ QStringList sockets;
+
+ if (data()->hostName.isEmpty() || data()->hostName == "localhost")
+ {
+ if (data()->localSocketFileName.isEmpty())
+ {
+ sockets.append("/tmp/.s.PGSQL.5432");
+
+ for(QStringList::ConstIterator it = sockets.constBegin(); it != sockets.constEnd(); it++)
+ {
+ if(QFile(*it).exists())
+ {
+ socket = (*it);
+ break;
+ }
+ }
+ }
+ else
+ {
+ socket=data()->localSocketFileName; //data()->fileName();
+ }
+ }
+ else
+ {
+ conninfo = "host='" + data()->hostName + "'";
+ }
+
+ //Build up the connection string
+ if (data()->port == 0)
+ data()->port = 5432;
+
+ conninfo += QString::fromLatin1(" port='%1'").arg(data()->port);
+
+ conninfo += QString::fromLatin1(" dbname='%1'").arg(dbName);
+
+ if (!data()->userName.isNull())
+ conninfo += QString::fromLatin1(" user='%1'").arg(data()->userName);
+
+ if (!data()->password.isNull())
+ conninfo += QString::fromLatin1(" password='%1'").arg(data()->password);
+
+ try
+ {
+ d->pqxxsql = new pqxx::connection( conninfo.latin1() );
+ drv_executeSQL( "SET DEFAULT_WITH_OIDS TO ON" ); //Postgres 8.1 changed the default to no oids but we need them
+
+ if (d->version) {
+//! @todo set version using the connection pointer when we drop libpqxx for libpq
+ }
+ return true;
+ }
+ catch(const std::exception &e)
+ {
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_useDatabase:exception - " << e.what() << endl;
+ d->errmsg = QString::fromUtf8( e.what() );
+
+ }
+ catch(...)
+ {
+ d->errmsg = i18n("Unknown error.");
+ }
+ return false;
+}
+
+//==================================================================================
+//Here we close the database connection
+bool pqxxSqlConnection::drv_closeDatabase()
+{
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_closeDatabase" << endl;
+// if (isConnected())
+// {
+ delete d->pqxxsql;
+ return true;
+// }
+/* js: not needed, right?
+ else
+ {
+ d->errmsg = "Not connected to database backend";
+ d->res = ERR_NO_CONNECTION;
+ }
+ return false;*/
+}
+
+//==================================================================================
+//Drops the given database
+bool pqxxSqlConnection::drv_dropDatabase( const QString &dbName )
+{
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_dropDatabase: " << dbName << endl;
+
+ //FIXME Maybe should check that dbname is no the currentdb
+ if (executeSQL("DROP DATABASE " + escapeName(dbName)))
+ return true;
+
+ return false;
+}
+
+//==================================================================================
+//Execute an SQL statement
+bool pqxxSqlConnection::drv_executeSQL( const QString& statement )
+{
+// KexiDBDrvDbg << "pqxxSqlConnection::drv_executeSQL: " << statement << endl;
+ bool ok = false;
+
+ // Clear the last result information...
+ delete d->res;
+ d->res = 0;
+
+// KexiDBDrvDbg << "About to try" << endl;
+ try
+ {
+ //Create a transaction
+ const bool implicityStarted = !m_trans;
+ if (implicityStarted)
+ (void)new pqxxTransactionData(this, true);
+
+ // m_trans = new pqxx::nontransaction(*m_pqxxsql);
+// KexiDBDrvDbg << "About to execute" << endl;
+ //Create a result object through the transaction
+ d->res = new pqxx::result(m_trans->data->exec(std::string(statement.utf8())));
+// KexiDBDrvDbg << "Executed" << endl;
+ //Commit the transaction
+ if (implicityStarted) {
+ pqxxTransactionData *t = m_trans;
+ drv_commitTransaction(t);
+ delete t;
+// m_trans = 0;
+ }
+
+ //If all went well then return true, errors picked up by the catch block
+ ok = true;
+ }
+ catch(const pqxx::sql_error& sqlerr) {
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_executeSQL: sql_error exception - " << sqlerr.query().c_str() << endl;
+ }
+ catch (const pqxx::broken_connection& bcerr) {
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_executeSQL: broken_connection exception" << endl;
+ }
+ catch (const std::exception &e)
+ {
+ //If an error ocurred then put the error description into _dbError
+ d->errmsg = QString::fromUtf8( e.what() );
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_executeSQL:exception - " << e.what() << endl;
+ }
+ catch(...)
+ {
+ d->errmsg = i18n("Unknown error.");
+ }
+ //KexiDBDrvDbg << "EXECUTE SQL OK: OID was " << (d->res ? d->res->inserted_oid() : 0) << endl;
+ return ok;
+}
+
+//==================================================================================
+//Return true if currently connected to a database, ignoring the m_is_connected falg.
+bool pqxxSqlConnection::drv_isDatabaseUsed() const
+{
+ if (d->pqxxsql->is_open())
+ {
+ return true;
+ }
+ return false;
+}
+
+//==================================================================================
+//Return the oid of the last insert - only works if sql was insert of 1 row
+Q_ULLONG pqxxSqlConnection::drv_lastInsertRowID()
+{
+ if (d->res)
+ {
+ pqxx::oid theOid = d->res->inserted_oid();
+
+ if (theOid != pqxx::oid_none)
+ {
+ return (Q_ULLONG)theOid;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ return 0;
+}
+
+//<queries taken from pqxxMigrate>
+bool pqxxSqlConnection::drv_containsTable( const QString &tableName )
+{
+ bool success;
+ return resultExists(QString("select 1 from pg_class where relkind='r' and relname LIKE %1")
+ .arg(driver()->escapeString(tableName)), success) && success;
+}
+
+bool pqxxSqlConnection::drv_getTablesList( QStringList &list )
+{
+ KexiDB::Cursor *cursor;
+ m_sql = "select lower(relname) from pg_class where relkind='r'";
+ if (!(cursor = executeQuery( m_sql ))) {
+ KexiDBDrvWarn << "pqxxSqlConnection::drv_getTablesList(): !executeQuery()" << endl;
+ return false;
+ }
+ list.clear();
+ cursor->moveFirst();
+ while (!cursor->eof() && !cursor->error()) {
+ list += cursor->value(0).toString();
+ cursor->moveNext();
+ }
+ if (cursor->error()) {
+ deleteCursor(cursor);
+ return false;
+ }
+ return deleteCursor(cursor);
+}
+//</taken from pqxxMigrate>
+
+TransactionData* pqxxSqlConnection::drv_beginTransaction()
+{
+ return new pqxxTransactionData(this, false);
+}
+
+bool pqxxSqlConnection::drv_commitTransaction(TransactionData *tdata)
+{
+ bool result = true;
+ try {
+ static_cast<pqxxTransactionData*>(tdata)->data->commit();
+ }
+ catch (const std::exception &e)
+ {
+ //If an error ocurred then put the error description into _dbError
+ d->errmsg = QString::fromUtf8( e.what() );
+ result = false;
+ }
+ catch (...) {
+ //! @todo
+ setError();
+ result = false;
+ }
+ if (m_trans == tdata)
+ m_trans = 0;
+ return result;
+}
+
+bool pqxxSqlConnection::drv_rollbackTransaction(TransactionData *tdata)
+{
+ bool result = true;
+ try {
+ static_cast<pqxxTransactionData*>(tdata)->data->abort();
+ }
+ catch (const std::exception &e)
+ {
+ //If an error ocurred then put the error description into _dbError
+ d->errmsg = QString::fromUtf8( e.what() );
+
+ result = false;
+ }
+ catch (...) {
+ d->errmsg = i18n("Unknown error.");
+ result = false;
+ }
+ if (m_trans == tdata)
+ m_trans = 0;
+ return result;
+}
+
+int pqxxSqlConnection::serverResult()
+{
+ return d->resultCode;
+}
+
+QString pqxxSqlConnection::serverResultName()
+{
+ return QString::null;
+}
+
+void pqxxSqlConnection::drv_clearServerResult()
+{
+ d->resultCode = 0;
+}
+
+QString pqxxSqlConnection::serverErrorMsg()
+{
+ return d->errmsg;
+}
+
+PreparedStatement::Ptr pqxxSqlConnection::prepareStatement(PreparedStatement::StatementType type,
+ FieldList& fields)
+{
+ return new pqxxPreparedStatement(type, *d, fields);
+}
+#include "pqxxconnection.moc"
diff --git a/kexi/kexidb/drivers/pqxx/pqxxconnection.h b/kexi/kexidb/drivers/pqxx/pqxxconnection.h
new file mode 100644
index 000000000..85bed42a0
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxconnection.h
@@ -0,0 +1,104 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef PQXXCONNECTION_H
+#define PQXXCONNECTION_H
+
+#include <qstringlist.h>
+
+#include <kexidb/connection.h>
+#include "pqxxcursor.h"
+
+
+
+
+namespace KexiDB
+{
+
+class pqxxSqlConnectionInternal;
+
+//! @internal
+class pqxxTransactionData : public TransactionData
+{
+ public:
+ pqxxTransactionData(Connection *conn, bool nontransaction);
+ ~pqxxTransactionData();
+ pqxx::transaction_base *data;
+};
+
+/**
+@author Adam Pigg
+*/
+class pqxxSqlConnection : public Connection
+{
+ Q_OBJECT
+
+ public:
+ virtual ~pqxxSqlConnection();
+
+ virtual Cursor* prepareQuery( const QString& statement = QString::null, uint cursor_options = 0 );
+ virtual Cursor* prepareQuery( QuerySchema& query, uint cursor_options = 0 );
+ virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type,
+ FieldList& fields);
+ protected:
+
+ pqxxSqlConnection( Driver *driver, ConnectionData &conn_data );
+
+ virtual bool drv_isDatabaseUsed() const;
+ virtual bool drv_connect(KexiDB::ServerVersionInfo& version);
+ virtual bool drv_disconnect();
+ virtual bool drv_getDatabasesList( QStringList &list );
+ virtual bool drv_createDatabase( const QString &dbName = QString::null );
+ virtual bool drv_useDatabase( const QString &dbName = QString::null, bool *cancelled = 0,
+ MessageHandler* msgHandler = 0 );
+ virtual bool drv_closeDatabase();
+ virtual bool drv_dropDatabase( const QString &dbName = QString::null );
+ virtual bool drv_executeSQL( const QString& statement );
+ virtual Q_ULLONG drv_lastInsertRowID();
+
+//TODO: move this somewhere to low level class (MIGRATION?)
+ virtual bool drv_getTablesList( QStringList &list );
+//TODO: move this somewhere to low level class (MIGRATION?)
+ virtual bool drv_containsTable( const QString &tableName );
+
+ virtual TransactionData* drv_beginTransaction();
+ virtual bool drv_commitTransaction(TransactionData *);
+ virtual bool drv_rollbackTransaction(TransactionData *);
+
+ //Error reporting
+ virtual int serverResult();
+ virtual QString serverResultName();
+ virtual void drv_clearServerResult();
+ virtual QString serverErrorMsg();
+
+ pqxxSqlConnectionInternal *d;
+ private:
+ QString escapeName(const QString &tn) const;
+ // pqxx::transaction_base* m_trans;
+ //! temporary solution for executeSQL()...
+ pqxxTransactionData *m_trans;
+
+
+
+ friend class pqxxSqlDriver;
+ friend class pqxxSqlCursor;
+ friend class pqxxTransactionData;
+};
+}
+#endif
diff --git a/kexi/kexidb/drivers/pqxx/pqxxconnection_p.cpp b/kexi/kexidb/drivers/pqxx/pqxxconnection_p.cpp
new file mode 100644
index 000000000..b4bc266af
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxconnection_p.cpp
@@ -0,0 +1,51 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+//
+// C++ Implementation: pqxxsqlconnectioninternal
+//
+// Description:
+//
+//
+// Author: Adam Pigg <adam@piggz.co.uk>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include "pqxxconnection_p.h"
+#include <kdebug.h>
+
+using namespace KexiDB;
+pqxxSqlConnectionInternal::pqxxSqlConnectionInternal(Connection *conn)
+ : ConnectionInternal(conn)
+ , pqxxsql(0)
+ , res(0)
+ , version(0)
+{
+}
+
+
+pqxxSqlConnectionInternal::~pqxxSqlConnectionInternal()
+{
+
+}
+
+void pqxxSqlConnectionInternal::storeResult()
+{
+ errmsg = "";
+}
diff --git a/kexi/kexidb/drivers/pqxx/pqxxconnection_p.h b/kexi/kexidb/drivers/pqxx/pqxxconnection_p.h
new file mode 100644
index 000000000..0c78e583f
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxconnection_p.h
@@ -0,0 +1,63 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+//
+// C++ Interface: pqxxsqlconnectioninternal
+//
+// Description:
+//
+//
+// Author: Adam Pigg <adam@piggz.co.uk>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef PQXXSQLCONNECTIONINTERNAL_H
+#define PQXXSQLCONNECTIONINTERNAL_H
+
+#include <kexidb/connection_p.h>
+#include <pqxx/pqxx>
+
+namespace KexiDB
+{
+
+/**
+ @internal
+ @author Adam Pigg <adam@piggz.co.uk>
+*/
+class pqxxSqlConnectionInternal : public ConnectionInternal
+{
+ public:
+ pqxxSqlConnectionInternal(Connection *conn);
+
+ virtual ~pqxxSqlConnectionInternal();
+
+ //! stores last result's message
+ virtual void storeResult();
+
+ pqxx::connection* pqxxsql;
+ pqxx::result* res;
+
+ KexiDB::ServerVersionInfo *version; //!< this is set in drv_connect(), so we can use it in drv_useDatabase()
+ //!< because pgsql really connects after "USE".
+
+ QString errmsg; //!< server-specific message of last operation
+ int resultCode; //!< result code of last operation on server
+};
+}
+#endif
diff --git a/kexi/kexidb/drivers/pqxx/pqxxcursor.cpp b/kexi/kexidb/drivers/pqxx/pqxxcursor.cpp
new file mode 100644
index 000000000..0004cf922
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxcursor.cpp
@@ -0,0 +1,339 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "pqxxcursor.h"
+#include "pqxxconnection.h"
+#include "pqxxconnection_p.h"
+
+#include <kexidb/error.h>
+#include <kexidb/global.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <cstdlib>
+
+using namespace KexiDB;
+
+
+unsigned int pqxxSqlCursor_trans_num=0; //!< debug helper
+
+static QByteArray pgsqlByteaToByteArray(const pqxx::result::field& r)
+{
+ return KexiDB::pgsqlByteaToByteArray(r.c_str(), r.size());
+}
+
+//==================================================================================
+//Constructor based on query statement
+pqxxSqlCursor::pqxxSqlCursor(KexiDB::Connection* conn, const QString& statement, uint options):
+ Cursor(conn,statement, options)
+{
+// KexiDBDrvDbg << "PQXXSQLCURSOR: constructor for query statement" << endl;
+ my_conn = static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql;
+ m_options = Buffered;
+ m_res = 0;
+// m_tran = 0;
+ m_implicityStarted = false;
+}
+
+//==================================================================================
+//Constructor base on query object
+pqxxSqlCursor::pqxxSqlCursor(Connection* conn, QuerySchema& query, uint options )
+ : Cursor( conn, query, options )
+{
+// KexiDBDrvDbg << "PQXXSQLCURSOR: constructor for query schema" << endl;
+ my_conn = static_cast<pqxxSqlConnection*>(conn)->d->pqxxsql;
+ m_options = Buffered;
+ m_res = 0;
+// m_tran = 0;
+ m_implicityStarted = false;
+}
+
+//==================================================================================
+//Destructor
+pqxxSqlCursor::~pqxxSqlCursor()
+{
+ close();
+}
+
+//==================================================================================
+//Create a cursor result set
+bool pqxxSqlCursor::drv_open()
+{
+// KexiDBDrvDbg << "pqxxSqlCursor::drv_open:" << m_sql << endl;
+
+ if (!my_conn->is_open())
+ {
+//! @todo this check should be moved to Connection! when drv_prepareQuery() arrive
+ //should never happen, but who knows
+ setError(ERR_NO_CONNECTION,i18n("No connection for cursor open operation specified"));
+ return false;
+ }
+
+ QCString cur_name;
+ //Set up a transaction
+ try
+ {
+ //m_tran = new pqxx::work(*my_conn, "cursor_open");
+ cur_name.sprintf("cursor_transaction%d", pqxxSqlCursor_trans_num++);
+
+// m_tran = new pqxx::nontransaction(*my_conn, (const char*)cur_name);
+ if (!((pqxxSqlConnection*)connection())->m_trans) {
+// my_conn->drv_beginTransaction();
+// if (implicityStarted)
+ (void)new pqxxTransactionData((pqxxSqlConnection*)connection(), true);
+ m_implicityStarted = true;
+ }
+
+ m_res = new pqxx::result(((pqxxSqlConnection*)connection())->m_trans->data->exec(std::string(m_sql.utf8())));
+ ((pqxxSqlConnection*)connection())
+ ->drv_commitTransaction(((pqxxSqlConnection*)connection())->m_trans);
+// my_conn->m_trans->commit();
+// KexiDBDrvDbg << "pqxxSqlCursor::drv_open: trans. committed: " << cur_name <<endl;
+
+ //We should now be placed before the first row, if any
+ m_fieldCount = m_res->columns() - (m_containsROWIDInfo ? 1 : 0);
+//js m_opened=true;
+ m_afterLast=false;
+ m_records_in_buf = m_res->size();
+ m_buffering_completed = true;
+ return true;
+ }
+ catch (const std::exception &e)
+ {
+ setError(ERR_DB_SPECIFIC, QString::fromUtf8( e.what()) );
+ KexiDBDrvWarn << "pqxxSqlCursor::drv_open:exception - " << QString::fromUtf8( e.what() ) << endl;
+ }
+ catch(...)
+ {
+ setError();
+ }
+// delete m_tran;
+// m_tran = 0;
+ if (m_implicityStarted) {
+ delete ((pqxxSqlConnection*)connection())->m_trans;
+ m_implicityStarted = false;
+ }
+// KexiDBDrvDbg << "pqxxSqlCursor::drv_open: trans. rolled back! - " << cur_name <<endl;
+ return false;
+}
+
+//==================================================================================
+//Delete objects
+bool pqxxSqlCursor::drv_close()
+{
+//js m_opened=false;
+
+ delete m_res;
+ m_res = 0;
+
+// if (m_implicityStarted) {
+// delete m_tran;
+// m_tran = 0;
+// m_implicityStarted = false;
+// }
+
+ return true;
+}
+
+//==================================================================================
+//Gets the next record...does not need to do much, just return fetchend if at end of result set
+void pqxxSqlCursor::drv_getNextRecord()
+{
+// KexiDBDrvDbg << "pqxxSqlCursor::drv_getNextRecord, size is " <<m_res->size() << " Current Position is " << (long)at() << endl;
+ if(at() < m_res->size() && at() >=0)
+ {
+ m_result = FetchOK;
+ }
+ else if (at() >= m_res->size())
+ {
+ m_result = FetchEnd;
+ }
+ else
+ {
+ m_result = FetchError;
+ }
+}
+
+//==================================================================================
+//Check the current position is within boundaries
+void pqxxSqlCursor::drv_getPrevRecord()
+{
+// KexiDBDrvDbg << "pqxxSqlCursor::drv_getPrevRecord" << endl;
+
+ if(at() < m_res->size() && at() >=0)
+ {
+ m_result = FetchOK;
+ }
+ else if (at() >= m_res->size())
+ {
+ m_result = FetchEnd;
+ }
+ else
+ {
+ m_result = FetchError;
+ }
+}
+
+//==================================================================================
+//Return the value for a given column for the current record
+QVariant pqxxSqlCursor::value(uint pos)
+{
+ if (pos < m_fieldCount)
+ return pValue(pos);
+ else
+ return QVariant();
+}
+
+//==================================================================================
+//Return the value for a given column for the current record - Private const version
+QVariant pqxxSqlCursor::pValue(uint pos)const
+{
+ if (m_res->size() <= 0)
+ {
+ KexiDBDrvWarn << "pqxxSqlCursor::value - ERROR: result size not greater than 0" << endl;
+ return QVariant();
+ }
+
+ if (pos>=(m_fieldCount+(m_containsROWIDInfo ? 1 : 0)))
+ {
+// KexiDBDrvWarn << "pqxxSqlCursor::value - ERROR: requested position is greater than the number of fields" << endl;
+ return QVariant();
+ }
+
+ KexiDB::Field *f = (m_fieldsExpanded && pos<QMIN(m_fieldsExpanded->count(), m_fieldCount))
+ ? m_fieldsExpanded->at(pos)->field : 0;
+
+// KexiDBDrvDbg << "pqxxSqlCursor::value(" << pos << ")" << endl;
+
+ //from most to least frequently used types:
+ if (f) //We probably have a schema type query so can use kexi to determin the row type
+ {
+ if ((f->isIntegerType()) || (/*ROWID*/!f && m_containsROWIDInfo && pos==m_fieldCount))
+ {
+ return (*m_res)[at()][pos].as(int());
+ }
+ else if (f->isTextType())
+ {
+ return QString::fromUtf8((*m_res)[at()][pos].c_str()); //utf8?
+ }
+ else if (f->isFPNumericType())
+ {
+ return (*m_res)[at()][pos].as(double());
+ }
+ else if (f->typeGroup() == Field::BLOBGroup)
+ {
+// pqxx::result::field r = (*m_res)[at()][pos];
+// kdDebug() << r.name() << ", " << r.c_str() << ", " << r.type() << ", " << r.size() << endl;
+ return ::pgsqlByteaToByteArray((*m_res)[at()][pos]);
+ }
+ }
+ else // We probably have a raw type query so use pqxx to determin the column type
+ {
+ return pgsqlCStrToVariant((*m_res)[at()][pos]);
+ }
+
+ return QString::fromUtf8((*m_res)[at()][pos].c_str(), (*m_res)[at()][pos].size()); //utf8?
+}
+
+//==================================================================================
+//Return the current record as a char**
+//who'd have thought we'd be using char** in this day and age :o)
+const char** pqxxSqlCursor::rowData() const
+{
+// KexiDBDrvDbg << "pqxxSqlCursor::recordData" << endl;
+
+ const char** row;
+
+ row = (const char**)malloc(m_res->columns()+1);
+ row[m_res->columns()] = NULL;
+ if (at() >= 0 && at() < m_res->size())
+ {
+ for(int i = 0; i < (int)m_res->columns(); i++)
+ {
+ row[i] = (char*)malloc(strlen((*m_res)[at()][i].c_str())+1);
+ strcpy((char*)(*m_res)[at()][i].c_str(), row[i]);
+// KexiDBDrvDbg << row[i] << endl;
+ }
+ }
+ else
+ {
+ KexiDBDrvWarn << "pqxxSqlCursor::recordData: m_at is invalid" << endl;
+ }
+ return row;
+}
+
+//==================================================================================
+//Store the current record in [data]
+void pqxxSqlCursor::storeCurrentRow(RowData &data) const
+{
+// KexiDBDrvDbg << "pqxxSqlCursor::storeCurrentRow: POSITION IS " << (long)m_at<< endl;
+
+ if (m_res->size()<=0)
+ return;
+
+ const uint realCount = m_fieldCount + (m_containsROWIDInfo ? 1 : 0);
+ data.resize(realCount);
+
+ for( uint i=0; i<realCount; i++)
+ {
+ data[i] = pValue(i);
+ }
+}
+
+//==================================================================================
+//
+void pqxxSqlCursor::drv_clearServerResult()
+{
+//! @todo pqxxSqlCursor: stuff with server results
+}
+
+//==================================================================================
+//Add the current record to the internal buffer
+//Implementation required but no need in this driver
+//Result set is a buffer so do not need another
+void pqxxSqlCursor::drv_appendCurrentRecordToBuffer()
+{
+
+}
+
+//==================================================================================
+//Move internal pointer to internal buffer +1
+//Implementation required but no need in this driver
+void pqxxSqlCursor::drv_bufferMovePointerNext()
+{
+
+}
+
+//==================================================================================
+//Move internal pointer to internal buffer -1
+//Implementation required but no need in this driver
+void pqxxSqlCursor::drv_bufferMovePointerPrev()
+{
+
+}
+
+//==================================================================================
+//Move internal pointer to internal buffer to N
+//Implementation required but no need in this driver
+void pqxxSqlCursor::drv_bufferMovePointerTo(Q_LLONG to)
+{
+ Q_UNUSED(to);
+}
+
diff --git a/kexi/kexidb/drivers/pqxx/pqxxcursor.h b/kexi/kexidb/drivers/pqxx/pqxxcursor.h
new file mode 100644
index 000000000..d596acca2
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxcursor.h
@@ -0,0 +1,110 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_CURSOR_PQXX_H
+#define KEXIDB_CURSOR_PQXX_H
+
+#include <kexidb/cursor.h>
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+
+#if 0
+#include <pqxx/all.h>
+#else
+#include <pqxx/pqxx>
+#endif
+
+#include <pqxx/binarystring>
+#include <migration/pqxx/pg_type.h>
+
+namespace KexiDB {
+
+class pqxxSqlCursor: public Cursor {
+public:
+ virtual ~pqxxSqlCursor();
+
+ virtual QVariant value(uint i);
+ virtual const char** rowData() const;
+ virtual void storeCurrentRow(RowData &data) const;
+
+//TODO virtual const char *** bufferData()
+
+//TODO virtual int serverResult() const;
+
+//TODO virtual QString serverResultName() const;
+
+//TODO virtual QString serverErrorMsg() const;
+
+protected:
+ pqxxSqlCursor(Connection* conn, const QString& statement = QString::null, uint options = NoOptions );
+ pqxxSqlCursor(Connection* conn, QuerySchema& query, uint options = NoOptions );
+ virtual void drv_clearServerResult();
+ virtual void drv_appendCurrentRecordToBuffer();
+ virtual void drv_bufferMovePointerNext();
+ virtual void drv_bufferMovePointerPrev();
+ virtual void drv_bufferMovePointerTo(Q_LLONG to);
+ virtual bool drv_open();
+ virtual bool drv_close();
+ virtual void drv_getNextRecord();
+ virtual void drv_getPrevRecord();
+
+private:
+ pqxx::result* m_res;
+// pqxx::nontransaction* m_tran;
+ pqxx::connection* my_conn;
+ QVariant pValue(uint pos)const;
+ bool m_implicityStarted : 1;
+ //QByteArray processBinaryData(pqxx::binarystring*) const;
+ friend class pqxxSqlConnection;
+};
+
+inline QVariant pgsqlCStrToVariant(const pqxx::result::field& r)
+{
+ switch(r.type())
+ {
+ case BOOLOID:
+ return QString::fromLatin1(r.c_str(), r.size())=="true"; //TODO check formatting
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ return r.as(int());
+ case FLOAT4OID:
+ case FLOAT8OID:
+ case NUMERICOID:
+ return r.as(double());
+ case DATEOID:
+ return QString::fromUtf8(r.c_str(), r.size()); //TODO check formatting
+ case TIMEOID:
+ return QString::fromUtf8(r.c_str(), r.size()); //TODO check formatting
+ case TIMESTAMPOID:
+ return QString::fromUtf8(r.c_str(), r.size()); //TODO check formatting
+ case BYTEAOID:
+ return KexiDB::pgsqlByteaToByteArray(r.c_str(), r.size());
+ case BPCHAROID:
+ case VARCHAROID:
+ case TEXTOID:
+ return QString::fromUtf8(r.c_str(), r.size()); //utf8?
+ default:
+ return QString::fromUtf8(r.c_str(), r.size()); //utf8?
+ }
+}
+
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/pqxx/pqxxdriver.cpp b/kexi/kexidb/drivers/pqxx/pqxxdriver.cpp
new file mode 100644
index 000000000..d8e6216d1
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxdriver.cpp
@@ -0,0 +1,181 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/connection.h>
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver_p.h>
+#include <kexidb/utils.h>
+#include "pqxxdriver.h"
+#include "pqxxconnection.h"
+#include <string>
+
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+KEXIDB_DRIVER_INFO( pqxxSqlDriver, pqxxsql )
+
+//==================================================================================
+//
+pqxxSqlDriver::pqxxSqlDriver( QObject *parent, const char *name, const QStringList &args )
+ : Driver( parent, name, args )
+{
+ d->isFileDriver = false;
+ d->features = SingleTransactions | CursorForward | CursorBackward;
+//! @todo enable this when kexidb supports multiple: d->features = MultipleTransactions | CursorForward | CursorBackward;
+
+ beh->UNSIGNED_TYPE_KEYWORD = "";
+ beh->ROW_ID_FIELD_NAME = "oid";
+ beh->SPECIAL_AUTO_INCREMENT_DEF = false;
+ beh->AUTO_INCREMENT_TYPE = "SERIAL";
+ beh->AUTO_INCREMENT_FIELD_OPTION = "";
+ beh->AUTO_INCREMENT_PK_FIELD_OPTION = "PRIMARY KEY";
+ beh->ALWAYS_AVAILABLE_DATABASE_NAME = "template1";
+ beh->QUOTATION_MARKS_FOR_IDENTIFIER = '"';
+ beh->SQL_KEYWORDS = keywords;
+ initSQLKeywords(233);
+
+ //predefined properties
+ d->properties["client_library_version"] = "";//TODO
+ d->properties["default_server_encoding"] = ""; //TODO
+
+ d->typeNames[Field::Byte]="SMALLINT";
+ d->typeNames[Field::ShortInteger]="SMALLINT";
+ d->typeNames[Field::Integer]="INTEGER";
+ d->typeNames[Field::BigInteger]="BIGINT";
+ d->typeNames[Field::Boolean]="BOOLEAN";
+ d->typeNames[Field::Date]="DATE";
+ d->typeNames[Field::DateTime]="TIMESTAMP";
+ d->typeNames[Field::Time]="TIME";
+ d->typeNames[Field::Float]="REAL";
+ d->typeNames[Field::Double]="DOUBLE PRECISION";
+ d->typeNames[Field::Text]="CHARACTER VARYING";
+ d->typeNames[Field::LongText]="TEXT";
+ d->typeNames[Field::BLOB]="BYTEA";
+}
+
+//==================================================================================
+//Override the default implementation to allow for NUMERIC type natively
+QString pqxxSqlDriver::sqlTypeName(int id_t, int p) const
+{
+ if (id_t==Field::Null)
+ return "NULL";
+ if (id_t==Field::Float || id_t==Field::Double)
+ {
+ if (p>0)
+ {
+ return "NUMERIC";
+ }
+ else
+ {
+ return d->typeNames[id_t];
+ }
+ }
+ else
+ {
+ return d->typeNames[id_t];
+ }
+}
+
+//==================================================================================
+//
+pqxxSqlDriver::~pqxxSqlDriver()
+{
+// delete d;
+}
+
+//==================================================================================
+//
+KexiDB::Connection*
+pqxxSqlDriver::drv_createConnection( ConnectionData &conn_data )
+{
+ return new pqxxSqlConnection( this, conn_data );
+}
+
+//==================================================================================
+//
+bool pqxxSqlDriver::isSystemObjectName( const QString& n ) const
+{
+ return Driver::isSystemObjectName(n);
+}
+
+//==================================================================================
+//
+bool pqxxSqlDriver::drv_isSystemFieldName( const QString& ) const
+{
+ return false;
+}
+
+//==================================================================================
+//
+bool pqxxSqlDriver::isSystemDatabaseName( const QString& n ) const
+{
+ return n.lower()=="template1" || n.lower()=="template0";
+}
+
+//==================================================================================
+//
+QString pqxxSqlDriver::escapeString( const QString& str) const
+{
+ return QString::fromLatin1("'")
+ + QString::fromAscii( pqxx::sqlesc(std::string(str.utf8())).c_str() )
+ + QString::fromLatin1("'");
+}
+
+//==================================================================================
+//
+QCString pqxxSqlDriver::escapeString( const QCString& str) const
+{
+ return QCString("'")
+ + QCString( pqxx::sqlesc(QString(str).ascii()).c_str() )
+ + QCString("'");
+}
+
+//==================================================================================
+//
+QString pqxxSqlDriver::drv_escapeIdentifier( const QString& str) const {
+ return QString(str).replace( '"', "\"\"" );
+}
+
+//==================================================================================
+//
+QCString pqxxSqlDriver::drv_escapeIdentifier( const QCString& str) const {
+ return QCString(str).replace( '"', "\"\"" );
+}
+
+//==================================================================================
+//
+QString pqxxSqlDriver::escapeBLOB(const QByteArray& array) const
+{
+ return KexiDB::escapeBLOB(array, KexiDB::BLOBEscapeOctal);
+}
+
+QString pqxxSqlDriver::valueToSQL( uint ftype, const QVariant& v ) const
+{
+ if (ftype==Field::Boolean) {
+ // use SQL compliant TRUE or FALSE as described here
+ // http://www.postgresql.org/docs/8.0/interactive/datatype-boolean.html
+ // 1 or 0 does not work
+ return v.toInt()==0 ? QString::fromLatin1("FALSE") : QString::fromLatin1("TRUE");
+ }
+ return Driver::valueToSQL(ftype, v);
+}
+
+
+#include "pqxxdriver.moc"
diff --git a/kexi/kexidb/drivers/pqxx/pqxxdriver.h b/kexi/kexidb/drivers/pqxx/pqxxdriver.h
new file mode 100644
index 000000000..bbfdddc3e
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxdriver.h
@@ -0,0 +1,71 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_DRIVER_PQXX_H
+#define KEXIDB_DRIVER_PQXX_H
+
+#include <qstringlist.h>
+
+#include <kexidb/driver.h>
+
+namespace KexiDB
+{
+
+class Connection;
+class DriverManager;
+
+//! PostgreSQL database driver.
+class pqxxSqlDriver : public Driver
+{
+ Q_OBJECT
+ KEXIDB_DRIVER
+
+ public:
+ pqxxSqlDriver( QObject *parent, const char *name, const QStringList &args = QStringList() );
+ ~pqxxSqlDriver();
+
+ virtual bool isSystemObjectName( const QString& n )const;
+ virtual bool isSystemDatabaseName( const QString& n )const;
+
+ //! Escape a string for use as a value
+ virtual QString escapeString( const QString& str) const;
+ virtual QCString escapeString( const QCString& str) const;
+ virtual QString sqlTypeName(int id_t, int p=0) const;
+
+ //! Escape BLOB value \a array
+ virtual QString escapeBLOB(const QByteArray& array) const;
+
+ /*! Escapes and converts value \a v (for type \a ftype)
+ to string representation required by SQL commands.
+ Reimplemented for boolean type only to use SQL compliant TRUE or FALSE */
+ virtual QString valueToSQL( uint ftype, const QVariant& v ) const;
+
+ protected:
+ virtual QString drv_escapeIdentifier( const QString& str) const;
+ virtual QCString drv_escapeIdentifier( const QCString& str) const;
+ virtual Connection *drv_createConnection( ConnectionData &conn_data );
+ virtual bool drv_isSystemFieldName( const QString& n )const;
+
+ private:
+ static const char *keywords[];
+};
+
+};
+
+#endif
diff --git a/kexi/kexidb/drivers/pqxx/pqxxkeywords.cpp b/kexi/kexidb/drivers/pqxx/pqxxkeywords.cpp
new file mode 100644
index 000000000..cc1a9f6e9
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxkeywords.cpp
@@ -0,0 +1,244 @@
+ /*
+ * This file has been automatically generated from
+ * koffice/kexi/tools/sql_keywords/sql_keywords.sh and
+ * postgresql-7.4.6/src/backend/parser/keywords.c.
+ *
+ * Please edit the sql_keywords.sh, not this file!
+ */
+#include <pqxxdriver.h>
+
+namespace KexiDB {
+ const char* pqxxSqlDriver::keywords[] = {
+ "ABORT",
+ "ABSOLUTE",
+ "ACCESS",
+ "ACTION",
+ "ADD",
+ "AGGREGATE",
+ "ALTER",
+ "ANALYSE",
+ "ANALYZE",
+ "ANY",
+ "ARRAY",
+ "ASSERTION",
+ "ASSIGNMENT",
+ "AT",
+ "AUTHORIZATION",
+ "BACKWARD",
+ "BIGINT",
+ "BINARY",
+ "BIT",
+ "BOOLEAN",
+ "BOTH",
+ "CACHE",
+ "CALLED",
+ "CAST",
+ "CHAIN",
+ "CHAR",
+ "CHARACTER",
+ "CHARACTERISTICS",
+ "CHECKPOINT",
+ "CLASS",
+ "CLOSE",
+ "CLUSTER",
+ "COALESCE",
+ "COLUMN",
+ "COMMENT",
+ "COMMITTED",
+ "CONSTRAINTS",
+ "CONVERSION",
+ "CONVERT",
+ "COPY",
+ "CREATEDB",
+ "CREATEUSER",
+ "CURRENT_DATE",
+ "CURRENT_TIME",
+ "CURRENT_TIMESTAMP",
+ "CURRENT_USER",
+ "CURSOR",
+ "CYCLE",
+ "DAY",
+ "DEALLOCATE",
+ "DEC",
+ "DECIMAL",
+ "DECLARE",
+ "DEFAULTS",
+ "DEFERRABLE",
+ "DEFERRED",
+ "DEFINER",
+ "DELIMITER",
+ "DELIMITERS",
+ "DO",
+ "DOMAIN",
+ "DOUBLE",
+ "EACH",
+ "ENCODING",
+ "ENCRYPTED",
+ "ESCAPE",
+ "EXCEPT",
+ "EXCLUDING",
+ "EXCLUSIVE",
+ "EXECUTE",
+ "EXISTS",
+ "EXTERNAL",
+ "EXTRACT",
+ "FALSE",
+ "FETCH",
+ "FIRST",
+ "FLOAT",
+ "FORCE",
+ "FORWARD",
+ "FREEZE",
+ "FUNCTION",
+ "GLOBAL",
+ "GRANT",
+ "HANDLER",
+ "HOLD",
+ "HOUR",
+ "ILIKE",
+ "IMMEDIATE",
+ "IMMUTABLE",
+ "IMPLICIT",
+ "INCLUDING",
+ "INCREMENT",
+ "INHERITS",
+ "INITIALLY",
+ "INOUT",
+ "INPUT",
+ "INSENSITIVE",
+ "INSTEAD",
+ "INT",
+ "INTERSECT",
+ "INTERVAL",
+ "INVOKER",
+ "ISNULL",
+ "ISOLATION",
+ "LANCOMPILER",
+ "LANGUAGE",
+ "LAST",
+ "LEADING",
+ "LEVEL",
+ "LISTEN",
+ "LOAD",
+ "LOCAL",
+ "LOCALTIME",
+ "LOCALTIMESTAMP",
+ "LOCATION",
+ "LOCK",
+ "MAXVALUE",
+ "MINUTE",
+ "MINVALUE",
+ "MODE",
+ "MONTH",
+ "MOVE",
+ "NAMES",
+ "NATIONAL",
+ "NCHAR",
+ "NEW",
+ "NEXT",
+ "NO",
+ "NOCREATEDB",
+ "NOCREATEUSER",
+ "NONE",
+ "NOTHING",
+ "NOTIFY",
+ "NOTNULL",
+ "NULLIF",
+ "NUMERIC",
+ "OF",
+ "OFF",
+ "OIDS",
+ "OLD",
+ "ONLY",
+ "OPERATOR",
+ "OPTION",
+ "OUT",
+ "OVERLAPS",
+ "OVERLAY",
+ "OWNER",
+ "PARTIAL",
+ "PASSWORD",
+ "PATH",
+ "PENDANT",
+ "PLACING",
+ "POSITION",
+ "PRECISION",
+ "PREPARE",
+ "PRESERVE",
+ "PRIOR",
+ "PRIVILEGES",
+ "PROCEDURAL",
+ "PROCEDURE",
+ "READ",
+ "REAL",
+ "RECHECK",
+ "REINDEX",
+ "RELATIVE",
+ "RENAME",
+ "RESET",
+ "RESTART",
+ "RETURNS",
+ "REVOKE",
+ "ROWS",
+ "RULE",
+ "SCHEMA",
+ "SCROLL",
+ "SECOND",
+ "SECURITY",
+ "SEQUENCE",
+ "SERIALIZABLE",
+ "SESSION",
+ "SESSION_USER",
+ "SETOF",
+ "SHARE",
+ "SHOW",
+ "SIMPLE",
+ "SMALLINT",
+ "SOME",
+ "STABLE",
+ "START",
+ "STATEMENT",
+ "STATISTICS",
+ "STDIN",
+ "STDOUT",
+ "STORAGE",
+ "STRICT",
+ "SUBSTRING",
+ "SYSID",
+ "TEMP",
+ "TEMPLATE",
+ "TIME",
+ "TIMESTAMP",
+ "TOAST",
+ "TRAILING",
+ "TREAT",
+ "TRIGGER",
+ "TRIM",
+ "TRUE",
+ "TRUNCATE",
+ "TRUSTED",
+ "TYPE",
+ "UNENCRYPTED",
+ "UNKNOWN",
+ "UNLISTEN",
+ "UNTIL",
+ "USAGE",
+ "USER",
+ "VACUUM",
+ "VALID",
+ "VALIDATOR",
+ "VARCHAR",
+ "VARYING",
+ "VERBOSE",
+ "VERSION",
+ "VIEW",
+ "VOLATILE",
+ "WITH",
+ "WITHOUT",
+ "WORK",
+ "WRITE",
+ "YEAR",
+ "ZONE",
+ 0
+ };
+}
diff --git a/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.cpp b/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.cpp
new file mode 100644
index 000000000..5c87f78a9
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.cpp
@@ -0,0 +1,56 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+//
+// C++ Implementation: pqxxpreparedstatement
+//
+// Description:
+//
+//
+// Author: Adam Pigg <adam@piggz.co.uk>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+#include "pqxxpreparedstatement.h"
+#include <kdebug.h>
+using namespace KexiDB;
+
+pqxxPreparedStatement::pqxxPreparedStatement(
+ StatementType type, ConnectionInternal& conn, FieldList& fields)
+ : KexiDB::PreparedStatement(type, conn, fields)
+ , m_conn(conn.connection)
+{
+// KexiDBDrvDbg << "pqxxPreparedStatement: Construction" << endl;
+}
+
+
+pqxxPreparedStatement::~pqxxPreparedStatement()
+{
+}
+
+bool pqxxPreparedStatement::execute()
+{
+// KexiDBDrvDbg << "pqxxPreparedStatement::execute()" << endl;
+ m_resetRequired = true;
+ if (m_conn->insertRecord(*m_fields, m_args)) {
+ return true;
+ }
+ return false;
+}
+
+
diff --git a/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.h b/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.h
new file mode 100644
index 000000000..232454d3b
--- /dev/null
+++ b/kexi/kexidb/drivers/pqxx/pqxxpreparedstatement.h
@@ -0,0 +1,49 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Adam Pigg <adam@piggz.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+//
+// C++ Interface: pqxxpreparedstatement
+//
+// Description:
+//
+//
+#ifndef PQXXPREPAREDSTATEMENT_H
+#define PQXXPREPAREDSTATEMENT_H
+#include <kexidb/preparedstatement.h>
+#include <kexidb/connection_p.h>
+
+namespace KexiDB
+{
+/**
+ @author Adam Pigg <adam@piggz.co.uk>
+*/
+class pqxxPreparedStatement : public PreparedStatement
+{
+ public:
+ pqxxPreparedStatement(StatementType type, ConnectionInternal& conn, FieldList& fields);
+
+ virtual ~pqxxPreparedStatement();
+
+ virtual bool execute();
+ bool m_resetRequired : 1;
+
+ private:
+ Connection* m_conn;
+};
+}
+#endif
diff --git a/kexi/kexidb/drivers/sqlite/Makefile.am b/kexi/kexidb/drivers/sqlite/Makefile.am
new file mode 100644
index 000000000..fc0ad677d
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/Makefile.am
@@ -0,0 +1,27 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexidb_sqlite3driver.la
+
+INCLUDES = -I$(top_srcdir)/kexi/3rdparty/kexisql3/src -I$(srcdir)/../.. \
+ -I$(top_srcdir)/kexi $(all_includes)
+
+kexidb_sqlite3driver_la_METASOURCES = AUTO
+
+kexidb_sqlite3driver_la_SOURCES = sqliteconnection.cpp sqlitedriver.cpp sqlitecursor.cpp \
+sqlitekeywords.cpp sqlitepreparedstatement.cpp sqlitevacuum.cpp sqliteadmin.cpp \
+sqlitealter.cpp
+
+kexidb_sqlite3driver_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) \
+ $(top_builddir)/kexi/3rdparty/kexisql3/src/libkexisql3.la \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la
+
+kexidb_sqlite3driver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO)
+
+
+kde_services_DATA = kexidb_sqlite3driver.desktop
+
+
+KDE_CXXFLAGS += -DKEXIDB_SQLITE_DRIVER_EXPORT= -D__KEXIDB__= \
+ -include $(top_srcdir)/kexi/kexidb/global.h
+
diff --git a/kexi/kexidb/drivers/sqlite/driver/sqlite.h b/kexi/kexidb/drivers/sqlite/driver/sqlite.h
new file mode 100644
index 000000000..680f81e2a
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/driver/sqlite.h
@@ -0,0 +1,687 @@
+/*
+** 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 614463 2006-12-17 21:08:15Z staniek $
+*/
+#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.2"
+
+/*
+** 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
+
+/*
+** 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 contraint 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_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*);
+
+/* If the parameter to this routine is one of the return value constants
+** defined above, then this routine returns a constant text string which
+** descripts (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" milleseconds of sleeping have been done. After
+** "ms" milleseconds 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*,...);
+
+/*
+** 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. The datatype
+** will be numeric or text (the only two types supported) if the
+** argument is SQLITE_NUMERIC or SQLITE_TEXT. If the argument is
+** SQLITE_ARGS, then the datatype is numeric if any argument to the
+** function is numeric and is text otherwise. If the second argument
+** is an integer, then the datatype of the result is the same as the
+** parameter to the function that corresponds to that integer.
+*/
+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 */
+
+/*
+** 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 cancelled, depending on the circumstances,
+** and the result code returned will be SQLITE_ABORT.
+*/
+int sqlite_finalize(sqlite_vm*, char **pzErrMsg);
+
+#ifdef __cplusplus
+} /* End of the 'extern "C"' block */
+#endif
+
+#endif /* _SQLITE_H_ */
diff --git a/kexi/kexidb/drivers/sqlite/kexidb_sqlite3driver.desktop b/kexi/kexidb/drivers/sqlite/kexidb_sqlite3driver.desktop
new file mode 100644
index 000000000..92e599312
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/kexidb_sqlite3driver.desktop
@@ -0,0 +1,56 @@
+[Desktop Entry]
+Name=SQLite3
+Name[sv]=Sqlite 3
+Comment=SQLite is default Kexi embedded SQL engine
+Comment[bg]=SQLite е СУБД по подразбиране в Kexi , с вградено SQL ядро
+Comment[ca]=SQLite és el motor SQL encastat i per defecte de Kexi
+Comment[cy]=Y peiriant SQL mewnadeiledig rhagosod Kexi yw SQLite
+Comment[da]=SQLite er en standard Kexi-indlejret SQL-motor
+Comment[de]=SQLite ist der standardmäßig in Kexi eingebettete SQL-Treiber
+Comment[el]=Η SQLite είναι η προκαθορισμένη ενσωματωμένη μηχανή SQL του Kexi
+Comment[es]=SQLite es el motor SQL incrustado en Kexi de forma predefinida
+Comment[et]=SQLite on Kexi vaikimisi kasutatav SQL mootor
+Comment[eu]=SQLite Kexi-ren kapsultatutako SQL motore lehenetsia da
+Comment[fa]=SQLite، پیش‌فرض موتور SQL نهفتۀ Kexi است
+Comment[fi]=SQLite on Kexin käyttämä sisäänrakennetti SQL-moottori.
+Comment[fr]=SQLite est le moteur SQL par défaut intégré à Kexi
+Comment[fy]=SQLite is de standert SQL-databank foar Kexi
+Comment[gl]=SQLite é o motor embebido de SQL de Kexi
+Comment[he]=SQLite הוא מנוע ה־SQL המוטבע של Kexi המשמש כברירת מחדל
+Comment[hi]=केएक्साई एम्बेडेड एसक्यूएल इंजिन के लिए एसक्यूएललाइट डिफ़ॉल्ट है
+Comment[hr]=SQLite je zadani Kexi ugrađeni pogon SQL pogona
+Comment[hu]=Az SQLite a Kexi alapértelmezett, beépített SQL-motorja
+Comment[is]=SQLite er sjálfgefna Kexi SQL vélin
+Comment[it]=SQLite è il motore predefinito integrato in Kexi
+Comment[ja]=SQLite は Kexi の標準埋め込み SQL エンジンです。
+Comment[km]=SQLite គឺ​ជា​ម៉ាស៊ីន SQL ដែល​បាន​បង្កប់​ក្នុង Kexi តាម​លំនាំដើម
+Comment[lv]=SQLite ir Kexi noklusējuma SQL dzinējs
+Comment[ms]=SQLite adalah KeXi piawai yang dipasang dalam enjin SQL
+Comment[nb]=SQLite er den innebygde SQL-motoren i Kexi
+Comment[nds]=SQLite is de standardinbett SQL-Driever för Kexi
+Comment[ne]=SQLite पूर्वनिर्धारित केक्सी सम्मिलित SQL इन्जिन हो
+Comment[nl]=SQLite is de standaard SQL-database voor Kexi
+Comment[nn]=SQLite er den innebygde SQL-motoren i Kexi
+Comment[pl]=SQLite jest domyślnym wbudowanym silnikiem SQL dla Kexi
+Comment[pt]=SQLite é o motor embebido de SQL do Kexi
+Comment[pt_BR]=SQLite é o mecanismo SQL embutido padrão do Kexi
+Comment[ru]=SQLite -- движок встроенного SQL по умолчанию в Kexi
+Comment[se]=SQLite lea Kexi:a sisahuksejuvvon SQL-mohtor
+Comment[sk]=SQLite je štandardný Kexi embedded SQL systém
+Comment[sl]=SQLite je privzet vključen pogon SQL za Kexi
+Comment[sr]=SQLite је подразумевани Kexi-јев уграђен SQL мотор
+Comment[sr@Latn]=SQLite je podrazumevani Kexi-jev ugrađen SQL motor
+Comment[sv]=Sqlite är inbyggd SQL-standarddatabas i Kexi
+Comment[tg]=SQLite -- движоки SQL, ки дар дар дохили Kexi бо пешфарз сохта шудааст
+Comment[tr]=SQlite Kexi'ye gömülü varsayılan SQL motorudur
+Comment[uk]=SQLite - це типовий рушій SQL вбудований в Kexi
+Comment[zh_CN]=SQLite 是嵌入 Kexi 的默认 SQL 引擎
+Comment[zh_TW]=SQLite 是 Kexi 預設內建的 SQL 引擎
+X-KDE-Library=kexidb_sqlite3driver
+ServiceTypes=Kexi/DBDriver
+Type=Service
+InitialPreference=8
+X-Kexi-DriverName=SQLite3
+X-Kexi-DriverType=File
+X-Kexi-FileDBDriverMimeList=application/x-sqlite3,application/x-kexiproject-sqlite3,application/x-hk_classes-sqlite3
+X-Kexi-KexiDBVersion=1.8
diff --git a/kexi/kexidb/drivers/sqlite/sqlite.pro b/kexi/kexidb/drivers/sqlite/sqlite.pro
new file mode 100644
index 000000000..7fde2926c
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlite.pro
@@ -0,0 +1,10 @@
+include( sqlite_common.pro )
+
+win32:LIBS += $$KDELIBDESTDIR/kexisql3$$KDELIBDEBUGLIB
+
+INCLUDEPATH += $(KEXI)/3rdparty/kexisql3/src
+
+TARGET = kexidb_sqlite3driver$$KDELIBDEBUG
+
+SOURCES += sqlitekeywords.cpp \
+sqlitevacuum.cpp
diff --git a/kexi/kexidb/drivers/sqlite/sqlite_common.pro b/kexi/kexidb/drivers/sqlite/sqlite_common.pro
new file mode 100644
index 000000000..81fb85b3f
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlite_common.pro
@@ -0,0 +1,16 @@
+include( ../common.pro )
+
+DEFINES += MAKE_KEXIDB_SQLITE_DRIVER_LIB
+
+system( bash kmoc )
+
+SOURCES = \
+sqliteconnection.cpp \
+sqlitedriver.cpp \
+sqliteadmin.cpp \
+sqlitecursor.cpp \
+sqlitepreparedstatement.cpp \
+sqlitealter.cpp
+
+HEADERS =
+
diff --git a/kexi/kexidb/drivers/sqlite/sqliteadmin.cpp b/kexi/kexidb/drivers/sqlite/sqliteadmin.cpp
new file mode 100644
index 000000000..8bd8085a9
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqliteadmin.cpp
@@ -0,0 +1,64 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qdir.h>
+
+#include "sqliteadmin.h"
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver_p.h>
+
+#ifndef SQLITE2
+# include "sqlitevacuum.h"
+#endif
+
+SQLiteAdminTools::SQLiteAdminTools()
+ : KexiDB::AdminTools()
+{
+}
+
+SQLiteAdminTools::~SQLiteAdminTools()
+{
+}
+
+bool SQLiteAdminTools::vacuum(const KexiDB::ConnectionData& data, const QString& databaseName)
+{
+ clearError();
+#ifdef SQLITE2
+ Q_UNUSED(data);
+ Q_UNUSED(databaseName);
+ return false;
+#else
+ KexiDB::DriverManager manager;
+ KexiDB::Driver *drv = manager.driver(data.driverName);
+ QString title( i18n("Could not compact database \"%1\".").arg(QDir::convertSeparators(databaseName)) );
+ if (!drv) {
+ setError(&manager, title);
+ return false;
+ }
+ SQLiteVacuum vacuum(data.dbPath()+QDir::separator()+databaseName);
+ tristate result = vacuum.run();
+ if (!result) {
+ setError(title);
+ return false;
+ }
+ else //success or cancelled
+ return true;
+#endif
+}
+
diff --git a/kexi/kexidb/drivers/sqlite/sqliteadmin.h b/kexi/kexidb/drivers/sqlite/sqliteadmin.h
new file mode 100644
index 000000000..d9f8a6b61
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqliteadmin.h
@@ -0,0 +1,36 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_SQLITEADMIN_H
+#define KEXIDB_SQLITEADMIN_H
+
+#include <kexidb/admin.h>
+
+//! @short An interface containing a set of tools for SQLite database administration.
+class SQLiteAdminTools : public KexiDB::AdminTools
+{
+ public:
+ SQLiteAdminTools();
+ virtual ~SQLiteAdminTools();
+
+ /*! Performs vacuum (compacting) for connection \a conn. */
+ virtual bool vacuum(const KexiDB::ConnectionData& data, const QString& databaseName);
+};
+
+#endif
diff --git a/kexi/kexidb/drivers/sqlite/sqlitealter.cpp b/kexi/kexidb/drivers/sqlite/sqlitealter.cpp
new file mode 100644
index 000000000..cc72e48ad
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitealter.cpp
@@ -0,0 +1,114 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+// ** bits of SQLiteConnection related to table altering **
+
+#include "sqliteconnection.h"
+#include <kexidb/utils.h>
+
+#include <kstaticdeleter.h>
+
+#include <qmap.h>
+
+using namespace KexiDB;
+
+enum SQLiteTypeAffinity { //as defined here: 2.1 Determination Of Column Affinity (http://sqlite.org/datatype3.html)
+ NoAffinity = 0, IntAffinity = 1, TextAffinity = 2, BLOBAffinity = 3
+};
+
+//! helper for affinityForType()
+static KStaticDeleter< QMap<int,int> > KexiDB_SQLite_affinityForType_deleter;
+QMap<int,int> *KexiDB_SQLite_affinityForType = 0;
+
+//! \return SQLite type affinity for \a type
+//! See doc/dev/alter_table_type_conversions.ods, page 2 for more info
+static SQLiteTypeAffinity affinityForType(Field::Type type)
+{
+ if (!KexiDB_SQLite_affinityForType) {
+ KexiDB_SQLite_affinityForType_deleter.setObject( KexiDB_SQLite_affinityForType, new QMap<int,int>() );
+ KexiDB_SQLite_affinityForType->insert(Field::Byte, IntAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::ShortInteger, IntAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::Integer, IntAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::BigInteger, IntAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::Boolean, IntAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::Date, TextAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::DateTime, TextAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::Time, TextAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::Float, IntAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::Double, IntAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::Text, TextAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::LongText, TextAffinity);
+ KexiDB_SQLite_affinityForType->insert(Field::BLOB, BLOBAffinity);
+ }
+ return static_cast<SQLiteTypeAffinity>((*KexiDB_SQLite_affinityForType)[(int)type]);
+}
+
+tristate SQLiteConnection::drv_changeFieldProperty(TableSchema &table, Field& field,
+ const QString& propertyName, const QVariant& value)
+{
+/* if (propertyName=="name") {
+
+ }*/
+ if (propertyName=="type") {
+ bool ok;
+ Field::Type type = KexiDB::intToFieldType( value.toUInt(&ok) );
+ if (!ok || Field::InvalidType == type) {
+ //! @todo msg
+ return false;
+ }
+ return changeFieldType(table, field, type);
+ }
+ // not found
+ return cancelled;
+}
+
+/*!
+ From http://sqlite.org/datatype3.html :
+ Version 3 enhances provides the ability to store integer and real numbers in a more compact
+ format and the capability to store BLOB data.
+
+ Each value stored in an SQLite database (or manipulated by the database engine) has one
+ of the following storage classes:
+ * NULL. The value is a NULL value.
+ * INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending
+ on the magnitude of the value.
+ * REAL. The value is a floating point value, stored as an 8-byte IEEE floating point number.
+ * TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16-LE).
+ * BLOB. The value is a blob of data, stored exactly as it was input.
+
+ Column Affinity
+ In SQLite version 3, the type of a value is associated with the value itself,
+ not with the column or variable in which the value is stored.
+.The type affinity of a column is the recommended type for data stored in that column.
+
+ See alter_table_type_conversions.ods for details.
+*/
+tristate SQLiteConnection::changeFieldType(TableSchema &table, Field& field,
+ Field::Type type)
+{
+ Q_UNUSED(table);
+ const Field::Type oldType = field.type();
+ const SQLiteTypeAffinity oldAffinity = affinityForType(oldType);
+ const SQLiteTypeAffinity newAffinity = affinityForType(type);
+ if (oldAffinity!=newAffinity) {
+ //type affinity will be changed
+ }
+
+ return cancelled;
+}
diff --git a/kexi/kexidb/drivers/sqlite/sqliteconnection.cpp b/kexi/kexidb/drivers/sqlite/sqliteconnection.cpp
new file mode 100644
index 000000000..6de41c59e
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqliteconnection.cpp
@@ -0,0 +1,414 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "sqliteconnection.h"
+#include "sqliteconnection_p.h"
+#include "sqlitecursor.h"
+#include "sqlitepreparedstatement.h"
+
+#include "sqlite.h"
+
+#ifndef SQLITE2
+# include "kexisql.h" //for isReadOnly()
+#endif
+
+#include <kexidb/driver.h>
+#include <kexidb/cursor.h>
+#include <kexidb/error.h>
+#include <kexiutils/utils.h>
+
+#include <qfile.h>
+#include <qdir.h>
+#include <qregexp.h>
+
+#include <kgenericfactory.h>
+#include <kdebug.h>
+
+//remove debug
+#undef KexiDBDrvDbg
+#define KexiDBDrvDbg if (0) kdDebug()
+
+using namespace KexiDB;
+
+SQLiteConnectionInternal::SQLiteConnectionInternal(Connection *connection)
+ : ConnectionInternal(connection)
+ , data(0)
+ , data_owned(true)
+ , errmsg_p(0)
+ , res(SQLITE_OK)
+ , temp_st(0x10000)
+#ifdef SQLITE3
+ , result_name(0)
+#endif
+{
+}
+
+SQLiteConnectionInternal::~SQLiteConnectionInternal()
+{
+ if (data_owned && data) {
+ free( data );
+ data = 0;
+ }
+//sqlite_freemem does this if (errmsg) {
+// free( errmsg );
+// errmsg = 0;
+// }
+}
+
+void SQLiteConnectionInternal::storeResult()
+{
+ if (errmsg_p) {
+ errmsg = errmsg_p;
+ sqlite_free(errmsg_p);
+ errmsg_p = 0;
+ }
+#ifdef SQLITE3
+ errmsg = (data && res!=SQLITE_OK) ? sqlite3_errmsg(data) : 0;
+#endif
+}
+
+/*! Used by driver */
+SQLiteConnection::SQLiteConnection( Driver *driver, ConnectionData &conn_data )
+ : Connection( driver, conn_data )
+ ,d(new SQLiteConnectionInternal(this))
+{
+}
+
+SQLiteConnection::~SQLiteConnection()
+{
+ KexiDBDrvDbg << "SQLiteConnection::~SQLiteConnection()" << endl;
+ //disconnect if was connected
+// disconnect();
+ destroy();
+ delete d;
+ KexiDBDrvDbg << "SQLiteConnection::~SQLiteConnection() ok" << endl;
+}
+
+bool SQLiteConnection::drv_connect(KexiDB::ServerVersionInfo& version)
+{
+ KexiDBDrvDbg << "SQLiteConnection::connect()" << endl;
+ version.string = QString(SQLITE_VERSION); //defined in sqlite3.h
+ QRegExp re("(\\d+)\\.(\\d+)\\.(\\d+)");
+ if (re.exactMatch(version.string)) {
+ version.major = re.cap(1).toUInt();
+ version.minor = re.cap(2).toUInt();
+ version.release = re.cap(3).toUInt();
+ }
+ return true;
+}
+
+bool SQLiteConnection::drv_disconnect()
+{
+ KexiDBDrvDbg << "SQLiteConnection::disconnect()" << endl;
+ return true;
+}
+
+bool SQLiteConnection::drv_getDatabasesList( QStringList &list )
+{
+ //this is one-db-per-file database
+ list.append( data()->fileName() ); //more consistent than dbFileName() ?
+ return true;
+}
+
+bool SQLiteConnection::drv_containsTable( const QString &tableName )
+{
+ bool success;
+ return resultExists(QString("select name from sqlite_master where type='table' and name LIKE %1")
+ .arg(driver()->escapeString(tableName)), success) && success;
+}
+
+bool SQLiteConnection::drv_getTablesList( QStringList &list )
+{
+ KexiDB::Cursor *cursor;
+ m_sql = "select lower(name) from sqlite_master where type='table'";
+ if (!(cursor = executeQuery( m_sql ))) {
+ KexiDBWarn << "Connection::drv_getTablesList(): !executeQuery()" << endl;
+ return false;
+ }
+ list.clear();
+ cursor->moveFirst();
+ while (!cursor->eof() && !cursor->error()) {
+ list += cursor->value(0).toString();
+ cursor->moveNext();
+ }
+ if (cursor->error()) {
+ deleteCursor(cursor);
+ return false;
+ }
+ return deleteCursor(cursor);
+}
+
+bool SQLiteConnection::drv_createDatabase( const QString &dbName )
+{
+ // SQLite creates a new db is it does not exist
+ return drv_useDatabase(dbName);
+#if 0
+ d->data = sqlite_open( QFile::encodeName( data()->fileName() ), 0/*mode: unused*/,
+ &d->errmsg_p );
+ d->storeResult();
+ return d->data != 0;
+#endif
+}
+
+bool SQLiteConnection::drv_useDatabase( const QString &dbName, bool *cancelled,
+ MessageHandler* msgHandler )
+{
+ Q_UNUSED(dbName);
+// KexiDBDrvDbg << "drv_useDatabase(): " << data()->fileName() << endl;
+#ifdef SQLITE2
+ Q_UNUSED(cancelled);
+ Q_UNUSED(msgHandler);
+ d->data = sqlite_open( QFile::encodeName( data()->fileName() ), 0/*mode: unused*/,
+ &d->errmsg_p );
+ d->storeResult();
+ return d->data != 0;
+#else //SQLITE3
+ //TODO: perhaps allow to use sqlite3_open16() as well for SQLite ~ 3.3 ?
+//! @todo add option (command line or in kexirc?)
+ int exclusiveFlag = Connection::isReadOnly() ? SQLITE_OPEN_READONLY : SQLITE_OPEN_WRITE_LOCKED; // <-- shared read + (if !r/o): exclusive write
+//! @todo add option
+ int allowReadonly = 1;
+ const bool wasReadOnly = Connection::isReadOnly();
+
+ d->res = sqlite3_open(
+ //QFile::encodeName( data()->fileName() ),
+ data()->fileName().utf8(), /* unicode expected since SQLite 3.1 */
+ &d->data,
+ exclusiveFlag,
+ allowReadonly /* If 1 and locking fails, try opening in read-only mode */
+ );
+ d->storeResult();
+
+ if (d->res == SQLITE_OK && cancelled && !wasReadOnly && allowReadonly && isReadOnly()) {
+ //opened as read only, ask
+ if (KMessageBox::Continue !=
+ askQuestion(
+ i18n("Do you want to open file \"%1\" as read-only?")
+ .arg(QDir::convertSeparators(data()->fileName()))
+ + "\n\n"
+ + i18n("The file is probably already open on this or another computer.") + " "
+ + i18n("Could not gain exclusive access for writing the file."),
+ KMessageBox::WarningContinueCancel, KMessageBox::Continue,
+ KGuiItem(i18n("Open As Read-Only"), "fileopen"), KStdGuiItem::cancel(),
+ "askBeforeOpeningFileReadOnly", KMessageBox::Notify, msgHandler ))
+ {
+ clearError();
+ if (!drv_closeDatabase())
+ return false;
+ *cancelled = true;
+ return false;
+ }
+ }
+
+ if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_READWRITE) {
+ setError(ERR_ACCESS_RIGHTS,
+ i18n("The file is probably already open on this or another computer.")+"\n\n"
+ + i18n("Could not gain exclusive access for reading and writing the file.") + " "
+ + i18n("Check the file's permissions and whether it is already opened and locked by another application."));
+ }
+ else if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_WRITE) {
+ setError(ERR_ACCESS_RIGHTS,
+ i18n("The file is probably already open on this or another computer.")+"\n\n"
+ + i18n("Could not gain exclusive access for writing the file.") + " "
+ + i18n("Check the file's permissions and whether it is already opened and locked by another application."));
+ }
+ return d->res == SQLITE_OK;
+#endif
+}
+
+bool SQLiteConnection::drv_closeDatabase()
+{
+ if (!d->data)
+ return false;
+
+#ifdef SQLITE2
+ sqlite_close(d->data);
+ d->data = 0;
+ return true;
+#else
+ const int res = sqlite_close(d->data);
+ if (SQLITE_OK == res) {
+ d->data = 0;
+ return true;
+ }
+ if (SQLITE_BUSY==res) {
+#if 0 //this is ANNOYING, needs fixing (by closing cursors or waiting)
+ setError(ERR_CLOSE_FAILED, i18n("Could not close busy database."));
+#else
+ return true;
+#endif
+ }
+ return false;
+#endif
+}
+
+bool SQLiteConnection::drv_dropDatabase( const QString &dbName )
+{
+ Q_UNUSED(dbName); // Each database is one single SQLite file.
+ const QString filename = data()->fileName();
+ if (QFile(filename).exists() && !QDir().remove(filename)) {
+ setError(ERR_ACCESS_RIGHTS, i18n("Could not remove file \"%1\".")
+ .arg(QDir::convertSeparators(filename)) + " "
+ + i18n("Check the file's permissions and whether it is already opened and locked by another application."));
+ return false;
+ }
+ return true;
+}
+
+//CursorData* SQLiteConnection::drv_createCursor( const QString& statement )
+Cursor* SQLiteConnection::prepareQuery( const QString& statement, uint cursor_options )
+{
+ return new SQLiteCursor( this, statement, cursor_options );
+}
+
+Cursor* SQLiteConnection::prepareQuery( QuerySchema& query, uint cursor_options )
+{
+ return new SQLiteCursor( this, query, cursor_options );
+}
+
+bool SQLiteConnection::drv_executeSQL( const QString& statement )
+{
+// KexiDBDrvDbg << "SQLiteConnection::drv_executeSQL(" << statement << ")" <<endl;
+// QCString st(statement.length()*2);
+// st = escapeString( statement.local8Bit() ); //?
+#ifdef SQLITE_UTF8
+ d->temp_st = statement.utf8();
+#else
+ d->temp_st = statement.local8Bit(); //latin1 only
+#endif
+
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addKexiDBDebug(QString("ExecuteSQL (SQLite): ")+statement);
+#endif
+
+ d->res = sqlite_exec(
+ d->data,
+ (const char*)d->temp_st,
+ 0/*callback*/,
+ 0,
+ &d->errmsg_p );
+ d->storeResult();
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addKexiDBDebug(d->res==SQLITE_OK ? " Success" : " Failure");
+#endif
+ return d->res==SQLITE_OK;
+}
+
+Q_ULLONG SQLiteConnection::drv_lastInsertRowID()
+{
+ return (Q_ULLONG)sqlite_last_insert_rowid(d->data);
+}
+
+int SQLiteConnection::serverResult()
+{
+ return d->res==0 ? Connection::serverResult() : d->res;
+}
+
+QString SQLiteConnection::serverResultName()
+{
+ QString r =
+#ifdef SQLITE2
+ QString::fromLatin1( sqlite_error_string(d->res) );
+#else //SQLITE3
+ QString::null; //fromLatin1( d->result_name );
+#endif
+ return r.isEmpty() ? Connection::serverResultName() : r;
+}
+
+void SQLiteConnection::drv_clearServerResult()
+{
+ if (!d)
+ return;
+ d->res = SQLITE_OK;
+#ifdef SQLITE2
+ d->errmsg_p = 0;
+#else
+// d->result_name = 0;
+#endif
+}
+
+QString SQLiteConnection::serverErrorMsg()
+{
+ return d->errmsg.isEmpty() ? Connection::serverErrorMsg() : d->errmsg;
+}
+
+PreparedStatement::Ptr SQLiteConnection::prepareStatement(PreparedStatement::StatementType type,
+ FieldList& fields)
+{
+//#ifndef SQLITE2 //TEMP IFDEF!
+ return new SQLitePreparedStatement(type, *d, fields);
+//#endif
+}
+
+bool SQLiteConnection::isReadOnly() const
+{
+#ifdef SQLITE2
+ return Connection::isReadOnly();
+#else
+ return d->data ? sqlite3_is_readonly(d->data) : false;
+#endif
+}
+
+#ifdef SQLITE2
+bool SQLiteConnection::drv_alterTableName(TableSchema& tableSchema, const QString& newName, bool replace)
+{
+ const QString oldTableName = tableSchema.name();
+ const bool destTableExists = this->tableSchema( newName ) != 0;
+
+ //1. drop the table
+ if (destTableExists) {
+ if (!replace)
+ return false;
+ if (!drv_dropTable( newName ))
+ return false;
+ }
+
+ //2. create a copy of the table
+//TODO: move this code to drv_copyTable()
+ tableSchema.setName(newName);
+
+//helper:
+#define drv_alterTableName_ERR \
+ tableSchema.setName(oldTableName) //restore old name
+
+ if (!drv_createTable( tableSchema )) {
+ drv_alterTableName_ERR;
+ return false;
+ }
+
+//TODO indices, etc.???
+
+ // 3. copy all rows to the new table
+ if (!executeSQL(QString::fromLatin1("INSERT INTO %1 SELECT * FROM %2")
+ .arg(escapeIdentifier(tableSchema.name())).arg(escapeIdentifier(oldTableName))))
+ {
+ drv_alterTableName_ERR;
+ return false;
+ }
+
+ // 4. drop old table.
+ if (!drv_dropTable( oldTableName )) {
+ drv_alterTableName_ERR;
+ return false;
+ }
+ return true;
+}
+#endif
+
+#include "sqliteconnection.moc"
diff --git a/kexi/kexidb/drivers/sqlite/sqliteconnection.h b/kexi/kexidb/drivers/sqlite/sqliteconnection.h
new file mode 100644
index 000000000..ba0d3b5a6
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqliteconnection.h
@@ -0,0 +1,125 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_CONN_SQLITE_H
+#define KEXIDB_CONN_SQLITE_H
+
+#include <qstringlist.h>
+
+#include <kexidb/connection.h>
+
+/*!
+ */
+
+namespace KexiDB
+{
+
+class SQLiteConnectionInternal;
+class Driver;
+
+//! sqlite-specific connection
+class SQLiteConnection : public Connection
+{
+ Q_OBJECT
+
+ public:
+ virtual ~SQLiteConnection();
+
+ virtual Cursor* prepareQuery( const QString& statement, uint cursor_options = 0 );
+ virtual Cursor* prepareQuery( QuerySchema& query, uint cursor_options = 0 );
+
+//#ifndef SQLITE2 //TEMP IFDEF!
+ virtual PreparedStatement::Ptr prepareStatement(PreparedStatement::StatementType type,
+ FieldList& fields);
+//#endif
+ /*! Reimplemented to provide real read-only flag of the connection */
+ virtual bool isReadOnly() const;
+
+ protected:
+ /*! Used by driver */
+ SQLiteConnection( Driver *driver, ConnectionData &conn_data );
+
+ virtual bool drv_connect(KexiDB::ServerVersionInfo& version);
+ virtual bool drv_disconnect();
+ virtual bool drv_getDatabasesList( QStringList &list );
+
+//TODO: move this somewhere to low level class (MIGRATION?)
+ virtual bool drv_getTablesList( QStringList &list );
+
+//TODO: move this somewhere to low level class (MIGRATION?)
+ virtual bool drv_containsTable( const QString &tableName );
+
+ /*! Creates new database using connection. Note: Do not pass \a dbName
+ arg because for file-based engine (that has one database per connection)
+ it is defined during connection. */
+ virtual bool drv_createDatabase( const QString &dbName = QString::null );
+
+ /*! Opens existing database using connection. Do not pass \a dbName
+ arg because for file-based engine (that has one database per connection)
+ it is defined during connection. If you pass it,
+ database file name will be changed. */
+ virtual bool drv_useDatabase( const QString &dbName = QString::null, bool *cancelled = 0,
+ MessageHandler* msgHandler = 0 );
+
+ virtual bool drv_closeDatabase();
+
+ /*! Drops database from the server using connection.
+ After drop, database shouldn't be accessible
+ anymore, so database file is just removed. See note from drv_useDatabase(). */
+ virtual bool drv_dropDatabase( const QString &dbName = QString::null );
+
+ //virtual bool drv_createTable( const KexiDB::Table& table );
+
+ virtual bool drv_executeSQL( const QString& statement );
+// virtual bool drv_executeQuery( const QString& statement );
+
+ virtual Q_ULLONG drv_lastInsertRowID();
+
+ virtual int serverResult();
+ virtual QString serverResultName();
+ virtual QString serverErrorMsg();
+ virtual void drv_clearServerResult();
+ virtual tristate drv_changeFieldProperty(TableSchema &table, Field& field,
+ const QString& propertyName, const QVariant& value);
+
+#ifdef SQLITE2
+ /*! Alters table's described \a tableSchema name to \a newName.
+ This implementation is ineffective but works.
+ - creates a copy of the table
+ - copies all rows
+ - drops old table.
+ All the above should be performed within single transaction.
+ \return true on success.
+ More advanced server backends implement this using "ALTER TABLE .. RENAME TO".
+ */
+ virtual bool drv_alterTableName(TableSchema& tableSchema, const QString& newName, bool replace = false);
+#endif
+
+ //! for drv_changeFieldProperty()
+ tristate changeFieldType(TableSchema &table, Field& field, Field::Type type);
+
+ SQLiteConnectionInternal* d;
+
+ friend class SQLiteDriver;
+ friend class SQLiteCursor;
+};
+
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/sqlite/sqliteconnection_p.h b/kexi/kexidb/drivers/sqlite/sqliteconnection_p.h
new file mode 100644
index 000000000..f295573d4
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqliteconnection_p.h
@@ -0,0 +1,73 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_SQLITECONN_P_H
+#define KEXIDB_SQLITECONN_P_H
+
+#include <kexidb/connection_p.h>
+
+#include "sqlite.h"
+
+//for compatibility
+#ifdef _SQLITE3_H_
+# define SQLITE3
+ typedef sqlite3 sqlite_struct;
+# define sqlite_free sqlite3_free
+# define sqlite_close sqlite3_close
+# define sqlite_exec sqlite3_exec
+# define sqlite_last_insert_rowid sqlite3_last_insert_rowid
+# define sqlite_error_string sqlite3_last_insert_row_id
+# define sqlite_libversion sqlite3_libversion
+# define sqlite_libencoding sqlite3_libencoding
+#else
+# ifndef SQLITE2
+# define SQLITE2
+# endif
+ typedef struct sqlite sqlite_struct;
+# define sqlite_free sqlite_freemem
+#endif
+
+namespace KexiDB
+{
+
+/*! Internal SQLite connection data. Also used inside SQLiteCursor. */
+class SQLiteConnectionInternal : public ConnectionInternal
+{
+ public:
+ SQLiteConnectionInternal(Connection* connection);
+ virtual ~SQLiteConnectionInternal();
+
+ //! stores last result's message
+ virtual void storeResult();
+
+ sqlite_struct *data;
+ bool data_owned; //!< true if data pointer should be freed on destruction
+ QString errmsg; //<! server-specific message of last operation
+ char *errmsg_p; //<! temporary: server-specific message of last operation
+ int res; //<! result code of last operation on server
+
+ QCString temp_st;
+#ifdef SQLITE3
+ const char *result_name;
+#endif
+};
+
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/sqlite/sqlitecursor.cpp b/kexi/kexidb/drivers/sqlite/sqlitecursor.cpp
new file mode 100644
index 000000000..4b18b437d
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitecursor.cpp
@@ -0,0 +1,567 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "sqlitecursor.h"
+
+#include "sqliteconnection.h"
+#include "sqliteconnection_p.h"
+
+#include <kexidb/error.h>
+#include <kexidb/driver.h>
+#include <kexiutils/utils.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qptrvector.h>
+#include <qdatetime.h>
+
+using namespace KexiDB;
+
+//! safer interpretations of boolean values for SQLite
+static bool sqliteStringToBool(const QString& s)
+{
+ return s.lower()=="yes" || (s.lower()!="no" && s!="0");
+}
+
+//----------------------------------------------------
+
+class KexiDB::SQLiteCursorData : public SQLiteConnectionInternal
+{
+ public:
+ SQLiteCursorData(Connection* conn)
+ :
+ SQLiteConnectionInternal(conn)
+// : curr_cols(0)
+// errmsg_p(0)
+// , res(SQLITE_OK)
+ , curr_coldata(0)
+ , curr_colname(0)
+ , cols_pointers_mem_size(0)
+// , rec_stored(false)
+/* MOVED TO Cursor:
+ , cols_pointers_mem_size(0)
+ , records_in_buf(0)
+ , buffering_completed(false)
+ , at_buffer(false)*/
+//#ifdef SQLITE3
+// , rowDataReadyToFetch(false)
+//#endif
+ {
+ data_owned = false;
+ }
+
+/*#ifdef SQLITE3
+ void fetchRowDataIfNeeded()
+ {
+ if (!rowDataReadyToFetch)
+ return true;
+ rowDataReadyToFetch = false;
+ m_fieldCount = sqlite3_data_count(data);
+ for (int i=0; i<m_fieldCount; i++) {
+
+ }
+ }
+#endif*/
+
+ QCString st;
+ //for sqlite:
+// sqlite_struct *data; //! taken from SQLiteConnection
+#ifdef SQLITE2
+ sqlite_vm *prepared_st_handle; //vm
+#else //SQLITE3
+ sqlite3_stmt *prepared_st_handle;
+#endif
+
+ char *utail;
+
+// QString errmsg; //<! server-specific message of last operation
+// char *errmsg_p; //<! temporary: server-specific message of last operation
+// int res; //<! result code of last operation on server
+
+// int curr_cols;
+ const char **curr_coldata;
+ const char **curr_colname;
+
+ int next_cols;
+// const char **next_coldata;
+// const char **next_colname;
+// bool rec_stored : 1; //! true, current record is stored in next_coldata
+
+/* MOVED TO Cursor:
+ uint cols_pointers_mem_size; //! size of record's array of pointers to values
+ int records_in_buf; //! number of records currently stored in the buffer
+ bool buffering_completed; //! true if we have already all records stored in the buffer
+ QPtrVector<const char*> records; //buffer data
+ bool at_buffer; //! true if we already point to the buffer with curr_coldata
+*/
+
+/* int prev_cols;
+ const char **prev_coldata;
+ const char **prev_colname;*/
+
+ uint cols_pointers_mem_size; //! size of record's array of pointers to values
+ QPtrVector<const char*> records;//! buffer data
+//#ifdef SQLITE3
+// bool rowDataReadyToFetch : 1;
+//#endif
+
+#ifdef SQLITE3
+ inline QVariant getValue(Field *f, int i)
+ {
+ int type = sqlite3_column_type(prepared_st_handle, i);
+ if (type==SQLITE_NULL) {
+ return QVariant();
+ }
+ else if (!f || type==SQLITE_TEXT) {
+//TODO: support for UTF-16
+#define GET_sqlite3_column_text QString::fromUtf8( (const char*)sqlite3_column_text(prepared_st_handle, i) )
+ if (!f || f->isTextType())
+ return GET_sqlite3_column_text;
+ else {
+ switch (f->type()) {
+ case Field::Date:
+ return QDate::fromString( GET_sqlite3_column_text, Qt::ISODate );
+ case Field::Time:
+ //QDateTime - a hack needed because QVariant(QTime) has broken isNull()
+ return KexiUtils::stringToHackedQTime(GET_sqlite3_column_text);
+ case Field::DateTime: {
+ QString tmp( GET_sqlite3_column_text );
+ tmp[10] = 'T'; //for ISODate compatibility
+ return QDateTime::fromString( tmp, Qt::ISODate );
+ }
+ case Field::Boolean:
+ return QVariant(sqliteStringToBool(GET_sqlite3_column_text), 1);
+ default:
+ return QVariant(); //TODO
+ }
+ }
+ }
+ else if (type==SQLITE_INTEGER) {
+ switch (f->type()) {
+ case Field::Byte:
+ case Field::ShortInteger:
+ case Field::Integer:
+ return QVariant( sqlite3_column_int(prepared_st_handle, i) );
+ case Field::BigInteger:
+ return QVariant( (Q_LLONG)sqlite3_column_int64(prepared_st_handle, i) );
+ case Field::Boolean:
+ return QVariant( sqlite3_column_int(prepared_st_handle, i)!=0, 1 );
+ default:;
+ }
+ if (f->isFPNumericType()) //WEIRD, YEAH?
+ return QVariant( (double)sqlite3_column_int(prepared_st_handle, i) );
+ else
+ return QVariant(); //TODO
+ }
+ else if (type==SQLITE_FLOAT) {
+ if (f && f->isFPNumericType())
+ return QVariant( sqlite3_column_double(prepared_st_handle, i) );
+ else if (!f || f->isIntegerType())
+ return QVariant( (double)sqlite3_column_double(prepared_st_handle, i) );
+ else
+ return QVariant(); //TODO
+ }
+ else if (type==SQLITE_BLOB) {
+ if (f && f->type()==Field::BLOB) {
+ QByteArray ba;
+//! @todo efficient enough?
+ ba.duplicate((const char*)sqlite3_column_blob(prepared_st_handle, i),
+ sqlite3_column_bytes(prepared_st_handle, i));
+ return ba;
+ } else
+ return QVariant(); //TODO
+ }
+ return QVariant();
+ }
+#endif //SQLITE3
+};
+
+SQLiteCursor::SQLiteCursor(Connection* conn, const QString& statement, uint options)
+ : Cursor( conn, statement, options )
+ , d( new SQLiteCursorData(conn) )
+{
+ d->data = static_cast<SQLiteConnection*>(conn)->d->data;
+}
+
+SQLiteCursor::SQLiteCursor(Connection* conn, QuerySchema& query, uint options )
+ : Cursor( conn, query, options )
+ , d( new SQLiteCursorData(conn) )
+{
+ d->data = static_cast<SQLiteConnection*>(conn)->d->data;
+}
+
+SQLiteCursor::~SQLiteCursor()
+{
+ close();
+ delete d;
+}
+
+bool SQLiteCursor::drv_open()
+{
+// d->st.resize(statement.length()*2);
+ //TODO: decode
+// d->st = statement.local8Bit();
+// d->st = m_conn->driver()->escapeString( statement.local8Bit() );
+
+ if(! d->data) {
+ // this may as example be the case if SQLiteConnection::drv_useDatabase()
+ // wasn't called before. Normaly sqlite_compile/sqlite3_prepare
+ // should handle it, but it crashes in in sqlite3SafetyOn at util.c:786
+ kdWarning() << "SQLiteCursor::drv_open(): Database handle undefined." << endl;
+ return false;
+ }
+
+#ifdef SQLITE2
+ d->st = m_sql.local8Bit();
+ d->res = sqlite_compile(
+ d->data,
+ d->st.data(),
+ (const char**)&d->utail,
+ &d->prepared_st_handle,
+ &d->errmsg_p );
+#else //SQLITE3
+ d->st = m_sql.utf8();
+ d->res = sqlite3_prepare(
+ d->data, /* Database handle */
+ d->st.data(), /* SQL statement, UTF-8 encoded */
+ d->st.length(), /* Length of zSql in bytes. */
+ &d->prepared_st_handle, /* OUT: Statement handle */
+ 0/*const char **pzTail*/ /* OUT: Pointer to unused portion of zSql */
+ );
+#endif
+ if (d->res!=SQLITE_OK) {
+ d->storeResult();
+ return false;
+ }
+//cursor is automatically @ first record
+// m_beforeFirst = true;
+
+ if (isBuffered()) {
+ d->records.resize(128); //TODO: manage size dynamically
+ }
+
+ return true;
+}
+
+/*bool SQLiteCursor::drv_getFirstRecord()
+{
+ bool ok = drv_getNextRecord();*/
+/* if ((m_options & Buffered) && ok) { //1st record is there:
+ //compute parameters for cursor's buffer:
+ //-size of record's array of pointer to values
+ d->cols_pointers_mem_size = d->curr_cols * sizeof(char*);
+ d->records_in_buf = 1;
+ }*/
+ /*return ok;
+}*/
+
+bool SQLiteCursor::drv_close()
+{
+#ifdef SQLITE2
+ d->res = sqlite_finalize( d->prepared_st_handle, &d->errmsg_p );
+#else //SQLITE3
+ d->res = sqlite3_finalize( d->prepared_st_handle );
+#endif
+ if (d->res!=SQLITE_OK) {
+ d->storeResult();
+ return false;
+ }
+ return true;
+}
+
+void SQLiteCursor::drv_getNextRecord()
+{
+#ifdef SQLITE2
+ static int _fieldCount;
+ d->res = sqlite_step(
+ d->prepared_st_handle,
+ &_fieldCount,
+ &d->curr_coldata,
+ &d->curr_colname);
+#else //SQLITE3
+ d->res = sqlite3_step( d->prepared_st_handle );
+#endif
+ if (d->res == SQLITE_ROW) {
+ m_result = FetchOK;
+#ifdef SQLITE2
+ m_fieldCount = (uint)_fieldCount;
+#else
+ m_fieldCount = sqlite3_data_count(d->prepared_st_handle);
+//#else //for SQLITE3 data fetching is delayed. Now we even do not take field count information
+// // -- just set a flag that we've a data not fetched but available
+// d->rowDataReadyToFetch = true;
+#endif
+ //(m_logicalFieldCount introduced) m_fieldCount -= (m_containsROWIDInfo ? 1 : 0);
+ } else {
+//#ifdef SQLITE3
+// d->rowDataReadyToFetch = false;
+//#endif
+ if (d->res==SQLITE_DONE)
+ m_result = FetchEnd;
+ else
+ m_result = FetchError;
+ }
+
+ //debug
+/*
+ if (m_result == FetchOK && d->curr_coldata) {
+ for (uint i=0;i<m_fieldCount;i++) {
+ KexiDBDrvDbg<<"col."<< i<<": "<< d->curr_colname[i]<<" "<< d->curr_colname[m_fieldCount+i]
+ << " = " << (d->curr_coldata[i] ? QString::fromLocal8Bit(d->curr_coldata[i]) : "(NULL)") <<endl;
+ }
+// KexiDBDrvDbg << "SQLiteCursor::drv_getNextRecord(): "<<m_fieldCount<<" col(s) fetched"<<endl;
+ }*/
+}
+
+void SQLiteCursor::drv_appendCurrentRecordToBuffer()
+{
+// KexiDBDrvDbg << "SQLiteCursor::drv_appendCurrentRecordToBuffer():" <<endl;
+ if (!d->cols_pointers_mem_size)
+ d->cols_pointers_mem_size = m_fieldCount * sizeof(char*);
+ const char **record = (const char**)malloc(d->cols_pointers_mem_size);
+ const char **src_col = d->curr_coldata;
+ const char **dest_col = record;
+ for (uint i=0; i<m_fieldCount; i++,src_col++,dest_col++) {
+// KexiDBDrvDbg << i <<": '" << *src_col << "'" <<endl;
+// KexiDBDrvDbg << "src_col: " << src_col << endl;
+ *dest_col = *src_col ? strdup(*src_col) : 0;
+ }
+ d->records.insert(m_records_in_buf,record);
+// KexiDBDrvDbg << "SQLiteCursor::drv_appendCurrentRecordToBuffer() ok." <<endl;
+}
+
+void SQLiteCursor::drv_bufferMovePointerNext()
+{
+ d->curr_coldata++; //move to next record in the buffer
+}
+
+void SQLiteCursor::drv_bufferMovePointerPrev()
+{
+ d->curr_coldata--; //move to prev record in the buffer
+}
+
+//compute a place in the buffer that contain next record's data
+//and move internal buffer pointer to that place
+void SQLiteCursor::drv_bufferMovePointerTo(Q_LLONG at)
+{
+ d->curr_coldata = d->records.at(at);
+}
+
+void SQLiteCursor::drv_clearBuffer()
+{
+ if (d->cols_pointers_mem_size>0) {
+ const uint records_in_buf = m_records_in_buf;
+ const char ***r_ptr = d->records.data();
+ for (uint i=0; i<records_in_buf; i++, r_ptr++) {
+ // const char **record = m_records.at(i);
+ const char **field_data = *r_ptr;
+ // for (int col=0; col<d->curr_cols; col++, field_data++) {
+ for (uint col=0; col<m_fieldCount; col++, field_data++) {
+ free((void*)*field_data); //free field memory
+ }
+ free(*r_ptr); //free pointers to fields array
+ }
+ }
+// d->curr_cols=0;
+// m_fieldCount=0;
+ m_records_in_buf=0;
+ d->cols_pointers_mem_size=0;
+// m_at_buffer=false;
+ d->records.clear();
+}
+
+/*
+void SQLiteCursor::drv_storeCurrentRecord()
+{
+#if 0
+ assert(!m_data->rec_stored);
+ m_data->rec_stored = true;
+ m_data->next_cols = m_data->curr_cols;
+ for (int i=0;i<m_data->curr_cols;i++) {
+ KexiDBDrvDbg<<"[COPY] "<<i<<": "<< m_data->curr_coldata[i]<<endl;
+ if (m_data->curr_coldata[i])
+ m_data->next_coldata[i] = strdup( m_data->curr_coldata[i] );
+ else
+ m_data->next_coldata[i] = 0;
+ }
+#endif
+}
+*/
+
+/*TODO
+const char *** SQLiteCursor::bufferData()
+{
+ if (!isBuffered())
+ return 0;
+ return m_records.data();
+}*/
+
+const char ** SQLiteCursor::rowData() const
+{
+ return d->curr_coldata;
+}
+
+void SQLiteCursor::storeCurrentRow(RowData &data) const
+{
+#ifdef SQLITE2
+ const char **col = d->curr_coldata;
+#endif
+ //const uint realCount = m_fieldCount + (m_containsROWIDInfo ? 1 : 0);
+ data.resize(m_fieldCount);
+ if (!m_fieldsExpanded) {//simple version: without types
+ for( uint i=0; i<m_fieldCount; i++ ) {
+#ifdef SQLITE2
+ data[i] = QVariant( *col );
+ col++;
+#else //SQLITE3
+ data[i] = QString::fromUtf8( (const char*)sqlite3_column_text(d->prepared_st_handle, i) );
+#endif
+ }
+ return;
+ }
+
+ //const uint fieldsExpandedCount = m_fieldsExpanded->count();
+ const uint maxCount = QMIN(m_fieldCount, m_fieldsExpanded->count());
+ // i - visible field's index, j - physical index
+ for( uint i=0, j=0; i<m_fieldCount; i++, j++ ) {
+// while (j < m_detailedVisibility.count() && !m_detailedVisibility[j]) //!m_query->isColumnVisible(j))
+// j++;
+ while (j < maxCount && !m_fieldsExpanded->at(j)->visible)
+ j++;
+ if (j >= (maxCount /*+(m_containsROWIDInfo ? 1 : 0)*/)) {
+ //ERR!
+ break;
+ }
+ //(m_logicalFieldCount introduced) Field *f = (m_containsROWIDInfo && i>=m_fieldCount) ? 0 : m_fieldsExpanded->at(j)->field;
+ Field *f = (i>=m_fieldCount) ? 0 : m_fieldsExpanded->at(j)->field;
+// KexiDBDrvDbg << "SQLiteCursor::storeCurrentRow(): col=" << (col ? *col : 0) << endl;
+
+#ifdef SQLITE2
+ if (!*col)
+ data[i] = QVariant();
+ else if (f && f->isTextType())
+# ifdef SQLITE_UTF8
+ data[i] = QString::fromUtf8( *col );
+# else
+ data[i] = QVariant( *col ); //only latin1
+# endif
+ else if (f && f->isFPNumericType())
+ data[i] = QVariant( QCString(*col).toDouble() );
+ else {
+ switch (f ? f->type() : Field::Integer/*ROWINFO*/) {
+//todo: use short, etc.
+ case Field::Byte:
+ case Field::ShortInteger:
+ case Field::Integer:
+ data[i] = QVariant( QCString(*col).toInt() );
+ case Field::BigInteger:
+ data[i] = QVariant( QString::fromLatin1(*col).toLongLong() );
+ case Field::Boolean:
+ data[i] = QVariant( sqliteStringToBool(QString::fromLatin1(*col)), 1 );
+ break;
+ case Field::Date:
+ data[i] = QDate::fromString( QString::fromLatin1(*col), Qt::ISODate );
+ break;
+ case Field::Time:
+ //QDateTime - a hack needed because QVariant(QTime) has broken isNull()
+ data[i] = KexiUtils::stringToHackedQTime(QString::fromLatin1(*col));
+ break;
+ case Field::DateTime: {
+ QString tmp( QString::fromLatin1(*col) );
+ tmp[10] = 'T';
+ data[i] = QDateTime::fromString( tmp, Qt::ISODate );
+ break;
+ }
+ default:
+ data[i] = QVariant( *col );
+ }
+ }
+
+ col++;
+#else //SQLITE3
+ data[i] = d->getValue(f, i); //, !f /*!f means ROWID*/);
+#endif
+ }
+}
+
+QVariant SQLiteCursor::value(uint i)
+{
+// if (i > (m_fieldCount-1+(m_containsROWIDInfo?1:0))) //range checking
+ if (i > (m_fieldCount-1)) //range checking
+ return QVariant();
+//TODO: allow disable range checking! - performance reasons
+// const KexiDB::Field *f = m_query ? m_query->field(i) : 0;
+ KexiDB::Field *f = (m_fieldsExpanded && i<m_fieldsExpanded->count())
+ ? m_fieldsExpanded->at(i)->field : 0;
+#ifdef SQLITE2
+ //from most to least frequently used types:
+//(m_logicalFieldCount introduced) if (i==m_fieldCount || f && f->isIntegerType())
+ if (!f || f->isIntegerType())
+ return QVariant( QCString(d->curr_coldata[i]).toInt() );
+ else if (!f || f->isTextType())
+ return QVariant( d->curr_coldata[i] );
+ else if (f->isFPNumericType())
+ return QVariant( QCString(d->curr_coldata[i]).toDouble() );
+
+ return QVariant( d->curr_coldata[i] ); //default
+#else
+ return d->getValue(f, i); //, i==m_logicalFieldCount/*ROWID*/);
+#endif
+}
+
+/*! Stores string value taken from field number \a i to \a str.
+ \return false when range checking failed.
+bool SQLiteCursor::storeStringValue(uint i, QString &str)
+{
+ if (i > (m_fieldCount-1)) //range checking
+ return false;
+ str = d->curr_coldata[i];
+ return true;
+}*/
+
+int SQLiteCursor::serverResult()
+{
+ return d->res;
+}
+
+QString SQLiteCursor::serverResultName()
+{
+#ifdef SQLITE2
+ return QString::fromLatin1( sqlite_error_string(d->res) );
+#else //SQLITE3
+ return QString::fromLatin1( d->result_name );
+#endif
+}
+
+QString SQLiteCursor::serverErrorMsg()
+{
+ return d->errmsg;
+}
+
+void SQLiteCursor::drv_clearServerResult()
+{
+ d->res = SQLITE_OK;
+ d->errmsg_p = 0;
+}
+
diff --git a/kexi/kexidb/drivers/sqlite/sqlitecursor.h b/kexi/kexidb/drivers/sqlite/sqlitecursor.h
new file mode 100644
index 000000000..b7170f67c
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitecursor.h
@@ -0,0 +1,92 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_SQLITECURSOR_H
+#define KEXIDB_SQLITECURSOR_H
+
+#include <qstring.h>
+
+#include <kexidb/cursor.h>
+#include "connection.h"
+
+namespace KexiDB {
+
+class SQLiteCursorData;
+
+/*!
+
+*/
+class SQLiteCursor : public Cursor
+{
+ public:
+ virtual ~SQLiteCursor();
+ virtual QVariant value(uint i);
+
+ /*! [PROTOTYPE] \return internal buffer data. */
+//TODO virtual const char *** bufferData()
+ /*! [PROTOTYPE] \return current record data or NULL if there is no current records. */
+ virtual const char ** rowData() const;
+
+ virtual void storeCurrentRow(RowData &data) const;
+
+// virtual bool save(RowData& data, RowEditBuffer& buf);
+
+ virtual int serverResult();
+ virtual QString serverResultName();
+ virtual QString serverErrorMsg();
+
+ protected:
+ /*! Cursor will operate on \a conn, raw \a statement will be used to execute query. */
+ SQLiteCursor(Connection* conn, const QString& statement, uint options = NoOptions );
+
+ /*! Cursor will operate on \a conn, \a query schema will be used to execute query. */
+ SQLiteCursor(Connection* conn, QuerySchema& query,
+ uint options = NoOptions );
+
+ virtual bool drv_open();
+
+ virtual bool drv_close();
+// virtual bool drv_moveFirst();
+ virtual void drv_getNextRecord();
+//unused virtual bool drv_getPrevRecord();
+
+ virtual void drv_appendCurrentRecordToBuffer();
+ virtual void drv_bufferMovePointerNext();
+ virtual void drv_bufferMovePointerPrev();
+ virtual void drv_bufferMovePointerTo(Q_LLONG at);
+
+//TODO virtual void drv_storeCurrentRecord();
+
+ //PROTOTYPE:
+ /*! Method called when cursor's buffer need to be cleared
+ (only for buffered cursor type), eg. in close(). */
+ virtual void drv_clearBuffer();
+
+ virtual void drv_clearServerResult();
+
+ SQLiteCursorData *d;
+
+ friend class SQLiteConnection;
+};
+
+} //namespace KexiDB
+
+#endif
+
+
diff --git a/kexi/kexidb/drivers/sqlite/sqlitedriver.cpp b/kexi/kexidb/drivers/sqlite/sqlitedriver.cpp
new file mode 100644
index 000000000..e2abc2466
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitedriver.cpp
@@ -0,0 +1,159 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/connection.h>
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver_p.h>
+#include <kexidb/utils.h>
+
+#include "sqlite.h"
+#include "sqlitedriver.h"
+#include "sqliteconnection.h"
+#include "sqliteconnection_p.h"
+#include "sqliteadmin.h"
+
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+#ifdef SQLITE2
+KEXIDB_DRIVER_INFO( SQLiteDriver, sqlite2 )
+#else
+KEXIDB_DRIVER_INFO( SQLiteDriver, sqlite3 )
+#endif
+
+//! driver specific private data
+//! @internal
+class KexiDB::SQLiteDriverPrivate
+{
+ public:
+ SQLiteDriverPrivate()
+ {
+ }
+};
+
+//PgSqlDB::PgSqlDB(QObject *parent, const char *name, const QStringList &)
+SQLiteDriver::SQLiteDriver( QObject *parent, const char *name, const QStringList &args )
+ : Driver( parent, name, args )
+ ,dp( new SQLiteDriverPrivate() )
+{
+ d->isFileDriver = true;
+ d->isDBOpenedAfterCreate = true;
+ d->features = SingleTransactions | CursorForward
+#ifndef SQLITE2
+ | CompactingDatabaseSupported;
+#endif
+ ;
+
+ //special method for autoincrement definition
+ beh->SPECIAL_AUTO_INCREMENT_DEF = true;
+ beh->AUTO_INCREMENT_FIELD_OPTION = ""; //not available
+ beh->AUTO_INCREMENT_TYPE = "INTEGER";
+ beh->AUTO_INCREMENT_PK_FIELD_OPTION = "PRIMARY KEY";
+ beh->AUTO_INCREMENT_REQUIRES_PK = true;
+ beh->ROW_ID_FIELD_NAME = "OID";
+ beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY=true;
+ beh->QUOTATION_MARKS_FOR_IDENTIFIER='"';
+ beh->SELECT_1_SUBQUERY_SUPPORTED = true;
+ beh->SQL_KEYWORDS = keywords;
+ initSQLKeywords(29);
+
+ //predefined properties
+ d->properties["client_library_version"] = sqlite_libversion();
+ d->properties["default_server_encoding"] =
+#ifdef SQLITE2
+ sqlite_libencoding();
+#else //SQLITE3
+ "UTF8"; //OK?
+#endif
+
+ d->typeNames[Field::Byte]="Byte";
+ d->typeNames[Field::ShortInteger]="ShortInteger";
+ d->typeNames[Field::Integer]="Integer";
+ d->typeNames[Field::BigInteger]="BigInteger";
+ d->typeNames[Field::Boolean]="Boolean";
+ d->typeNames[Field::Date]="Date"; // In fact date/time types could be declared as datetext etc.
+ d->typeNames[Field::DateTime]="DateTime"; // to force text affinity..., see http://sqlite.org/datatype3.html
+ d->typeNames[Field::Time]="Time"; //
+ d->typeNames[Field::Float]="Float";
+ d->typeNames[Field::Double]="Double";
+ d->typeNames[Field::Text]="Text";
+ d->typeNames[Field::LongText]="CLOB";
+ d->typeNames[Field::BLOB]="BLOB";
+}
+
+SQLiteDriver::~SQLiteDriver()
+{
+ delete dp;
+}
+
+
+KexiDB::Connection*
+SQLiteDriver::drv_createConnection( ConnectionData &conn_data )
+{
+ return new SQLiteConnection( this, conn_data );
+}
+
+bool SQLiteDriver::isSystemObjectName( const QString& n ) const
+{
+ return Driver::isSystemObjectName(n) || n.lower().startsWith("sqlite_");
+}
+
+bool SQLiteDriver::drv_isSystemFieldName( const QString& n ) const
+{
+ return n.lower()=="_rowid_"
+ || n.lower()=="rowid"
+ || n.lower()=="oid";
+}
+
+QString SQLiteDriver::escapeString(const QString& str) const
+{
+ return QString("'")+QString(str).replace( '\'', "''" ) + "'";
+}
+
+QCString SQLiteDriver::escapeString(const QCString& str) const
+{
+ return QCString("'")+QCString(str).replace( '\'', "''" )+"'";
+}
+
+QString SQLiteDriver::escapeBLOB(const QByteArray& array) const
+{
+ return KexiDB::escapeBLOB(array, KexiDB::BLOBEscapeXHex);
+}
+
+QString SQLiteDriver::drv_escapeIdentifier( const QString& str) const
+{
+ return QString(str).replace( '"', "\"\"" );
+}
+
+QCString SQLiteDriver::drv_escapeIdentifier( const QCString& str) const
+{
+ return QCString(str).replace( '"', "\"\"" );
+}
+
+AdminTools* SQLiteDriver::drv_createAdminTools() const
+{
+#ifdef SQLITE2
+ return new AdminTools(); //empty impl.
+#else
+ return new SQLiteAdminTools();
+#endif
+}
+
+#include "sqlitedriver.moc"
diff --git a/kexi/kexidb/drivers/sqlite/sqlitedriver.h b/kexi/kexidb/drivers/sqlite/sqlitedriver.h
new file mode 100644
index 000000000..66d4d4d26
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitedriver.h
@@ -0,0 +1,82 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_DRIVER_SQLITE_H
+#define KEXIDB_DRIVER_SQLITE_H
+
+#include <qstringlist.h>
+
+#include <kexidb/driver.h>
+
+namespace KexiDB
+{
+
+class Connection;
+class DriverManager;
+class SQLiteDriverPrivate;
+
+//! SQLite database driver.
+class SQLiteDriver : public Driver
+{
+ Q_OBJECT
+ KEXIDB_DRIVER
+
+ public:
+ SQLiteDriver( QObject *parent, const char *name, const QStringList &args = QStringList() );
+ virtual ~SQLiteDriver();
+
+ /*! \return true if \a n is a system object name;
+ for this driver any object with name prefixed with "sqlite_"
+ is considered as system object.
+ */
+ virtual bool isSystemObjectName( const QString& n ) const;
+
+ /*! \return false for this driver. */
+ virtual bool isSystemDatabaseName( const QString& ) const { return false; }
+
+ //! Escape a string for use as a value
+ virtual QString escapeString(const QString& str) const;
+ virtual QCString escapeString(const QCString& str) const;
+
+ //! Escape BLOB value \a array
+ virtual QString escapeBLOB(const QByteArray& array) const;
+
+ protected:
+ virtual QString drv_escapeIdentifier( const QString& str) const;
+ virtual QCString drv_escapeIdentifier( const QCString& str) const;
+ virtual Connection *drv_createConnection( ConnectionData &conn_data );
+ virtual AdminTools* drv_createAdminTools() const;
+
+ /*! \return true if \a n is a system field name;
+ for this driver fields with name equal "_ROWID_"
+ is considered as system field.
+ */
+ virtual bool drv_isSystemFieldName( const QString& n ) const;
+
+ SQLiteDriverPrivate *dp;
+
+ private:
+ static const char *keywords[];
+
+};
+
+}
+
+#endif
+
diff --git a/kexi/kexidb/drivers/sqlite/sqlitekeywords.cpp b/kexi/kexidb/drivers/sqlite/sqlitekeywords.cpp
new file mode 100644
index 000000000..a3d6095b8
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitekeywords.cpp
@@ -0,0 +1,39 @@
+ /*
+ * This file has been automatically generated from
+ * koffice/kexi/tools/sql_keywords/sql_keywords.sh and
+ * ../../3rdparty/kexisql3/src/tokenize.c.
+ *
+ * Please edit the sql_keywords.sh, not this file!
+ */
+#include <sqlitedriver.h>
+
+namespace KexiDB {
+ const char* SQLiteDriver::keywords[] = {
+ "ABORT",
+ "ATTACH",
+ "CLUSTER",
+ "CONFLICT",
+ "DEFERRED",
+ "DEFERRABLE",
+ "DETACH",
+ "EACH",
+ "EXCEPT",
+ "FAIL",
+ "GLOB",
+ "IMMEDIATE",
+ "INITIALLY",
+ "INSTEAD",
+ "INTERSECT",
+ "ISNULL",
+ "NOTNULL",
+ "OF",
+ "PRAGMA",
+ "RAISE",
+ "STATEMENT",
+ "TEMP",
+ "TRIGGER",
+ "VACUUM",
+ "VIEW",
+ 0
+ };
+}
diff --git a/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.cpp b/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.cpp
new file mode 100644
index 000000000..9103b131e
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.cpp
@@ -0,0 +1,242 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "sqlitepreparedstatement.h"
+
+#include <kdebug.h>
+#include <assert.h>
+
+using namespace KexiDB;
+
+SQLitePreparedStatement::SQLitePreparedStatement(StatementType type, ConnectionInternal& conn,
+ FieldList& fields)
+ : KexiDB::PreparedStatement(type, conn, fields)
+ , SQLiteConnectionInternal(conn.connection)
+ , prepared_st_handle(0)
+ , m_resetRequired(false)
+{
+ data_owned = false;
+ data = dynamic_cast<KexiDB::SQLiteConnectionInternal&>(conn).data; //copy
+
+ temp_st = generateStatementString();
+#ifdef SQLITE2
+ //! @todo
+#else
+ if (!temp_st.isEmpty()) {
+ res = sqlite3_prepare(
+ data, /* Database handle */
+ temp_st, //const char *zSql, /* SQL statement, UTF-8 encoded */
+ temp_st.length(), //int nBytes, /* Length of zSql in bytes. */
+ &prepared_st_handle, //sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ 0 //const char **pzTail /* OUT: Pointer to unused portion of zSql */
+ );
+ if (SQLITE_OK != res) {
+//! @todo copy error msg
+ }
+ }
+#endif
+}
+
+SQLitePreparedStatement::~SQLitePreparedStatement()
+{
+#ifdef SQLITE2
+//! @todo
+#else
+ sqlite3_finalize(prepared_st_handle);
+ prepared_st_handle = 0;
+#endif
+}
+
+bool SQLitePreparedStatement::execute()
+{
+#ifdef SQLITE2
+//! @todo
+#else
+ if (!prepared_st_handle)
+ return false;
+ if (m_resetRequired) {
+ res = sqlite3_reset(prepared_st_handle);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ m_resetRequired = false;
+ }
+
+ int arg=1; //arg index counted from 1
+ KexiDB::Field *field;
+
+ Field::List _dummy;
+ Field::ListIterator itFields(_dummy);
+ //for INSERT, we're iterating over inserting values
+ //for SELECT, we're iterating over WHERE conditions
+ if (m_type == SelectStatement)
+ itFields = *m_whereFields;
+ else if (m_type == InsertStatement)
+ itFields = m_fields->fieldsIterator();
+ else
+ assert(0); //impl. error
+
+ for (QValueListConstIterator<QVariant> it = m_args.constBegin();
+ (field = itFields.current()); ++it, ++itFields, arg++)
+ {
+ if (it==m_args.constEnd() || (*it).isNull()) {//no value to bind or the value is null: bind NULL
+ res = sqlite3_bind_null(prepared_st_handle, arg);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ continue;
+ }
+ if (field->isTextType()) {
+ //! @todo optimize: make a static copy so SQLITE_STATIC can be used
+ QCString utf8String((*it).toString().utf8());
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ (const char*)utf8String, utf8String.length(), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ }
+ else switch (field->type()) {
+ case KexiDB::Field::Byte:
+ case KexiDB::Field::ShortInteger:
+ case KexiDB::Field::Integer:
+ {
+//! @todo what about unsigned > INT_MAX ?
+ bool ok;
+ const int value = (*it).toInt(&ok);
+ if (ok) {
+ res = sqlite3_bind_int(prepared_st_handle, arg, value);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ }
+ else {
+ res = sqlite3_bind_null(prepared_st_handle, arg);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ }
+ break;
+ }
+ case KexiDB::Field::Float:
+ case KexiDB::Field::Double:
+ res = sqlite3_bind_double(prepared_st_handle, arg, (*it).toDouble());
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::BigInteger:
+ {
+//! @todo what about unsigned > LLONG_MAX ?
+ bool ok;
+ Q_LLONG value = (*it).toLongLong(&ok);
+ if (ok) {
+ res = sqlite3_bind_int64(prepared_st_handle, arg, value);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ }
+ else {
+ res = sqlite3_bind_null(prepared_st_handle, arg);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ }
+ break;
+ }
+ case KexiDB::Field::Boolean:
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ QString::number((*it).toBool() ? 1 : 0).latin1(),
+ 1, SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::Time:
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ (*it).toTime().toString(Qt::ISODate).latin1(),
+ sizeof("HH:MM:SS"), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::Date:
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ (*it).toDate().toString(Qt::ISODate).latin1(),
+ sizeof("YYYY-MM-DD"), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::DateTime:
+ res = sqlite3_bind_text(prepared_st_handle, arg,
+ (*it).toDateTime().toString(Qt::ISODate).latin1(),
+ sizeof("YYYY-MM-DDTHH:MM:SS"), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ case KexiDB::Field::BLOB:
+ {
+ const QByteArray byteArray((*it).toByteArray());
+ res = sqlite3_bind_blob(prepared_st_handle, arg,
+ (const char*)byteArray, byteArray.size(), SQLITE_TRANSIENT /*??*/);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ break;
+ }
+ default:
+ KexiDBWarn << "PreparedStatement::execute(): unsupported field type: "
+ << field->type() << " - NULL value bound to column #" << arg << endl;
+ res = sqlite3_bind_null(prepared_st_handle, arg);
+ if (SQLITE_OK != res) {
+ //! @todo msg?
+ return false;
+ }
+ } //switch
+ }
+
+ //real execution
+ res = sqlite3_step(prepared_st_handle);
+ m_resetRequired = true;
+ if (m_type == InsertStatement && res == SQLITE_DONE) {
+ return true;
+ }
+ if (m_type == SelectStatement) {
+ //fetch result
+
+ //todo
+ }
+#endif
+ return false;
+}
+
diff --git a/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.h b/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.h
new file mode 100644
index 000000000..b8b9c3764
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitepreparedstatement.h
@@ -0,0 +1,50 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_SQLITEPREPAREDSTATEMENT_H
+//&& !defined SQLITE2
+#define KEXIDB_SQLITEPREPAREDSTATEMENT_H
+
+#include <kexidb/preparedstatement.h>
+#include "sqliteconnection_p.h"
+
+namespace KexiDB {
+
+/*! Implementation of prepared statements for SQLite driver. */
+class SQLitePreparedStatement : public PreparedStatement, SQLiteConnectionInternal
+{
+ public:
+ SQLitePreparedStatement(StatementType type, ConnectionInternal& conn,
+ FieldList& fields);
+
+ virtual ~SQLitePreparedStatement();
+
+ virtual bool execute();
+
+#ifdef SQLITE2
+ sqlite_vm *prepared_st_handle;
+#else //SQLITE3
+ sqlite3_stmt *prepared_st_handle;
+#endif
+ bool m_resetRequired : 1;
+};
+
+}
+
+#endif
diff --git a/kexi/kexidb/drivers/sqlite/sqlitevacuum.cpp b/kexi/kexidb/drivers/sqlite/sqlitevacuum.cpp
new file mode 100644
index 000000000..adf8709a5
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitevacuum.cpp
@@ -0,0 +1,150 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/global.h>
+#include "sqlitevacuum.h"
+
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <ktempfile.h>
+#include <kmessagebox.h>
+#include <kio/global.h>
+
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qapplication.h>
+#include <qprocess.h>
+#include <qcursor.h>
+
+#include <unistd.h>
+
+SQLiteVacuum::SQLiteVacuum(const QString& filePath)
+: m_filePath(filePath)
+{
+ m_process = 0;
+ m_percent = 0;
+ m_dlg = 0;
+ m_result = true;
+}
+
+SQLiteVacuum::~SQLiteVacuum()
+{
+ delete m_process;
+ if (m_dlg)
+ m_dlg->close();
+ delete m_dlg;
+}
+
+tristate SQLiteVacuum::run()
+{
+ const QString ksqlite_app = KStandardDirs::findExe( "ksqlite" );
+ if (ksqlite_app.isEmpty()) {
+ m_result = false;
+ return m_result;
+ }
+ QFileInfo fi(m_filePath);
+ if (!fi.isReadable()) {
+ KexiDBDrvWarn << "SQLiteVacuum::run(): No such file" << m_filePath << endl;
+ return false;
+ }
+ const uint origSize = fi.size();
+
+ QStringList args;
+ args << ksqlite_app << "-verbose-vacuum" << m_filePath << "vacuum";
+ m_process = new QProcess(args, this, "process");
+ m_process->setWorkingDirectory( QFileInfo(m_filePath).dir(true) );
+ connect( m_process, SIGNAL(readyReadStdout()), this, SLOT(readFromStdout()) );
+ connect( m_process, SIGNAL(processExited()), this, SLOT(processExited()) );
+ if (!m_process->start()) {
+ m_result = false;
+ return m_result;
+ }
+ m_dlg = new KProgressDialog(0, 0, i18n("Compacting database"),
+ "<qt>"+i18n("Compacting database \"%1\"...")
+ .arg("<nobr>"+QDir::convertSeparators(QFileInfo(m_filePath).fileName())+"</nobr>")
+ );
+ m_dlg->adjustSize();
+ m_dlg->resize(300, m_dlg->height());
+ connect(m_dlg, SIGNAL(cancelClicked()), this, SLOT(cancelClicked()));
+ m_dlg->setMinimumDuration(1000);
+ m_dlg->setAutoClose(true);
+ m_dlg->progressBar()->setTotalSteps(100);
+ m_dlg->exec();
+ while (m_process->isRunning()) {
+ readFromStdout();
+ usleep(50000);
+ }
+ delete m_process;
+ m_process = 0;
+ if (m_result == true) {
+ const uint newSize = QFileInfo(m_filePath).size();
+ const uint decrease = 100-100*newSize/origSize;
+ KMessageBox::information(0, i18n("The database has been compacted. Current size decreased by %1% to %2.")
+ .arg(decrease).arg(KIO::convertSize(newSize)));
+ }
+ return m_result;
+}
+
+void SQLiteVacuum::readFromStdout()
+{
+ while (true) {
+ QString s( m_process->readLineStdout() ); //readStdout();
+ if (s.isEmpty())
+ break;
+ m_dlg->progressBar()->setProgress(m_percent);
+// KexiDBDrvDbg << m_percent << " " << s << endl;
+ if (s.startsWith("VACUUM: ")) {
+ //set previously known progress
+ m_dlg->progressBar()->setProgress(m_percent);
+ //update progress info
+ if (s.mid(8,4)=="100%") {
+ m_percent = 100;
+ m_dlg->setAllowCancel(false);
+ m_dlg->setCursor(QCursor(Qt::WaitCursor));
+ }
+ else if (s.mid(9,1)=="%") {
+ m_percent = s.mid(8,1).toInt();
+ }
+ else if (s.mid(10,1)=="%") {
+ m_percent = s.mid(8,2).toInt();
+ }
+ m_process->writeToStdin(" ");
+ }
+ }
+}
+
+void SQLiteVacuum::processExited()
+{
+// KexiDBDrvDbg << sender()->name() << " EXIT" << endl;
+ m_dlg->close();
+ delete m_dlg;
+ m_dlg = 0;
+}
+
+void SQLiteVacuum::cancelClicked()
+{
+ if (!m_process->normalExit()) {
+ m_process->writeToStdin("q"); //quit
+ m_result = cancelled;
+ }
+}
+
+#include "sqlitevacuum.moc"
diff --git a/kexi/kexidb/drivers/sqlite/sqlitevacuum.h b/kexi/kexidb/drivers/sqlite/sqlitevacuum.h
new file mode 100644
index 000000000..4424b7fcd
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite/sqlitevacuum.h
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef SQLITE_VACUUM_H
+#define SQLITE_VACUUM_H
+
+#include <qobject.h>
+#include <qstring.h>
+
+#include <kexiutils/tristate.h>
+
+class QProcess;
+class KTempFile;
+class KProgressDialog;
+
+//! @short Helper class performing interactive compacting (VACUUM) of the SQLite database
+/*! Proved SQLite database filename in the constructor.
+ Then execute run() should be executed.
+
+ KProgressDialog will be displayed. Its progress bar will be updated whenever another
+ table's data compacting is performed. User can click "Cancel" button in any time
+ (except the final committing) to cancel the operation. In this case,
+ it's guaranteed that the original file remains unchanged.
+
+ This is possible because we rely on SQLite's VACUUM SQL command, which itself temporarily
+ creates a copy of the original database file, and replaces the orginal with the new only
+ on success.
+*/
+class SQLiteVacuum : public QObject
+{
+ Q_OBJECT
+ public:
+ SQLiteVacuum(const QString& filePath);
+ ~SQLiteVacuum();
+
+ /*! Performs compacting procedure.
+ \return true on success, false on failure and cancelled if user
+ clicked "Cancel" button in the progress dialog. */
+ tristate run();
+
+ public slots:
+ void readFromStdout();
+ void processExited();
+ void cancelClicked();
+
+ protected:
+ QString m_filePath;
+ QProcess *m_process;
+ KProgressDialog* m_dlg;
+ int m_percent;
+ tristate m_result;
+};
+
+#endif
diff --git a/kexi/kexidb/drivers/sqlite2/Makefile.am b/kexi/kexidb/drivers/sqlite2/Makefile.am
new file mode 100644
index 000000000..891a071a8
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/Makefile.am
@@ -0,0 +1,31 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexidb_sqlite2driver.la
+
+INCLUDES = -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/kexidb \
+ -I$(top_srcdir)/kexi/3rdparty/kexisql/src \
+ -I$(srcdir)/../sqlite \
+ -I$(srcdir)/../sqlite/moc \
+ -I../sqlite \
+ $(all_includes)
+
+kexidb_sqlite2driver_la_METASOURCES = AUTO
+
+kexidb_sqlite2driver_la_SOURCES = sqliteconnection.cpp sqlitedriver.cpp sqlitecursor.cpp \
+ sqlitepreparedstatement.cpp sqliteadmin.cpp sqlitealter.cpp
+
+kexidb_sqlite2driver_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) \
+ $(top_builddir)/kexi/3rdparty/kexisql/src/libkexisql2.la \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la
+
+kexidb_sqlite2driver_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO)
+
+
+kde_services_DATA = kexidb_sqlite2driver.desktop
+
+
+KDE_CXXFLAGS += -DKEXIDB_SQLITE_DRIVER_EXPORT= -D__KEXIDB__= \
+ -DSQLITE2= -include $(top_srcdir)/kexi/kexidb/global.h
+
diff --git a/kexi/kexidb/drivers/sqlite2/kexidb_sqlite2driver.desktop b/kexi/kexidb/drivers/sqlite2/kexidb_sqlite2driver.desktop
new file mode 100644
index 000000000..5e0959df6
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/kexidb_sqlite2driver.desktop
@@ -0,0 +1,57 @@
+[Desktop Entry]
+Name=SQLite2
+Name[sv]=Sqlite 2
+Comment=SQLite is default Kexi embedded SQL engine
+Comment[bg]=SQLite е СУБД по подразбиране в Kexi , с вградено SQL ядро
+Comment[ca]=SQLite és el motor SQL encastat i per defecte de Kexi
+Comment[cy]=Y peiriant SQL mewnadeiledig rhagosod Kexi yw SQLite
+Comment[da]=SQLite er en standard Kexi-indlejret SQL-motor
+Comment[de]=SQLite ist der standardmäßig in Kexi eingebettete SQL-Treiber
+Comment[el]=Η SQLite είναι η προκαθορισμένη ενσωματωμένη μηχανή SQL του Kexi
+Comment[es]=SQLite es el motor SQL incrustado en Kexi de forma predefinida
+Comment[et]=SQLite on Kexi vaikimisi kasutatav SQL mootor
+Comment[eu]=SQLite Kexi-ren kapsultatutako SQL motore lehenetsia da
+Comment[fa]=SQLite، پیش‌فرض موتور SQL نهفتۀ Kexi است
+Comment[fi]=SQLite on Kexin käyttämä sisäänrakennetti SQL-moottori.
+Comment[fr]=SQLite est le moteur SQL par défaut intégré à Kexi
+Comment[fy]=SQLite is de standert SQL-databank foar Kexi
+Comment[gl]=SQLite é o motor embebido de SQL de Kexi
+Comment[he]=SQLite הוא מנוע ה־SQL המוטבע של Kexi המשמש כברירת מחדל
+Comment[hi]=केएक्साई एम्बेडेड एसक्यूएल इंजिन के लिए एसक्यूएललाइट डिफ़ॉल्ट है
+Comment[hr]=SQLite je zadani Kexi ugrađeni pogon SQL pogona
+Comment[hu]=Az SQLite a Kexi alapértelmezett, beépített SQL-motorja
+Comment[is]=SQLite er sjálfgefna Kexi SQL vélin
+Comment[it]=SQLite è il motore predefinito integrato in Kexi
+Comment[ja]=SQLite は Kexi の標準埋め込み SQL エンジンです。
+Comment[km]=SQLite គឺ​ជា​ម៉ាស៊ីន SQL ដែល​បាន​បង្កប់​ក្នុង Kexi តាម​លំនាំដើម
+Comment[lv]=SQLite ir Kexi noklusējuma SQL dzinējs
+Comment[ms]=SQLite adalah KeXi piawai yang dipasang dalam enjin SQL
+Comment[nb]=SQLite er den innebygde SQL-motoren i Kexi
+Comment[nds]=SQLite is de standardinbett SQL-Driever för Kexi
+Comment[ne]=SQLite पूर्वनिर्धारित केक्सी सम्मिलित SQL इन्जिन हो
+Comment[nl]=SQLite is de standaard SQL-database voor Kexi
+Comment[nn]=SQLite er den innebygde SQL-motoren i Kexi
+Comment[pl]=SQLite jest domyślnym wbudowanym silnikiem SQL dla Kexi
+Comment[pt]=SQLite é o motor embebido de SQL do Kexi
+Comment[pt_BR]=SQLite é o mecanismo SQL embutido padrão do Kexi
+Comment[ru]=SQLite -- движок встроенного SQL по умолчанию в Kexi
+Comment[se]=SQLite lea Kexi:a sisahuksejuvvon SQL-mohtor
+Comment[sk]=SQLite je štandardný Kexi embedded SQL systém
+Comment[sl]=SQLite je privzet vključen pogon SQL za Kexi
+Comment[sr]=SQLite је подразумевани Kexi-јев уграђен SQL мотор
+Comment[sr@Latn]=SQLite je podrazumevani Kexi-jev ugrađen SQL motor
+Comment[sv]=Sqlite är inbyggd SQL-standarddatabas i Kexi
+Comment[tg]=SQLite -- движоки SQL, ки дар дар дохили Kexi бо пешфарз сохта шудааст
+Comment[tr]=SQlite Kexi'ye gömülü varsayılan SQL motorudur
+Comment[uk]=SQLite - це типовий рушій SQL вбудований в Kexi
+Comment[zh_CN]=SQLite 是嵌入 Kexi 的默认 SQL 引擎
+Comment[zh_TW]=SQLite 是 Kexi 預設內建的 SQL 引擎
+X-KDE-Library=kexidb_sqlite2driver
+ServiceTypes=Kexi/DBDriver
+Type=Service
+InitialPreference=8
+X-Kexi-DriverName=SQLite2
+X-Kexi-DriverType=File
+X-Kexi-FileDBDriverMimeList=application/x-sqlite2,application/x-kexiproject-sqlite2,application/x-kexiproject-sqlite,application/x-hk_classes-sqlite2
+X-Kexi-KexiDBVersion=1.8
+X-Kexi-DoNotAllowProjectImportingTo=true
diff --git a/kexi/kexidb/drivers/sqlite2/sqlite2.pro b/kexi/kexidb/drivers/sqlite2/sqlite2.pro
new file mode 100644
index 000000000..e9597ebdb
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqlite2.pro
@@ -0,0 +1,12 @@
+include( ../sqlite/sqlite_common.pro )
+
+LIBS += $$KDELIBDESTDIR/kexisql2$$KDELIBDEBUGLIB
+
+INCLUDEPATH += $(KEXI)/3rdparty/kexisql/src ../sqlite/moc
+
+system( bash kmoc ../sqlite )
+
+DEFINES += SQLITE2
+
+TARGET = kexidb_sqlite2driver$$KDELIBDEBUG
+
diff --git a/kexi/kexidb/drivers/sqlite2/sqliteadmin.cpp b/kexi/kexidb/drivers/sqlite2/sqliteadmin.cpp
new file mode 100644
index 000000000..8b0ca7eae
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqliteadmin.cpp
@@ -0,0 +1 @@
+#include "../sqlite/sqliteadmin.cpp"
diff --git a/kexi/kexidb/drivers/sqlite2/sqliteadmin.h b/kexi/kexidb/drivers/sqlite2/sqliteadmin.h
new file mode 100644
index 000000000..bd55e6c60
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqliteadmin.h
@@ -0,0 +1 @@
+#include "../sqlite/sqliteadmin.h"
diff --git a/kexi/kexidb/drivers/sqlite2/sqlitealter.cpp b/kexi/kexidb/drivers/sqlite2/sqlitealter.cpp
new file mode 100644
index 000000000..1942fee79
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqlitealter.cpp
@@ -0,0 +1 @@
+#include "../sqlite/sqlitealter.cpp"
diff --git a/kexi/kexidb/drivers/sqlite2/sqliteconnection.cpp b/kexi/kexidb/drivers/sqlite2/sqliteconnection.cpp
new file mode 100644
index 000000000..51bda0d88
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqliteconnection.cpp
@@ -0,0 +1 @@
+#include "../sqlite/sqliteconnection.cpp"
diff --git a/kexi/kexidb/drivers/sqlite2/sqliteconnection.h b/kexi/kexidb/drivers/sqlite2/sqliteconnection.h
new file mode 100644
index 000000000..34e22a13e
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqliteconnection.h
@@ -0,0 +1,2 @@
+#include "../sqlite/sqliteconnection.h"
+
diff --git a/kexi/kexidb/drivers/sqlite2/sqliteconnection_p.h b/kexi/kexidb/drivers/sqlite2/sqliteconnection_p.h
new file mode 100644
index 000000000..53bc38e90
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqliteconnection_p.h
@@ -0,0 +1,2 @@
+#include "../sqlite/sqliteconnection_p.h"
+
diff --git a/kexi/kexidb/drivers/sqlite2/sqlitecursor.cpp b/kexi/kexidb/drivers/sqlite2/sqlitecursor.cpp
new file mode 100644
index 000000000..764fc4dbb
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqlitecursor.cpp
@@ -0,0 +1,2 @@
+#include "../sqlite/sqlitecursor.cpp"
+
diff --git a/kexi/kexidb/drivers/sqlite2/sqlitecursor.h b/kexi/kexidb/drivers/sqlite2/sqlitecursor.h
new file mode 100644
index 000000000..8d4f87fe8
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqlitecursor.h
@@ -0,0 +1,2 @@
+#include "../sqlite/sqlitecursor.h"
+
diff --git a/kexi/kexidb/drivers/sqlite2/sqlitedriver.cpp b/kexi/kexidb/drivers/sqlite2/sqlitedriver.cpp
new file mode 100644
index 000000000..de8ce2d8b
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqlitedriver.cpp
@@ -0,0 +1,2 @@
+#include "../sqlite/sqlitedriver.cpp"
+#include "../sqlite/sqlitekeywords.cpp"
diff --git a/kexi/kexidb/drivers/sqlite2/sqlitedriver.h b/kexi/kexidb/drivers/sqlite2/sqlitedriver.h
new file mode 100644
index 000000000..a0af4b9f3
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqlitedriver.h
@@ -0,0 +1,2 @@
+#include "../sqlite/sqlitedriver.h"
+
diff --git a/kexi/kexidb/drivers/sqlite2/sqlitepreparedstatement.cpp b/kexi/kexidb/drivers/sqlite2/sqlitepreparedstatement.cpp
new file mode 100644
index 000000000..2e45bb0b8
--- /dev/null
+++ b/kexi/kexidb/drivers/sqlite2/sqlitepreparedstatement.cpp
@@ -0,0 +1 @@
+#include "../sqlite/sqlitepreparedstatement.cpp"
diff --git a/kexi/kexidb/error.h b/kexi/kexidb/error.h
new file mode 100644
index 000000000..01ab80529
--- /dev/null
+++ b/kexi/kexidb/error.h
@@ -0,0 +1,138 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KEXI_ERROR_H_
+#define _KEXI_ERROR_H_
+
+#include <qstring.h>
+
+#include "kexidb/kexidb_export.h"
+
+/*! Fine-grained KexiDB error codes */
+
+#define ERR_NONE 0
+#define ERR_NO_NAME_SPECIFIED 9 //! used when name (e.g. for database) was not specified
+#define ERR_DRIVERMANAGER 10
+#define ERR_INVALID_IDENTIFIER 11 //! used when name (e.g. for database) was not specified
+#define ERR_MISSING_DB_LOCATION 20
+#define ERR_ALREADY_CONNECTED 30
+#define ERR_NO_CONNECTION 40 //!< when opened connection was expected using KexiDB::Connection
+#define ERR_CONNECTION_FAILED 41 //!< when connection has failed
+#define ERR_CLOSE_FAILED 42 //!< when closing has failed
+#define ERR_NO_DB_USED 43 //!< when used database was expected in KexiDB::Connection
+#define ERR_OBJECT_EXISTS 50
+#define ERR_OBJECT_THE_SAME 51
+#define ERR_OBJECT_NOT_FOUND 60
+#define ERR_ACCESS_RIGHTS 70
+#define ERR_TRANSACTION_ACTIVE 80
+#define ERR_NO_TRANSACTION_ACTIVE 81
+#define ERR_NO_DB_PROPERTY 90 //! database property not found, see DatabaseProperties class
+#define ERR_DB_SPECIFIC 100
+#define ERR_CURSOR_NOT_OPEN 110
+#define ERR_SINGLE_DB_NAME_MISMATCH 120
+#define ERR_CURSOR_RECORD_FETCHING 130 //!< eg. for Cursor::drv_getNextRecord()
+#define ERR_UNSUPPORTED_DRV_FEATURE 140 //!< given driver's feature is unsupported (eg. transactins)
+#define ERR_ROLLBACK_OR_COMMIT_TRANSACTION 150 //!< error during transaction rollback or commit
+#define ERR_SYSTEM_NAME_RESERVED 160 //!< system name is reserved and cannot be used
+ //!< (e.g. for table, db, or field name)
+#define ERR_CANNOT_CREATE_EMPTY_OBJECT 170 //!< empty object cannot be created
+ //!< (e.g. table without fields)
+#define ERR_INVALID_DRIVER_IMPL 180 //! driver's implementation is invalid
+#define ERR_INCOMPAT_DRIVER_VERSION 181 //!< driver's version is incompatible
+#define ERR_INCOMPAT_DATABASE_VERSION 182 //!< db's version is incompatible with currently
+ //!< used Kexi version
+#define ERR_INVALID_DATABASE_CONTENTS 183 //!< db's contents are invalid
+ //!< (e.g. no enough information to open db)
+
+//! errors related to data updating on the server
+#define ERR_UPDATE_NULL_PKEY_FIELD 190 //!< null pkey field on updating
+#define ERR_UPDATE_SERVER_ERROR 191 //!< error @ the server side during data updating
+#define ERR_UPDATE_NO_MASTER_TABLE 192 //!< data could not be edited because there
+ //!< is no master table defined
+#define ERR_UPDATE_NO_MASTER_TABLES_PKEY 193 //!< data could not be edited
+ //!< because it's master table has
+ //!< no primary key defined
+#define ERR_UPDATE_NO_ENTIRE_MASTER_TABLES_PKEY 194 //!< data could not be edited
+ //!< because it does not contain entire
+ //!< master table's primary key
+
+//! errors related to data inserting on the server
+#define ERR_INSERT_NULL_PKEY_FIELD 220 //!< null pkey field on updating
+#define ERR_INSERT_SERVER_ERROR 221 //!< error @ the server side during data inserting
+#define ERR_INSERT_NO_MASTER_TABLE 222 //!< data could not be inserted because there
+ //!< is no master table defined
+#define ERR_INSERT_NO_MASTER_TABLES_PKEY 223 //!< data could not be inserted because master
+ //!< table has no primary key defined
+#define ERR_INSERT_NO_ENTIRE_MASTER_TABLES_PKEY 224 //!< data could not be inserted
+ //!< because it does not contain entire
+ //!< master table's primary key
+
+//! errors related to data deleting on the server
+#define ERR_DELETE_NULL_PKEY_FIELD 250 //!< null pkey field on updating
+#define ERR_DELETE_SERVER_ERROR 251 //!< error @ the server side during data deleting
+#define ERR_DELETE_NO_MASTER_TABLE 252 //!< data could not be deleted because there
+ //!< is no master table defined
+#define ERR_DELETE_NO_MASTER_TABLES_PKEY 253 //!< data could not be deleted because master
+ //!< table has no primary key defined
+#define ERR_DELETE_NO_ENTIRE_MASTER_TABLES_PKEY 254 //!< data could not be deleted
+ //!< because it does not contain entire
+ //!< master table's primary key
+
+//! errors related to queries
+#define ERR_SQL_EXECUTION_ERROR 260 //!< general server error for sql statement execution
+ //!< usually returned by Connection::executeSQL()
+#define ERR_SQL_PARSE_ERROR 270 //!< Parse error coming from arser::parse()
+
+#define ERR_OTHER 0xffff //!< use this if you have not (yet?) the name for given error
+
+
+namespace KexiDB {
+
+/*! This class contains a result information
+ for various data manipulation operations, like cell/row updating/inserting. */
+class KEXI_DB_EXPORT ResultInfo
+{
+ public:
+ ResultInfo()
+ {
+ success = true;
+ allowToDiscardChanges = false;
+ column = -1;
+ }
+ /*! Sets information to default values. */
+ void clear() {
+ success = true;
+ allowToDiscardChanges = false;
+ column = -1;
+ msg = QString::null;
+ desc = QString::null;
+ }
+ bool success : 1; //!< result of the operation, true by default
+ bool allowToDiscardChanges : 1; //!< True if changes can be discarded, false by default
+ //!< If true, additional "Discard changes" messagebox
+ //!< button can be displayed.
+ QString msg, desc; //!< error message and detailed description, both empty by default
+ int column; //!< faulty column, -1 (the default) means: there is no faulty column
+};
+
+}//namespace
+
+#endif
+
diff --git a/kexi/kexidb/expression.cpp b/kexi/kexidb/expression.cpp
new file mode 100644
index 000000000..49bb231ad
--- /dev/null
+++ b/kexi/kexidb/expression.cpp
@@ -0,0 +1,914 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ Based on nexp.cpp : Parser module of Python-like language
+ (C) 2001 Jaroslaw Staniek, MIMUW (www.mimuw.edu.pl)
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "expression.h"
+#include "utils.h"
+#include "parser/sqlparser.h"
+#include "parser/parser_p.h"
+
+#include <ctype.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qdatetime.h>
+
+KEXI_DB_EXPORT QString KexiDB::exprClassName(int c)
+{
+ if (c==KexiDBExpr_Unary)
+ return "Unary";
+ else if (c==KexiDBExpr_Arithm)
+ return "Arithm";
+ else if (c==KexiDBExpr_Logical)
+ return "Logical";
+ else if (c==KexiDBExpr_Relational)
+ return "Relational";
+ else if (c==KexiDBExpr_SpecialBinary)
+ return "SpecialBinary";
+ else if (c==KexiDBExpr_Const)
+ return "Const";
+ else if (c==KexiDBExpr_Variable)
+ return "Variable";
+ else if (c==KexiDBExpr_Function)
+ return "Function";
+ else if (c==KexiDBExpr_Aggregation)
+ return "Aggregation";
+ else if (c==KexiDBExpr_TableList)
+ return "TableList";
+ else if (c==KexiDBExpr_QueryParameter)
+ return "QueryParameter";
+
+ return "Unknown";
+}
+
+using namespace KexiDB;
+
+//=========================================
+
+BaseExpr::BaseExpr(int token)
+ : m_cl(KexiDBExpr_Unknown)
+ , m_par(0)
+ , m_token(token)
+{
+}
+
+BaseExpr::~BaseExpr()
+{
+}
+
+Field::Type BaseExpr::type()
+{
+ return Field::InvalidType;
+}
+
+QString BaseExpr::debugString()
+{
+ return QString("BaseExpr(%1,type=%1)").arg(m_token).arg(Driver::defaultSQLTypeName(type()));
+}
+
+bool BaseExpr::validate(ParseInfo& /*parseInfo*/)
+{
+ return true;
+}
+
+extern const char * const tname(int offset);
+#define safe_tname(token) ((token>=255 && token<=__LAST_TOKEN) ? tname(token-255) : "")
+
+QString BaseExpr::tokenToDebugString(int token)
+{
+ if (token < 254) {
+ if (isprint(token))
+ return QString(QChar(uchar(token)));
+ else
+ return QString::number(token);
+ }
+ return QString(safe_tname(token));
+}
+
+QString BaseExpr::tokenToString()
+{
+ if (m_token < 255 && isprint(m_token))
+ return tokenToDebugString();
+ return QString::null;
+}
+
+NArgExpr* BaseExpr::toNArg() { return dynamic_cast<NArgExpr*>(this); }
+UnaryExpr* BaseExpr::toUnary() { return dynamic_cast<UnaryExpr*>(this); }
+BinaryExpr* BaseExpr::toBinary() { return dynamic_cast<BinaryExpr*>(this); }
+ConstExpr* BaseExpr::toConst() { return dynamic_cast<ConstExpr*>(this); }
+VariableExpr* BaseExpr::toVariable() { return dynamic_cast<VariableExpr*>(this); }
+FunctionExpr* BaseExpr::toFunction() { return dynamic_cast<FunctionExpr*>(this); }
+QueryParameterExpr* BaseExpr::toQueryParameter() { return dynamic_cast<QueryParameterExpr*>(this); }
+
+//=========================================
+
+NArgExpr::NArgExpr(int aClass, int token)
+ : BaseExpr(token)
+{
+ m_cl = aClass;
+ list.setAutoDelete(true);
+}
+
+NArgExpr::NArgExpr(const NArgExpr& expr)
+ : BaseExpr(expr)
+{
+ foreach_list (BaseExpr::ListIterator, it, expr.list)
+ add( it.current()->copy() );
+}
+
+NArgExpr::~NArgExpr()
+{
+}
+
+NArgExpr* NArgExpr::copy() const
+{
+ return new NArgExpr(*this);
+}
+
+QString NArgExpr::debugString()
+{
+ QString s = QString("NArgExpr(")
+ + "class=" + exprClassName(m_cl);
+ for ( BaseExpr::ListIterator it(list); it.current(); ++it ) {
+ s+=", ";
+ s+=it.current()->debugString();
+ }
+ s+=")";
+ return s;
+}
+
+QString NArgExpr::toString( QuerySchemaParameterValueListIterator* params )
+{
+ QString s;
+ s.reserve(256);
+ foreach_list( BaseExpr::ListIterator, it, list) {
+ if (!s.isEmpty())
+ s+=", ";
+ s+=it.current()->toString(params);
+ }
+ return s;
+}
+
+void NArgExpr::getQueryParameters(QuerySchemaParameterList& params)
+{
+ foreach_list( BaseExpr::ListIterator, it, list)
+ it.current()->getQueryParameters(params);
+}
+
+BaseExpr* NArgExpr::arg(int nr)
+{
+ return list.at(nr);
+}
+
+void NArgExpr::add(BaseExpr *expr)
+{
+ list.append(expr);
+ expr->setParent(this);
+}
+
+void NArgExpr::prepend(BaseExpr *expr)
+{
+ list.prepend(expr);
+ expr->setParent(this);
+}
+
+int NArgExpr::args()
+{
+ return list.count();
+}
+
+bool NArgExpr::validate(ParseInfo& parseInfo)
+{
+ if (!BaseExpr::validate(parseInfo))
+ return false;
+
+ foreach_list(BaseExpr::ListIterator, it, list) {
+ if (!it.current()->validate(parseInfo))
+ return false;
+ }
+ return true;
+}
+
+//=========================================
+UnaryExpr::UnaryExpr(int token, BaseExpr *arg)
+ : BaseExpr(token)
+ , m_arg(arg)
+{
+ m_cl = KexiDBExpr_Unary;
+ if (m_arg)
+ m_arg->setParent(this);
+}
+
+UnaryExpr::UnaryExpr(const UnaryExpr& expr)
+ : BaseExpr(expr)
+ , m_arg( expr.m_arg ? expr.m_arg->copy() : 0 )
+{
+ if (m_arg)
+ m_arg->setParent(this);
+}
+
+UnaryExpr::~UnaryExpr()
+{
+ delete m_arg;
+}
+
+UnaryExpr* UnaryExpr::copy() const
+{
+ return new UnaryExpr(*this);
+}
+
+QString UnaryExpr::debugString()
+{
+ return "UnaryExpr('"
+ + tokenToDebugString() + "', "
+ + (m_arg ? m_arg->debugString() : QString("<NONE>"))
+ + QString(",type=%1)").arg(Driver::defaultSQLTypeName(type()));
+}
+
+QString UnaryExpr::toString(QuerySchemaParameterValueListIterator* params)
+{
+ if (m_token=='(') //parentheses (special case)
+ return "(" + (m_arg ? m_arg->toString(params) : "<NULL>") + ")";
+ if (m_token < 255 && isprint(m_token))
+ return tokenToDebugString() + (m_arg ? m_arg->toString(params) : "<NULL>");
+ if (m_token==NOT)
+ return "NOT " + (m_arg ? m_arg->toString(params) : "<NULL>");
+ if (m_token==SQL_IS_NULL)
+ return (m_arg ? m_arg->toString(params) : "<NULL>") + " IS NULL";
+ if (m_token==SQL_IS_NOT_NULL)
+ return (m_arg ? m_arg->toString(params) : "<NULL>") + " IS NOT NULL";
+ return QString("{INVALID_OPERATOR#%1} ").arg(m_token) + (m_arg ? m_arg->toString(params) : "<NULL>");
+}
+
+void UnaryExpr::getQueryParameters(QuerySchemaParameterList& params)
+{
+ if (m_arg)
+ m_arg->getQueryParameters(params);
+}
+
+Field::Type UnaryExpr::type()
+{
+ //NULL IS NOT NULL : BOOLEAN
+ //NULL IS NULL : BOOLEAN
+ switch (m_token) {
+ case SQL_IS_NULL:
+ case SQL_IS_NOT_NULL:
+ return Field::Boolean;
+ }
+ const Field::Type t = m_arg->type();
+ if (t==Field::Null)
+ return Field::Null;
+ if (m_token==NOT)
+ return Field::Boolean;
+
+ return t;
+}
+
+bool UnaryExpr::validate(ParseInfo& parseInfo)
+{
+ if (!BaseExpr::validate(parseInfo))
+ return false;
+
+ if (!m_arg->validate(parseInfo))
+ return false;
+
+//! @todo compare types... e.g. NOT applied to Text makes no sense...
+
+ // update type
+ if (m_arg->toQueryParameter()) {
+ m_arg->toQueryParameter()->setType(type());
+ }
+
+ return true;
+#if 0
+ BaseExpr *n = l.at(0);
+
+ n->check();
+/*typ wyniku:
+ const bool dla "NOT <bool>" (negacja)
+ int dla "# <str>" (dlugosc stringu)
+ int dla "+/- <int>"
+ */
+ if (is(NOT) && n->nodeTypeIs(TYP_BOOL)) {
+ node_type=new NConstType(TYP_BOOL);
+ }
+ else if (is('#') && n->nodeTypeIs(TYP_STR)) {
+ node_type=new NConstType(TYP_INT);
+ }
+ else if ((is('+') || is('-')) && n->nodeTypeIs(TYP_INT)) {
+ node_type=new NConstType(TYP_INT);
+ }
+ else {
+ ERR("Niepoprawny argument typu '%s' dla operatora '%s'",
+ n->nodeTypeName(),is(NOT)?QString("not"):QChar(typ()));
+ }
+#endif
+}
+
+//=========================================
+BinaryExpr::BinaryExpr(int aClass, BaseExpr *left_expr, int token, BaseExpr *right_expr)
+ : BaseExpr(token)
+ , m_larg(left_expr)
+ , m_rarg(right_expr)
+{
+ m_cl = aClass;
+ if (m_larg)
+ m_larg->setParent(this);
+ if (m_rarg)
+ m_rarg->setParent(this);
+}
+
+BinaryExpr::BinaryExpr(const BinaryExpr& expr)
+ : BaseExpr(expr)
+ , m_larg( expr.m_larg ? expr.m_larg->copy() : 0 )
+ , m_rarg( expr.m_rarg ? expr.m_rarg->copy() : 0 )
+{
+}
+
+BinaryExpr::~BinaryExpr()
+{
+ delete m_larg;
+ delete m_rarg;
+}
+
+BinaryExpr* BinaryExpr::copy() const
+{
+ return new BinaryExpr(*this);
+}
+
+bool BinaryExpr::validate(ParseInfo& parseInfo)
+{
+ if (!BaseExpr::validate(parseInfo))
+ return false;
+
+ if (!m_larg->validate(parseInfo))
+ return false;
+ if (!m_rarg->validate(parseInfo))
+ return false;
+
+//! @todo compare types..., BITWISE_SHIFT_RIGHT requires integers, etc...
+
+ //update type for query parameters
+ QueryParameterExpr * queryParameter = m_larg->toQueryParameter();
+ if (queryParameter)
+ queryParameter->setType(m_rarg->type());
+ queryParameter = m_rarg->toQueryParameter();
+ if (queryParameter)
+ queryParameter->setType(m_larg->type());
+
+ return true;
+}
+
+Field::Type BinaryExpr::type()
+{
+ const Field::Type lt = m_larg->type(), rt = m_rarg->type();
+ if (lt==Field::InvalidType || rt == Field::InvalidType)
+ return Field::InvalidType;
+ if (lt==Field::Null || rt == Field::Null) {
+ if (m_token!=OR) //note that NULL OR something != NULL
+ return Field::Null;
+ }
+
+ switch (m_token) {
+ case BITWISE_SHIFT_RIGHT:
+ case BITWISE_SHIFT_LEFT:
+ case CONCATENATION:
+ return lt;
+ }
+
+ const bool ltInt = Field::isIntegerType(lt);
+ const bool rtInt = Field::isIntegerType(rt);
+ if (ltInt && rtInt)
+ return KexiDB::maximumForIntegerTypes(lt, rt);
+
+ if (Field::isFPNumericType(lt) && rtInt)
+ return lt;
+ if (Field::isFPNumericType(rt) && ltInt)
+ return rt;
+ if ((lt==Field::Double || lt==Field::Float) && rtInt)
+ return lt;
+ if ((rt==Field::Double || rt==Field::Float) && ltInt)
+ return rt;
+
+ return Field::Boolean;
+}
+
+QString BinaryExpr::debugString()
+{
+ return QString("BinaryExpr(")
+ + "class=" + exprClassName(m_cl)
+ + "," + (m_larg ? m_larg->debugString() : QString("<NONE>"))
+ + ",'" + tokenToDebugString() + "',"
+ + (m_rarg ? m_rarg->debugString() : QString("<NONE>"))
+ + QString(",type=%1)").arg(Driver::defaultSQLTypeName(type()));
+}
+
+QString BinaryExpr::tokenToString()
+{
+ if (m_token < 255 && isprint(m_token))
+ return tokenToDebugString();
+ // other arithmetic operations: << >>
+ switch (m_token) {
+ case BITWISE_SHIFT_RIGHT: return ">>";
+ case BITWISE_SHIFT_LEFT: return "<<";
+ // other relational operations: <= >= <> (or !=) LIKE IN
+ case NOT_EQUAL: return "<>";
+ case NOT_EQUAL2: return "!=";
+ case LESS_OR_EQUAL: return "<=";
+ case GREATER_OR_EQUAL: return ">=";
+ case LIKE: return "LIKE";
+ case SQL_IN: return "IN";
+ // other logical operations: OR (or ||) AND (or &&) XOR
+ case SIMILAR_TO: return "SIMILAR TO";
+ case NOT_SIMILAR_TO: return "NOT SIMILAR TO";
+ case OR: return "OR";
+ case AND: return "AND";
+ case XOR: return "XOR";
+ // other string operations: || (as CONCATENATION)
+ case CONCATENATION: return "||";
+ // SpecialBinary "pseudo operators":
+ /* not handled here */
+ default:;
+ }
+ return QString("{INVALID_BINARY_OPERATOR#%1} ").arg(m_token);
+}
+
+QString BinaryExpr::toString(QuerySchemaParameterValueListIterator* params)
+{
+#define INFIX(a) \
+ (m_larg ? m_larg->toString(params) : "<NULL>") + " " + a + " " + (m_rarg ? m_rarg->toString(params) : "<NULL>")
+ return INFIX(tokenToString());
+}
+
+void BinaryExpr::getQueryParameters(QuerySchemaParameterList& params)
+{
+ if (m_larg)
+ m_larg->getQueryParameters(params);
+ if (m_rarg)
+ m_rarg->getQueryParameters(params);
+}
+
+//=========================================
+ConstExpr::ConstExpr( int token, const QVariant& val)
+: BaseExpr( token )
+, value(val)
+{
+ m_cl = KexiDBExpr_Const;
+}
+
+ConstExpr::ConstExpr(const ConstExpr& expr)
+ : BaseExpr(expr)
+ , value(expr.value)
+{
+}
+
+ConstExpr::~ConstExpr()
+{
+}
+
+ConstExpr* ConstExpr::copy() const
+{
+ return new ConstExpr(*this);
+}
+
+Field::Type ConstExpr::type()
+{
+ if (m_token==SQL_NULL)
+ return Field::Null;
+ else if (m_token==INTEGER_CONST) {
+//TODO ok?
+//TODO: add sign info?
+ if (value.type() == QVariant::Int || value.type() == QVariant::UInt) {
+ Q_LLONG v = value.toInt();
+ if (v <= 0xff && v > -0x80)
+ return Field::Byte;
+ if (v <= 0xffff && v > -0x8000)
+ return Field::ShortInteger;
+ return Field::Integer;
+ }
+ return Field::BigInteger;
+ }
+ else if (m_token==CHARACTER_STRING_LITERAL) {
+//TODO: Field::defaultTextLength() is hardcoded now!
+ if (value.toString().length() > Field::defaultTextLength())
+ return Field::LongText;
+ else
+ return Field::Text;
+ }
+ else if (m_token==REAL_CONST)
+ return Field::Double;
+ else if (m_token==DATE_CONST)
+ return Field::Date;
+ else if (m_token==DATETIME_CONST)
+ return Field::DateTime;
+ else if (m_token==TIME_CONST)
+ return Field::Time;
+
+ return Field::InvalidType;
+}
+
+QString ConstExpr::debugString()
+{
+ return QString("ConstExpr('") + tokenToDebugString() +"'," + toString()
+ + QString(",type=%1)").arg(Driver::defaultSQLTypeName(type()));
+}
+
+QString ConstExpr::toString(QuerySchemaParameterValueListIterator* params)
+{
+ Q_UNUSED(params);
+ if (m_token==SQL_NULL)
+ return "NULL";
+ else if (m_token==CHARACTER_STRING_LITERAL)
+//TODO: better escaping!
+ return "'" + value.toString() + "'";
+ else if (m_token==REAL_CONST)
+ return QString::number(value.toPoint().x())+"."+QString::number(value.toPoint().y());
+ else if (m_token==DATE_CONST)
+ return "'" + value.toDate().toString(Qt::ISODate) + "'";
+ else if (m_token==DATETIME_CONST)
+ return "'" + value.toDateTime().date().toString(Qt::ISODate)
+ + " " + value.toDateTime().time().toString(Qt::ISODate) + "'";
+ else if (m_token==TIME_CONST)
+ return "'" + value.toTime().toString(Qt::ISODate) + "'";
+
+ return value.toString();
+}
+
+void ConstExpr::getQueryParameters(QuerySchemaParameterList& params)
+{
+ Q_UNUSED(params);
+}
+
+bool ConstExpr::validate(ParseInfo& parseInfo)
+{
+ if (!BaseExpr::validate(parseInfo))
+ return false;
+
+ return type()!=Field::InvalidType;
+}
+
+//=========================================
+QueryParameterExpr::QueryParameterExpr(const QString& message)
+: ConstExpr( QUERY_PARAMETER, message )
+, m_type(Field::Text)
+{
+ m_cl = KexiDBExpr_QueryParameter;
+}
+
+QueryParameterExpr::QueryParameterExpr(const QueryParameterExpr& expr)
+ : ConstExpr(expr)
+ , m_type(expr.m_type)
+{
+}
+
+QueryParameterExpr::~QueryParameterExpr()
+{
+}
+
+QueryParameterExpr* QueryParameterExpr::copy() const
+{
+ return new QueryParameterExpr(*this);
+}
+
+Field::Type QueryParameterExpr::type()
+{
+ return m_type;
+}
+
+void QueryParameterExpr::setType(Field::Type type)
+{
+ m_type = type;
+}
+
+QString QueryParameterExpr::debugString()
+{
+ return QString("QueryParameterExpr('") + QString::fromLatin1("[%2]").arg(value.toString())
+ + QString("',type=%1)").arg(Driver::defaultSQLTypeName(type()));
+}
+
+QString QueryParameterExpr::toString(QuerySchemaParameterValueListIterator* params)
+{
+ return params ? params->getPreviousValueAsString(type()) : QString::fromLatin1("[%2]").arg(value.toString());
+}
+
+void QueryParameterExpr::getQueryParameters(QuerySchemaParameterList& params)
+{
+ QuerySchemaParameter param;
+ param.message = value.toString();
+ param.type = type();
+ params.append( param );
+}
+
+bool QueryParameterExpr::validate(ParseInfo& parseInfo)
+{
+ Q_UNUSED(parseInfo);
+ return type()!=Field::InvalidType;
+}
+
+//=========================================
+VariableExpr::VariableExpr(const QString& _name)
+: BaseExpr( 0/*undefined*/ )
+, name(_name)
+, field(0)
+, tablePositionForField(-1)
+, tableForQueryAsterisk(0)
+{
+ m_cl = KexiDBExpr_Variable;
+}
+
+VariableExpr::VariableExpr(const VariableExpr& expr)
+ : BaseExpr(expr)
+ , name(expr.name)
+ , field(expr.field)
+ , tablePositionForField(expr.tablePositionForField)
+ , tableForQueryAsterisk(expr.tableForQueryAsterisk)
+{
+}
+
+VariableExpr::~VariableExpr()
+{
+}
+
+VariableExpr* VariableExpr::copy() const
+{
+ return new VariableExpr(*this);
+}
+
+QString VariableExpr::debugString()
+{
+ return QString("VariableExpr(") + name
+ + QString(",type=%1)").arg(field ? Driver::defaultSQLTypeName(type()) : QString("FIELD NOT DEFINED YET"));
+}
+
+QString VariableExpr::toString(QuerySchemaParameterValueListIterator* params)
+{
+ Q_UNUSED(params);
+ return name;
+}
+
+void VariableExpr::getQueryParameters(QuerySchemaParameterList& params)
+{
+ Q_UNUSED(params);
+}
+
+//! We're assuming it's called after VariableExpr::validate()
+Field::Type VariableExpr::type()
+{
+ if (field)
+ return field->type();
+
+ //BTW, asterisks are not stored in VariableExpr outside of parser, so ok.
+ return Field::InvalidType;
+}
+
+#define IMPL_ERROR(errmsg) parseInfo.errMsg = "Implementation error"; parseInfo.errDescr = errmsg
+
+bool VariableExpr::validate(ParseInfo& parseInfo)
+{
+ if (!BaseExpr::validate(parseInfo))
+ return false;
+ field = 0;
+ tablePositionForField = -1;
+ tableForQueryAsterisk = 0;
+
+/* taken from parser's addColumn(): */
+ KexiDBDbg << "checking variable name: " << name << endl;
+ int dotPos = name.find('.');
+ QString tableName, fieldName;
+//TODO: shall we also support db name?
+ if (dotPos>0) {
+ tableName = name.left(dotPos);
+ fieldName = name.mid(dotPos+1);
+ }
+ if (tableName.isEmpty()) {//fieldname only
+ fieldName = name;
+ if (fieldName=="*") {
+// querySchema->addAsterisk( new QueryAsterisk(querySchema) );
+ return true;
+ }
+
+ //find first table that has this field
+ Field *firstField = 0;
+ foreach_list(TableSchema::ListIterator, it, *parseInfo.querySchema->tables()) {
+ Field *f = it.current()->field(fieldName);
+ if (f) {
+ if (!firstField) {
+ firstField = f;
+ }
+ else if (f->table()!=firstField->table()) {
+ //ambiguous field name
+ parseInfo.errMsg = i18n("Ambiguous field name");
+ parseInfo.errDescr = i18n("Both table \"%1\" and \"%2\" have defined \"%3\" field. "
+ "Use \"<tableName>.%4\" notation to specify table name.")
+ .arg(firstField->table()->name()).arg(f->table()->name())
+ .arg(fieldName).arg(fieldName);
+ return false;
+ }
+ }
+ }
+ if (!firstField) {
+ parseInfo.errMsg = i18n("Field not found");
+ parseInfo.errDescr = i18n("Table containing \"%1\" field not found").arg(fieldName);
+ return false;
+ }
+ //ok
+ field = firstField; //store
+// querySchema->addField(firstField);
+ return true;
+ }
+
+ //table.fieldname or tableAlias.fieldname
+ tableName = tableName.lower();
+ TableSchema *ts = parseInfo.querySchema->table( tableName );
+ if (ts) {//table.fieldname
+ //check if "table" is covered by an alias
+ const QValueList<int> tPositions = parseInfo.querySchema->tablePositions(tableName);
+ QValueList<int>::ConstIterator it = tPositions.constBegin();
+ QCString tableAlias;
+ bool covered = true;
+ for (; it!=tPositions.constEnd() && covered; ++it) {
+ tableAlias = parseInfo.querySchema->tableAlias(*it);
+ if (tableAlias.isEmpty() || tableAlias.lower()==tableName.latin1())
+ covered = false; //uncovered
+ KexiDBDbg << " --" << "covered by " << tableAlias << " alias" << endl;
+ }
+ if (covered) {
+ parseInfo.errMsg = i18n("Could not access the table directly using its name");
+ parseInfo.errDescr = i18n("Table \"%1\" is covered by aliases. Instead of \"%2\", "
+ "you can write \"%3\"").arg(tableName)
+ .arg(tableName+"."+fieldName).arg(tableAlias+"."+fieldName.latin1());
+ return false;
+ }
+ }
+
+ int tablePosition = -1;
+ if (!ts) {//try to find tableAlias
+ tablePosition = parseInfo.querySchema->tablePositionForAlias( tableName.latin1() );
+ if (tablePosition>=0) {
+ ts = parseInfo.querySchema->tables()->at(tablePosition);
+ if (ts) {
+// KexiDBDbg << " --it's a tableAlias.name" << endl;
+ }
+ }
+ }
+
+ if (!ts) {
+ parseInfo.errMsg = i18n("Table not found");
+ parseInfo.errDescr = i18n("Unknown table \"%1\"").arg(tableName);
+ return false;
+ }
+
+ QValueList<int> *positionsList = parseInfo.repeatedTablesAndAliases[ tableName ];
+ if (!positionsList) { //for sanity
+ IMPL_ERROR(tableName + "." + fieldName + ", !positionsList ");
+ return false;
+ }
+
+ //it's a table.*
+ if (fieldName=="*") {
+ if (positionsList->count()>1) {
+ parseInfo.errMsg = i18n("Ambiguous \"%1.*\" expression").arg(tableName);
+ parseInfo.errDescr = i18n("More than one \"%1\" table or alias defined").arg(tableName);
+ return false;
+ }
+ tableForQueryAsterisk = ts;
+// querySchema->addAsterisk( new QueryAsterisk(querySchema, ts) );
+ return true;
+ }
+
+// KexiDBDbg << " --it's a table.name" << endl;
+ Field *realField = ts->field(fieldName);
+ if (!realField) {
+ parseInfo.errMsg = i18n("Field not found");
+ parseInfo.errDescr = i18n("Table \"%1\" has no \"%2\" field")
+ .arg(tableName).arg(fieldName);
+ return false;
+ }
+
+ // check if table or alias is used twice and both have the same column
+ // (so the column is ambiguous)
+ int numberOfTheSameFields = 0;
+ for (QValueList<int>::iterator it = positionsList->begin();
+ it!=positionsList->end();++it)
+ {
+ TableSchema *otherTS = parseInfo.querySchema->tables()->at(*it);
+ if (otherTS->field(fieldName))
+ numberOfTheSameFields++;
+ if (numberOfTheSameFields>1) {
+ parseInfo.errMsg = i18n("Ambiguous \"%1.%2\" expression")
+ .arg(tableName).arg(fieldName);
+ parseInfo.errDescr = i18n("More than one \"%1\" table or alias defined containing \"%2\" field")
+ .arg(tableName).arg(fieldName);
+ return false;
+ }
+ }
+ field = realField; //store
+ tablePositionForField = tablePosition;
+// querySchema->addField(realField, tablePosition);
+
+ return true;
+}
+
+//=========================================
+static QValueList<QCString> FunctionExpr_builtIns;
+static const char* FunctionExpr_builtIns_[] =
+{"SUM", "MIN", "MAX", "AVG", "COUNT", "STD", "STDDEV", "VARIANCE", 0 };
+
+QValueList<QCString> FunctionExpr::builtInAggregates()
+{
+ if (FunctionExpr_builtIns.isEmpty()) {
+ for (const char **p = FunctionExpr_builtIns_; *p; p++)
+ FunctionExpr_builtIns << *p;
+ }
+ return FunctionExpr_builtIns;
+}
+
+FunctionExpr::FunctionExpr( const QString& _name, NArgExpr* args_ )
+ : BaseExpr( 0/*undefined*/ )
+ , name(_name)
+ , args(args_)
+{
+ if (isBuiltInAggregate(name.latin1()))
+ m_cl = KexiDBExpr_Aggregation;
+ else
+ m_cl = KexiDBExpr_Function;
+ if (args)
+ args->setParent( this );
+}
+
+FunctionExpr::FunctionExpr( const FunctionExpr& expr )
+ : BaseExpr( 0/*undefined*/ )
+ , name(expr.name)
+ , args(expr.args ? args->copy() : 0)
+{
+ if (args)
+ args->setParent( this );
+}
+
+FunctionExpr::~FunctionExpr()
+{
+ delete args;
+}
+
+FunctionExpr* FunctionExpr::copy() const
+{
+ return new FunctionExpr(*this);
+}
+
+QString FunctionExpr::debugString()
+{
+ QString res;
+ res.append( QString("FunctionExpr(") + name );
+ if (args)
+ res.append(QString(",") + args->debugString());
+ res.append(QString(",type=%1)").arg(Driver::defaultSQLTypeName(type())));
+ return res;
+}
+
+QString FunctionExpr::toString(QuerySchemaParameterValueListIterator* params)
+{
+ return name + "(" + (args ? args->toString(params) : QString::null) + ")";
+}
+
+void FunctionExpr::getQueryParameters(QuerySchemaParameterList& params)
+{
+ args->getQueryParameters(params);
+}
+
+Field::Type FunctionExpr::type()
+{
+ //TODO
+ return Field::InvalidType;
+}
+
+bool FunctionExpr::validate(ParseInfo& parseInfo)
+{
+ if (!BaseExpr::validate(parseInfo))
+ return false;
+
+ return args ? args->validate(parseInfo) : true;
+}
+
+bool FunctionExpr::isBuiltInAggregate(const QCString& fname)
+{
+ return builtInAggregates().find(fname.upper())!=FunctionExpr_builtIns.end();
+}
diff --git a/kexi/kexidb/expression.h b/kexi/kexidb/expression.h
new file mode 100644
index 000000000..6ee98f326
--- /dev/null
+++ b/kexi/kexidb/expression.h
@@ -0,0 +1,311 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ Design based on nexp.h : Parser module of Python-like language
+ (C) 2001 Jaroslaw Staniek, MIMUW (www.mimuw.edu.pl)
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_EXPRESSION_H
+#define KEXIDB_EXPRESSION_H
+
+#include "field.h"
+#include "queryschema.h"
+
+#include <kdebug.h>
+#include "global.h"
+
+namespace KexiDB {
+
+//! classes
+#define KexiDBExpr_Unknown 0
+#define KexiDBExpr_Unary 1
+#define KexiDBExpr_Arithm 2
+#define KexiDBExpr_Logical 3
+#define KexiDBExpr_Relational 4
+#define KexiDBExpr_SpecialBinary 5
+#define KexiDBExpr_Const 6
+#define KexiDBExpr_Variable 7
+#define KexiDBExpr_Function 8
+#define KexiDBExpr_Aggregation 9
+#define KexiDBExpr_TableList 10
+#define KexiDBExpr_QueryParameter 11
+
+//! Custom tokens are not used in parser but used as extension in expression classes.
+//#define KEXIDB_CUSTOM_TOKEN 0x1000
+
+//! \return class name of class \a c
+KEXI_DB_EXPORT QString exprClassName(int c);
+
+class ParseInfo;
+class NArgExpr;
+class UnaryExpr;
+class BinaryExpr;
+class ConstExpr;
+class VariableExpr;
+class FunctionExpr;
+class QueryParameterExpr;
+class QuerySchemaParameterValueListIterator;
+//class QuerySchemaParameterList;
+
+//! A base class for all expressions
+class KEXI_DB_EXPORT BaseExpr
+{
+public:
+ typedef QPtrList<BaseExpr> List;
+ typedef QPtrListIterator<BaseExpr> ListIterator;
+
+ BaseExpr(int token);
+ virtual ~BaseExpr();
+
+ //! \return a deep copy of this object.
+//! @todo a nonpointer will be returned here when we move to implicit data sharing
+ virtual BaseExpr* copy() const = 0;
+
+ int token() const { return m_token; }
+
+ virtual Field::Type type();
+
+ BaseExpr* parent() const { return m_par; }
+
+ virtual void setParent(BaseExpr *p) { m_par = p; }
+
+ virtual bool validate(ParseInfo& parseInfo);
+
+ /*! \return string as a representation of this expression element by running recursive calls.
+ \a param, if not 0, points to a list item containing value of a query parameter
+ (used in QueryParameterExpr). */
+ virtual QString toString(QuerySchemaParameterValueListIterator* params = 0) = 0;
+
+ /*! Collects query parameters (messages and types) reculsively and saves them to params.
+ The leaf nodes are objects of QueryParameterExpr class. */
+ virtual void getQueryParameters(QuerySchemaParameterList& params) = 0;
+
+ inline void debug() { KexiDBDbg << debugString() << endl; }
+
+ virtual QString debugString();
+
+ /*! \return single character if the token is < 256
+ or token name, e.g. LESS_OR_EQUAL (for debugging). */
+ inline QString tokenToDebugString() { return tokenToDebugString(m_token); }
+
+ static QString tokenToDebugString(int token);
+
+ /*! \return string for token, like "<=" or ">" */
+ virtual QString tokenToString();
+
+ int exprClass() const { return m_cl; }
+
+ /*! Convenience type casts. */
+ NArgExpr* toNArg();
+ UnaryExpr* toUnary();
+ BinaryExpr* toBinary();
+ ConstExpr* toConst();
+ VariableExpr* toVariable();
+ FunctionExpr* toFunction();
+ QueryParameterExpr* toQueryParameter();
+
+protected:
+ int m_cl; //!< class
+ BaseExpr *m_par; //!< parent expression
+ int m_token;
+};
+
+//! A base class N-argument operation
+class KEXI_DB_EXPORT NArgExpr : public BaseExpr
+{
+public:
+ NArgExpr(int aClass, int token);
+ NArgExpr(const NArgExpr& expr);
+ virtual ~NArgExpr();
+ //! \return a deep copy of this object.
+ virtual NArgExpr* copy() const;
+ void add(BaseExpr *expr);
+ void prepend(BaseExpr *expr);
+ BaseExpr *arg(int n);
+ int args();
+ virtual QString debugString();
+ virtual QString toString(QuerySchemaParameterValueListIterator* params = 0);
+ virtual void getQueryParameters(QuerySchemaParameterList& params);
+ virtual bool validate(ParseInfo& parseInfo);
+ BaseExpr::List list;
+};
+
+//! An unary argument operation: + - NOT (or !) ~ "IS NULL" "IS NOT NULL"
+class KEXI_DB_EXPORT UnaryExpr : public BaseExpr
+{
+public:
+ UnaryExpr(int token, BaseExpr *arg);
+ UnaryExpr(const UnaryExpr& expr);
+ virtual ~UnaryExpr();
+ //! \return a deep copy of this object.
+ virtual UnaryExpr* copy() const;
+ virtual Field::Type type();
+ virtual QString debugString();
+ virtual QString toString(QuerySchemaParameterValueListIterator* params = 0);
+ virtual void getQueryParameters(QuerySchemaParameterList& params);
+ BaseExpr *arg() const { return m_arg; }
+ virtual bool validate(ParseInfo& parseInfo);
+
+ BaseExpr *m_arg;
+};
+
+/*! A base class for binary operation
+ - arithmetic operations: + - / * % << >> & | ||
+ - relational operations: = (or ==) < > <= >= <> (or !=) LIKE IN 'SIMILAR TO' 'NOT SIMILAR TO'
+ - logical operations: OR (or ||) AND (or &&) XOR
+ - SpecialBinary "pseudo operators":
+ * e.g. "f1 f2" : token == 0
+ * e.g. "f1 AS f2" : token == AS
+*/
+class KEXI_DB_EXPORT BinaryExpr : public BaseExpr
+{
+public:
+ BinaryExpr(int aClass, BaseExpr *left_expr, int token, BaseExpr *right_expr);
+ BinaryExpr(const BinaryExpr& expr);
+ virtual ~BinaryExpr();
+ //! \return a deep copy of this object.
+ virtual BinaryExpr* copy() const;
+ virtual Field::Type type();
+ virtual QString debugString();
+ virtual QString toString(QuerySchemaParameterValueListIterator* params = 0);
+ virtual void getQueryParameters(QuerySchemaParameterList& params);
+ BaseExpr *left() const { return m_larg; }
+ BaseExpr *right() const { return m_rarg; }
+ virtual bool validate(ParseInfo& parseInfo);
+ virtual QString tokenToString();
+
+ BaseExpr *m_larg;
+ BaseExpr *m_rarg;
+};
+
+/*! String, integer, float constants also includes NULL value.
+ token can be: IDENTIFIER, SQL_NULL, CHARACTER_STRING_LITERAL,
+ INTEGER_CONST, REAL_CONST */
+class KEXI_DB_EXPORT ConstExpr : public BaseExpr
+{
+public:
+ ConstExpr(int token, const QVariant& val);
+ ConstExpr(const ConstExpr& expr);
+ virtual ~ConstExpr();
+ //! \return a deep copy of this object.
+ virtual ConstExpr* copy() const;
+ virtual Field::Type type();
+ virtual QString debugString();
+ virtual QString toString(QuerySchemaParameterValueListIterator* params = 0);
+ virtual void getQueryParameters(QuerySchemaParameterList& params);
+ virtual bool validate(ParseInfo& parseInfo);
+ QVariant value;
+};
+
+//! Query parameter used to getting user input of constant values.
+//! It contains a message that is displayed to the user.
+class KEXI_DB_EXPORT QueryParameterExpr : public ConstExpr
+{
+public:
+ QueryParameterExpr(const QString& message);
+ QueryParameterExpr(const QueryParameterExpr& expr);
+ virtual ~QueryParameterExpr();
+ //! \return a deep copy of this object.
+ virtual QueryParameterExpr* copy() const;
+ virtual Field::Type type();
+ /*! Sets expected type of the parameter. The default is String.
+ This method is called from parent's expression validate().
+ This depends on the type of the related expression.
+ For instance: query "SELECT * FROM cars WHERE name=[enter name]",
+ "[enter name]" has parameter of the same type as "name" field.
+ "=" binary expression's validate() will be called for the left side
+ of the expression and then the right side will have type set to String.
+ */
+ void setType(Field::Type type);
+ virtual QString debugString();
+ virtual QString toString(QuerySchemaParameterValueListIterator* params = 0);
+ virtual void getQueryParameters(QuerySchemaParameterList& params);
+ virtual bool validate(ParseInfo& parseInfo);
+protected:
+ Field::Type m_type;
+};
+
+//! Variables like <i>fieldname</i> or <i>tablename</i>.<i>fieldname</i>
+class KEXI_DB_EXPORT VariableExpr : public BaseExpr
+{
+public:
+ VariableExpr(const QString& _name);
+ VariableExpr(const VariableExpr& expr);
+ virtual ~VariableExpr();
+ //! \return a deep copy of this object.
+ virtual VariableExpr* copy() const;
+ virtual Field::Type type();
+ virtual QString debugString();
+ virtual QString toString(QuerySchemaParameterValueListIterator* params = 0);
+ virtual void getQueryParameters(QuerySchemaParameterList& params);
+
+ /*! Validation. Sets field, tablePositionForField
+ and tableForQueryAsterisk members.
+ See addColumn() in parse.y to see how it's used on column adding. */
+ virtual bool validate(ParseInfo& parseInfo);
+
+ /*! Verbatim name as returned by scanner. */
+ QString name;
+
+ /* NULL by default. After successful validate() it will point to a field,
+ if the variable is of a form "tablename.fieldname" or "fieldname",
+ otherwise (eg. for asterisks) -still NULL.
+ Only meaningful for column expressions within a query. */
+ Field *field;
+
+ /* -1 by default. After successful validate() it will contain a position of a table
+ within query that needs to be bound to the field.
+ This value can be either be -1 if no binding is needed.
+ This value is used in the Parser to call
+ QuerySchema::addField(Field* field, int bindToTable);
+ Only meaningful for column expressions within a query. */
+ int tablePositionForField;
+
+ /*! NULL by default. After successful validate() it will point to a table
+ that is referenced by asterisk, i.e. "*.tablename".
+ This is set to NULL if this variable is not an asterisk of that form. */
+ TableSchema *tableForQueryAsterisk;
+};
+
+//! - aggregation functions like SUM, COUNT, MAX, ...
+//! - builtin functions like CURRENT_TIME()
+//! - user defined functions
+class KEXI_DB_EXPORT FunctionExpr : public BaseExpr
+{
+public:
+ FunctionExpr(const QString& _name, NArgExpr* args_ = 0);
+ FunctionExpr(const FunctionExpr& expr);
+ virtual ~FunctionExpr();
+ //! \return a deep copy of this object.
+ virtual FunctionExpr* copy() const;
+ virtual Field::Type type();
+ virtual QString debugString();
+ virtual QString toString(QuerySchemaParameterValueListIterator* params = 0);
+ virtual void getQueryParameters(QuerySchemaParameterList& params);
+ virtual bool validate(ParseInfo& parseInfo);
+
+ static QValueList<QCString> builtInAggregates();
+ static bool isBuiltInAggregate(const QCString& fname);
+
+ QString name;
+ NArgExpr* args;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/field.cpp b/kexi/kexidb/field.cpp
new file mode 100644
index 000000000..882332727
--- /dev/null
+++ b/kexi/kexidb/field.cpp
@@ -0,0 +1,726 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "field.h"
+#include "connection.h"
+#include "driver.h"
+#include "expression.h"
+#include "utils.h"
+
+// we use here i18n() but this depends on kde libs: TODO: add #ifdefs
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qdatetime.h>
+
+#include <assert.h>
+
+using namespace KexiDB;
+
+Field::FieldTypeNames Field::m_typeNames;
+Field::FieldTypeGroupNames Field::m_typeGroupNames;
+
+Field::Field()
+{
+ init();
+ setConstraints(NoConstraints);
+}
+
+
+Field::Field(TableSchema *tableSchema)
+{
+ init();
+ m_parent = tableSchema;
+ m_order = tableSchema->fieldCount();
+ setConstraints(NoConstraints);
+}
+
+Field::Field(QuerySchema *querySchema, BaseExpr* expr)
+{
+ init();
+ m_parent = querySchema;
+ m_order = querySchema->fieldCount();
+ setConstraints(NoConstraints);
+ if (expr)
+ setExpression(expr);
+}
+
+Field::Field(const QString& name, Type ctype,
+ uint cconst, uint options, uint length, uint precision,
+ QVariant defaultValue, const QString& caption, const QString& description,
+ uint width)
+ : m_parent(0)
+ ,m_name(name.lower())
+ ,m_length(length)
+ ,m_precision(precision)
+ ,m_visibleDecimalPlaces(-1)
+ ,m_options(options)
+ ,m_defaultValue(defaultValue)
+ ,m_order(-1)
+ ,m_caption(caption)
+ ,m_desc(description)
+ ,m_width(width)
+ ,m_expr(0)
+ ,m_customProperties(0)
+ ,m_type(ctype)
+{
+ setConstraints(cconst);
+ if (m_length==0) {//0 means default length:
+ if (m_type==Field::Text)
+ m_length = defaultTextLength();
+ }
+}
+
+/*! Copy constructor. */
+Field::Field(const Field& f)
+{
+ (*this) = f;
+ if (f.m_customProperties)
+ m_customProperties = new CustomPropertiesMap( f.customProperties() );
+
+ if (f.m_expr) {//deep copy the expression
+//TODO m_expr = new BaseExpr(*f.m_expr);
+
+// m_expr->m_field = this;
+ } else
+ m_expr = 0;
+}
+
+Field::~Field()
+{
+ delete m_expr;
+ delete m_customProperties;
+}
+
+Field* Field::copy() const
+{
+ return new Field(*this);
+}
+
+void Field::init()
+{
+ m_parent = 0;
+ m_name = "";
+ m_type = InvalidType;
+ m_length = 0;
+ m_precision = 0;
+ m_visibleDecimalPlaces = -1;
+ m_options = NoOptions;
+ m_defaultValue = QVariant(QString::null);
+ m_order = -1;
+ m_width = 0;
+ m_expr = 0;
+ m_customProperties = 0;
+}
+
+Field::Type Field::type() const
+{
+ if (m_expr)
+ return m_expr->type();
+ return m_type;
+}
+
+QVariant::Type Field::variantType(uint type)
+{
+ switch(type)
+ {
+ case Byte:
+ case ShortInteger:
+ case Integer:
+ case BigInteger:
+ return QVariant::Int;
+ case Boolean:
+ return QVariant::Bool;
+ case Date:
+ return QVariant::Date;
+ case DateTime:
+ return QVariant::DateTime;
+ case Time:
+ return QVariant::Time;
+ case Float:
+ case Double:
+ return QVariant::Double;
+ case Text:
+ case LongText:
+ return QVariant::String;
+ case BLOB:
+ return QVariant::ByteArray;
+ default:
+ return QVariant::Invalid;
+ }
+
+ return QVariant::Invalid;
+}
+
+QString Field::typeName(uint type)
+{
+ m_typeNames.init();
+ return (type <= LastType) ? m_typeNames.at(type) : QString::number(type);
+}
+
+QString Field::typeString(uint type)
+{
+ m_typeNames.init();
+ return (type <= LastType) ? m_typeNames.at((int)LastType+1 + type) : QString("Type%1").arg(type);
+}
+
+QString Field::typeGroupName(uint typeGroup)
+{
+ m_typeGroupNames.init();
+ return (typeGroup <= LastTypeGroup) ? m_typeGroupNames.at(typeGroup) : typeGroupString(typeGroup);
+}
+
+QString Field::typeGroupString(uint typeGroup)
+{
+ m_typeGroupNames.init();
+ return (typeGroup <= LastTypeGroup) ? m_typeGroupNames.at((int)LastTypeGroup+1 + typeGroup) : QString("TypeGroup%1").arg(typeGroup);
+}
+
+Field::Type Field::typeForString(const QString& typeString)
+{
+ m_typeNames.init();
+ QMap<QString,Type>::ConstIterator it = m_typeNames.str2num.find(typeString.lower());
+ if (it==m_typeNames.str2num.end())
+ return InvalidType;
+ return it.data();
+}
+
+Field::TypeGroup Field::typeGroupForString(const QString& typeGroupString)
+{
+ m_typeGroupNames.init();
+ QMap<QString,TypeGroup>::ConstIterator it = m_typeGroupNames.str2num.find(typeGroupString.lower());
+ if (it==m_typeGroupNames.str2num.end())
+ return InvalidGroup;
+ return it.data();
+}
+
+bool Field::isIntegerType( uint type )
+{
+ switch (type) {
+ case Field::Byte:
+ case Field::ShortInteger:
+ case Field::Integer:
+ case Field::BigInteger:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool Field::isNumericType( uint type )
+{
+ switch (type) {
+ case Field::Byte:
+ case Field::ShortInteger:
+ case Field::Integer:
+ case Field::BigInteger:
+ case Field::Float:
+ case Field::Double:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool Field::isFPNumericType( uint type )
+{
+ return type==Field::Float || type==Field::Double;
+}
+
+bool Field::isDateTimeType(uint type)
+{
+ switch (type) {
+ case Field::Date:
+ case Field::DateTime:
+ case Field::Time:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool Field::isTextType( uint type )
+{
+ switch (type) {
+ case Field::Text:
+ case Field::LongText:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool Field::hasEmptyProperty(uint type)
+{
+ return Field::isTextType(type) || type==BLOB;
+}
+
+bool Field::isAutoIncrementAllowed(uint type)
+{
+ return Field::isIntegerType(type);
+}
+
+Field::TypeGroup Field::typeGroup(uint type)
+{
+ if (Field::isTextType(type))
+ return TextGroup;
+ else if (Field::isIntegerType(type))
+ return IntegerGroup;
+ else if (Field::isFPNumericType(type))
+ return FloatGroup;
+ else if (type==Boolean)
+ return BooleanGroup;
+ else if (Field::isDateTimeType(type))
+ return DateTimeGroup;
+ else if (type==BLOB)
+ return BLOBGroup;
+
+ return InvalidGroup; //unknown
+}
+
+TableSchema*
+Field::table() const
+{
+ return dynamic_cast<TableSchema*>(m_parent);
+}
+
+void
+Field::setTable(TableSchema *tableSchema)
+{
+ m_parent = tableSchema;
+}
+
+QuerySchema*
+Field::query() const
+{
+ return dynamic_cast<QuerySchema*>(m_parent);
+}
+
+void
+Field::setQuery(QuerySchema *querySchema)
+{
+ m_parent = querySchema;
+}
+
+void
+Field::setName(const QString& n)
+{
+ m_name = n.lower();
+}
+
+void
+Field::setType(Type t)
+{
+ if (m_expr) {
+ KexiDBWarn << QString("Field::setType(%1)").arg(t)
+ << " could not set type because the field has expression assigned!" << endl;
+ return;
+ }
+ m_type = t;
+}
+
+void
+Field::setConstraints(uint c)
+{
+ m_constraints = c;
+ //pkey must be unique notnull
+ if (isPrimaryKey()) {
+ setPrimaryKey(true);
+ }
+ if (isIndexed()) {
+ setIndexed(true);
+ }
+ if (isAutoIncrement() && !isAutoIncrementAllowed()) {
+ setAutoIncrement(false);
+ }
+}
+
+void
+Field::setLength(uint l)
+{
+ if (type()!=Field::Text)
+ return;
+ m_length = l;
+}
+
+void
+Field::setPrecision(uint p)
+{
+ if (!isFPNumericType())
+ return;
+ m_precision = p;
+}
+
+void
+Field::setScale(uint s)
+{
+ if (!isFPNumericType())
+ return;
+ m_length = s;
+}
+
+void
+Field::setVisibleDecimalPlaces(int p)
+{
+ if (!KexiDB::supportsVisibleDecimalPlacesProperty(type()))
+ return;
+ m_visibleDecimalPlaces = p < 0 ? -1 : p;
+}
+
+void
+Field::setUnsigned(bool u)
+{
+ m_options |= Unsigned;
+ m_options ^= (!u * Unsigned);
+}
+
+void
+Field::setDefaultValue(const QVariant& def)
+{
+ m_defaultValue = def;
+}
+
+bool
+Field::setDefaultValue(const QCString& def)
+{
+ if (def.isNull()) {
+ m_defaultValue = QVariant();
+ return true;
+ }
+
+ bool ok;
+ switch(type())
+ {
+ case Byte: {
+ unsigned int v = def.toUInt(&ok);
+ if (!ok || v > 255)
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant(v);
+ break;
+ }case ShortInteger: {
+ int v = def.toInt(&ok);
+ if (!ok || (!(m_options & Unsigned) && (v < -32768 || v > 32767)) || ((m_options & Unsigned) && (v < 0 || v > 65535)))
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant(v);
+ break;
+ }case Integer: {//4 bytes
+ long v = def.toLong(&ok);
+//js: FIXME if (!ok || (!(m_options & Unsigned) && (-v > 0x080000000 || v > (0x080000000-1))) || ((m_options & Unsigned) && (v < 0 || v > 0x100000000)))
+ if (!ok || (!(m_options & Unsigned) && (-v > (int)0x07FFFFFFF || v > (int)(0x080000000-1))))
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant((Q_LLONG)v);
+ break;
+ }case BigInteger: {//8 bytes
+//! @todo BigInteger support
+/*
+ Q_LLONG long v = def.toLongLong(&ok);
+//TODO: 2-part decoding
+ if (!ok || (!(m_options & Unsigned) && (-v > 0x080000000 || v > (0x080000000-1))))
+ m_defaultValue = QVariant();
+ else
+ if (m_options & Unsigned)
+ m_defaultValue=QVariant((Q_ULLONG) v);
+ else
+ m_defaultValue = QVariant((Q_LLONG)v);*/
+ break;
+ }case Boolean: {
+ unsigned short v = def.toUShort(&ok);
+ if (!ok || v > 1)
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant((bool)v);
+ break;
+ }case Date: {//YYYY-MM-DD
+ QDate date = QDate::fromString( def, Qt::ISODate );
+ if (!date.isValid())
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant(date);
+ break;
+ }case DateTime: {//YYYY-MM-DDTHH:MM:SS
+ QDateTime dt = QDateTime::fromString( def, Qt::ISODate );
+ if (!dt.isValid())
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant(dt);
+ break;
+ }case Time: {//HH:MM:SS
+ QTime time = QTime::fromString( def, Qt::ISODate );
+ if (!time.isValid())
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant(time);
+ break;
+ }case Float: {
+ float v = def.toFloat(&ok);
+ if (!ok || ((m_options & Unsigned) && (v < 0.0)))
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant(v);
+ break;
+ }case Double: {
+ double v = def.toDouble(&ok);
+ if (!ok || ((m_options & Unsigned) && (v < 0.0)))
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant(v);
+ break;
+ }case Text: {
+ if (def.isNull() || (def.length() > 255))
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant((QString)def);
+ break;
+ }case LongText: {
+ if (def.isNull())
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant((QString)def);
+ break;
+ }case BLOB: {
+//TODO
+ if (def.isNull())
+ m_defaultValue = QVariant();
+ else
+ m_defaultValue = QVariant(def);
+ break;
+ }default:
+ m_defaultValue = QVariant();
+ }
+ return m_defaultValue.isNull();
+}
+
+void
+Field::setAutoIncrement(bool a)
+{
+ if (a && !isAutoIncrementAllowed())
+ return;
+ if (isAutoIncrement() != a)
+ m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::AutoInc);
+}
+
+void
+Field::setPrimaryKey(bool p)
+{
+ if(isPrimaryKey() != p)
+ m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::PrimaryKey);
+ if (p) {//also set implied constraints
+ setUniqueKey(true);
+ setNotNull(true);
+ setNotEmpty(true);
+ setIndexed(true);
+ }
+ else {
+//! \todo is this ok for all engines?
+ setAutoIncrement(false);
+ }
+}
+
+void
+Field::setUniqueKey(bool u)
+{
+ if(isUniqueKey() != u) {
+ m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::Unique);
+ if (u)
+ setNotNull(true);
+ }
+}
+
+void
+Field::setForeignKey(bool f)
+{
+ if (isForeignKey() != f)
+ m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::ForeignKey);
+}
+
+void
+Field::setNotNull(bool n)
+{
+ if (isNotNull() != n)
+ m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::NotNull);
+}
+
+void Field::setNotEmpty(bool n)
+{
+ if (isNotEmpty() != n)
+ m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::NotEmpty);
+}
+
+void Field::setIndexed(bool s)
+{
+ if (isIndexed() != s)
+ m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::Indexed);
+ if (!s) {//also set implied constraints
+ setPrimaryKey(false);
+ setUniqueKey(false);
+ setNotNull(false);
+ setNotEmpty(false);
+ }
+}
+
+
+QString Field::debugString() const
+{
+ KexiDB::Connection *conn = table() ? table()->connection() : 0;
+ QString dbg = (m_name.isEmpty() ? "<NONAME> " : m_name + " ");
+ if (m_options & Field::Unsigned)
+ dbg += " UNSIGNED ";
+ dbg += (conn && conn->driver()) ? conn->driver()->sqlTypeName(type()) : Driver::defaultSQLTypeName(type());
+ if (isFPNumericType() && m_precision>0) {
+ if (scale()>0)
+ dbg += QString::fromLatin1("(%1,%2)").arg(m_precision).arg(scale());
+ else
+ dbg += QString::fromLatin1("(%1)").arg(m_precision);
+ }
+ else if (m_type==Field::Text && m_length>0)
+ dbg += QString::fromLatin1("(%1)").arg(m_length);
+ if (m_constraints & Field::AutoInc)
+ dbg += " AUTOINC";
+ if (m_constraints & Field::Unique)
+ dbg += " UNIQUE";
+ if (m_constraints & Field::PrimaryKey)
+ dbg += " PKEY";
+ if (m_constraints & Field::ForeignKey)
+ dbg += " FKEY";
+ if (m_constraints & Field::NotNull)
+ dbg += " NOTNULL";
+ if (m_constraints & Field::NotEmpty)
+ dbg += " NOTEMPTY";
+ if (!m_defaultValue.isNull())
+ dbg += QString(" DEFAULT=[%1]").arg(m_defaultValue.typeName()) + KexiDB::variantToString(m_defaultValue);
+ if (m_expr)
+ dbg += " EXPRESSION=" + m_expr->debugString();
+ if (m_customProperties && !m_customProperties->isEmpty()) {
+ dbg += QString(" CUSTOM PROPERTIES (%1): ").arg(m_customProperties->count());
+ bool first = true;
+ foreach (CustomPropertiesMap::ConstIterator, it, *m_customProperties) {
+ if (first)
+ first = false;
+ else
+ dbg += ", ";
+ dbg += QString("%1 = %2 (%3)").arg(it.key()).arg(it.data().toString()).arg(it.data().typeName());
+ }
+ }
+ return dbg;
+}
+
+void Field::debug()
+{
+ KexiDBDbg << debugString() << endl;
+}
+
+void Field::setExpression(KexiDB::BaseExpr *expr)
+{
+ assert(!m_parent || dynamic_cast<QuerySchema*>(m_parent));
+ if (m_expr==expr)
+ return;
+ if (m_expr) {
+ delete m_expr;
+ }
+ m_expr = expr;
+}
+
+QVariant Field::customProperty(const QCString& propertyName,
+ const QVariant& defaultValue) const
+{
+ if (!m_customProperties)
+ return defaultValue;
+ CustomPropertiesMap::ConstIterator it(m_customProperties->find(propertyName));
+ if (it==m_customProperties->constEnd())
+ return defaultValue;
+ return it.data();
+}
+
+void Field::setCustomProperty(const QCString& propertyName, const QVariant& value)
+{
+ if (propertyName.isEmpty())
+ return;
+ if (!m_customProperties)
+ m_customProperties = new CustomPropertiesMap();
+ m_customProperties->insert(propertyName, value);
+}
+
+//-------------------------------------------------------
+#define ADDTYPE(type, i18, str) this->at(Field::type) = i18; \
+ this->at(Field::type+Field::LastType+1) = str; \
+ str2num.insert(QString::fromLatin1(str).lower(), type)
+#define ADDGROUP(type, i18, str) this->at(Field::type) = i18; \
+ this->at(Field::type+Field::LastTypeGroup+1) = str; \
+ str2num.insert(QString::fromLatin1(str).lower(), type)
+
+Field::FieldTypeNames::FieldTypeNames()
+ : QValueVector<QString>()
+ , m_initialized(false)
+{
+}
+
+void Field::FieldTypeNames::init()
+{
+ if (m_initialized)
+ return;
+ m_initialized = true;
+ resize((Field::LastType + 1)*2);
+
+ ADDTYPE( InvalidType, i18n("Invalid Type"), "InvalidType" );
+ ADDTYPE( Byte, i18n("Byte"), "Byte" );
+ ADDTYPE( ShortInteger, i18n("Short Integer Number"), "ShortInteger" );
+ ADDTYPE( Integer, i18n("Integer Number"), "Integer" );
+ ADDTYPE( BigInteger, i18n("Big Integer Number"), "BigInteger" );
+ ADDTYPE( Boolean, i18n("Yes/No Value"), "Boolean" );
+ ADDTYPE( Date, i18n("Date"), "Date" );
+ ADDTYPE( DateTime, i18n("Date and Time"), "DateTime" );
+ ADDTYPE( Time, i18n("Time"), "Time" );
+ ADDTYPE( Float, i18n("Single Precision Number"), "Float" );
+ ADDTYPE( Double, i18n("Double Precision Number"), "Double" );
+ ADDTYPE( Text, i18n("Text"), "Text" );
+ ADDTYPE( LongText, i18n("Long Text"), "LongText" );
+ ADDTYPE( BLOB, i18n("Object"), "BLOB" );
+}
+
+//-------------------------------------------------------
+
+Field::FieldTypeGroupNames::FieldTypeGroupNames()
+ : QValueVector<QString>()
+ , m_initialized(false)
+{
+}
+
+void Field::FieldTypeGroupNames::init()
+{
+ if (m_initialized)
+ return;
+ m_initialized = true;
+ resize((Field::LastTypeGroup + 1)*2);
+
+ ADDGROUP( InvalidGroup, i18n("Invalid Group"), "InvalidGroup" );
+ ADDGROUP( TextGroup, i18n("Text"), "TextGroup" );
+ ADDGROUP( IntegerGroup, i18n("Integer Number"), "IntegerGroup" );
+ ADDGROUP( FloatGroup, i18n("Floating Point Number"), "FloatGroup" );
+ ADDGROUP( BooleanGroup, i18n("Yes/No"), "BooleanGroup" );
+ ADDGROUP( DateTimeGroup, i18n("Date/Time"), "DateTimeGroup" );
+ ADDGROUP( BLOBGroup, i18n("Object"), "BLOBGroup" );
+}
+
+//-------------------------------------------------------
+
diff --git a/kexi/kexidb/field.h b/kexi/kexidb/field.h
new file mode 100644
index 000000000..1fec04f63
--- /dev/null
+++ b/kexi/kexidb/field.h
@@ -0,0 +1,632 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDB_FIELD_H
+#define KEXIDB_FIELD_H
+
+#include <qvariant.h>
+#include <qstring.h>
+#include <qpair.h>
+#include <qvaluevector.h>
+#include <qptrvector.h>
+#include "kexidb/kexidb_export.h"
+namespace KexiDB {
+
+class TableSchema;
+class QuerySchema;
+class FieldList;
+class BaseExpr;
+
+//! Meta-data for a field
+/*! KexiDB::Field provides information about single database field.
+
+ Field class has defined following members:
+ - name
+ - type
+ - database constraints
+ - additional options
+ - length (make sense mostly for string types)
+ - precision (for floating-point type)
+ - defaultValue
+ - caption (user readable name that can be e.g. translated)
+ - description (user readable name additional text, can be useful for developers)
+ - width (a hint for displaying in tabular mode or as text box)
+
+ Field can also have assigned expression (see KexiDB::BaseExpr class,
+ and expression() method).
+ If an expression is defined, then field's name is
+
+ Note that aliases for fields are defined within query, not in Field object,
+ because the same field can be used in different queries with different alias.
+
+ Notes for advanced use: Field obeject is designed to be owned by a parent object.
+ Such a parent object can be KexiDB::TableSchema, if the field defines single table column,
+ or KexiDB::QuerySchema, if the field defines an expression (KexiDB::BaseExpr class).
+
+ Using expression class for fields allos to define expressions within queries like
+ "SELECT AVG(price) FROM products"
+
+ You can choose whether your field is owned by query or table,
+ using appropriate constructor, or using parameterless constructor and
+ calling setTable() or setQuery() later.
+
+*/
+class KEXI_DB_EXPORT Field
+{
+ public:
+ typedef QPtrList<Field> List; //!< list of fields
+ typedef QPtrVector<Field> Vector; //!< vector of fields
+ typedef QPtrListIterator<Field> ListIterator; //!< iterator for list of fields
+ typedef QPair<Field*,Field*> Pair; //!< fields pair
+ typedef QPtrList<Pair> PairList; //!< list of fields pair
+
+ /*! Unified (most common used) types of fields. */
+ enum Type
+ {
+ InvalidType = 0, /*!< Unsupported/Unimplemented type */
+ Byte = 1, /*!< 1 byte, signed or unsigned */
+ ShortInteger = 2,/*!< 2 bytes, signed or unsigned */
+ Integer = 3, /*!< 4 bytes, signed or unsigned */
+ BigInteger = 4, /*!< 8 bytes, signed or unsigned */
+ Boolean = 5, /*!< 0 or 1 */
+ Date = 6, /*!< */
+ DateTime = 7, /*!< */
+ Time = 8, /*!< */
+ Float = 9, /*!< 4 bytes */
+ Double = 10, /*!< 8 bytes */
+ Text = 11, /*!< Other name: Varchar; no more than 200 bytes, for efficiency */
+ LongText = 12, /*!< Other name: Memo. More than 200 bytes*/
+ BLOB = 13, /*!< Large binary object */
+
+ LastType = 13, /*!< This line should be at the end of the list of types! */
+
+ Null = 64, /*!< Used for fields that are "NULL" expressions. */
+
+ //! Special, internal types:
+ Asterisk = 128, /*!< Used in QueryAsterisk subclass objects only,
+ not used in table definitions,
+ but only in query definitions */
+ Enum = 129, /*!< An integer internal with a string list of hints */
+ Map = 130 /*!< Mapping from string to string list (more generic than Enum */
+ };
+
+//TODO: make this configurable
+ static uint defaultTextLength() { return 200; }
+
+ /*! Type groups for fields. */
+ enum TypeGroup
+ {
+ InvalidGroup = 0,
+ TextGroup = 1,
+ IntegerGroup = 2,
+ FloatGroup = 3,
+ BooleanGroup = 4,
+ DateTimeGroup = 5,
+ BLOBGroup = 6, /* large binary object */
+
+ LastTypeGroup = 6 // This line should be at the end of the enum!
+ };
+
+ /*! Possible constraints defined for a field. */
+ enum Constraints
+ {
+ NoConstraints = 0,
+ AutoInc = 1,
+ Unique = 2,
+ PrimaryKey = 4,
+ ForeignKey = 8,
+ NotNull = 16,
+ NotEmpty = 32, //!< only legal for string-like and blob fields
+ Indexed = 64
+ };
+
+ /*! Possible options defined for a field. */
+ enum Options
+ {
+ NoOptions = 0,
+ Unsigned = 1
+ };
+
+ /*! Creates a database field as a child of \a tableSchema table
+ No other properties are set (even the name), so these should be set later. */
+ Field(TableSchema *tableSchema);
+
+ /*! Creates a database field without any properties set.
+ These should be set later. */
+ Field();
+
+ /*! Creates a database field with specified properties. */
+ Field(const QString& name, Type ctype,
+ uint cconst=NoConstraints,
+ uint options = NoOptions,
+ uint length=0, uint precision=0,
+ QVariant defaultValue=QVariant(),
+ const QString& caption = QString::null,
+ const QString& description = QString::null,
+ uint width = 0);
+
+ /*! Copy constructor. */
+ Field(const Field& f);
+
+ virtual ~Field();
+
+ //! Converts type \a type to QVariant equivalent as accurate as possible
+ static QVariant::Type variantType(uint type);
+
+ /*! \return a i18n'd type name for \a type (\a type has to be an element from Field::Type,
+ not greater than Field::LastType) */
+ static QString typeName(uint type);
+
+ /*! \return type string for \a type, e.g. "Integer" for Integer type
+ (not-i18n'd, \a type has to be an element from Field::Type,
+ not greater than Field::LastType) */
+ static QString typeString(uint type);
+
+ /*! \return type for a given \a typeString */
+ static Type typeForString(const QString& typeString);
+
+ /*! \return type group for a given \a typeGroupString */
+ static TypeGroup typeGroupForString(const QString& typeGroupString);
+
+ /*! \return group for \a type */
+ static TypeGroup typeGroup(uint type);
+
+ /*! \return a i18n'd group name for \a typeGroup
+ (\a typeGroup has to be an element from Field::TypeGroup) */
+ static QString typeGroupName(uint typeGroup);
+
+ /*! \return type group string for \a typeGroup, e.g. "IntegerGroup" for IntegerGroup type
+ (not-i18n'd, \a type has to be an element from Field::Type,
+ not greater than Field::LastType) */
+ static QString typeGroupString(uint typeGroup);
+
+ /* ! \return the name of this field */
+ inline QString name() const { return m_name; }
+
+ /*! \return table schema of table that owns this field
+ or null if it has no table assigned.
+ @see query() */
+ virtual TableSchema* table() const;
+
+ /*! Sets \a table schema of table that owns this field.
+ This does not adds the field to \a table object.
+ You do not need to call this method by hand.
+ Call TableSchema::addField(Field *field) instead.
+ @see setQuery() */
+ virtual void setTable(TableSchema *table);
+
+ /*! For special use when the field defines expression.
+ \return query schema of query that owns this field
+ or null if it has no query assigned.
+ @see table() */
+ QuerySchema* query() const;
+
+ /*! For special use when field defines expression.
+ Sets \a query schema of query that owns this field.
+ This does not adds the field to \a query object.
+ You do not need to call this method by hand.
+ Call QuerySchema::addField() instead.
+ @see setQuery() */
+ void setQuery(QuerySchema *query);
+
+ /*! \return true if the field is autoincrement (e.g. integer/numeric) */
+ inline bool isAutoIncrement() const { return constraints() & AutoInc; }
+
+ /*! \return true if the field is member of single-field primary key */
+ inline bool isPrimaryKey() const { return constraints() & PrimaryKey; }
+
+ /*! \return true if the field is member of single-field unique key */
+ inline bool isUniqueKey() const { return constraints() & Unique; }
+
+ /*! \return true if the field is member of single-field foreign key */
+ inline bool isForeignKey() const { return constraints() & ForeignKey; }
+
+ /*! \return true if the field is not allowed to be null */
+ inline bool isNotNull() const { return constraints() & NotNull; }
+
+ /*! \return true if the field is not allowed to be null */
+ inline bool isNotEmpty() const { return constraints() & NotEmpty; }
+
+ /*! \return true if the field is indexed using single-field database index. */
+ inline bool isIndexed() const { return constraints() & Indexed; }
+
+ /*! \return true if the field is of any numeric type (integer or floating point) */
+ inline bool isNumericType() const { return Field::isNumericType(type()); }
+
+ /*! static version of isNumericType() method
+ *! \return true if the field is of any numeric type (integer or floating point)*/
+ static bool isNumericType(uint type);
+
+ /*! \return true if the field is of any integer type */
+ inline bool isIntegerType() const { return Field::isIntegerType(type()); }
+
+ /*! static version of isIntegerType() method
+ *! \return true if the field is of any integer type */
+ static bool isIntegerType(uint type);
+
+ /*! \return true if the field is of any floating point numeric type */
+ inline bool isFPNumericType() const { return Field::isFPNumericType(type()); }
+
+ /*! static version of isFPNumericType() method
+ *! \return true if the field is of any floating point numeric type */
+ static bool isFPNumericType(uint type);
+
+ /*! \return true if the field is of any date or time related type */
+ inline bool isDateTimeType() const { return Field::isDateTimeType(type()); }
+
+ /*! static version of isDateTimeType() method
+ *! \return true if the field is of any date or time related type */
+ static bool isDateTimeType(uint type);
+
+ /*! @return true if the field is of any text type */
+ inline bool isTextType() const { return Field::isTextType(type()); }
+
+ /*! static version of isTextType() method
+ *! \return true if the field is of any text type */
+ static bool isTextType(uint type);
+
+ uint options() const { return m_options; }
+
+ void setOptions(uint options) { m_options = options; }
+
+ //! Converts field's type to QVariant equivalent as accurate as possible
+ inline QVariant::Type variantType() const { return variantType(type()); }
+
+ /*! \return a type for this field. If there's expression assigned,
+ type of the expression is returned instead. */
+ Type type() const;
+
+ //! \return a i18n'd type name for this field
+ inline QString typeName() const { return Field::typeName(type()); }
+
+ //! \return type group for this field
+ inline TypeGroup typeGroup() const { return Field::typeGroup(type()); }
+
+ //! \return a i18n'd type group name for this field
+ inline QString typeGroupName() const { return Field::typeGroupName(type()); }
+
+ //! \return a type string for this field,
+ //! for example "Integer" string for Field::Integer type.
+ inline QString typeString() const { return Field::typeString(type()); }
+
+ //! \return a type group string for this field,
+ //! for example "Integer" string for Field::IntegerGroup.
+ inline QString typeGroupString() const { return Field::typeGroupString(type()); }
+
+ /*! \return (optional) subtype for this field.
+ Subtype is a string providing additional hint for field's type.
+ E.g. for BLOB type, it can be a MIME type or certain QVariant type name,
+ for example: "QPixmap", "QColor" or "QFont" */
+ inline QString subType() const { return m_subType; }
+
+ /*! Sets (optional) subtype for this field.
+ \sa subType() */
+ inline void setSubType(const QString& subType) { m_subType = subType; }
+
+ //! \return default value for this field. Null value means there
+ //! is no default value declared. The variant value is compatible with field's type.
+ inline QVariant defaultValue() const { return m_defaultValue; }
+
+ /*! \return length of text, only meaningful if the field type is text.
+ 0 means "default length". */
+ inline uint length() const { return m_length; }
+
+ /*! \return precision for numeric and other fields that have both length (scale)
+ and precision (floating point types). */
+ inline uint precision() const { return m_precision; }
+
+ /*! \return scale for numeric and other fields that have both length (scale)
+ and precision (floating point types).
+ The scale of a numeric is the count of decimal digits in the fractional part,
+ to the right of the decimal point. The precision of a numeric is the total count
+ of significant digits in the whole number, that is, the number of digits
+ to both sides of the decimal point. So the number 23.5141 has a precision
+ of 6 and a scale of 4. Integers can be considered to have a scale of zero. */
+ inline uint scale() const { return m_length; }
+
+//! @todo should we keep extended properties here or move them to a QVariant dictionary?
+ /*! \return number of decimal places that should be visible to the user,
+ e.g. within table view widget, form or printout.
+ Only meaningful if the field type is floating point or (in the future: decimal or currency).
+
+ - Any value less than 0 (-1 is the default) means that there should be displayed all digits
+ of the fractional part, except the ending zeros. This is known as "auto" mode.
+ For example, 12.345000 becomes 12.345.
+
+ - Value of 0 means that all the fractional part should be hidden (as well as the dot or comma).
+ For example, 12.345000 becomes 12.
+
+ - Value N > 0 means that the fractional part should take exactly N digits.
+ If the fractional part is shorter than N, additional zeros are appended.
+ For example, "12.345" becomes "12.345000" if N=6.
+ */
+ inline int visibleDecimalPlaces() const { return m_visibleDecimalPlaces; }
+
+ /*! \return the constraints defined for this field. */
+ inline uint constraints() const { return m_constraints; }
+
+ /*! \return order of this field in containing table (counting starts from 0)
+ (-1 if unspecified). */
+ inline int order() const { return m_order; }
+
+ /*! \return caption of this field. */
+ inline QString caption() const { return m_caption; }
+
+ /*! \return caption of this field or - if empty - return its name. */
+ inline QString captionOrName() const { return m_caption.isEmpty() ? m_name : m_caption; }
+
+ /*! \return description text for this field. */
+ inline QString description() const { return m_desc; }
+
+ /*! \return width of this field (usually in pixels or points)
+ 0 (the default) means there is no hint for the width. */
+ inline uint width() const { return m_width; }
+
+ //! if the type has the unsigned attribute
+ inline bool isUnsigned() const { return m_options & Unsigned; }
+
+ /*! \return true if this field has EMPTY property (i.e. it is of type
+ string or is a BLOB). */
+ inline bool hasEmptyProperty() const { return Field::hasEmptyProperty(type()); }
+
+ /*! static version of hasEmptyProperty() method
+ \return true if this field type has EMPTY property (i.e. it is string or BLOB type) */
+ static bool hasEmptyProperty(uint type);
+
+ /*! \return true if this field can be auto-incremented.
+ Actually, returns true for integer field type. \sa IntegerType, isAutoIncrement() */
+ inline bool isAutoIncrementAllowed() const { return Field::isAutoIncrementAllowed(type()); }
+
+ /*! static version of isAutoIncrementAllowed() method
+ \return true if this field type can be auto-incremented. */
+ static bool isAutoIncrementAllowed(uint type);
+
+ /*! Sets type \a t for this field. This does nothing if there's already expression assigned,
+ see expression(). */
+ void setType(Type t);
+
+ /*! Sets name \a name for this field. */
+ void setName(const QString& name);
+
+ /*! Sets constraints to \a c. If PrimaryKey is set in \a c, also
+ constraits implied by being primary key are enforced (see setPrimaryKey()).
+ If Indexed is not set in \a c, constraits implied by not being are
+ enforced as well (see setIndexed()). */
+ void setConstraints(uint c);
+
+ /*! Sets length for this field. Only works for Text Type (even not LongText!).
+ 0 means "default length". @see length() */
+ void setLength(uint l);
+
+ /*! Sets scale for this field. Only works for floating-point types.
+ @see scale() */
+ void setScale(uint s);
+
+ /*! Sets number of decimal places that should be visible to the user.
+ @see visibleDecimalPlaces() */
+ void setVisibleDecimalPlaces(int p);
+
+ /*! Sets scale for this field. Only works for floating-point types. */
+ void setPrecision(uint p);
+
+ /*! Sets unsigned flag for this field. Only works for integer types. */
+ void setUnsigned(bool u);
+
+ /*! Sets default value for this field. Setting null value removes the default value.
+ @see defaultValue() */
+ void setDefaultValue(const QVariant& def);
+
+ /*! Sets default value decoded from QCString.
+ Decoding errors are detected (value is strictly checked against field type)
+ - if one is encountered, default value is cleared (defaultValue()==QVariant()).
+ \return true if given value was valid for field type. */
+ bool setDefaultValue(const QCString& def);
+
+ /*! Sets auto increment flag. Only available to set true,
+ if isAutoIncrementAllowed() is true. */
+ void setAutoIncrement(bool a);
+
+ /*! Specifies whether the field is single-field primary key or not
+ (KexiDB::PrimeryKey item).
+ Use this with caution. Setting this to true implies setting:
+ - setUniqueKey(true)
+ - setNotNull(true)
+ - setNotEmpty(true)
+ - setIndexed(true)
+
+ Setting this to false implies setting setAutoIncrement(false). */
+ void setPrimaryKey(bool p);
+
+ /*! Specifies whether the field has single-field unique constraint or not
+ (KexiDB::Unique item). Setting this to true implies setting Indexed flag
+ to true (setIndexed(true)), because index is required it control unique constraint. */
+ void setUniqueKey(bool u);
+
+ /*! Sets whether the field has to be declared with single-field foreign key.
+ Used in IndexSchema::setForeigKey(). */
+ void setForeignKey(bool f);
+
+ /*! Specifies whether the field has single-field unique constraint or not
+ (KexiDB::NotNull item). Setting this to true implies setting Indexed flag
+ to true (setIndexed(true)), because index is required it control
+ not null constraint. */
+ void setNotNull(bool n);
+
+ /*! Specifies whether the field has single-field unique constraint or not
+ (KexiDB::NotEmpty item). Setting this to true implies setting Indexed flag
+ to true (setIndexed(true)), because index is required it control
+ not empty constraint. */
+ void setNotEmpty(bool n);
+
+ /*! Specifies whether the field is indexed (KexiDB::Indexed item)
+ (by single-field implicit index) or not.
+ Use this with caution. Since index is used to control unique,
+ not null/empty constratins, setting this to false implies setting:
+ - setPrimaryKey(false)
+ - setUniqueKey(false)
+ - setNotNull(false)
+ - setNotEmpty(false)
+ because above flags need index to be present.
+ Similarly, setting one of the above flags to true, will automatically
+ do setIndexed(true) for the same reason. */
+ void setIndexed(bool s);
+
+ /*! Sets caption for this field to \a caption. */
+ void setCaption(const QString& caption) { m_caption=caption; }
+
+ /*! Sets description for this field to \a description. */
+ void setDescription(const QString& description) { m_desc=description; }
+
+ /*! Sets visible width for this field to \a w
+ (usually in pixels or points). 0 means there is no hint for the width. */
+ void setWidth(uint w) { m_width=w; }
+
+ /*! There can be added asterisks (QueryAsterisk objects)
+ to query schemas' field list. QueryAsterisk subclasses Field class,
+ and to check if the given object (pointed by Field*)
+ is asterisk or just ordinary field definition,
+ you can call this method. This is just effective version of QObject::isA().
+ Every QueryAsterisk object returns true here,
+ and every Field object returns false.
+ */
+ virtual bool isQueryAsterisk() const { return false; }
+
+ /*! \return string for debugging purposes. */
+ virtual QString debugString() const;
+
+ /*! Shows debug information about this field. */
+ void debug();
+
+ /*! \return KexiDB::BaseExpr object if the field value is an
+ expression. Unless the expression is set with setExpression(), it is null.
+ */
+ inline KexiDB::BaseExpr *expression() { return m_expr; }
+
+ /*! Sets expression data \a expr. If there was
+ already expression set, it is destroyed before new assignment.
+ This Field object becames owner of \a expr object,
+ so you do not have to worry about deleting it later.
+ If the \a expr is null, current field's expression is deleted, if exists.
+
+ Because the field defines an expression, it should be assigned to a query,
+ not to a table.
+ */
+ void setExpression(KexiDB::BaseExpr *expr);
+
+ /*! \return true if there is expression defined for this field.
+ This method is provided for better readibility
+ - does the same as expression()!=NULL but */
+ inline bool isExpression() const { return m_expr!=NULL; }
+
+//<TMP>
+ /*! \return the hints for enum fields. */
+ QValueVector<QString> enumHints() const { return m_hints; }
+ QString enumHint(uint num) { return (num < m_hints.size()) ? m_hints.at(num) : QString::null; }
+ /*! sets the hint for enum fields */
+ void setEnumHints(const QValueVector<QString> &l) { m_hints = l; }
+//</TMP>
+
+ /*! \return custom property \a propertyName.
+ If there is no such a property, \a defaultValue is returned. */
+ QVariant customProperty(const QCString& propertyName,
+ const QVariant& defaultValue = QVariant()) const;
+
+ //! Sets value \a value for custom property \a propertyName
+ void setCustomProperty(const QCString& propertyName, const QVariant& value);
+
+ //! A data type used for handling custom properties of a field
+ typedef QMap<QCString,QVariant> CustomPropertiesMap;
+
+ //! \return all custom properties
+ inline const CustomPropertiesMap customProperties() const {
+ return m_customProperties ? *m_customProperties : CustomPropertiesMap(); }
+
+ protected:
+ /*! Creates a database field as a child of \a querySchema table
+ Assigns \a expr expression to this field, if present.
+ Used internally by query schemas, e.g. to declare asterisks or
+ to add expression columns.
+ No other properties are set, so these should be set later. */
+ Field(QuerySchema *querySchema, BaseExpr* expr = 0);
+
+ /*! @internal Used by constructors. */
+ void init();
+
+ //! \return a deep copy of this object. Used in @ref FieldList(const FieldList& fl).
+ virtual Field* copy() const;
+
+ FieldList *m_parent; //!< In most cases this points to a TableSchema
+ //!< object that field is assigned.
+ QString m_name;
+ QString m_subType;
+ uint m_constraints;
+ uint m_length; //!< also used for storing scale for floating point types
+ uint m_precision;
+ int m_visibleDecimalPlaces; //!< used in visibleDecimalPlaces()
+ uint m_options;
+ QVariant m_defaultValue;
+ int m_order;
+ QString m_caption;
+ QString m_desc;
+ uint m_width;
+ QValueVector<QString> m_hints;
+
+ KexiDB::BaseExpr *m_expr;
+ CustomPropertiesMap* m_customProperties;
+
+ //! @internal Used in m_typeNames member to handle i18n'd type names
+ class KEXI_DB_EXPORT FieldTypeNames : public QValueVector<QString> {
+ public:
+ FieldTypeNames();
+ void init();
+ QMap<QString,Type> str2num;
+ protected:
+ bool m_initialized : 1;
+ };
+
+ //! @internal Used in m_typeGroupNames member to handle i18n'd type group names
+ class KEXI_DB_EXPORT FieldTypeGroupNames : public QValueVector<QString> {
+ public:
+ FieldTypeGroupNames();
+ void init();
+ QMap<QString,TypeGroup> str2num;
+ protected:
+ bool m_initialized : 1;
+ };
+
+ //! real i18n'd type names (and not-i18n'd type name strings)
+ static FieldTypeNames m_typeNames;
+
+ //! real i18n'd type group names (and not-i18n'd group name strings)
+ static FieldTypeGroupNames m_typeGroupNames;
+
+ private:
+ Type m_type;
+
+ friend class Connection;
+ friend class FieldList;
+ friend class TableSchema;
+ friend class QuerySchema;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/fieldlist.cpp b/kexi/kexidb/fieldlist.cpp
new file mode 100644
index 000000000..ee159c723
--- /dev/null
+++ b/kexi/kexidb/fieldlist.cpp
@@ -0,0 +1,278 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/fieldlist.h>
+#include <kexidb/object.h>
+
+#include <kdebug.h>
+
+#include <assert.h>
+
+using namespace KexiDB;
+
+FieldList::FieldList(bool owner)
+ //reasonable sizes: TODO
+ : m_fields_by_name(101, false)
+{
+ m_fields.setAutoDelete( owner );
+ m_fields_by_name.setAutoDelete( false );
+ m_autoinc_fields = 0;
+}
+
+FieldList::FieldList(const FieldList& fl, bool deepCopyFields)
+ : m_fields_by_name( fl.m_fields_by_name.size() )
+{
+ m_fields.setAutoDelete( fl.m_fields.autoDelete() );
+ m_fields_by_name.setAutoDelete( false );
+ m_autoinc_fields = 0;
+
+ if (deepCopyFields) {
+ //deep copy for the fields
+ for (Field::ListIterator f_it(fl.m_fields); f_it.current(); ++f_it) {
+ Field *f = f_it.current()->copy();
+ if (f_it.current()->m_parent == &fl)
+ f->m_parent = this;
+ addField( f );
+ }
+ }
+}
+
+FieldList::~FieldList()
+{
+ delete m_autoinc_fields;
+}
+
+void FieldList::clear()
+{
+// m_name = QString::null;
+ m_fields.clear();
+ m_fields_by_name.clear();
+ m_sqlFields = QString::null;
+ delete m_autoinc_fields;
+ m_autoinc_fields = 0;
+}
+
+FieldList& FieldList::insertField(uint index, KexiDB::Field *field)
+{
+ assert(field);
+ if (!field)
+ return *this;
+ if (index>m_fields.count()) {
+ KexiDBFatal << "FieldList::insertField(): index (" << index << ") out of range" << endl;
+ return *this;
+ }
+ if (!m_fields.insert(index, field))
+ return *this;
+ if (!field->name().isEmpty())
+ m_fields_by_name.insert(field->name().lower(),field);
+ m_sqlFields = QString::null;
+ return *this;
+}
+
+void FieldList::renameField(const QString& oldName, const QString& newName)
+{
+ renameField( m_fields_by_name[ oldName ], newName );
+}
+
+void FieldList::renameField(KexiDB::Field *field, const QString& newName)
+{
+ if (!field || field != m_fields_by_name[ field->name() ]) {
+ KexiDBFatal << "FieldList::renameField() no field found "
+ << (field ? QString("\"%1\"").arg(field->name()) : QString::null) << endl;
+ return;
+ }
+ m_fields_by_name.take( field->name() );
+ field->setName( newName );
+ m_fields_by_name.insert( field->name(), field );
+}
+
+
+FieldList& FieldList::addField(KexiDB::Field *field)
+{
+ return insertField(m_fields.count(), field);
+}
+
+void FieldList::removeField(KexiDB::Field *field)
+{
+ assert(field);
+ if (!field)
+ return;
+ m_fields_by_name.remove(field->name());
+ m_fields.remove(field);
+ m_sqlFields = QString::null;
+}
+
+Field* FieldList::field(const QString& name)
+{
+ return m_fields_by_name[name];
+}
+
+QString FieldList::debugString()
+{
+ QString dbg;
+ dbg.reserve(512);
+ Field::ListIterator it( m_fields );
+ Field *field;
+ bool start = true;
+ if (!it.current())
+ dbg = "<NO FIELDS>";
+ for (; (field = it.current())!=0; ++it) {
+ if (!start)
+ dbg += ",\n";
+ else
+ start = false;
+ dbg += " ";
+ dbg += field->debugString();
+ }
+ return dbg;
+}
+
+void FieldList::debug()
+{
+ KexiDBDbg << debugString() << endl;
+}
+
+#define _ADD_FIELD(fname) \
+{ \
+ if (fname.isEmpty()) return fl; \
+ f = m_fields_by_name[fname]; \
+ if (!f) { KexiDBWarn << subListWarning1(fname) << endl; delete fl; return 0; } \
+ fl->addField(f); \
+}
+
+static QString subListWarning1(const QString& fname) {
+ return QString("FieldList::subList() could not find field \"%1\"").arg(fname);
+}
+
+FieldList* FieldList::subList(const QString& n1, const QString& n2,
+ const QString& n3, const QString& n4,
+ const QString& n5, const QString& n6,
+ const QString& n7, const QString& n8,
+ const QString& n9, const QString& n10,
+ const QString& n11, const QString& n12,
+ const QString& n13, const QString& n14,
+ const QString& n15, const QString& n16,
+ const QString& n17, const QString& n18)
+{
+ if (n1.isEmpty())
+ return 0;
+ Field *f;
+ FieldList *fl = new FieldList(false);
+ _ADD_FIELD(n1);
+ _ADD_FIELD(n2);
+ _ADD_FIELD(n3);
+ _ADD_FIELD(n4);
+ _ADD_FIELD(n5);
+ _ADD_FIELD(n6);
+ _ADD_FIELD(n7);
+ _ADD_FIELD(n8);
+ _ADD_FIELD(n9);
+ _ADD_FIELD(n10);
+ _ADD_FIELD(n11);
+ _ADD_FIELD(n12);
+ _ADD_FIELD(n13);
+ _ADD_FIELD(n14);
+ _ADD_FIELD(n15);
+ _ADD_FIELD(n16);
+ _ADD_FIELD(n17);
+ _ADD_FIELD(n18);
+ return fl;
+}
+
+FieldList* FieldList::subList(const QStringList& list)
+{
+ Field *f;
+ FieldList *fl = new FieldList(false);
+ for(QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) {
+ _ADD_FIELD( (*it) );
+ }
+ return fl;
+}
+
+FieldList* FieldList::subList(const QValueList<uint>& list)
+{
+ Field *f;
+ FieldList *fl = new FieldList(false);
+ foreach(QValueList<uint>::ConstIterator, it, list) {
+ f = field(*it);
+ if (!f) {
+ KexiDBWarn << QString("FieldList::subList() could not find field at position %1").arg(*it) << endl;
+ delete fl;
+ return 0;
+ }
+ fl->addField(f);
+ }
+ return fl;
+}
+
+QStringList FieldList::names() const
+{
+ QStringList r;
+// for (QDictIterator<Field> it(m_fields_by_name);it.current();++it) {
+// r += it.currentKey().lower();
+// }
+ for (Field::ListIterator it(m_fields); it.current(); ++it) {
+ r += it.current()->name().lower();
+ }
+ return r;
+}
+
+//static
+QString FieldList::sqlFieldsList(Field::List* list, Driver *driver,
+ const QString& separator, const QString& tableAlias, int drvEscaping)
+{
+ if (!list)
+ return QString::null;
+ QString result;
+ result.reserve(256);
+ bool start = true;
+ const QString tableAliasAndDot( tableAlias.isEmpty() ? QString::null : (tableAlias + ".") );
+ for (Field::ListIterator it( *list ); it.current(); ++it) {
+ if (!start)
+ result += separator;
+ else
+ start = false;
+ result += (tableAliasAndDot + driver->escapeIdentifier( it.current()->name(), drvEscaping ));
+ }
+ return result;
+}
+
+QString FieldList::sqlFieldsList(Driver *driver,
+ const QString& separator, const QString& tableAlias, int drvEscaping)
+{
+ if (!m_sqlFields.isEmpty())
+ return m_sqlFields;
+
+ m_sqlFields = FieldList::sqlFieldsList( &m_fields, driver, separator, tableAlias, drvEscaping );
+ return m_sqlFields;
+}
+
+Field::List* FieldList::autoIncrementFields()
+{
+ if (!m_autoinc_fields) {
+ m_autoinc_fields = new Field::List();
+ Field *f;
+ for (Field::ListIterator f_it(m_fields); (f = f_it.current()); ++f_it) {
+ if (f->isAutoIncrement()) {
+ m_autoinc_fields->append( f_it.current() );
+ }
+ }
+ }
+ return m_autoinc_fields;
+}
diff --git a/kexi/kexidb/fieldlist.h b/kexi/kexidb/fieldlist.h
new file mode 100644
index 000000000..fd47459eb
--- /dev/null
+++ b/kexi/kexidb/fieldlist.h
@@ -0,0 +1,175 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_FIELDLIST_H
+#define KEXIDB_FIELDLIST_H
+
+#include <qvaluelist.h>
+#include <qdict.h>
+#include <qstring.h>
+
+#include <kexidb/field.h>
+#include <kexidb/driver.h>
+
+namespace KexiDB {
+
+class Connection;
+
+/*! Helper class that stores list of fields.
+*/
+
+class KEXI_DB_EXPORT FieldList
+{
+ public:
+ /*! Creates empty list of fields. If \a owner is true, the list will be
+ owner of any added field, what means that these field
+ will be removed on the list destruction. Otherwise, the list
+ just points any field that was added.
+ \sa isOwner()
+ */
+ FieldList(bool owner = false);
+
+ /*! Copy constructor.
+ If \a deepCopyFields is true, all fields are deeply copied, else only pointer are copied.
+ Reimplemented in QuerySchema constructor. */
+ FieldList(const FieldList& fl, bool deepCopyFields = true);
+
+ /*! Destroys the list. If the list owns fields (see constructor),
+ these are also deleted. */
+ virtual ~FieldList();
+
+ /*! \return number of fields in the list. */
+ inline uint fieldCount() const { return m_fields.count(); }
+
+ /*! Adds \a field at the and of field list. */
+ FieldList& addField(Field *field);
+
+ /*! Inserts \a field into a specified position (\a index).
+
+ Note: You can reimplement this method but you should still call
+ this implementation in your subclass. */
+ virtual FieldList& insertField(uint index, Field *field);
+
+ /*! Removes field from the field list. Use with care.
+
+ Note: You can reimplement this method but you should still call
+ this implementation in your subclass. */
+ virtual void removeField(KexiDB::Field *field);
+
+ /*! \return field id or NULL if there is no such a field. */
+ inline Field* field(uint id) { return (id < m_fields.count()) ? m_fields.at(id) : 0; }
+
+ /*! \return field with name \a name or NULL if there is no such a field. */
+ virtual Field* field(const QString& name);
+
+ /*! \return true if this list contains given \a field. */
+ inline bool hasField(const Field* field) { return m_fields.findRef(field)!=-1; }
+
+ /*! \return first occurrence of \a field in the list
+ or -1 if this list does not contain this field. */
+ inline int indexOf(const Field* field) { return m_fields.findRef(field); }
+
+ /*! \return list of field names for this list. */
+ QStringList names() const;
+
+ Field::ListIterator fieldsIterator() const { return Field::ListIterator(m_fields); }
+
+ inline Field::List* fields() { return &m_fields; }
+
+ /*! \return list of autoincremented fields. The list is owned by this FieldList object. */
+ Field::List* autoIncrementFields();
+
+ /*! \return true if fields in the list are owned by this list. */
+ inline bool isOwner() const { return m_fields.autoDelete(); }
+
+ /*! Removes all fields from the list. */
+ virtual void clear();
+
+ /*! \return String for debugging purposes. */
+ virtual QString debugString();
+
+ /*! Shows debug information about all fields in the list. */
+ void debug();
+
+ /*! Creates and returns a list that contain fields selected by name.
+ At least one field (exising on this list) should be selected, otherwise 0 is
+ returned. Returned FieldList object is not owned by any parent (so you need
+ to destroy yourself) and Field objects included in it are not owned by it
+ (but still as before, by 'this' object).
+ Returned list can be usable e.g. as argument for Connection::insertRecord().
+ 0 is returned if at least one name cannot be found.
+ */
+ FieldList* subList(const QString& n1, const QString& n2 = QString::null,
+ const QString& n3 = QString::null, const QString& n4 = QString::null,
+ const QString& n5 = QString::null, const QString& n6 = QString::null,
+ const QString& n7 = QString::null, const QString& n8 = QString::null,
+ const QString& n9 = QString::null, const QString& n10 = QString::null,
+ const QString& n11 = QString::null, const QString& n12 = QString::null,
+ const QString& n13 = QString::null, const QString& n14 = QString::null,
+ const QString& n15 = QString::null, const QString& n16 = QString::null,
+ const QString& n17 = QString::null, const QString& n18 = QString::null
+ );
+
+ /*! Like above, but with a QStringList */
+ FieldList* subList(const QStringList& list);
+
+ /*! Like above, but with a list of field indices */
+ FieldList* subList(const QValueList<uint>& list);
+
+ /*! \return a string that is a result of all field names concatenated
+ and with \a separator. This is usable e.g. as argument like "field1,field2"
+ for "INSERT INTO (xxx) ..". The result of this method is effectively cached,
+ and it is invalidated when set of fields changes (e.g. using clear()
+ or addField()).
+ \a tableAlias, if provided is prepended to each field, so the resulting
+ names will be in form tableAlias.fieldName. This option is used for building
+ queries with joins, where fields have to be spicified without ambiguity.
+ See @ref Connection::selectStatement() for example use.
+ \a drvEscaping can be used to alter default escaping type.
+ */
+ QString sqlFieldsList(Driver *driver, const QString& separator = QString::fromLatin1(","),
+ const QString& tableAlias = QString::null,
+ int drvEscaping = Driver::EscapeDriver|Driver::EscapeAsNecessary);
+
+ /*! Like above, but this is convenient static function, so you can pass any \a list here. */
+ static QString sqlFieldsList(Field::List* list, Driver *driver,
+ const QString& separator = QString::fromLatin1(","), const QString& tableAlias = QString::null,
+ int drvEscaping = Driver::EscapeDriver|Driver::EscapeAsNecessary);
+
+ /*! @internal Renames field \a oldName to \a newName.
+ Do not use this for physical renaming columns. Use AlterTableHandler instead. */
+ void renameField(const QString& oldName, const QString& newName);
+
+ /*! @internal
+ \overload void renameField(const QString& oldName, const QString& newName) */
+ void renameField(KexiDB::Field *field, const QString& newName);
+
+ protected:
+ Field::List m_fields;
+ QDict<Field> m_fields_by_name; //!< Fields collected by name. Not used by QuerySchema.
+ Field::List *m_autoinc_fields;
+
+ private:
+ //! cached
+ QString m_sqlFields;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/fieldvalidator.cpp b/kexi/kexidb/fieldvalidator.cpp
new file mode 100644
index 000000000..e657d2faf
--- /dev/null
+++ b/kexi/kexidb/fieldvalidator.cpp
@@ -0,0 +1,100 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "fieldvalidator.h"
+#include "field.h"
+
+#include <kexiutils/longlongvalidator.h>
+#include <knumvalidator.h>
+#include <qwidget.h>
+
+using namespace KexiDB;
+
+FieldValidator::FieldValidator( const Field &field, QWidget * parent, const char * name )
+ : KexiUtils::MultiValidator(parent, name)
+{
+//! @todo merge this code with KexiTableEdit code!
+//! @todo set maximum length validator
+//! @todo handle input mask (via QLineEdit::setInputMask()
+ const Field::Type t = field.type();
+ if (field.isIntegerType()) {
+ QValidator *validator = 0;
+ const bool u = field.isUnsigned();
+ int bottom, top;
+ if (t==Field::Byte) {
+ bottom = u ? 0 : -0x80;
+ top = u ? 0xff : 0x7f;
+ }
+ else if (t==Field::ShortInteger) {
+ bottom = u ? 0 : -0x8000;
+ top = u ? 0xffff : 0x7fff;
+ }
+ else if (t==Field::Integer) {
+ bottom = u ? 0 : -0x7fffffff-1;
+ top = u ? 0xffffffff : 0x7fffffff;
+ }
+ else if (t==Field::BigInteger) {
+//! @todo handle unsigned (using ULongLongValidator)
+ validator = new KexiUtils::LongLongValidator(0);
+ }
+
+ if (!validator)
+ validator = new KIntValidator(bottom, top, 0); //the default
+ addSubvalidator( validator );
+ }
+ else if (field.isFPNumericType()) {
+ QValidator *validator;
+ if (t==Field::Float) {
+ if (field.isUnsigned()) //ok?
+ validator = new KDoubleValidator(0, 3.4e+38, field.scale(), 0);
+ else
+ validator = new KDoubleValidator(this);
+ }
+ else {//double
+ if (field.isUnsigned()) //ok?
+ validator = new KDoubleValidator(0, 1.7e+308, field.scale(), 0);
+ else
+ validator = new KDoubleValidator(this);
+ }
+ addSubvalidator( validator );
+ }
+ else if (t==Field::Date) {
+//! @todo add validator
+// QValidator *validator = new KDateValidator(this);
+// setValidator( validator );
+//moved setInputMask( dateFormatter()->inputMask() );
+ }
+ else if (t==Field::Time) {
+//! @todo add validator
+//moved setInputMask( timeFormatter()->inputMask() );
+ }
+ else if (t==Field::DateTime) {
+//moved setInputMask(
+//moved dateTimeInputMask( *dateFormatter(), *timeFormatter() ) );
+ }
+ else if (t==Field::Boolean) {
+//! @todo add BooleanValidator
+ addSubvalidator( new KIntValidator(0, 1) );
+ }
+}
+
+FieldValidator::~FieldValidator()
+{
+}
+
diff --git a/kexi/kexidb/fieldvalidator.h b/kexi/kexidb/fieldvalidator.h
new file mode 100644
index 000000000..4c5dbbfe7
--- /dev/null
+++ b/kexi/kexidb/fieldvalidator.h
@@ -0,0 +1,49 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_DB_FIELDVALIDATOR_H
+#define KEXI_DB_FIELDVALIDATOR_H
+
+#include <kexidb/kexidb_export.h>
+#include <kexiutils/validator.h>
+
+class QWidget;
+
+namespace KexiDB {
+class Field;
+
+//! @short A validator for KexiDB data types
+/*! This can be used by QLineEdit or subclass to provide validated
+ text entry. Curently is supports all integer types, floating point types and booleans.
+ Internal validators like KIntValidator or KexiUtils::LongLongValidator are used.
+ 'unsigned' and 'scale' parameters are taken into account when setting up internal validators.
+ @todo date/time support for types
+ @todo add validation of the maximum length and other field's properties
+*/
+class KEXI_DB_EXPORT FieldValidator : public KexiUtils::MultiValidator
+{
+ public:
+ //! Setups the validator for \a field. Does not keep a pointer to \a field.
+ FieldValidator( const Field &field, QWidget * parent, const char * name = 0 );
+ ~FieldValidator();
+};
+
+}
+
+#endif
diff --git a/kexi/kexidb/global.cpp b/kexi/kexidb/global.cpp
new file mode 100644
index 000000000..cbbfb723c
--- /dev/null
+++ b/kexi/kexidb/global.cpp
@@ -0,0 +1,55 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/global.h>
+
+using namespace KexiDB;
+
+DatabaseVersionInfo::DatabaseVersionInfo()
+{
+ major = 0;
+ minor = 0;
+}
+
+DatabaseVersionInfo::DatabaseVersionInfo(uint majorVersion, uint minorVersion)
+{
+ major = majorVersion;
+ minor = minorVersion;
+}
+
+//------------------------
+
+ServerVersionInfo::ServerVersionInfo()
+{
+ major = 0;
+ minor = 0;
+ release = 0;
+}
+
+void ServerVersionInfo::clear()
+{
+ major = 0;
+ minor = 0;
+ release = 0;
+ string = QString::null;
+}
+
+//------------------------
+
+DatabaseVersionInfo KexiDB::version() { return KEXIDB_VERSION; }
diff --git a/kexi/kexidb/global.h b/kexi/kexidb/global.h
new file mode 100644
index 000000000..78c1b68b6
--- /dev/null
+++ b/kexi/kexidb/global.h
@@ -0,0 +1,171 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_GLOBAL_H
+#define KEXIDB_GLOBAL_H
+
+#include <kexidb/kexidb_export.h>
+#include <qstring.h>
+
+//global public definitions
+
+/*! KexiDB implementation version.
+ It is altered after every API change:
+ - major number is increased after KexiDB storage format change,
+ - minor is increased after adding binary-incompatible change.
+ In external code: do not use this to get library version information:
+ use KexiDB::versionMajor() and KexiDB::versionMinor() instead to get real version.
+*/
+#define KEXIDB_VERSION_MAJOR 1
+#define KEXIDB_VERSION_MINOR 8
+
+/*! KexiDB implementation version. @see KEXIDB_VERSION_MAJOR, KEXIDB_VERSION_MINOR */
+#define KEXIDB_VERSION KexiDB::DatabaseVersionInfo(KEXIDB_VERSION_MAJOR, KEXIDB_VERSION_MINOR)
+
+/*! \namespace KexiDB
+\brief High-level database connectivity library with database backend drivers
+
+@author Jaroslaw Staniek <js@iidea.pl>
+
+\section Framework
+DriverManager
+
+Database access
+ - Connection
+ - ConnectionData
+
+Database structure
+ - Schema
+ - tableschema
+ - queryschema
+ - indexschema
+
+Stored in the database.
+
+
+Data representation
+ - Record
+ - Field
+
+
+\section Drivers
+
+Drivers are loaded using DriverManager::driver(const QString& name). The names
+of drivers are given in their drivers .desktop file in the
+X-Kexi-DriverName field.
+
+KexiDB supports two kinds of databases: file-based and network-based databases.
+The type of a driver is available from several places. The X-Kexi-DriverType
+field in the driver's .desktop file, is read by the DriverManager and
+available by calling DriverManager::driverInfo(const QString &name) and using
+the Driver::Info#fileBased member from the result. Given a reference to a
+Driver, its type can also be found directly using Driver::isFileDriver() const.
+
+Each database backend driver consists of three main classes: a driver,
+a connection and a cursor class, e.g SQLiteDriver, SQLiteConnection,
+SQLiteCursor.
+
+The driver classes subclass the Driver class. They set Driver#m_typeNames,
+which maps KexiDB's Field::Type on to the types supported by the database. They also
+provide functions for escaping strings and checking table names. These may be
+used, for example, on a database backend that uses the database name as a
+filename. In this case, it should be ensured that all the characters in the
+database name are valid characters in a filename.
+
+The connection classes subclass the Connection class, and include most of the
+calls to the native database API.
+
+The cursor classes subclass Cursor, and implement cursor functionality specific
+to the database backend.
+
+*/
+namespace KexiDB {
+
+#define KexiDBDbg kdDebug(44000) //! Debug area for core KexiDB code
+#define KexiDBDrvDbg kdDebug(44001) //! Debug area for KexiDB's drivers implementation code
+#define KexiDBWarn kdWarning(44000)
+#define KexiDBDrvWarn kdWarning(44001)
+#define KexiDBFatal kdFatal(44000)
+
+/*! @short Contains database version information about a Kexi-compatible database.
+ The version is stored as internal database properties. */
+class KEXI_DB_EXPORT DatabaseVersionInfo
+{
+ public:
+ DatabaseVersionInfo();
+ DatabaseVersionInfo(uint majorVersion, uint minorVersion);
+
+ //! Major version number, e.g. 1 for 1.8
+ uint major;
+
+ //! Minor version number, e.g. 8 for 1.8
+ uint minor;
+};
+
+//! \return KexiDB version info
+KEXI_DB_EXPORT DatabaseVersionInfo version();
+
+/*! @short Contains version information about a database backend. */
+class KEXI_DB_EXPORT ServerVersionInfo
+{
+ public:
+ ServerVersionInfo();
+
+ //! Clears the information - integers will be set to 0 and string to null
+ void clear();
+
+ //! Major version number, e.g. 1 for 1.2.3
+ uint major;
+
+ //! Minor version number, e.g. 2 for 1.2.3
+ uint minor;
+
+ //! Release version number, e.g. 3 for 1.2.3
+ uint release;
+
+ //! Version string, as returned by the server
+ QString string;
+};
+
+/*! Object types set like table or query. */
+enum ObjectTypes {
+ UnknownObjectType = -1, //!< helper
+ AnyObjectType = 0, //!< helper
+ TableObjectType = 1,
+ QueryObjectType = 2,
+ LastObjectType = 2, //ALWAYS UPDATE THIS
+
+ KexiDBSystemTableObjectType = 128,//!< helper, not used in storage
+ //!< (allows to select kexidb system tables
+ //!< may be or'd with TableObjectType)
+ IndexObjectType = 256 //!< special
+};
+
+}
+
+#ifndef futureI18n
+# define futureI18n QString
+# define futureI18n2(a,b) QString(b)
+#endif
+
+#ifndef FUTURE_I18N_NOOP
+# define FUTURE_I18N_NOOP(x) (x)
+#endif
+
+#endif
diff --git a/kexi/kexidb/indexschema.cpp b/kexi/kexidb/indexschema.cpp
new file mode 100644
index 000000000..20dc9e829
--- /dev/null
+++ b/kexi/kexidb/indexschema.cpp
@@ -0,0 +1,199 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/indexschema.h>
+
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/tableschema.h>
+
+#include <assert.h>
+
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+IndexSchema::IndexSchema(TableSchema *tableSchema)
+ : FieldList(false)//fields are not owned by IndexSchema object
+ , SchemaData(KexiDB::IndexObjectType)
+ , m_tableSchema(tableSchema)
+ , m_primary( false )
+ , m_unique( false )
+ , m_isAutoGenerated( false )
+ , m_isForeignKey( false )
+{
+ m_master_owned_rels.setAutoDelete(true); //rels at the master-side are owned
+}
+
+IndexSchema::IndexSchema(const IndexSchema& idx, TableSchema& parentTable)
+// : FieldList(static_cast<const FieldList&>(idx))//fields are not owned by IndexSchema object
+ : FieldList(false)//fields are not owned by IndexSchema object
+ , SchemaData(static_cast<const SchemaData&>(idx))
+ , m_tableSchema(&parentTable)
+ , m_primary( idx.m_primary )
+ , m_unique( idx.m_unique )
+ , m_isAutoGenerated( idx.m_isAutoGenerated )
+ , m_isForeignKey( idx.m_isForeignKey )
+{
+ m_master_owned_rels.setAutoDelete(true); //rels at the master-side are owned
+
+ //deep copy of the fields
+ for (Field::ListIterator f_it(idx.m_fields); f_it.current(); ++f_it) {
+ Field *parentTableField = parentTable.field( f_it.current()->name() );
+ if (!parentTableField) {
+ KexiDBWarn << "IndexSchema::IndexSchema(const IndexSchema& idx, const TableSchema& parentTable): "
+ "cannot find field '" << f_it.current()->name() << " in parentTable. Empty index will be created!" << endl;
+ FieldList::clear();
+ break;
+ }
+ addField( parentTableField );
+ }
+
+//js TODO: copy relationships!
+// Reference::List m_refs_to; //! list of references to table (of this index)
+// Reference::List m_refs_from; //! list of references from the table (of this index),
+// //! this index is foreign key for these references
+// //! and therefore - owner of these
+}
+
+IndexSchema::~IndexSchema()
+{
+ /* It's a list of relationships to the table (of this index), i.e. any such relationship in which
+ the table is at 'master' side will be cleared and relationships will be destroyed.
+ So, we need to detach all these relationships from details-side, corresponding indices.
+ */
+
+ QPtrListIterator<Relationship> it(m_master_owned_rels);
+ for (;it.current();++it) {
+ if (it.current()->detailsIndex()) {
+ it.current()->detailsIndex()->detachRelationship(it.current());
+ }
+ }
+ //ok, now m_master_owned_rels will be just cleared automatically
+}
+
+FieldList& IndexSchema::addField(Field *field)
+{
+ if (field->table() != m_tableSchema) {
+ KexiDBDbg << "IndexSchema::addField(" << (field ? field->name() : 0)
+ << "): WARNING: field doas not belong to the same table '"
+ << (field && field->table() ? field->table()->name() : 0)
+ << "'as index!" << endl;
+ return *this;
+ }
+ return FieldList::addField(field);
+}
+
+
+KexiDB::TableSchema* IndexSchema::table() const
+{
+ return m_tableSchema;
+}
+
+bool IndexSchema::isAutoGenerated() const
+{
+ return m_isAutoGenerated;
+}
+
+void IndexSchema::setAutoGenerated(bool set)
+{
+ m_isAutoGenerated = set;
+}
+
+bool IndexSchema::isPrimaryKey() const
+{
+ return m_primary;
+}
+
+void IndexSchema::setPrimaryKey(bool set)
+{
+ m_primary = set;
+ if (m_primary)
+ m_unique = true;
+}
+
+bool IndexSchema::isUnique() const
+{
+ return m_unique;
+}
+
+void IndexSchema::setUnique(bool set)
+{
+ m_unique=set;
+ if (!m_unique)
+ m_primary=false;
+}
+
+void IndexSchema::setForeignKey(bool set)
+{
+ m_isForeignKey = set;
+ if (m_isForeignKey) {
+ setUnique(false);
+ }
+ if (fieldCount()==1) {
+ m_fields.first()->setForeignKey(true);
+ }
+}
+
+QString IndexSchema::debugString()
+{
+ return QString("INDEX ") + schemaDataDebugString() + "\n"
+ + (m_isForeignKey ? "FOREIGN KEY " : "")
+ + (m_isAutoGenerated ? "AUTOGENERATED " : "")
+ + (m_primary ? "PRIMARY " : "")
+ + ((!m_primary) && m_unique ? "UNIQUE " : "")
+ + FieldList::debugString();
+}
+
+void IndexSchema::attachRelationship(Relationship *rel)
+{
+ attachRelationship(rel, true);
+}
+
+void IndexSchema::attachRelationship(Relationship *rel, bool ownedByMaster)
+{
+ if (!rel)
+ return;
+ if (rel->masterIndex()==this) {
+ if (ownedByMaster) {
+ if (m_master_owned_rels.findRef(rel)==-1) {
+ m_master_owned_rels.append(rel);
+ }
+ }
+ else {//not owned
+ if (m_master_rels.findRef(rel)==-1) {
+ m_master_rels.append(rel);
+ }
+ }
+ }
+ else if (rel->detailsIndex()==this) {
+ if (m_details_rels.findRef(rel)==-1) {
+ m_details_rels.append(rel);
+ }
+ }
+}
+
+void IndexSchema::detachRelationship(Relationship *rel)
+{
+ if (!rel)
+ return;
+ m_master_owned_rels.take( m_master_owned_rels.findRef(rel) ); //for sanity
+ m_master_rels.take( m_master_rels.findRef(rel) ); //for sanity
+ m_details_rels.take( m_details_rels.findRef(rel) ); //for sanity
+}
diff --git a/kexi/kexidb/indexschema.h b/kexi/kexidb/indexschema.h
new file mode 100644
index 000000000..a8bec4338
--- /dev/null
+++ b/kexi/kexidb/indexschema.h
@@ -0,0 +1,209 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_INDEX_H
+#define KEXIDB_INDEX_H
+
+#include <qvaluelist.h>
+#include <qstring.h>
+
+#include <kexidb/fieldlist.h>
+#include <kexidb/schemadata.h>
+#include <kexidb/relationship.h>
+
+namespace KexiDB {
+
+class Connection;
+class TableSchema;
+class QuerySchema;
+class Relationship;
+
+/*! @short Provides information about database index that can be created for a database table.
+
+ IndexSchema object stores information about table fields that
+ defines this index and additional properties like: whether index is unique
+ or primary key (requires unique). Single-field index can be also auto generated.
+*/
+class KEXI_DB_EXPORT IndexSchema : public FieldList, public SchemaData
+{
+ public:
+ typedef QPtrList<IndexSchema> List;
+ typedef QPtrListIterator<IndexSchema> ListIterator;
+
+ /*! Constructs empty index schema object
+ that is assigned to \a table, and will be owned by this table.
+ Any fields added with addField() won't be owned by index,
+ but by its table. Do not forget to add these fields to table,
+ because adding these to IndexSchema is not enough.
+ */
+ IndexSchema(TableSchema *tableSchema);
+
+ /*! Copy constructor. Copies all attributes from index \a idx, and
+ fields assigned with it but the fields are taken (by name) from
+ \a parentTable, not from \a idx itself, so it's possible to copy of index
+ for a copy of table.
+
+ To copy an index within the same table it's enough to call:
+ \code
+ new IndexSchema(idx, *idx.table());
+ \endcode
+ @todo All relationships should be also copied
+ */
+ IndexSchema(const IndexSchema& idx, TableSchema& parentTable);
+
+ /*! Destroys the index. Field objects are not deleted.
+ All Relationship objects listed in masterRelationships() list
+ are destroyed (these are also detached from
+ detail-side indices before destruction).
+ Relationship objects listed in detailsRelationships() are not touched. */
+ virtual ~IndexSchema();
+
+ /*! Adds field at the end of field list.
+ Field will not be owned by index. Field must belong to a table
+ the index is bulit on, otherwise field couldn't be added. */
+ virtual FieldList& addField(Field *field);
+
+ /*! \return table that index is defined for. */
+ TableSchema* table() const;
+
+ /*! \return list of relationships from the table (of this index),
+ i.e. any such relationship in which this table is at 'master' side.
+ See Relationship class documentation for more information.
+ All objects listed here will be automatically destroyed on this IndexSchema object destruction. */
+ Relationship::List* masterRelationships() { return &m_master_rels; }
+
+ /*! \return list of relationships to the table (of this index),
+ i.e. any such relationship in which this table is at 'details' side.
+ See Relationship class documentation for more information. */
+ Relationship::List* detailsRelationships() { return &m_details_rels; }
+
+ /*! Attaches relationship definition \a rel to this IndexSchema object.
+ If \a rel relationship has this IndexSchema defined at the master-side,
+ \a rel is added to the list of master relationships (available with masterRelationships()).
+ If \a rel relationship has this IndexSchema defined at the details-side,
+ \a rel is added to the list of details relationships (available with detailsRelationships()).
+ For the former case, attached \a rel object is now owned by this IndexSchema object.
+
+ Note: call detachRelationship() for IndexSchema object that \a rel
+ was previously attached to, if any. */
+ void attachRelationship(Relationship *rel);
+
+ /*! Detaches relationship definition \a rel for this IndexSchema object
+ from the list of master relationships (available with masterRelationships()),
+ or from details relationships list, depending for which side of the relationship
+ is this IndexSchem object assigned.
+
+ Note: If \a rel was detached from masterRelationships() list,
+ this object now has no parent, so you need to attach it to somewhere or destruct it.
+ */
+ void detachRelationship(Relationship *rel);
+
+ /*! \return true if index is auto-generated.
+ Auto-generated index is one-field index
+ that was automatically generated
+ for CREATE TABLE statement when the field has
+ UNIQUE or PRIMARY KEY constraint enabled.
+
+ Any newly created IndexSchema object
+ has this flag set to false.
+
+ This flag is handled internally by TableSchema.
+ It can be usable for GUI application if we do not
+ want display implicity/auto generated indices
+ on the indices list or we if want to show these
+ indices to the user in a special way.
+ */
+ bool isAutoGenerated() const;
+
+ /*! \return true if this index is primary key of its table.
+ This can be one or multifield. */
+ bool isPrimaryKey() const;
+
+ /*! Sets PRIMARY KEY flag. \sa isPrimary().
+ Note: Setting PRIMARY KEY on (true),
+ UNIQUE flag will be also implicity set. */
+ void setPrimaryKey(bool set);
+
+ /*! \return true if this is unique index.
+ This can be one or multifield. */
+ bool isUnique() const;
+
+ /*! Sets UNIQUE flag. \sa isUnique().
+ Note: Setting UNIQUE off (false), PRIMARY KEY flag will
+ be also implicity set off, because this UNIQUE
+ is the requirement for PRIMARY KEYS. */
+ void setUnique(bool set);
+
+ /*! \return String for debugging purposes. */
+ virtual QString debugString();
+ protected:
+
+ /*! Internal constructor for convenience.
+ Constructs a new index schema object
+ that is assigned, and adds one \a field to it.
+ The field must be assigned to a table.
+ This results in the implicit index that is usually used interanlly
+ to declare foreign keys, used in relationships.
+ */
+// IndexSchema(Field* singleField);
+
+ /*! Sets auto-generated flag. This method should be called only
+ from TableSchema code
+ \sa isAutoGenerated(). */
+ void setAutoGenerated(bool set);
+
+ /*! If \a set is true, declares that the index defines a foreign key,
+ created implicity for Relationship object. Setting this to true, implies
+ clearing 'primary key', 'unique' and 'auto generated' flags.
+ If this index contains just single field, it's 'foreign field'
+ flag will be set to true as well. */
+ void setForeignKey(bool set);
+
+ /*! Internal version of attachRelationship(). If \a ownedByMaster is true,
+ attached \a rel object will be owned by this index. */
+ void attachRelationship(Relationship *rel, bool ownedByMaster);
+
+ TableSchema *m_tableSchema; //! table on that index is built
+
+ /*! a list of master relationships for the table (of this index),
+ this index is a master key for these relationships
+ and therefore - owner of these */
+
+ Relationship::List m_master_owned_rels;
+
+ /*! a list of master relationships that are not owned by this schema */
+ Relationship::List m_master_rels;
+
+ /*! a list of relationships to table (of this index) */
+ Relationship::List m_details_rels;
+
+ bool m_primary : 1;
+ bool m_unique : 1;
+ bool m_isAutoGenerated : 1;
+ bool m_isForeignKey : 1;
+
+ friend class Connection;
+ friend class TableSchema;
+ friend class QuerySchema;
+ friend class Relationship;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/kexidb.pro b/kexi/kexidb/kexidb.pro
new file mode 100644
index 000000000..d16e3f590
--- /dev/null
+++ b/kexi/kexidb/kexidb.pro
@@ -0,0 +1,51 @@
+TEMPLATE = lib
+
+include( $(KEXI)/kexidb/common.pro )
+
+# needed to export library classes:
+DEFINES += MAKE_KEXI_DB_LIB
+
+TARGET = kexidb$$KDEBUG
+
+DEFINES += YYERROR_VERBOSE=1
+
+system( bash kmoc )
+
+SOURCES = \
+object.cpp \
+drivermanager.cpp \
+driver.cpp \
+driver_p.cpp \
+admin.cpp \
+connectiondata.cpp \
+connection.cpp \
+utils.cpp \
+field.cpp \
+schemadata.cpp \
+tableschema.cpp \
+queryschema.cpp \
+queryschemaparameter.cpp \
+transaction.cpp \
+indexschema.cpp \
+cursor.cpp \
+fieldlist.cpp \
+global.cpp \
+relationship.cpp \
+roweditbuffer.cpp \
+msghandler.cpp \
+dbobjectnamevalidator.cpp \
+fieldvalidator.cpp \
+dbproperties.cpp \
+\
+parser/parser.cpp \
+parser/parser_p.cpp \
+parser/sqlparser.cpp \
+parser/sqlscanner.cpp \
+expression.cpp \
+keywords.cpp \
+preparedstatement.cpp \
+alter.cpp \
+lookupfieldschema.cpp \
+simplecommandlineapp.cpp
+
+#HEADERS =
diff --git a/kexi/kexidb/kexidb_driver.desktop b/kexi/kexidb/kexidb_driver.desktop
new file mode 100644
index 000000000..db0e71723
--- /dev/null
+++ b/kexi/kexidb/kexidb_driver.desktop
@@ -0,0 +1,73 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=Kexi/DBDriver
+Comment=Kexi SQL-Driver plugin
+Comment[ar]=ملحق سوّاقة SQL لدى Kexi
+Comment[bg]=Приставка на Kexi за SQL драйвери
+Comment[br]=Lugent SQL-Driver evit Kexi
+Comment[ca]=Extensió del controlador SQL de Kexi
+Comment[cy]=Ategyn Gyrrydd-SQL Kexi
+Comment[da]=Kexi SQL-driver-plugin
+Comment[de]=Kexi SQL-Treiber-Modul
+Comment[el]=Πρόσθετο οδηγού SQL του Kexi
+Comment[eo]=Kexi SQL-pelila kromaĵo
+Comment[es]=Complemento de SQL-Driver de Kexi
+Comment[et]=Kexi SQL-draiveri plugin
+Comment[eu]=Kexi-en SQL-kontrolatzailearen plugina
+Comment[fa]=وصلۀ گردانندۀ Kexi SQL
+Comment[fi]=Kexi SQL-laajennus
+Comment[fr]=Module de pilotage SQL de Kexi
+Comment[fy]=Kexi SQL-stjoerprogramma plugin
+Comment[gl]=Plugin do Controlador de SQL de Kexi
+Comment[he]=תוסף מנהל התקן SQL ל־Kexi
+Comment[hr]=Dodatak za Kexi SQL upravljački program
+Comment[hu]=Kexi SQL-elérési bővítőmodul
+Comment[is]=Kexi SQL-rekil íforrit
+Comment[it]=Driver SQL per Kexi
+Comment[ja]=Kexi SQL ドライバ プラグイン
+Comment[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​បញ្ជា SQL សម្រាប់ Kexi
+Comment[lo]=ປລັກອິນພາບ
+Comment[lt]=Kexi SQL-Driver įskiepis
+Comment[lv]=Kexi SQL-Draivera spraudnis
+Comment[ms]=Plugin Pemacu SQL Kexi
+Comment[nb]=SQL-drivermodul for Kexi
+Comment[nds]=SQL-Drievermoduul för Kexi
+Comment[ne]=केक्सी SQL-ड्राइभर प्लगइन
+Comment[nl]=Kexi SQL-stuurprogramma-plugin
+Comment[nn]=SQL-drivarmodul for Kexi
+Comment[pl]=Wtyczka sterownika SQL dla programu Kexi
+Comment[pt]='Plugin' do Controlador de SQL do Kexi
+Comment[pt_BR]=Plugin de Driver-SQL do Kexi
+Comment[ru]=Модуль драйвера SQL Kexi
+Comment[se]=Kexi SQL-stivrran lassemoduvla
+Comment[sk]=Modul ovládača SQL pre Kexi
+Comment[sl]=Vstavek Kexi SQL-Driver
+Comment[sr]=SQL-управљачки прикључак Kexi-ја
+Comment[sr@Latn]=SQL-upravljački priključak Kexi-ja
+Comment[sv]=Kexi SQL-insticksdrivrutin
+Comment[ta]=kexi SQL இயக்கி சொருகு
+Comment[tg]=Модули драйвери Kexi SQL
+Comment[tr]=Kexi SQl-Sürücüsü eki
+Comment[uk]=Втулок драйвера SQL Kexi
+Comment[uz]=Kexi SQL-drayver plagini
+Comment[uz@cyrillic]=Kexi SQL-драйвер плагини
+Comment[zh_CN]=Kexi SQL 驱动插件
+Comment[zh_TW]=Kexi SQL-驅動外掛程式
+
+[PropertyDef::X-Kexi-FileDBDriverMime]
+Type=QString
+
+[PropertyDef::X-Kexi-FileDBDriverMimeList]
+Type=QStringList
+
+[PropertyDef::X-Kexi-DriverName]
+Type=QString
+
+[PropertyDef::X-Kexi-DriverType]
+Type=QString
+
+[PropertyDef::X-Kexi-KexiDBVersion]
+Type=QString
+
+[PropertyDef::X-Kexi-DoNotAllowProjectImportingTo]
+Type=bool
diff --git a/kexi/kexidb/kexidb_export.h b/kexi/kexidb/kexidb_export.h
new file mode 100644
index 000000000..a9f4a6404
--- /dev/null
+++ b/kexi/kexidb/kexidb_export.h
@@ -0,0 +1,61 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef _KEXIDB_EXPORT_H_
+#define _KEXIDB_EXPORT_H_
+
+#ifdef __cplusplus
+# include <kdeversion.h> /* this will also include <kdelibs_export.h>, if available */
+#endif
+/* KDE_EXPORT will be defined multiple times without this on kdelibs 3.3 (tested on 3.3.1) */
+#include <kdemacros.h>
+
+/* workaround for KDElibs < 3.2 on !win32 */
+#ifndef KDE_EXPORT
+# define KDE_EXPORT
+#endif
+
+/* TODO: #include <koffice_export.h> ??? */
+#ifdef MAKE_KEXI_DB_LIB
+# define KEXI_DB_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXI_DB_EXPORT KDE_IMPORT
+#else
+# define KEXI_DB_EXPORT
+#endif
+
+#ifdef MAKE_KEXIMIGR_LIB
+# define KEXIMIGR_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIMIGR_EXPORT KDE_IMPORT
+#else
+# define KEXIMIGR_EXPORT //for apps
+#endif
+
+/* -- compile-time settings -- */
+#if defined(Q_WS_WIN) || defined(KEXI_OPTIONS)
+/* defined in a .pro file or 'KEXI_OPTIONS' env. variable */
+#else
+
+#endif
+
+/* Might want to add GUI defines here if widgets are to be
+ * distributed as part of kexidb - mart */
+
+#endif //KEXI_EXPORT_H
diff --git a/kexi/kexidb/keywords.cpp b/kexi/kexidb/keywords.cpp
new file mode 100644
index 000000000..055634975
--- /dev/null
+++ b/kexi/kexidb/keywords.cpp
@@ -0,0 +1,92 @@
+ /*
+ * This file has been automatically generated from
+ * koffice/kexi/tools/sql_keywords/sql_keywords.sh and
+ * koffice/kexi/kexidb/parser/sqlscanner.l
+ * and koffice/kexi/tools/sql_keywords/kexi__reserved.
+ *
+ * Please edit the sql_keywords.sh, not this file!
+ */
+#include <driver_p.h>
+
+namespace KexiDB {
+ const char* DriverPrivate::kexiSQLKeywords[] = {
+ "AND",
+ "AS",
+ "CREATE",
+ "FROM",
+ "IN",
+ "INTEGER",
+ "IS",
+ "JOIN",
+ "LEFT",
+ "LIKE",
+ "NOT",
+ "NULL",
+ "ON",
+ "OR",
+ "RIGHT",
+ "SELECT",
+ "SIMILAR",
+ "TABLE",
+ "TO",
+ "WHERE",
+ "XOR",
+ "AFTER",
+ "ALL",
+ "ASC",
+ "BEFORE",
+ "BEGIN",
+ "BETWEEN",
+ "BY",
+ "CASCADE",
+ "CASE",
+ "CHECK",
+ "COLLATE",
+ "COMMIT",
+ "CONSTRAINT",
+ "CROSS",
+ "DATABASE",
+ "DEFAULT",
+ "DELETE",
+ "DESC",
+ "DISTINCT",
+ "DROP",
+ "END",
+ "ELSE",
+ "EXPLAIN",
+ "FOR",
+ "FOREIGN",
+ "FULL",
+ "GROUP",
+ "HAVING",
+ "IGNORE",
+ "INDEX",
+ "INNER",
+ "INSERT",
+ "INTO",
+ "KEY",
+ "LIMIT",
+ "MATCH",
+ "NATURAL",
+ "OFFSET",
+ "ORDER",
+ "OUTER",
+ "PRIMARY",
+ "REFERENCES",
+ "REPLACE",
+ "RESTRICT",
+ "ROLLBACK",
+ "ROW",
+ "SET",
+ "TEMPORARY",
+ "THEN",
+ "TRANSACTION",
+ "UNION",
+ "UNIQUE",
+ "UPDATE",
+ "USING",
+ "VALUES",
+ "WHEN",
+ 0
+ };
+}
diff --git a/kexi/kexidb/lookupfieldschema.cpp b/kexi/kexidb/lookupfieldschema.cpp
new file mode 100644
index 000000000..f21ec5882
--- /dev/null
+++ b/kexi/kexidb/lookupfieldschema.cpp
@@ -0,0 +1,394 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "lookupfieldschema.h"
+#include "utils.h"
+
+#include <qdom.h>
+#include <qvariant.h>
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+
+LookupFieldSchema::RowSource::RowSource()
+: m_type(NoType)
+, m_values(0)
+{
+}
+
+LookupFieldSchema::RowSource::~RowSource()
+{
+ delete m_values;
+}
+
+void LookupFieldSchema::RowSource::setName(const QString& name)
+{
+ m_name = name;
+ if (m_values)
+ m_values->clear();
+}
+
+QString LookupFieldSchema::RowSource::typeName() const
+{
+ switch (m_type) {
+ case Table: return "table";
+ case Query: return "query";
+ case SQLStatement: return "sql";
+ case ValueList: return "valuelist";
+ case FieldList: return "fieldlist";
+ default:;
+ }
+ return QString::null;
+}
+
+void LookupFieldSchema::RowSource::setTypeByName( const QString& typeName )
+{
+ if (typeName=="table")
+ setType( Table );
+ else if (typeName=="query")
+ setType( Query );
+ else if (typeName=="sql")
+ setType( SQLStatement );
+ else if (typeName=="valuelist")
+ setType( ValueList );
+ else if (typeName=="fieldlist")
+ setType( FieldList );
+ else
+ setType( NoType );
+}
+
+QStringList LookupFieldSchema::RowSource::values() const
+{
+ return m_values ? *m_values : QStringList();
+}
+
+void LookupFieldSchema::RowSource::setValues(const QStringList& values)
+{
+ m_name = QString::null;
+ if (m_values)
+ *m_values = values;
+ else
+ m_values = new QStringList(values);
+}
+
+QString LookupFieldSchema::RowSource::debugString() const
+{
+ return QString("rowSourceType:'%1' rowSourceName:'%2' rowSourceValues:'%3'\n")
+ .arg(typeName()).arg(name()).arg(m_values ? m_values->join("|") : QString::null);
+}
+
+void LookupFieldSchema::RowSource::debug() const
+{
+ KexiDBDbg << debugString() << endl;
+}
+
+//---------------------------------------
+
+LookupFieldSchema::LookupFieldSchema()
+ : m_boundColumn(-1)
+ , m_maximumListRows(KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS)
+ , m_displayWidget(KEXIDB_LOOKUP_FIELD_DEFAULT_DISPLAY_WIDGET)
+ , m_columnHeadersVisible(KEXIDB_LOOKUP_FIELD_DEFAULT_HEADERS_VISIBLE)
+ , m_limitToList(KEXIDB_LOOKUP_FIELD_DEFAULT_LIMIT_TO_LIST)
+{
+}
+
+LookupFieldSchema::~LookupFieldSchema()
+{
+}
+
+void LookupFieldSchema::setMaximumListRows(uint rows)
+{
+ if (rows==0)
+ m_maximumListRows = KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS;
+ else if (rows>KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS)
+ m_maximumListRows = KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS;
+ else
+ m_maximumListRows = rows;
+}
+
+QString LookupFieldSchema::debugString() const
+{
+ QString columnWidthsStr;
+ foreach (QValueList<int>::ConstIterator, it, m_columnWidths) {
+ if (!columnWidthsStr.isEmpty())
+ columnWidthsStr.append(";");
+ columnWidthsStr.append( QString::number(*it) );
+ }
+
+ QString visibleColumnsString;
+ foreach (QValueList<uint>::ConstIterator, it, m_visibleColumns) {
+ if (!visibleColumnsString.isEmpty())
+ visibleColumnsString.append(";");
+ visibleColumnsString.append(QString::number(*it));
+ }
+
+ return QString("LookupFieldSchema( %1\n"
+ " boundColumn:%2 visibleColumns:%3 maximumListRows:%4 displayWidget:%5\n"
+ " columnHeadersVisible:%6 limitToList:%7\n"
+ " columnWidths:%8 )")
+ .arg(m_rowSource.debugString())
+ .arg(m_boundColumn).arg(visibleColumnsString).arg(m_maximumListRows)
+ .arg( m_displayWidget==ComboBox ? "ComboBox" : "ListBox")
+ .arg(m_columnHeadersVisible).arg(m_limitToList)
+ .arg(columnWidthsStr);
+}
+
+void LookupFieldSchema::debug() const
+{
+ KexiDBDbg << debugString() << endl;
+}
+
+/* static */
+LookupFieldSchema *LookupFieldSchema::loadFromDom(const QDomElement& lookupEl)
+{
+ LookupFieldSchema *lookupFieldSchema = new LookupFieldSchema();
+ for (QDomNode node = lookupEl.firstChild(); !node.isNull(); node = node.nextSibling()) {
+ QDomElement el = node.toElement();
+ QString name( el.tagName() );
+ if (name=="row-source") {
+ /*<row-source>
+ empty
+ | <type>table|query|sql|valuelist|fieldlist</type> #required because there can be table and query with the same name
+ "fieldlist" (basically a list of column names of a table/query,
+ "Field List" as in MSA)
+ <name>string</name> #table/query name, etc. or KEXISQL SELECT QUERY
+ <values><value>...</value> #for "valuelist" type
+ <value>...</value>
+ </values>
+ </row-source> */
+ for (el = el.firstChild().toElement(); !el.isNull(); el=el.nextSibling().toElement()) {
+ if (el.tagName()=="type")
+ lookupFieldSchema->rowSource().setTypeByName( el.text() );
+ else if (el.tagName()=="name")
+ lookupFieldSchema->rowSource().setName( el.text() );
+//! @todo handle fieldlist (retrieve from external table or so?), use lookupFieldSchema.rowSource().setValues()
+ }
+ }
+ else if (name=="bound-column") {
+ /* <bound-column>
+ <number>number</number> #in later implementation there can be more columns
+ </bound-column> */
+ const QVariant val = KexiDB::loadPropertyValueFromDom( el.firstChild() );
+ if (val.type()==QVariant::Int)
+ lookupFieldSchema->setBoundColumn( val.toInt() );
+ }
+ else if (name=="visible-column") {
+ /* <visible-column> #a column that has to be visible in the combo box
+ <number>number 1</number>
+ <number>number 2</number>
+ [..]
+ </visible-column> */
+ QValueList<uint> list;
+ for (QDomNode childNode = el.firstChild(); !childNode.isNull(); childNode = childNode.nextSibling()) {
+ const QVariant val = KexiDB::loadPropertyValueFromDom( childNode );
+ if (val.type()==QVariant::Int)
+ list.append( val.toUInt() );
+ }
+ lookupFieldSchema->setVisibleColumns( list );
+ }
+ else if (name=="column-widths") {
+ /* <column-widths> #column widths, -1 means 'default'
+ <number>int</number>
+ ...
+ <number>int</number>
+ </column-widths> */
+ QVariant val;
+ QValueList<int> columnWidths;
+ for (el = el.firstChild().toElement(); !el.isNull(); el=el.nextSibling().toElement()) {
+ QVariant val = KexiDB::loadPropertyValueFromDom( el );
+ if (val.type()==QVariant::Int)
+ columnWidths.append(val.toInt());
+ }
+ lookupFieldSchema->setColumnWidths( columnWidths );
+ }
+ else if (name=="show-column-headers") {
+ /* <show-column-headers>
+ <bool>true/false</bool>
+ </show-column-headers> */
+ const QVariant val = KexiDB::loadPropertyValueFromDom( el.firstChild() );
+ if (val.type()==QVariant::Bool)
+ lookupFieldSchema->setColumnHeadersVisible( val.toBool() );
+ }
+ else if (name=="list-rows") {
+ /* <list-rows>
+ <number>1..100</number>
+ </list-rows> */
+ const QVariant val = KexiDB::loadPropertyValueFromDom( el.firstChild() );
+ if (val.type()==QVariant::Int)
+ lookupFieldSchema->setMaximumListRows( val.toUInt() );
+ }
+ else if (name=="limit-to-list") {
+ /* <limit-to-list>
+ <bool>true/false</bool>
+ </limit-to-list> */
+ const QVariant val = KexiDB::loadPropertyValueFromDom( el.firstChild() );
+ if (val.type()==QVariant::Bool)
+ lookupFieldSchema->setLimitToList( val.toBool() );
+ }
+ else if (name=="display-widget") {
+ if (el.text()=="combobox")
+ lookupFieldSchema->setDisplayWidget( LookupFieldSchema::ComboBox );
+ else if (el.text()=="listbox")
+ lookupFieldSchema->setDisplayWidget( LookupFieldSchema::ListBox );
+ }
+ }
+ return lookupFieldSchema;
+}
+
+/* static */
+void LookupFieldSchema::saveToDom(LookupFieldSchema& lookupSchema, QDomDocument& doc, QDomElement& parentEl)
+{
+ QDomElement lookupColumnEl, rowSourceEl, rowSourceTypeEl, nameEl;
+ if (!lookupSchema.rowSource().name().isEmpty()) {
+ lookupColumnEl = doc.createElement("lookup-column");
+ parentEl.appendChild( lookupColumnEl );
+
+ rowSourceEl = doc.createElement("row-source");
+ lookupColumnEl.appendChild( rowSourceEl );
+
+ rowSourceTypeEl = doc.createElement("type");
+ rowSourceEl.appendChild( rowSourceTypeEl );
+ rowSourceTypeEl.appendChild( doc.createTextNode(lookupSchema.rowSource().typeName()) ); //can be empty
+
+ nameEl = doc.createElement("name");
+ rowSourceEl.appendChild( nameEl );
+ nameEl.appendChild( doc.createTextNode(lookupSchema.rowSource().name()) );
+ }
+
+ const QStringList& values( lookupSchema.rowSource().values() );
+ if (!values.isEmpty()) {
+ QDomElement valuesEl( doc.createElement("values") );
+ rowSourceEl.appendChild( valuesEl );
+ for (QStringList::ConstIterator it = values.constBegin(); it!=values.constEnd(); ++it) {
+ QDomElement valueEl( doc.createElement("value") );
+ valuesEl.appendChild( valueEl );
+ valueEl.appendChild( doc.createTextNode(*it) );
+ }
+ }
+
+ if (lookupSchema.boundColumn()>=0)
+ KexiDB::saveNumberElementToDom(doc, lookupColumnEl, "bound-column", lookupSchema.boundColumn());
+
+ QValueList<uint> visibleColumns(lookupSchema.visibleColumns());
+ if (!visibleColumns.isEmpty()) {
+ QDomElement visibleColumnEl( doc.createElement("visible-column") );
+ lookupColumnEl.appendChild( visibleColumnEl );
+ foreach (QValueList<uint>::ConstIterator, it, visibleColumns) {
+ QDomElement numberEl( doc.createElement("number") );
+ visibleColumnEl.appendChild( numberEl );
+ numberEl.appendChild( doc.createTextNode( QString::number(*it) ) );
+ }
+ }
+
+ const QValueList<int> columnWidths(lookupSchema.columnWidths());
+ if (!columnWidths.isEmpty()) {
+ QDomElement columnWidthsEl( doc.createElement("column-widths") );
+ lookupColumnEl.appendChild( columnWidthsEl );
+ for (QValueList<int>::ConstIterator it = columnWidths.constBegin(); it!=columnWidths.constEnd(); ++it) {
+ QDomElement columnWidthEl( doc.createElement("number") );
+ columnWidthsEl.appendChild( columnWidthEl );
+ columnWidthEl.appendChild( doc.createTextNode( QString::number(*it) ) );
+ }
+ }
+
+ if (lookupSchema.columnHeadersVisible()!=KEXIDB_LOOKUP_FIELD_DEFAULT_HEADERS_VISIBLE)
+ KexiDB::saveBooleanElementToDom(doc, lookupColumnEl, "show-column-headers", lookupSchema.columnHeadersVisible());
+ if (lookupSchema.maximumListRows()!=KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS)
+ KexiDB::saveNumberElementToDom(doc, lookupColumnEl, "list-rows", lookupSchema.maximumListRows());
+ if (lookupSchema.limitToList()!=KEXIDB_LOOKUP_FIELD_DEFAULT_LIMIT_TO_LIST)
+ KexiDB::saveBooleanElementToDom(doc, lookupColumnEl, "limit-to-list", lookupSchema.limitToList());
+
+ if (lookupSchema.displayWidget()!=KEXIDB_LOOKUP_FIELD_DEFAULT_DISPLAY_WIDGET) {
+ QDomElement displayWidgetEl( doc.createElement("display-widget") );
+ lookupColumnEl.appendChild( displayWidgetEl );
+ displayWidgetEl.appendChild(
+ doc.createTextNode( (lookupSchema.displayWidget()==ListBox) ? "listbox" : "combobox" ) );
+ }
+}
+
+//static
+bool LookupFieldSchema::setProperty(
+ LookupFieldSchema& lookup, const QCString& propertyName, const QVariant& value )
+{
+ bool ok;
+ if ("rowSource" == propertyName || "rowSourceType" == propertyName || "rowSourceValues" == propertyName) {
+ LookupFieldSchema::RowSource rowSource( lookup.rowSource() );
+ if ("rowSource" == propertyName)
+ rowSource.setName(value.toString());
+ else if ("rowSourceType" == propertyName)
+ rowSource.setTypeByName(value.toString());
+ else if ("rowSourceValues" == propertyName)
+ rowSource.setValues(value.toStringList());
+ lookup.setRowSource(rowSource);
+ }
+ else if ("boundColumn" == propertyName ) {
+ const int ival = value.toInt(&ok);
+ if (!ok)
+ return false;
+ lookup.setBoundColumn( ival );
+ }
+ else if ("visibleColumn" == propertyName ) {
+ QValueList<QVariant> variantList;
+ if (value.type()==QVariant::Int) {
+//! @todo Remove this case: it's for backward compatibility with Kexi's 1.1.2 table designer GUI
+//! supporting only single lookup column.
+ variantList.append( value.toInt() );
+ }
+ else {
+ variantList = value.toList();
+ }
+ QValueList<uint> visibleColumns;
+ foreach (QValueList<QVariant>::ConstIterator, it, variantList) {
+ const uint ival = (*it).toUInt(&ok);
+ if (!ok)
+ return false;
+ visibleColumns.append( ival );
+ }
+ lookup.setVisibleColumns( visibleColumns );
+ }
+ else if ("columnWidths" == propertyName ) {
+ QValueList<QVariant> variantList( value.toList() );
+ QValueList<int> widths;
+ foreach (QValueList<QVariant>::ConstIterator, it, variantList) {
+ const uint ival = (*it).toInt(&ok);
+ if (!ok)
+ return false;
+ widths.append( ival );
+ }
+ lookup.setColumnWidths( widths );
+ }
+ else if ("showColumnHeaders" == propertyName ) {
+ lookup.setColumnHeadersVisible( value.toBool() );
+ }
+ else if ("listRows" == propertyName ) {
+ lookup.setMaximumListRows( value.toBool() );
+ }
+ else if ("limitToList" == propertyName ) {
+ lookup.setLimitToList( value.toBool() );
+ }
+ else if ("displayWidget" == propertyName ) {
+ const uint ival = value.toUInt(&ok);
+ if (!ok || ival > LookupFieldSchema::ListBox)
+ return false;
+ lookup.setDisplayWidget((LookupFieldSchema::DisplayWidget)ival);
+ }
+ return true;
+}
diff --git a/kexi/kexidb/lookupfieldschema.h b/kexi/kexidb/lookupfieldschema.h
new file mode 100644
index 000000000..9494b348e
--- /dev/null
+++ b/kexi/kexidb/lookupfieldschema.h
@@ -0,0 +1,236 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDB_LOOKUPFIELDSCHEMA_H
+#define KEXIDB_LOOKUPFIELDSCHEMA_H
+
+#include <qvaluelist.h>
+#include <qstringlist.h>
+
+class QDomElement;
+class QDomDocument;
+class QVariant;
+
+namespace KexiDB {
+
+//! default value for LookupFieldSchema::columnHeadersVisible()
+#define KEXIDB_LOOKUP_FIELD_DEFAULT_HEADERS_VISIBLE false
+
+//! default value for LookupFieldSchema::maximumListRows()
+#define KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS 8
+
+//! maximum value for LookupFieldSchema::maximumListRows()
+#define KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS 100
+
+//! default value for LookupFieldSchema::limitToList()
+#define KEXIDB_LOOKUP_FIELD_DEFAULT_LIMIT_TO_LIST true
+
+//! default value for LookupFieldSchema::displayWidget()
+#define KEXIDB_LOOKUP_FIELD_DEFAULT_DISPLAY_WIDGET KexiDB::LookupFieldSchema::ComboBox
+
+
+//! @short Provides information about lookup field's setup.
+/*!
+ LookupFieldSchema object is owned by TableSchema and created upon creating or retrieving the table schema
+ from the database metadata.
+
+ @see LookupFieldSchema *TableSchema::lookupFieldSchema( Field& field ) const
+*/
+class KEXI_DB_EXPORT LookupFieldSchema
+{
+ public:
+
+ //! Row source information that can be specified for the lookup field schema
+ class KEXI_DB_EXPORT RowSource {
+ public:
+ //! Row source type
+ enum Type {
+ NoType, //!< used for invalid schema
+ Table, //!< table as lookup row source
+ Query, //!< named query as lookup row source
+ SQLStatement, //!< anonymous query as lookup row source
+ ValueList, //!< a fixed list of values as lookup row source
+ FieldList //!< a list of column names from a table/query will be displayed
+ };
+
+ RowSource();
+ ~RowSource();
+
+ /*! @return row source type: table, query, anonymous; in the future it will
+ be also fixed value list and field list. The latter is basically a list
+ of column names of a table/query, "Field List" in MSA. */
+ Type type() const { return m_type; }
+
+ /*! Sets row source type to \a type. */
+ void setType(Type type) { m_type = type; }
+
+ /*! @return row source type name. @see setTypeByName() */
+ QString typeName() const;
+
+ /*! Sets row source type by name using \a typeName. Accepted (cast sensitive)
+ names are "table", "query", "sql", "valuelist", "fieldlist".
+ For other value NoType type is set. */
+ void setTypeByName( const QString& typeName );
+
+ /*! @return a string for row source: table name, query name or anonymous query
+ provided as KEXISQL string. If rowSourceType() is a ValueList,
+ rowSourceValues() should be used instead. If rowSourceType() is a FieldList,
+ rowSource() should return table or query name. */
+ QString name() const { return m_name; }
+
+ /*! Sets row source value. @see value() */
+ void setName(const QString& name);
+
+ /*! @return row source values specified if type() is ValueList. */
+ QStringList values() const;
+
+ /*! Sets row source values used if type() is ValueList.
+ Using it clears name (see name()). */
+ void setValues(const QStringList& values);
+
+ /*! \return String for debugging purposes. */
+ QString debugString() const;
+
+ /*! Shows debug information. */
+ void debug() const;
+ private:
+ Type m_type;
+ QString m_name;
+ QStringList *m_values;
+ };
+
+ LookupFieldSchema();
+
+ ~LookupFieldSchema();
+
+ /*! @return row source information for the lookup field schema */
+ RowSource& rowSource() { return m_rowSource; }
+
+ /*! Sets row source for the lookup field schema */
+ void setRowSource(const RowSource& rowSource) { m_rowSource = rowSource; }
+
+ /*! @return bound column: an integer specifying a column that is bound
+ (counted from 0). -1 means unspecified value. */
+//! @todo in later implementation there can be more columns
+ int boundColumn() const { return m_boundColumn; }
+
+ /*! Sets bound column number to \a column. @see boundColumn() */
+ void setBoundColumn(int column) { m_boundColumn = column>=0 ? column : -1; }
+
+ /*! @return a list of visible column: a list of integers specifying a column that has
+ to be visible in the combo box (counted from 0).
+ Empty list means unspecified value. */
+ QValueList<uint> visibleColumns() const { return m_visibleColumns; }
+
+ /*! Sets a list of visible columns to \a list.
+ Columns will be separated with a single space character when displayed. */
+ void setVisibleColumns(const QValueList<uint>& list) { m_visibleColumns = list; }
+
+ /*! A helper method.
+ If visibleColumns() contains one item, this item is returned (a typical case).
+ If visibleColumns() contains no item, -1 is returned.
+ If visibleColumns() multiple items, \a fieldsCount - 1 is returned. */
+ inline int visibleColumn(uint fieldsCount) const {
+ if (m_visibleColumns.count()==1)
+ return (m_visibleColumns.first() < fieldsCount)
+ ? (int)m_visibleColumns.first() : -1;
+ if (m_visibleColumns.isEmpty())
+ return -1;
+ return fieldsCount - 1;
+ }
+
+ /*! @return a number of ordered integers specifying column widths;
+ -1 means 'default width' for a given column. */
+ const QValueList<int> columnWidths() const { return m_columnWidths; }
+
+ /*! Sets column widths. @see columnWidths */
+ void setColumnWidths(const QValueList<int>& widths) { m_columnWidths = widths; }
+
+ /*! @return true if column headers are visible in the associated
+ combo box popup or the list view. The default is false. */
+ bool columnHeadersVisible() const { return m_columnHeadersVisible; }
+
+ /*! Sets "column headers visibility" flag. @see columnHeadersVisible() */
+ void setColumnHeadersVisible(bool set) { m_columnHeadersVisible = set; }
+
+ /*! @return integer property specifying a maximum number of rows
+ that can be displayed in a combo box popup or a list box. The default is
+ equal to KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS constant. */
+ uint maximumListRows() const { return m_maximumListRows; }
+
+ /*! Sets maximum number of rows that can be displayed in a combo box popup
+ or a list box. If \a rows is 0, KEXIDB_LOOKUP_FIELD_DEFAULT_LIST_ROWS is set.
+ If \a rows is greater than KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS,
+ KEXIDB_LOOKUP_FIELD_MAX_LIST_ROWS is set. */
+ void setMaximumListRows(uint rows);
+
+ /*! @return true if , only values present on the list can be selected using
+ the combo box. The default is true. */
+ bool limitToList() const { return m_limitToList; }
+
+ /*! Sets "limit to list" flag. @see limitToList() */
+ void setLimitToList(bool set) { m_limitToList = set; }
+
+ //! used in displayWidget()
+ enum DisplayWidget {
+ ComboBox = 0, //!< (the default) combobox widget should be displayed in forms for this lookup field
+ ListBox = 1 //!< listbox widget should be displayed in forms for this lookup field
+ };
+
+ /*! @return the widget type that should be displayed within
+ the forms for this lookup field. The default is ComboBox.
+ For the Table View, combo box is always displayed. */
+ DisplayWidget displayWidget() const { return m_displayWidget; }
+
+ /*! Sets type of widget to display within the forms for this lookup field. @see displayWidget() */
+ void setDisplayWidget(DisplayWidget widget) { m_displayWidget = widget; }
+
+ /*! \return String for debugging purposes. */
+ QString debugString() const;
+
+ /*! Shows debug information. */
+ void debug() const;
+
+ /*! Loads data of lookup column schema from DOM tree.
+ The data can be outdated or invalid, so the app should handle such cases.
+ @return a new LookupFieldSchema object even if lookupEl contains no valid contents. */
+ static LookupFieldSchema* loadFromDom(const QDomElement& lookupEl);
+
+ /*! Saves data of lookup column schema to \a parentEl DOM element of \a doc document. */
+ static void saveToDom(LookupFieldSchema& lookupSchema, QDomDocument& doc, QDomElement& parentEl);
+
+ /*! Sets property of name \a propertyName and value \a value for the lookup schema \a lookup
+ \return true on successful set and false on failure because of invalid value or invalid property name. */
+ static bool setProperty(
+ LookupFieldSchema& lookup, const QCString& propertyName, const QVariant& value );
+
+ protected:
+ RowSource m_rowSource;
+ int m_boundColumn;
+ QValueList<uint> m_visibleColumns;
+ QValueList<int> m_columnWidths;
+ uint m_maximumListRows;
+ DisplayWidget m_displayWidget;
+ bool m_columnHeadersVisible : 1;
+ bool m_limitToList : 1;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/msghandler.cpp b/kexi/kexidb/msghandler.cpp
new file mode 100644
index 000000000..1cacae5ef
--- /dev/null
+++ b/kexi/kexidb/msghandler.cpp
@@ -0,0 +1,62 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/msghandler.h>
+
+using namespace KexiDB;
+
+MessageTitle::MessageTitle(Object* o, const QString& msg)
+ : m_obj(o)
+ , m_prevMsgTitle(o->m_msgTitle)
+{
+ m_obj->m_msgTitle = msg;
+}
+
+MessageTitle::~MessageTitle()
+{
+ m_obj->m_msgTitle = m_prevMsgTitle;
+}
+
+//------------------------------------------------
+
+MessageHandler::MessageHandler(QWidget *parent)
+ : m_messageHandlerParentWidget(parent)
+ , m_enableMessages(true)
+{
+}
+
+MessageHandler::~MessageHandler()
+{
+}
+
+int MessageHandler::askQuestion( const QString& message,
+ KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult,
+ const KGuiItem &buttonYes,
+ const KGuiItem &buttonNo,
+ const QString &dontShowAskAgainName,
+ int options )
+{
+ Q_UNUSED(message);
+ Q_UNUSED(dlgType);
+ Q_UNUSED(buttonYes);
+ Q_UNUSED(buttonNo);
+ Q_UNUSED(dontShowAskAgainName);
+ Q_UNUSED(options);
+ return defaultResult;
+}
diff --git a/kexi/kexidb/msghandler.h b/kexi/kexidb/msghandler.h
new file mode 100644
index 000000000..da907c7e6
--- /dev/null
+++ b/kexi/kexidb/msghandler.h
@@ -0,0 +1,98 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_MSGHANDLER_H
+#define KEXIDB_MSGHANDLER_H
+
+#include <kexidb/object.h>
+#include <qguardedptr.h>
+#include <qwidget.h>
+
+namespace KexiDB {
+
+/*! A helper class for setting temporary message title for an KexiDB::Object.
+ Message title is a text prepended to error or warning messages.
+ Use it this way:
+ \code
+ KexiDB::MessageTitle title(myKexiDBObject, i18n("Terrible error occurred"));
+ \endcode
+ After leaving current from code block, object's message title will be reverted
+ to previous value.
+*/
+class KEXI_DB_EXPORT MessageTitle
+{
+ public:
+ MessageTitle(KexiDB::Object* o, const QString& msg = QString::null);
+ ~MessageTitle();
+
+ protected:
+ Object* m_obj;
+ QString m_prevMsgTitle;
+};
+
+/*! A prototype for Message Handler usable
+ for reacting on messages sent by KexiDB::Object object(s).
+*/
+class KEXI_DB_EXPORT MessageHandler
+{
+ public:
+ enum MessageType { Error, Sorry, Warning };
+
+ /*! Constructs mesage handler, \a parent is a widget that will be a parent
+ for displaying gui elements (e.g. message boxes). Can be 0 for non-gui usage. */
+ MessageHandler(QWidget *parent = 0);
+ virtual ~MessageHandler();
+
+ /*! This method can be used to block/unblock messages.
+ Sometimes you are receiving both lower- and higher-level messages,
+ but you do not need to display two message boxes but only one (higher level with details).
+ All you need is to call enableMessages(false) before action that can fail
+ and restore messages by enableMessages(true) after the action.
+ See KexiMainWindowImpl::renameObject() implementation for example. */
+ inline void enableMessages(bool enable) { m_enableMessages = enable; }
+
+ /*! Shows error message with \a title (it is not caption) and details. */
+ virtual void showErrorMessage(const QString &title,
+ const QString &details = QString::null) = 0;
+
+ /*! Shows error message with \a msg text. Existing error message from \a obj object
+ is also copied, if present. */
+ virtual void showErrorMessage(KexiDB::Object *obj, const QString& msg = QString::null) = 0;
+
+ /*! Interactively asks a question. For GUI version, KMessageBox class is used.
+ See KMessageBox documentation for explanation of the parameters.
+ \a defaultResult is returned in case when no message handler is installed.
+ \a message should be i18n's string.
+ Value from KMessageBox::ButtonCode enum is returned.
+ Reimplement this. This implementation does nothing, just returns \a defaultResult. */
+ virtual int askQuestion( const QString& message,
+ KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult,
+ const KGuiItem &buttonYes=KStdGuiItem::yes(),
+ const KGuiItem &buttonNo=KStdGuiItem::no(),
+ const QString &dontShowAskAgainName = QString::null,
+ int options = KMessageBox::Notify );
+
+ protected:
+ QGuardedPtr<QWidget> m_messageHandlerParentWidget;
+ bool m_enableMessages : 1;
+};
+
+}
+
+#endif
diff --git a/kexi/kexidb/object.cpp b/kexi/kexidb/object.cpp
new file mode 100644
index 000000000..f4228bc72
--- /dev/null
+++ b/kexi/kexidb/object.cpp
@@ -0,0 +1,191 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/object.h>
+#include <kexidb/error.h>
+#include <kexidb/msghandler.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+#define ERRMSG(a) \
+ { if (m_msgHandler) m_msgHandler->showErrorMessage(a); }
+
+Object::Object(MessageHandler* handler)
+: m_previousServerResultNum(0)
+, m_previousServerResultNum2(0)
+, m_msgHandler(handler)
+, d(0) //empty
+{
+ clearError();
+}
+
+Object::~Object()
+{
+}
+
+#define STORE_PREV_ERR \
+ m_previousServerResultNum = m_previousServerResultNum2; \
+ m_previousServerResultName = m_previousServerResultName2; \
+ m_previousServerResultNum2 = serverResult(); \
+ m_previousServerResultName2 = serverResultName(); \
+ KexiDBDbg << "Object ERROR: " << m_previousServerResultNum2 << ": " \
+ << m_previousServerResultName2 <<endl
+
+void Object::setError( int code, const QString &msg )
+{
+ STORE_PREV_ERR;
+
+ m_errno=code;
+ m_errorSql = m_sql;
+ if (m_errno==ERR_OTHER && msg.isEmpty())
+ m_errMsg = i18n("Unspecified error encountered");
+ else
+ m_errMsg = msg;
+ m_hasError = code!=ERR_NONE;
+
+ if (m_hasError)
+ ERRMSG(this);
+}
+
+void Object::setError( const QString &msg )
+{
+ setError( ERR_OTHER, msg );
+}
+
+void Object::setError( const QString& title, const QString &msg )
+{
+ STORE_PREV_ERR;
+
+ m_errno=ERR_OTHER;
+ QString origMsgTitle( m_msgTitle ); //store
+
+ m_msgTitle += title;
+ m_errMsg = msg;
+ m_errorSql = m_sql;
+ m_hasError = true;
+ if (m_hasError)
+ ERRMSG(this);
+
+ m_msgTitle = origMsgTitle; //revert
+}
+
+void Object::setError( KexiDB::Object *obj, const QString& prependMessage )
+{
+ setError( obj, obj ? obj->errorNum() : ERR_OTHER, prependMessage );
+}
+
+void Object::setError( KexiDB::Object *obj, int code, const QString& prependMessage )
+{
+ if (obj && (obj->errorNum()!=0 || !obj->serverErrorMsg().isEmpty())) {
+ STORE_PREV_ERR;
+
+ m_errno = obj->errorNum();
+ m_hasError = obj->error();
+ if (m_errno==0) {
+ m_errno = code;
+ m_hasError = true;
+ }
+ m_errMsg = (prependMessage.isEmpty() ? QString::null : (prependMessage + " "))
+ + obj->errorMsg();
+ m_sql = obj->m_sql;
+ m_errorSql = obj->m_errorSql;
+ m_serverResult = obj->serverResult();
+ if (m_serverResult==0) //try copied
+ m_serverResult = obj->m_serverResult;
+ m_serverResultName = obj->serverResultName();
+ if (m_serverResultName.isEmpty()) //try copied
+ m_serverResultName = obj->m_serverResultName;
+ m_serverErrorMsg = obj->serverErrorMsg();
+ if (m_serverErrorMsg.isEmpty()) //try copied
+ m_serverErrorMsg = obj->m_serverErrorMsg;
+ //override
+ if (code!=0 && code!=ERR_OTHER)
+ m_errno = code;
+ if (m_hasError)
+ ERRMSG(this);
+ }
+ else {
+ setError( code!=0 ? code : ERR_OTHER, prependMessage );
+ }
+}
+
+void Object::clearError()
+{
+ m_errno = 0;
+ m_hasError = false;
+ m_errMsg = QString::null;
+ m_sql = QString::null;
+ m_errorSql = QString::null;
+ m_serverResult = 0;
+ m_serverResultName = QString::null;
+ m_serverErrorMsg = QString::null;
+ drv_clearServerResult();
+}
+
+QString Object::serverErrorMsg()
+{
+ return m_serverErrorMsg;
+}
+
+int Object::serverResult()
+{
+ return m_serverResult;
+}
+
+QString Object::serverResultName()
+{
+ return m_serverResultName;
+}
+
+void Object::debugError()
+{
+ if (error()) {
+ KexiDBDbg << "KEXIDB ERROR: " << errorMsg() << endl;
+ QString s = serverErrorMsg(), sn = serverResultName();
+ if (!s.isEmpty())
+ KexiDBDbg << "KEXIDB SERVER ERRMSG: " << s << endl;
+ if (!sn.isEmpty())
+ KexiDBDbg << "KEXIDB SERVER RESULT NAME: " << sn << endl;
+ if (serverResult()!=0)
+ KexiDBDbg << "KEXIDB SERVER RESULT #: " << serverResult() << endl;
+ } else
+ KexiDBDbg << "KEXIDB OK." << endl;
+}
+
+int Object::askQuestion( const QString& message,
+ KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult,
+ const KGuiItem &buttonYes,
+ const KGuiItem &buttonNo,
+ const QString &dontShowAskAgainName,
+ int options,
+ MessageHandler* msgHandler )
+{
+ if (msgHandler)
+ return msgHandler->askQuestion(message, dlgType, defaultResult, buttonYes, buttonNo,
+ dontShowAskAgainName, options);
+
+ if (m_msgHandler)
+ return m_msgHandler->askQuestion(message, dlgType, defaultResult, buttonYes, buttonNo,
+ dontShowAskAgainName, options);
+
+ return defaultResult;
+}
diff --git a/kexi/kexidb/object.h b/kexi/kexidb/object.h
new file mode 100644
index 000000000..aff98491b
--- /dev/null
+++ b/kexi/kexidb/object.h
@@ -0,0 +1,186 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_OBJECT_H
+#define KEXIDB_OBJECT_H
+
+#include <kexidb/error.h>
+#include <kmessagebox.h>
+#include <kstdguiitem.h>
+#include <qstring.h>
+
+namespace KexiDB {
+
+class MessageHandler;
+
+/*! Prototype of KexiDB object, handles result of last operation.
+*/
+class KEXI_DB_EXPORT Object
+{
+ public:
+ /*! \return true if there was error during last operation on the object. */
+ bool error() const { return m_hasError; }
+
+ /*! \return (localized) error message if there was error during last operation on the object,
+ else: 0. */
+ const QString& errorMsg() const { return m_errMsg; }
+
+ /*! \return error number of if there was error during last operation on the object,
+ else: 0. */
+ int errorNum() const { return m_errno; }
+
+ //! \return previous server result number, for error displaying purposes.
+ int previousServerResult() const { return m_previousServerResultNum; }
+
+ QString previousServerResultName() const { return m_previousServerResultName; }
+
+ /*! Sends errorMsg() to debug output. */
+ void debugError();
+
+ /*! Clears error flag.
+ Also calls drv_clearServerResult().
+ You can reimplement this method in subclasses to clear even more members,
+ but remember to also call Object::clearError(). */
+ virtual void clearError();
+
+ /*! KexiDB library offers detailed error numbers using errorNum()
+ and detailed error i18n'd messages using errorMsg() -
+ this information is not engine-dependent (almost).
+ Use this in your application to give users more information on what's up.
+
+ This method returns (non-i18n'd !) engine-specific error message,
+ if there was any error during last server-side operation,
+ otherwise null string.
+ Reimplement this for your driver
+ - default implementation just returns null string.
+ \sa serverErrorMsg()
+ */
+ virtual QString serverErrorMsg();
+
+ /*! \return engine-specific last server-side operation result number.
+ Use this in your application to give users more information on what's up.
+
+ Reimplement this for your driver - default implementation just returns 0.
+ Note that this result value is not the same as the one returned
+ by errorNum() (Object::m_errno member)
+ \sa serverErrorMsg(), drv_clearServerResult()
+ */
+ virtual int serverResult();
+
+ /*! \return engine-specific last server-side operation result name,
+ (name for serverResult()).
+ Use this in your application to give users more information on what's up.
+
+ Reimplement this for your driver - default implementation
+ just returns null string.
+ Note that this result name is not the same as the error message returned
+ by serverErorMsg() or erorMsg()
+ \sa serverErrorMsg(), drv_clearServerResult()
+ */
+ virtual QString serverResultName();
+
+ /*! \return message title that sometimes is provided and prepended
+ to the main warning/error message. Used by MessageHandler. */
+ QString msgTitle() const { return m_msgTitle; }
+
+ /*! \return sql string of actually executed SQL statement,
+ usually using drv_executeSQL(). If there was error during executing SQL statement,
+ before, that string is returned instead. */
+ const QString recentSQLString() const { return m_errorSql.isEmpty() ? m_sql : m_errorSql; }
+
+ protected:
+ /* Constructs a new object.
+ \a handler can be provided to receive error messages. */
+ Object(MessageHandler* handler = 0);
+
+ virtual ~Object();
+
+ /*! Sets the (localized) error code to \a code and message to \a msg.
+ You have to set at least nonzero error code \a code,
+ although it is also adviced to set descriptive message \a msg.
+ Eventually, if you omit all parameters, ERR_OTHER code will be set
+ and default message for this will be set.
+ Use this in KexiDB::Object subclasses to informa the world about your
+ object's state. */
+ virtual void setError(int code = ERR_OTHER, const QString &msg = QString::null );
+
+ /*! \overload void setError(int code, const QString &msg = QString::null )
+ Sets error code to ERR_OTHER. Use this if you don't care about
+ setting error code.
+ */
+ virtual void setError( const QString &msg );
+
+ /*! \overload void setError(const QString &msg)
+ Also sets \a title. */
+ virtual void setError( const QString &title, const QString &msg );
+
+ /*! Copies the (localized) error message and code from other KexiDB::Object. */
+ void setError( KexiDB::Object *obj, const QString& prependMessage = QString::null );
+
+ /*! Copies the (localized) error message and code from other KexiDB::Object
+ with custom error \a code. */
+ virtual void setError( KexiDB::Object *obj, int code,
+ const QString& prependMessage = QString::null );
+
+ /*! Interactively asks a question. Console or GUI can be used for this,
+ depending on installed message handler. For GUI version, KMessageBox class is used.
+ See KexiDB::MessageHandler::askQuestion() for details. */
+ virtual int askQuestion( const QString& message,
+ KMessageBox::DialogType dlgType, KMessageBox::ButtonCode defaultResult,
+ const KGuiItem &buttonYes=KStdGuiItem::yes(),
+ const KGuiItem &buttonNo=KStdGuiItem::no(),
+ const QString &dontShowAskAgainName = QString::null,
+ int options = KMessageBox::Notify,
+ MessageHandler* msgHandler = 0 );
+
+ /*! Clears number of last server operation's result stored
+ as a single integer. Formally, this integer should be set to value
+ that means "NO ERRORS" or "OK". This method is called by clearError().
+ For reimplementation. By default does nothing.
+ \sa serverErrorMsg()
+ */
+ virtual void drv_clearServerResult() {};
+
+ //! used to store of actually executed SQL statement
+ QString m_sql, m_errorSql;
+ int m_serverResult;
+ QString m_serverResultName, m_serverErrorMsg;
+ QString m_errMsg;
+
+ private:
+ int m_errno;
+ bool m_hasError;
+
+ //! previous server result number, for error displaying purposes.
+ int m_previousServerResultNum, m_previousServerResultNum2;
+ //! previous server result name, for error displaying purposes.
+ QString m_previousServerResultName, m_previousServerResultName2;
+
+ QString m_msgTitle;
+ MessageHandler *m_msgHandler;
+
+ class Private;
+ Private *d; //!< for future extensions
+
+ friend class MessageTitle;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/parser/Makefile.am b/kexi/kexidb/parser/Makefile.am
new file mode 100644
index 000000000..31cfbc1c3
--- /dev/null
+++ b/kexi/kexidb/parser/Makefile.am
@@ -0,0 +1,38 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexidbparser.la
+libkexidbparser_la_SOURCES = sqlscanner.cpp sqlparser.cpp parser.cpp parser_p.cpp
+libkexidbparser_la_LIBADD = $(LIB_KPARTS) $(LIB_KDEUI) ../libkexidb.la
+libkexidbparser_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO)
+
+noinst_HEADERS = parser_p.h
+
+INCLUDES = -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/kexidb $(all_includes)
+METASOURCES = AUTO
+
+parser:
+ cd $(srcdir); \
+ lex -osqlscanner.cpp sqlscanner.l; \
+ bison -dv sqlparser.y; \
+ echo '#ifndef _SQLPARSER_H_' > sqlparser.h; \
+ echo '#define _SQLPARSER_H_' >> sqlparser.h; \
+ echo '#include "field.h"' >> sqlparser.h; \
+ echo '#include "parser.h"' >> sqlparser.h; \
+ echo '#include "sqltypes.h"' >> sqlparser.h; \
+ echo '' >> sqlparser.h; \
+ echo 'bool parseData(KexiDB::Parser *p, const char *data);' >> sqlparser.h; \
+ cat sqlparser.tab.h >> sqlparser.h; \
+ echo '#endif' >> sqlparser.h; \
+ cat sqlparser.tab.c > sqlparser.cpp; \
+ echo "const char * const tname(int offset) { return yytname[offset]; }" >> sqlparser.cpp; \
+ ./extract_tokens.sh > tokens.cpp; \
+ rm -f sqlparser.tab.h sqlparser.tab.c
+
+coffie:
+ echo 'making coffie...'
+ sleep 5
+
+KDE_OPTIONS=nofinal
+KDE_CXXFLAGS += -DYYERROR_VERBOSE=1
+
+.PHONY: parser coffie
diff --git a/kexi/kexidb/parser/TODO b/kexi/kexidb/parser/TODO
new file mode 100644
index 000000000..dbef39425
--- /dev/null
+++ b/kexi/kexidb/parser/TODO
@@ -0,0 +1,9 @@
+- interpretion
+ - CREATE TABLE
+ - missing keys
+ - SELECT
+ - ALTER
+ - UPDATE
+ - only op code for now, ignore rest
+ - INSERT
+ - only op code for now, ignore rest
diff --git a/kexi/kexidb/parser/extract_tokens.sh b/kexi/kexidb/parser/extract_tokens.sh
new file mode 100755
index 000000000..4be9fdb14
--- /dev/null
+++ b/kexi/kexidb/parser/extract_tokens.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+echo "/* WARNING! All changes made in this file will be lost! Run 'make parser' instead. */"
+for t in `grep "\"[a-zA-Z_]*\"" sqlscanner.l | sed -e "s/\(^[^\"]*\)\"\([^\"]*\)\".*$/\2/g" | sort | uniq` ; do
+ if [ "$t" = "ZZZ" ] ; then break ; fi
+ echo "INS(\"$t\");";
+done
diff --git a/kexi/kexidb/parser/parser.cpp b/kexi/kexidb/parser/parser.cpp
new file mode 100644
index 000000000..f64ec96d3
--- /dev/null
+++ b/kexi/kexidb/parser/parser.cpp
@@ -0,0 +1,155 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <connection.h>
+#include <tableschema.h>
+#include "parser.h"
+#include "parser_p.h"
+#include "sqlparser.h"
+
+extern const char * reserved_keywords[];
+
+using namespace KexiDB;
+
+Parser::Parser(Connection *db)
+ : d(new ParserPrivate)
+{
+ d->db = db;
+}
+
+Parser::~Parser()
+{
+ delete d;
+}
+
+Parser::OPCode Parser::operation() const { return (OPCode)d->operation; }
+
+QString
+Parser::operationString() const
+{
+ switch((OPCode)d->operation) {
+ case OP_Error:
+ return "Error";
+ case OP_CreateTable:
+ return "CreateTable";
+ case OP_AlterTable:
+ return "AlterTable";
+ case OP_Select:
+ return "Select";
+ case OP_Insert:
+ return "Insert";
+ case OP_Update:
+ return "Update";
+ case OP_Delete:
+ return "Delete";
+ default: //OP_None
+ return "None";
+ }
+}
+
+TableSchema *Parser::table() { TableSchema *t = d->table; d->table=0; return t; }
+
+QuerySchema *Parser::query() { QuerySchema *s = d->select; d->select=0; return s; }
+
+Connection *Parser::db() const { return d->db; }
+
+ParserError Parser::error() const { return d->error; }
+
+QString Parser::statement() const { return d->statement; }
+
+void Parser::setOperation(OPCode op) { d->operation = op; }
+
+QuerySchema *Parser::select() const { return d->select; }
+
+void Parser::setError(const ParserError &err) { d->error = err; }
+
+void
+Parser::createTable(const char *t)
+{
+ if (d->table)
+ return;
+
+ d->table = new KexiDB::TableSchema(t);
+}
+
+void
+Parser::setQuerySchema(QuerySchema *query)
+{
+ if (d->select)
+ delete d->select;
+
+ d->select = query;
+}
+
+void Parser::init()
+{
+ if (d->initialized)
+ return;
+#define INS(p) d->reservedKeywords.insert(p, (char*)1, 0)
+#include "tokens.cpp"
+ d->initialized = true;
+}
+
+bool Parser::isReservedKeyword(const char *str)
+{
+ return d->reservedKeywords.find(str);
+}
+
+bool
+Parser::parse(const QString &statement)
+{
+ init();
+ clear();
+ d->statement = statement;
+
+ KexiDB::Parser *oldParser = parser;
+ KexiDB::Field *oldField = field;
+ bool res = parseData(this, statement.utf8());
+ parser = oldParser;
+ field = oldField;
+ return res;
+}
+
+void
+Parser::clear()
+{
+ d->clear();
+}
+
+//-------------------------------------
+
+ParserError::ParserError()
+: m_at(-1)
+{
+// m_isNull = true;
+}
+
+ParserError::ParserError(const QString &type, const QString &error, const QString &hint, int at)
+{
+ m_type = type;
+ m_error = error;
+ m_hint = hint;
+ m_at = at;
+}
+
+ParserError::~ParserError()
+{
+}
+
diff --git a/kexi/kexidb/parser/parser.h b/kexi/kexidb/parser/parser.h
new file mode 100644
index 000000000..ec2942e1f
--- /dev/null
+++ b/kexi/kexidb/parser/parser.h
@@ -0,0 +1,240 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDBPARSER_H
+#define KEXIDBPARSER_H
+
+#include <qobject.h>
+#include <qptrlist.h>
+#include <qvariant.h>
+
+#include <kexidb/field.h>
+#include <kexidb/expression.h>
+
+namespace KexiDB
+{
+
+class Connection;
+class QuerySchema;
+class TableSchema;
+class Field;
+
+/**
+ * Provides detailed i18n'ed error description about the \a Parser .
+ */
+class KEXI_DB_EXPORT ParserError
+{
+ public:
+
+ /**
+ * Empty constructor.
+ */
+ ParserError();
+
+ /**
+ * Constructor.
+ *
+ * \param type The errortype.
+ * \param error A description of the error.
+ * \param hint Token where the error happend.
+ * \param at The position where the error happend.
+ */
+ ParserError(const QString &type, const QString &error, const QString &hint, int at);
+
+ /**
+ * Destructor.
+ */
+ ~ParserError();
+
+ /**
+ * \return the errortype.
+ */
+ QString type() { return m_type; }
+
+ /**
+ * \return a descriping error message.
+ */
+ QString error() { return m_error; }
+
+ /**
+ * \return position where the error happend.
+ */
+ int at() { return m_at; }
+
+ private:
+ QString m_type;
+ QString m_error;
+ QString m_hint;
+ int m_at;
+// bool m_isNull;
+};
+
+class ParserPrivate;
+
+/**
+ * Parser for SQL statements.
+ *
+ * The best and prefeerred way to run queries is using the KexiDB::Parser functionality
+ * and use the resulting QuerySchema object since this offers a database-backend-independent
+ * way to deal with SQL statements on the one hand and offers high level
+ * functionality on the other. Also BLOBs like images are handled that way.
+ *
+ * For example if we like to use the SELECT statement
+ * "SELECT dir.path, media.filename FROM dir, media WHERE dir.id=media.dirId AND media.id=%s"
+ * we are able to use the \a Connection::prepareStatement method which takes the type of
+ * the statement (in our case \a PreparedStatement::SelectStatement ), a list of fields (in
+ * our case dir.path and media.filename) and returns a \a PreparedStatement::Ptr instance.
+ * By using the \a QuerySchema::addRelationship and \a QuerySchema::addToWhereExpression methods
+ * the SQL statement could be extended with relationships and WHERE expressions.
+ *
+ * For more, see \a KexiDB::PreparedStatement and \a Connection::selectStatement() . A more
+ * complex example that looks at what the user has defined and carefully builds
+ * \a KexiDB::QuerySchema object, including the WHERE expression can be found in
+ * the Query Designer's source code in the method \a KexiQueryDesignerGuiEditor::buildSchema().
+ */
+class KEXI_DB_EXPORT Parser
+{
+ public:
+
+ /**
+ * The operation-code of the statement.
+ */
+ enum OPCode
+ {
+ OP_None = 0, /// No statement parsed or reseted.
+ OP_Error, /// Error while parsing.
+ OP_CreateTable, /// Create a table.
+ OP_AlterTable, /// Alter an existing table
+ OP_Select, /// Query-statement.
+ OP_Insert, /// Insert new content.
+ OP_Update, /// Update existing content.
+ OP_Delete /// Delete existing content.
+ };
+
+ /**
+ * constructs an empty object of the parser
+ * \param connection is used for things like wildcard resolution. If 0 parser works in "pure mode"
+ */
+ Parser(Connection *connection);
+ ~Parser();
+
+ /**
+ * clears previous results and runs the parser
+ */
+ bool parse(const QString &statement);
+
+ /**
+ * rests results
+ */
+ void clear();
+
+ /**
+ * \return the resulting operation or OP_Error if failed
+ */
+ OPCode operation() const;
+
+ /**
+ * \return the resulting operation as string.
+ */
+ QString operationString() const;
+
+ /**
+ * \return a pointer to a KexiDBTable on CREATE TABLE
+ * or 0 on any other operation or error. Returned object is owned by you.
+ * You can call this method only once every time after doing parse().
+ * Next time, the call will return 0.
+ */
+ TableSchema *table();
+
+ /**
+ * \return a pointer to KexiDBSelect if 'SELECT ...' was called
+ * or 0 on any other operation or error. Returned object is owned by you.
+ * You can call this method only once every time after doing parse().
+ * Next time, the call will return 0.
+ */
+ QuerySchema *query();
+
+ /**
+ * \return a pointer to the used database connection or 0 if not set
+ * You can call this method only once every time after doing parse().
+ * Next time, the call will return 0.
+ */
+ Connection *db() const;
+
+ /**
+ * \return detailed information about last error.
+ * If no error occurred ParserError isNull()
+ */
+ ParserError error() const;
+
+ /**
+ * \return the statement passed on the last \a parse method-call.
+ */
+ QString statement() const;
+
+ /**
+ * \internal
+ * sets the operation (only parser will need to call this)
+ */
+ void setOperation(OPCode op);
+
+ /**
+ * \internal
+ * creates a new table (only parser will need to call this)
+ */
+ void createTable(const char *t);
+
+ /**
+ * \internal
+ * sets \a query schema object (only parser will need to call this)
+ */
+//todo: other query types
+ void setQuerySchema(QuerySchema *query);
+
+ /**
+ * \internal
+ * \return query schema
+ */
+ QuerySchema *select() const;
+
+ /**
+ * \internal
+ * INTERNAL use only: sets a error
+ */
+ void setError(const ParserError &err);
+
+ /**
+ * \return true if the \param str is an reserved
+ * keyword (see tokens.cpp for a list of reserved
+ * keywords).
+ */
+ bool isReservedKeyword(const char *str);
+
+ protected:
+ void init();
+
+ ParserError m_error; //!< detailed information about last error.
+ ParserPrivate *d; //!< \internal d-pointer class.
+};
+
+}
+
+#endif
+
diff --git a/kexi/kexidb/parser/parser_p.cpp b/kexi/kexidb/parser/parser_p.cpp
new file mode 100644
index 000000000..ca935d605
--- /dev/null
+++ b/kexi/kexidb/parser/parser_p.cpp
@@ -0,0 +1,641 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "parser_p.h"
+#include "sqlparser.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qregexp.h>
+
+#include <assert.h>
+
+using namespace KexiDB;
+
+Parser *parser = 0;
+Field *field = 0;
+//bool requiresTable;
+QPtrList<Field> fieldList;
+int current = 0;
+QString ctoken = "";
+
+//-------------------------------------
+
+ParserPrivate::ParserPrivate()
+ : reservedKeywords(997, 997, false)
+ , initialized(false)
+{
+ clear();
+ table = 0;
+ select = 0;
+ db = 0;
+}
+
+ParserPrivate::~ParserPrivate()
+{
+ delete select;
+ delete table;
+}
+
+void ParserPrivate::clear()
+{
+ operation = Parser::OP_None;
+ error = ParserError();
+}
+
+//-------------------------------------
+
+ParseInfo::ParseInfo(KexiDB::QuerySchema *query)
+ : repeatedTablesAndAliases(997, false)
+ , querySchema(query)
+{
+ repeatedTablesAndAliases.setAutoDelete(true);
+}
+
+ParseInfo::~ParseInfo()
+{
+}
+
+//-------------------------------------
+
+extern int yyparse();
+extern void tokenize(const char *data);
+
+void yyerror(const char *str)
+{
+ KexiDBDbg << "error: " << str << endl;
+ KexiDBDbg << "at character " << current << " near tooken " << ctoken << endl;
+ parser->setOperation(Parser::OP_Error);
+
+ const bool otherError = (qstrnicmp(str, "other error", 11)==0);
+
+ if (parser->error().type().isEmpty()
+ && (str==0 || strlen(str)==0
+ || qstrnicmp(str, "syntax error", 12)==0 || qstrnicmp(str, "parse error", 11)==0)
+ || otherError)
+ {
+ KexiDBDbg << parser->statement() << endl;
+ QString ptrline = "";
+ for(int i=0; i < current; i++)
+ ptrline += " ";
+
+ ptrline += "^";
+
+ KexiDBDbg << ptrline << endl;
+
+ //lexer may add error messages
+ QString lexerErr = parser->error().error();
+
+ QString errtypestr(str);
+ if (lexerErr.isEmpty()) {
+#if 0
+ if (errtypestr.startsWith("parse error, unexpected ")) {
+ //something like "parse error, unexpected IDENTIFIER, expecting ',' or ')'"
+ QString e = errtypestr.mid(24);
+ KexiDBDbg << e <<endl;
+ QString token = "IDENTIFIER";
+ if (e.startsWith(token)) {
+ QRegExp re("'.'");
+ int pos=0;
+ pos = re.search(e, pos);
+ QStringList captured=re.capturedTexts();
+ if (captured.count()>=2) {
+// KexiDBDbg << "**" << captured.at(1) << endl;
+// KexiDBDbg << "**" << captured.at(2) << endl;
+ }
+ }
+
+
+
+// IDENTIFIER, expecting '")) {
+ e = errtypestr.mid(47);
+ KexiDBDbg << e <<endl;
+// ,' or ')'
+// lexerErr i18n("identifier was expected");
+
+ } else
+#endif
+ if (errtypestr.startsWith("parse error, expecting `IDENTIFIER'"))
+ lexerErr = i18n("identifier was expected");
+ }
+
+ if (!otherError) {
+ if (!lexerErr.isEmpty())
+ lexerErr.prepend(": ");
+
+ if (parser->isReservedKeyword(ctoken.latin1()))
+ parser->setError( ParserError(i18n("Syntax Error"),
+ i18n("\"%1\" is a reserved keyword").arg(ctoken)+lexerErr, ctoken, current) );
+ else
+ parser->setError( ParserError(i18n("Syntax Error"),
+ i18n("Syntax Error near \"%1\"").arg(ctoken)+lexerErr, ctoken, current) );
+ }
+ }
+}
+
+void setError(const QString& errName, const QString& errDesc)
+{
+ parser->setError( ParserError(errName, errDesc, ctoken, current) );
+ yyerror(errName.latin1());
+}
+
+void setError(const QString& errDesc)
+{
+ setError("other error", errDesc);
+}
+
+/* this is better than assert() */
+#define IMPL_ERROR(errmsg) setError("Implementation error", errmsg)
+
+bool parseData(Parser *p, const char *data)
+{
+/* todo: make this REENTRANT */
+ parser = p;
+ parser->clear();
+ field = 0;
+ fieldList.clear();
+// requiresTable = false;
+
+ if (!data) {
+ ParserError err(i18n("Error"), i18n("No query specified"), ctoken, current);
+ parser->setError(err);
+ yyerror("");
+ parser = 0;
+ return false;
+ }
+
+ tokenize(data);
+ if (!parser->error().type().isEmpty()) {
+ parser = 0;
+ return false;
+ }
+ yyparse();
+
+ bool ok = true;
+ if(parser->operation() == Parser::OP_Select)
+ {
+ KexiDBDbg << "parseData(): ok" << endl;
+// KexiDBDbg << "parseData(): " << tableDict.count() << " loaded tables" << endl;
+/* TableSchema *ts;
+ for(QDictIterator<TableSchema> it(tableDict); TableSchema *s = tableList.first(); s; s = tableList.next())
+ {
+ KexiDBDbg << " " << s->name() << endl;
+ }*/
+/*removed
+ Field::ListIterator it = parser->select()->fieldsIterator();
+ for(Field *item; (item = it.current()); ++it)
+ {
+ if(tableList.findRef(item->table()) == -1)
+ {
+ ParserError err(i18n("Field List Error"), i18n("Unknown table '%1' in field list").arg(item->table()->name()), ctoken, current);
+ parser->setError(err);
+
+ yyerror("fieldlisterror");
+ ok = false;
+ }
+ }*/
+ //take the dummy table out of the query
+// parser->select()->removeTable(dummy);
+ }
+ else {
+ ok = false;
+ }
+
+// tableDict.clear();
+ parser = 0;
+ return ok;
+}
+
+
+/* Adds \a column to \a querySchema. \a column can be in a form of
+ table.field, tableAlias.field or field
+*/
+bool addColumn( ParseInfo& parseInfo, BaseExpr* columnExpr )
+{
+ if (!columnExpr->validate(parseInfo)) {
+ setError(parseInfo.errMsg, parseInfo.errDescr);
+ return false;
+ }
+
+ VariableExpr *v_e = columnExpr->toVariable();
+ if (columnExpr->exprClass() == KexiDBExpr_Variable && v_e) {
+ //it's a variable:
+ if (v_e->name=="*") {//all tables asterisk
+ if (parseInfo.querySchema->tables()->isEmpty()) {
+ setError(i18n("\"*\" could not be used if no tables are specified"));
+ return false;
+ }
+ parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) );
+ }
+ else if (v_e->tableForQueryAsterisk) {//one-table asterisk
+ parseInfo.querySchema->addAsterisk(
+ new QueryAsterisk(parseInfo.querySchema, v_e->tableForQueryAsterisk) );
+ }
+ else if (v_e->field) {//"table.field" or "field" (bound to a table or not)
+ parseInfo.querySchema->addField(v_e->field, v_e->tablePositionForField);
+ }
+ else {
+ IMPL_ERROR("addColumn(): unknown case!");
+ return false;
+ }
+ return true;
+ }
+
+ //it's complex expression
+ parseInfo.querySchema->addExpression(columnExpr);
+
+#if 0
+ KexiDBDbg << "found variable name: " << varName << endl;
+ int dotPos = varName.find('.');
+ QString tableName, fieldName;
+//TODO: shall we also support db name?
+ if (dotPos>0) {
+ tableName = varName.left(dotPos);
+ fieldName = varName.mid(dotPos+1);
+ }
+ if (tableName.isEmpty()) {//fieldname only
+ fieldName = varName;
+ if (fieldName=="*") {
+ parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) );
+ }
+ else {
+ //find first table that has this field
+ Field *firstField = 0;
+ for (TableSchema::ListIterator it(*parseInfo.querySchema->tables()); it.current(); ++it) {
+ Field *f = it.current()->field(fieldName);
+ if (f) {
+ if (!firstField) {
+ firstField = f;
+ } else if (f->table()!=firstField->table()) {
+ //ambiguous field name
+ setError(i18n("Ambiguous field name"),
+ i18n("Both table \"%1\" and \"%2\" have defined \"%3\" field. "
+ "Use \"<tableName>.%4\" notation to specify table name.")
+ .arg(firstField->table()->name()).arg(f->table()->name())
+ .arg(fieldName).arg(fieldName));
+ return false;
+ }
+ }
+ }
+ if (!firstField) {
+ setError(i18n("Field not found"),
+ i18n("Table containing \"%1\" field not found").arg(fieldName));
+ return false;
+ }
+ //ok
+ parseInfo.querySchema->addField(firstField);
+ }
+ }
+ else {//table.fieldname or tableAlias.fieldname
+ tableName = tableName.lower();
+ TableSchema *ts = parseInfo.querySchema->table( tableName );
+ if (ts) {//table.fieldname
+ //check if "table" is covered by an alias
+ const QValueList<int> tPositions = parseInfo.querySchema->tablePositions(tableName);
+ QValueList<int>::ConstIterator it = tPositions.constBegin();
+ QCString tableAlias;
+ bool covered = true;
+ for (; it!=tPositions.constEnd() && covered; ++it) {
+ tableAlias = parseInfo.querySchema->tableAlias(*it);
+ if (tableAlias.isEmpty() || tableAlias.lower()==tableName.latin1())
+ covered = false; //uncovered
+ KexiDBDbg << " --" << "covered by " << tableAlias << " alias" << endl;
+ }
+ if (covered) {
+ setError(i18n("Could not access the table directly using its name"),
+ i18n("Table \"%1\" is covered by aliases. Instead of \"%2\", "
+ "you can write \"%3\"").arg(tableName)
+ .arg(tableName+"."+fieldName).arg(tableAlias+"."+fieldName.latin1()));
+ return false;
+ }
+ }
+
+ int tablePosition = -1;
+ if (!ts) {//try to find tableAlias
+ tablePosition = parseInfo.querySchema->tablePositionForAlias( tableName.latin1() );
+ if (tablePosition>=0) {
+ ts = parseInfo.querySchema->tables()->at(tablePosition);
+ if (ts) {
+// KexiDBDbg << " --it's a tableAlias.name" << endl;
+ }
+ }
+ }
+
+
+ if (ts) {
+ QValueList<int> *positionsList = repeatedTablesAndAliases[ tableName ];
+ if (!positionsList) {
+ IMPL_ERROR(tableName + "." + fieldName + ", !positionsList ");
+ return false;
+ }
+
+ if (fieldName=="*") {
+ if (positionsList->count()>1) {
+ setError(i18n("Ambiguous \"%1.*\" expression").arg(tableName),
+ i18n("More than one \"%1\" table or alias defined").arg(tableName));
+ return false;
+ }
+ parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema, ts) );
+ }
+ else {
+// KexiDBDbg << " --it's a table.name" << endl;
+ Field *realField = ts->field(fieldName);
+ if (realField) {
+ // check if table or alias is used twice and both have the same column
+ // (so the column is ambiguous)
+ int numberOfTheSameFields = 0;
+ for (QValueList<int>::iterator it = positionsList->begin();
+ it!=positionsList->end();++it)
+ {
+ TableSchema *otherTS = parseInfo.querySchema->tables()->at(*it);
+ if (otherTS->field(fieldName))
+ numberOfTheSameFields++;
+ if (numberOfTheSameFields>1) {
+ setError(i18n("Ambiguous \"%1.%2\" expression").arg(tableName).arg(fieldName),
+ i18n("More than one \"%1\" table or alias defined containing \"%2\" field")
+ .arg(tableName).arg(fieldName));
+ return false;
+ }
+ }
+
+ parseInfo.querySchema->addField(realField, tablePosition);
+ }
+ else {
+ setError(i18n("Field not found"), i18n("Table \"%1\" has no \"%2\" field")
+ .arg(tableName).arg(fieldName));
+ return false;
+ }
+ }
+ }
+ else {
+ tableNotFoundError(tableName);
+ return false;
+ }
+ }
+#endif
+ return true;
+}
+
+//! clean up no longer needed temporary objects
+#define CLEANUP \
+ delete colViews; \
+ delete tablesList; \
+ delete options
+
+QuerySchema* buildSelectQuery(
+ QuerySchema* querySchema, NArgExpr* colViews, NArgExpr* tablesList,
+ SelectOptionsInternal* options )
+{
+ ParseInfo parseInfo(querySchema);
+
+ //-------tables list
+// assert( tablesList ); //&& tablesList->exprClass() == KexiDBExpr_TableList );
+
+ uint columnNum = 0;
+/*TODO: use this later if there are columns that use database fields,
+ e.g. "SELECT 1 from table1 t, table2 t") is ok however. */
+ //used to collect information about first repeated table name or alias:
+// QDict<char> tableNamesAndTableAliases(997, false);
+// QString repeatedTableNameOrTableAlias;
+ if (tablesList) {
+ for (int i=0; i<tablesList->args(); i++, columnNum++) {
+ BaseExpr *e = tablesList->arg(i);
+ VariableExpr* t_e = 0;
+ QCString aliasString;
+ if (e->exprClass() == KexiDBExpr_SpecialBinary) {
+ BinaryExpr* t_with_alias = e->toBinary();
+ assert(t_with_alias);
+ assert(t_with_alias->left()->exprClass() == KexiDBExpr_Variable);
+ assert(t_with_alias->right()->exprClass() == KexiDBExpr_Variable
+ && (t_with_alias->token()==AS || t_with_alias->token()==0));
+ t_e = t_with_alias->left()->toVariable();
+ aliasString = t_with_alias->right()->toVariable()->name.latin1();
+ }
+ else {
+ t_e = e->toVariable();
+ }
+ assert(t_e);
+ QCString tname = t_e->name.latin1();
+ TableSchema *s = parser->db()->tableSchema(tname);
+ if(!s) {
+ setError(//i18n("Field List Error"),
+ i18n("Table \"%1\" does not exist").arg(tname));
+ // yyerror("fieldlisterror");
+ CLEANUP;
+ return 0;
+ }
+ QCString tableOrAliasName;
+ if (!aliasString.isEmpty()) {
+ tableOrAliasName = aliasString;
+// KexiDBDbg << "- add alias for table: " << aliasString << endl;
+ } else {
+ tableOrAliasName = tname;
+ }
+ // 1. collect information about first repeated table name or alias
+ // (potential ambiguity)
+ QValueList<int> *list = parseInfo.repeatedTablesAndAliases[tableOrAliasName];
+ if (list) {
+ //another table/alias with the same name
+ list->append( i );
+// KexiDBDbg << "- another table/alias with name: " << tableOrAliasName << endl;
+ }
+ else {
+ list = new QValueList<int>();
+ list->append( i );
+ parseInfo.repeatedTablesAndAliases.insert( tableOrAliasName, list );
+// KexiDBDbg << "- first table/alias with name: " << tableOrAliasName << endl;
+ }
+ /* if (repeatedTableNameOrTableAlias.isEmpty()) {
+ if (tableNamesAndTableAliases[tname])
+ repeatedTableNameOrTableAlias=tname;
+ else
+ tableNamesAndTableAliases.insert(tname, (const char*)1);
+ }
+ if (!aliasString.isEmpty()) {
+ KexiDBDbg << "- add alias for table: " << aliasString << endl;
+ // querySchema->setTableAlias(columnNum, aliasString);
+ //2. collect information about first repeated table name or alias
+ // (potential ambiguity)
+ if (repeatedTableNameOrTableAlias.isEmpty()) {
+ if (tableNamesAndTableAliases[aliasString])
+ repeatedTableNameOrTableAlias=aliasString;
+ else
+ tableNamesAndTableAliases.insert(aliasString, (const char*)1);
+ }
+ }*/
+// KexiDBDbg << "addTable: " << tname << endl;
+ querySchema->addTable( s, aliasString );
+ }
+ }
+
+ /* set parent table if there's only one */
+// if (parser->select()->tables()->count()==1)
+ if (querySchema->tables()->count()==1)
+ querySchema->setMasterTable(querySchema->tables()->first());
+
+ //-------add fields
+ if (colViews) {
+ BaseExpr *e;
+ columnNum = 0;
+ const uint colCount = colViews->list.count();
+// for (BaseExpr::ListIterator it(colViews->list);(e = it.current()); columnNum++)
+ colViews->list.first();
+ for (; columnNum<colCount; columnNum++) {
+ e = colViews->list.current();
+ bool moveNext = true; //used to avoid ++it when an item is taken from the list
+ BaseExpr *columnExpr = e;
+ VariableExpr* aliasVariable = 0;
+ if (e->exprClass() == KexiDBExpr_SpecialBinary && e->toBinary()
+ && (e->token()==AS || e->token()==0))
+ {
+ //KexiDBExpr_SpecialBinary: with alias
+ columnExpr = e->toBinary()->left();
+ // isFieldWithAlias = true;
+ aliasVariable = e->toBinary()->right()->toVariable();
+ if (!aliasVariable) {
+ setError(i18n("Invalid alias definition for column \"%1\"")
+ .arg(columnExpr->toString())); //ok?
+ CLEANUP;
+ return 0;
+ }
+ }
+
+ const int c = columnExpr->exprClass();
+ const bool isExpressionField =
+ c == KexiDBExpr_Const
+ || c == KexiDBExpr_Unary
+ || c == KexiDBExpr_Arithm
+ || c == KexiDBExpr_Logical
+ || c == KexiDBExpr_Relational
+ || c == KexiDBExpr_Const
+ || c == KexiDBExpr_Function
+ || c == KexiDBExpr_Aggregation;
+
+ if (c == KexiDBExpr_Variable) {
+ //just a variable, do nothing, addColumn() will handle this
+ }
+ else if (isExpressionField) {
+ //expression object will be reused, take, will be owned, do not destroy
+// KexiDBDbg << colViews->list.count() << " " << it.current()->debugString() << endl;
+ colViews->list.take(); //take() doesn't work
+ moveNext = false;
+ }
+ else if (aliasVariable) {
+ //take first (left) argument of the special binary expr, will be owned, do not destroy
+ e->toBinary()->m_larg = 0;
+ }
+ else {
+ setError(i18n("Invalid \"%1\" column definition").arg(e->toString())); //ok?
+ CLEANUP;
+ return 0;
+ }
+
+ if (!addColumn( parseInfo, columnExpr )) {
+ CLEANUP;
+ return 0;
+ }
+
+ if (aliasVariable) {
+// KexiDBDbg << "ALIAS \"" << aliasVariable->name << "\" set for column "
+// << columnNum << endl;
+ querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1());
+ }
+ /* if (e->exprClass() == KexiDBExpr_SpecialBinary && dynamic_cast<BinaryExpr*>(e)
+ && (e->type()==AS || e->type()==0))
+ {
+ //also add alias
+ VariableExpr* aliasVariable =
+ dynamic_cast<VariableExpr*>(dynamic_cast<BinaryExpr*>(e)->right());
+ if (!aliasVariable) {
+ setError(i18n("Invalid column alias definition")); //ok?
+ return 0;
+ }
+ kdDebug() << "ALIAS \"" << aliasVariable->name << "\" set for column "
+ << columnNum << endl;
+ querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1());
+ }*/
+
+ if (moveNext) {
+ colViews->list.next();
+// ++it;
+ }
+ }
+ }
+ //----- SELECT options
+ if (options) {
+ //----- WHERE expr.
+ if (options->whereExpr) {
+ if (!options->whereExpr->validate(parseInfo)) {
+ setError(parseInfo.errMsg, parseInfo.errDescr);
+ CLEANUP;
+ return false;
+ }
+ querySchema->setWhereExpression(options->whereExpr);
+ }
+ //----- ORDER BY
+ if (options->orderByColumns) {
+ OrderByColumnList &orderByColumnList = querySchema->orderByColumnList();
+ OrderByColumnInternal::ListConstIterator it = options->orderByColumns->constEnd();
+ uint count = options->orderByColumns->count();
+ --it;
+ for (;count>0; --it, --count)
+ /*opposite direction due to parser specifics*/
+ {
+ //first, try to find a column name or alias (outside of asterisks)
+ QueryColumnInfo *columnInfo = querySchema->columnInfo( (*it).aliasOrName, false/*outside of asterisks*/ );
+ if (columnInfo) {
+ orderByColumnList.appendColumn( *columnInfo, (*it).ascending );
+ }
+ else {
+ //failed, try to find a field name within all the tables
+ if ((*it).columnNumber != -1) {
+ if (!orderByColumnList.appendColumn( *querySchema,
+ (*it).ascending, (*it).columnNumber-1 ))
+ {
+ setError(i18n("Could not define sorting - no column at position %1")
+ .arg((*it).columnNumber));
+ CLEANUP;
+ return 0;
+ }
+ }
+ else {
+ Field * f = querySchema->findTableField((*it).aliasOrName);
+ if (!f) {
+ setError(i18n("Could not define sorting - "
+ "column name or alias \"%1\" does not exist").arg((*it).aliasOrName));
+ CLEANUP;
+ return 0;
+ }
+ orderByColumnList.appendField( *f, (*it).ascending );
+ }
+ }
+ }
+ }
+ }
+
+// KexiDBDbg << "Select ColViews=" << (colViews ? colViews->debugString() : QString::null)
+// << " Tables=" << (tablesList ? tablesList->debugString() : QString::null) << endl;
+
+ CLEANUP;
+ return querySchema;
+}
+
+#undef CLEANUP
+
diff --git a/kexi/kexidb/parser/parser_p.h b/kexi/kexidb/parser/parser_p.h
new file mode 100644
index 000000000..6a695bc59
--- /dev/null
+++ b/kexi/kexidb/parser/parser_p.h
@@ -0,0 +1,86 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_PARSER_P_H
+#define KEXIDB_PARSER_P_H
+
+#include <qvaluelist.h>
+#include <qdict.h>
+#include <qasciicache.h>
+#include <qstring.h>
+
+#include <kexidb/queryschema.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/connection.h>
+#include <kexidb/expression.h>
+#include "sqltypes.h"
+#include "parser.h"
+
+namespace KexiDB {
+
+//! @internal
+class ParserPrivate
+{
+ public:
+ ParserPrivate();
+ ~ParserPrivate();
+
+ void clear();
+
+ int operation;
+ TableSchema *table;
+ QuerySchema *select;
+ Connection *db;
+ QString statement;
+ ParserError error;
+ QAsciiCache<char> reservedKeywords;
+ bool initialized : 1;
+};
+
+
+/*! Data used on parsing. @internal */
+class ParseInfo
+{
+ public:
+ ParseInfo(QuerySchema *query);
+ ~ParseInfo();
+
+ //! collects positions of tables/aliases with the same names
+ QDict< QValueList<int> > repeatedTablesAndAliases;
+
+ QString errMsg, errDescr; //helpers
+ QuerySchema *querySchema;
+};
+
+}
+
+void yyerror(const char *str);
+void setError(const QString& errName, const QString& errDesc);
+void setError(const QString& errDesc);
+//bool parseData(KexiDB::Parser *p, const char *data);
+bool addColumn( KexiDB::ParseInfo& parseInfo, KexiDB::BaseExpr* columnExpr );
+KexiDB::QuerySchema* buildSelectQuery(
+ KexiDB::QuerySchema* querySchema, KexiDB::NArgExpr* colViews,
+ KexiDB::NArgExpr* tablesList = 0, SelectOptionsInternal* options = 0 ); //KexiDB::BaseExpr* whereExpr = 0 );
+
+extern KexiDB::Parser *parser;
+extern KexiDB::Field *field;
+
+
+#endif
diff --git a/kexi/kexidb/parser/sqlparser.cpp b/kexi/kexidb/parser/sqlparser.cpp
new file mode 100644
index 000000000..682b8aa02
--- /dev/null
+++ b/kexi/kexidb/parser/sqlparser.cpp
@@ -0,0 +1,3472 @@
+/* A Bison parser, made by GNU Bison 2.2. */
+
+/* Skeleton implementation for Bison's Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT 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, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output. */
+#define YYBISON 1
+
+/* Bison version. */
+#define YYBISON_VERSION "2.2"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Using locations. */
+#define YYLSP_NEEDED 0
+
+
+
+/* Tokens. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ /* Put the tokens into the symbol table, so that GDB and other debuggers
+ know about them. */
+ enum yytokentype {
+ UMINUS = 258,
+ SQL_TYPE = 259,
+ SQL_ABS = 260,
+ ACOS = 261,
+ AMPERSAND = 262,
+ SQL_ABSOLUTE = 263,
+ ADA = 264,
+ ADD = 265,
+ ADD_DAYS = 266,
+ ADD_HOURS = 267,
+ ADD_MINUTES = 268,
+ ADD_MONTHS = 269,
+ ADD_SECONDS = 270,
+ ADD_YEARS = 271,
+ ALL = 272,
+ ALLOCATE = 273,
+ ALTER = 274,
+ AND = 275,
+ ANY = 276,
+ ARE = 277,
+ AS = 278,
+ ASIN = 279,
+ ASC = 280,
+ ASCII = 281,
+ ASSERTION = 282,
+ ATAN = 283,
+ ATAN2 = 284,
+ AUTHORIZATION = 285,
+ AUTO_INCREMENT = 286,
+ AVG = 287,
+ BEFORE = 288,
+ SQL_BEGIN = 289,
+ BETWEEN = 290,
+ BIGINT = 291,
+ BINARY = 292,
+ BIT = 293,
+ BIT_LENGTH = 294,
+ BITWISE_SHIFT_LEFT = 295,
+ BITWISE_SHIFT_RIGHT = 296,
+ BREAK = 297,
+ BY = 298,
+ CASCADE = 299,
+ CASCADED = 300,
+ CASE = 301,
+ CAST = 302,
+ CATALOG = 303,
+ CEILING = 304,
+ CENTER = 305,
+ SQL_CHAR = 306,
+ CHAR_LENGTH = 307,
+ CHARACTER_STRING_LITERAL = 308,
+ CHECK = 309,
+ CLOSE = 310,
+ COALESCE = 311,
+ COBOL = 312,
+ COLLATE = 313,
+ COLLATION = 314,
+ COLUMN = 315,
+ COMMIT = 316,
+ COMPUTE = 317,
+ CONCAT = 318,
+ CONCATENATION = 319,
+ CONNECT = 320,
+ CONNECTION = 321,
+ CONSTRAINT = 322,
+ CONSTRAINTS = 323,
+ CONTINUE = 324,
+ CONVERT = 325,
+ CORRESPONDING = 326,
+ COS = 327,
+ COT = 328,
+ COUNT = 329,
+ CREATE = 330,
+ CURDATE = 331,
+ CURRENT = 332,
+ CURRENT_DATE = 333,
+ CURRENT_TIME = 334,
+ CURRENT_TIMESTAMP = 335,
+ CURTIME = 336,
+ CURSOR = 337,
+ DATABASE = 338,
+ SQL_DATE = 339,
+ DATE_FORMAT = 340,
+ DATE_REMAINDER = 341,
+ DATE_VALUE = 342,
+ DAY = 343,
+ DAYOFMONTH = 344,
+ DAYOFWEEK = 345,
+ DAYOFYEAR = 346,
+ DAYS_BETWEEN = 347,
+ DEALLOCATE = 348,
+ DEC = 349,
+ DECLARE = 350,
+ DEFAULT = 351,
+ DEFERRABLE = 352,
+ DEFERRED = 353,
+ SQL_DELETE = 354,
+ DESC = 355,
+ DESCRIBE = 356,
+ DESCRIPTOR = 357,
+ DIAGNOSTICS = 358,
+ DICTIONARY = 359,
+ DIRECTORY = 360,
+ DISCONNECT = 361,
+ DISPLACEMENT = 362,
+ DISTINCT = 363,
+ DOMAIN_TOKEN = 364,
+ SQL_DOUBLE = 365,
+ DOUBLE_QUOTED_STRING = 366,
+ DROP = 367,
+ ELSE = 368,
+ END = 369,
+ END_EXEC = 370,
+ EQUAL = 371,
+ ESCAPE = 372,
+ EXCEPT = 373,
+ SQL_EXCEPTION = 374,
+ EXEC = 375,
+ EXECUTE = 376,
+ EXISTS = 377,
+ EXP = 378,
+ EXPONENT = 379,
+ EXTERNAL = 380,
+ EXTRACT = 381,
+ SQL_FALSE = 382,
+ FETCH = 383,
+ FIRST = 384,
+ SQL_FLOAT = 385,
+ FLOOR = 386,
+ FN = 387,
+ FOR = 388,
+ FOREIGN = 389,
+ FORTRAN = 390,
+ FOUND = 391,
+ FOUR_DIGITS = 392,
+ FROM = 393,
+ FULL = 394,
+ GET = 395,
+ GLOBAL = 396,
+ GO = 397,
+ GOTO = 398,
+ GRANT = 399,
+ GREATER_OR_EQUAL = 400,
+ HAVING = 401,
+ HOUR = 402,
+ HOURS_BETWEEN = 403,
+ IDENTITY = 404,
+ IFNULL = 405,
+ SQL_IGNORE = 406,
+ IMMEDIATE = 407,
+ SQL_IN = 408,
+ INCLUDE = 409,
+ INDEX = 410,
+ INDICATOR = 411,
+ INITIALLY = 412,
+ INNER = 413,
+ INPUT = 414,
+ INSENSITIVE = 415,
+ INSERT = 416,
+ INTEGER = 417,
+ INTERSECT = 418,
+ INTERVAL = 419,
+ INTO = 420,
+ IS = 421,
+ ISOLATION = 422,
+ JOIN = 423,
+ JUSTIFY = 424,
+ KEY = 425,
+ LANGUAGE = 426,
+ LAST = 427,
+ LCASE = 428,
+ LEFT = 429,
+ LENGTH = 430,
+ LESS_OR_EQUAL = 431,
+ LEVEL = 432,
+ LIKE = 433,
+ LINE_WIDTH = 434,
+ LOCAL = 435,
+ LOCATE = 436,
+ LOG = 437,
+ SQL_LONG = 438,
+ LOWER = 439,
+ LTRIM = 440,
+ LTRIP = 441,
+ MATCH = 442,
+ SQL_MAX = 443,
+ MICROSOFT = 444,
+ SQL_MIN = 445,
+ MINUS = 446,
+ MINUTE = 447,
+ MINUTES_BETWEEN = 448,
+ MOD = 449,
+ MODIFY = 450,
+ MODULE = 451,
+ MONTH = 452,
+ MONTHS_BETWEEN = 453,
+ MUMPS = 454,
+ NAMES = 455,
+ NATIONAL = 456,
+ NCHAR = 457,
+ NEXT = 458,
+ NODUP = 459,
+ NONE = 460,
+ NOT = 461,
+ NOT_EQUAL = 462,
+ NOT_EQUAL2 = 463,
+ NOW = 464,
+ SQL_NULL = 465,
+ SQL_IS = 466,
+ SQL_IS_NULL = 467,
+ SQL_IS_NOT_NULL = 468,
+ NULLIF = 469,
+ NUMERIC = 470,
+ OCTET_LENGTH = 471,
+ ODBC = 472,
+ OF = 473,
+ SQL_OFF = 474,
+ SQL_ON = 475,
+ ONLY = 476,
+ OPEN = 477,
+ OPTION = 478,
+ OR = 479,
+ ORDER = 480,
+ OUTER = 481,
+ OUTPUT = 482,
+ OVERLAPS = 483,
+ PAGE = 484,
+ PARTIAL = 485,
+ SQL_PASCAL = 486,
+ PERSISTENT = 487,
+ CQL_PI = 488,
+ PLI = 489,
+ POSITION = 490,
+ PRECISION = 491,
+ PREPARE = 492,
+ PRESERVE = 493,
+ PRIMARY = 494,
+ PRIOR = 495,
+ PRIVILEGES = 496,
+ PROCEDURE = 497,
+ PRODUCT = 498,
+ PUBLIC = 499,
+ QUARTER = 500,
+ QUIT = 501,
+ RAND = 502,
+ READ_ONLY = 503,
+ REAL = 504,
+ REFERENCES = 505,
+ REPEAT = 506,
+ REPLACE = 507,
+ RESTRICT = 508,
+ REVOKE = 509,
+ RIGHT = 510,
+ ROLLBACK = 511,
+ ROWS = 512,
+ RPAD = 513,
+ RTRIM = 514,
+ SCHEMA = 515,
+ SCREEN_WIDTH = 516,
+ SCROLL = 517,
+ SECOND = 518,
+ SECONDS_BETWEEN = 519,
+ SELECT = 520,
+ SEQUENCE = 521,
+ SETOPT = 522,
+ SET = 523,
+ SHOWOPT = 524,
+ SIGN = 525,
+ SIMILAR_TO = 526,
+ NOT_SIMILAR_TO = 527,
+ INTEGER_CONST = 528,
+ REAL_CONST = 529,
+ DATE_CONST = 530,
+ DATETIME_CONST = 531,
+ TIME_CONST = 532,
+ SIN = 533,
+ SQL_SIZE = 534,
+ SMALLINT = 535,
+ SOME = 536,
+ SPACE = 537,
+ SQL = 538,
+ SQL_TRUE = 539,
+ SQLCA = 540,
+ SQLCODE = 541,
+ SQLERROR = 542,
+ SQLSTATE = 543,
+ SQLWARNING = 544,
+ SQRT = 545,
+ STDEV = 546,
+ SUBSTRING = 547,
+ SUM = 548,
+ SYSDATE = 549,
+ SYSDATE_FORMAT = 550,
+ SYSTEM = 551,
+ TABLE = 552,
+ TAN = 553,
+ TEMPORARY = 554,
+ THEN = 555,
+ THREE_DIGITS = 556,
+ TIME = 557,
+ TIMESTAMP = 558,
+ TIMEZONE_HOUR = 559,
+ TIMEZONE_MINUTE = 560,
+ TINYINT = 561,
+ TO = 562,
+ TO_CHAR = 563,
+ TO_DATE = 564,
+ TRANSACTION = 565,
+ TRANSLATE = 566,
+ TRANSLATION = 567,
+ TRUNCATE = 568,
+ GENERAL_TITLE = 569,
+ TWO_DIGITS = 570,
+ UCASE = 571,
+ UNION = 572,
+ UNIQUE = 573,
+ SQL_UNKNOWN = 574,
+ UPDATE = 575,
+ UPPER = 576,
+ USAGE = 577,
+ USER = 578,
+ IDENTIFIER = 579,
+ IDENTIFIER_DOT_ASTERISK = 580,
+ QUERY_PARAMETER = 581,
+ USING = 582,
+ VALUE = 583,
+ VALUES = 584,
+ VARBINARY = 585,
+ VARCHAR = 586,
+ VARYING = 587,
+ VENDOR = 588,
+ VIEW = 589,
+ WEEK = 590,
+ WHEN = 591,
+ WHENEVER = 592,
+ WHERE = 593,
+ WHERE_CURRENT_OF = 594,
+ WITH = 595,
+ WORD_WRAPPED = 596,
+ WORK = 597,
+ WRAPPED = 598,
+ XOR = 599,
+ YEAR = 600,
+ YEARS_BETWEEN = 601,
+ SCAN_ERROR = 602,
+ __LAST_TOKEN = 603,
+ ILIKE = 604
+ };
+#endif
+/* Tokens. */
+#define UMINUS 258
+#define SQL_TYPE 259
+#define SQL_ABS 260
+#define ACOS 261
+#define AMPERSAND 262
+#define SQL_ABSOLUTE 263
+#define ADA 264
+#define ADD 265
+#define ADD_DAYS 266
+#define ADD_HOURS 267
+#define ADD_MINUTES 268
+#define ADD_MONTHS 269
+#define ADD_SECONDS 270
+#define ADD_YEARS 271
+#define ALL 272
+#define ALLOCATE 273
+#define ALTER 274
+#define AND 275
+#define ANY 276
+#define ARE 277
+#define AS 278
+#define ASIN 279
+#define ASC 280
+#define ASCII 281
+#define ASSERTION 282
+#define ATAN 283
+#define ATAN2 284
+#define AUTHORIZATION 285
+#define AUTO_INCREMENT 286
+#define AVG 287
+#define BEFORE 288
+#define SQL_BEGIN 289
+#define BETWEEN 290
+#define BIGINT 291
+#define BINARY 292
+#define BIT 293
+#define BIT_LENGTH 294
+#define BITWISE_SHIFT_LEFT 295
+#define BITWISE_SHIFT_RIGHT 296
+#define BREAK 297
+#define BY 298
+#define CASCADE 299
+#define CASCADED 300
+#define CASE 301
+#define CAST 302
+#define CATALOG 303
+#define CEILING 304
+#define CENTER 305
+#define SQL_CHAR 306
+#define CHAR_LENGTH 307
+#define CHARACTER_STRING_LITERAL 308
+#define CHECK 309
+#define CLOSE 310
+#define COALESCE 311
+#define COBOL 312
+#define COLLATE 313
+#define COLLATION 314
+#define COLUMN 315
+#define COMMIT 316
+#define COMPUTE 317
+#define CONCAT 318
+#define CONCATENATION 319
+#define CONNECT 320
+#define CONNECTION 321
+#define CONSTRAINT 322
+#define CONSTRAINTS 323
+#define CONTINUE 324
+#define CONVERT 325
+#define CORRESPONDING 326
+#define COS 327
+#define COT 328
+#define COUNT 329
+#define CREATE 330
+#define CURDATE 331
+#define CURRENT 332
+#define CURRENT_DATE 333
+#define CURRENT_TIME 334
+#define CURRENT_TIMESTAMP 335
+#define CURTIME 336
+#define CURSOR 337
+#define DATABASE 338
+#define SQL_DATE 339
+#define DATE_FORMAT 340
+#define DATE_REMAINDER 341
+#define DATE_VALUE 342
+#define DAY 343
+#define DAYOFMONTH 344
+#define DAYOFWEEK 345
+#define DAYOFYEAR 346
+#define DAYS_BETWEEN 347
+#define DEALLOCATE 348
+#define DEC 349
+#define DECLARE 350
+#define DEFAULT 351
+#define DEFERRABLE 352
+#define DEFERRED 353
+#define SQL_DELETE 354
+#define DESC 355
+#define DESCRIBE 356
+#define DESCRIPTOR 357
+#define DIAGNOSTICS 358
+#define DICTIONARY 359
+#define DIRECTORY 360
+#define DISCONNECT 361
+#define DISPLACEMENT 362
+#define DISTINCT 363
+#define DOMAIN_TOKEN 364
+#define SQL_DOUBLE 365
+#define DOUBLE_QUOTED_STRING 366
+#define DROP 367
+#define ELSE 368
+#define END 369
+#define END_EXEC 370
+#define EQUAL 371
+#define ESCAPE 372
+#define EXCEPT 373
+#define SQL_EXCEPTION 374
+#define EXEC 375
+#define EXECUTE 376
+#define EXISTS 377
+#define EXP 378
+#define EXPONENT 379
+#define EXTERNAL 380
+#define EXTRACT 381
+#define SQL_FALSE 382
+#define FETCH 383
+#define FIRST 384
+#define SQL_FLOAT 385
+#define FLOOR 386
+#define FN 387
+#define FOR 388
+#define FOREIGN 389
+#define FORTRAN 390
+#define FOUND 391
+#define FOUR_DIGITS 392
+#define FROM 393
+#define FULL 394
+#define GET 395
+#define GLOBAL 396
+#define GO 397
+#define GOTO 398
+#define GRANT 399
+#define GREATER_OR_EQUAL 400
+#define HAVING 401
+#define HOUR 402
+#define HOURS_BETWEEN 403
+#define IDENTITY 404
+#define IFNULL 405
+#define SQL_IGNORE 406
+#define IMMEDIATE 407
+#define SQL_IN 408
+#define INCLUDE 409
+#define INDEX 410
+#define INDICATOR 411
+#define INITIALLY 412
+#define INNER 413
+#define INPUT 414
+#define INSENSITIVE 415
+#define INSERT 416
+#define INTEGER 417
+#define INTERSECT 418
+#define INTERVAL 419
+#define INTO 420
+#define IS 421
+#define ISOLATION 422
+#define JOIN 423
+#define JUSTIFY 424
+#define KEY 425
+#define LANGUAGE 426
+#define LAST 427
+#define LCASE 428
+#define LEFT 429
+#define LENGTH 430
+#define LESS_OR_EQUAL 431
+#define LEVEL 432
+#define LIKE 433
+#define LINE_WIDTH 434
+#define LOCAL 435
+#define LOCATE 436
+#define LOG 437
+#define SQL_LONG 438
+#define LOWER 439
+#define LTRIM 440
+#define LTRIP 441
+#define MATCH 442
+#define SQL_MAX 443
+#define MICROSOFT 444
+#define SQL_MIN 445
+#define MINUS 446
+#define MINUTE 447
+#define MINUTES_BETWEEN 448
+#define MOD 449
+#define MODIFY 450
+#define MODULE 451
+#define MONTH 452
+#define MONTHS_BETWEEN 453
+#define MUMPS 454
+#define NAMES 455
+#define NATIONAL 456
+#define NCHAR 457
+#define NEXT 458
+#define NODUP 459
+#define NONE 460
+#define NOT 461
+#define NOT_EQUAL 462
+#define NOT_EQUAL2 463
+#define NOW 464
+#define SQL_NULL 465
+#define SQL_IS 466
+#define SQL_IS_NULL 467
+#define SQL_IS_NOT_NULL 468
+#define NULLIF 469
+#define NUMERIC 470
+#define OCTET_LENGTH 471
+#define ODBC 472
+#define OF 473
+#define SQL_OFF 474
+#define SQL_ON 475
+#define ONLY 476
+#define OPEN 477
+#define OPTION 478
+#define OR 479
+#define ORDER 480
+#define OUTER 481
+#define OUTPUT 482
+#define OVERLAPS 483
+#define PAGE 484
+#define PARTIAL 485
+#define SQL_PASCAL 486
+#define PERSISTENT 487
+#define CQL_PI 488
+#define PLI 489
+#define POSITION 490
+#define PRECISION 491
+#define PREPARE 492
+#define PRESERVE 493
+#define PRIMARY 494
+#define PRIOR 495
+#define PRIVILEGES 496
+#define PROCEDURE 497
+#define PRODUCT 498
+#define PUBLIC 499
+#define QUARTER 500
+#define QUIT 501
+#define RAND 502
+#define READ_ONLY 503
+#define REAL 504
+#define REFERENCES 505
+#define REPEAT 506
+#define REPLACE 507
+#define RESTRICT 508
+#define REVOKE 509
+#define RIGHT 510
+#define ROLLBACK 511
+#define ROWS 512
+#define RPAD 513
+#define RTRIM 514
+#define SCHEMA 515
+#define SCREEN_WIDTH 516
+#define SCROLL 517
+#define SECOND 518
+#define SECONDS_BETWEEN 519
+#define SELECT 520
+#define SEQUENCE 521
+#define SETOPT 522
+#define SET 523
+#define SHOWOPT 524
+#define SIGN 525
+#define SIMILAR_TO 526
+#define NOT_SIMILAR_TO 527
+#define INTEGER_CONST 528
+#define REAL_CONST 529
+#define DATE_CONST 530
+#define DATETIME_CONST 531
+#define TIME_CONST 532
+#define SIN 533
+#define SQL_SIZE 534
+#define SMALLINT 535
+#define SOME 536
+#define SPACE 537
+#define SQL 538
+#define SQL_TRUE 539
+#define SQLCA 540
+#define SQLCODE 541
+#define SQLERROR 542
+#define SQLSTATE 543
+#define SQLWARNING 544
+#define SQRT 545
+#define STDEV 546
+#define SUBSTRING 547
+#define SUM 548
+#define SYSDATE 549
+#define SYSDATE_FORMAT 550
+#define SYSTEM 551
+#define TABLE 552
+#define TAN 553
+#define TEMPORARY 554
+#define THEN 555
+#define THREE_DIGITS 556
+#define TIME 557
+#define TIMESTAMP 558
+#define TIMEZONE_HOUR 559
+#define TIMEZONE_MINUTE 560
+#define TINYINT 561
+#define TO 562
+#define TO_CHAR 563
+#define TO_DATE 564
+#define TRANSACTION 565
+#define TRANSLATE 566
+#define TRANSLATION 567
+#define TRUNCATE 568
+#define GENERAL_TITLE 569
+#define TWO_DIGITS 570
+#define UCASE 571
+#define UNION 572
+#define UNIQUE 573
+#define SQL_UNKNOWN 574
+#define UPDATE 575
+#define UPPER 576
+#define USAGE 577
+#define USER 578
+#define IDENTIFIER 579
+#define IDENTIFIER_DOT_ASTERISK 580
+#define QUERY_PARAMETER 581
+#define USING 582
+#define VALUE 583
+#define VALUES 584
+#define VARBINARY 585
+#define VARCHAR 586
+#define VARYING 587
+#define VENDOR 588
+#define VIEW 589
+#define WEEK 590
+#define WHEN 591
+#define WHENEVER 592
+#define WHERE 593
+#define WHERE_CURRENT_OF 594
+#define WITH 595
+#define WORD_WRAPPED 596
+#define WORK 597
+#define WRAPPED 598
+#define XOR 599
+#define YEAR 600
+#define YEARS_BETWEEN 601
+#define SCAN_ERROR 602
+#define __LAST_TOKEN 603
+#define ILIKE 604
+
+
+
+
+/* Copy the first part of user declarations. */
+#line 438 "sqlparser.y"
+
+#ifndef YYDEBUG /* compat. */
+# define YYDEBUG 0
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <iostream>
+#include <assert.h>
+#include <limits.h>
+//TODO OK?
+#ifdef Q_WS_WIN
+//workaround for bug on msvc
+# undef LLONG_MIN
+#endif
+#ifndef LLONG_MAX
+# define LLONG_MAX 0x7fffffffffffffffLL
+#endif
+#ifndef LLONG_MIN
+# define LLONG_MIN 0x8000000000000000LL
+#endif
+#ifndef LLONG_MAX
+# define ULLONG_MAX 0xffffffffffffffffLL
+#endif
+
+#ifdef _WIN32
+# include <malloc.h>
+#endif
+
+#include <qobject.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <qptrlist.h>
+#include <qcstring.h>
+#include <qvariant.h>
+
+#include <connection.h>
+#include <queryschema.h>
+#include <field.h>
+#include <tableschema.h>
+
+#include "parser.h"
+#include "parser_p.h"
+#include "sqltypes.h"
+
+int yylex();
+
+// using namespace std;
+using namespace KexiDB;
+
+#define YY_NO_UNPUT
+#define YYSTACK_USE_ALLOCA 1
+#define YYMAXDEPTH 255
+
+ extern "C"
+ {
+ int yywrap()
+ {
+ return 1;
+ }
+ }
+
+#if 0
+ struct yyval
+ {
+ QString parserUserName;
+ int integerValue;
+ KexiDBField::ColumnType coltype;
+ }
+#endif
+
+
+
+/* Enabling traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+
+/* Enabling verbose error messages. */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+/* Enabling the token table. */
+#ifndef YYTOKEN_TABLE
+# define YYTOKEN_TABLE 0
+#endif
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+#line 511 "sqlparser.y"
+{
+ QString* stringValue;
+ Q_LLONG integerValue;
+ bool booleanValue;
+ struct realType realValue;
+ KexiDB::Field::Type colType;
+ KexiDB::Field *field;
+ KexiDB::BaseExpr *expr;
+ KexiDB::NArgExpr *exprList;
+ KexiDB::ConstExpr *constExpr;
+ KexiDB::QuerySchema *querySchema;
+ SelectOptionsInternal *selectOptions;
+ OrderByColumnInternal::List *orderByColumns;
+ QVariant *variantValue;
+}
+/* Line 193 of yacc.c. */
+#line 883 "sqlparser.tab.c"
+ YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+
+
+/* Copy the second part of user declarations. */
+
+
+/* Line 216 of yacc.c. */
+#line 896 "sqlparser.tab.c"
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#elif (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+typedef signed char yytype_int8;
+#else
+typedef short int yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(msgid) dgettext ("bison-runtime", msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(msgid) msgid
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(e) ((void) (e))
+#else
+# define YYUSE(e) /* empty */
+#endif
+
+/* Identity function, used to suppress warnings about constant conditions. */
+#ifndef lint
+# define YYID(n) (n)
+#else
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+static int
+YYID (int i)
+#else
+static int
+YYID (i)
+ int i;
+#endif
+{
+ return i;
+}
+#endif
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef _STDLIB_H
+# define _STDLIB_H 1
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's `empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0))
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined _STDLIB_H \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef _STDLIB_H
+# define _STDLIB_H 1
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yytype_int16 yyss;
+ YYSTYPE yyvs;
+ };
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+/* Copy COUNT objects from FROM to TO. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(To, From, Count) \
+ __builtin_memcpy (To, From, (Count) * sizeof (*(From)))
+# else
+# define YYCOPY(To, From, Count) \
+ do \
+ { \
+ YYSIZE_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (To)[yyi] = (From)[yyi]; \
+ } \
+ while (YYID (0))
+# endif
+# endif
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack) \
+ do \
+ { \
+ YYSIZE_T yynewbytes; \
+ YYCOPY (&yyptr->Stack, Stack, yysize); \
+ Stack = &yyptr->Stack; \
+ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / sizeof (*yyptr); \
+ } \
+ while (YYID (0))
+
+#endif
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 10
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 335
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 373
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 37
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 108
+/* YYNRULES -- Number of states. */
+#define YYNSTATES 176
+
+/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */
+#define YYUNDEFTOK 2
+#define YYMAXUTOK 604
+
+#define YYTRANSLATE(YYX) \
+ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */
+static const yytype_uint16 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 357, 352, 370, 361,
+ 358, 359, 351, 350, 355, 349, 356, 362, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 354,
+ 364, 363, 365, 360, 353, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 368, 2, 369, 367, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 371, 2, 372, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 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, 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, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 267, 268, 269, 270, 271, 272, 273, 274,
+ 275, 276, 277, 278, 279, 280, 281, 282, 283, 284,
+ 285, 286, 287, 288, 289, 290, 291, 292, 293, 294,
+ 295, 296, 297, 298, 299, 300, 301, 302, 303, 304,
+ 305, 306, 307, 308, 309, 310, 311, 312, 313, 314,
+ 315, 316, 317, 318, 319, 320, 321, 322, 323, 324,
+ 325, 326, 327, 328, 329, 330, 331, 332, 333, 334,
+ 335, 336, 337, 338, 339, 340, 341, 342, 343, 344,
+ 345, 346, 347, 348, 366
+};
+
+#if YYDEBUG
+/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in
+ YYRHS. */
+static const yytype_uint16 yyprhs[] =
+{
+ 0, 0, 3, 5, 9, 11, 14, 16, 18, 19,
+ 27, 31, 33, 36, 40, 43, 45, 48, 51, 53,
+ 55, 60, 65, 66, 69, 73, 76, 80, 85, 87,
+ 89, 93, 98, 103, 106, 108, 111, 115, 120, 122,
+ 126, 128, 130, 132, 134, 138, 142, 146, 148, 152,
+ 156, 160, 164, 168, 170, 174, 178, 182, 186, 190,
+ 194, 196, 199, 202, 204, 208, 212, 214, 218, 222,
+ 226, 230, 232, 236, 240, 244, 246, 249, 252, 255,
+ 258, 260, 262, 265, 269, 271, 273, 275, 277, 279,
+ 283, 287, 291, 295, 298, 302, 304, 306, 309, 313,
+ 317, 319, 321, 323, 327, 330, 332, 337, 339
+};
+
+/* YYRHS -- A `-1'-separated list of the rules' RHS. */
+static const yytype_int16 yyrhs[] =
+{
+ 374, 0, -1, 375, -1, 376, 354, 375, -1, 376,
+ -1, 376, 354, -1, 377, -1, 384, -1, -1, 75,
+ 297, 324, 378, 358, 379, 359, -1, 379, 355, 380,
+ -1, 380, -1, 324, 383, -1, 324, 383, 381, -1,
+ 381, 382, -1, 382, -1, 239, 170, -1, 206, 210,
+ -1, 31, -1, 4, -1, 4, 358, 273, 359, -1,
+ 331, 358, 273, 359, -1, -1, 385, 406, -1, 385,
+ 406, 403, -1, 385, 403, -1, 385, 406, 386, -1,
+ 385, 406, 403, 386, -1, 265, -1, 387, -1, 225,
+ 43, 388, -1, 387, 225, 43, 388, -1, 225, 43,
+ 388, 387, -1, 338, 391, -1, 389, -1, 389, 390,
+ -1, 389, 355, 388, -1, 389, 390, 355, 388, -1,
+ 324, -1, 324, 356, 324, -1, 273, -1, 25, -1,
+ 100, -1, 392, -1, 393, 20, 392, -1, 393, 224,
+ 392, -1, 393, 344, 392, -1, 393, -1, 394, 365,
+ 393, -1, 394, 145, 393, -1, 394, 364, 393, -1,
+ 394, 176, 393, -1, 394, 363, 393, -1, 394, -1,
+ 395, 207, 394, -1, 395, 208, 394, -1, 395, 178,
+ 394, -1, 395, 153, 394, -1, 395, 271, 394, -1,
+ 395, 272, 394, -1, 395, -1, 395, 212, -1, 395,
+ 213, -1, 396, -1, 397, 40, 396, -1, 397, 41,
+ 396, -1, 397, -1, 398, 350, 397, -1, 398, 349,
+ 397, -1, 398, 370, 397, -1, 398, 371, 397, -1,
+ 398, -1, 399, 362, 398, -1, 399, 351, 398, -1,
+ 399, 352, 398, -1, 399, -1, 349, 399, -1, 350,
+ 399, -1, 372, 399, -1, 206, 399, -1, 324, -1,
+ 326, -1, 324, 401, -1, 324, 356, 324, -1, 210,
+ -1, 53, -1, 273, -1, 274, -1, 400, -1, 358,
+ 391, 359, -1, 358, 402, 359, -1, 391, 355, 402,
+ -1, 391, 355, 391, -1, 138, 404, -1, 404, 355,
+ 405, -1, 405, -1, 324, -1, 324, 324, -1, 324,
+ 23, 324, -1, 406, 355, 407, -1, 407, -1, 408,
+ -1, 409, -1, 408, 23, 324, -1, 408, 324, -1,
+ 391, -1, 108, 358, 408, 359, -1, 351, -1, 324,
+ 356, 351, -1
+};
+
+/* YYRLINE[YYN] -- source line where rule number YYN was defined. */
+static const yytype_uint16 yyrline[] =
+{
+ 0, 580, 580, 590, 594, 595, 605, 609, 617, 616,
+ 626, 626, 632, 640, 656, 656, 662, 667, 672, 680,
+ 685, 692, 699, 707, 714, 719, 725, 731, 740, 750,
+ 756, 762, 769, 779, 788, 797, 807, 815, 827, 833,
+ 840, 847, 851, 858, 863, 868, 872, 877, 882, 886,
+ 890, 894, 898, 903, 908, 913, 917, 921, 925, 929,
+ 934, 939, 943, 948, 953, 957, 962, 967, 972, 976,
+ 980, 985, 990, 994, 998, 1003, 1009, 1013, 1017, 1021,
+ 1025, 1033, 1039, 1046, 1053, 1060, 1066, 1083, 1089, 1094,
+ 1102, 1112, 1117, 1126, 1171, 1176, 1184, 1212, 1223, 1239,
+ 1245, 1254, 1263, 1268, 1277, 1289, 1333, 1342, 1351
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "$end", "error", "$undefined", "UMINUS", "SQL_TYPE", "SQL_ABS", "ACOS",
+ "AMPERSAND", "SQL_ABSOLUTE", "ADA", "ADD", "ADD_DAYS", "ADD_HOURS",
+ "ADD_MINUTES", "ADD_MONTHS", "ADD_SECONDS", "ADD_YEARS", "ALL",
+ "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "AS", "ASIN", "ASC", "ASCII",
+ "ASSERTION", "ATAN", "ATAN2", "AUTHORIZATION", "AUTO_INCREMENT", "AVG",
+ "BEFORE", "SQL_BEGIN", "BETWEEN", "BIGINT", "BINARY", "BIT",
+ "BIT_LENGTH", "BITWISE_SHIFT_LEFT", "BITWISE_SHIFT_RIGHT", "BREAK", "BY",
+ "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CEILING", "CENTER",
+ "SQL_CHAR", "CHAR_LENGTH", "CHARACTER_STRING_LITERAL", "CHECK", "CLOSE",
+ "COALESCE", "COBOL", "COLLATE", "COLLATION", "COLUMN", "COMMIT",
+ "COMPUTE", "CONCAT", "CONCATENATION", "CONNECT", "CONNECTION",
+ "CONSTRAINT", "CONSTRAINTS", "CONTINUE", "CONVERT", "CORRESPONDING",
+ "COS", "COT", "COUNT", "CREATE", "CURDATE", "CURRENT", "CURRENT_DATE",
+ "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURTIME", "CURSOR", "DATABASE",
+ "SQL_DATE", "DATE_FORMAT", "DATE_REMAINDER", "DATE_VALUE", "DAY",
+ "DAYOFMONTH", "DAYOFWEEK", "DAYOFYEAR", "DAYS_BETWEEN", "DEALLOCATE",
+ "DEC", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "SQL_DELETE",
+ "DESC", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS", "DICTIONARY",
+ "DIRECTORY", "DISCONNECT", "DISPLACEMENT", "DISTINCT", "DOMAIN_TOKEN",
+ "SQL_DOUBLE", "DOUBLE_QUOTED_STRING", "DROP", "ELSE", "END", "END_EXEC",
+ "EQUAL", "ESCAPE", "EXCEPT", "SQL_EXCEPTION", "EXEC", "EXECUTE",
+ "EXISTS", "EXP", "EXPONENT", "EXTERNAL", "EXTRACT", "SQL_FALSE", "FETCH",
+ "FIRST", "SQL_FLOAT", "FLOOR", "FN", "FOR", "FOREIGN", "FORTRAN",
+ "FOUND", "FOUR_DIGITS", "FROM", "FULL", "GET", "GLOBAL", "GO", "GOTO",
+ "GRANT", "GREATER_OR_EQUAL", "HAVING", "HOUR", "HOURS_BETWEEN",
+ "IDENTITY", "IFNULL", "SQL_IGNORE", "IMMEDIATE", "SQL_IN", "INCLUDE",
+ "INDEX", "INDICATOR", "INITIALLY", "INNER", "INPUT", "INSENSITIVE",
+ "INSERT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "IS", "ISOLATION",
+ "JOIN", "JUSTIFY", "KEY", "LANGUAGE", "LAST", "LCASE", "LEFT", "LENGTH",
+ "LESS_OR_EQUAL", "LEVEL", "LIKE", "LINE_WIDTH", "LOCAL", "LOCATE", "LOG",
+ "SQL_LONG", "LOWER", "LTRIM", "LTRIP", "MATCH", "SQL_MAX", "MICROSOFT",
+ "SQL_MIN", "MINUS", "MINUTE", "MINUTES_BETWEEN", "MOD", "MODIFY",
+ "MODULE", "MONTH", "MONTHS_BETWEEN", "MUMPS", "NAMES", "NATIONAL",
+ "NCHAR", "NEXT", "NODUP", "NONE", "NOT", "NOT_EQUAL", "NOT_EQUAL2",
+ "NOW", "SQL_NULL", "SQL_IS", "SQL_IS_NULL", "SQL_IS_NOT_NULL", "NULLIF",
+ "NUMERIC", "OCTET_LENGTH", "ODBC", "OF", "SQL_OFF", "SQL_ON", "ONLY",
+ "OPEN", "OPTION", "OR", "ORDER", "OUTER", "OUTPUT", "OVERLAPS", "PAGE",
+ "PARTIAL", "SQL_PASCAL", "PERSISTENT", "CQL_PI", "PLI", "POSITION",
+ "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES",
+ "PROCEDURE", "PRODUCT", "PUBLIC", "QUARTER", "QUIT", "RAND", "READ_ONLY",
+ "REAL", "REFERENCES", "REPEAT", "REPLACE", "RESTRICT", "REVOKE", "RIGHT",
+ "ROLLBACK", "ROWS", "RPAD", "RTRIM", "SCHEMA", "SCREEN_WIDTH", "SCROLL",
+ "SECOND", "SECONDS_BETWEEN", "SELECT", "SEQUENCE", "SETOPT", "SET",
+ "SHOWOPT", "SIGN", "SIMILAR_TO", "NOT_SIMILAR_TO", "INTEGER_CONST",
+ "REAL_CONST", "DATE_CONST", "DATETIME_CONST", "TIME_CONST", "SIN",
+ "SQL_SIZE", "SMALLINT", "SOME", "SPACE", "SQL", "SQL_TRUE", "SQLCA",
+ "SQLCODE", "SQLERROR", "SQLSTATE", "SQLWARNING", "SQRT", "STDEV",
+ "SUBSTRING", "SUM", "SYSDATE", "SYSDATE_FORMAT", "SYSTEM", "TABLE",
+ "TAN", "TEMPORARY", "THEN", "THREE_DIGITS", "TIME", "TIMESTAMP",
+ "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TINYINT", "TO", "TO_CHAR",
+ "TO_DATE", "TRANSACTION", "TRANSLATE", "TRANSLATION", "TRUNCATE",
+ "GENERAL_TITLE", "TWO_DIGITS", "UCASE", "UNION", "UNIQUE", "SQL_UNKNOWN",
+ "UPDATE", "UPPER", "USAGE", "USER", "IDENTIFIER",
+ "IDENTIFIER_DOT_ASTERISK", "QUERY_PARAMETER", "USING", "VALUE", "VALUES",
+ "VARBINARY", "VARCHAR", "VARYING", "VENDOR", "VIEW", "WEEK", "WHEN",
+ "WHENEVER", "WHERE", "WHERE_CURRENT_OF", "WITH", "WORD_WRAPPED", "WORK",
+ "WRAPPED", "XOR", "YEAR", "YEARS_BETWEEN", "SCAN_ERROR", "__LAST_TOKEN",
+ "'-'", "'+'", "'*'", "'%'", "'@'", "';'", "','", "'.'", "'$'", "'('",
+ "')'", "'?'", "'''", "'/'", "'='", "'<'", "'>'", "ILIKE", "'^'", "'['",
+ "']'", "'&'", "'|'", "'~'", "$accept", "TopLevelStatement",
+ "StatementList", "Statement", "CreateTableStatement", "@1", "ColDefs",
+ "ColDef", "ColKeys", "ColKey", "ColType", "SelectStatement", "Select",
+ "SelectOptions", "WhereClause", "OrderByClause", "OrderByColumnId",
+ "OrderByOption", "aExpr", "aExpr2", "aExpr3", "aExpr4", "aExpr5",
+ "aExpr6", "aExpr7", "aExpr8", "aExpr9", "aExpr10", "aExprList",
+ "aExprList2", "Tables", "FlatTableList", "FlatTable", "ColViews",
+ "ColItem", "ColExpression", "ColWildCard", 0
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to
+ token YYLEX-NUM. */
+static const yytype_uint16 yytoknum[] =
+{
+ 0, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 267, 268, 269, 270, 271, 272, 273, 274,
+ 275, 276, 277, 278, 279, 280, 281, 282, 283, 284,
+ 285, 286, 287, 288, 289, 290, 291, 292, 293, 294,
+ 295, 296, 297, 298, 299, 300, 301, 302, 303, 304,
+ 305, 306, 307, 308, 309, 310, 311, 312, 313, 314,
+ 315, 316, 317, 318, 319, 320, 321, 322, 323, 324,
+ 325, 326, 327, 328, 329, 330, 331, 332, 333, 334,
+ 335, 336, 337, 338, 339, 340, 341, 342, 343, 344,
+ 345, 346, 347, 348, 349, 350, 351, 352, 353, 354,
+ 355, 356, 357, 358, 359, 360, 361, 362, 363, 364,
+ 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
+ 375, 376, 377, 378, 379, 380, 381, 382, 383, 384,
+ 385, 386, 387, 388, 389, 390, 391, 392, 393, 394,
+ 395, 396, 397, 398, 399, 400, 401, 402, 403, 404,
+ 405, 406, 407, 408, 409, 410, 411, 412, 413, 414,
+ 415, 416, 417, 418, 419, 420, 421, 422, 423, 424,
+ 425, 426, 427, 428, 429, 430, 431, 432, 433, 434,
+ 435, 436, 437, 438, 439, 440, 441, 442, 443, 444,
+ 445, 446, 447, 448, 449, 450, 451, 452, 453, 454,
+ 455, 456, 457, 458, 459, 460, 461, 462, 463, 464,
+ 465, 466, 467, 468, 469, 470, 471, 472, 473, 474,
+ 475, 476, 477, 478, 479, 480, 481, 482, 483, 484,
+ 485, 486, 487, 488, 489, 490, 491, 492, 493, 494,
+ 495, 496, 497, 498, 499, 500, 501, 502, 503, 504,
+ 505, 506, 507, 508, 509, 510, 511, 512, 513, 514,
+ 515, 516, 517, 518, 519, 520, 521, 522, 523, 524,
+ 525, 526, 527, 528, 529, 530, 531, 532, 533, 534,
+ 535, 536, 537, 538, 539, 540, 541, 542, 543, 544,
+ 545, 546, 547, 548, 549, 550, 551, 552, 553, 554,
+ 555, 556, 557, 558, 559, 560, 561, 562, 563, 564,
+ 565, 566, 567, 568, 569, 570, 571, 572, 573, 574,
+ 575, 576, 577, 578, 579, 580, 581, 582, 583, 584,
+ 585, 586, 587, 588, 589, 590, 591, 592, 593, 594,
+ 595, 596, 597, 598, 599, 600, 601, 602, 603, 45,
+ 43, 42, 37, 64, 59, 44, 46, 36, 40, 41,
+ 63, 39, 47, 61, 60, 62, 604, 94, 91, 93,
+ 38, 124, 126
+};
+# endif
+
+/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
+static const yytype_uint16 yyr1[] =
+{
+ 0, 373, 374, 375, 375, 375, 376, 376, 378, 377,
+ 379, 379, 380, 380, 381, 381, 382, 382, 382, 383,
+ 383, 383, 383, 384, 384, 384, 384, 384, 385, 386,
+ 386, 386, 386, 387, 388, 388, 388, 388, 389, 389,
+ 389, 390, 390, 391, 392, 392, 392, 392, 393, 393,
+ 393, 393, 393, 393, 394, 394, 394, 394, 394, 394,
+ 394, 395, 395, 395, 396, 396, 396, 397, 397, 397,
+ 397, 397, 398, 398, 398, 398, 399, 399, 399, 399,
+ 399, 399, 399, 399, 399, 399, 399, 399, 399, 400,
+ 401, 402, 402, 403, 404, 404, 405, 405, 405, 406,
+ 406, 407, 407, 407, 407, 408, 408, 409, 409
+};
+
+/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */
+static const yytype_uint8 yyr2[] =
+{
+ 0, 2, 1, 3, 1, 2, 1, 1, 0, 7,
+ 3, 1, 2, 3, 2, 1, 2, 2, 1, 1,
+ 4, 4, 0, 2, 3, 2, 3, 4, 1, 1,
+ 3, 4, 4, 2, 1, 2, 3, 4, 1, 3,
+ 1, 1, 1, 1, 3, 3, 3, 1, 3, 3,
+ 3, 3, 3, 1, 3, 3, 3, 3, 3, 3,
+ 1, 2, 2, 1, 3, 3, 1, 3, 3, 3,
+ 3, 1, 3, 3, 3, 1, 2, 2, 2, 2,
+ 1, 1, 2, 3, 1, 1, 1, 1, 1, 3,
+ 3, 3, 3, 2, 3, 1, 1, 2, 3, 3,
+ 1, 1, 1, 3, 2, 1, 4, 1, 3
+};
+
+/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state
+ STATE-NUM when YYTABLE doesn't specify something else to do. Zero
+ means the default is an error. */
+static const yytype_uint8 yydefact[] =
+{
+ 0, 0, 28, 0, 2, 4, 6, 7, 0, 0,
+ 1, 5, 85, 0, 0, 0, 84, 86, 87, 80,
+ 81, 0, 0, 107, 0, 0, 105, 43, 47, 53,
+ 60, 63, 66, 71, 75, 88, 25, 23, 100, 101,
+ 102, 8, 3, 0, 96, 93, 95, 80, 79, 0,
+ 0, 82, 76, 77, 0, 78, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 61, 62,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 26, 29, 24, 0, 104, 0,
+ 0, 0, 97, 0, 0, 83, 108, 0, 0, 89,
+ 44, 45, 46, 49, 51, 52, 50, 48, 57, 56,
+ 54, 55, 58, 59, 64, 65, 68, 67, 69, 70,
+ 73, 74, 72, 0, 33, 99, 0, 27, 103, 0,
+ 106, 98, 94, 0, 90, 40, 38, 30, 34, 0,
+ 22, 0, 11, 92, 91, 0, 32, 41, 42, 0,
+ 35, 31, 19, 0, 12, 0, 9, 39, 36, 0,
+ 0, 0, 18, 0, 0, 13, 15, 10, 37, 0,
+ 0, 17, 16, 14, 20, 21
+};
+
+/* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int16 yydefgoto[] =
+{
+ -1, 3, 4, 5, 6, 89, 141, 142, 165, 166,
+ 154, 7, 8, 84, 85, 137, 138, 150, 26, 27,
+ 28, 29, 30, 31, 32, 33, 34, 35, 51, 98,
+ 36, 45, 46, 37, 38, 39, 40
+};
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+#define YYPACT_NINF -336
+static const yytype_int16 yypact[] =
+{
+ -67, -271, -336, 39, -336, -311, -336, -336, -50, -265,
+ -336, -67, -336, -298, -262, -37, -336, -336, -336, -335,
+ -336, -37, -37, -336, -37, -37, -336, -336, -18, -135,
+ -142, -336, -7, -325, -332, -336, -336, -133, -336, -19,
+ -336, -336, -336, -40, -9, -291, -336, -318, -336, -309,
+ -37, -336, -336, -336, -292, -336, -37, -37, -37, -37,
+ -37, -37, -37, -37, -37, -37, -37, -37, -336, -336,
+ -37, -37, -37, -37, -37, -37, -37, -37, -37, -37,
+ -37, 26, -37, -47, -336, -153, -216, -251, -336, -284,
+ -273, -237, -336, -262, -231, -336, -336, -260, -263, -336,
+ -336, -336, -336, -336, -336, -336, -336, -336, -336, -336,
+ -336, -336, -336, -336, -336, -336, -336, -336, -336, -336,
+ -336, -336, -336, -261, -336, -336, 51, -336, -336, -227,
+ -336, -336, -336, -37, -336, -336, -258, -239, -25, -261,
+ -3, -324, -336, -260, -336, -224, -336, -336, -336, -261,
+ -254, -336, -256, -255, -24, -227, -336, -336, -336, -261,
+ -169, -168, -336, -104, -63, -24, -336, -336, -336, -250,
+ -249, -336, -336, -336, -336, -336
+};
+
+/* YYPGOTO[NTERM-NUM]. */
+static const yytype_int16 yypgoto[] =
+{
+ -336, -336, 97, -336, -336, -336, -336, -44, -336, -53,
+ -336, -336, -336, 27, -23, -122, -336, -336, -6, -1,
+ 18, -17, -336, -21, 8, 11, 7, -336, -336, -16,
+ 78, -336, 23, -336, 35, 76, -336
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule which
+ number is the opposite. If zero, do what YYDEFACT says.
+ If YYTABLE_NINF, syntax error. */
+#define YYTABLE_NINF -1
+static const yytype_uint8 yytable[] =
+{
+ 147, 152, 56, 12, 87, 14, 12, 162, 1, 81,
+ 59, 64, 135, 12, 91, 95, 12, 151, 54, 78,
+ 79, 49, 48, 50, 74, 75, 9, 158, 52, 53,
+ 80, 155, 55, 72, 73, 156, 65, 168, 94, 10,
+ 50, 60, 96, 11, 97, 76, 77, 108, 109, 110,
+ 111, 114, 115, 112, 113, 100, 101, 102, 13, 41,
+ 43, 13, 44, 136, 93, 66, 67, 99, 13, 123,
+ 68, 69, 126, 128, 129, 148, 124, 103, 104, 105,
+ 106, 107, 116, 117, 118, 119, 130, 131, 14, 120,
+ 121, 122, 81, 95, 139, 133, 134, 140, 145, 82,
+ 157, 159, 160, 161, 169, 170, 171, 172, 42, 174,
+ 175, 167, 173, 127, 146, 86, 132, 144, 125, 90,
+ 0, 0, 82, 0, 0, 0, 0, 143, 0, 70,
+ 71, 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, 15, 0, 0, 15,
+ 16, 0, 0, 16, 0, 0, 15, 0, 0, 15,
+ 16, 0, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 0, 163, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 82, 57, 0, 0, 0,
+ 0, 0, 0, 0, 0, 164, 0, 0, 0, 0,
+ 0, 0, 83, 17, 18, 0, 17, 18, 61, 62,
+ 63, 0, 0, 17, 18, 0, 17, 18, 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, 19, 0, 20, 19, 0, 20,
+ 0, 0, 0, 0, 47, 0, 20, 47, 0, 20,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 21,
+ 22, 23, 21, 22, 23, 88, 0, 0, 24, 21,
+ 22, 24, 21, 22, 0, 92, 0, 0, 24, 0,
+ 0, 24, 25, 0, 0, 25, 58, 0, 153, 0,
+ 149, 0, 25, 0, 0, 25
+};
+
+static const yytype_int16 yycheck[] =
+{
+ 25, 4, 20, 53, 23, 138, 53, 31, 75, 225,
+ 145, 153, 273, 53, 23, 324, 53, 139, 24, 351,
+ 352, 356, 15, 358, 349, 350, 297, 149, 21, 22,
+ 362, 355, 25, 40, 41, 359, 178, 159, 356, 0,
+ 358, 176, 351, 354, 50, 370, 371, 64, 65, 66,
+ 67, 72, 73, 70, 71, 56, 57, 58, 108, 324,
+ 358, 108, 324, 324, 355, 207, 208, 359, 108, 43,
+ 212, 213, 225, 324, 358, 100, 82, 59, 60, 61,
+ 62, 63, 74, 75, 76, 77, 359, 324, 138, 78,
+ 79, 80, 225, 324, 43, 355, 359, 324, 356, 338,
+ 324, 355, 358, 358, 273, 273, 210, 170, 11, 359,
+ 359, 155, 165, 86, 137, 37, 93, 133, 83, 43,
+ -1, -1, 338, -1, -1, -1, -1, 133, -1, 271,
+ 272, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 206, -1, -1, 206,
+ 210, -1, -1, 210, -1, -1, 206, -1, -1, 206,
+ 210, -1, -1, 210, -1, -1, -1, -1, -1, -1,
+ -1, -1, 206, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, 265, -1,
+ -1, -1, -1, -1, -1, 338, 224, -1, -1, -1,
+ -1, -1, -1, -1, -1, 239, -1, -1, -1, -1,
+ -1, -1, 355, 273, 274, -1, 273, 274, 363, 364,
+ 365, -1, -1, 273, 274, -1, 273, 274, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 324, -1, 326, 324, -1, 326,
+ -1, -1, -1, -1, 324, -1, 326, 324, -1, 326,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, 349,
+ 350, 351, 349, 350, 351, 324, -1, -1, 358, 349,
+ 350, 358, 349, 350, -1, 324, -1, -1, 358, -1,
+ -1, 358, 372, -1, -1, 372, 344, -1, 331, -1,
+ 355, -1, 372, -1, -1, 372
+};
+
+/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+ symbol of state STATE-NUM. */
+static const yytype_uint16 yystos[] =
+{
+ 0, 75, 265, 374, 375, 376, 377, 384, 385, 297,
+ 0, 354, 53, 108, 138, 206, 210, 273, 274, 324,
+ 326, 349, 350, 351, 358, 372, 391, 392, 393, 394,
+ 395, 396, 397, 398, 399, 400, 403, 406, 407, 408,
+ 409, 324, 375, 358, 324, 404, 405, 324, 399, 356,
+ 358, 401, 399, 399, 391, 399, 20, 224, 344, 145,
+ 176, 363, 364, 365, 153, 178, 207, 208, 212, 213,
+ 271, 272, 40, 41, 349, 350, 370, 371, 351, 352,
+ 362, 225, 338, 355, 386, 387, 403, 23, 324, 378,
+ 408, 23, 324, 355, 356, 324, 351, 391, 402, 359,
+ 392, 392, 392, 393, 393, 393, 393, 393, 394, 394,
+ 394, 394, 394, 394, 396, 396, 397, 397, 397, 397,
+ 398, 398, 398, 43, 391, 407, 225, 386, 324, 358,
+ 359, 324, 405, 355, 359, 273, 324, 388, 389, 43,
+ 324, 379, 380, 391, 402, 356, 387, 25, 100, 355,
+ 390, 388, 4, 331, 383, 355, 359, 324, 388, 355,
+ 358, 358, 31, 206, 239, 381, 382, 380, 388, 273,
+ 273, 210, 170, 382, 359, 359
+};
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+#define YYEMPTY (-2)
+#define YYEOF 0
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+
+
+/* Like YYERROR except do call yyerror. This remains here temporarily
+ to ease the transition to the new meaning of YYERROR, for GCC.
+ Once GCC version 2 has supplanted version 1, this can go. */
+
+#define YYFAIL goto yyerrlab
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+do \
+ if (yychar == YYEMPTY && yylen == 1) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ yytoken = YYTRANSLATE (yychar); \
+ YYPOPSTACK (1); \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+while (YYID (0))
+
+
+#define YYTERROR 1
+#define YYERRCODE 256
+
+
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+ If N is 0, then set CURRENT to the empty location which ends
+ the previous symbol: RHS[0] (always defined). */
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K])
+#ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do \
+ if (YYID (N)) \
+ { \
+ (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \
+ (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \
+ (Current).last_line = YYRHSLOC (Rhs, N).last_line; \
+ (Current).last_column = YYRHSLOC (Rhs, N).last_column; \
+ } \
+ else \
+ { \
+ (Current).first_line = (Current).last_line = \
+ YYRHSLOC (Rhs, 0).last_line; \
+ (Current).first_column = (Current).last_column = \
+ YYRHSLOC (Rhs, 0).last_column; \
+ } \
+ while (YYID (0))
+#endif
+
+
+/* YY_LOCATION_PRINT -- Print the location on the stream.
+ This macro was not mandated originally: define only if we know
+ we won't break user code: when these are the locations we know. */
+
+#ifndef YY_LOCATION_PRINT
+# if YYLTYPE_IS_TRIVIAL
+# define YY_LOCATION_PRINT(File, Loc) \
+ fprintf (File, "%d.%d-%d.%d", \
+ (Loc).first_line, (Loc).first_column, \
+ (Loc).last_line, (Loc).last_column)
+# else
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+# endif
+#endif
+
+
+/* YYLEX -- calling `yylex' with the right arguments. */
+
+#ifdef YYLEX_PARAM
+# define YYLEX yylex (YYLEX_PARAM)
+#else
+# define YYLEX yylex ()
+#endif
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (YYID (0))
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Type, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (YYID (0))
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT. |
+`--------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, const YYSTYPE * const yyvaluep)
+#else
+static void
+yy_symbol_value_print (yyoutput, yytype, yyvaluep)
+ FILE *yyoutput;
+ int yytype;
+ const YYSTYPE * const yyvaluep;
+#endif
+{
+ if (!yyvaluep)
+ return;
+# ifdef YYPRINT
+ if (yytype < YYNTOKENS)
+ YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# else
+ YYUSE (yyoutput);
+# endif
+ switch (yytype)
+ {
+ default:
+ break;
+ }
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT. |
+`--------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, const YYSTYPE * const yyvaluep)
+#else
+static void
+yy_symbol_print (yyoutput, yytype, yyvaluep)
+ FILE *yyoutput;
+ int yytype;
+ const YYSTYPE * const yyvaluep;
+#endif
+{
+ if (yytype < YYNTOKENS)
+ YYFPRINTF (yyoutput, "token %s (", yytname[yytype]);
+ else
+ YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]);
+
+ yy_symbol_value_print (yyoutput, yytype, yyvaluep);
+ YYFPRINTF (yyoutput, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+static void
+yy_stack_print (yytype_int16 *bottom, yytype_int16 *top)
+#else
+static void
+yy_stack_print (bottom, top)
+ yytype_int16 *bottom;
+ yytype_int16 *top;
+#endif
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; bottom <= top; ++bottom)
+ YYFPRINTF (stderr, " %d", *bottom);
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (YYID (0))
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+static void
+yy_reduce_print (YYSTYPE *yyvsp,
+ int yyrule)
+#else
+static void
+yy_reduce_print (yyvsp, yyrule
+ )
+ YYSTYPE *yyvsp;
+
+ int yyrule;
+#endif
+{
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ unsigned long int yylno = yyrline[yyrule];
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ fprintf (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi],
+ &(yyvsp[(yyi + 1) - (yynrhs)])
+ );
+ fprintf (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyvsp, Rule); \
+} while (YYID (0))
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+# if defined __GLIBC__ && defined _STRING_H
+# define yystrlen strlen
+# else
+/* Return the length of YYSTR. */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+static YYSIZE_T
+yystrlen (const char *yystr)
+#else
+static YYSIZE_T
+yystrlen (yystr)
+ const char *yystr;
+#endif
+{
+ YYSIZE_T yylen;
+ for (yylen = 0; yystr[yylen]; yylen++)
+ continue;
+ return yylen;
+}
+# endif
+# endif
+
+# ifndef yystpcpy
+# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+# define yystpcpy stpcpy
+# else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+ YYDEST. */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+#else
+static char *
+yystpcpy (yydest, yysrc)
+ char *yydest;
+ const char *yysrc;
+#endif
+{
+ char *yyd = yydest;
+ const char *yys = yysrc;
+
+ while ((*yyd++ = *yys++) != '\0')
+ continue;
+
+ return yyd - 1;
+}
+# endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+ quotes and backslashes, so that it's suitable for yyerror. The
+ heuristic is that double-quoting is unnecessary unless the string
+ contains an apostrophe, a comma, or backslash (other than
+ backslash-backslash). YYSTR is taken from yytname. If YYRES is
+ null, do not copy; instead, return the length of what the result
+ would have been. */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+ if (*yystr == '"')
+ {
+ YYSIZE_T yyn = 0;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ /* Fall through. */
+ default:
+ if (yyres)
+ yyres[yyn] = *yyp;
+ yyn++;
+ break;
+
+ case '"':
+ if (yyres)
+ yyres[yyn] = '\0';
+ return yyn;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ if (! yyres)
+ return yystrlen (yystr);
+
+ return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into YYRESULT an error message about the unexpected token
+ YYCHAR while in state YYSTATE. Return the number of bytes copied,
+ including the terminating null byte. If YYRESULT is null, do not
+ copy anything; just return the number of bytes that would be
+ copied. As a special case, return 0 if an ordinary "syntax error"
+ message will do. Return YYSIZE_MAXIMUM if overflow occurs during
+ size calculation. */
+static YYSIZE_T
+yysyntax_error (char *yyresult, int yystate, int yychar)
+{
+ int yyn = yypact[yystate];
+
+ if (! (YYPACT_NINF < yyn && yyn <= YYLAST))
+ return 0;
+ else
+ {
+ int yytype = YYTRANSLATE (yychar);
+ YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]);
+ YYSIZE_T yysize = yysize0;
+ YYSIZE_T yysize1;
+ int yysize_overflow = 0;
+ enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+ int yyx;
+
+# if 0
+ /* This is so xgettext sees the translatable formats that are
+ constructed on the fly. */
+ YY_("syntax error, unexpected %s");
+ YY_("syntax error, unexpected %s, expecting %s");
+ YY_("syntax error, unexpected %s, expecting %s or %s");
+ YY_("syntax error, unexpected %s, expecting %s or %s or %s");
+ YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s");
+# endif
+ char *yyfmt;
+ char const *yyf;
+ static char const yyunexpected[] = "syntax error, unexpected %s";
+ static char const yyexpecting[] = ", expecting %s";
+ static char const yyor[] = " or %s";
+ char yyformat[sizeof yyunexpected
+ + sizeof yyexpecting - 1
+ + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2)
+ * (sizeof yyor - 1))];
+ char const *yyprefix = yyexpecting;
+
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. */
+ int yyxbegin = yyn < 0 ? -yyn : 0;
+
+ /* Stay within bounds of both yycheck and yytname. */
+ int yychecklim = YYLAST - yyn + 1;
+ int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ int yycount = 1;
+
+ yyarg[0] = yytname[yytype];
+ yyfmt = yystpcpy (yyformat, yyunexpected);
+
+ for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR)
+ {
+ if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+ {
+ yycount = 1;
+ yysize = yysize0;
+ yyformat[sizeof yyunexpected - 1] = '\0';
+ break;
+ }
+ yyarg[yycount++] = yytname[yyx];
+ yysize1 = yysize + yytnamerr (0, yytname[yyx]);
+ yysize_overflow |= (yysize1 < yysize);
+ yysize = yysize1;
+ yyfmt = yystpcpy (yyfmt, yyprefix);
+ yyprefix = yyor;
+ }
+
+ yyf = YY_(yyformat);
+ yysize1 = yysize + yystrlen (yyf);
+ yysize_overflow |= (yysize1 < yysize);
+ yysize = yysize1;
+
+ if (yysize_overflow)
+ return YYSIZE_MAXIMUM;
+
+ if (yyresult)
+ {
+ /* Avoid sprintf, as that infringes on the user's name space.
+ Don't have undefined behavior even if the translation
+ produced a string with the wrong number of "%s"s. */
+ char *yyp = yyresult;
+ int yyi = 0;
+ while ((*yyp = *yyf) != '\0')
+ {
+ if (*yyp == '%' && yyf[1] == 's' && yyi < yycount)
+ {
+ yyp += yytnamerr (yyp, yyarg[yyi++]);
+ yyf += 2;
+ }
+ else
+ {
+ yyp++;
+ yyf++;
+ }
+ }
+ }
+ return yysize;
+ }
+}
+#endif /* YYERROR_VERBOSE */
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep)
+#else
+static void
+yydestruct (yymsg, yytype, yyvaluep)
+ const char *yymsg;
+ int yytype;
+ YYSTYPE *yyvaluep;
+#endif
+{
+ YYUSE (yyvaluep);
+
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+ switch (yytype)
+ {
+
+ default:
+ break;
+ }
+}
+
+
+/* Prevent warnings from -Wmissing-prototypes. */
+
+#ifdef YYPARSE_PARAM
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void *YYPARSE_PARAM);
+#else
+int yyparse ();
+#endif
+#else /* ! YYPARSE_PARAM */
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void);
+#else
+int yyparse ();
+#endif
+#endif /* ! YYPARSE_PARAM */
+
+
+
+/* The look-ahead symbol. */
+int yychar;
+
+/* The semantic value of the look-ahead symbol. */
+YYSTYPE yylval;
+
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+#ifdef YYPARSE_PARAM
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void *YYPARSE_PARAM)
+#else
+int
+yyparse (YYPARSE_PARAM)
+ void *YYPARSE_PARAM;
+#endif
+#else /* ! YYPARSE_PARAM */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+ || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void)
+#else
+int
+yyparse ()
+
+#endif
+#endif
+{
+
+ int yystate;
+ int yyn;
+ int yyresult;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus;
+ /* Look-ahead token as an internal (translated) token number. */
+ int yytoken = 0;
+#if YYERROR_VERBOSE
+ /* Buffer for error messages, and its allocated size. */
+ char yymsgbuf[128];
+ char *yymsg = yymsgbuf;
+ YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+ /* Three stacks and their tools:
+ `yyss': related to states,
+ `yyvs': related to semantic values,
+ `yyls': related to locations.
+
+ Refer to the stacks thru separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* The state stack. */
+ yytype_int16 yyssa[YYINITDEPTH];
+ yytype_int16 *yyss = yyssa;
+ yytype_int16 *yyssp;
+
+ /* The semantic value stack. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp;
+
+
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ YYSIZE_T yystacksize = YYINITDEPTH;
+
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yystate = 0;
+ yyerrstatus = 0;
+ yynerrs = 0;
+ yychar = YYEMPTY; /* Cause a token to be read. */
+
+ /* Initialize stack pointers.
+ Waste one element of value and location stack
+ so that they stay on the same level as the state stack.
+ The wasted elements are never initialized. */
+
+ yyssp = yyss;
+ yyvsp = yyvs;
+
+ goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+ yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+ yysetstate:
+ *yyssp = yystate;
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ YYSTYPE *yyvs1 = yyvs;
+ yytype_int16 *yyss1 = yyss;
+
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * sizeof (*yyssp),
+ &yyvs1, yysize * sizeof (*yyvsp),
+
+ &yystacksize);
+
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+ goto yyexhaustedlab;
+# else
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ goto yyexhaustedlab;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yytype_int16 *yyss1 = yyss;
+ union yyalloc *yyptr =
+ (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+ if (! yyptr)
+ goto yyexhaustedlab;
+ YYSTACK_RELOCATE (yyss);
+ YYSTACK_RELOCATE (yyvs);
+
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+#endif /* no yyoverflow */
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+
+ YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+ (unsigned long int) yystacksize));
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+ goto yybackup;
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+
+ /* Do appropriate processing given the current state. Read a
+ look-ahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to look-ahead token. */
+ yyn = yypact[yystate];
+ if (yyn == YYPACT_NINF)
+ goto yydefault;
+
+ /* Not known => get a look-ahead token if don't already have one. */
+
+ /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token: "));
+ yychar = YYLEX;
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = yytoken = YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yyn == 0 || yyn == YYTABLE_NINF)
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ if (yyn == YYFINAL)
+ YYACCEPT;
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the look-ahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+ /* Discard the shifted token unless it is eof. */
+ if (yychar != YYEOF)
+ yychar = YYEMPTY;
+
+ yystate = yyn;
+ *++yyvsp = yylval;
+
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ `$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 2:
+#line 581 "sqlparser.y"
+ {
+//todo: multiple statements
+//todo: not only "select" statements
+ parser->setOperation(Parser::OP_Select);
+ parser->setQuerySchema((yyvsp[(1) - (1)].querySchema));
+;}
+ break;
+
+ case 3:
+#line 591 "sqlparser.y"
+ {
+//todo: multiple statements
+;}
+ break;
+
+ case 5:
+#line 596 "sqlparser.y"
+ {
+ (yyval.querySchema) = (yyvsp[(1) - (2)].querySchema);
+;}
+ break;
+
+ case 6:
+#line 606 "sqlparser.y"
+ {
+YYACCEPT;
+;}
+ break;
+
+ case 7:
+#line 610 "sqlparser.y"
+ {
+ (yyval.querySchema) = (yyvsp[(1) - (1)].querySchema);
+;}
+ break;
+
+ case 8:
+#line 617 "sqlparser.y"
+ {
+ parser->setOperation(Parser::OP_CreateTable);
+ parser->createTable((yyvsp[(3) - (3)].stringValue)->latin1());
+ delete (yyvsp[(3) - (3)].stringValue);
+;}
+ break;
+
+ case 11:
+#line 627 "sqlparser.y"
+ {
+;}
+ break;
+
+ case 12:
+#line 633 "sqlparser.y"
+ {
+ KexiDBDbg << "adding field " << *(yyvsp[(1) - (2)].stringValue) << endl;
+ field->setName((yyvsp[(1) - (2)].stringValue)->latin1());
+ parser->table()->addField(field);
+ field = 0;
+ delete (yyvsp[(1) - (2)].stringValue);
+;}
+ break;
+
+ case 13:
+#line 641 "sqlparser.y"
+ {
+ KexiDBDbg << "adding field " << *(yyvsp[(1) - (3)].stringValue) << endl;
+ field->setName(*(yyvsp[(1) - (3)].stringValue));
+ delete (yyvsp[(1) - (3)].stringValue);
+ parser->table()->addField(field);
+
+// if(field->isPrimaryKey())
+// parser->table()->addPrimaryKey(field->name());
+
+// delete field;
+// field = 0;
+;}
+ break;
+
+ case 15:
+#line 657 "sqlparser.y"
+ {
+;}
+ break;
+
+ case 16:
+#line 663 "sqlparser.y"
+ {
+ field->setPrimaryKey(true);
+ KexiDBDbg << "primary" << endl;
+;}
+ break;
+
+ case 17:
+#line 668 "sqlparser.y"
+ {
+ field->setNotNull(true);
+ KexiDBDbg << "not_null" << endl;
+;}
+ break;
+
+ case 18:
+#line 673 "sqlparser.y"
+ {
+ field->setAutoIncrement(true);
+ KexiDBDbg << "ainc" << endl;
+;}
+ break;
+
+ case 19:
+#line 681 "sqlparser.y"
+ {
+ field = new Field();
+ field->setType((yyvsp[(1) - (1)].colType));
+;}
+ break;
+
+ case 20:
+#line 686 "sqlparser.y"
+ {
+ KexiDBDbg << "sql + length" << endl;
+ field = new Field();
+ field->setPrecision((yyvsp[(3) - (4)].integerValue));
+ field->setType((yyvsp[(1) - (4)].colType));
+;}
+ break;
+
+ case 21:
+#line 693 "sqlparser.y"
+ {
+ field = new Field();
+ field->setPrecision((yyvsp[(3) - (4)].integerValue));
+ field->setType(Field::Text);
+;}
+ break;
+
+ case 22:
+#line 699 "sqlparser.y"
+ {
+ // SQLITE compatibillity
+ field = new Field();
+ field->setType(Field::InvalidType);
+;}
+ break;
+
+ case 23:
+#line 708 "sqlparser.y"
+ {
+ KexiDBDbg << "Select ColViews=" << (yyvsp[(2) - (2)].exprList)->debugString() << endl;
+
+ if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (2)].querySchema), (yyvsp[(2) - (2)].exprList) )))
+ return 0;
+;}
+ break;
+
+ case 24:
+#line 715 "sqlparser.y"
+ {
+ if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (3)].querySchema), (yyvsp[(2) - (3)].exprList), (yyvsp[(3) - (3)].exprList) )))
+ return 0;
+;}
+ break;
+
+ case 25:
+#line 720 "sqlparser.y"
+ {
+ KexiDBDbg << "Select ColViews Tables" << endl;
+ if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (2)].querySchema), 0, (yyvsp[(2) - (2)].exprList) )))
+ return 0;
+;}
+ break;
+
+ case 26:
+#line 726 "sqlparser.y"
+ {
+ KexiDBDbg << "Select ColViews Conditions" << endl;
+ if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (3)].querySchema), (yyvsp[(2) - (3)].exprList), 0, (yyvsp[(3) - (3)].selectOptions) )))
+ return 0;
+;}
+ break;
+
+ case 27:
+#line 732 "sqlparser.y"
+ {
+ KexiDBDbg << "Select ColViews Tables SelectOptions" << endl;
+ if (!((yyval.querySchema) = buildSelectQuery( (yyvsp[(1) - (4)].querySchema), (yyvsp[(2) - (4)].exprList), (yyvsp[(3) - (4)].exprList), (yyvsp[(4) - (4)].selectOptions) )))
+ return 0;
+;}
+ break;
+
+ case 28:
+#line 741 "sqlparser.y"
+ {
+ KexiDBDbg << "SELECT" << endl;
+// parser->createSelect();
+// parser->setOperation(Parser::OP_Select);
+ (yyval.querySchema) = new QuerySchema();
+;}
+ break;
+
+ case 29:
+#line 751 "sqlparser.y"
+ {
+ KexiDBDbg << "WhereClause" << endl;
+ (yyval.selectOptions) = new SelectOptionsInternal;
+ (yyval.selectOptions)->whereExpr = (yyvsp[(1) - (1)].expr);
+;}
+ break;
+
+ case 30:
+#line 757 "sqlparser.y"
+ {
+ KexiDBDbg << "OrderByClause" << endl;
+ (yyval.selectOptions) = new SelectOptionsInternal;
+ (yyval.selectOptions)->orderByColumns = (yyvsp[(3) - (3)].orderByColumns);
+;}
+ break;
+
+ case 31:
+#line 763 "sqlparser.y"
+ {
+ KexiDBDbg << "WhereClause ORDER BY OrderByClause" << endl;
+ (yyval.selectOptions) = new SelectOptionsInternal;
+ (yyval.selectOptions)->whereExpr = (yyvsp[(1) - (4)].expr);
+ (yyval.selectOptions)->orderByColumns = (yyvsp[(4) - (4)].orderByColumns);
+;}
+ break;
+
+ case 32:
+#line 770 "sqlparser.y"
+ {
+ KexiDBDbg << "OrderByClause WhereClause" << endl;
+ (yyval.selectOptions) = new SelectOptionsInternal;
+ (yyval.selectOptions)->whereExpr = (yyvsp[(4) - (4)].expr);
+ (yyval.selectOptions)->orderByColumns = (yyvsp[(3) - (4)].orderByColumns);
+;}
+ break;
+
+ case 33:
+#line 780 "sqlparser.y"
+ {
+ (yyval.expr) = (yyvsp[(2) - (2)].expr);
+;}
+ break;
+
+ case 34:
+#line 789 "sqlparser.y"
+ {
+ KexiDBDbg << "ORDER BY IDENTIFIER" << endl;
+ (yyval.orderByColumns) = new OrderByColumnInternal::List;
+ OrderByColumnInternal orderByColumn;
+ orderByColumn.setColumnByNameOrNumber( *(yyvsp[(1) - (1)].variantValue) );
+ (yyval.orderByColumns)->append( orderByColumn );
+ delete (yyvsp[(1) - (1)].variantValue);
+;}
+ break;
+
+ case 35:
+#line 798 "sqlparser.y"
+ {
+ KexiDBDbg << "ORDER BY IDENTIFIER OrderByOption" << endl;
+ (yyval.orderByColumns) = new OrderByColumnInternal::List;
+ OrderByColumnInternal orderByColumn;
+ orderByColumn.setColumnByNameOrNumber( *(yyvsp[(1) - (2)].variantValue) );
+ orderByColumn.ascending = (yyvsp[(2) - (2)].booleanValue);
+ (yyval.orderByColumns)->append( orderByColumn );
+ delete (yyvsp[(1) - (2)].variantValue);
+;}
+ break;
+
+ case 36:
+#line 808 "sqlparser.y"
+ {
+ (yyval.orderByColumns) = (yyvsp[(3) - (3)].orderByColumns);
+ OrderByColumnInternal orderByColumn;
+ orderByColumn.setColumnByNameOrNumber( *(yyvsp[(1) - (3)].variantValue) );
+ (yyval.orderByColumns)->append( orderByColumn );
+ delete (yyvsp[(1) - (3)].variantValue);
+;}
+ break;
+
+ case 37:
+#line 816 "sqlparser.y"
+ {
+ (yyval.orderByColumns) = (yyvsp[(4) - (4)].orderByColumns);
+ OrderByColumnInternal orderByColumn;
+ orderByColumn.setColumnByNameOrNumber( *(yyvsp[(1) - (4)].variantValue) );
+ orderByColumn.ascending = (yyvsp[(2) - (4)].booleanValue);
+ (yyval.orderByColumns)->append( orderByColumn );
+ delete (yyvsp[(1) - (4)].variantValue);
+;}
+ break;
+
+ case 38:
+#line 828 "sqlparser.y"
+ {
+ (yyval.variantValue) = new QVariant( *(yyvsp[(1) - (1)].stringValue) );
+ KexiDBDbg << "OrderByColumnId: " << *(yyval.variantValue) << endl;
+ delete (yyvsp[(1) - (1)].stringValue);
+;}
+ break;
+
+ case 39:
+#line 834 "sqlparser.y"
+ {
+ (yyval.variantValue) = new QVariant( *(yyvsp[(1) - (3)].stringValue) + "." + *(yyvsp[(3) - (3)].stringValue) );
+ KexiDBDbg << "OrderByColumnId: " << *(yyval.variantValue) << endl;
+ delete (yyvsp[(1) - (3)].stringValue);
+ delete (yyvsp[(3) - (3)].stringValue);
+;}
+ break;
+
+ case 40:
+#line 841 "sqlparser.y"
+ {
+ (yyval.variantValue) = new QVariant((yyvsp[(1) - (1)].integerValue));
+ KexiDBDbg << "OrderByColumnId: " << *(yyval.variantValue) << endl;
+;}
+ break;
+
+ case 41:
+#line 848 "sqlparser.y"
+ {
+ (yyval.booleanValue) = true;
+;}
+ break;
+
+ case 42:
+#line 852 "sqlparser.y"
+ {
+ (yyval.booleanValue) = false;
+;}
+ break;
+
+ case 44:
+#line 864 "sqlparser.y"
+ {
+// KexiDBDbg << "AND " << $3.debugString() << endl;
+ (yyval.expr) = new BinaryExpr( KexiDBExpr_Logical, (yyvsp[(1) - (3)].expr), AND, (yyvsp[(3) - (3)].expr) );
+;}
+ break;
+
+ case 45:
+#line 869 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr( KexiDBExpr_Logical, (yyvsp[(1) - (3)].expr), OR, (yyvsp[(3) - (3)].expr) );
+;}
+ break;
+
+ case 46:
+#line 873 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr( KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), XOR, (yyvsp[(3) - (3)].expr) );
+;}
+ break;
+
+ case 48:
+#line 883 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), '>', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 49:
+#line 887 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), GREATER_OR_EQUAL, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 50:
+#line 891 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), '<', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 51:
+#line 895 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), LESS_OR_EQUAL, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 52:
+#line 899 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), '=', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 54:
+#line 909 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), NOT_EQUAL, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 55:
+#line 914 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), NOT_EQUAL2, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 56:
+#line 918 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), LIKE, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 57:
+#line 922 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), SQL_IN, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 58:
+#line 926 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), SIMILAR_TO, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 59:
+#line 930 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Relational, (yyvsp[(1) - (3)].expr), NOT_SIMILAR_TO, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 61:
+#line 940 "sqlparser.y"
+ {
+ (yyval.expr) = new UnaryExpr( SQL_IS_NULL, (yyvsp[(1) - (2)].expr) );
+;}
+ break;
+
+ case 62:
+#line 944 "sqlparser.y"
+ {
+ (yyval.expr) = new UnaryExpr( SQL_IS_NOT_NULL, (yyvsp[(1) - (2)].expr) );
+;}
+ break;
+
+ case 64:
+#line 954 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), BITWISE_SHIFT_LEFT, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 65:
+#line 958 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), BITWISE_SHIFT_RIGHT, (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 67:
+#line 968 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '+', (yyvsp[(3) - (3)].expr));
+ (yyval.expr)->debug();
+;}
+ break;
+
+ case 68:
+#line 973 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '-', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 69:
+#line 977 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '&', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 70:
+#line 981 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '|', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 72:
+#line 991 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '/', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 73:
+#line 995 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '*', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 74:
+#line 999 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(KexiDBExpr_Arithm, (yyvsp[(1) - (3)].expr), '%', (yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 76:
+#line 1010 "sqlparser.y"
+ {
+ (yyval.expr) = new UnaryExpr( '-', (yyvsp[(2) - (2)].expr) );
+;}
+ break;
+
+ case 77:
+#line 1014 "sqlparser.y"
+ {
+ (yyval.expr) = new UnaryExpr( '+', (yyvsp[(2) - (2)].expr) );
+;}
+ break;
+
+ case 78:
+#line 1018 "sqlparser.y"
+ {
+ (yyval.expr) = new UnaryExpr( '~', (yyvsp[(2) - (2)].expr) );
+;}
+ break;
+
+ case 79:
+#line 1022 "sqlparser.y"
+ {
+ (yyval.expr) = new UnaryExpr( NOT, (yyvsp[(2) - (2)].expr) );
+;}
+ break;
+
+ case 80:
+#line 1026 "sqlparser.y"
+ {
+ (yyval.expr) = new VariableExpr( *(yyvsp[(1) - (1)].stringValue) );
+
+//TODO: simplify this later if that's 'only one field name' expression
+ KexiDBDbg << " + identifier: " << *(yyvsp[(1) - (1)].stringValue) << endl;
+ delete (yyvsp[(1) - (1)].stringValue);
+;}
+ break;
+
+ case 81:
+#line 1034 "sqlparser.y"
+ {
+ (yyval.expr) = new QueryParameterExpr( *(yyvsp[(1) - (1)].stringValue) );
+ KexiDBDbg << " + query parameter: " << (yyval.expr)->debugString() << endl;
+ delete (yyvsp[(1) - (1)].stringValue);
+;}
+ break;
+
+ case 82:
+#line 1040 "sqlparser.y"
+ {
+ KexiDBDbg << " + function: " << *(yyvsp[(1) - (2)].stringValue) << "(" << (yyvsp[(2) - (2)].exprList)->debugString() << ")" << endl;
+ (yyval.expr) = new FunctionExpr(*(yyvsp[(1) - (2)].stringValue), (yyvsp[(2) - (2)].exprList));
+ delete (yyvsp[(1) - (2)].stringValue);
+;}
+ break;
+
+ case 83:
+#line 1047 "sqlparser.y"
+ {
+ (yyval.expr) = new VariableExpr( *(yyvsp[(1) - (3)].stringValue) + "." + *(yyvsp[(3) - (3)].stringValue) );
+ KexiDBDbg << " + identifier.identifier: " << *(yyvsp[(1) - (3)].stringValue) << "." << *(yyvsp[(3) - (3)].stringValue) << endl;
+ delete (yyvsp[(1) - (3)].stringValue);
+ delete (yyvsp[(3) - (3)].stringValue);
+;}
+ break;
+
+ case 84:
+#line 1054 "sqlparser.y"
+ {
+ (yyval.expr) = new ConstExpr( SQL_NULL, QVariant() );
+ KexiDBDbg << " + NULL" << endl;
+// $$ = new Field();
+ //$$->setName(QString::null);
+;}
+ break;
+
+ case 85:
+#line 1061 "sqlparser.y"
+ {
+ (yyval.expr) = new ConstExpr( CHARACTER_STRING_LITERAL, *(yyvsp[(1) - (1)].stringValue) );
+ KexiDBDbg << " + constant " << (yyvsp[(1) - (1)].stringValue) << endl;
+ delete (yyvsp[(1) - (1)].stringValue);
+;}
+ break;
+
+ case 86:
+#line 1067 "sqlparser.y"
+ {
+ QVariant val;
+ if ((yyvsp[(1) - (1)].integerValue) <= INT_MAX && (yyvsp[(1) - (1)].integerValue) >= INT_MIN)
+ val = (int)(yyvsp[(1) - (1)].integerValue);
+ else if ((yyvsp[(1) - (1)].integerValue) <= UINT_MAX && (yyvsp[(1) - (1)].integerValue) >= 0)
+ val = (uint)(yyvsp[(1) - (1)].integerValue);
+ else if ((yyvsp[(1) - (1)].integerValue) <= (Q_LLONG)LLONG_MAX && (yyvsp[(1) - (1)].integerValue) >= (Q_LLONG)LLONG_MIN)
+ val = (Q_LLONG)(yyvsp[(1) - (1)].integerValue);
+
+// if ($1 < ULLONG_MAX)
+// val = (Q_ULLONG)$1;
+//TODO ok?
+
+ (yyval.expr) = new ConstExpr( INTEGER_CONST, val );
+ KexiDBDbg << " + int constant: " << val.toString() << endl;
+;}
+ break;
+
+ case 87:
+#line 1084 "sqlparser.y"
+ {
+ (yyval.expr) = new ConstExpr( REAL_CONST, QPoint( (yyvsp[(1) - (1)].realValue).integer, (yyvsp[(1) - (1)].realValue).fractional ) );
+ KexiDBDbg << " + real constant: " << (yyvsp[(1) - (1)].realValue).integer << "." << (yyvsp[(1) - (1)].realValue).fractional << endl;
+;}
+ break;
+
+ case 89:
+#line 1095 "sqlparser.y"
+ {
+ KexiDBDbg << "(expr)" << endl;
+ (yyval.expr) = new UnaryExpr('(', (yyvsp[(2) - (3)].expr));
+;}
+ break;
+
+ case 90:
+#line 1103 "sqlparser.y"
+ {
+// $$ = new NArgExpr(0, 0);
+// $$->add( $1 );
+// $$->add( $3 );
+ (yyval.exprList) = (yyvsp[(2) - (3)].exprList);
+;}
+ break;
+
+ case 91:
+#line 1113 "sqlparser.y"
+ {
+ (yyval.exprList) = (yyvsp[(3) - (3)].exprList);
+ (yyval.exprList)->prepend( (yyvsp[(1) - (3)].expr) );
+;}
+ break;
+
+ case 92:
+#line 1118 "sqlparser.y"
+ {
+ (yyval.exprList) = new NArgExpr(0, 0);
+ (yyval.exprList)->add( (yyvsp[(1) - (3)].expr) );
+ (yyval.exprList)->add( (yyvsp[(3) - (3)].expr) );
+;}
+ break;
+
+ case 93:
+#line 1127 "sqlparser.y"
+ {
+ (yyval.exprList) = (yyvsp[(2) - (2)].exprList);
+;}
+ break;
+
+ case 94:
+#line 1172 "sqlparser.y"
+ {
+ (yyval.exprList) = (yyvsp[(1) - (3)].exprList);
+ (yyval.exprList)->add((yyvsp[(3) - (3)].expr));
+;}
+ break;
+
+ case 95:
+#line 1177 "sqlparser.y"
+ {
+ (yyval.exprList) = new NArgExpr(KexiDBExpr_TableList, IDENTIFIER); //ok?
+ (yyval.exprList)->add((yyvsp[(1) - (1)].expr));
+;}
+ break;
+
+ case 96:
+#line 1185 "sqlparser.y"
+ {
+ KexiDBDbg << "FROM: '" << *(yyvsp[(1) - (1)].stringValue) << "'" << endl;
+ (yyval.expr) = new VariableExpr(*(yyvsp[(1) - (1)].stringValue));
+
+ /*
+//TODO: this isn't ok for more tables:
+ Field::ListIterator it = parser->select()->fieldsIterator();
+ for(Field *item; (item = it.current()); ++it)
+ {
+ if(item->table() == dummy)
+ {
+ item->setTable(schema);
+ }
+
+ if(item->table() && !item->isQueryAsterisk())
+ {
+ Field *f = item->table()->field(item->name());
+ if(!f)
+ {
+ ParserError err(i18n("Field List Error"), i18n("Unknown column '%1' in table '%2'").arg(item->name()).arg(schema->name()), ctoken, current);
+ parser->setError(err);
+ yyerror("fieldlisterror");
+ }
+ }
+ }*/
+ delete (yyvsp[(1) - (1)].stringValue);
+;}
+ break;
+
+ case 97:
+#line 1213 "sqlparser.y"
+ {
+ //table + alias
+ (yyval.expr) = new BinaryExpr(
+ KexiDBExpr_SpecialBinary,
+ new VariableExpr(*(yyvsp[(1) - (2)].stringValue)), 0,
+ new VariableExpr(*(yyvsp[(2) - (2)].stringValue))
+ );
+ delete (yyvsp[(1) - (2)].stringValue);
+ delete (yyvsp[(2) - (2)].stringValue);
+;}
+ break;
+
+ case 98:
+#line 1224 "sqlparser.y"
+ {
+ //table + alias
+ (yyval.expr) = new BinaryExpr(
+ KexiDBExpr_SpecialBinary,
+ new VariableExpr(*(yyvsp[(1) - (3)].stringValue)), AS,
+ new VariableExpr(*(yyvsp[(3) - (3)].stringValue))
+ );
+ delete (yyvsp[(1) - (3)].stringValue);
+ delete (yyvsp[(3) - (3)].stringValue);
+;}
+ break;
+
+ case 99:
+#line 1240 "sqlparser.y"
+ {
+ (yyval.exprList) = (yyvsp[(1) - (3)].exprList);
+ (yyval.exprList)->add( (yyvsp[(3) - (3)].expr) );
+ KexiDBDbg << "ColViews: ColViews , ColItem" << endl;
+;}
+ break;
+
+ case 100:
+#line 1246 "sqlparser.y"
+ {
+ (yyval.exprList) = new NArgExpr(0,0);
+ (yyval.exprList)->add( (yyvsp[(1) - (1)].expr) );
+ KexiDBDbg << "ColViews: ColItem" << endl;
+;}
+ break;
+
+ case 101:
+#line 1255 "sqlparser.y"
+ {
+// $$ = new Field();
+// dummy->addField($$);
+// $$->setExpression( $1 );
+// parser->select()->addField($$);
+ (yyval.expr) = (yyvsp[(1) - (1)].expr);
+ KexiDBDbg << " added column expr: '" << (yyvsp[(1) - (1)].expr)->debugString() << "'" << endl;
+;}
+ break;
+
+ case 102:
+#line 1264 "sqlparser.y"
+ {
+ (yyval.expr) = (yyvsp[(1) - (1)].expr);
+ KexiDBDbg << " added column wildcard: '" << (yyvsp[(1) - (1)].expr)->debugString() << "'" << endl;
+;}
+ break;
+
+ case 103:
+#line 1269 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(
+ KexiDBExpr_SpecialBinary, (yyvsp[(1) - (3)].expr), AS,
+ new VariableExpr(*(yyvsp[(3) - (3)].stringValue))
+ );
+ KexiDBDbg << " added column expr: " << (yyval.expr)->debugString() << endl;
+ delete (yyvsp[(3) - (3)].stringValue);
+;}
+ break;
+
+ case 104:
+#line 1278 "sqlparser.y"
+ {
+ (yyval.expr) = new BinaryExpr(
+ KexiDBExpr_SpecialBinary, (yyvsp[(1) - (2)].expr), 0,
+ new VariableExpr(*(yyvsp[(2) - (2)].stringValue))
+ );
+ KexiDBDbg << " added column expr: " << (yyval.expr)->debugString() << endl;
+ delete (yyvsp[(2) - (2)].stringValue);
+;}
+ break;
+
+ case 105:
+#line 1290 "sqlparser.y"
+ {
+ (yyval.expr) = (yyvsp[(1) - (1)].expr);
+;}
+ break;
+
+ case 106:
+#line 1334 "sqlparser.y"
+ {
+ (yyval.expr) = (yyvsp[(3) - (4)].expr);
+//TODO
+// $$->setName("DISTINCT(" + $3->name() + ")");
+;}
+ break;
+
+ case 107:
+#line 1343 "sqlparser.y"
+ {
+ (yyval.expr) = new VariableExpr("*");
+ KexiDBDbg << "all columns" << endl;
+
+// QueryAsterisk *ast = new QueryAsterisk(parser->select(), dummy);
+// parser->select()->addAsterisk(ast);
+// requiresTable = true;
+;}
+ break;
+
+ case 108:
+#line 1352 "sqlparser.y"
+ {
+ QString s( *(yyvsp[(1) - (3)].stringValue) );
+ s += ".*";
+ (yyval.expr) = new VariableExpr(s);
+ KexiDBDbg << " + all columns from " << s << endl;
+ delete (yyvsp[(1) - (3)].stringValue);
+;}
+ break;
+
+
+/* Line 1267 of yacc.c. */
+#line 3256 "sqlparser.tab.c"
+ default: break;
+ }
+ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+
+ *++yyvsp = yyval;
+
+
+ /* Now `shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+
+ yyn = yyr1[yyn];
+
+ yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+ if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+ yystate = yytable[yystate];
+ else
+ yystate = yydefgoto[yyn - YYNTOKENS];
+
+ goto yynewstate;
+
+
+/*------------------------------------.
+| yyerrlab -- here on detecting error |
+`------------------------------------*/
+yyerrlab:
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+#if ! YYERROR_VERBOSE
+ yyerror (YY_("syntax error"));
+#else
+ {
+ YYSIZE_T yysize = yysyntax_error (0, yystate, yychar);
+ if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM)
+ {
+ YYSIZE_T yyalloc = 2 * yysize;
+ if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM))
+ yyalloc = YYSTACK_ALLOC_MAXIMUM;
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+ yymsg = (char *) YYSTACK_ALLOC (yyalloc);
+ if (yymsg)
+ yymsg_alloc = yyalloc;
+ else
+ {
+ yymsg = yymsgbuf;
+ yymsg_alloc = sizeof yymsgbuf;
+ }
+ }
+
+ if (0 < yysize && yysize <= yymsg_alloc)
+ {
+ (void) yysyntax_error (yymsg, yystate, yychar);
+ yyerror (yymsg);
+ }
+ else
+ {
+ yyerror (YY_("syntax error"));
+ if (yysize != 0)
+ goto yyexhaustedlab;
+ }
+ }
+#endif
+ }
+
+
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse look-ahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse look-ahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+
+ /* Pacify compilers like GCC when the user code never invokes
+ YYERROR and the label yyerrorlab therefore never appears in user
+ code. */
+ if (/*CONSTCOND*/ 0)
+ goto yyerrorlab;
+
+ /* Do not reclaim the symbols of the rule which action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (yyn != YYPACT_NINF)
+ {
+ yyn += YYTERROR;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ yystos[yystate], yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ if (yyn == YYFINAL)
+ YYACCEPT;
+
+ *++yyvsp = yylval;
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+#ifndef yyoverflow
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here. |
+`-------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ /* Fall through. */
+#endif
+
+yyreturn:
+ if (yychar != YYEOF && yychar != YYEMPTY)
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ /* Do not reclaim the symbols of the rule which action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ yystos[*yyssp], yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+#endif
+ return yyresult;
+}
+
+
+#line 1367 "sqlparser.y"
+
+
+
+const char * const tname(int offset) { return yytname[offset]; }
diff --git a/kexi/kexidb/parser/sqlparser.h b/kexi/kexidb/parser/sqlparser.h
new file mode 100644
index 000000000..7caf4b879
--- /dev/null
+++ b/kexi/kexidb/parser/sqlparser.h
@@ -0,0 +1,778 @@
+#ifndef _SQLPARSER_H_
+#define _SQLPARSER_H_
+#include "field.h"
+#include "parser.h"
+#include "sqltypes.h"
+
+bool parseData(KexiDB::Parser *p, const char *data);
+/* A Bison parser, made by GNU Bison 2.2. */
+
+/* Skeleton interface for Bison's Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+ Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT 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, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* Tokens. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ /* Put the tokens into the symbol table, so that GDB and other debuggers
+ know about them. */
+ enum yytokentype {
+ UMINUS = 258,
+ SQL_TYPE = 259,
+ SQL_ABS = 260,
+ ACOS = 261,
+ AMPERSAND = 262,
+ SQL_ABSOLUTE = 263,
+ ADA = 264,
+ ADD = 265,
+ ADD_DAYS = 266,
+ ADD_HOURS = 267,
+ ADD_MINUTES = 268,
+ ADD_MONTHS = 269,
+ ADD_SECONDS = 270,
+ ADD_YEARS = 271,
+ ALL = 272,
+ ALLOCATE = 273,
+ ALTER = 274,
+ AND = 275,
+ ANY = 276,
+ ARE = 277,
+ AS = 278,
+ ASIN = 279,
+ ASC = 280,
+ ASCII = 281,
+ ASSERTION = 282,
+ ATAN = 283,
+ ATAN2 = 284,
+ AUTHORIZATION = 285,
+ AUTO_INCREMENT = 286,
+ AVG = 287,
+ BEFORE = 288,
+ SQL_BEGIN = 289,
+ BETWEEN = 290,
+ BIGINT = 291,
+ BINARY = 292,
+ BIT = 293,
+ BIT_LENGTH = 294,
+ BITWISE_SHIFT_LEFT = 295,
+ BITWISE_SHIFT_RIGHT = 296,
+ BREAK = 297,
+ BY = 298,
+ CASCADE = 299,
+ CASCADED = 300,
+ CASE = 301,
+ CAST = 302,
+ CATALOG = 303,
+ CEILING = 304,
+ CENTER = 305,
+ SQL_CHAR = 306,
+ CHAR_LENGTH = 307,
+ CHARACTER_STRING_LITERAL = 308,
+ CHECK = 309,
+ CLOSE = 310,
+ COALESCE = 311,
+ COBOL = 312,
+ COLLATE = 313,
+ COLLATION = 314,
+ COLUMN = 315,
+ COMMIT = 316,
+ COMPUTE = 317,
+ CONCAT = 318,
+ CONCATENATION = 319,
+ CONNECT = 320,
+ CONNECTION = 321,
+ CONSTRAINT = 322,
+ CONSTRAINTS = 323,
+ CONTINUE = 324,
+ CONVERT = 325,
+ CORRESPONDING = 326,
+ COS = 327,
+ COT = 328,
+ COUNT = 329,
+ CREATE = 330,
+ CURDATE = 331,
+ CURRENT = 332,
+ CURRENT_DATE = 333,
+ CURRENT_TIME = 334,
+ CURRENT_TIMESTAMP = 335,
+ CURTIME = 336,
+ CURSOR = 337,
+ DATABASE = 338,
+ SQL_DATE = 339,
+ DATE_FORMAT = 340,
+ DATE_REMAINDER = 341,
+ DATE_VALUE = 342,
+ DAY = 343,
+ DAYOFMONTH = 344,
+ DAYOFWEEK = 345,
+ DAYOFYEAR = 346,
+ DAYS_BETWEEN = 347,
+ DEALLOCATE = 348,
+ DEC = 349,
+ DECLARE = 350,
+ DEFAULT = 351,
+ DEFERRABLE = 352,
+ DEFERRED = 353,
+ SQL_DELETE = 354,
+ DESC = 355,
+ DESCRIBE = 356,
+ DESCRIPTOR = 357,
+ DIAGNOSTICS = 358,
+ DICTIONARY = 359,
+ DIRECTORY = 360,
+ DISCONNECT = 361,
+ DISPLACEMENT = 362,
+ DISTINCT = 363,
+ DOMAIN_TOKEN = 364,
+ SQL_DOUBLE = 365,
+ DOUBLE_QUOTED_STRING = 366,
+ DROP = 367,
+ ELSE = 368,
+ END = 369,
+ END_EXEC = 370,
+ EQUAL = 371,
+ ESCAPE = 372,
+ EXCEPT = 373,
+ SQL_EXCEPTION = 374,
+ EXEC = 375,
+ EXECUTE = 376,
+ EXISTS = 377,
+ EXP = 378,
+ EXPONENT = 379,
+ EXTERNAL = 380,
+ EXTRACT = 381,
+ SQL_FALSE = 382,
+ FETCH = 383,
+ FIRST = 384,
+ SQL_FLOAT = 385,
+ FLOOR = 386,
+ FN = 387,
+ FOR = 388,
+ FOREIGN = 389,
+ FORTRAN = 390,
+ FOUND = 391,
+ FOUR_DIGITS = 392,
+ FROM = 393,
+ FULL = 394,
+ GET = 395,
+ GLOBAL = 396,
+ GO = 397,
+ GOTO = 398,
+ GRANT = 399,
+ GREATER_OR_EQUAL = 400,
+ HAVING = 401,
+ HOUR = 402,
+ HOURS_BETWEEN = 403,
+ IDENTITY = 404,
+ IFNULL = 405,
+ SQL_IGNORE = 406,
+ IMMEDIATE = 407,
+ SQL_IN = 408,
+ INCLUDE = 409,
+ INDEX = 410,
+ INDICATOR = 411,
+ INITIALLY = 412,
+ INNER = 413,
+ INPUT = 414,
+ INSENSITIVE = 415,
+ INSERT = 416,
+ INTEGER = 417,
+ INTERSECT = 418,
+ INTERVAL = 419,
+ INTO = 420,
+ IS = 421,
+ ISOLATION = 422,
+ JOIN = 423,
+ JUSTIFY = 424,
+ KEY = 425,
+ LANGUAGE = 426,
+ LAST = 427,
+ LCASE = 428,
+ LEFT = 429,
+ LENGTH = 430,
+ LESS_OR_EQUAL = 431,
+ LEVEL = 432,
+ LIKE = 433,
+ LINE_WIDTH = 434,
+ LOCAL = 435,
+ LOCATE = 436,
+ LOG = 437,
+ SQL_LONG = 438,
+ LOWER = 439,
+ LTRIM = 440,
+ LTRIP = 441,
+ MATCH = 442,
+ SQL_MAX = 443,
+ MICROSOFT = 444,
+ SQL_MIN = 445,
+ MINUS = 446,
+ MINUTE = 447,
+ MINUTES_BETWEEN = 448,
+ MOD = 449,
+ MODIFY = 450,
+ MODULE = 451,
+ MONTH = 452,
+ MONTHS_BETWEEN = 453,
+ MUMPS = 454,
+ NAMES = 455,
+ NATIONAL = 456,
+ NCHAR = 457,
+ NEXT = 458,
+ NODUP = 459,
+ NONE = 460,
+ NOT = 461,
+ NOT_EQUAL = 462,
+ NOT_EQUAL2 = 463,
+ NOW = 464,
+ SQL_NULL = 465,
+ SQL_IS = 466,
+ SQL_IS_NULL = 467,
+ SQL_IS_NOT_NULL = 468,
+ NULLIF = 469,
+ NUMERIC = 470,
+ OCTET_LENGTH = 471,
+ ODBC = 472,
+ OF = 473,
+ SQL_OFF = 474,
+ SQL_ON = 475,
+ ONLY = 476,
+ OPEN = 477,
+ OPTION = 478,
+ OR = 479,
+ ORDER = 480,
+ OUTER = 481,
+ OUTPUT = 482,
+ OVERLAPS = 483,
+ PAGE = 484,
+ PARTIAL = 485,
+ SQL_PASCAL = 486,
+ PERSISTENT = 487,
+ CQL_PI = 488,
+ PLI = 489,
+ POSITION = 490,
+ PRECISION = 491,
+ PREPARE = 492,
+ PRESERVE = 493,
+ PRIMARY = 494,
+ PRIOR = 495,
+ PRIVILEGES = 496,
+ PROCEDURE = 497,
+ PRODUCT = 498,
+ PUBLIC = 499,
+ QUARTER = 500,
+ QUIT = 501,
+ RAND = 502,
+ READ_ONLY = 503,
+ REAL = 504,
+ REFERENCES = 505,
+ REPEAT = 506,
+ REPLACE = 507,
+ RESTRICT = 508,
+ REVOKE = 509,
+ RIGHT = 510,
+ ROLLBACK = 511,
+ ROWS = 512,
+ RPAD = 513,
+ RTRIM = 514,
+ SCHEMA = 515,
+ SCREEN_WIDTH = 516,
+ SCROLL = 517,
+ SECOND = 518,
+ SECONDS_BETWEEN = 519,
+ SELECT = 520,
+ SEQUENCE = 521,
+ SETOPT = 522,
+ SET = 523,
+ SHOWOPT = 524,
+ SIGN = 525,
+ SIMILAR_TO = 526,
+ NOT_SIMILAR_TO = 527,
+ INTEGER_CONST = 528,
+ REAL_CONST = 529,
+ DATE_CONST = 530,
+ DATETIME_CONST = 531,
+ TIME_CONST = 532,
+ SIN = 533,
+ SQL_SIZE = 534,
+ SMALLINT = 535,
+ SOME = 536,
+ SPACE = 537,
+ SQL = 538,
+ SQL_TRUE = 539,
+ SQLCA = 540,
+ SQLCODE = 541,
+ SQLERROR = 542,
+ SQLSTATE = 543,
+ SQLWARNING = 544,
+ SQRT = 545,
+ STDEV = 546,
+ SUBSTRING = 547,
+ SUM = 548,
+ SYSDATE = 549,
+ SYSDATE_FORMAT = 550,
+ SYSTEM = 551,
+ TABLE = 552,
+ TAN = 553,
+ TEMPORARY = 554,
+ THEN = 555,
+ THREE_DIGITS = 556,
+ TIME = 557,
+ TIMESTAMP = 558,
+ TIMEZONE_HOUR = 559,
+ TIMEZONE_MINUTE = 560,
+ TINYINT = 561,
+ TO = 562,
+ TO_CHAR = 563,
+ TO_DATE = 564,
+ TRANSACTION = 565,
+ TRANSLATE = 566,
+ TRANSLATION = 567,
+ TRUNCATE = 568,
+ GENERAL_TITLE = 569,
+ TWO_DIGITS = 570,
+ UCASE = 571,
+ UNION = 572,
+ UNIQUE = 573,
+ SQL_UNKNOWN = 574,
+ UPDATE = 575,
+ UPPER = 576,
+ USAGE = 577,
+ USER = 578,
+ IDENTIFIER = 579,
+ IDENTIFIER_DOT_ASTERISK = 580,
+ QUERY_PARAMETER = 581,
+ USING = 582,
+ VALUE = 583,
+ VALUES = 584,
+ VARBINARY = 585,
+ VARCHAR = 586,
+ VARYING = 587,
+ VENDOR = 588,
+ VIEW = 589,
+ WEEK = 590,
+ WHEN = 591,
+ WHENEVER = 592,
+ WHERE = 593,
+ WHERE_CURRENT_OF = 594,
+ WITH = 595,
+ WORD_WRAPPED = 596,
+ WORK = 597,
+ WRAPPED = 598,
+ XOR = 599,
+ YEAR = 600,
+ YEARS_BETWEEN = 601,
+ SCAN_ERROR = 602,
+ __LAST_TOKEN = 603,
+ ILIKE = 604
+ };
+#endif
+/* Tokens. */
+#define UMINUS 258
+#define SQL_TYPE 259
+#define SQL_ABS 260
+#define ACOS 261
+#define AMPERSAND 262
+#define SQL_ABSOLUTE 263
+#define ADA 264
+#define ADD 265
+#define ADD_DAYS 266
+#define ADD_HOURS 267
+#define ADD_MINUTES 268
+#define ADD_MONTHS 269
+#define ADD_SECONDS 270
+#define ADD_YEARS 271
+#define ALL 272
+#define ALLOCATE 273
+#define ALTER 274
+#define AND 275
+#define ANY 276
+#define ARE 277
+#define AS 278
+#define ASIN 279
+#define ASC 280
+#define ASCII 281
+#define ASSERTION 282
+#define ATAN 283
+#define ATAN2 284
+#define AUTHORIZATION 285
+#define AUTO_INCREMENT 286
+#define AVG 287
+#define BEFORE 288
+#define SQL_BEGIN 289
+#define BETWEEN 290
+#define BIGINT 291
+#define BINARY 292
+#define BIT 293
+#define BIT_LENGTH 294
+#define BITWISE_SHIFT_LEFT 295
+#define BITWISE_SHIFT_RIGHT 296
+#define BREAK 297
+#define BY 298
+#define CASCADE 299
+#define CASCADED 300
+#define CASE 301
+#define CAST 302
+#define CATALOG 303
+#define CEILING 304
+#define CENTER 305
+#define SQL_CHAR 306
+#define CHAR_LENGTH 307
+#define CHARACTER_STRING_LITERAL 308
+#define CHECK 309
+#define CLOSE 310
+#define COALESCE 311
+#define COBOL 312
+#define COLLATE 313
+#define COLLATION 314
+#define COLUMN 315
+#define COMMIT 316
+#define COMPUTE 317
+#define CONCAT 318
+#define CONCATENATION 319
+#define CONNECT 320
+#define CONNECTION 321
+#define CONSTRAINT 322
+#define CONSTRAINTS 323
+#define CONTINUE 324
+#define CONVERT 325
+#define CORRESPONDING 326
+#define COS 327
+#define COT 328
+#define COUNT 329
+#define CREATE 330
+#define CURDATE 331
+#define CURRENT 332
+#define CURRENT_DATE 333
+#define CURRENT_TIME 334
+#define CURRENT_TIMESTAMP 335
+#define CURTIME 336
+#define CURSOR 337
+#define DATABASE 338
+#define SQL_DATE 339
+#define DATE_FORMAT 340
+#define DATE_REMAINDER 341
+#define DATE_VALUE 342
+#define DAY 343
+#define DAYOFMONTH 344
+#define DAYOFWEEK 345
+#define DAYOFYEAR 346
+#define DAYS_BETWEEN 347
+#define DEALLOCATE 348
+#define DEC 349
+#define DECLARE 350
+#define DEFAULT 351
+#define DEFERRABLE 352
+#define DEFERRED 353
+#define SQL_DELETE 354
+#define DESC 355
+#define DESCRIBE 356
+#define DESCRIPTOR 357
+#define DIAGNOSTICS 358
+#define DICTIONARY 359
+#define DIRECTORY 360
+#define DISCONNECT 361
+#define DISPLACEMENT 362
+#define DISTINCT 363
+#define DOMAIN_TOKEN 364
+#define SQL_DOUBLE 365
+#define DOUBLE_QUOTED_STRING 366
+#define DROP 367
+#define ELSE 368
+#define END 369
+#define END_EXEC 370
+#define EQUAL 371
+#define ESCAPE 372
+#define EXCEPT 373
+#define SQL_EXCEPTION 374
+#define EXEC 375
+#define EXECUTE 376
+#define EXISTS 377
+#define EXP 378
+#define EXPONENT 379
+#define EXTERNAL 380
+#define EXTRACT 381
+#define SQL_FALSE 382
+#define FETCH 383
+#define FIRST 384
+#define SQL_FLOAT 385
+#define FLOOR 386
+#define FN 387
+#define FOR 388
+#define FOREIGN 389
+#define FORTRAN 390
+#define FOUND 391
+#define FOUR_DIGITS 392
+#define FROM 393
+#define FULL 394
+#define GET 395
+#define GLOBAL 396
+#define GO 397
+#define GOTO 398
+#define GRANT 399
+#define GREATER_OR_EQUAL 400
+#define HAVING 401
+#define HOUR 402
+#define HOURS_BETWEEN 403
+#define IDENTITY 404
+#define IFNULL 405
+#define SQL_IGNORE 406
+#define IMMEDIATE 407
+#define SQL_IN 408
+#define INCLUDE 409
+#define INDEX 410
+#define INDICATOR 411
+#define INITIALLY 412
+#define INNER 413
+#define INPUT 414
+#define INSENSITIVE 415
+#define INSERT 416
+#define INTEGER 417
+#define INTERSECT 418
+#define INTERVAL 419
+#define INTO 420
+#define IS 421
+#define ISOLATION 422
+#define JOIN 423
+#define JUSTIFY 424
+#define KEY 425
+#define LANGUAGE 426
+#define LAST 427
+#define LCASE 428
+#define LEFT 429
+#define LENGTH 430
+#define LESS_OR_EQUAL 431
+#define LEVEL 432
+#define LIKE 433
+#define LINE_WIDTH 434
+#define LOCAL 435
+#define LOCATE 436
+#define LOG 437
+#define SQL_LONG 438
+#define LOWER 439
+#define LTRIM 440
+#define LTRIP 441
+#define MATCH 442
+#define SQL_MAX 443
+#define MICROSOFT 444
+#define SQL_MIN 445
+#define MINUS 446
+#define MINUTE 447
+#define MINUTES_BETWEEN 448
+#define MOD 449
+#define MODIFY 450
+#define MODULE 451
+#define MONTH 452
+#define MONTHS_BETWEEN 453
+#define MUMPS 454
+#define NAMES 455
+#define NATIONAL 456
+#define NCHAR 457
+#define NEXT 458
+#define NODUP 459
+#define NONE 460
+#define NOT 461
+#define NOT_EQUAL 462
+#define NOT_EQUAL2 463
+#define NOW 464
+#define SQL_NULL 465
+#define SQL_IS 466
+#define SQL_IS_NULL 467
+#define SQL_IS_NOT_NULL 468
+#define NULLIF 469
+#define NUMERIC 470
+#define OCTET_LENGTH 471
+#define ODBC 472
+#define OF 473
+#define SQL_OFF 474
+#define SQL_ON 475
+#define ONLY 476
+#define OPEN 477
+#define OPTION 478
+#define OR 479
+#define ORDER 480
+#define OUTER 481
+#define OUTPUT 482
+#define OVERLAPS 483
+#define PAGE 484
+#define PARTIAL 485
+#define SQL_PASCAL 486
+#define PERSISTENT 487
+#define CQL_PI 488
+#define PLI 489
+#define POSITION 490
+#define PRECISION 491
+#define PREPARE 492
+#define PRESERVE 493
+#define PRIMARY 494
+#define PRIOR 495
+#define PRIVILEGES 496
+#define PROCEDURE 497
+#define PRODUCT 498
+#define PUBLIC 499
+#define QUARTER 500
+#define QUIT 501
+#define RAND 502
+#define READ_ONLY 503
+#define REAL 504
+#define REFERENCES 505
+#define REPEAT 506
+#define REPLACE 507
+#define RESTRICT 508
+#define REVOKE 509
+#define RIGHT 510
+#define ROLLBACK 511
+#define ROWS 512
+#define RPAD 513
+#define RTRIM 514
+#define SCHEMA 515
+#define SCREEN_WIDTH 516
+#define SCROLL 517
+#define SECOND 518
+#define SECONDS_BETWEEN 519
+#define SELECT 520
+#define SEQUENCE 521
+#define SETOPT 522
+#define SET 523
+#define SHOWOPT 524
+#define SIGN 525
+#define SIMILAR_TO 526
+#define NOT_SIMILAR_TO 527
+#define INTEGER_CONST 528
+#define REAL_CONST 529
+#define DATE_CONST 530
+#define DATETIME_CONST 531
+#define TIME_CONST 532
+#define SIN 533
+#define SQL_SIZE 534
+#define SMALLINT 535
+#define SOME 536
+#define SPACE 537
+#define SQL 538
+#define SQL_TRUE 539
+#define SQLCA 540
+#define SQLCODE 541
+#define SQLERROR 542
+#define SQLSTATE 543
+#define SQLWARNING 544
+#define SQRT 545
+#define STDEV 546
+#define SUBSTRING 547
+#define SUM 548
+#define SYSDATE 549
+#define SYSDATE_FORMAT 550
+#define SYSTEM 551
+#define TABLE 552
+#define TAN 553
+#define TEMPORARY 554
+#define THEN 555
+#define THREE_DIGITS 556
+#define TIME 557
+#define TIMESTAMP 558
+#define TIMEZONE_HOUR 559
+#define TIMEZONE_MINUTE 560
+#define TINYINT 561
+#define TO 562
+#define TO_CHAR 563
+#define TO_DATE 564
+#define TRANSACTION 565
+#define TRANSLATE 566
+#define TRANSLATION 567
+#define TRUNCATE 568
+#define GENERAL_TITLE 569
+#define TWO_DIGITS 570
+#define UCASE 571
+#define UNION 572
+#define UNIQUE 573
+#define SQL_UNKNOWN 574
+#define UPDATE 575
+#define UPPER 576
+#define USAGE 577
+#define USER 578
+#define IDENTIFIER 579
+#define IDENTIFIER_DOT_ASTERISK 580
+#define QUERY_PARAMETER 581
+#define USING 582
+#define VALUE 583
+#define VALUES 584
+#define VARBINARY 585
+#define VARCHAR 586
+#define VARYING 587
+#define VENDOR 588
+#define VIEW 589
+#define WEEK 590
+#define WHEN 591
+#define WHENEVER 592
+#define WHERE 593
+#define WHERE_CURRENT_OF 594
+#define WITH 595
+#define WORD_WRAPPED 596
+#define WORK 597
+#define WRAPPED 598
+#define XOR 599
+#define YEAR 600
+#define YEARS_BETWEEN 601
+#define SCAN_ERROR 602
+#define __LAST_TOKEN 603
+#define ILIKE 604
+
+
+
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+#line 511 "sqlparser.y"
+{
+ QString* stringValue;
+ Q_LLONG integerValue;
+ bool booleanValue;
+ struct realType realValue;
+ KexiDB::Field::Type colType;
+ KexiDB::Field *field;
+ KexiDB::BaseExpr *expr;
+ KexiDB::NArgExpr *exprList;
+ KexiDB::ConstExpr *constExpr;
+ KexiDB::QuerySchema *querySchema;
+ SelectOptionsInternal *selectOptions;
+ OrderByColumnInternal::List *orderByColumns;
+ QVariant *variantValue;
+}
+/* Line 1528 of yacc.c. */
+#line 763 "sqlparser.tab.h"
+ YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+extern YYSTYPE yylval;
+
+#endif
diff --git a/kexi/kexidb/parser/sqlparser.y b/kexi/kexidb/parser/sqlparser.y
new file mode 100644
index 000000000..5a8357f22
--- /dev/null
+++ b/kexi/kexidb/parser/sqlparser.y
@@ -0,0 +1,1368 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+%token UMINUS
+
+%token SQL_TYPE
+%token SQL_ABS
+%token ACOS
+%token AMPERSAND
+%token SQL_ABSOLUTE
+%token ADA
+%token ADD
+%token ADD_DAYS
+%token ADD_HOURS
+%token ADD_MINUTES
+%token ADD_MONTHS
+%token ADD_SECONDS
+%token ADD_YEARS
+%token ALL
+%token ALLOCATE
+%token ALTER
+%token AND
+%token ANY
+%token ARE
+%token AS
+%token ASIN
+%token ASC
+%token ASCII
+%token ASSERTION
+%token ATAN
+%token ATAN2
+%token AUTHORIZATION
+%token AUTO_INCREMENT
+%token AVG
+%token BEFORE
+%token SQL_BEGIN
+%token BETWEEN
+%token BIGINT
+%token BINARY
+%token BIT
+%token BIT_LENGTH
+%token BITWISE_SHIFT_LEFT
+%token BITWISE_SHIFT_RIGHT
+%token BREAK
+%token BY
+%token CASCADE
+%token CASCADED
+%token CASE
+%token CAST
+%token CATALOG
+%token CEILING
+%token CENTER
+%token SQL_CHAR
+%token CHAR_LENGTH
+%token CHARACTER_STRING_LITERAL
+%token CHECK
+%token CLOSE
+%token COALESCE
+%token COBOL
+%token COLLATE
+%token COLLATION
+%token COLUMN
+%token COMMIT
+%token COMPUTE
+%token CONCAT
+%token CONCATENATION /* || */
+%token CONNECT
+%token CONNECTION
+%token CONSTRAINT
+%token CONSTRAINTS
+%token CONTINUE
+%token CONVERT
+%token CORRESPONDING
+%token COS
+%token COT
+%token COUNT
+%token CREATE
+%token CURDATE
+%token CURRENT
+%token CURRENT_DATE
+%token CURRENT_TIME
+%token CURRENT_TIMESTAMP
+%token CURTIME
+%token CURSOR
+%token DATABASE
+%token SQL_DATE
+%token DATE_FORMAT
+%token DATE_REMAINDER
+%token DATE_VALUE
+%token DAY
+%token DAYOFMONTH
+%token DAYOFWEEK
+%token DAYOFYEAR
+%token DAYS_BETWEEN
+%token DEALLOCATE
+%token DEC
+%token DECLARE
+%token DEFAULT
+%token DEFERRABLE
+%token DEFERRED
+%token SQL_DELETE
+%token DESC
+%token DESCRIBE
+%token DESCRIPTOR
+%token DIAGNOSTICS
+%token DICTIONARY
+%token DIRECTORY
+%token DISCONNECT
+%token DISPLACEMENT
+%token DISTINCT
+%token DOMAIN_TOKEN
+%token SQL_DOUBLE
+%token DOUBLE_QUOTED_STRING
+%token DROP
+%token ELSE
+%token END
+%token END_EXEC
+%token EQUAL
+%token ESCAPE
+%token EXCEPT
+%token SQL_EXCEPTION
+%token EXEC
+%token EXECUTE
+%token EXISTS
+%token EXP
+%token EXPONENT
+%token EXTERNAL
+%token EXTRACT
+%token SQL_FALSE
+%token FETCH
+%token FIRST
+%token SQL_FLOAT
+%token FLOOR
+%token FN
+%token FOR
+%token FOREIGN
+%token FORTRAN
+%token FOUND
+%token FOUR_DIGITS
+%token FROM
+%token FULL
+%token GET
+%token GLOBAL
+%token GO
+%token GOTO
+%token GRANT
+%token GREATER_OR_EQUAL
+//%token GREATER_THAN
+//conflict %token GROUP
+%token HAVING
+%token HOUR
+%token HOURS_BETWEEN
+%token IDENTITY
+%token IFNULL
+%token SQL_IGNORE
+%token IMMEDIATE
+%token SQL_IN
+%token INCLUDE
+%token INDEX
+%token INDICATOR
+%token INITIALLY
+%token INNER
+%token INPUT
+%token INSENSITIVE
+%token INSERT
+%token INTEGER
+%token INTERSECT
+%token INTERVAL
+%token INTO
+%token IS
+%token ISOLATION
+%token JOIN
+%token JUSTIFY
+%token KEY
+%token LANGUAGE
+%token LAST
+%token LCASE
+%token LEFT
+%token LENGTH
+%token LESS_OR_EQUAL
+//%token LESS_THAN
+%token LEVEL
+%token LIKE
+%token LINE_WIDTH
+%token LOCAL
+%token LOCATE
+%token LOG
+%token SQL_LONG
+%token LOWER
+%token LTRIM
+%token LTRIP
+%token MATCH
+%token SQL_MAX
+%token MICROSOFT
+%token SQL_MIN
+%token MINUS
+%token MINUTE
+%token MINUTES_BETWEEN
+%token MOD
+%token MODIFY
+%token MODULE
+%token MONTH
+%token MONTHS_BETWEEN
+%token MUMPS
+%token NAMES
+%token NATIONAL
+%token NCHAR
+%token NEXT
+%token NODUP
+%token NONE
+%token NOT
+%token NOT_EQUAL //<>
+%token NOT_EQUAL2 //!=
+%token NOW
+%token SQL_NULL
+%token SQL_IS
+%token SQL_IS_NULL /*helper */
+%token SQL_IS_NOT_NULL /*helper */
+%token NULLIF
+%token NUMERIC
+%token OCTET_LENGTH
+%token ODBC
+%token OF
+%token SQL_OFF
+%token SQL_ON
+%token ONLY
+%token OPEN
+%token OPTION
+%token OR
+%token ORDER
+%token OUTER
+%token OUTPUT
+%token OVERLAPS
+%token PAGE
+%token PARTIAL
+%token SQL_PASCAL
+%token PERSISTENT
+%token CQL_PI
+%token PLI
+%token POSITION
+%token PRECISION
+%token PREPARE
+%token PRESERVE
+%token PRIMARY
+%token PRIOR
+%token PRIVILEGES
+%token PROCEDURE
+%token PRODUCT
+%token PUBLIC
+%token QUARTER
+%token QUIT
+%token RAND
+%token READ_ONLY
+%token REAL
+%token REFERENCES
+%token REPEAT
+%token REPLACE
+%token RESTRICT
+%token REVOKE
+%token RIGHT
+%token ROLLBACK
+%token ROWS
+%token RPAD
+%token RTRIM
+%token SCHEMA
+%token SCREEN_WIDTH
+%token SCROLL
+%token SECOND
+%token SECONDS_BETWEEN
+%token SELECT
+%token SEQUENCE
+%token SETOPT
+%token SET
+%token SHOWOPT
+%token SIGN
+//%token SIMILAR
+%token SIMILAR_TO /* helper */
+%token NOT_SIMILAR_TO /* helper */
+%token INTEGER_CONST
+%token REAL_CONST
+%token DATE_CONST
+%token DATETIME_CONST
+%token TIME_CONST
+%token SIN
+%token SQL_SIZE
+%token SMALLINT
+%token SOME
+%token SPACE
+%token SQL
+%token SQL_TRUE
+%token SQLCA
+%token SQLCODE
+%token SQLERROR
+%token SQLSTATE
+%token SQLWARNING
+%token SQRT
+%token STDEV
+%token SUBSTRING
+%token SUM
+%token SYSDATE
+%token SYSDATE_FORMAT
+%token SYSTEM
+%token TABLE
+%token TAN
+%token TEMPORARY
+%token THEN
+%token THREE_DIGITS
+%token TIME
+%token TIMESTAMP
+%token TIMEZONE_HOUR
+%token TIMEZONE_MINUTE
+%token TINYINT
+%token TO
+%token TO_CHAR
+%token TO_DATE
+%token TRANSACTION
+%token TRANSLATE
+%token TRANSLATION
+%token TRUNCATE
+%token GENERAL_TITLE
+%token TWO_DIGITS
+%token UCASE
+%token UNION
+%token UNIQUE
+%token SQL_UNKNOWN
+//%token UNSIGNED_INTEGER
+%token UPDATE
+%token UPPER
+%token USAGE
+%token USER
+%token IDENTIFIER
+%token IDENTIFIER_DOT_ASTERISK
+%token QUERY_PARAMETER
+//%token ERROR_DIGIT_BEFORE_IDENTIFIER
+%token USING
+%token VALUE
+%token VALUES
+%token VARBINARY
+%token VARCHAR
+%token VARYING
+%token VENDOR
+%token VIEW
+%token WEEK
+%token WHEN
+%token WHENEVER
+%token WHERE
+%token WHERE_CURRENT_OF
+%token WITH
+%token WORD_WRAPPED
+%token WORK
+%token WRAPPED
+%token XOR
+%token YEAR
+%token YEARS_BETWEEN
+
+%token SCAN_ERROR
+%token __LAST_TOKEN /* sentinel */
+
+%token '-' '+'
+%token '*'
+%token '%'
+%token '@'
+%token ';'
+%token ','
+%token '.'
+%token '$'
+//%token '<'
+//%token '>'
+%token '(' ')'
+%token '?'
+%token '\''
+%token '/'
+
+%type <stringValue> IDENTIFIER
+%type <stringValue> IDENTIFIER_DOT_ASTERISK
+%type <stringValue> QUERY_PARAMETER
+%type <stringValue> CHARACTER_STRING_LITERAL
+%type <stringValue> DOUBLE_QUOTED_STRING
+
+/*
+%type <field> ColExpression
+%type <field> ColView
+*/
+%type <expr> ColExpression
+%type <expr> ColWildCard
+//%type <expr> ColView
+%type <expr> ColItem
+%type <exprList> ColViews
+%type <expr> aExpr
+%type <expr> aExpr2
+%type <expr> aExpr3
+%type <expr> aExpr4
+%type <expr> aExpr5
+%type <expr> aExpr6
+%type <expr> aExpr7
+%type <expr> aExpr8
+%type <expr> aExpr9
+%type <expr> aExpr10
+%type <exprList> aExprList
+%type <exprList> aExprList2
+%type <expr> WhereClause
+%type <orderByColumns> OrderByClause
+%type <booleanValue> OrderByOption
+%type <variantValue> OrderByColumnId
+%type <selectOptions> SelectOptions
+%type <expr> FlatTable
+%type <exprList> Tables
+%type <exprList> FlatTableList
+%type <querySchema> SelectStatement
+%type <querySchema> Select
+/*todo : list*/
+%type <querySchema> StatementList
+/*todo: not onlu select*/
+%type <querySchema> Statement
+
+%type <colType> SQL_TYPE
+%type <integerValue> INTEGER_CONST
+%type <realValue> REAL_CONST
+/*%type <integerValue> SIGNED_INTEGER */
+
+%{
+#ifndef YYDEBUG /* compat. */
+# define YYDEBUG 0
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <iostream>
+#include <assert.h>
+#include <limits.h>
+//TODO OK?
+#ifdef Q_WS_WIN
+//workaround for bug on msvc
+# undef LLONG_MIN
+#endif
+#ifndef LLONG_MAX
+# define LLONG_MAX 0x7fffffffffffffffLL
+#endif
+#ifndef LLONG_MIN
+# define LLONG_MIN 0x8000000000000000LL
+#endif
+#ifndef LLONG_MAX
+# define ULLONG_MAX 0xffffffffffffffffLL
+#endif
+
+#ifdef _WIN32
+# include <malloc.h>
+#endif
+
+#include <qobject.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <qptrlist.h>
+#include <qcstring.h>
+#include <qvariant.h>
+
+#include <connection.h>
+#include <queryschema.h>
+#include <field.h>
+#include <tableschema.h>
+
+#include "parser.h"
+#include "parser_p.h"
+#include "sqltypes.h"
+
+int yylex();
+
+// using namespace std;
+using namespace KexiDB;
+
+#define YY_NO_UNPUT
+#define YYSTACK_USE_ALLOCA 1
+#define YYMAXDEPTH 255
+
+ extern "C"
+ {
+ int yywrap()
+ {
+ return 1;
+ }
+ }
+
+#if 0
+ struct yyval
+ {
+ QString parserUserName;
+ int integerValue;
+ KexiDBField::ColumnType coltype;
+ }
+#endif
+
+%}
+
+%union {
+ QString* stringValue;
+ Q_LLONG integerValue;
+ bool booleanValue;
+ struct realType realValue;
+ KexiDB::Field::Type colType;
+ KexiDB::Field *field;
+ KexiDB::BaseExpr *expr;
+ KexiDB::NArgExpr *exprList;
+ KexiDB::ConstExpr *constExpr;
+ KexiDB::QuerySchema *querySchema;
+ SelectOptionsInternal *selectOptions;
+ OrderByColumnInternal::List *orderByColumns;
+ QVariant *variantValue;
+}
+
+//%left '=' NOT_EQUAL '>' GREATER_OR_EQUAL '<' LESS_OR_EQUAL LIKE '%' NOT
+//%left '+' '-'
+//%left ASTERISK SLASH
+
+/* precedence: lowest to highest */
+%left UNION EXCEPT
+%left INTERSECT
+%left OR
+%left AND XOR
+%right NOT
+//%right '='
+//%nonassoc '<' '>'
+//%nonassoc '=' '<' '>' "<=" ">=" "<>" ":=" LIKE ILIKE SIMILAR
+//%nonassoc '=' LESS_THAN GREATER_THAN LESS_OR_EQUAL GREATER_OR_EQUAL NOT_EQUAL
+%nonassoc '=' '<' '>'
+//LESS_THAN GREATER_THAN
+%nonassoc LESS_OR_EQUAL GREATER_OR_EQUAL
+%nonassoc NOT_EQUAL NOT_EQUAL2
+%nonassoc SQL_IN LIKE ILIKE SIMILAR_TO NOT_SIMILAR_TO
+//%nonassoc LIKE ILIKE SIMILAR
+//%nonassoc ESCAPE
+//%nonassoc OVERLAPS
+%nonassoc BETWEEN
+//%nonassoc IN_P
+//%left POSTFIXOP // dummy for postfix Op rules
+//%left Op OPERATOR // multi-character ops and user-defined operators
+//%nonassoc NOTNULL
+//%nonassoc ISNULL
+//%nonassoc IS NULL_P TRUE_P FALSE_P UNKNOWN // sets precedence for IS NULL, etc
+%left '+' '-'
+%left '*' '/' '%'
+%left '^'
+%left UMINUS
+// Unary Operators
+//%left AT ZONE // sets precedence for AT TIME ZONE
+//%right UMINUS
+%left '[' ']'
+%left '(' ')'
+//%left TYPECAST
+%left '.'
+
+/*
+ * These might seem to be low-precedence, but actually they are not part
+ * of the arithmetic hierarchy at all in their use as JOIN operators.
+ * We make them high-precedence to support their use as function names.
+ * They wouldn't be given a precedence at all, were it not that we need
+ * left-associativity among the JOIN rules themselves.
+ */
+/*%left JOIN UNIONJOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
+*/
+%%
+
+TopLevelStatement :
+StatementList
+{
+//todo: multiple statements
+//todo: not only "select" statements
+ parser->setOperation(Parser::OP_Select);
+ parser->setQuerySchema($1);
+}
+;
+
+StatementList:
+Statement ';' StatementList
+{
+//todo: multiple statements
+}
+| Statement
+| Statement ';'
+{
+ $$ = $1;
+}
+;
+
+/* Statement CreateTableStatement { YYACCEPT; }
+ | Statement SelectStatement { }
+*/
+Statement :
+CreateTableStatement
+{
+YYACCEPT;
+}
+| SelectStatement
+{
+ $$ = $1;
+}
+;
+
+CreateTableStatement :
+CREATE TABLE IDENTIFIER
+{
+ parser->setOperation(Parser::OP_CreateTable);
+ parser->createTable($3->latin1());
+ delete $3;
+}
+'(' ColDefs ')'
+;
+
+ColDefs:
+ColDefs ',' ColDef|ColDef
+{
+}
+;
+
+ColDef:
+IDENTIFIER ColType
+{
+ KexiDBDbg << "adding field " << *$1 << endl;
+ field->setName($1->latin1());
+ parser->table()->addField(field);
+ field = 0;
+ delete $1;
+}
+| IDENTIFIER ColType ColKeys
+{
+ KexiDBDbg << "adding field " << *$1 << endl;
+ field->setName(*$1);
+ delete $1;
+ parser->table()->addField(field);
+
+// if(field->isPrimaryKey())
+// parser->table()->addPrimaryKey(field->name());
+
+// delete field;
+// field = 0;
+}
+;
+
+ColKeys:
+ColKeys ColKey|ColKey
+{
+}
+;
+
+ColKey:
+PRIMARY KEY
+{
+ field->setPrimaryKey(true);
+ KexiDBDbg << "primary" << endl;
+}
+| NOT SQL_NULL
+{
+ field->setNotNull(true);
+ KexiDBDbg << "not_null" << endl;
+}
+| AUTO_INCREMENT
+{
+ field->setAutoIncrement(true);
+ KexiDBDbg << "ainc" << endl;
+}
+;
+
+ColType:
+SQL_TYPE
+{
+ field = new Field();
+ field->setType($1);
+}
+| SQL_TYPE '(' INTEGER_CONST ')'
+{
+ KexiDBDbg << "sql + length" << endl;
+ field = new Field();
+ field->setPrecision($3);
+ field->setType($1);
+}
+| VARCHAR '(' INTEGER_CONST ')'
+{
+ field = new Field();
+ field->setPrecision($3);
+ field->setType(Field::Text);
+}
+|
+{
+ // SQLITE compatibillity
+ field = new Field();
+ field->setType(Field::InvalidType);
+}
+;
+
+SelectStatement:
+Select ColViews
+{
+ KexiDBDbg << "Select ColViews=" << $2->debugString() << endl;
+
+ if (!($$ = buildSelectQuery( $1, $2 )))
+ return 0;
+}
+| Select ColViews Tables
+{
+ if (!($$ = buildSelectQuery( $1, $2, $3 )))
+ return 0;
+}
+| Select Tables
+{
+ KexiDBDbg << "Select ColViews Tables" << endl;
+ if (!($$ = buildSelectQuery( $1, 0, $2 )))
+ return 0;
+}
+| Select ColViews SelectOptions
+{
+ KexiDBDbg << "Select ColViews Conditions" << endl;
+ if (!($$ = buildSelectQuery( $1, $2, 0, $3 )))
+ return 0;
+}
+| Select ColViews Tables SelectOptions
+{
+ KexiDBDbg << "Select ColViews Tables SelectOptions" << endl;
+ if (!($$ = buildSelectQuery( $1, $2, $3, $4 )))
+ return 0;
+}
+;
+
+Select:
+SELECT
+{
+ KexiDBDbg << "SELECT" << endl;
+// parser->createSelect();
+// parser->setOperation(Parser::OP_Select);
+ $$ = new QuerySchema();
+}
+;
+
+SelectOptions: /* todo: more options (having, group by, limit...) */
+WhereClause
+{
+ KexiDBDbg << "WhereClause" << endl;
+ $$ = new SelectOptionsInternal;
+ $$->whereExpr = $1;
+}
+| ORDER BY OrderByClause
+{
+ KexiDBDbg << "OrderByClause" << endl;
+ $$ = new SelectOptionsInternal;
+ $$->orderByColumns = $3;
+}
+| WhereClause ORDER BY OrderByClause
+{
+ KexiDBDbg << "WhereClause ORDER BY OrderByClause" << endl;
+ $$ = new SelectOptionsInternal;
+ $$->whereExpr = $1;
+ $$->orderByColumns = $4;
+}
+| ORDER BY OrderByClause WhereClause
+{
+ KexiDBDbg << "OrderByClause WhereClause" << endl;
+ $$ = new SelectOptionsInternal;
+ $$->whereExpr = $4;
+ $$->orderByColumns = $3;
+}
+;
+
+WhereClause:
+WHERE aExpr
+{
+ $$ = $2;
+}
+;
+
+/* todo: support "ORDER BY NULL" as described here http://dev.mysql.com/doc/refman/5.1/en/select.html */
+/* todo: accept expr and position as well */
+OrderByClause:
+OrderByColumnId
+{
+ KexiDBDbg << "ORDER BY IDENTIFIER" << endl;
+ $$ = new OrderByColumnInternal::List;
+ OrderByColumnInternal orderByColumn;
+ orderByColumn.setColumnByNameOrNumber( *$1 );
+ $$->append( orderByColumn );
+ delete $1;
+}
+| OrderByColumnId OrderByOption
+{
+ KexiDBDbg << "ORDER BY IDENTIFIER OrderByOption" << endl;
+ $$ = new OrderByColumnInternal::List;
+ OrderByColumnInternal orderByColumn;
+ orderByColumn.setColumnByNameOrNumber( *$1 );
+ orderByColumn.ascending = $2;
+ $$->append( orderByColumn );
+ delete $1;
+}
+| OrderByColumnId ',' OrderByClause
+{
+ $$ = $3;
+ OrderByColumnInternal orderByColumn;
+ orderByColumn.setColumnByNameOrNumber( *$1 );
+ $$->append( orderByColumn );
+ delete $1;
+}
+| OrderByColumnId OrderByOption ',' OrderByClause
+{
+ $$ = $4;
+ OrderByColumnInternal orderByColumn;
+ orderByColumn.setColumnByNameOrNumber( *$1 );
+ orderByColumn.ascending = $2;
+ $$->append( orderByColumn );
+ delete $1;
+}
+;
+
+OrderByColumnId:
+IDENTIFIER
+{
+ $$ = new QVariant( *$1 );
+ KexiDBDbg << "OrderByColumnId: " << *$$ << endl;
+ delete $1;
+}
+| IDENTIFIER '.' IDENTIFIER
+{
+ $$ = new QVariant( *$1 + "." + *$3 );
+ KexiDBDbg << "OrderByColumnId: " << *$$ << endl;
+ delete $1;
+ delete $3;
+}
+| INTEGER_CONST
+{
+ $$ = new QVariant($1);
+ KexiDBDbg << "OrderByColumnId: " << *$$ << endl;
+}
+
+OrderByOption:
+ASC
+{
+ $$ = true;
+}
+| DESC
+{
+ $$ = false;
+}
+;
+
+aExpr:
+aExpr2
+;
+
+/* --- binary logical --- */
+aExpr2:
+aExpr3 AND aExpr2
+{
+// KexiDBDbg << "AND " << $3.debugString() << endl;
+ $$ = new BinaryExpr( KexiDBExpr_Logical, $1, AND, $3 );
+}
+| aExpr3 OR aExpr2
+{
+ $$ = new BinaryExpr( KexiDBExpr_Logical, $1, OR, $3 );
+}
+| aExpr3 XOR aExpr2
+{
+ $$ = new BinaryExpr( KexiDBExpr_Arithm, $1, XOR, $3 );
+}
+|
+aExpr3
+;
+
+/* relational op precedence */
+aExpr3:
+aExpr4 '>' %prec GREATER_OR_EQUAL aExpr3
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, '>', $3);
+}
+| aExpr4 GREATER_OR_EQUAL aExpr3
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, GREATER_OR_EQUAL, $3);
+}
+| aExpr4 '<' %prec LESS_OR_EQUAL aExpr3
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, '<', $3);
+}
+| aExpr4 LESS_OR_EQUAL aExpr3
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, LESS_OR_EQUAL, $3);
+}
+| aExpr4 '=' aExpr3
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, '=', $3);
+}
+|
+aExpr4
+;
+
+/* relational (equality) op precedence */
+aExpr4:
+aExpr5 NOT_EQUAL aExpr4
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, NOT_EQUAL, $3);
+}
+|
+aExpr5 NOT_EQUAL2 aExpr4
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, NOT_EQUAL2, $3);
+}
+| aExpr5 LIKE aExpr4
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, LIKE, $3);
+}
+| aExpr5 SQL_IN aExpr4
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, SQL_IN, $3);
+}
+| aExpr5 SIMILAR_TO aExpr4
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, SIMILAR_TO, $3);
+}
+| aExpr5 NOT_SIMILAR_TO aExpr4
+{
+ $$ = new BinaryExpr(KexiDBExpr_Relational, $1, NOT_SIMILAR_TO, $3);
+}
+|
+aExpr5
+;
+
+/* --- unary logical right --- */
+aExpr5:
+aExpr5 SQL_IS_NULL
+{
+ $$ = new UnaryExpr( SQL_IS_NULL, $1 );
+}
+| aExpr5 SQL_IS_NOT_NULL
+{
+ $$ = new UnaryExpr( SQL_IS_NOT_NULL, $1 );
+}
+|
+aExpr6
+;
+
+/* arithm. lowest precedence */
+aExpr6:
+aExpr7 BITWISE_SHIFT_LEFT aExpr6
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, BITWISE_SHIFT_LEFT, $3);
+}
+| aExpr7 BITWISE_SHIFT_RIGHT aExpr6
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, BITWISE_SHIFT_RIGHT, $3);
+}
+|
+aExpr7
+;
+
+/* arithm. lower precedence */
+aExpr7:
+aExpr8 '+' aExpr7
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '+', $3);
+ $$->debug();
+}
+| aExpr8 '-' %prec UMINUS aExpr7
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '-', $3);
+}
+| aExpr8 '&' aExpr7
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '&', $3);
+}
+| aExpr8 '|' aExpr7
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '|', $3);
+}
+|
+aExpr8
+;
+
+/* arithm. higher precedence */
+aExpr8:
+aExpr9 '/' aExpr8
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '/', $3);
+}
+| aExpr9 '*' aExpr8
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '*', $3);
+}
+| aExpr9 '%' aExpr8
+{
+ $$ = new BinaryExpr(KexiDBExpr_Arithm, $1, '%', $3);
+}
+|
+aExpr9
+;
+
+/* parenthesis, unary operators, and terminals precedence */
+aExpr9:
+/* --- unary logical left --- */
+'-' aExpr9
+{
+ $$ = new UnaryExpr( '-', $2 );
+}
+| '+' aExpr9
+{
+ $$ = new UnaryExpr( '+', $2 );
+}
+| '~' aExpr9
+{
+ $$ = new UnaryExpr( '~', $2 );
+}
+| NOT aExpr9
+{
+ $$ = new UnaryExpr( NOT, $2 );
+}
+| IDENTIFIER
+{
+ $$ = new VariableExpr( *$1 );
+
+//TODO: simplify this later if that's 'only one field name' expression
+ KexiDBDbg << " + identifier: " << *$1 << endl;
+ delete $1;
+}
+| QUERY_PARAMETER
+{
+ $$ = new QueryParameterExpr( *$1 );
+ KexiDBDbg << " + query parameter: " << $$->debugString() << endl;
+ delete $1;
+}
+| IDENTIFIER aExprList
+{
+ KexiDBDbg << " + function: " << *$1 << "(" << $2->debugString() << ")" << endl;
+ $$ = new FunctionExpr(*$1, $2);
+ delete $1;
+}
+/*TODO: shall we also support db name? */
+| IDENTIFIER '.' IDENTIFIER
+{
+ $$ = new VariableExpr( *$1 + "." + *$3 );
+ KexiDBDbg << " + identifier.identifier: " << *$1 << "." << *$3 << endl;
+ delete $1;
+ delete $3;
+}
+| SQL_NULL
+{
+ $$ = new ConstExpr( SQL_NULL, QVariant() );
+ KexiDBDbg << " + NULL" << endl;
+// $$ = new Field();
+ //$$->setName(QString::null);
+}
+| CHARACTER_STRING_LITERAL
+{
+ $$ = new ConstExpr( CHARACTER_STRING_LITERAL, *$1 );
+ KexiDBDbg << " + constant " << $1 << endl;
+ delete $1;
+}
+| INTEGER_CONST
+{
+ QVariant val;
+ if ($1 <= INT_MAX && $1 >= INT_MIN)
+ val = (int)$1;
+ else if ($1 <= UINT_MAX && $1 >= 0)
+ val = (uint)$1;
+ else if ($1 <= (Q_LLONG)LLONG_MAX && $1 >= (Q_LLONG)LLONG_MIN)
+ val = (Q_LLONG)$1;
+
+// if ($1 < ULLONG_MAX)
+// val = (Q_ULLONG)$1;
+//TODO ok?
+
+ $$ = new ConstExpr( INTEGER_CONST, val );
+ KexiDBDbg << " + int constant: " << val.toString() << endl;
+}
+| REAL_CONST
+{
+ $$ = new ConstExpr( REAL_CONST, QPoint( $1.integer, $1.fractional ) );
+ KexiDBDbg << " + real constant: " << $1.integer << "." << $1.fractional << endl;
+}
+|
+aExpr10
+;
+
+
+aExpr10:
+'(' aExpr ')'
+{
+ KexiDBDbg << "(expr)" << endl;
+ $$ = new UnaryExpr('(', $2);
+}
+;
+
+aExprList:
+'(' aExprList2 ')'
+{
+// $$ = new NArgExpr(0, 0);
+// $$->add( $1 );
+// $$->add( $3 );
+ $$ = $2;
+}
+;
+
+aExprList2:
+aExpr ',' aExprList2
+{
+ $$ = $3;
+ $$->prepend( $1 );
+}
+| aExpr ',' aExpr
+{
+ $$ = new NArgExpr(0, 0);
+ $$->add( $1 );
+ $$->add( $3 );
+}
+;
+
+Tables:
+FROM FlatTableList
+{
+ $$ = $2;
+}
+/*
+| Tables LEFT JOIN IDENTIFIER SQL_ON ColExpression
+{
+ KexiDBDbg << "LEFT JOIN: '" << *$4 << "' ON " << $6 << endl;
+ addTable($4->toQString());
+ delete $4;
+}
+| Tables LEFT OUTER JOIN IDENTIFIER SQL_ON ColExpression
+{
+ KexiDBDbg << "LEFT OUTER JOIN: '" << $5 << "' ON " << $7 << endl;
+ addTable($5);
+}
+| Tables INNER JOIN IDENTIFIER SQL_ON ColExpression
+{
+ KexiDBDbg << "INNER JOIN: '" << *$4 << "' ON " << $6 << endl;
+ addTable($4->toQString());
+ delete $4;
+}
+| Tables RIGHT JOIN IDENTIFIER SQL_ON ColExpression
+{
+ KexiDBDbg << "RIGHT JOIN: '" << *$4 << "' ON " << $6 << endl;
+ addTable(*$4);
+ delete $4;
+}
+| Tables RIGHT OUTER JOIN IDENTIFIER SQL_ON ColExpression
+{
+ KexiDBDbg << "RIGHT OUTER JOIN: '" << *$5 << "' ON " << $7 << endl;
+ addTable($5->toQString());
+ delete $5;
+}*/
+;
+
+/*
+FlatTableList:
+aFlatTableList
+{
+ $$
+}
+;*/
+
+FlatTableList:
+FlatTableList ',' FlatTable
+{
+ $$ = $1;
+ $$->add($3);
+}
+|FlatTable
+{
+ $$ = new NArgExpr(KexiDBExpr_TableList, IDENTIFIER); //ok?
+ $$->add($1);
+}
+;
+
+FlatTable:
+IDENTIFIER
+{
+ KexiDBDbg << "FROM: '" << *$1 << "'" << endl;
+ $$ = new VariableExpr(*$1);
+
+ /*
+//TODO: this isn't ok for more tables:
+ Field::ListIterator it = parser->select()->fieldsIterator();
+ for(Field *item; (item = it.current()); ++it)
+ {
+ if(item->table() == dummy)
+ {
+ item->setTable(schema);
+ }
+
+ if(item->table() && !item->isQueryAsterisk())
+ {
+ Field *f = item->table()->field(item->name());
+ if(!f)
+ {
+ ParserError err(i18n("Field List Error"), i18n("Unknown column '%1' in table '%2'").arg(item->name()).arg(schema->name()), ctoken, current);
+ parser->setError(err);
+ yyerror("fieldlisterror");
+ }
+ }
+ }*/
+ delete $1;
+}
+| IDENTIFIER IDENTIFIER
+{
+ //table + alias
+ $$ = new BinaryExpr(
+ KexiDBExpr_SpecialBinary,
+ new VariableExpr(*$1), 0,
+ new VariableExpr(*$2)
+ );
+ delete $1;
+ delete $2;
+}
+| IDENTIFIER AS IDENTIFIER
+{
+ //table + alias
+ $$ = new BinaryExpr(
+ KexiDBExpr_SpecialBinary,
+ new VariableExpr(*$1), AS,
+ new VariableExpr(*$3)
+ );
+ delete $1;
+ delete $3;
+}
+;
+
+
+
+ColViews:
+ColViews ',' ColItem
+{
+ $$ = $1;
+ $$->add( $3 );
+ KexiDBDbg << "ColViews: ColViews , ColItem" << endl;
+}
+|ColItem
+{
+ $$ = new NArgExpr(0,0);
+ $$->add( $1 );
+ KexiDBDbg << "ColViews: ColItem" << endl;
+}
+;
+
+ColItem:
+ColExpression
+{
+// $$ = new Field();
+// dummy->addField($$);
+// $$->setExpression( $1 );
+// parser->select()->addField($$);
+ $$ = $1;
+ KexiDBDbg << " added column expr: '" << $1->debugString() << "'" << endl;
+}
+| ColWildCard
+{
+ $$ = $1;
+ KexiDBDbg << " added column wildcard: '" << $1->debugString() << "'" << endl;
+}
+| ColExpression AS IDENTIFIER
+{
+ $$ = new BinaryExpr(
+ KexiDBExpr_SpecialBinary, $1, AS,
+ new VariableExpr(*$3)
+ );
+ KexiDBDbg << " added column expr: " << $$->debugString() << endl;
+ delete $3;
+}
+| ColExpression IDENTIFIER
+{
+ $$ = new BinaryExpr(
+ KexiDBExpr_SpecialBinary, $1, 0,
+ new VariableExpr(*$2)
+ );
+ KexiDBDbg << " added column expr: " << $$->debugString() << endl;
+ delete $2;
+}
+;
+
+ColExpression:
+aExpr
+{
+ $$ = $1;
+}
+/* HANDLED BY 'IDENTIFIER aExprList'
+| IDENTIFIER '(' ColViews ')'
+{
+ $$ = new FunctionExpr( $1, $3 );
+}*/
+/*
+| SUM '(' ColExpression ')'
+{
+ FunctionExpr(
+// $$ = new AggregationExpr( SUM, );
+//TODO
+// $$->setName("SUM(" + $3->name() + ")");
+//wait $$->containsGroupingAggregate(true);
+//wait parser->select()->grouped(true);
+}
+| SQL_MIN '(' ColExpression ')'
+{
+ $$ = $3;
+//TODO
+// $$->setName("MIN(" + $3->name() + ")");
+//wait $$->containsGroupingAggregate(true);
+//wait parser->select()->grouped(true);
+}
+| SQL_MAX '(' ColExpression ')'
+{
+ $$ = $3;
+//TODO
+// $$->setName("MAX(" + $3->name() + ")");
+//wait $$->containsGroupingAggregate(true);
+//wait parser->select()->grouped(true);
+}
+| AVG '(' ColExpression ')'
+{
+ $$ = $3;
+//TODO
+// $$->setName("AVG(" + $3->name() + ")");
+//wait $$->containsGroupingAggregate(true);
+//wait parser->select()->grouped(true);
+}*/
+//?
+| DISTINCT '(' ColExpression ')'
+{
+ $$ = $3;
+//TODO
+// $$->setName("DISTINCT(" + $3->name() + ")");
+}
+;
+
+ColWildCard:
+'*'
+{
+ $$ = new VariableExpr("*");
+ KexiDBDbg << "all columns" << endl;
+
+// QueryAsterisk *ast = new QueryAsterisk(parser->select(), dummy);
+// parser->select()->addAsterisk(ast);
+// requiresTable = true;
+}
+| IDENTIFIER '.' '*'
+{
+ QString s( *$1 );
+ s += ".*";
+ $$ = new VariableExpr(s);
+ KexiDBDbg << " + all columns from " << s << endl;
+ delete $1;
+}
+/*| ERROR_DIGIT_BEFORE_IDENTIFIER
+{
+ $$ = new VariableExpr($1);
+ KexiDBDbg << " Invalid identifier! " << $1 << endl;
+ setError(i18n("Invalid identifier \"%1\"").arg($1));
+}*/
+;
+
+%%
+
diff --git a/kexi/kexidb/parser/sqlscanner.cpp b/kexi/kexidb/parser/sqlscanner.cpp
new file mode 100644
index 000000000..c3984a39d
--- /dev/null
+++ b/kexi/kexidb/parser/sqlscanner.cpp
@@ -0,0 +1,2051 @@
+#line 2 "sqlscanner.cpp"
+/* A lexical scanner generated by flex */
+
+/* Scanner skeleton version:
+ * $Header: /home/daffy/u0/vern/flex/RCS/flex.skl,v 2.91 96/09/10 16:58:48 vern Exp $
+ */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+
+#include <stdio.h>
+#include <errno.h>
+
+/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */
+#ifdef c_plusplus
+#ifndef __cplusplus
+#define __cplusplus
+#endif
+#endif
+
+
+#ifdef __cplusplus
+
+#include <stdlib.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+/* Use prototypes in function declarations. */
+#define YY_USE_PROTOS
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else /* ! __cplusplus */
+
+#if __STDC__
+
+#define YY_USE_PROTOS
+#define YY_USE_CONST
+
+#endif /* __STDC__ */
+#endif /* ! __cplusplus */
+
+#ifdef __TURBOC__
+ #pragma warn -rch
+ #pragma warn -use
+#include <io.h>
+#include <stdlib.h>
+#define YY_USE_CONST
+#define YY_USE_PROTOS
+#endif
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+
+#ifdef YY_USE_PROTOS
+#define YY_PROTO(proto) proto
+#else
+#define YY_PROTO(proto) ()
+#endif
+
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index. If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN yy_start = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state. The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START ((yy_start - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart( yyin )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#define YY_BUF_SIZE 16384
+
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+
+extern int yyleng;
+extern FILE *yyin, *yyout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+/* The funky do-while in the following #define is used to turn the definition
+ * int a single C statement (which needs a semi-colon terminator). This
+ * avoids problems with code like:
+ *
+ * if ( condition_holds )
+ * yyless( 5 );
+ * else
+ * do_something_else();
+ *
+ * Prior to using the do-while the compiler would get upset at the
+ * "else" because it interpreted the "if" statement as being all
+ * done when it reached the ';' after the yyless() call.
+ */
+
+/* Return all but the first 'n' matched characters back to the input stream. */
+
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ *yy_cp = yy_hold_char; \
+ YY_RESTORE_YY_MORE_OFFSET \
+ yy_c_buf_p = yy_cp = yy_bp + n - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+
+#define unput(c) yyunput( c, yytext_ptr )
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+typedef unsigned int yy_size_t;
+
+
+struct yy_buffer_state
+ {
+ FILE *yy_input_file;
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ yy_size_t yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+ /* When an EOF's been seen but there's still some text to process
+ * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+ * shouldn't try reading from the input source any more. We might
+ * still have a bunch of tokens to match, though, because of
+ * possible backing-up.
+ *
+ * When we actually see the EOF, we change the status to "new"
+ * (via yyrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
+ */
+#define YY_BUFFER_EOF_PENDING 2
+ };
+
+static YY_BUFFER_STATE yy_current_buffer = 0;
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ */
+#define YY_CURRENT_BUFFER yy_current_buffer
+
+
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+
+static int yy_n_chars; /* number of characters read into yy_ch_buf */
+
+
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = (char *) 0;
+static int yy_init = 1; /* whether we need to initialize */
+static int yy_start = 0; /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+void yyrestart YY_PROTO(( FILE *input_file ));
+
+void yy_switch_to_buffer YY_PROTO(( YY_BUFFER_STATE new_buffer ));
+void yy_load_buffer_state YY_PROTO(( void ));
+YY_BUFFER_STATE yy_create_buffer YY_PROTO(( FILE *file, int size ));
+void yy_delete_buffer YY_PROTO(( YY_BUFFER_STATE b ));
+void yy_init_buffer YY_PROTO(( YY_BUFFER_STATE b, FILE *file ));
+void yy_flush_buffer YY_PROTO(( YY_BUFFER_STATE b ));
+#define YY_FLUSH_BUFFER yy_flush_buffer( yy_current_buffer )
+
+YY_BUFFER_STATE yy_scan_buffer YY_PROTO(( char *base, yy_size_t size ));
+YY_BUFFER_STATE yy_scan_string YY_PROTO(( yyconst char *yy_str ));
+YY_BUFFER_STATE yy_scan_bytes YY_PROTO(( yyconst char *bytes, int len ));
+
+static void *yy_flex_alloc YY_PROTO(( yy_size_t ));
+static void *yy_flex_realloc YY_PROTO(( void *, yy_size_t ));
+static void yy_flex_free YY_PROTO(( void * ));
+
+#define yy_new_buffer yy_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+ { \
+ if ( ! yy_current_buffer ) \
+ yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ yy_current_buffer->yy_is_interactive = is_interactive; \
+ }
+
+#define yy_set_bol(at_bol) \
+ { \
+ if ( ! yy_current_buffer ) \
+ yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ yy_current_buffer->yy_at_bol = at_bol; \
+ }
+
+#define YY_AT_BOL() (yy_current_buffer->yy_at_bol)
+
+
+#define yywrap() 1
+#define YY_SKIP_YYWRAP
+typedef unsigned char YY_CHAR;
+FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0;
+typedef int yy_state_type;
+extern char *yytext;
+#define yytext_ptr yytext
+
+static yy_state_type yy_get_previous_state YY_PROTO(( void ));
+static yy_state_type yy_try_NUL_trans YY_PROTO(( yy_state_type current_state ));
+static int yy_get_next_buffer YY_PROTO(( void ));
+static void yy_fatal_error YY_PROTO(( yyconst char msg[] ));
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ yytext_ptr = yy_bp; \
+ yyleng = (int) (yy_cp - yy_bp); \
+ yy_hold_char = *yy_cp; \
+ *yy_cp = '\0'; \
+ yy_c_buf_p = yy_cp;
+
+#define YY_NUM_RULES 43
+#define YY_END_OF_BUFFER 44
+static yyconst short int yy_accept[148] =
+ { 0,
+ 0, 0, 44, 43, 41, 42, 43, 42, 42, 43,
+ 42, 7, 42, 42, 42, 42, 39, 39, 39, 39,
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 42, 41, 2, 0, 38, 9, 0, 8,
+ 8, 7, 39, 27, 4, 1, 3, 5, 28, 0,
+ 39, 10, 35, 39, 39, 39, 6, 22, 39, 39,
+ 39, 39, 39, 24, 25, 39, 39, 39, 39, 39,
+ 39, 26, 8, 40, 9, 36, 39, 39, 39, 39,
+ 0, 39, 39, 39, 21, 39, 39, 39, 39, 39,
+ 39, 39, 29, 39, 37, 12, 39, 0, 14, 15,
+
+ 16, 0, 23, 39, 39, 39, 39, 39, 39, 39,
+ 39, 0, 0, 0, 34, 30, 39, 39, 32, 33,
+ 11, 39, 0, 0, 0, 31, 39, 13, 0, 20,
+ 0, 39, 0, 0, 0, 0, 0, 0, 0, 0,
+ 18, 19, 0, 0, 0, 17, 0
+ } ;
+
+static yyconst int yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 2, 3, 4, 5, 1, 5, 6, 7, 5,
+ 5, 5, 5, 5, 5, 8, 5, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 5, 5, 10,
+ 11, 12, 5, 5, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 15,
+ 15, 31, 32, 33, 34, 15, 35, 36, 37, 15,
+ 13, 1, 14, 5, 15, 5, 16, 17, 18, 19,
+
+ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 15, 15, 31, 32, 33, 34, 15, 35, 36,
+ 37, 15, 1, 38, 1, 5, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1
+ } ;
+
+static yyconst int yy_meta[39] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 2, 2, 1,
+ 1, 1, 3, 3, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 1
+ } ;
+
+static yyconst short int yy_base[152] =
+ { 0,
+ 0, 0, 254, 255, 251, 241, 247, 255, 244, 242,
+ 239, 31, 31, 236, 33, 0, 238, 38, 39, 40,
+ 41, 42, 43, 44, 45, 47, 49, 55, 74, 46,
+ 60, 56, 207, 242, 255, 239, 255, 255, 235, 232,
+ 231, 50, 231, 255, 255, 255, 255, 255, 255, 224,
+ 76, 48, 229, 77, 79, 80, 81, 88, 83, 84,
+ 91, 85, 92, 228, 93, 94, 95, 96, 98, 100,
+ 101, 255, 226, 255, 226, 225, 105, 115, 117, 118,
+ 125, 120, 122, 121, 129, 126, 127, 128, 132, 134,
+ 135, 136, 224, 131, 223, 222, 138, 127, 221, 220,
+
+ 219, 146, 218, 142, 148, 151, 155, 157, 160, 163,
+ 164, 192, 188, 190, 205, 204, 158, 171, 203, 202,
+ 201, 162, 206, 179, 177, 196, 166, 195, 173, 255,
+ 177, 184, 166, 172, 174, 171, 180, 165, 167, 159,
+ 255, 255, 187, 183, 158, 255, 255, 216, 219, 58,
+ 222
+ } ;
+
+static yyconst short int yy_def[152] =
+ { 0,
+ 147, 1, 147, 147, 147, 147, 148, 147, 147, 149,
+ 147, 150, 147, 147, 147, 151, 150, 150, 150, 150,
+ 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
+ 150, 150, 147, 147, 147, 148, 147, 147, 149, 147,
+ 147, 150, 150, 147, 147, 147, 147, 147, 147, 151,
+ 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
+ 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
+ 150, 147, 147, 147, 150, 150, 150, 150, 150, 150,
+ 147, 150, 150, 150, 150, 150, 150, 150, 150, 150,
+ 150, 150, 150, 150, 150, 150, 150, 147, 150, 150,
+
+ 150, 147, 150, 150, 150, 150, 150, 150, 150, 150,
+ 150, 147, 147, 147, 150, 150, 150, 150, 150, 150,
+ 150, 150, 147, 147, 147, 150, 150, 150, 147, 147,
+ 147, 150, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 0, 147, 147, 147,
+ 147
+ } ;
+
+static yyconst short int yy_nxt[294] =
+ { 0,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 8, 17, 18, 19, 20, 21, 17,
+ 22, 17, 17, 23, 24, 17, 25, 17, 26, 27,
+ 28, 29, 30, 17, 31, 32, 17, 33, 41, 42,
+ 44, 45, 46, 48, 49, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 41, 42, 43,
+ 55, 69, 147, 147, 60, 76, 51, 147, 61, 52,
+ 54, 57, 56, 59, 58, 53, 62, 64, 66, 65,
+ 63, 147, 70, 147, 147, 71, 147, 147, 147, 81,
+ 147, 147, 147, 67, 75, 147, 77, 68, 147, 147,
+
+ 147, 147, 147, 147, 83, 147, 82, 147, 147, 79,
+ 78, 87, 147, 80, 91, 88, 84, 85, 86, 92,
+ 94, 89, 147, 90, 147, 147, 81, 147, 147, 147,
+ 102, 93, 95, 147, 147, 147, 147, 97, 147, 147,
+ 101, 147, 147, 147, 96, 147, 104, 102, 99, 147,
+ 105, 106, 103, 98, 100, 147, 112, 107, 147, 111,
+ 113, 108, 147, 110, 147, 147, 109, 147, 117, 147,
+ 147, 147, 115, 147, 129, 135, 119, 114, 147, 120,
+ 116, 118, 121, 122, 144, 135, 127, 146, 144, 143,
+ 126, 147, 128, 142, 141, 140, 132, 139, 137, 136,
+
+ 134, 133, 147, 147, 131, 130, 138, 129, 147, 147,
+ 147, 147, 147, 125, 124, 145, 36, 36, 36, 39,
+ 39, 39, 50, 50, 123, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 73, 147, 147, 74, 147, 73,
+ 40, 37, 37, 34, 72, 147, 47, 40, 37, 38,
+ 37, 35, 34, 147, 3, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147
+
+ } ;
+
+static yyconst short int yy_chk[294] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 12, 12,
+ 13, 13, 13, 15, 15, 18, 19, 20, 21, 22,
+ 23, 24, 25, 30, 26, 52, 27, 42, 42, 150,
+ 21, 30, 28, 32, 25, 52, 18, 31, 25, 18,
+ 20, 23, 22, 24, 23, 19, 26, 27, 28, 27,
+ 26, 29, 31, 51, 54, 32, 55, 56, 57, 58,
+ 59, 60, 62, 29, 51, 58, 54, 29, 61, 63,
+
+ 65, 66, 67, 68, 60, 69, 59, 70, 71, 56,
+ 55, 65, 77, 57, 69, 66, 61, 62, 63, 70,
+ 77, 67, 78, 68, 79, 80, 81, 82, 84, 83,
+ 85, 71, 78, 86, 87, 88, 85, 80, 94, 89,
+ 84, 90, 91, 92, 79, 97, 87, 102, 82, 104,
+ 88, 89, 86, 81, 83, 105, 98, 90, 106, 97,
+ 98, 91, 107, 94, 108, 117, 92, 109, 106, 122,
+ 110, 111, 104, 127, 129, 135, 108, 102, 118, 109,
+ 105, 107, 110, 111, 144, 132, 118, 145, 143, 140,
+ 117, 132, 122, 139, 138, 137, 127, 136, 134, 133,
+
+ 131, 129, 128, 126, 125, 124, 135, 123, 121, 120,
+ 119, 116, 115, 114, 113, 144, 148, 148, 148, 149,
+ 149, 149, 151, 151, 112, 103, 101, 100, 99, 96,
+ 95, 93, 76, 75, 73, 64, 53, 50, 43, 41,
+ 40, 39, 36, 34, 33, 17, 14, 11, 10, 9,
+ 7, 6, 5, 3, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147, 147, 147, 147, 147, 147, 147, 147,
+ 147, 147, 147
+
+ } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "sqlscanner.l"
+#define INITIAL 0
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#line 22 "sqlscanner.l"
+#include <field.h>
+#include <expression.h>
+
+#include "sqlparser.h"
+#include "sqltypes.h"
+#include <iostream>
+#include <kdebug.h>
+#include <klocale.h>
+
+#define YY_NO_UNPUT
+#define ECOUNT current += yyleng; ctoken = yytext
+
+extern void setError(const QString& errDesc);
+extern void setError(const QString& errName, const QString& errDesc);
+
+/* *** Please reflect changes to this file in ../driver_p.cpp *** */
+#define YY_NEVER_INTERACTIVE 1
+/*identifier [a-zA-Z_][a-zA-Z_0-9]* */
+/* quoted_identifier (\"[a-zA-Z_0-9]+\") */
+/* todo: support for real numbers */
+#line 524 "sqlscanner.cpp"
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap YY_PROTO(( void ));
+#else
+extern int yywrap YY_PROTO(( void ));
+#endif
+#endif
+
+#ifndef YY_NO_UNPUT
+static void yyunput YY_PROTO(( int c, char *buf_ptr ));
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy YY_PROTO(( char *, yyconst char *, int ));
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen YY_PROTO(( yyconst char * ));
+#endif
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+static int yyinput YY_PROTO(( void ));
+#else
+static int input YY_PROTO(( void ));
+#endif
+#endif
+
+#if YY_STACK_USED
+static int yy_start_stack_ptr = 0;
+static int yy_start_stack_depth = 0;
+static int *yy_start_stack = 0;
+#ifndef YY_NO_PUSH_STATE
+static void yy_push_state YY_PROTO(( int new_state ));
+#endif
+#ifndef YY_NO_POP_STATE
+static void yy_pop_state YY_PROTO(( void ));
+#endif
+#ifndef YY_NO_TOP_STATE
+static int yy_top_state YY_PROTO(( void ));
+#endif
+
+#else
+#define YY_NO_PUSH_STATE 1
+#define YY_NO_POP_STATE 1
+#define YY_NO_TOP_STATE 1
+#endif
+
+#ifdef YY_MALLOC_DECL
+YY_MALLOC_DECL
+#else
+#if __STDC__
+#ifndef __cplusplus
+#include <stdlib.h>
+#endif
+#else
+/* Just try to get by without declaring the routines. This will fail
+ * miserably on non-ANSI systems for which sizeof(size_t) != sizeof(int)
+ * or sizeof(void*) != sizeof(int).
+ */
+#endif
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+ if ( yy_current_buffer->yy_is_interactive ) \
+ { \
+ int c = '*', n; \
+ for ( n = 0; n < max_size && \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+ buf[n] = (char) c; \
+ if ( c == '\n' ) \
+ buf[n++] = (char) c; \
+ if ( c == EOF && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ result = n; \
+ } \
+ else \
+ { \
+ errno=0; \
+ while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \
+ { \
+ if( errno != EINTR) \
+ { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno=0; \
+ clearerr(yyin); \
+ } \
+ }
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL int yylex YY_PROTO(( void ))
+#endif
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+ YY_USER_ACTION
+
+YY_DECL
+ {
+ register yy_state_type yy_current_state;
+ register char *yy_cp, *yy_bp;
+ register int yy_act;
+
+#line 58 "sqlscanner.l"
+
+
+
+#line 690 "sqlscanner.cpp"
+
+ if ( yy_init )
+ {
+ yy_init = 0;
+
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! yy_start )
+ yy_start = 1; /* first start state */
+
+ if ( ! yyin )
+ yyin = stdin;
+
+ if ( ! yyout )
+ yyout = stdout;
+
+ if ( ! yy_current_buffer )
+ yy_current_buffer =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+
+ yy_load_buffer_state();
+ }
+
+ while ( 1 ) /* loops until end-of-file is reached */
+ {
+ yy_cp = yy_c_buf_p;
+
+ /* Support of yytext. */
+ *yy_cp = yy_hold_char;
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+ yy_current_state = yy_start;
+yy_match:
+ do
+ {
+ register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 148 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ ++yy_cp;
+ }
+ while ( yy_base[yy_current_state] != 255 );
+
+yy_find_action:
+ yy_act = yy_accept[yy_current_state];
+ if ( yy_act == 0 )
+ { /* have to back up */
+ yy_cp = yy_last_accepting_cpos;
+ yy_current_state = yy_last_accepting_state;
+ yy_act = yy_accept[yy_current_state];
+ }
+
+ YY_DO_BEFORE_ACTION;
+
+
+do_action: /* This label is used only to access EOF actions. */
+
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = yy_hold_char;
+ yy_cp = yy_last_accepting_cpos;
+ yy_current_state = yy_last_accepting_state;
+ goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 61 "sqlscanner.l"
+{
+ ECOUNT;
+ return NOT_EQUAL;
+}
+ YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 66 "sqlscanner.l"
+{
+ ECOUNT;
+ return NOT_EQUAL2;
+}
+ YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 71 "sqlscanner.l"
+{
+ ECOUNT;
+ return '=';
+}
+ YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 76 "sqlscanner.l"
+{
+ ECOUNT;
+ return LESS_OR_EQUAL;
+}
+ YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 81 "sqlscanner.l"
+{
+ ECOUNT;
+ return GREATER_OR_EQUAL;
+}
+ YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 86 "sqlscanner.l"
+{
+ ECOUNT;
+ return SQL_IN;
+}
+ YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 91 "sqlscanner.l"
+{
+//TODO: what about hex or octal values?
+ //we're using QString:toLongLong() here because atoll() is not so portable:
+ ECOUNT;
+ bool ok;
+ yylval.integerValue = QString(yytext).toLongLong( &ok );
+ if (!ok) {
+ setError(i18n("Invalid integer number"),i18n("This integer number may be too large."));
+ return SCAN_ERROR;
+ }
+// yylval.integerValue = atol(yytext);
+ return INTEGER_CONST;
+}
+ YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 105 "sqlscanner.l"
+{
+ char *p = yytext;
+ if (yytext[0]=='.') { /* no integer part */
+ yylval.realValue.integer = 0;
+ }
+ else {
+ yylval.realValue.integer = atoi(p);
+ int i=0;
+ while (p && i < yyleng && *p != '.') {
+ i++;
+ p++;
+ }
+ if (i==0 || !p || *p!='.') {
+ yylval.realValue.fractional = 0;
+ return REAL_CONST;
+ }
+ }
+ /* fractional part */
+ p++;
+ yylval.realValue.fractional = atoi(p);
+ return REAL_CONST;
+}
+ YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 128 "sqlscanner.l"
+{
+ ECOUNT;
+ return AND;
+}
+ YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 133 "sqlscanner.l"
+{
+ ECOUNT;
+ return AS;
+}
+ YY_BREAK
+case 11:
+YY_RULE_SETUP
+#line 138 "sqlscanner.l"
+{
+ ECOUNT;
+ return CREATE;
+}
+ YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 143 "sqlscanner.l"
+{
+ ECOUNT;
+ return FROM;
+}
+ YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 148 "sqlscanner.l"
+{
+ ECOUNT;
+ return SQL_TYPE;
+}
+ YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 153 "sqlscanner.l"
+{
+ ECOUNT;
+ return JOIN;
+}
+ YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 158 "sqlscanner.l"
+{
+ ECOUNT;
+ return LEFT;
+}
+ YY_BREAK
+case 16:
+YY_RULE_SETUP
+#line 163 "sqlscanner.l"
+{
+ ECOUNT;
+ return LIKE;
+}
+ YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 168 "sqlscanner.l"
+{
+ ECOUNT;
+ return NOT_SIMILAR_TO;
+}
+ YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 173 "sqlscanner.l"
+{
+ ECOUNT;
+ return SIMILAR_TO;
+}
+ YY_BREAK
+case 19:
+YY_RULE_SETUP
+#line 178 "sqlscanner.l"
+{
+ ECOUNT;
+ return SQL_IS_NOT_NULL;
+}
+ YY_BREAK
+case 20:
+YY_RULE_SETUP
+#line 183 "sqlscanner.l"
+{
+ ECOUNT;
+ return SQL_IS_NULL;
+}
+ YY_BREAK
+case 21:
+YY_RULE_SETUP
+#line 188 "sqlscanner.l"
+{
+ ECOUNT;
+ return NOT;
+}
+ YY_BREAK
+case 22:
+YY_RULE_SETUP
+#line 193 "sqlscanner.l"
+{
+ ECOUNT;
+ return SQL_IS;
+}
+ YY_BREAK
+case 23:
+YY_RULE_SETUP
+#line 198 "sqlscanner.l"
+{
+ ECOUNT;
+ return SQL_NULL;
+}
+ YY_BREAK
+case 24:
+YY_RULE_SETUP
+#line 203 "sqlscanner.l"
+{
+ ECOUNT;
+ return SQL_ON;
+}
+ YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 208 "sqlscanner.l"
+{
+ ECOUNT;
+ return OR;
+}
+ YY_BREAK
+case 26:
+YY_RULE_SETUP
+#line 213 "sqlscanner.l"
+{ /* also means OR for numbers (mysql) */
+ ECOUNT;
+ return CONCATENATION;
+}
+ YY_BREAK
+case 27:
+YY_RULE_SETUP
+#line 218 "sqlscanner.l"
+{
+ ECOUNT;
+ return BITWISE_SHIFT_LEFT;
+}
+ YY_BREAK
+case 28:
+YY_RULE_SETUP
+#line 223 "sqlscanner.l"
+{
+ ECOUNT;
+ return BITWISE_SHIFT_RIGHT;
+}
+ YY_BREAK
+case 29:
+YY_RULE_SETUP
+#line 228 "sqlscanner.l"
+{
+ ECOUNT;
+ return XOR;
+}
+ YY_BREAK
+case 30:
+YY_RULE_SETUP
+#line 233 "sqlscanner.l"
+{
+ ECOUNT;
+ return RIGHT;
+}
+ YY_BREAK
+case 31:
+YY_RULE_SETUP
+#line 238 "sqlscanner.l"
+{
+ ECOUNT;
+ return SELECT;
+}
+ YY_BREAK
+case 32:
+YY_RULE_SETUP
+#line 243 "sqlscanner.l"
+{
+ ECOUNT;
+ return TABLE;
+}
+ YY_BREAK
+case 33:
+YY_RULE_SETUP
+#line 248 "sqlscanner.l"
+{
+ ECOUNT;
+ return WHERE;
+}
+ YY_BREAK
+case 34:
+YY_RULE_SETUP
+#line 253 "sqlscanner.l"
+{
+ ECOUNT;
+ return ORDER;
+}
+ YY_BREAK
+case 35:
+YY_RULE_SETUP
+#line 258 "sqlscanner.l"
+{
+ ECOUNT;
+ return BY;
+}
+ YY_BREAK
+case 36:
+YY_RULE_SETUP
+#line 263 "sqlscanner.l"
+{
+ ECOUNT;
+ return ASC;
+}
+ YY_BREAK
+case 37:
+YY_RULE_SETUP
+#line 268 "sqlscanner.l"
+{
+ ECOUNT;
+ return DESC;
+}
+ YY_BREAK
+case 38:
+YY_RULE_SETUP
+#line 273 "sqlscanner.l"
+{
+ ECOUNT;
+ yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2));
+ return CHARACTER_STRING_LITERAL;
+
+/* "ZZZ" sentinel for script */
+}
+ YY_BREAK
+case 39:
+YY_RULE_SETUP
+#line 281 "sqlscanner.l"
+{
+ KexiDBDbg << "yytext: '" << yytext << "' (" << yyleng << ")" << endl;
+ ECOUNT;
+ yylval.stringValue = new QString(QString::fromUtf8(yytext, yyleng));
+ if (yytext[0]>='0' && yytext[0]<='9') {
+ setError(i18n("Invalid identifier"),
+ i18n("Identifiers should start with a letter or '_' character"));
+ return SCAN_ERROR;
+ }
+ return IDENTIFIER;
+}
+ YY_BREAK
+case 40:
+YY_RULE_SETUP
+#line 293 "sqlscanner.l"
+{
+ KexiDBDbg << "yytext: '" << yytext << "' (" << yyleng << ")" << endl;
+ ECOUNT;
+ yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2));
+ return QUERY_PARAMETER;
+}
+ YY_BREAK
+case 41:
+YY_RULE_SETUP
+#line 300 "sqlscanner.l"
+{
+ ECOUNT;
+}
+ YY_BREAK
+case 42:
+YY_RULE_SETUP
+#line 304 "sqlscanner.l"
+{
+ KexiDBDbg << "char: '" << yytext[0] << "'" << endl;
+ ECOUNT;
+ return yytext[0];
+}
+ YY_BREAK
+case 43:
+YY_RULE_SETUP
+#line 310 "sqlscanner.l"
+ECHO;
+ YY_BREAK
+#line 1153 "sqlscanner.cpp"
+case YY_STATE_EOF(INITIAL):
+ yyterminate();
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = (int) (yy_cp - yytext_ptr) - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = yy_hold_char;
+ YY_RESTORE_YY_MORE_OFFSET
+
+ if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_NEW )
+ {
+ /* We're scanning a new file or input source. It's
+ * possible that this happened because the user
+ * just pointed yyin at a new source and called
+ * yylex(). If so, then we have to assure
+ * consistency between yy_current_buffer and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input source.
+ */
+ yy_n_chars = yy_current_buffer->yy_n_chars;
+ yy_current_buffer->yy_input_file = yyin;
+ yy_current_buffer->yy_buffer_status = YY_BUFFER_NORMAL;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( yy_c_buf_p <= &yy_current_buffer->yy_ch_buf[yy_n_chars] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ yy_c_buf_p = yytext_ptr + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state();
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++yy_c_buf_p;
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+ yy_cp = yy_c_buf_p;
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer() )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ yy_did_buffer_switch_on_eof = 0;
+
+ if ( yywrap() )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ yy_c_buf_p = yytext_ptr + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! yy_did_buffer_switch_on_eof )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ yy_c_buf_p =
+ yytext_ptr + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state();
+
+ yy_cp = yy_c_buf_p;
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ yy_c_buf_p =
+ &yy_current_buffer->yy_ch_buf[yy_n_chars];
+
+ yy_current_state = yy_get_previous_state();
+
+ yy_cp = yy_c_buf_p;
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+ } /* end of yylex */
+
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+
+static int yy_get_next_buffer()
+ {
+ register char *dest = yy_current_buffer->yy_ch_buf;
+ register char *source = yytext_ptr;
+ register int number_to_move, i;
+ int ret_val;
+
+ if ( yy_c_buf_p > &yy_current_buffer->yy_ch_buf[yy_n_chars + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( yy_current_buffer->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( yy_c_buf_p - yytext_ptr - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a single character, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = (int) (yy_c_buf_p - yytext_ptr) - 1;
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ yy_current_buffer->yy_n_chars = yy_n_chars = 0;
+
+ else
+ {
+ int num_to_read =
+ yy_current_buffer->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+#ifdef YY_USES_REJECT
+ YY_FATAL_ERROR(
+"input buffer overflow, can't enlarge buffer because scanner uses REJECT" );
+#else
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = yy_current_buffer;
+
+ int yy_c_buf_p_offset =
+ (int) (yy_c_buf_p - b->yy_ch_buf);
+
+ if ( b->yy_is_our_buffer )
+ {
+ int new_size = b->yy_buf_size * 2;
+
+ if ( new_size <= 0 )
+ b->yy_buf_size += b->yy_buf_size / 8;
+ else
+ b->yy_buf_size *= 2;
+
+ b->yy_ch_buf = (char *)
+ /* Include room in for 2 EOB chars. */
+ yy_flex_realloc( (void *) b->yy_ch_buf,
+ b->yy_buf_size + 2 );
+ }
+ else
+ /* Can't grow it, we don't own it. */
+ b->yy_ch_buf = 0;
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = yy_current_buffer->yy_buf_size -
+ number_to_move - 1;
+#endif
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&yy_current_buffer->yy_ch_buf[number_to_move]),
+ yy_n_chars, num_to_read );
+
+ yy_current_buffer->yy_n_chars = yy_n_chars;
+ }
+
+ if ( yy_n_chars == 0 )
+ {
+ if ( number_to_move == YY_MORE_ADJ )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ yyrestart( yyin );
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ yy_current_buffer->yy_buffer_status =
+ YY_BUFFER_EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ yy_n_chars += number_to_move;
+ yy_current_buffer->yy_ch_buf[yy_n_chars] = YY_END_OF_BUFFER_CHAR;
+ yy_current_buffer->yy_ch_buf[yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR;
+
+ yytext_ptr = &yy_current_buffer->yy_ch_buf[0];
+
+ return ret_val;
+ }
+
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+static yy_state_type yy_get_previous_state()
+ {
+ register yy_state_type yy_current_state;
+ register char *yy_cp;
+
+ yy_current_state = yy_start;
+
+ for ( yy_cp = yytext_ptr + YY_MORE_ADJ; yy_cp < yy_c_buf_p; ++yy_cp )
+ {
+ register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 148 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ }
+
+ return yy_current_state;
+ }
+
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+
+#ifdef YY_USE_PROTOS
+static yy_state_type yy_try_NUL_trans( yy_state_type yy_current_state )
+#else
+static yy_state_type yy_try_NUL_trans( yy_current_state )
+yy_state_type yy_current_state;
+#endif
+ {
+ register int yy_is_jam;
+ register char *yy_cp = yy_c_buf_p;
+
+ register YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 148 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ yy_is_jam = (yy_current_state == 147);
+
+ return yy_is_jam ? 0 : yy_current_state;
+ }
+
+
+#ifndef YY_NO_UNPUT
+#ifdef YY_USE_PROTOS
+static void yyunput( int c, register char *yy_bp )
+#else
+static void yyunput( c, yy_bp )
+int c;
+register char *yy_bp;
+#endif
+ {
+ register char *yy_cp = yy_c_buf_p;
+
+ /* undo effects of setting up yytext */
+ *yy_cp = yy_hold_char;
+
+ if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 )
+ { /* need to shift things up to make room */
+ /* +2 for EOB chars. */
+ register int number_to_move = yy_n_chars + 2;
+ register char *dest = &yy_current_buffer->yy_ch_buf[
+ yy_current_buffer->yy_buf_size + 2];
+ register char *source =
+ &yy_current_buffer->yy_ch_buf[number_to_move];
+
+ while ( source > yy_current_buffer->yy_ch_buf )
+ *--dest = *--source;
+
+ yy_cp += (int) (dest - source);
+ yy_bp += (int) (dest - source);
+ yy_current_buffer->yy_n_chars =
+ yy_n_chars = yy_current_buffer->yy_buf_size;
+
+ if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 )
+ YY_FATAL_ERROR( "flex scanner push-back overflow" );
+ }
+
+ *--yy_cp = (char) c;
+
+
+ yytext_ptr = yy_bp;
+ yy_hold_char = *yy_cp;
+ yy_c_buf_p = yy_cp;
+ }
+#endif /* ifndef YY_NO_UNPUT */
+
+
+#ifdef __cplusplus
+static int yyinput()
+#else
+static int input()
+#endif
+ {
+ int c;
+
+ *yy_c_buf_p = yy_hold_char;
+
+ if ( *yy_c_buf_p == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( yy_c_buf_p < &yy_current_buffer->yy_ch_buf[yy_n_chars] )
+ /* This was really a NUL. */
+ *yy_c_buf_p = '\0';
+
+ else
+ { /* need more input */
+ int offset = yy_c_buf_p - yytext_ptr;
+ ++yy_c_buf_p;
+
+ switch ( yy_get_next_buffer() )
+ {
+ case EOB_ACT_LAST_MATCH:
+ /* This happens because yy_g_n_b()
+ * sees that we've accumulated a
+ * token and flags that we need to
+ * try matching the token before
+ * proceeding. But for input(),
+ * there's no matching to consider.
+ * So convert the EOB_ACT_LAST_MATCH
+ * to EOB_ACT_END_OF_FILE.
+ */
+
+ /* Reset buffer status. */
+ yyrestart( yyin );
+
+ /* fall through */
+
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( yywrap() )
+ return EOF;
+
+ if ( ! yy_did_buffer_switch_on_eof )
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput();
+#else
+ return input();
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ yy_c_buf_p = yytext_ptr + offset;
+ break;
+ }
+ }
+ }
+
+ c = *(unsigned char *) yy_c_buf_p; /* cast for 8-bit char's */
+ *yy_c_buf_p = '\0'; /* preserve yytext */
+ yy_hold_char = *++yy_c_buf_p;
+
+
+ return c;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yyrestart( FILE *input_file )
+#else
+void yyrestart( input_file )
+FILE *input_file;
+#endif
+ {
+ if ( ! yy_current_buffer )
+ yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE );
+
+ yy_init_buffer( yy_current_buffer, input_file );
+ yy_load_buffer_state();
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer )
+#else
+void yy_switch_to_buffer( new_buffer )
+YY_BUFFER_STATE new_buffer;
+#endif
+ {
+ if ( yy_current_buffer == new_buffer )
+ return;
+
+ if ( yy_current_buffer )
+ {
+ /* Flush out information for old buffer. */
+ *yy_c_buf_p = yy_hold_char;
+ yy_current_buffer->yy_buf_pos = yy_c_buf_p;
+ yy_current_buffer->yy_n_chars = yy_n_chars;
+ }
+
+ yy_current_buffer = new_buffer;
+ yy_load_buffer_state();
+
+ /* We don't actually know whether we did this switch during
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ yy_did_buffer_switch_on_eof = 1;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_load_buffer_state( void )
+#else
+void yy_load_buffer_state()
+#endif
+ {
+ yy_n_chars = yy_current_buffer->yy_n_chars;
+ yytext_ptr = yy_c_buf_p = yy_current_buffer->yy_buf_pos;
+ yyin = yy_current_buffer->yy_input_file;
+ yy_hold_char = *yy_c_buf_p;
+ }
+
+
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )
+#else
+YY_BUFFER_STATE yy_create_buffer( file, size )
+FILE *file;
+int size;
+#endif
+ {
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) yy_flex_alloc( b->yy_buf_size + 2 );
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_is_our_buffer = 1;
+
+ yy_init_buffer( b, file );
+
+ return b;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_delete_buffer( YY_BUFFER_STATE b )
+#else
+void yy_delete_buffer( b )
+YY_BUFFER_STATE b;
+#endif
+ {
+ if ( ! b )
+ return;
+
+ if ( b == yy_current_buffer )
+ yy_current_buffer = (YY_BUFFER_STATE) 0;
+
+ if ( b->yy_is_our_buffer )
+ yy_flex_free( (void *) b->yy_ch_buf );
+
+ yy_flex_free( (void *) b );
+ }
+
+
+#ifndef _WIN32
+#include <unistd.h>
+#else
+#ifndef YY_ALWAYS_INTERACTIVE
+#ifndef YY_NEVER_INTERACTIVE
+extern int isatty YY_PROTO(( int ));
+#endif
+#endif
+#endif
+
+#ifdef YY_USE_PROTOS
+void yy_init_buffer( YY_BUFFER_STATE b, FILE *file )
+#else
+void yy_init_buffer( b, file )
+YY_BUFFER_STATE b;
+FILE *file;
+#endif
+
+
+ {
+ yy_flush_buffer( b );
+
+ b->yy_input_file = file;
+ b->yy_fill_buffer = 1;
+
+#if YY_ALWAYS_INTERACTIVE
+ b->yy_is_interactive = 1;
+#else
+#if YY_NEVER_INTERACTIVE
+ b->yy_is_interactive = 0;
+#else
+ b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+#endif
+#endif
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_flush_buffer( YY_BUFFER_STATE b )
+#else
+void yy_flush_buffer( b )
+YY_BUFFER_STATE b;
+#endif
+
+ {
+ if ( ! b )
+ return;
+
+ b->yy_n_chars = 0;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[0];
+
+ b->yy_at_bol = 1;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ if ( b == yy_current_buffer )
+ yy_load_buffer_state();
+ }
+
+
+#ifndef YY_NO_SCAN_BUFFER
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_buffer( char *base, yy_size_t size )
+#else
+YY_BUFFER_STATE yy_scan_buffer( base, size )
+char *base;
+yy_size_t size;
+#endif
+ {
+ YY_BUFFER_STATE b;
+
+ if ( size < 2 ||
+ base[size-2] != YY_END_OF_BUFFER_CHAR ||
+ base[size-1] != YY_END_OF_BUFFER_CHAR )
+ /* They forgot to leave room for the EOB's. */
+ return 0;
+
+ b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+ b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */
+ b->yy_buf_pos = b->yy_ch_buf = base;
+ b->yy_is_our_buffer = 0;
+ b->yy_input_file = 0;
+ b->yy_n_chars = b->yy_buf_size;
+ b->yy_is_interactive = 0;
+ b->yy_at_bol = 1;
+ b->yy_fill_buffer = 0;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ yy_switch_to_buffer( b );
+
+ return b;
+ }
+#endif
+
+
+#ifndef YY_NO_SCAN_STRING
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_string( yyconst char *yy_str )
+#else
+YY_BUFFER_STATE yy_scan_string( yy_str )
+yyconst char *yy_str;
+#endif
+ {
+ int len;
+ for ( len = 0; yy_str[len]; ++len )
+ ;
+
+ return yy_scan_bytes( yy_str, len );
+ }
+#endif
+
+
+#ifndef YY_NO_SCAN_BYTES
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_bytes( yyconst char *bytes, int len )
+#else
+YY_BUFFER_STATE yy_scan_bytes( bytes, len )
+yyconst char *bytes;
+int len;
+#endif
+ {
+ YY_BUFFER_STATE b;
+ char *buf;
+ yy_size_t n;
+ int i;
+
+ /* Get memory for full buffer, including space for trailing EOB's. */
+ n = len + 2;
+ buf = (char *) yy_flex_alloc( n );
+ if ( ! buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+ for ( i = 0; i < len; ++i )
+ buf[i] = bytes[i];
+
+ buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR;
+
+ b = yy_scan_buffer( buf, n );
+ if ( ! b )
+ YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+ /* It's okay to grow etc. this buffer, and we should throw it
+ * away when we're done.
+ */
+ b->yy_is_our_buffer = 1;
+
+ return b;
+ }
+#endif
+
+
+#ifndef YY_NO_PUSH_STATE
+#ifdef YY_USE_PROTOS
+static void yy_push_state( int new_state )
+#else
+static void yy_push_state( new_state )
+int new_state;
+#endif
+ {
+ if ( yy_start_stack_ptr >= yy_start_stack_depth )
+ {
+ yy_size_t new_size;
+
+ yy_start_stack_depth += YY_START_STACK_INCR;
+ new_size = yy_start_stack_depth * sizeof( int );
+
+ if ( ! yy_start_stack )
+ yy_start_stack = (int *) yy_flex_alloc( new_size );
+
+ else
+ yy_start_stack = (int *) yy_flex_realloc(
+ (void *) yy_start_stack, new_size );
+
+ if ( ! yy_start_stack )
+ YY_FATAL_ERROR(
+ "out of memory expanding start-condition stack" );
+ }
+
+ yy_start_stack[yy_start_stack_ptr++] = YY_START;
+
+ BEGIN(new_state);
+ }
+#endif
+
+
+#ifndef YY_NO_POP_STATE
+static void yy_pop_state()
+ {
+ if ( --yy_start_stack_ptr < 0 )
+ YY_FATAL_ERROR( "start-condition stack underflow" );
+
+ BEGIN(yy_start_stack[yy_start_stack_ptr]);
+ }
+#endif
+
+
+#ifndef YY_NO_TOP_STATE
+static int yy_top_state()
+ {
+ return yy_start_stack[yy_start_stack_ptr - 1];
+ }
+#endif
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+#ifdef YY_USE_PROTOS
+static void yy_fatal_error( yyconst char msg[] )
+#else
+static void yy_fatal_error( msg )
+char msg[];
+#endif
+ {
+ (void) fprintf( stderr, "%s\n", msg );
+ exit( YY_EXIT_FAILURE );
+ }
+
+
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ yytext[yyleng] = yy_hold_char; \
+ yy_c_buf_p = yytext + n; \
+ yy_hold_char = *yy_c_buf_p; \
+ *yy_c_buf_p = '\0'; \
+ yyleng = n; \
+ } \
+ while ( 0 )
+
+
+/* Internal utility routines. */
+
+#ifndef yytext_ptr
+#ifdef YY_USE_PROTOS
+static void yy_flex_strncpy( char *s1, yyconst char *s2, int n )
+#else
+static void yy_flex_strncpy( s1, s2, n )
+char *s1;
+yyconst char *s2;
+int n;
+#endif
+ {
+ register int i;
+ for ( i = 0; i < n; ++i )
+ s1[i] = s2[i];
+ }
+#endif
+
+#ifdef YY_NEED_STRLEN
+#ifdef YY_USE_PROTOS
+static int yy_flex_strlen( yyconst char *s )
+#else
+static int yy_flex_strlen( s )
+yyconst char *s;
+#endif
+ {
+ register int n;
+ for ( n = 0; s[n]; ++n )
+ ;
+
+ return n;
+ }
+#endif
+
+
+#ifdef YY_USE_PROTOS
+static void *yy_flex_alloc( yy_size_t size )
+#else
+static void *yy_flex_alloc( size )
+yy_size_t size;
+#endif
+ {
+ return (void *) malloc( size );
+ }
+
+#ifdef YY_USE_PROTOS
+static void *yy_flex_realloc( void *ptr, yy_size_t size )
+#else
+static void *yy_flex_realloc( ptr, size )
+void *ptr;
+yy_size_t size;
+#endif
+ {
+ /* The cast to (char *) in the following accommodates both
+ * implementations that use char* generic pointers, and those
+ * that use void* generic pointers. It works with the latter
+ * because both ANSI C and C++ allow castless assignment from
+ * any pointer type to void*, and deal with argument conversions
+ * as though doing an assignment.
+ */
+ return (void *) realloc( (char *) ptr, size );
+ }
+
+#ifdef YY_USE_PROTOS
+static void yy_flex_free( void *ptr )
+#else
+static void yy_flex_free( ptr )
+void *ptr;
+#endif
+ {
+ free( ptr );
+ }
+
+#if YY_MAIN
+int main()
+ {
+ yylex();
+ return 0;
+ }
+#endif
+#line 310 "sqlscanner.l"
+
+
+void tokenize(const char *data)
+{
+ yy_switch_to_buffer(yy_scan_string(data));
+ ctoken = "";
+ current = 0;
+}
+
diff --git a/kexi/kexidb/parser/sqlscanner.l b/kexi/kexidb/parser/sqlscanner.l
new file mode 100644
index 000000000..5f74a0caf
--- /dev/null
+++ b/kexi/kexidb/parser/sqlscanner.l
@@ -0,0 +1,318 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+%{
+#include <field.h>
+#include <expression.h>
+
+#include "sqlparser.h"
+#include "sqltypes.h"
+#include <iostream>
+#include <kdebug.h>
+#include <klocale.h>
+
+#define YY_NO_UNPUT
+#define ECOUNT current += yyleng; ctoken = yytext
+
+extern void setError(const QString& errDesc);
+extern void setError(const QString& errName, const QString& errDesc);
+
+%}
+
+/* *** Please reflect changes to this file in ../driver_p.cpp *** */
+
+%option case-insensitive
+%option noyywrap
+%option never-interactive
+
+whitespace [ \t\n]
+digit [0-9]
+/*identifier [a-zA-Z_][a-zA-Z_0-9]* */
+identifier [a-zA-Z_0-9]+
+/* quoted_identifier (\"[a-zA-Z_0-9]+\") */
+query_parameter \[[^\[\]]+\]
+
+integer {digit}+
+decimal (({digit}*\.{digit}+)|({digit}+\.{digit}*))
+real ((({digit}*\.{digit}+)|({digit}+\.{digit}*)|({digit}+))([Ee][-+]?{digit}+))
+/* todo: support for real numbers */
+
+
+%%
+
+
+"<>" {
+ ECOUNT;
+ return NOT_EQUAL;
+}
+
+"!=" {
+ ECOUNT;
+ return NOT_EQUAL2;
+}
+
+"==" {
+ ECOUNT;
+ return '=';
+}
+
+"<=" {
+ ECOUNT;
+ return LESS_OR_EQUAL;
+}
+
+">=" {
+ ECOUNT;
+ return GREATER_OR_EQUAL;
+}
+
+"IN" {
+ ECOUNT;
+ return SQL_IN;
+}
+
+{integer} {
+//TODO: what about hex or octal values?
+ //we're using QString:toLongLong() here because atoll() is not so portable:
+ ECOUNT;
+ bool ok;
+ yylval.integerValue = QString(yytext).toLongLong( &ok );
+ if (!ok) {
+ setError(i18n("Invalid integer number"),i18n("This integer number may be too large."));
+ return SCAN_ERROR;
+ }
+// yylval.integerValue = atol(yytext);
+ return INTEGER_CONST;
+}
+
+{decimal} {
+ char *p = yytext;
+ if (yytext[0]=='.') { /* no integer part */
+ yylval.realValue.integer = 0;
+ }
+ else {
+ yylval.realValue.integer = atoi(p);
+ int i=0;
+ while (p && i < yyleng && *p != '.') {
+ i++;
+ p++;
+ }
+ if (i==0 || !p || *p!='.') {
+ yylval.realValue.fractional = 0;
+ return REAL_CONST;
+ }
+ }
+ /* fractional part */
+ p++;
+ yylval.realValue.fractional = atoi(p);
+ return REAL_CONST;
+}
+
+("AND"|"&&") {
+ ECOUNT;
+ return AND;
+}
+
+"AS" {
+ ECOUNT;
+ return AS;
+}
+
+"CREATE" {
+ ECOUNT;
+ return CREATE;
+}
+
+"FROM" {
+ ECOUNT;
+ return FROM;
+}
+
+"INTEGER" {
+ ECOUNT;
+ return SQL_TYPE;
+}
+
+"JOIN" {
+ ECOUNT;
+ return JOIN;
+}
+
+"LEFT" {
+ ECOUNT;
+ return LEFT;
+}
+
+"LIKE" {
+ ECOUNT;
+ return LIKE;
+}
+
+"NOT"{whitespace}+"SIMILAR"{whitespace}+"TO" {
+ ECOUNT;
+ return NOT_SIMILAR_TO;
+}
+
+"SIMILAR"{whitespace}+"TO" {
+ ECOUNT;
+ return SIMILAR_TO;
+}
+
+"IS"{whitespace}+"NOT"{whitespace}+"NULL" {
+ ECOUNT;
+ return SQL_IS_NOT_NULL;
+}
+
+"IS"{whitespace}+"NULL" {
+ ECOUNT;
+ return SQL_IS_NULL;
+}
+
+"NOT" {
+ ECOUNT;
+ return NOT;
+}
+
+"IS" {
+ ECOUNT;
+ return SQL_IS;
+}
+
+"NULL" {
+ ECOUNT;
+ return SQL_NULL;
+}
+
+"ON" {
+ ECOUNT;
+ return SQL_ON;
+}
+
+"OR" {
+ ECOUNT;
+ return OR;
+}
+
+"||" { /* also means OR for numbers (mysql) */
+ ECOUNT;
+ return CONCATENATION;
+}
+
+"<<" {
+ ECOUNT;
+ return BITWISE_SHIFT_LEFT;
+}
+
+">>" {
+ ECOUNT;
+ return BITWISE_SHIFT_RIGHT;
+}
+
+"XOR" {
+ ECOUNT;
+ return XOR;
+}
+
+"RIGHT" {
+ ECOUNT;
+ return RIGHT;
+}
+
+"SELECT" {
+ ECOUNT;
+ return SELECT;
+}
+
+"TABLE" {
+ ECOUNT;
+ return TABLE;
+}
+
+"WHERE" {
+ ECOUNT;
+ return WHERE;
+}
+
+"ORDER" {
+ ECOUNT;
+ return ORDER;
+}
+
+"BY" {
+ ECOUNT;
+ return BY;
+}
+
+"ASC" {
+ ECOUNT;
+ return ASC;
+}
+
+"DESC" {
+ ECOUNT;
+ return DESC;
+}
+
+(['][^']*[']|["][^\"]*["]) {
+ ECOUNT;
+ yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2));
+ return CHARACTER_STRING_LITERAL;
+
+/* "ZZZ" sentinel for script */
+}
+
+{identifier} {
+ KexiDBDbg << "yytext: '" << yytext << "' (" << yyleng << ")" << endl;
+ ECOUNT;
+ yylval.stringValue = new QString(QString::fromUtf8(yytext, yyleng));
+ if (yytext[0]>='0' && yytext[0]<='9') {
+ setError(i18n("Invalid identifier"),
+ i18n("Identifiers should start with a letter or '_' character"));
+ return SCAN_ERROR;
+ }
+ return IDENTIFIER;
+}
+
+{query_parameter} {
+ KexiDBDbg << "yytext: '" << yytext << "' (" << yyleng << ")" << endl;
+ ECOUNT;
+ yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2));
+ return QUERY_PARAMETER;
+}
+
+{whitespace}+ {
+ ECOUNT;
+}
+
+[\~\!\@\#\^\&\|\`\?,()\[\]\.;\:\+\-\*\/\%\^\<\>\=] {
+ KexiDBDbg << "char: '" << yytext[0] << "'" << endl;
+ ECOUNT;
+ return yytext[0];
+}
+
+%%
+
+void tokenize(const char *data)
+{
+ yy_switch_to_buffer(yy_scan_string(data));
+ ctoken = "";
+ current = 0;
+}
+
diff --git a/kexi/kexidb/parser/sqltypes.h b/kexi/kexidb/parser/sqltypes.h
new file mode 100644
index 000000000..c0879a3c4
--- /dev/null
+++ b/kexi/kexidb/parser/sqltypes.h
@@ -0,0 +1,80 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef SQLTYPES_H
+#define SQLTYPES_H
+
+#include <qvariant.h>
+
+extern int current;
+extern QString ctoken;
+
+struct dateType
+{
+ int year;
+ int month;
+ int day;
+};
+
+struct realType
+{
+ int integer;
+ int fractional;
+};
+
+//! @internal
+struct OrderByColumnInternal
+{
+ typedef QValueList<OrderByColumnInternal> List;
+ typedef QValueListConstIterator<OrderByColumnInternal> ListConstIterator;
+ OrderByColumnInternal()
+ : columnNumber(-1)
+ , ascending(true)
+ {
+ }
+
+ void setColumnByNameOrNumber(const QVariant& nameOrNumber) {
+ if (nameOrNumber.type()==QVariant::String) {
+ aliasOrName = nameOrNumber.toString();
+ columnNumber = -1;
+ }
+ else {
+ columnNumber = nameOrNumber.toInt();
+ aliasOrName = QString::null;
+ }
+ }
+
+ QString aliasOrName; //!< Can include a "tablename." prefix
+ int columnNumber; //!< Optional, used instead of aliasOrName to refer to column
+ //!< by its number rather than name.
+ bool ascending : 1;
+};
+
+//! @internal
+struct SelectOptionsInternal
+{
+ SelectOptionsInternal() : whereExpr(0), orderByColumns(0) {}
+ ~SelectOptionsInternal() {
+ delete orderByColumns; // delete because this is internal temp. structure
+ }
+ KexiDB::BaseExpr* whereExpr;
+ OrderByColumnInternal::List* orderByColumns;
+};
+
+#endif
diff --git a/kexi/kexidb/parser/tokens.cpp b/kexi/kexidb/parser/tokens.cpp
new file mode 100644
index 000000000..9d196c524
--- /dev/null
+++ b/kexi/kexidb/parser/tokens.cpp
@@ -0,0 +1,25 @@
+/* WARNING! All changes made in this file will be lost! Run 'make parser' instead. */
+INS("AND");
+INS("AS");
+INS("ASC");
+INS("BY");
+INS("CREATE");
+INS("DESC");
+INS("FROM");
+INS("IN");
+INS("INTEGER");
+INS("IS");
+INS("JOIN");
+INS("LEFT");
+INS("LIKE");
+INS("NOT");
+INS("NULL");
+INS("ON");
+INS("OR");
+INS("ORDER");
+INS("RIGHT");
+INS("SELECT");
+INS("SIMILAR");
+INS("TABLE");
+INS("WHERE");
+INS("XOR");
diff --git a/kexi/kexidb/preparedstatement.cpp b/kexi/kexidb/preparedstatement.cpp
new file mode 100644
index 000000000..24947dffa
--- /dev/null
+++ b/kexi/kexidb/preparedstatement.cpp
@@ -0,0 +1,136 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "preparedstatement.h"
+
+#include <kexidb/connection.h>
+#include <kexidb/connection_p.h>
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+PreparedStatement::PreparedStatement(StatementType type, ConnectionInternal& conn,
+ FieldList& fields, const QStringList& where)
+ : KShared()
+ , m_type(type)
+ , m_fields(&fields)
+ , m_where(where.isEmpty() ? new QStringList(where) : 0)
+ , m_whereFields(0)
+{
+ Q_UNUSED(conn);
+}
+
+PreparedStatement::~PreparedStatement()
+{
+ delete m_where;
+ delete m_whereFields;
+}
+
+QCString PreparedStatement::generateStatementString()
+{
+ QCString s(1024);
+ if (m_type == SelectStatement) {
+//! @todo only tables and trivial queries supported for select...
+ s = "SELECT ";
+ bool first = true;
+// for (uint i=0; i<m_fields->fieldCount(); i++) {
+ for (Field::ListIterator it(m_fields->fieldsIterator()); it.current(); ++it) {
+ if (first)
+ first = false;
+ else
+ s.append(", ");
+ s.append(it.current()->name().latin1());
+ }
+ first = true;
+ s.append(" WHERE ");
+// for (uint i=0; i<m_fields->fieldCount(); i++) {
+
+ m_whereFields = new Field::List();
+ for (QStringList::ConstIterator it=m_where->constBegin(); it!=m_where->constEnd(); ++it) {
+// for (Field::ListIterator it(m_fields->fieldsIterator()); it.current(); ++it) {
+ if (first)
+ first = false;
+ else
+ s.append(" AND ");
+ Field *f = m_fields->field(*it);
+ if (!f) {
+ KexiDBWarn << "PreparedStatement::generateStatementString(): no '"
+ << *it << "' field found" << endl;
+ continue;
+ }
+ m_whereFields->append(f);
+ s.append((*it).latin1());
+ s.append("=?");
+ }
+ }
+ else if (m_type == InsertStatement /*&& dynamic_cast<TableSchema*>(m_fields)*/) {
+//! @todo only tables supported for insert; what about views?
+
+ TableSchema *table = m_fields->fieldCount()>0 ? m_fields->field(0)->table() : 0;
+ if (!table)
+ return ""; //err
+
+ QCString namesList;
+ bool first = true;
+ const bool allTableFieldsUsed = dynamic_cast<TableSchema*>(m_fields); //we are using a selection of fields only
+ Field::ListIterator it = m_fields->fieldsIterator();
+ for (uint i=0; i<m_fields->fieldCount(); i++, ++it) {
+ if (first) {
+ s.append( "?" );
+ if (!allTableFieldsUsed)
+ namesList = it.current()->name().latin1();
+ first = false;
+ } else {
+ s.append( ",?" );
+ if (!allTableFieldsUsed)
+ namesList.append(QCString(", ")+it.current()->name().latin1());
+ }
+ }
+ s.append(")");
+ s.prepend(QCString("INSERT INTO ") + table->name().latin1()
+ + (allTableFieldsUsed ? QCString() : (" (" + namesList + ")"))
+ + " VALUES (");
+ }
+ return s;
+}
+
+PreparedStatement& PreparedStatement::operator<< ( const QVariant& value )
+{
+ m_args.append(value);
+ return *this;
+}
+
+/*bool PreparedStatement::insert()
+{
+ const bool res = m_conn->drv_prepareStatement(this);
+ const bool res = m_conn->drv_insertRecord(this);
+ clearArguments();
+ return res;
+}
+
+bool PreparedStatement::select()
+{
+ const bool res = m_conn->drv_bindArgumentForPreparedStatement(this, m_args.count()-1);
+}*/
+
+void PreparedStatement::clearArguments()
+{
+ m_args.clear();
+}
+
diff --git a/kexi/kexidb/preparedstatement.h b/kexi/kexidb/preparedstatement.h
new file mode 100644
index 000000000..368140e5b
--- /dev/null
+++ b/kexi/kexidb/preparedstatement.h
@@ -0,0 +1,117 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_PREPAREDSTATEMENT_H
+#define KEXIDB_PREPAREDSTATEMENT_H
+
+#include <qvariant.h>
+#include <ksharedptr.h>
+
+#include "field.h"
+
+namespace KexiDB {
+
+class ConnectionInternal;
+class TableSchema;
+class FieldList;
+
+/*! @short Prepared database command for optimizing sequences of multiple database actions
+
+ Currently INSERT and SELECT statements are supported.
+ For example, wher using PreparedStatement for INSERTs,
+ you can gain about 30% speedup compared to using multiple
+ connection.insertRecord(*tabelSchema, dbRowBuffer).
+
+ To use PreparedStatement, create is using KexiDB::Connection:prepareStatement(),
+ providing table schema; set up arguments using operator << ( const QVariant& value );
+ and call execute() when ready. PreparedStatement objects are accessed
+ using KDE shared pointers, i.e KexiDB::PreparedStatement::Ptr, so you do not need
+ to remember about destroying them. However, when underlying Connection object
+ is destroyed, PreparedStatement should not be used.
+
+ Let's assume tableSchema contains two columns NUMBER integer and TEXT text.
+ Following code inserts 10000 records with random numbers and text strings
+ obtained elsewhere using getText(i).
+ \code
+ bool insertMultiple(KexiDB::Connection &conn, KexiDB::TableSchema& tableSchema)
+ {
+ KexiDB::PreparedStatement::Ptr prepared = conn.prepareStatement(
+ KexiDB::PreparedStatement::InsertStatement, tableSchema);
+ for (i=0; i<10000; i++) {
+ prepared << rand() << getText(i);
+ if (!prepared.execute())
+ return false;
+ prepared.clearArguments();
+ }
+ return true;
+ }
+ \endcode
+
+ If you do not call clearArguments() after every insert, you can insert
+ the same value multiple times using execute() what increases efficiency even more.
+
+ Another use case is inserting large objects (BLOBs or CLOBs).
+ Depending on database backend, you can avoid escaping BLOBs.
+ See KexiFormView::storeData() for example use.
+*/
+class KEXI_DB_EXPORT PreparedStatement : public KShared
+{
+ public:
+ typedef KSharedPtr<PreparedStatement> Ptr;
+
+ //! Defines type of the prepared statement.
+ enum StatementType {
+ SelectStatement, //!< SELECT statement will be prepared end executed
+ InsertStatement //!< INSERT statement will be prepared end executed
+ };
+
+ //! Creates Prepared statement. In your code use KexiDB::Connection:prepareStatement() instead.
+ PreparedStatement(StatementType type, ConnectionInternal& conn, FieldList& fields,
+ const QStringList& where = QStringList());
+
+ virtual ~PreparedStatement();
+
+ //! Appends argument \a value to the statement.
+ PreparedStatement& operator<< ( const QVariant& value );
+
+ //! Clears arguments of the prepared statement. Usually used after execute()
+ void clearArguments();
+
+ /*! Executes the prepared statement. In most cases you will need to clear
+ arguments after executing, using clearArguments().
+ A number arguments set up for the statement must be the same as a number of fields
+ defined in the underlying database table.
+ \return false on failure. Detailed error status can be obtained
+ from KexiDB::Connection object used to create this statement. */
+ virtual bool execute() = 0;
+
+ protected:
+//! @todo is this portable across backends?
+ QCString generateStatementString();
+
+ StatementType m_type;
+ FieldList *m_fields;
+ QValueList<QVariant> m_args;
+ QStringList* m_where;
+ Field::List* m_whereFields;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/queryschema.cpp b/kexi/kexidb/queryschema.cpp
new file mode 100644
index 000000000..4da4d2b8e
--- /dev/null
+++ b/kexi/kexidb/queryschema.cpp
@@ -0,0 +1,1859 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidb/queryschema.h"
+#include "kexidb/driver.h"
+#include "kexidb/connection.h"
+#include "kexidb/expression.h"
+#include "kexidb/parser/sqlparser.h"
+#include "utils.h"
+#include "lookupfieldschema.h"
+
+#include <assert.h>
+
+#include <qvaluelist.h>
+#include <qasciidict.h>
+#include <qptrdict.h>
+#include <qintdict.h>
+#include <qbitarray.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+using namespace KexiDB;
+
+QueryColumnInfo::QueryColumnInfo(Field *f, const QCString& _alias, bool _visible,
+ QueryColumnInfo *foreignColumn)
+ : field(f), alias(_alias), visible(_visible), m_indexForVisibleLookupValue(-1)
+ , m_foreignColumn(foreignColumn)
+{
+}
+
+QueryColumnInfo::~QueryColumnInfo()
+{
+}
+
+QString QueryColumnInfo::debugString() const
+{
+ return field->name() +
+ ( alias.isEmpty() ? QString::null
+ : (QString::fromLatin1(" AS ") + QString(alias)) );
+}
+
+//=======================================
+namespace KexiDB {
+//! @internal
+class QuerySchemaPrivate
+{
+ public:
+ QuerySchemaPrivate(QuerySchema* q, QuerySchemaPrivate* copy = 0)
+ : query(q)
+ , masterTable(0)
+ , fakeRowIDField(0)
+ , fakeRowIDCol(0)
+ , maxIndexWithAlias(-1)
+ , visibility(64)
+ , fieldsExpanded(0)
+ , internalFields(0)
+ , fieldsExpandedWithInternalAndRowID(0)
+ , fieldsExpandedWithInternal(0)
+ , autoincFields(0)
+ , columnsOrder(0)
+ , columnsOrderWithoutAsterisks(0)
+ , columnsOrderExpanded(0)
+ , pkeyFieldsOrder(0)
+ , pkeyFieldsCount(0)
+ , tablesBoundToColumns(64, -1)
+ , tablePositionsForAliases(67, false)
+ , columnPositionsForAliases(67, false)
+ , whereExpr(0)
+ , ownedVisibleColumns(0)
+ , regenerateExprAliases(false)
+ {
+ columnAliases.setAutoDelete(true);
+ tableAliases.setAutoDelete(true);
+ asterisks.setAutoDelete(true);
+ relations.setAutoDelete(true);
+ tablePositionsForAliases.setAutoDelete(true);
+ columnPositionsForAliases.setAutoDelete(true);
+ visibility.fill(false);
+ if (copy) {
+ // deep copy
+ *this = *copy;
+ if (copy->fieldsExpanded)
+ fieldsExpanded = new QueryColumnInfo::Vector(*copy->fieldsExpanded);
+ if (copy->internalFields)
+ internalFields = new QueryColumnInfo::Vector(*copy->internalFields);
+ if (copy->fieldsExpandedWithInternalAndRowID)
+ fieldsExpandedWithInternalAndRowID = new QueryColumnInfo::Vector(
+ *copy->fieldsExpandedWithInternalAndRowID);
+ if (copy->fieldsExpandedWithInternal)
+ fieldsExpandedWithInternal = new QueryColumnInfo::Vector(
+ *copy->fieldsExpandedWithInternal);
+ if (copy->autoincFields)
+ autoincFields = new QueryColumnInfo::List(*copy->autoincFields);
+ if (copy->columnsOrder)
+ columnsOrder = new QMap<QueryColumnInfo*,int>(*copy->columnsOrder);
+ if (copy->columnsOrderWithoutAsterisks)
+ columnsOrderWithoutAsterisks = new QMap<QueryColumnInfo*,int>(
+ *copy->columnsOrderWithoutAsterisks);
+ if (copy->columnsOrderExpanded)
+ columnsOrderExpanded = new QMap<QueryColumnInfo*,int>(*copy->columnsOrderExpanded);
+ if (copy->pkeyFieldsOrder)
+ pkeyFieldsOrder = new QValueVector<int>(*copy->pkeyFieldsOrder);
+ if (copy->whereExpr)
+ whereExpr = copy->whereExpr->copy();
+ if (copy->fakeRowIDCol)
+ fakeRowIDCol = new QueryColumnInfo(*copy->fakeRowIDCol);
+ if (copy->fakeRowIDField)
+ fakeRowIDField = new Field(*copy->fakeRowIDField);
+ if (copy->ownedVisibleColumns)
+ ownedVisibleColumns = new Field::List(*copy->ownedVisibleColumns);
+ }
+ }
+ ~QuerySchemaPrivate()
+ {
+ delete fieldsExpanded;
+ delete internalFields;
+ delete fieldsExpandedWithInternalAndRowID;
+ delete fieldsExpandedWithInternal;
+ delete autoincFields;
+ delete columnsOrder;
+ delete columnsOrderWithoutAsterisks;
+ delete columnsOrderExpanded;
+ delete pkeyFieldsOrder;
+ delete whereExpr;
+ delete fakeRowIDCol;
+ delete fakeRowIDField;
+ delete ownedVisibleColumns;
+ }
+
+ void clear()
+ {
+ columnAliases.clear();
+ tableAliases.clear();
+ asterisks.clear();
+ relations.clear();
+ masterTable = 0;
+ tables.clear();
+ clearCachedData();
+ delete pkeyFieldsOrder;
+ pkeyFieldsOrder=0;
+ visibility.fill(false);
+ tablesBoundToColumns = QValueVector<int>(64,-1);
+ tablePositionsForAliases.clear();
+ columnPositionsForAliases.clear();
+ }
+
+ void clearCachedData()
+ {
+ orderByColumnList.clear();
+ if (fieldsExpanded) {
+ delete fieldsExpanded;
+ fieldsExpanded = 0;
+ delete internalFields;
+ internalFields = 0;
+ delete columnsOrder;
+ columnsOrder = 0;
+ delete columnsOrderWithoutAsterisks;
+ columnsOrderWithoutAsterisks = 0;
+ delete columnsOrderExpanded;
+ columnsOrderExpanded = 0;
+ delete autoincFields;
+ autoincFields = 0;
+ autoIncrementSQLFieldsList = QString::null;
+ columnInfosByNameExpanded.clear();
+ columnInfosByName.clear();
+ delete ownedVisibleColumns;
+ ownedVisibleColumns = 0;
+ }
+ }
+
+ void setColumnAliasInternal(uint position, const QCString& alias)
+ {
+ columnAliases.replace(position, new QCString(alias));
+ columnPositionsForAliases.replace(alias, new int(position));
+ maxIndexWithAlias = QMAX( maxIndexWithAlias, (int)position );
+ }
+
+ void setColumnAlias(uint position, const QCString& alias)
+ {
+ QCString *oldAlias = columnAliases.take(position);
+ if (oldAlias) {
+ tablePositionsForAliases.remove(*oldAlias);
+ delete oldAlias;
+ }
+ if (alias.isEmpty()) {
+ maxIndexWithAlias = -1;
+ }
+ else {
+ setColumnAliasInternal(position, alias);
+ }
+ }
+
+ bool hasColumnAliases()
+ {
+ tryRegenerateExprAliases();
+ return !columnAliases.isEmpty();
+ }
+
+ QCString* columnAlias(uint position)
+ {
+ tryRegenerateExprAliases();
+ return columnAliases[position];
+ }
+
+ QuerySchema *query;
+
+ /*! Master table of the query. (may be NULL)
+ Any data modifications can be performed if we know master table.
+ If null, query's records cannot be modified. */
+ TableSchema *masterTable;
+
+ /*! List of tables used in this query */
+ TableSchema::List tables;
+
+ Field *fakeRowIDField; //! used to mark a place for ROWID
+ QueryColumnInfo *fakeRowIDCol; //! used to mark a place for ROWID
+
+ protected:
+ void tryRegenerateExprAliases()
+ {
+ if (!regenerateExprAliases)
+ return;
+ //regenerate missing aliases for experessions
+ Field *f;
+ uint p=0;
+ uint colNum=0; //used to generate a name
+ QCString columnAlias;
+ for (Field::ListIterator it(query->fieldsIterator()); (f = it.current()); ++it, p++) {
+ if (f->isExpression() && !columnAliases[p]) {
+ //missing
+ for (;;) { //find 1st unused
+ colNum++;
+ columnAlias = (i18n("short for 'expression' word (only latin letters, please)", "expr")
+ + QString::number(colNum)).latin1();
+ if (!tablePositionsForAliases[columnAlias])
+ break;
+ }
+ setColumnAliasInternal(p, columnAlias);
+ }
+ }
+ regenerateExprAliases = false;
+ }
+
+ /*! Used to mapping columns to its aliases for this query */
+ QIntDict<QCString> columnAliases;
+
+ public:
+ /*! Used to mapping tables to its aliases for this query */
+ QIntDict<QCString> tableAliases;
+
+ /*! Helper used with aliases */
+ int maxIndexWithAlias;
+
+ /*! Helper used with tableAliases */
+ int maxIndexWithTableAlias;
+
+ /*! Used to store visibility flag for every field */
+ QBitArray visibility;
+
+ /*! List of asterisks defined for this query */
+ Field::List asterisks;
+
+ /*! Temporary field vector for using in fieldsExpanded() */
+// Field::Vector *fieldsExpanded;
+ QueryColumnInfo::Vector *fieldsExpanded;
+
+ /*! Temporary field vector containing internal fields used for lookup columns. */
+ QueryColumnInfo::Vector *internalFields;
+
+ /*! Temporary, used to cache sum of expanded fields and internal fields (+rowid) used for lookup columns.
+ Contains not auto-deleted items.*/
+ QueryColumnInfo::Vector *fieldsExpandedWithInternalAndRowID;
+
+ /*! Temporary, used to cache sum of expanded fields and internal fields used for lookup columns.
+ Contains not auto-deleted items.*/
+ QueryColumnInfo::Vector *fieldsExpandedWithInternal;
+
+ /*! A list of fields for ORDER BY section. @see QuerySchema::orderByColumnList(). */
+ OrderByColumnList orderByColumnList;
+
+ /*! A cache for autoIncrementFields(). */
+ QueryColumnInfo::List *autoincFields;
+
+ /*! A cache for autoIncrementSQLFieldsList(). */
+ QString autoIncrementSQLFieldsList;
+ QGuardedPtr<Driver> lastUsedDriverForAutoIncrementSQLFieldsList;
+
+ /*! A map for fast lookup of query columns' order (unexpanded version). */
+ QMap<QueryColumnInfo*,int> *columnsOrder;
+
+ /*! A map for fast lookup of query columns' order (unexpanded version without asterisks). */
+ QMap<QueryColumnInfo*,int> *columnsOrderWithoutAsterisks;
+
+ /*! A map for fast lookup of query columns' order.
+ This is exactly opposite information compared to vector returned
+ by fieldsExpanded() */
+ QMap<QueryColumnInfo*,int> *columnsOrderExpanded;
+
+// QValueList<bool> detailedVisibility;
+
+ /*! order of PKEY fields (e.g. for updateRow() ) */
+ QValueVector<int> *pkeyFieldsOrder;
+
+ /*! number of PKEY fields within the query */
+ uint pkeyFieldsCount;
+
+ /*! forced (predefined) statement */
+ QString statement;
+
+ /*! Relationships defined for this query. */
+ Relationship::List relations;
+
+ /*! Information about columns bound to tables.
+ Used a table is used in FROM section more than once
+ (using table aliases).
+
+ This list is updated by insertField(uint position, Field *field,
+ int bindToTable, bool visible), using bindToTable parameter.
+
+ Example: for this statement:
+ SELECT t1.a, othertable.x, t2.b FROM table t1, table t2, othertable;
+ tablesBoundToColumns list looks like this:
+ [ 0, -1, 1 ]
+ - first column is bound to table 0 "t1"
+ - second coulmn is not specially bound (othertable.x isn't ambiguous)
+ - third column is bound to table 1 "t2"
+ */
+ QValueVector<int> tablesBoundToColumns;
+
+ /*! Collects table positions for aliases: used in tablePositionForAlias(). */
+ QAsciiDict<int> tablePositionsForAliases;
+
+ /*! Collects column positions for aliases: used in columnPositionForAlias(). */
+ QAsciiDict<int> columnPositionsForAliases;
+
+ /*! WHERE expression */
+ BaseExpr *whereExpr;
+
+ QDict<QueryColumnInfo> columnInfosByNameExpanded;
+
+ QDict<QueryColumnInfo> columnInfosByName; //!< Same as columnInfosByNameExpanded but asterisks are skipped
+
+ //! field schemas created for multiple joined columns like a||' '||b||' '||c
+ Field::List *ownedVisibleColumns;
+
+ /*! Set by insertField(): true, if aliases for expression columns should
+ be generated on next columnAlias() call. */
+ bool regenerateExprAliases : 1;
+};
+}
+
+//=======================================
+
+OrderByColumn::OrderByColumn()
+ : m_column(0)
+ , m_pos(-1)
+ , m_field(0)
+ , m_ascending(true)
+{
+}
+
+OrderByColumn::OrderByColumn(QueryColumnInfo& column, bool ascending, int pos)
+ : m_column(&column)
+ , m_pos(pos)
+ , m_field(0)
+ , m_ascending(ascending)
+{
+}
+
+OrderByColumn::OrderByColumn(Field& field, bool ascending)
+ : m_column(0)
+ , m_pos(-1)
+ , m_field(&field)
+ , m_ascending(ascending)
+{
+}
+
+OrderByColumn::~OrderByColumn()
+{
+}
+
+QString OrderByColumn::debugString() const
+{
+ QString orderString( m_ascending ? "ascending" : "descending" );
+ if (m_column) {
+ if (m_pos>-1)
+ return QString("COLUMN_AT_POSITION_%1(%2, %3)")
+ .arg(m_pos+1).arg(m_column->debugString()).arg(orderString);
+ else
+ return QString("COLUMN(%1, %2)").arg(m_column->debugString()).arg(orderString);
+ }
+ return m_field ? QString("FIELD(%1, %2)").arg(m_field->debugString()).arg(orderString)
+ : QString("NONE");
+}
+
+QString OrderByColumn::toSQLString(bool includeTableName, Driver *drv, int identifierEscaping) const
+{
+ const QString orderString( m_ascending ? "" : " DESC" );
+ QString fieldName, tableName;
+ if (m_column) {
+ if (m_pos>-1)
+ return QString::number(m_pos+1) + orderString;
+ else {
+ if (includeTableName && m_column->alias.isEmpty()) {
+ tableName = m_column->field->table()->name();
+ if (drv)
+ tableName = drv->escapeIdentifier(tableName, identifierEscaping);
+ tableName += ".";
+ }
+ fieldName = m_column->aliasOrName();
+ if (drv)
+ fieldName = drv->escapeIdentifier(fieldName, identifierEscaping);
+ }
+ }
+ else {
+ if (includeTableName) {
+ tableName = m_field->table()->name();
+ if (drv)
+ tableName = drv->escapeIdentifier(tableName, identifierEscaping);
+ tableName += ".";
+ }
+ fieldName = m_field ? m_field->name() : "??"/*error*/;
+ if (drv)
+ fieldName = drv->escapeIdentifier(fieldName, identifierEscaping);
+ }
+ return tableName + fieldName + orderString;
+}
+
+//=======================================
+
+OrderByColumnList::OrderByColumnList()
+ : OrderByColumnListBase()
+{
+}
+
+bool OrderByColumnList::appendFields(QuerySchema& querySchema,
+ const QString& field1, bool ascending1,
+ const QString& field2, bool ascending2,
+ const QString& field3, bool ascending3,
+ const QString& field4, bool ascending4,
+ const QString& field5, bool ascending5)
+{
+ uint numAdded = 0;
+#define ADD_COL(fieldName, ascending) \
+ if (ok && !fieldName.isEmpty()) { \
+ if (!appendField( querySchema, fieldName, ascending )) \
+ ok = false; \
+ else \
+ numAdded++; \
+ }
+ bool ok = true;
+ ADD_COL(field1, ascending1);
+ ADD_COL(field2, ascending2);
+ ADD_COL(field3, ascending3);
+ ADD_COL(field4, ascending4);
+ ADD_COL(field5, ascending5);
+#undef ADD_COL
+ if (ok)
+ return true;
+ for (uint i=0; i<numAdded; i++)
+ pop_back();
+ return false;
+}
+
+OrderByColumnList::~OrderByColumnList()
+{
+}
+
+void OrderByColumnList::appendColumn(QueryColumnInfo& columnInfo, bool ascending)
+{
+ appendColumn( OrderByColumn(columnInfo, ascending) );
+}
+
+bool OrderByColumnList::appendColumn(QuerySchema& querySchema, bool ascending, int pos)
+{
+ QueryColumnInfo::Vector fieldsExpanded( querySchema.fieldsExpanded() );
+ QueryColumnInfo* ci = (pos >= (int)fieldsExpanded.size()) ? 0 : fieldsExpanded[pos];
+ if (!ci)
+ return false;
+ appendColumn( OrderByColumn(*ci, ascending, pos) );
+ return true;
+}
+
+void OrderByColumnList::appendField(Field& field, bool ascending)
+{
+ appendColumn( OrderByColumn(field, ascending) );
+}
+
+bool OrderByColumnList::appendField(QuerySchema& querySchema,
+ const QString& fieldName, bool ascending)
+{
+ QueryColumnInfo *columnInfo = querySchema.columnInfo( fieldName );
+ if (columnInfo) {
+ appendColumn( OrderByColumn(*columnInfo, ascending) );
+ return true;
+ }
+ Field *field = querySchema.findTableField(fieldName);
+ if (field) {
+ appendColumn( OrderByColumn(*field, ascending) );
+ return true;
+ }
+ KexiDBWarn << "OrderByColumnList::addColumn(QuerySchema& querySchema, "
+ "const QString& column, bool ascending): no such field \"" << fieldName << "\"" << endl;
+ return false;
+}
+
+void OrderByColumnList::appendColumn(const OrderByColumn& column)
+{
+ append( column );
+}
+
+QString OrderByColumnList::debugString() const
+{
+ if (isEmpty())
+ return "NONE";
+ QString dbg;
+ for (OrderByColumn::ListConstIterator it=constBegin(); it!=constEnd(); ++it) {
+ if (!dbg.isEmpty())
+ dbg += "\n";
+ dbg += (*it).debugString();
+ }
+ return dbg;
+}
+
+QString OrderByColumnList::toSQLString(bool includeTableNames, Driver *drv, int identifierEscaping) const
+{
+ QString string;
+ for (OrderByColumn::ListConstIterator it=constBegin(); it!=constEnd(); ++it) {
+ if (!string.isEmpty())
+ string += ", ";
+ string += (*it).toSQLString(includeTableNames, drv, identifierEscaping);
+ }
+ return string;
+}
+
+//=======================================
+
+QuerySchema::QuerySchema()
+ : FieldList(false)//fields are not owned by QuerySchema object
+ , SchemaData(KexiDB::QueryObjectType)
+ , d( new QuerySchemaPrivate(this) )
+{
+ init();
+}
+
+QuerySchema::QuerySchema(TableSchema& tableSchema)
+ : FieldList(false)
+ , SchemaData(KexiDB::QueryObjectType)
+ , d( new QuerySchemaPrivate(this) )
+{
+ d->masterTable = &tableSchema;
+ init();
+/* if (!d->masterTable) {
+ KexiDBWarn << "QuerySchema(TableSchema*): !d->masterTable" << endl;
+ m_name = QString::null;
+ return;
+ }*/
+ addTable(d->masterTable);
+ //defaults:
+ //inherit name from a table
+ m_name = d->masterTable->name();
+ //inherit caption from a table
+ m_caption = d->masterTable->caption();
+
+//replaced by explicit field list: //add all fields of the table as asterisk:
+//replaced by explicit field list: addField( new QueryAsterisk(this) );
+
+ // add explicit field list to avoid problems (e.g. with fields added outside of Kexi):
+ for (Field::ListIterator it( d->masterTable->fieldsIterator() ); it.current(); ++it) {
+ addField( it.current() );
+ }
+}
+
+QuerySchema::QuerySchema(const QuerySchema& querySchema)
+ : FieldList(querySchema, false /* !deepCopyFields */)
+ , SchemaData(querySchema)
+ , d( new QuerySchemaPrivate(this, querySchema.d) )
+{
+ //only deep copy query asterisks
+ for (Field::ListIterator f_it(querySchema.m_fields); f_it.current(); ++f_it) {
+ Field *f;
+ if (dynamic_cast<QueryAsterisk*>( f_it.current() )) {
+ f = f_it.current()->copy();
+ if (static_cast<const KexiDB::FieldList *>(f_it.current()->m_parent) == &querySchema)
+ f->m_parent = this;
+ }
+ else
+ f = f_it.current();
+ addField( f );
+ }
+}
+
+QuerySchema::~QuerySchema()
+{
+ delete d;
+}
+
+void QuerySchema::init()
+{
+ m_type = KexiDB::QueryObjectType;
+//m_fields_by_name.setAutoDelete( true ); //because we're using QueryColumnInfoEntry objects
+}
+
+void QuerySchema::clear()
+{
+ FieldList::clear();
+ SchemaData::clear();
+ d->clear();
+}
+
+FieldList& QuerySchema::insertField(uint position, Field *field, bool visible)
+{
+ return insertField(position, field, -1/*don't bind*/, visible);
+}
+
+/*virtual*/
+FieldList& QuerySchema::insertField(uint position, Field *field)
+{
+ return insertField( position, field, -1/*don't bind*/, true );
+}
+
+FieldList& QuerySchema::insertField(uint position, Field *field,
+ int bindToTable, bool visible)
+{
+ if (!field) {
+ KexiDBWarn << "QuerySchema::insertField(): !field" << endl;
+ return *this;
+ }
+
+ if (position>m_fields.count()) {
+ KexiDBWarn << "QuerySchema::insertField(): position (" << position << ") out of range" << endl;
+ return *this;
+ }
+ if (!field->isQueryAsterisk() && !field->isExpression() && !field->table()) {
+ KexiDBWarn << "QuerySchema::insertField(): WARNING: field '"<<field->name()
+ <<"' must contain table information!" <<endl;
+ return *this;
+ }
+ if (fieldCount()>=d->visibility.size()) {
+ d->visibility.resize(d->visibility.size()*2);
+ d->tablesBoundToColumns.resize(d->tablesBoundToColumns.size()*2);
+ }
+ d->clearCachedData();
+ FieldList::insertField(position, field);
+ if (field->isQueryAsterisk()) {
+ d->asterisks.append(field);
+ //if this is single-table asterisk,
+ //add a table to list if doesn't exist there:
+ if (field->table() && (d->tables.findRef(field->table())==-1))
+ d->tables.append(field->table());
+ }
+ else if (field->table()) {
+ //add a table to list if doesn't exist there:
+ if (d->tables.findRef(field->table())==-1)
+ d->tables.append(field->table());
+ }
+// //visible by default
+// setFieldVisible(field, true);
+// d->visibility.setBit(fieldCount()-1, visible);
+ //update visibility
+ //--move bits to make a place for a new one
+ for (uint i=fieldCount()-1; i>position; i--)
+ d->visibility.setBit(i, d->visibility.testBit(i-1));
+ d->visibility.setBit(position, visible);
+
+ //bind to table
+ if (bindToTable < -1 && bindToTable>(int)d->tables.count()) {
+ KexiDBWarn << "QuerySchema::insertField(): bindToTable (" << bindToTable
+ << ") out of range" << endl;
+ bindToTable = -1;
+ }
+ //--move items to make a place for a new one
+ for (uint i=fieldCount()-1; i>position; i--)
+ d->tablesBoundToColumns[i] = d->tablesBoundToColumns[i-1];
+ d->tablesBoundToColumns[ position ] = bindToTable;
+
+ KexiDBDbg << "QuerySchema::insertField(): bound to table (" << bindToTable << "): " <<endl;
+ if (bindToTable==-1)
+ KexiDBDbg << " <NOT SPECIFIED>" << endl;
+ else
+ KexiDBDbg << " name=" << d->tables.at(bindToTable)->name()
+ << " alias=" << tableAlias(bindToTable) << endl;
+ QString s;
+ for (uint i=0; i<fieldCount();i++)
+ s+= (QString::number(d->tablesBoundToColumns[i]) + " ");
+ KexiDBDbg << "tablesBoundToColumns == [" << s << "]" <<endl;
+
+ if (field->isExpression())
+ d->regenerateExprAliases = true;
+
+ return *this;
+}
+
+int QuerySchema::tableBoundToColumn(uint columnPosition) const
+{
+ if (columnPosition > d->tablesBoundToColumns.count()) {
+ KexiDBWarn << "QuerySchema::tableBoundToColumn(): columnPosition (" << columnPosition
+ << ") out of range" << endl;
+ return -1;
+ }
+ return d->tablesBoundToColumns[columnPosition];
+}
+
+KexiDB::FieldList& QuerySchema::addField(KexiDB::Field* field, bool visible)
+{
+ return insertField(m_fields.count(), field, visible);
+}
+
+KexiDB::FieldList& QuerySchema::addField(KexiDB::Field* field, int bindToTable,
+ bool visible)
+{
+ return insertField(m_fields.count(), field, bindToTable, visible);
+}
+
+void QuerySchema::removeField(KexiDB::Field *field)
+{
+ if (!field)
+ return;
+ d->clearCachedData();
+ if (field->isQueryAsterisk()) {
+ d->asterisks.remove(field); //this will destroy this asterisk
+ }
+//TODO: should we also remove table for this field or asterisk?
+ FieldList::removeField(field);
+}
+
+FieldList& QuerySchema::addExpression(BaseExpr* expr, bool visible)
+{
+ return addField( new Field(this, expr), visible );
+}
+
+bool QuerySchema::isColumnVisible(uint position) const
+{
+ return (position < fieldCount()) ? d->visibility.testBit(position) : false;
+}
+
+void QuerySchema::setColumnVisible(uint position, bool v)
+{
+ if (position < fieldCount())
+ d->visibility.setBit(position, v);
+}
+
+FieldList& QuerySchema::addAsterisk(QueryAsterisk *asterisk, bool visible)
+{
+ if (!asterisk)
+ return *this;
+ //make unique name
+ asterisk->m_name = (asterisk->table() ? asterisk->table()->name() + ".*" : "*")
+ + QString::number(asterisks()->count());
+ return addField(asterisk, visible);
+}
+
+Connection* QuerySchema::connection() const
+{
+ TableSchema *mt = masterTable();
+ return mt ? mt->connection() : 0;
+}
+
+QString QuerySchema::debugString()
+{
+ QString dbg;
+ dbg.reserve(1024);
+ //fields
+ TableSchema *mt = masterTable();
+ dbg = QString("QUERY ") + schemaDataDebugString() + "\n"
+ + "-masterTable=" + (mt ? mt->name() :"<NULL>")
+ + "\n-COLUMNS:\n"
+ + ((fieldCount()>0) ? FieldList::debugString() : "<NONE>") + "\n"
+ + "-FIELDS EXPANDED ";
+
+ QString dbg1;
+ uint fieldsExpandedCount = 0;
+ if (fieldCount()>0) {
+ QueryColumnInfo::Vector fe( fieldsExpanded() );
+ fieldsExpandedCount = fe.size();
+ for ( uint i=0; i < fieldsExpandedCount; i++ ) {
+ QueryColumnInfo *ci = fe[i];
+ if (!dbg1.isEmpty())
+ dbg1 += ",\n";
+ dbg1 += ci->debugString();
+ }
+ dbg1 += "\n";
+ }
+ else {
+ dbg1 = "<NONE>\n";
+ }
+ dbg1.prepend( QString("(%1):\n").arg(fieldsExpandedCount) );
+ dbg += dbg1;
+
+ //it's safer to delete fieldsExpanded for now
+ // (debugString() could be called before all fields are added)
+//causes a crash d->clearCachedData();
+
+ //bindings
+ QString dbg2;
+ dbg2.reserve(512);
+ for (uint i = 0; i<fieldCount(); i++) {
+ int tablePos = tableBoundToColumn(i);
+ if (tablePos>=0) {
+ QCString tAlias = tableAlias(tablePos);
+ if (!tAlias.isEmpty()) {
+ dbg2 += (QString::fromLatin1(" field \"") + FieldList::field(i)->name()
+ + "\" uses alias \"" + QString(tAlias) + "\" of table \""
+ + d->tables.at(tablePos)->name() + "\"\n");
+ }
+ }
+ }
+ if (!dbg2.isEmpty()) {
+ dbg += "\n-BINDINGS:\n";
+ dbg += dbg2;
+ }
+
+ //tables
+ TableSchema *table;
+ QString table_names;
+ table_names.reserve(512);
+ for ( table = d->tables.first(); table; table = d->tables.next() ) {
+ if (!table_names.isEmpty())
+ table_names += ", ";
+ table_names += (QString("'") + table->name() + "'");
+ }
+ if (d->tables.isEmpty())
+ table_names = "<NONE>";
+ dbg += (QString("-TABLES:\n") + table_names);
+ QString aliases;
+ if (!d->hasColumnAliases())
+ aliases = "<NONE>\n";
+ else {
+ Field::ListIterator it( m_fields );
+ for (int i=0; it.current(); ++it, i++) {
+ QCString *alias = d->columnAlias(i);
+ if (alias)
+ aliases += (QString("field #%1: ").arg(i)
+ + (it.current()->name().isEmpty() ? "<noname>" : it.current()->name())
+ + " -> " + (const char*)*alias + "\n");
+ }
+ }
+ //aliases
+ dbg += QString("\n-COLUMN ALIASES:\n" + aliases);
+ if (d->tableAliases.isEmpty())
+ aliases = "<NONE>";
+ else {
+ aliases = "";
+ TableSchema::ListIterator t_it(d->tables);
+ for (int i=0; t_it.current(); ++t_it, i++) {
+ QCString *alias = d->tableAliases[i];
+ if (alias)
+ aliases += (QString("table #%1: ").arg(i)
+ + (t_it.current()->name().isEmpty() ? "<noname>" : t_it.current()->name())
+ + " -> " + (const char*)*alias + "\n");
+ }
+ }
+ dbg += QString("-TABLE ALIASES:\n" + aliases);
+ QString where = d->whereExpr ? d->whereExpr->debugString() : QString::null;
+ if (!where.isEmpty())
+ dbg += (QString("\n-WHERE EXPRESSION:\n") + where);
+ if (!orderByColumnList().isEmpty())
+ dbg += (QString("\n-ORDER BY (%1):\n").arg(orderByColumnList().count())
+ + orderByColumnList().debugString());
+ return dbg;
+}
+
+TableSchema* QuerySchema::masterTable() const
+{
+ if (d->masterTable)
+ return d->masterTable;
+ if (d->tables.isEmpty())
+ return 0;
+
+ //try to find master table if there's only one table (with possible aliasses)
+ int num = 0;
+ QString tableNameLower;
+ for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) {
+ if (!tableNameLower.isEmpty() && it.current()->name().lower()!=tableNameLower) {
+ //two or more different tables
+ return 0;
+ }
+ tableNameLower = tableAlias(num);
+ }
+ return d->tables.first();
+}
+
+void QuerySchema::setMasterTable(TableSchema *table)
+{
+ if (table)
+ d->masterTable=table;
+}
+
+TableSchema::List* QuerySchema::tables() const
+{
+ return &d->tables;
+}
+
+void QuerySchema::addTable(TableSchema *table, const QCString& alias)
+{
+ KexiDBDbg << "QuerySchema::addTable() " << (void *)table
+ << " alias=" << alias << endl;
+ if (!table)
+ return;
+
+ //only append table if:
+ //-it has alias
+ //-it has no alias but there is no such table on the list
+ if (alias.isEmpty() && d->tables.findRef(table)!=-1) {
+ const QString& tableNameLower = table->name().lower();
+ const QString& aliasLower = QString(alias.lower());
+ int num = 0;
+ for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) {
+ if (it.current()->name().lower()==tableNameLower) {
+ const QString& tAlias = tableAlias(num);
+ if (tAlias == aliasLower) {
+ KexiDBWarn << "QuerySchema::addTable(): table with \""
+ << tAlias << "\" alias already added!" << endl;
+ return;
+ }
+ }
+ }
+ }
+
+ d->tables.append(table);
+
+ if (!alias.isEmpty())
+ setTableAlias(d->tables.count()-1, alias);
+}
+
+void QuerySchema::removeTable(TableSchema *table)
+{
+ if (!table)
+ return;
+ if (d->masterTable == table)
+ d->masterTable = 0;
+ d->tables.remove(table);
+ //todo: remove fields!
+}
+
+TableSchema* QuerySchema::table(const QString& tableName) const
+{
+//TODO: maybe use tables_byname?
+ for (TableSchema::ListIterator it(d->tables); it.current(); ++it) {
+ if (it.current()->name().lower()==tableName.lower())
+ return it.current();
+ }
+ return 0;
+}
+
+bool QuerySchema::contains(TableSchema *table) const
+{
+ return d->tables.findRef(table)!=-1;
+}
+
+Field* QuerySchema::findTableField(const QString &tableOrTableAndFieldName) const
+{
+ QString tableName, fieldName;
+ if (!KexiDB::splitToTableAndFieldParts(tableOrTableAndFieldName,
+ tableName, fieldName, KexiDB::SetFieldNameIfNoTableName)) {
+ return 0;
+ }
+ if (tableName.isEmpty()) {
+ for (TableSchema::ListIterator it(d->tables); it.current(); ++it) {
+ if (it.current()->field(fieldName))
+ return it.current()->field(fieldName);
+ }
+ return 0;
+ }
+ TableSchema *tableSchema = table(tableName);
+ if (!tableSchema)
+ return 0;
+ return tableSchema->field(fieldName);
+}
+
+QCString QuerySchema::columnAlias(uint position) const
+{
+ QCString *a = d->columnAlias(position);
+ return a ? *a : QCString();
+}
+
+bool QuerySchema::hasColumnAlias(uint position) const
+{
+ return d->columnAlias(position)!=0;
+}
+
+void QuerySchema::setColumnAlias(uint position, const QCString& alias)
+{
+ if (position >= m_fields.count()) {
+ KexiDBWarn << "QuerySchema::setColumnAlias(): position (" << position
+ << ") out of range!" << endl;
+ return;
+ }
+ QCString fixedAlias = alias.stripWhiteSpace();
+ Field *f = FieldList::field(position);
+ if (f->captionOrName().isEmpty() && fixedAlias.isEmpty()) {
+ KexiDBWarn << "QuerySchema::setColumnAlias(): position (" << position
+ << ") could not remove alias when no name is specified for expression column!" << endl;
+ return;
+ }
+ d->setColumnAlias(position, fixedAlias);
+}
+
+QCString QuerySchema::tableAlias(uint position) const
+{
+ QCString *a = d->tableAliases[position];
+ return a ? *a : QCString();
+}
+
+int QuerySchema::tablePositionForAlias(const QCString& name) const
+{
+ int *num = d->tablePositionsForAliases[name];
+ if (!num)
+ return -1;
+ return *num;
+}
+
+int QuerySchema::tablePosition(const QString& tableName) const
+{
+ int num = 0;
+ for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) {
+ if (it.current()->name().lower()==tableName.lower())
+ return num;
+ }
+ return -1;
+}
+
+QValueList<int> QuerySchema::tablePositions(const QString& tableName) const
+{
+ int num = 0;
+ QValueList<int> result;
+ const QString& tableNameLower = tableName.lower();
+ for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) {
+ if (it.current()->name().lower()==tableNameLower) {
+ result += num;
+ }
+ }
+ return result;
+}
+
+bool QuerySchema::hasTableAlias(uint position) const
+{
+ return d->tableAliases[position]!=0;
+}
+
+int QuerySchema::columnPositionForAlias(const QCString& name) const
+{
+ int *num = d->columnPositionsForAliases[name];
+ if (!num)
+ return -1;
+ return *num;
+}
+
+void QuerySchema::setTableAlias(uint position, const QCString& alias)
+{
+ if (position >= d->tables.count()) {
+ KexiDBWarn << "QuerySchema::setTableAlias(): position (" << position
+ << ") out of range!" << endl;
+ return;
+ }
+ QCString fixedAlias = alias.stripWhiteSpace();
+ if (fixedAlias.isEmpty()) {
+ QCString *oldAlias = d->tableAliases.take(position);
+ if (oldAlias) {
+ d->tablePositionsForAliases.remove(*oldAlias);
+ delete oldAlias;
+ }
+// d->maxIndexWithTableAlias = -1;
+ }
+ else {
+ d->tableAliases.replace(position, new QCString(fixedAlias));
+ d->tablePositionsForAliases.replace(fixedAlias, new int(position));
+// d->maxIndexWithTableAlias = QMAX( d->maxIndexWithTableAlias, (int)index );
+ }
+}
+
+Relationship::List* QuerySchema::relationships() const
+{
+ return &d->relations;
+}
+
+Field::List* QuerySchema::asterisks() const
+{
+ return &d->asterisks;
+}
+
+QString QuerySchema::statement() const
+{
+ return d->statement;
+}
+
+void QuerySchema::setStatement(const QString &s)
+{
+ d->statement = s;
+}
+
+Field* QuerySchema::field(const QString& identifier, bool expanded)
+{
+ QueryColumnInfo *ci = columnInfo(identifier, expanded);
+ return ci ? ci->field : 0;
+}
+
+QueryColumnInfo* QuerySchema::columnInfo(const QString& identifier, bool expanded)
+{
+ computeFieldsExpanded();
+ return expanded ? d->columnInfosByNameExpanded[identifier] : d->columnInfosByName[identifier];
+}
+
+QueryColumnInfo::Vector QuerySchema::fieldsExpanded(FieldsExpandedOptions options)
+{
+ computeFieldsExpanded();
+ if (options == WithInternalFields || options == WithInternalFieldsAndRowID) {
+ //a ref to a proper pointer (as we cache the vector for two cases)
+ QueryColumnInfo::Vector*& tmpFieldsExpandedWithInternal =
+ (options == WithInternalFields) ? d->fieldsExpandedWithInternal : d->fieldsExpandedWithInternalAndRowID;
+ //special case
+ if (!tmpFieldsExpandedWithInternal) {
+ //glue expanded and internal fields and cache it
+ const uint size = d->fieldsExpanded->count()
+ + (d->internalFields ? d->internalFields->count() : 0)
+ + ((options == WithInternalFieldsAndRowID) ? 1 : 0) /*ROWID*/;
+ tmpFieldsExpandedWithInternal = new QueryColumnInfo::Vector( size );
+ const uint fieldsExpandedVectorSize = d->fieldsExpanded->size();
+ for (uint i=0; i<fieldsExpandedVectorSize; i++)
+ tmpFieldsExpandedWithInternal->insert(i, d->fieldsExpanded->at(i));
+ const uint internalFieldsCount = d->internalFields ? d->internalFields->size() : 0;
+ if (internalFieldsCount > 0) {
+ for (uint i=0; i < internalFieldsCount; i++)
+ tmpFieldsExpandedWithInternal->insert(
+ fieldsExpandedVectorSize + i, d->internalFields->at(i));
+ }
+ if (options == WithInternalFieldsAndRowID) {
+ if (!d->fakeRowIDField) {
+ d->fakeRowIDField = new Field("rowID", Field::BigInteger);
+ d->fakeRowIDCol = new QueryColumnInfo(d->fakeRowIDField, QCString(), true);
+ }
+ tmpFieldsExpandedWithInternal->insert(
+ fieldsExpandedVectorSize + internalFieldsCount, d->fakeRowIDCol );
+ }
+ }
+ return *tmpFieldsExpandedWithInternal;
+ }
+
+ if (options == Default)
+ return *d->fieldsExpanded;
+
+ //options == Unique:
+ QDict<char> columnsAlreadyFound;
+ QueryColumnInfo::Vector result( d->fieldsExpanded->count() ); //initial size is set
+// QMapConstIterator<QueryColumnInfo*, bool> columnsAlreadyFoundIt;
+ //compute unique list
+ uint uniqueListCount = 0;
+ for (uint i=0; i<d->fieldsExpanded->count(); i++) {
+ QueryColumnInfo *ci = (*d->fieldsExpanded)[i];
+// columnsAlreadyFoundIt = columnsAlreadyFound.find(ci);
+// uint foundColumnIndex = -1;
+ if (!columnsAlreadyFound[ci->aliasOrName()]) {// columnsAlreadyFoundIt==columnsAlreadyFound.constEnd())
+ columnsAlreadyFound.insert(ci->aliasOrName(), (char*)1);
+ result.insert(uniqueListCount++, ci);
+ }
+ }
+ result.resize(uniqueListCount); //update result size
+ return result;
+}
+
+QueryColumnInfo::Vector QuerySchema::internalFields()
+{
+ computeFieldsExpanded();
+ return d->internalFields ? *d->internalFields : QueryColumnInfo::Vector();
+}
+
+QueryColumnInfo* QuerySchema::expandedOrInternalField(uint index)
+{
+ QueryColumnInfo::Vector vector = fieldsExpanded(WithInternalFields);
+ return (index < vector.size()) ? vector[index] : 0;
+}
+
+inline QString lookupColumnKey(Field *foreignField, Field* field)
+{
+ QString res;
+ if (field->table()) // can be 0 for anonymous fields built as joined multiple visible columns
+ res = field->table()->name() + ".";
+ return res + field->name() + "_" + foreignField->table()->name() + "." + foreignField->name();
+}
+
+void QuerySchema::computeFieldsExpanded()
+{
+ if (d->fieldsExpanded)
+ return;
+
+ if (!d->columnsOrder) {
+ d->columnsOrder = new QMap<QueryColumnInfo*,int>();
+ d->columnsOrderWithoutAsterisks = new QMap<QueryColumnInfo*,int>();
+ }
+ else {
+ d->columnsOrder->clear();
+ d->columnsOrderWithoutAsterisks->clear();
+ }
+ if (d->ownedVisibleColumns)
+ d->ownedVisibleColumns->clear();
+
+ //collect all fields in a list (not a vector yet, because we do not know its size)
+ QueryColumnInfo::List list; //temporary
+ QueryColumnInfo::List lookup_list; //temporary, for collecting additional fields related to lookup fields
+ QMap<QueryColumnInfo*, bool> columnInfosOutsideAsterisks; //helper for filling d->columnInfosByName
+ uint i = 0;
+ uint fieldPosition = 0;
+ uint numberOfColumnsWithMultipleVisibleFields = 0; //used to find an unique name for anonymous field
+ Field *f;
+ for (Field::ListIterator it = fieldsIterator(); (f = it.current()); ++it, fieldPosition++) {
+ if (f->isQueryAsterisk()) {
+ if (static_cast<QueryAsterisk*>(f)->isSingleTableAsterisk()) {
+ Field::List *ast_fields = static_cast<QueryAsterisk*>(f)->table()->fields();
+ for (Field *ast_f = ast_fields->first(); ast_f; ast_f=ast_fields->next()) {
+// d->detailedVisibility += isFieldVisible(fieldPosition);
+ QueryColumnInfo *ci = new QueryColumnInfo(ast_f, QCString()/*no field for asterisk!*/,
+ isColumnVisible(fieldPosition));
+ list.append( ci );
+ KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) columns order: "
+ << ci->debugString() << " at position " << fieldPosition << endl;
+ d->columnsOrder->insert(ci, fieldPosition);
+// list.append(ast_f);
+ }
+ }
+ else {//all-tables asterisk: iterate through table list
+ for (TableSchema *table = d->tables.first(); table; table = d->tables.next()) {
+ //add all fields from this table
+ Field::List *tab_fields = table->fields();
+ for (Field *tab_f = tab_fields->first(); tab_f; tab_f = tab_fields->next()) {
+//! \todo (js): perhaps not all fields should be appended here
+// d->detailedVisibility += isFieldVisible(fieldPosition);
+// list.append(tab_f);
+ QueryColumnInfo *ci = new QueryColumnInfo(tab_f, QCString()/*no field for asterisk!*/,
+ isColumnVisible(fieldPosition));
+ list.append( ci );
+ KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) columns order: "
+ << ci->debugString() << " at position " << fieldPosition << endl;
+ d->columnsOrder->insert(ci, fieldPosition);
+ }
+ }
+ }
+ }
+ else {
+ //a single field
+// d->detailedVisibility += isFieldVisible(fieldPosition);
+ QueryColumnInfo *ci = new QueryColumnInfo(f, columnAlias(fieldPosition), isColumnVisible(fieldPosition));
+ list.append( ci );
+ columnInfosOutsideAsterisks.insert( ci, true );
+ KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) column's order: "
+ << ci->debugString() << " at position " << fieldPosition << endl;
+ d->columnsOrder->insert(ci, fieldPosition);
+ d->columnsOrderWithoutAsterisks->insert(ci, fieldPosition);
+
+ //handle lookup field schema
+ LookupFieldSchema *lookupFieldSchema = f->table() ? f->table()->lookupFieldSchema( *f ) : 0;
+ if (!lookupFieldSchema || lookupFieldSchema->boundColumn()<0)
+ continue;
+ // Lookup field schema found:
+ // Now we also need to fetch "visible" value from the lookup table, not only the value of binding.
+ // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken)
+ // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField"
+ LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource();
+ if (rowSource.type() == LookupFieldSchema::RowSource::Table) {
+ TableSchema *lookupTable = connection()->tableSchema( rowSource.name() );
+ FieldList* visibleColumns = 0;
+ Field *boundField = 0;
+ if (lookupTable
+ && (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount()
+ && (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() ))
+ && (boundField = lookupTable->field( lookupFieldSchema->boundColumn() )))
+ {
+ Field *visibleColumn = 0;
+ // for single visible column, just add it as-is
+ if (visibleColumns->fieldCount() == 1) {
+ visibleColumn = visibleColumns->fields()->first();
+ }
+ else {
+ // for multiple visible columns, build an expression column
+ // (the expression object will be owned by column info)
+ visibleColumn = new Field();
+ visibleColumn->setName(
+ QString::fromLatin1("[multiple_visible_fields_%1]")
+ .arg( ++numberOfColumnsWithMultipleVisibleFields ));
+ visibleColumn->setExpression(
+ new ConstExpr(CHARACTER_STRING_LITERAL, QVariant()/*not important*/));
+ if (!d->ownedVisibleColumns) {
+ d->ownedVisibleColumns = new Field::List();
+ d->ownedVisibleColumns->setAutoDelete(true);
+ }
+ d->ownedVisibleColumns->append( visibleColumn ); // remember to delete later
+ }
+
+ lookup_list.append(
+ new QueryColumnInfo(visibleColumn, QCString(), true/*visible*/, ci/*foreign*/) );
+/*
+ //add visibleField to the list of SELECTed fields if it is not yes present there
+ if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) {
+ if (!table( visibleField->table()->name() )) {
+ }
+ if (!sql.isEmpty())
+ sql += QString::fromLatin1(", ");
+ sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "."
+ + escapeIdentifier(visibleField->name(), drvEscaping));
+ }*/
+ }
+ delete visibleColumns;
+ }
+ else if (rowSource.type() == LookupFieldSchema::RowSource::Query) {
+ QuerySchema *lookupQuery = connection()->querySchema( rowSource.name() );
+ if (!lookupQuery)
+ continue;
+ const QueryColumnInfo::Vector lookupQueryFieldsExpanded( lookupQuery->fieldsExpanded() );
+ if ((uint)lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count())
+ continue;
+ QueryColumnInfo *boundColumnInfo = 0;
+ if (!(boundColumnInfo = lookupQueryFieldsExpanded[ lookupFieldSchema->boundColumn() ]))
+ continue;
+ Field *boundField = boundColumnInfo->field;
+ if (!boundField)
+ continue;
+ const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() );
+ bool ok = true;
+ // all indices in visibleColumns should be in [0..lookupQueryFieldsExpanded.size()-1]
+ foreach (QValueList<uint>::ConstIterator, visibleColumnsIt, visibleColumns) {
+ if ((*visibleColumnsIt) >= lookupQueryFieldsExpanded.count()) {
+ ok = false;
+ break;
+ }
+ }
+ if (!ok)
+ continue;
+ Field *visibleColumn = 0;
+ // for single visible column, just add it as-is
+ if (visibleColumns.count() == 1) {
+ visibleColumn = lookupQueryFieldsExpanded[ visibleColumns.first() ]->field;
+ }
+ else {
+ // for multiple visible columns, build an expression column
+ // (the expression object will be owned by column info)
+ visibleColumn = new Field();
+ visibleColumn->setName(
+ QString::fromLatin1("[multiple_visible_fields_%1]")
+ .arg( ++numberOfColumnsWithMultipleVisibleFields ));
+ visibleColumn->setExpression(
+ new ConstExpr(CHARACTER_STRING_LITERAL, QVariant()/*not important*/));
+ if (!d->ownedVisibleColumns) {
+ d->ownedVisibleColumns = new Field::List();
+ d->ownedVisibleColumns->setAutoDelete(true);
+ }
+ d->ownedVisibleColumns->append( visibleColumn ); // remember to delete later
+ }
+
+ lookup_list.append(
+ new QueryColumnInfo(visibleColumn, QCString(), true/*visible*/, ci/*foreign*/) );
+/*
+ //add visibleField to the list of SELECTed fields if it is not yes present there
+ if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) {
+ if (!table( visibleField->table()->name() )) {
+ }
+ if (!sql.isEmpty())
+ sql += QString::fromLatin1(", ");
+ sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "."
+ + escapeIdentifier(visibleField->name(), drvEscaping));
+ }*/
+ }
+ }
+ }
+ //prepare clean vector for expanded list, and a map for order information
+ if (!d->fieldsExpanded) {
+ d->fieldsExpanded = new QueryColumnInfo::Vector( list.count() );// Field::Vector( list.count() );
+ d->fieldsExpanded->setAutoDelete(true);
+ d->columnsOrderExpanded = new QMap<QueryColumnInfo*,int>();
+ }
+ else {//for future:
+ d->fieldsExpanded->clear();
+ d->fieldsExpanded->resize( list.count() );
+ d->columnsOrderExpanded->clear();
+ }
+
+ /*fill (based on prepared 'list' and 'lookup_list'):
+ -the vector
+ -the map
+ -"fields by name" dictionary
+ */
+ d->columnInfosByName.clear();
+ d->columnInfosByNameExpanded.clear();
+ i=0;
+ QueryColumnInfo *ci;
+ for (QueryColumnInfo::ListIterator it(list); (ci = it.current()); ++it, i++) {
+ d->fieldsExpanded->insert(i, ci);
+ d->columnsOrderExpanded->insert(ci, i);
+ //remember field by name/alias/table.name if there's no such string yet in d->columnInfosByNameExpanded
+ if (!ci->alias.isEmpty()) {
+ //store alias and table.alias
+ if (!d->columnInfosByNameExpanded[ ci->alias ])
+ d->columnInfosByNameExpanded.insert( ci->alias, ci );
+ QString tableAndAlias( ci->alias );
+ if (ci->field->table())
+ tableAndAlias.prepend(ci->field->table()->name() + ".");
+ if (!d->columnInfosByNameExpanded[ tableAndAlias ])
+ d->columnInfosByNameExpanded.insert( tableAndAlias, ci );
+ //the same for "unexpanded" list
+ if (columnInfosOutsideAsterisks.contains(ci)) {
+ if (!d->columnInfosByName[ ci->alias ])
+ d->columnInfosByName.insert( ci->alias, ci );
+ if (!d->columnInfosByName[ tableAndAlias ])
+ d->columnInfosByName.insert( tableAndAlias, ci );
+ }
+ }
+ else {
+ //no alias: store name and table.name
+ if (!d->columnInfosByNameExpanded[ ci->field->name() ])
+ d->columnInfosByNameExpanded.insert( ci->field->name(), ci );
+ QString tableAndName( ci->field->name() );
+ if (ci->field->table())
+ tableAndName.prepend(ci->field->table()->name() + ".");
+ if (!d->columnInfosByNameExpanded[ tableAndName ])
+ d->columnInfosByNameExpanded.insert( tableAndName, ci );
+ //the same for "unexpanded" list
+ if (columnInfosOutsideAsterisks.contains(ci)) {
+ if (!d->columnInfosByName[ ci->field->name() ])
+ d->columnInfosByName.insert( ci->field->name(), ci );
+ if (!d->columnInfosByName[ tableAndName ])
+ d->columnInfosByName.insert( tableAndName, ci );
+ }
+ }
+ }
+
+ //remove duplicates for lookup fields
+ QDict<uint> lookup_dict(101); //used to fight duplicates and to update QueryColumnInfo::indexForVisibleLookupValue()
+ // (a mapping from table.name string to uint* lookupFieldIndex
+ lookup_dict.setAutoDelete(true);
+ i=0;
+ for (QueryColumnInfo::ListIterator it(lookup_list); (ci = it.current());)
+ {
+ const QString key( lookupColumnKey(ci->foreignColumn()->field, ci->field) );
+ if ( /* not needed columnInfo( tableAndFieldName ) || */
+ lookup_dict[ key ]) {
+ // this table.field is already fetched by this query
+ ++it;
+ lookup_list.removeRef( ci );
+ }
+ else {
+ lookup_dict.replace( key, new uint( i ) );
+ ++it;
+ i++;
+ }
+ }
+
+ //create internal expanded list with lookup fields
+ if (d->internalFields) {
+ d->internalFields->clear();
+ d->internalFields->resize( lookup_list.count() );
+ }
+ delete d->fieldsExpandedWithInternal; //clear cache
+ delete d->fieldsExpandedWithInternalAndRowID; //clear cache
+ d->fieldsExpandedWithInternal = 0;
+ d->fieldsExpandedWithInternalAndRowID = 0;
+ if (!lookup_list.isEmpty() && !d->internalFields) {//create on demand
+ d->internalFields = new QueryColumnInfo::Vector( lookup_list.count() );
+ d->internalFields->setAutoDelete(true);
+ }
+ i=0;
+ for (QueryColumnInfo::ListIterator it(lookup_list); it.current();i++, ++it)
+ {
+ //add it to the internal list
+ d->internalFields->insert(i, it.current());
+ d->columnsOrderExpanded->insert(it.current(), list.count()+i);
+ }
+
+ //update QueryColumnInfo::indexForVisibleLookupValue() cache for columns
+ numberOfColumnsWithMultipleVisibleFields = 0;
+ for (i=0; i < d->fieldsExpanded->size(); i++) {
+ QueryColumnInfo* ci = d->fieldsExpanded->at(i);
+//! @todo QuerySchema itself will also support lookup fields...
+ LookupFieldSchema *lookupFieldSchema
+ = ci->field->table() ? ci->field->table()->lookupFieldSchema( *ci->field ) : 0;
+ if (!lookupFieldSchema || lookupFieldSchema->boundColumn()<0)
+ continue;
+ LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource();
+ if (rowSource.type() == LookupFieldSchema::RowSource::Table) {
+ TableSchema *lookupTable = connection()->tableSchema( rowSource.name() );
+ FieldList* visibleColumns = 0;
+ if (lookupTable
+ && (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount()
+ && (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() )))
+ {
+ Field *visibleColumn = 0;
+ // for single visible column, just add it as-is
+ if (visibleColumns->fieldCount() == 1)
+ {
+ visibleColumn = visibleColumns->fields()->first();
+ const QString key( lookupColumnKey(ci->field, visibleColumn) );
+ uint *index = lookup_dict[ key ];
+ if (index)
+ ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index );
+ }
+ else {
+ const QString key( QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3")
+ .arg( ++numberOfColumnsWithMultipleVisibleFields )
+ .arg(ci->field->table()->name()).arg(ci->field->name()) );
+ uint *index = lookup_dict[ key ];
+ if (index)
+ ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index );
+ }
+ }
+ delete visibleColumns;
+ }
+ else if (rowSource.type() == LookupFieldSchema::RowSource::Query) {
+ QuerySchema *lookupQuery = connection()->querySchema( rowSource.name() );
+ if (!lookupQuery)
+ continue;
+ const QueryColumnInfo::Vector lookupQueryFieldsExpanded( lookupQuery->fieldsExpanded() );
+ if ((uint)lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count())
+ continue;
+ QueryColumnInfo *boundColumnInfo = 0;
+ if (!(boundColumnInfo = lookupQueryFieldsExpanded[ lookupFieldSchema->boundColumn() ]))
+ continue;
+ Field *boundField = boundColumnInfo->field;
+ if (!boundField)
+ continue;
+ const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() );
+ Field *visibleColumn = 0;
+ // for single visible column, just add it as-is
+ if (visibleColumns.count() == 1) {
+ visibleColumn = lookupQueryFieldsExpanded[ visibleColumns.first() ]->field;
+ const QString key( lookupColumnKey(ci->field, visibleColumn) );
+ uint *index = lookup_dict[ key ];
+ if (index)
+ ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index );
+ }
+ else {
+ const QString key( QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3")
+ .arg( ++numberOfColumnsWithMultipleVisibleFields )
+ .arg(ci->field->table()->name()).arg(ci->field->name()) );
+ uint *index = lookup_dict[ key ];
+ if (index)
+ ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index );
+ }
+ }
+ else {
+ KexiDBWarn << "QuerySchema::computeFieldsExpanded(): unsupported row source type "
+ << rowSource.typeName() << endl;
+ }
+ }
+}
+
+QMap<QueryColumnInfo*,int> QuerySchema::columnsOrder(ColumnsOrderOptions options)
+{
+ if (!d->columnsOrder)
+ computeFieldsExpanded();
+ if (options == UnexpandedList)
+ return *d->columnsOrder;
+ else if (options == UnexpandedListWithoutAsterisks)
+ return *d->columnsOrderWithoutAsterisks;
+ return *d->columnsOrderExpanded;
+}
+
+QValueVector<int> QuerySchema::pkeyFieldsOrder()
+{
+ if (d->pkeyFieldsOrder)
+ return *d->pkeyFieldsOrder;
+
+ TableSchema *tbl = masterTable();
+ if (!tbl || !tbl->primaryKey())
+ return QValueVector<int>();
+
+ //get order of PKEY fields (e.g. for rows updating or inserting )
+ IndexSchema *pkey = tbl->primaryKey();
+ pkey->debug();
+ debug();
+ d->pkeyFieldsOrder = new QValueVector<int>( pkey->fieldCount(), -1 );
+
+ const uint fCount = fieldsExpanded().count();
+ d->pkeyFieldsCount = 0;
+ for (uint i = 0; i<fCount; i++) {
+ QueryColumnInfo *fi = d->fieldsExpanded->at(i);
+ const int fieldIndex = fi->field->table()==tbl ? pkey->indexOf(fi->field) : -1;
+ if (fieldIndex!=-1/* field found in PK */
+ && d->pkeyFieldsOrder->at(fieldIndex)==-1 /* first time */)
+ {
+ KexiDBDbg << "QuerySchema::pkeyFieldsOrder(): FIELD " << fi->field->name()
+ << " IS IN PKEY AT POSITION #" << fieldIndex << endl;
+// (*d->pkeyFieldsOrder)[j]=i;
+ (*d->pkeyFieldsOrder)[fieldIndex]=i;
+ d->pkeyFieldsCount++;
+// j++;
+ }
+ }
+ KexiDBDbg << "QuerySchema::pkeyFieldsOrder(): " << d->pkeyFieldsCount
+ << " OUT OF " << pkey->fieldCount() << " PKEY'S FIELDS FOUND IN QUERY " << name() << endl;
+ return *d->pkeyFieldsOrder;
+}
+
+uint QuerySchema::pkeyFieldsCount()
+{
+ (void)pkeyFieldsOrder(); /* rebuild information */
+ return d->pkeyFieldsCount;
+}
+
+Relationship* QuerySchema::addRelationship( Field *field1, Field *field2 )
+{
+//@todo: find existing global db relationships
+ Relationship *r = new Relationship(this, field1, field2);
+ if (r->isEmpty()) {
+ delete r;
+ return 0;
+ }
+
+ d->relations.append( r );
+ return r;
+}
+
+QueryColumnInfo::List* QuerySchema::autoIncrementFields()
+{
+ if (!d->autoincFields) {
+ d->autoincFields = new QueryColumnInfo::List();
+ }
+ TableSchema *mt = masterTable();
+ if (!mt) {
+ KexiDBWarn << "QuerySchema::autoIncrementFields(): no master table!" << endl;
+ return d->autoincFields;
+ }
+ if (d->autoincFields->isEmpty()) {//no cache
+ QueryColumnInfo::Vector fexp = fieldsExpanded();
+ for (int i=0; i<(int)fexp.count(); i++) {
+ QueryColumnInfo *fi = fexp[i];
+ if (fi->field->table() == mt && fi->field->isAutoIncrement()) {
+ d->autoincFields->append( fi );
+ }
+ }
+ }
+ return d->autoincFields;
+}
+
+QString QuerySchema::sqlColumnsList(QueryColumnInfo::List* infolist, Driver *driver)
+{
+ if (!infolist)
+ return QString::null;
+ QString result;
+ result.reserve(256);
+ QueryColumnInfo::ListIterator it( *infolist );
+ bool start = true;
+ for (; it.current(); ++it) {
+ if (!start)
+ result += ",";
+ else
+ start = false;
+ result += driver->escapeIdentifier( it.current()->field->name() );
+ }
+ return result;
+}
+
+QString QuerySchema::autoIncrementSQLFieldsList(Driver *driver)
+{
+ if ((Driver *)d->lastUsedDriverForAutoIncrementSQLFieldsList != driver
+ || d->autoIncrementSQLFieldsList.isEmpty())
+ {
+ d->autoIncrementSQLFieldsList = QuerySchema::sqlColumnsList( autoIncrementFields(), driver );
+ d->lastUsedDriverForAutoIncrementSQLFieldsList = driver;
+ }
+ return d->autoIncrementSQLFieldsList;
+}
+
+void QuerySchema::setWhereExpression(BaseExpr *expr)
+{
+ delete d->whereExpr;
+ d->whereExpr = expr;
+}
+
+void QuerySchema::addToWhereExpression(KexiDB::Field *field, const QVariant& value, int relation)
+{
+ int token;
+ if (value.isNull())
+ token = SQL_NULL;
+ else if (field->isIntegerType()) {
+ token = INTEGER_CONST;
+ }
+ else if (field->isFPNumericType()) {
+ token = REAL_CONST;
+ }
+ else {
+ token = CHARACTER_STRING_LITERAL;
+//! @todo date, time
+ }
+
+ BinaryExpr * newExpr = new BinaryExpr(
+ KexiDBExpr_Relational,
+ new ConstExpr( token, value ),
+ relation,
+ new VariableExpr((field->table() ? (field->table()->name()+".") : QString::null)+field->name())
+ );
+ if (d->whereExpr) {
+ d->whereExpr = new BinaryExpr(
+ KexiDBExpr_Logical,
+ d->whereExpr,
+ AND,
+ newExpr
+ );
+ }
+ else {
+ d->whereExpr = newExpr;
+ }
+}
+
+/*
+void QuerySchema::addToWhereExpression(KexiDB::Field *field, const QVariant& value)
+ switch (value.type()) {
+ case Int: case UInt: case Bool: case LongLong: case ULongLong:
+ token = INTEGER_CONST;
+ break;
+ case Double:
+ token = REAL_CONST;
+ break;
+ default:
+ token = CHARACTER_STRING_LITERAL;
+ }
+//! @todo date, time
+
+*/
+
+BaseExpr *QuerySchema::whereExpression() const
+{
+ return d->whereExpr;
+}
+
+void QuerySchema::setOrderByColumnList(const OrderByColumnList& list)
+{
+ d->orderByColumnList = list;
+// all field names should be found, exit otherwise ..........?
+}
+
+OrderByColumnList& QuerySchema::orderByColumnList() const
+{
+ return d->orderByColumnList;
+}
+
+QuerySchemaParameterList QuerySchema::parameters()
+{
+ if (!whereExpression())
+ return QuerySchemaParameterList();
+ QuerySchemaParameterList params;
+ whereExpression()->getQueryParameters(params);
+ return params;
+}
+
+/*
+ new field1, Field *field2
+ if (!field1 || !field2) {
+ KexiDBWarn << "QuerySchema::addRelationship(): !masterField || !detailsField" << endl;
+ return;
+ }
+ if (field1->isQueryAsterisk() || field2->isQueryAsterisk()) {
+ KexiDBWarn << "QuerySchema::addRelationship(): relationship's fields cannot be asterisks" << endl;
+ return;
+ }
+ if (!hasField(field1) && !hasField(field2)) {
+ KexiDBWarn << "QuerySchema::addRelationship(): fields do not belong to this query" << endl;
+ return;
+ }
+ if (field1->table() == field2->table()) {
+ KexiDBWarn << "QuerySchema::addRelationship(): fields cannot belong to the same table" << endl;
+ return;
+ }
+//@todo: check more things: -types
+//@todo: find existing global db relationships
+
+ Field *masterField = 0, *detailsField = 0;
+ IndexSchema *masterIndex = 0, *detailsIndex = 0;
+ if (field1->isPrimaryKey() && field2->isPrimaryKey()) {
+ //2 primary keys
+ masterField = field1;
+ masterIndex = masterField->table()->primaryKey();
+ detailsField = field2;
+ detailsIndex = masterField->table()->primaryKey();
+ }
+ else if (field1->isPrimaryKey()) {
+ masterField = field1;
+ masterIndex = masterField->table()->primaryKey();
+ detailsField = field2;
+//@todo: check if it already exists
+ detailsIndex = new IndexSchema(detailsField->table());
+ detailsIndex->addField(detailsField);
+ detailsIndex->setForeigKey(true);
+ // detailsField->setForeignKey(true);
+ }
+ else if (field2->isPrimaryKey()) {
+ detailsField = field1;
+ masterField = field2;
+ masterIndex = masterField->table()->primaryKey();
+//@todo
+ }
+
+ if (!masterIndex || !detailsIndex)
+ return; //failed
+
+ Relationship *rel = new Relationship(masterIndex, detailsIndex);
+
+ d->relations.append( rel );
+}*/
+
+//---------------------------------------------------
+
+QueryAsterisk::QueryAsterisk( QuerySchema *query, TableSchema *table )
+ :Field()
+ ,m_table(table)
+{
+ assert(query);
+ m_parent = query;
+ setType(Field::Asterisk);
+}
+
+QueryAsterisk::~QueryAsterisk()
+{
+}
+
+Field* QueryAsterisk::copy() const
+{
+ return new QueryAsterisk(*this);
+}
+
+void QueryAsterisk::setTable(TableSchema *table)
+{
+ KexiDBDbg << "QueryAsterisk::setTable()" << endl;
+ m_table=table;
+}
+
+QString QueryAsterisk::debugString() const
+{
+ QString dbg;
+ if (isAllTableAsterisk()) {
+ dbg += "ALL-TABLES ASTERISK (*) ON TABLES(";
+ TableSchema *table;
+ QString table_names;
+ for (TableSchema::ListIterator it( *query()->tables() ); (table = it.current()); ++it) {
+ if (!table_names.isEmpty())
+ table_names += ", ";
+ table_names += table->name();
+ }
+ dbg += (table_names + ")");
+ }
+ else {
+ dbg += ("SINGLE-TABLE ASTERISK (" + table()->name() + ".*)");
+ }
+ return dbg;
+}
+
diff --git a/kexi/kexidb/queryschema.h b/kexi/kexidb/queryschema.h
new file mode 100644
index 000000000..76dfa757b
--- /dev/null
+++ b/kexi/kexidb/queryschema.h
@@ -0,0 +1,832 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_QUERY_H
+#define KEXIDB_QUERY_H
+
+#include <qvaluevector.h>
+#include <qstring.h>
+#include <qmap.h>
+#include <qptrlist.h>
+
+#include "fieldlist.h"
+#include "schemadata.h"
+#include "tableschema.h"
+#include "relationship.h"
+
+namespace KexiDB {
+
+class Connection;
+class QueryAsterisk;
+class QuerySchemaPrivate;
+class QuerySchemaParameter;
+typedef QValueList<QuerySchemaParameter> QuerySchemaParameterList;
+
+//! @short Helper class that assigns additional information for the column in a query
+/*! The following information is assigned:
+ - alias
+ - visibility
+ QueryColumnInfo::Vector is created and returned by QuerySchema::fieldsExpanded().
+ It is efficiently cached within the QuerySchema object.
+*/
+class KEXI_DB_EXPORT QueryColumnInfo
+{
+ public:
+ typedef QPtrVector<QueryColumnInfo> Vector;
+ typedef QPtrList<QueryColumnInfo> List;
+ typedef QPtrListIterator<QueryColumnInfo> ListIterator;
+
+ QueryColumnInfo(Field *f, const QCString& _alias, bool _visible, QueryColumnInfo *foreignColumn = 0);
+ ~QueryColumnInfo();
+
+ //! \return alias if it is not empty, field's name otherwise.
+ inline QCString aliasOrName() const {
+ return alias.isEmpty() ? field->name().latin1() : (const char*)alias;
+ }
+
+ //! \return field's caption if it is not empty, field's alias otherwise.
+ //! If alias is also empty - returns field's name.
+ inline QString captionOrAliasOrName() const {
+ return field->caption().isEmpty() ? QString(aliasOrName()) : field->caption(); }
+
+ Field *field;
+ QCString alias;
+
+ /*! \return index of column with visible lookup value within the 'fields expanded' vector.
+ -1 means no visible lookup value is available because there is no lookup for the column defined.
+ Cached for efficiency as we use this information frequently.
+ @see LookupFieldSchema::visibleVolumn() */
+ inline int indexForVisibleLookupValue() const { return m_indexForVisibleLookupValue; }
+
+ /*! Sets index of column with visible lookup value within the 'fields expanded' vector. */
+ inline void setIndexForVisibleLookupValue(int index) { m_indexForVisibleLookupValue = index; }
+
+ //! \return non-0 if this column is a visible column for other column
+ QueryColumnInfo *foreignColumn() const { return m_foreignColumn; }
+
+ /*! \return string for debugging purposes. */
+ QString debugString() const;
+
+ //! true if this column is visible to the user (and its data is fetched by the engine)
+ bool visible : 1;
+
+ private:
+ /*! Index of column with visible lookup value within the 'fields expanded' vector.
+ @see indexForVisibleLookupValue() */
+ int m_indexForVisibleLookupValue;
+
+ //! Non-0 if this column is a visible column for \a m_foreignColumn
+ QueryColumnInfo *m_foreignColumn;
+};
+
+//! @short KexiDB::OrderByColumn provides information about a single query column used for sorting
+/*! The column can be expression or table field. */
+class KEXI_DB_EXPORT OrderByColumn
+{
+ public:
+ typedef QValueListConstIterator<OrderByColumn> ListConstIterator;
+ OrderByColumn();
+ OrderByColumn(QueryColumnInfo& column, bool ascending = true, int pos = -1);
+
+ //! Like above but used when the field \a field is not present on the list of columns.
+ //! (e.g. SELECT a FROM t ORDER BY b; where T is a table with fields (a,b)).
+ OrderByColumn(Field& field, bool ascending = true);
+
+ ~OrderByColumn();
+
+ //! A column to sort.
+ inline QueryColumnInfo* column() const { return m_column; }
+
+ /*! A helper for column() that allows you to know that sorting column
+ was defined by providing its position. -1 by default.
+ Example query: SELECT a, b FROM T ORDER BY 2 */
+ inline int position() const { return m_pos; }
+
+ //! A field to sort, used only in case when the second constructor was used.
+ inline Field *field() const { return m_field; }
+
+ //! \return true if ascending sorting should be performed (the default).
+ inline bool ascending() const { return m_ascending; }
+
+ //! \return true if this column is thesame as \a col
+ bool operator== ( const OrderByColumn& col ) const
+ { return m_column==col.m_column && m_field==col.m_field
+ && m_ascending==col.m_ascending; }
+
+ /*! \return string for debugging purposes. */
+ QString debugString() const;
+
+ /*! \return a string like "name ASC" usable for building a SQL statement.
+ If \a includeTableNames is true (the default) field is output in a form
+ of "tablename.fieldname" (but only if fieldname is not a name of alias).
+ \a drv and \a identifierEscaping are used for escaping the table and field identifiers. */
+ QString toSQLString(bool includeTableName = true,
+ Driver *drv = 0, int identifierEscaping = Driver::EscapeDriver|Driver::EscapeAsNecessary) const;
+
+ protected:
+ //! Column to sort
+ QueryColumnInfo* m_column; //!< 0 if m_field is non-0.
+ int m_pos; //!< A helper for m_column that allows to know that sorting column
+ //!< was defined by providing its position. -1 by default.
+ //!< e.g. SELECT a, b FROM T ORDER BY 2
+ Field* m_field; //!< Used only in case when the second contructor is used.
+
+ //! true if ascending sorting should be performed (the default).
+ bool m_ascending : 1;
+};
+
+//! A base for KexiDB::OrderByColumnList
+typedef QValueList<OrderByColumn> OrderByColumnListBase;
+
+//! @short KexiDB::OrderByColumnList provides list of sorted columns for a query schema
+class KEXI_DB_EXPORT OrderByColumnList : protected OrderByColumnListBase
+{
+ public:
+ /*! Constructs empty list of ordered columns. */
+ OrderByColumnList();
+
+ ~OrderByColumnList();
+
+ /*! Appends multiple fields for sorting. \a querySchema
+ is used to find appropriate field or alias name.
+ \return false if there is at least one name for which a field or alias name does not exist
+ (all the newly appended fields are removed in this case) */
+ bool appendFields(QuerySchema& querySchema,
+ const QString& field1, bool ascending1 = true,
+ const QString& field2 = QString::null, bool ascending2 = true,
+ const QString& field3 = QString::null, bool ascending3 = true,
+ const QString& field4 = QString::null, bool ascending4 = true,
+ const QString& field5 = QString::null, bool ascending5 = true);
+
+ /*! Appends column \a columnInfo. Ascending sorting is set is \a ascending is true. */
+ void appendColumn(QueryColumnInfo& columnInfo, bool ascending = true);
+
+ /*! Appends a field \a field. Ascending sorting is set is \a ascending is true.
+ Read documentation of \ref OrderByColumn(const Field& field, bool ascending = true)
+ for more info. */
+ void appendField(Field& field, bool ascending = true);
+
+ /*! Appends field with a name \a field. Ascending sorting is set is \a ascending is true.
+ \return true on successful appending, and false if there is no such field or alias
+ name in the \a querySchema. */
+ bool appendField(QuerySchema& querySchema, const QString& fieldName,
+ bool ascending = true);
+
+ /*! Appends a column that is at position \a pos (counted from 0).
+ \return true on successful adding and false if there is no such position \a pos. */
+ bool appendColumn(QuerySchema& querySchema, bool ascending = true, int pos = -1);
+
+ /*! Appends \a column to the list. */
+ void appendColumn(const OrderByColumn& column);
+
+ /*! \return true if the list is empty. */
+ bool isEmpty() const { return OrderByColumnListBase::isEmpty(); }
+
+ /*! \return number of elements of the list. */
+ uint count() const { return OrderByColumnListBase::count(); }
+
+ /*! Removes all elements from the list. */
+ void clear() { OrderByColumnListBase::clear(); }
+
+ const_iterator constBegin () const { return OrderByColumnListBase::constBegin(); }
+ const_iterator constEnd () const { return OrderByColumnListBase::constEnd(); }
+
+ /*! \return string for debugging purposes. */
+ QString debugString() const;
+
+ /*! \return a string like "name ASC, 2 DESC" usable for building a SQL statement.
+ If \a includeTableNames is true (the default) fields are output in a form
+ of "tablename.fieldname".
+ \a drv and \a identifierEscaping are used for escaping the table and field identifiers. */
+ QString toSQLString(bool includeTableNames = true,
+ Driver *drv = 0, int identifierEscaping = Driver::EscapeDriver|Driver::EscapeAsNecessary) const;
+};
+
+//! @short KexiDB::QuerySchema provides information about database query
+/*! The query that can be executed using KexiDB-compatible SQL database engine
+ or used as an introspection tool. KexiDB parser builds QuerySchema objects
+ by parsing SQL statements. */
+class KEXI_DB_EXPORT QuerySchema : public FieldList, public SchemaData
+{
+ public:
+ /*! Creates empty query object (without columns). */
+ QuerySchema();
+
+ /*! Creates query schema object that is equivalent to "SELECT * FROM table"
+ sql command. Schema of \a table is used to contruct this query --
+ it is defined by just adding all the fields to the query in natural order.
+ To avoid problems (e.g. with fields added outside of Kexi using ALTER TABLE)
+ we do not use "all-tables query asterisk" (see QueryAsterisk) item to achieve
+ this effect.
+
+ Properties such as the name and caption of the query are inherited
+ from table schema.
+
+ We consider that query schema based on \a table is not (a least yet) stored
+ in a system table, so query connection is set to NULL
+ (even if \a tableSchema's connection is not NULL).
+ Id of the created query is set to 0. */
+ QuerySchema(TableSchema& tableSchema);
+
+ /*! Copy constructor. Creates deep copy of \a querySchema.
+ QueryAsterisk objects are deeply copied while only pointers to Field objects are copied. */
+ QuerySchema(const QuerySchema& querySchema);
+
+ virtual ~QuerySchema();
+
+ /*! Inserts \a field to the columns list at \a position.
+ Inserted field will not be owned by this QuerySchema object,
+ but still by corresponding TableSchema.
+
+ As \a field object you can also pass KexiDB::QueryAsterisk,
+ (see QueryAsterisk class description).
+
+ Note: After inserting a field, corresponding table will be automatically
+ added to query's tables list if it is not present there (see tables()).
+ Field must have its table assigned.
+
+ Added field will be visible. Use insertField(position, field, false)
+ to add invisible field.
+ */
+ virtual FieldList& insertField(uint position, Field *field);
+
+ /* Like above method, but you can also set column's visibility.
+ New column is not bound explicitly to any table.
+ */
+ FieldList& insertField(uint position, Field *field, bool visible);
+
+ /* Like above method, but you can also explicitly bound the new column
+ to specific position on tables list.
+ If \a visible is true (the default), the field will be visible.
+ If bindToTable==-1, no particular table should be bound.
+ @see tableBoundToColumn(uint columnPosition) */
+ FieldList& insertField(uint position, Field *field,
+ int bindToTable, bool visible = true);
+
+ /*! Adds \a field to the columns list.
+ If \a visible is true (the default), the field will be visible.
+ \sa insertField() */
+ FieldList& addField(Field* field, bool visible = true);
+
+ /*! Adds \a field to the columns list. Also binds to a table
+ at \a bindToTable position. Use bindToTable==-1 if no table should be bound.
+ If \a visible is true (the default), the field will be visible.
+ \sa insertField()
+ \sa tableBoundToColumn(uint columnPosition)
+ */
+ FieldList& addField(Field* field, int bindToTable,
+ bool visible = true);
+
+ /*! Removes field from the columns list. Use with care. */
+ virtual void removeField(Field *field);
+
+ /*! Adds a field built on top of \a expr expression.
+ This creates a new Field object and adds it to the query schema using addField(). */
+ FieldList& addExpression(BaseExpr* expr, bool visible = true);
+
+ /*! \return visibility flag for column at \a position.
+ By default column is visible. */
+ bool isColumnVisible(uint position) const;
+
+ //! Sets visibility flag for column at \a position to \a v.
+ void setColumnVisible(uint position, bool v);
+
+ /*! Adds \a asterisk at the and of columns list. */
+ FieldList& addAsterisk(QueryAsterisk *asterisk, bool visible = true);
+
+ /*! Removes all columns and their aliases from the columns list,
+ removes all tables and their aliases from the tables list within this query.
+ Sets master table information to NULL.
+ Does not destroy any objects though. Clears name and all other properties.
+ \sa FieldList::clear() */
+ virtual void clear();
+
+ /*! \return string for debugging purposes. */
+ virtual QString debugString();
+
+ /*! If query was created using a connection,
+ returns this connection object, otherwise NULL. */
+ Connection* connection() const;
+
+ /*! \return table that is master to this query.
+ All potentially-editable columns within this query belong just to this table.
+ This method also can return NULL if there are no tables at all,
+ or if previously assigned master table schema has been removed
+ with removeTable().
+ Every query that has at least one table defined, should have
+ assigned a master table.
+ If no master table is assigned explicitym but this method there is only
+ one table used for this query even if there are table aliases,
+ a single table is returned here.
+ (e.g. "T" table is returned for "SELECT T1.A, T2.B FROM T T1, T T2" statement). */
+ TableSchema* masterTable() const;
+
+ /*! Sets master table of this query to \a table.
+ This table should be also added to query's tables list
+ using addTable(). If \a table equals NULL, nothing is performed.
+ \sa masterTable() */
+ void setMasterTable(TableSchema *table);
+
+ /*! \return list of tables used in a query.
+ This also includes master table.
+ \sa masterTable() */
+ TableSchema::List* tables() const;
+
+ /*! Adds \a table schema as one of tables used in a query.
+ if \a alias is not empty, it will be assigned to this table
+ using setTableAlias(position, alias)
+ */
+ void addTable(TableSchema *table, const QCString& alias = QCString());
+
+ /*! Removes \a table schema from this query.
+ This does not destroy \a table object but only takes it out of the list.
+ If this table was master for the query, master table information is also
+ invalidated. */
+ void removeTable(TableSchema *table);
+
+ /*! \return table with name \a tableName or 0 if this query has no such table. */
+ TableSchema* table(const QString& tableName) const;
+
+ /*! \return true if the query uses \a table. */
+ bool contains(TableSchema *table) const;
+
+ /*! Convenience function.
+ \return table field by searching through all tables in this query.
+ The field does not need to be included on the list of query columns.
+ Similarly, query aliases are not taken into account.
+
+ \a tableOrTableAndFieldName string may contain table name and field name
+ with '.' character between them, e.g. "mytable.myfield".
+ This is recommended way to avoid ambiguity.
+ 0 is returned if the query has no such
+ table defined of the table has no such field defined.
+ If you do not provide a table name, the first field found is returned.
+
+ QuerySchema::table("mytable")->field("myfield") could be
+ alternative for findTableField("mytable.myfield") but it can crash
+ if "mytable" is not defined in the query.
+
+ @see KexiDB::splitToTableAndFieldParts()
+ */
+ Field* findTableField(const QString &tableOrTableAndFieldName) const;
+
+ /*! \return alias of a column at \a position or null string
+ If there is no alias for this column
+ or if there is no such column within the query defined.
+ If the column is an expression and has no alias defined,
+ a new unique alias will be generated automatically on this call.
+ */
+ QCString columnAlias(uint position) const;
+
+ /*! Provided for convenience.
+ \return true if a column at \a position has non empty alias defined
+ within the query.
+ If there is no alias for this column,
+ or if there is no such column in the query defined, false is returned. */
+ bool hasColumnAlias(uint position) const;
+
+ /*! Sets \a alias for a column at \a position, within the query.
+ Passing empty string to \a alias clears alias for a given column. */
+ void setColumnAlias(uint position, const QCString& alias);
+
+ /*! \return a table position (within FROM section),
+ that is bound to column at \a columnPosition (within SELECT section).
+ This information can be used to find if there is alias defined for
+ a table that is referenced by a given column.
+
+ For example, for "SELECT t2.id FROM table1 t1, table2 t2" query statement,
+ columnBoundToTable(0) returns 1, what means that table at position 1
+ (within FROM section) is bound to column at position 0, so we can
+ now call tableAlias(1) to see if we have used alias for this column (t2.d)
+ or just a table name (table2.d).
+
+ These checkings are performed e.g. by Connection::queryStatement()
+ to construct a statement string maximally identical to originally
+ defined query statement.
+
+ -1 is returned if:
+ - \a columnPosition is out of range (i.e. < 0 or >= fieldCount())
+ - a column at \a columnPosition is not bound to any table (i.e.
+ no database field is used for this column,
+ e.g. "1" constant for "SELECT 1 from table" query statement)
+ */
+ int tableBoundToColumn(uint columnPosition) const;
+
+ /*! \return alias of a table at \a position (within FROM section)
+ or null string if there is no alias for this table
+ or if there is no such table within the query defined. */
+ QCString tableAlias(uint position) const;
+
+ /*! \return table position (within FROM section) that has attached
+ alias \a name.
+ If there is no such alias, -1 is returned.
+ Only first table's position attached for this alias is returned.
+ It is not especially bad, since aliases rarely can be duplicated,
+ what leads to ambiguity.
+ Duplicated aliases are only allowed for trivial queries that have
+ no database fields used within their columns,
+ e.g. "SELECT 1 from table1 t, table2 t" is ok
+ but "SELECT t.id from table1 t, table2 t" is not.
+ */
+ int tablePositionForAlias(const QCString& name) const;
+
+ /*! \return table position (within FROM section) for \a tableName.
+ -1 is returend if there's no such table declared in the FROM section.
+ \sa tablePositions()
+ */
+ int tablePosition(const QString& tableName) const;
+
+ /*! \return a list of all \a tableName table occurrences (within FROM section).
+ E.g. for "SELECT * FROM table t, table t2" [0, 1] list is returned.
+ Empty list is returned there's no such table declared
+ in the FROM section at all.
+ \sa tablePosition()
+ */
+ QValueList<int> tablePositions(const QString& tableName) const;
+
+ /*! Provided for convenience.
+ \return true if a table at \a position (within FROM section of the the query)
+ has non empty alias defined.
+ If there is no alias for this table,
+ or if there is no such table in the query defined, false is returned. */
+ bool hasTableAlias(uint position) const;
+
+ /*! \return column position that has defined alias \a name.
+ If there is no such alias, -1 is returned. */
+ int columnPositionForAlias(const QCString& name) const;
+
+ /*! Sets \a alias for a table at \a position (within FROM section
+ of the the query).
+ Passing empty sting to \a alias clears alias for a given table
+ (only for specified \a position). */
+ void setTableAlias(uint position, const QCString& alias);
+
+ /*! \return a list of relationships defined for this query */
+ Relationship::List* relationships() const;
+
+ /*! Adds a new relationship defined by \a field1 and \a field2.
+ Both fields should belong to two different tables of this query.
+ This is convenience function useful for a typical cases.
+ It automatically creates Relationship object for this query.
+ If one of the fields are primary keys, it will be detected
+ and appropriate master-detail relation will be established.
+ This functiuon does nothing if the arguments are invalid. */
+ Relationship* addRelationship( Field *field1, Field *field2 );
+
+ /*! \return list of QueryAsterisk objects defined for this query */
+ Field::List* asterisks() const;
+
+ /*! \return field for \a identifier or 0 if no field for this name
+ was found within the query. fieldsExpanded() method is used
+ to lookup expanded list of the query fields, so queries with asterisks
+ are processed well.
+ If a field has alias defined, name is not taken into account,
+ but only its alias. If a field has no alias:
+ - field's name is checked
+ - field's table and field's name are checked in a form of "tablename.fieldname",
+ so you can provide \a identifier in this form to avoid ambiguity.
+
+ If there are more than one fields with the same name equal to \a identifier,
+ first-found is returned (checking is performed from first to last query field).
+ Structures needed to compute result of this method are cached,
+ so only first usage costs o(n) - another usages cost o(1).
+
+ Example:
+ Let query be defined by "SELECT T.B AS X, T.* FROM T" statement and let T
+ be table containing fields A, B, C.
+ Expanded list of columns for the query is: T.B AS X, T.A, T.B, T.C.
+ - Calling field("B") will return a pointer to third query column (not the first,
+ because it is covered by "X" alias). Additionally, calling field("X")
+ will return the same pointer.
+ - Calling field("T.A") will return the same pointer as field("A").
+ */
+ virtual Field* field(const QString& name, bool expanded = true);
+
+ /*! \return field id or NULL if there is no such a field. */
+ inline Field* field(uint id) { return FieldList::field(id); }
+
+ /*! Like QuerySchema::field(const QString& name) but returns not only Field
+ object for \a identifier but entire QueryColumnInfo object.
+ \a identifier can be:
+ - a fieldname
+ - an aliasname
+ - a tablename.fieldname
+ - a tablename.aliasname
+ Note that if there are two occurrrences of the same name,
+ only the first is accessible using this method. For instance,
+ calling columnInfo("name") for "SELECT t1.name, t2.name FROM t1, t2" statement
+ will only return the column related to t1.name and not t2.name, so you'll need to
+ explicitly specify "t2.name" as the identifier to get the second column. */
+ QueryColumnInfo* columnInfo(const QString& identifier, bool expanded = true);
+
+ /*! Options used in fieldsExpanded(). */
+ enum FieldsExpandedOptions {
+ Default, //!< All fields are returned even if duplicated
+ Unique, //!< Unique list of fields is returned
+ WithInternalFields, //!< Like Default but internal fields (for lookup) are appended
+ WithInternalFieldsAndRowID //!< Like WithInternalFields but RowID (big int type) field
+ //!< is appended after internal fields
+ };
+
+ /*! \return fully expanded list of fields.
+ QuerySchema::fields() returns vector of fields used for the query columns,
+ but in a case when there are asterisks defined for the query,
+ it does not expand QueryAsterisk objects to field lists but return every
+ asterisk as-is.
+ This could be inconvenient when you need just a fully expanded list of fields,
+ so this method does the work for you.
+
+ If \a options is Unique, each field is returned in the vector only once
+ (first found field is selected).
+ Note however, that the same field can be returned more than once if it has attached
+ a different alias.
+ For example, let t be TABLE( a, b ) and let query be defined
+ by "SELECT *, a AS alfa FROM t" statement. Both fieldsExpanded(Default)
+ and fieldsExpanded(Unique) will return [ a, b, a (alfa) ] list.
+ On the other hand, for query defined by "SELECT *, a FROM t" statement,
+ fieldsExpanded(Default) will return [ a, b, a ] list while
+ fieldsExpanded(Unique) will return [ a, b ] list.
+
+ If \a options is WithInternalFields or WithInternalFieldsAndRowID,
+ additional internal fields are also appended to the vector.
+
+ If \a options is WithInternalFieldsAndRowID,
+ one fake BigInteger column is appended to make space for ROWID column used
+ by KexiDB::Cursor implementations. For example, let persons be TABLE( surname, city_id ),
+ let city_number reference cities.is in TABLE cities( id, name ) and let query q be defined
+ by "SELECT * FROM t" statement. If we want to display persons' city names instead of city_id's.
+ To do this, cities.name has to be retrieved as well, so the following statement should be used:
+ "SELECT * FROM persons, cities.name LEFT OUTER JOIN cities ON persons.city_id=cities.id".
+ Thus, calling fieldsExpanded(WithInternalFieldsAndRowID) will return 4 elements instead of 2:
+ persons.surname, persons.city_id, cities.name, {ROWID}. The {ROWID} item is the placeholder
+ used for fetching ROWID by KexiDB cursors.
+
+ By default, all fields are returned in the vector even
+ if there are multiple occurrences of one or more (options == Default).
+
+ Note: You should assign the resulted vector in your space - it will be shared
+ and implicity copied on any modification.
+ This method's result is cached by QuerySchema object.
+@todo js: UPDATE CACHE!
+ */
+ QueryColumnInfo::Vector fieldsExpanded(FieldsExpandedOptions options = Default);
+
+ /*! \return list of fields internal fields used for lookup columns. */
+ QueryColumnInfo::Vector internalFields();
+
+ /*! \return info for expanded of internal field at index \a index.
+ The returned field can be either logical or internal (for lookup),
+ the latter case is true if \a index &gt;= fieldsExpanded().count().
+ Equivalent of QuerySchema::fieldsExpanded(WithInternalFields).at(index). */
+ QueryColumnInfo* expandedOrInternalField(uint index);
+
+ /*! Options used in columnsOrder(). */
+ enum ColumnsOrderOptions {
+ UnexpandedList, //!< A map for unexpanded list is created
+ UnexpandedListWithoutAsterisks, //!< A map for unexpanded list is created, with asterisks skipped
+ ExpandedList //!< A map for expanded list is created
+ };
+
+ /*! \return a map for fast lookup of query columns' order.
+ - If \a options is UnexpandedList, each QueryColumnInfo pointer is mapped to the index
+ within (unexpanded) list of fields, i.e. "*" or "table.*" asterisks are considered
+ to be single items.
+ - If \a options is UnexpandedListWithoutAsterisks, each QueryColumnInfo pointer
+ is mapped to the index within (unexpanded) list of columns that come from asterisks
+ like "*" or "table.*" are not included in the map at all.
+ - If \a options is ExpandedList (the default) this method provides is exactly opposite
+ information compared to vector returned by fieldsExpanded().
+
+ This method's result is cached by the QuerySchema object.
+ Note: indices of internal fields (see internalFields()) are also returned
+ here - in this case the index is counted as a sum of size(e) + i (where "e" is
+ the list of expanded fields and i is the column index within internal fields list).
+ This feature is used eg. at the end of Connection::updateRow() where need indices of
+ fields (including internal) to update all the values in memory.
+
+ Example use: let t be table (int id, name text, surname text) and q be query
+ defined by a statement "select * from t".
+
+ - columnsOrder(ExpandedList) will return the following map: QueryColumnInfo(id)->0,
+ QueryColumnInfo(name)->1, QueryColumnInfo(surname)->2.
+ - columnsOrder(UnexpandedList) will return the following map: QueryColumnInfo(id)->0,
+ QueryColumnInfo(name)->0, QueryColumnInfo(surname)->0 because the column
+ list is not expanded. This way you can use the returned index to get Field*
+ pointer using field(uint) method of FieldList superclass.
+ - columnsOrder(UnexpandedListWithoutAsterisks) will return the following map:
+ QueryColumnInfo(id)->0,
+ */
+ QMap<QueryColumnInfo*,int> columnsOrder(ColumnsOrderOptions options = ExpandedList);
+
+ /*! \return table describing order of primary key (PKEY) fields within the query.
+ Indexing is performed against vector returned by fieldsExpanded().
+ It is usable for e.g. Conenction::updateRow(), when we need
+ to locate each primary key's field in a constant time.
+
+ Returned vector is owned and cached by QuerySchema object. When you assign it,
+ it is implicity shared. Its size is equal to number of primary key
+ fields defined for master table (masterTable()->primaryKey()->fieldCount()).
+
+ Each element of the returned vector:
+ - can belong to [0..fieldsExpanded().count()-1] if there is such
+ primary key's field in the fieldsExpanded() list.
+ - can be equal to -1 if there is no such primary key's field
+ in the fieldsExpanded() list.
+
+ If there are more than one primary key's field included in the query,
+ only first-found column (oin the fieldsExpanded() list) for each pkey's field is included.
+
+ Returns empty vector if there is no master table or no master table's pkey.
+ @see example for pkeyFieldsCount().
+@todo js: UPDATE CACHE!
+ */
+ QValueVector<int> pkeyFieldsOrder();
+
+ /*! \return number of master table's primary key fields included in this query.
+ This method is useful to quickly check whether the vector returned by pkeyFieldsOrder()
+ if filled completely.
+
+ User e.g. in Connection::updateRow() to check if entire primary
+ key information is specified.
+
+ Examples: let table T has (ID1 INTEGER, ID2 INTEGER, A INTEGER) fields,
+ and let (ID1, ID2) is T's primary key.
+ -# The query defined by "SELECT * FROM T" statement contains all T's
+ primary key's fields as T is the master table, and thus pkeyFieldsCount()
+ will return 2 (both primary key's fields are in the fieldsExpanded() list),
+ and pkeyFieldsOrder() will return vector {0, 1}.
+ -# The query defined by "SELECT A, ID2 FROM T" statement, and thus pkeyFieldsCount()
+ will return 1 (only one primary key's field is in the fieldsExpanded() list),
+ and pkeyFieldsOrder() will return vector {-1, 1}, as second primary key's field
+ is at position #1 and first field is not specified at all within the query.
+ */
+ uint pkeyFieldsCount();
+
+ /*! \return a list of field infos for all auto-incremented fields
+ from master table of this query. This result is cached for efficiency.
+ fieldsExpanded() is used for that.
+ */
+ QueryColumnInfo::List* autoIncrementFields();
+
+ /*! \return a preset statement (if any). */
+ QString statement() const;
+
+ /*! Forces a query statement (i.e. no statement is composed from QuerySchema's content) */
+ void setStatement(const QString &s);
+
+ /*! \return a string that is a result of concatenating all column names
+ for \a infolist, with "," between each one.
+ This is usable e.g. as argument like "field1,field2"
+ for "INSERT INTO (xxx) ..". The result of this method is effectively cached,
+ and it is invalidated when set of fields changes (e.g. using clear()
+ or addField()).
+
+ This method is similar to FieldList::sqlFieldsList() it just uses
+ QueryColumnInfo::List instead of Field::List.
+ */
+ static QString sqlColumnsList(QueryColumnInfo::List* infolist, Driver *driver);
+
+ /*! \return cached sql list created using sqlColumnsList() on a list returned
+ by autoIncrementFields(). */
+ QString autoIncrementSQLFieldsList(Driver *driver);
+
+ /*! Sets a WHERE expression \a exp. It will be owned by this query,
+ so you can forget about it. Previously set WHERE expression will be deleted.
+ You can pass 0 to remove expresssion. */
+ void setWhereExpression(BaseExpr *expr);
+
+ /*! \return WHERE expression or 0 if this query has no WHERE expression */
+ BaseExpr *whereExpression() const;
+
+ /*! Adds a part to WHERE expression.
+ Simplifies creating of WHERE expression, if used instead
+ of setWhereExpression(BaseExpr *expr). */
+ void addToWhereExpression(KexiDB::Field *field, const QVariant& value, int relation = '=');
+
+ /*! Sets a list of columns for ORDER BY section of the query.
+ Each name on the list must be a field or alias present within the query
+ and must not be covered by aliases. If one or more names cannot be found
+ within the query, the method will have no effect.
+ Any previous ORDER BY settings will be removed.
+
+ Note that this information is cleared whenever you call methods that
+ modify list of columns (QueryColumnInfo), i.e. insertFiled(),
+ addField(), removeField(), addExpression(), etc.
+ (because OrderByColumn items can point to a QueryColumnInfo that's removed by these
+ methods), so you should use setOrderByColumnList() method after the query
+ is completely built. */
+ void setOrderByColumnList(const OrderByColumnList& list);
+
+ /*! \return a list of columns listed in ORDER BY section of the query.
+ Read notes for \ref setOrderByColumnList(). */
+ OrderByColumnList& orderByColumnList() const;
+
+ /*! \return query schema parameters. These are taked from the WHERE section
+ (a tree of expression items). */
+ QuerySchemaParameterList parameters();
+
+ protected:
+ void init();
+
+ void computeFieldsExpanded();
+
+ QuerySchemaPrivate *d;
+
+ friend class Connection;
+ friend class QuerySchemaPrivate;
+};
+
+//! @short KexiDB::QueryAsterisk class encapsulates information about single asterisk in query definition
+/*! There are two types of query asterisks:
+
+ 1. "Single-table" asterisk, that references all fields of given table used
+ in the query.
+ Example SQL statement:
+ \code
+ SELECT staff.*, cars.model from staff, cars WHERE staff.car = cars.number;
+ \endcode
+ The "staff.*" element is our "single-table" asterisk;
+ this tells us that we want to get all fields of table "staff".
+
+ 2. "All-tables" asterisk, that references all fields of all tables used in the query.
+ Example SQL statement:
+ \code
+ SELECT * from staff, cars WHERE staff.car = cars.number;
+ \endcode
+ The "*" is our "all-tables" asterisk;
+ this tells us that we want to get all fields of all used tables (here: "staff" and "cars").
+
+ There can be many asterisks of 1st type defined for given single query.
+ There can be one asterisk of 2nd type defined for given single query.
+*/
+class KEXI_DB_EXPORT QueryAsterisk : public Field
+{
+ public:
+ /*! Constructs query asterisk definition object.
+ Pass table schema to \a table if this asterisk should be
+ of type "single-table", otherwise (if you want to define
+ "all-tables" type asterisk), omit this parameter.
+
+ QueryAsterisk objects are owned by QuerySchema object
+ (not by TableSchema object like for ordinary Field objects)
+ for that the QueryAsterisk object was added (using QuerySchema::addField()).
+ */
+ QueryAsterisk( QuerySchema *query, TableSchema *table = 0 );
+
+ virtual ~QueryAsterisk();
+
+ /*! \return Query object for that this asterisk object is defined */
+ QuerySchema *query() const { return static_cast<QuerySchema*>(m_parent); }
+
+ /*! \return Table schema for this asterisk
+ if it has "single-table" type (1st type)
+ or NULL if it has "all-tables" type (2nd type) defined. */
+ virtual TableSchema* table() const { return m_table; }
+
+ /*! Sets table schema for this asterisk.
+ \a table may be NULL - then the asterisk becames "all-tables" type asterisk. */
+ virtual void setTable(TableSchema *table);
+
+ /*! Reimplemented. */
+ virtual bool isQueryAsterisk() const { return true; }
+
+ /*! This is convenience method that returns true
+ if the asterisk has "all-tables" type (2nd type).*/
+ bool isSingleTableAsterisk() const { return m_table!=NULL; }
+
+ /*! This is convenience method that returns true
+ if the asterisk has "single-tables" type (2nd type).*/
+ bool isAllTableAsterisk() const { return m_table==NULL; }
+
+ /*! \return String for debugging purposes. */
+ virtual QString debugString() const;
+
+ protected:
+ //! \return a deep copy of this object. Used in FieldList(const FieldList& fl).
+ virtual Field* copy() const;
+
+ /*! Table schema for this asterisk */
+ TableSchema* m_table;
+
+ friend class QuerySchema;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/queryschemaparameter.cpp b/kexi/kexidb/queryschemaparameter.cpp
new file mode 100644
index 000000000..3703de247
--- /dev/null
+++ b/kexi/kexidb/queryschemaparameter.cpp
@@ -0,0 +1,103 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "queryschemaparameter.h"
+#include "driver.h"
+
+#include <kdebug.h>
+#include <qguardedptr.h>
+
+using namespace KexiDB;
+
+QuerySchemaParameter::QuerySchemaParameter()
+ : type(Field::InvalidType)
+{
+}
+
+QuerySchemaParameter::~QuerySchemaParameter()
+{
+}
+
+QString QuerySchemaParameter::debugString() const
+{
+ return QString("msg=\"%1\" type=\"%2\"").arg(Field::typeName(type)).arg(message);
+}
+
+void KexiDB::debug(const QuerySchemaParameterList& list)
+{
+ KexiDBDbg << QString("Query parameters (%1):").arg(list.count()) << endl;
+ foreach(QuerySchemaParameterListConstIterator, it, list)
+ KexiDBDbg << " - " << (*it).debugString() << endl;
+}
+
+//================================================
+
+class QuerySchemaParameterValueListIterator::Private
+{
+ public:
+ Private(const Driver& aDriver, const QValueList<QVariant>& aParams)
+ : driver(&aDriver)
+ , params(aParams)
+ {
+ //move to last item, as the order is reversed due to parser's internals
+ paramsIt = params.fromLast(); //constBegin();
+ paramsItPosition = params.count();
+ }
+ QGuardedPtr<const Driver> driver;
+ const QValueList<QVariant> params;
+ QValueList<QVariant>::ConstIterator paramsIt;
+ uint paramsItPosition;
+};
+
+QuerySchemaParameterValueListIterator::QuerySchemaParameterValueListIterator(
+ const Driver& driver, const QValueList<QVariant>& params)
+ : d( new Private(driver, params) )
+{
+}
+
+QuerySchemaParameterValueListIterator::~QuerySchemaParameterValueListIterator()
+{
+ delete d;
+}
+
+QVariant QuerySchemaParameterValueListIterator::getPreviousValue()
+{
+ if (d->paramsItPosition == 0) { //d->params.constEnd()) {
+ KexiDBWarn << "QuerySchemaParameterValues::getPreviousValue() no prev value" << endl;
+ return QVariant();
+ }
+ QVariant res( *d->paramsIt );
+ --d->paramsItPosition;
+ --d->paramsIt;
+// ++d->paramsIt;
+ return res;
+}
+
+QString QuerySchemaParameterValueListIterator::getPreviousValueAsString(Field::Type type)
+{
+ if (d->paramsItPosition == 0) { //d->params.constEnd()) {
+ KexiDBWarn << "QuerySchemaParameterValues::getPreviousValueAsString() no prev value" << endl;
+ return d->driver->valueToSQL(type, QVariant()); //"NULL"
+ }
+ QString res( d->driver->valueToSQL(type, *d->paramsIt) );
+ --d->paramsItPosition;
+ --d->paramsIt;
+// ++d->paramsIt;
+ return res;
+}
diff --git a/kexi/kexidb/queryschemaparameter.h b/kexi/kexidb/queryschemaparameter.h
new file mode 100644
index 000000000..e7c008805
--- /dev/null
+++ b/kexi/kexidb/queryschemaparameter.h
@@ -0,0 +1,69 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_QUERYSCHEMAPARAMETER_H
+#define KEXIDB_QUERYSCHEMAPARAMETER_H
+
+#include "queryschema.h"
+
+namespace KexiDB
+{
+
+//! @short A single parameter of a query schema
+class KEXI_DB_EXPORT QuerySchemaParameter
+{
+ public:
+ QuerySchemaParameter();
+ ~QuerySchemaParameter();
+
+ QString debugString() const;
+
+ Field::Type type; //!< A datatype of the parameter
+ QString message; //!< A user-visible message that will be displayed to ask for value of the parameter
+};
+
+typedef QValueList<QuerySchemaParameter> QuerySchemaParameterList;
+typedef QValueList<QuerySchemaParameter>::Iterator QuerySchemaParameterListIterator;
+typedef QValueList<QuerySchemaParameter>::ConstIterator QuerySchemaParameterListConstIterator;
+
+//! Shows debug information for \a list
+KEXI_DB_EXPORT void debug(const QuerySchemaParameterList& list);
+
+//! @short An iteratof for a list of values of query schema parameters providing
+//! Allows to iterate over parameters and return QVariant value or well-formatted string.
+//! The iterator is initially set to the last item because of the parser requirements
+class KEXI_DB_EXPORT QuerySchemaParameterValueListIterator
+{
+ public:
+ QuerySchemaParameterValueListIterator(const Driver& driver, const QValueList<QVariant>& params);
+ ~QuerySchemaParameterValueListIterator();
+
+ //! \return previous value
+ QVariant getPreviousValue();
+
+ //! \return previous value as string formatted using driver's escaping
+ QString getPreviousValueAsString(Field::Type type);
+ protected:
+ class Private;
+ Private * const d;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/record.h b/kexi/kexidb/record.h
new file mode 100644
index 000000000..29f3d6702
--- /dev/null
+++ b/kexi/kexidb/record.h
@@ -0,0 +1,71 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_RECORD_H
+#define KEXIDB_RECORD_H
+
+#include <qvaluelist.h>
+#include <qstring.h>
+
+#include <kexidb/field.h>
+
+namespace KexiDB {
+
+/*! KexiDB::Record provides single database record.
+*/
+
+class KEXI_DB_EXPORT Record {
+public:
+ Record(const QString & name);
+
+//TODO.............
+ Table();
+ ~Table();
+ const QString& name() const;
+ void setName(const QString& name);
+ unsigned int fieldCount() const;
+ KexiDB::Field field(unsigned int id) const;
+ QStringList primaryKeys() const;
+ bool hasPrimaryKeys() const;
+//js void addField(KexiDB::Field field);
+//js void addPrimaryKey(const QString& key);
+private:
+//js QStringList m_primaryKeys;
+ QValueList<Field> m_fields;
+ QString m_name;
+ Connection* m_conn;
+};
+
+
+/*
+class KexiDBTableFields: public QValueList<KexiDBField> {
+public:
+ KexiDBTable(const QString & name);
+ ~KexiDBTable();
+ void addField(KexiDBField);
+// const QString& tableName() const;
+
+private:
+// QString m_tableName;
+};
+*/
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/relationship.cpp b/kexi/kexidb/relationship.cpp
new file mode 100644
index 000000000..a7796207b
--- /dev/null
+++ b/kexi/kexidb/relationship.cpp
@@ -0,0 +1,201 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/relationship.h>
+
+#include <kexidb/indexschema.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/queryschema.h>
+#include <kexidb/driver.h>
+
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+Relationship::Relationship()
+ : m_masterIndex(0)
+ , m_detailsIndex(0)
+ , m_masterIndexOwned(false)
+ , m_detailsIndexOwned(false)
+{
+ m_pairs.setAutoDelete(true);
+}
+
+Relationship::Relationship(IndexSchema* masterIndex, IndexSchema* detailsIndex)
+ : m_masterIndex(0)
+ , m_detailsIndex(0)
+ , m_masterIndexOwned(false)
+ , m_detailsIndexOwned(false)
+{
+ m_pairs.setAutoDelete(true);
+ setIndices(masterIndex, detailsIndex);
+}
+
+Relationship::Relationship( QuerySchema *query, Field *field1, Field *field2 )
+ : m_masterIndex(0)
+ , m_detailsIndex(0)
+ , m_masterIndexOwned(false)
+ , m_detailsIndexOwned(false)
+{
+ m_pairs.setAutoDelete(true);
+ createIndices( query, field1, field2 );
+}
+
+Relationship::~Relationship()
+{
+ if (m_masterIndexOwned)
+ delete m_masterIndex;
+ if (m_detailsIndexOwned)
+ delete m_detailsIndex;
+}
+
+void Relationship::createIndices( QuerySchema *query, Field *field1, Field *field2 )
+{
+ if (!field1 || !field2 || !query) {
+ KexiDBWarn << "Relationship::addRelationship(): !masterField || !detailsField || !query" << endl;
+ return;
+ }
+ if (field1->isQueryAsterisk() || field2->isQueryAsterisk()) {
+ KexiDBWarn << "Relationship::addRelationship(): relationship's fields cannot be asterisks" << endl;
+ return;
+ }
+ if (field1->table() == field2->table()) {
+ KexiDBWarn << "Relationship::addRelationship(): fields cannot belong to the same table" << endl;
+ return;
+ }
+// if (!query->hasField(field1) && !query->hasField(field2)) {
+ if (!query->contains(field1->table()) || !query->contains(field2->table())) {
+ KexiDBWarn << "Relationship::addRelationship(): fields do not belong to this query" << endl;
+ return;
+ }
+//@todo: check more things: -types
+//@todo: find existing global db relationships
+
+ Field *masterField = 0, *detailsField = 0;
+ bool p1 = field1->isPrimaryKey(), p2 = field2->isPrimaryKey();
+ if (p1 && p2) {
+ //2 primary keys
+ masterField = field1;
+ m_masterIndex = masterField->table()->primaryKey();
+ detailsField = field2;
+ m_detailsIndex = detailsField->table()->primaryKey();
+ }
+ else if (!p1 && p2) {
+ //foreign + primary: swap
+ Field *tmp = field1;
+ field1 = field2;
+ field2 = tmp;
+ p1 = !p1;
+ p2 = !p2;
+ }
+
+ if (p1 && !p2) {
+ //primary + foreign
+ masterField = field1;
+ m_masterIndex = masterField->table()->primaryKey();
+ detailsField = field2;
+ //create foreign key
+//@todo: check if it already exists
+ m_detailsIndex = new IndexSchema(detailsField->table());
+ m_detailsIndexOwned = true;
+ m_detailsIndex->addField(detailsField);
+ m_detailsIndex->setForeignKey(true);
+ }
+ else if (!p1 && !p2) {
+ masterField = field1;
+ m_masterIndex = new IndexSchema(masterField->table());
+ m_masterIndexOwned = true;
+ m_masterIndex->addField(masterField);
+ m_masterIndex->setForeignKey(true);
+
+ detailsField = field2;
+ m_detailsIndex = new IndexSchema(detailsField->table());
+ m_detailsIndexOwned = true;
+ m_detailsIndex->addField(detailsField);
+ m_detailsIndex->setForeignKey(true);
+ }
+
+ if (!m_masterIndex || !m_detailsIndex)
+ return; //failed
+
+ setIndices(m_masterIndex, m_detailsIndex, false);
+}
+
+TableSchema* Relationship::masterTable() const
+{
+ return m_masterIndex ? m_masterIndex->table() : 0;
+}
+
+TableSchema* Relationship::detailsTable() const
+{
+ return m_detailsIndex ? m_detailsIndex->table() : 0;
+}
+
+void Relationship::setIndices(IndexSchema* masterIndex, IndexSchema* detailsIndex)
+{
+ setIndices(masterIndex, detailsIndex, true);
+}
+
+void Relationship::setIndices(IndexSchema* masterIndex, IndexSchema* detailsIndex, bool ownedByMaster)
+{
+ m_masterIndex = 0;
+ m_detailsIndex = 0;
+ m_pairs.clear();
+ if (!masterIndex || !detailsIndex || !masterIndex->table() || !detailsIndex->table()
+ || masterIndex->table()==detailsIndex->table() || masterIndex->fieldCount()!=detailsIndex->fieldCount())
+ return;
+ Field::ListIterator it1(*masterIndex->fields());
+ Field::ListIterator it2(*detailsIndex->fields());
+ for (;it1.current() && it1.current(); ++it1, ++it2) {
+ Field *f1 = it1.current(); //masterIndex->fields()->first();
+ Field *f2 = it2.current(); //detailsIndex->fields()->first();
+ // while (f1 && f2) {
+ if (f1->type()!=f1->type() && f1->isIntegerType()!=f2->isIntegerType() && f1->isTextType()!=f2->isTextType()) {
+ KexiDBWarn << "Relationship::setIndices(INDEX on '"<<masterIndex->table()->name()
+ <<"',INDEX on "<<detailsIndex->table()->name()<<"): !equal field types: "
+ <<Driver::defaultSQLTypeName(f1->type())<<" "<<f1->name()<<", "
+ <<Driver::defaultSQLTypeName(f2->type())<<" "<<f2->name() <<endl;
+ m_pairs.clear();
+ return;
+ }
+#if 0 //too STRICT!
+ if ((f1->isUnsigned() && !f2->isUnsigned()) || (!f1->isUnsigned() && f1->isUnsigned())) {
+ KexiDBWarn << "Relationship::setIndices(INDEX on '"<<masterIndex->table()->name()
+ <<"',INDEX on "<<detailsIndex->table()->name()<<"): !equal signedness of field types: "
+ <<Driver::defaultSQLTypeName(f1->type())<<" "<<f1->name()<<", "
+ <<Driver::defaultSQLTypeName(f2->type())<<" "<<f2->name() <<endl;
+ m_pairs.clear();
+ return;
+ }
+#endif
+ m_pairs.append( new Field::Pair(f1,f2) );
+ }
+ //ok: update information
+ if (m_masterIndex) {//detach yourself
+ m_masterIndex->detachRelationship(this);
+ }
+ if (m_detailsIndex) {//detach yourself
+ m_detailsIndex->detachRelationship(this);
+ }
+ m_masterIndex = masterIndex;
+ m_detailsIndex = detailsIndex;
+ m_masterIndex->attachRelationship(this, ownedByMaster);
+ m_detailsIndex->attachRelationship(this, ownedByMaster);
+}
+
diff --git a/kexi/kexidb/relationship.h b/kexi/kexidb/relationship.h
new file mode 100644
index 000000000..b72c72094
--- /dev/null
+++ b/kexi/kexidb/relationship.h
@@ -0,0 +1,156 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDB_RELATIONSHIP_H
+#define KEXIDB_RELATIONSHIP_H
+
+#include <kexidb/field.h>
+
+namespace KexiDB {
+
+/*! KexiDB::Relationship provides information about one-to-many relationship between two tables.
+ Relationship is defined by a pair of (potentially multi-field) indices:
+ - "one" or "master" side: unique key
+ - "many" or "details" side: referenced foreign key
+ <pre>
+ [unique key, master] ----< [foreign key, details]
+ </pre>
+
+ In this documentation, we will call table that owns fields of "one" side as
+ "master side of the relationship", and the table that owns foreign key fields of
+ as "details side of the relationship".
+ Use masterTable(), and detailsTable() to get one-side table and many-side table, respectively.
+
+ Note: some engines (e.g. MySQL with InnoDB) requires that indices at both sides
+ have to be explicitly created.
+
+ \todo (js) It is planned that this will be handled by KexiDB internally and transparently.
+
+ Each (of the two) key can be defined (just like index) as list of fields owned by one table.
+ Indeed, relationship info can retrieved from Relationship object in two ways:
+ -# pair of indices; use masterIndex(), detailsIndex() for that
+ -# ordered list of field pairs (<master-side-field, details-side-field>); use fieldPairs() for that
+
+ No assigned objects (like fields, indices) are owned by Relationship object. The exception is that
+ list of field-pairs is internally created (on demand) and owned.
+
+ Relationship object is owned by IndexSchema object (the one that is defined at master-side of the
+ relationship).
+ Note also that IndexSchema objects are owned by appropriate tables, so thus
+ there is implicit ownership between TableSchema and Relationship.
+
+ If Relationship object is not attached to IndexSchema object,
+ you should care about destroying it by hand.
+
+ Example:
+ <pre>
+ ----------
+ ---r1--<| |
+ | Table A [uk]----r3---<
+ ---r2--<| |
+ ----------
+ </pre>
+ Table A has two relationships (r1, r2) at details side and one (r3) at master side.
+ [uk] stands for unique key.
+*/
+
+class IndexSchema;
+class TableSchema;
+class QuerySchema;
+
+class KEXI_DB_EXPORT Relationship
+{
+ public:
+ typedef QPtrList<Relationship> List;
+ typedef QPtrListIterator<Relationship> ListIterator;
+
+ /*! Creates uninitialized Relationship object.
+ setIndices() will be required to call.
+ */
+ Relationship();
+
+ /*! Creates Relationship object and initialises it just by
+ calling setIndices(). If setIndices() failed, object is still uninitialised.
+ */
+ Relationship(IndexSchema* masterIndex, IndexSchema* detailsIndex);
+
+ virtual ~Relationship();
+
+ /*! \return index defining master side of this relationship
+ or null if there is no information defined. */
+ IndexSchema* masterIndex() const { return m_masterIndex; }
+
+ /*! \return index defining referenced side of this relationship.
+ or null if there is no information defined. */
+ IndexSchema* detailsIndex() const { return m_detailsIndex; }
+
+ /*! \return ordered list of field pairs -- alternative form
+ for representation of relationship or null if there is no information defined.
+ Each pair has a form of <master-side-field, details-side-field>. */
+ Field::PairList* fieldPairs() { return &m_pairs; }
+
+ bool isEmpty() const { return m_pairs.isEmpty(); }
+
+ /*! \return table assigned at "master / one" side of this relationship.
+ or null if there is no information defined. */
+ TableSchema* masterTable() const;
+
+ /*! \return table assigned at "details / many / foreign" side of this relationship.
+ or null if there is no information defined. */
+ TableSchema* detailsTable() const;
+
+ /*! Sets \a masterIndex and \a detailsIndex indices for this relationship.
+ This also sets information about tables for master- and details- sides.
+ Notes:
+ - both indices must contain the same number of fields
+ - both indices must not be owned by the same table, and table (owner) must be not null.
+ - corresponding field types must be the same
+ - corresponding field types' signedness must be the same
+ If above rules are not fulfilled, information about this relationship is cleared.
+ On success, this Relationship object is detached from previous IndexSchema objects that were
+ assigned before, and new are attached.
+ */
+ void setIndices(IndexSchema* masterIndex, IndexSchema* detailsIndex);
+
+ protected:
+ Relationship( QuerySchema *query, Field *field1, Field *field2 );
+
+ void createIndices( QuerySchema *query, Field *field1, Field *field2 );
+
+ /*! Internal version of setIndices(). \a ownedByMaster parameter is passed
+ to IndexSchema::attachRelationship() */
+ void setIndices(IndexSchema* masterIndex, IndexSchema* detailsIndex, bool ownedByMaster);
+
+ IndexSchema *m_masterIndex;
+ IndexSchema *m_detailsIndex;
+
+ Field::PairList m_pairs;
+
+ bool m_masterIndexOwned : 1;
+ bool m_detailsIndexOwned : 1;
+
+ friend class Connection;
+ friend class TableSchema;
+ friend class QuerySchema;
+ friend class IndexSchema;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/roweditbuffer.cpp b/kexi/kexidb/roweditbuffer.cpp
new file mode 100644
index 000000000..7b5b57110
--- /dev/null
+++ b/kexi/kexidb/roweditbuffer.cpp
@@ -0,0 +1,129 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "roweditbuffer.h"
+#include "utils.h"
+
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+
+RowEditBuffer::RowEditBuffer(bool dbAwareBuffer)
+: m_simpleBuffer(dbAwareBuffer ? 0 : new SimpleMap())
+, m_simpleBufferIt(dbAwareBuffer ? 0 : new SimpleMap::ConstIterator())
+, m_dbBuffer(dbAwareBuffer ? new DBMap() : 0)
+, m_dbBufferIt(dbAwareBuffer ? new DBMap::Iterator() : 0)
+, m_defaultValuesDbBuffer(dbAwareBuffer ? new QMap<QueryColumnInfo*,bool>() : 0)
+, m_defaultValuesDbBufferIt(dbAwareBuffer ? new QMap<QueryColumnInfo*,bool>::ConstIterator() : 0)
+{
+}
+
+RowEditBuffer::~RowEditBuffer()
+{
+ delete m_simpleBuffer;
+ delete m_simpleBufferIt;
+ delete m_dbBuffer;
+ delete m_defaultValuesDbBuffer;
+ delete m_dbBufferIt;
+}
+
+const QVariant* RowEditBuffer::at( QueryColumnInfo& ci, bool useDefaultValueIfPossible ) const
+{
+ if (!m_dbBuffer) {
+ KexiDBWarn << "RowEditBuffer::at(QueryColumnInfo&): not db-aware buffer!" << endl;
+ return 0;
+ }
+ *m_dbBufferIt = m_dbBuffer->find( &ci );
+ QVariant* result = 0;
+ if (*m_dbBufferIt!=m_dbBuffer->end())
+ result = &(*m_dbBufferIt).data();
+ if ( useDefaultValueIfPossible
+ && (!result || result->isNull())
+ && ci.field && !ci.field->defaultValue().isNull() && KexiDB::isDefaultValueAllowed(ci.field)
+ && !hasDefaultValueAt(ci) )
+ {
+ //no buffered or stored value: try to get a default value declared in a field, so user can modify it
+ if (!result)
+ m_dbBuffer->insert(&ci, ci.field->defaultValue() );
+ result = &(*m_dbBuffer)[ &ci ];
+ m_defaultValuesDbBuffer->insert(&ci, true);
+ }
+ return (const QVariant*)result;
+}
+
+const QVariant* RowEditBuffer::at( Field& f ) const
+{
+ if (!m_simpleBuffer) {
+ KexiDBWarn << "RowEditBuffer::at(Field&): this is db-aware buffer!" << endl;
+ return 0;
+ }
+ *m_simpleBufferIt = m_simpleBuffer->find( f.name() );
+ if (*m_simpleBufferIt==m_simpleBuffer->constEnd())
+ return 0;
+ return &(*m_simpleBufferIt).data();
+}
+
+const QVariant* RowEditBuffer::at( const QString& fname ) const
+{
+ if (!m_simpleBuffer) {
+ KexiDBWarn << "RowEditBuffer::at(Field&): this is db-aware buffer!" << endl;
+ return 0;
+ }
+ *m_simpleBufferIt = m_simpleBuffer->find( fname );
+ if (*m_simpleBufferIt==m_simpleBuffer->constEnd())
+ return 0;
+ return &(*m_simpleBufferIt).data();
+}
+
+void RowEditBuffer::clear() {
+ if (m_dbBuffer) {
+ m_dbBuffer->clear();
+ m_defaultValuesDbBuffer->clear();
+ }
+ if (m_simpleBuffer)
+ m_simpleBuffer->clear();
+}
+
+bool RowEditBuffer::isEmpty() const
+{
+ if (m_dbBuffer)
+ return m_dbBuffer->isEmpty();
+ if (m_simpleBuffer)
+ return m_simpleBuffer->isEmpty();
+ return true;
+}
+
+void RowEditBuffer::debug()
+{
+ if (isDBAware()) {
+ KexiDBDbg << "RowEditBuffer type=DB-AWARE, " << m_dbBuffer->count() <<" items"<< endl;
+ for (DBMap::ConstIterator it = m_dbBuffer->constBegin(); it!=m_dbBuffer->constEnd(); ++it) {
+ KexiDBDbg << "* field name=" <<it.key()->field->name()<<" val="
+ << (it.data().isNull() ? QString("<NULL>") : it.data().toString())
+ << (hasDefaultValueAt(*it.key()) ? " DEFAULT" : "") <<endl;
+ }
+ return;
+ }
+ KexiDBDbg << "RowEditBuffer type=SIMPLE, " << m_simpleBuffer->count() <<" items"<< endl;
+ for (SimpleMap::ConstIterator it = m_simpleBuffer->constBegin(); it!=m_simpleBuffer->constEnd(); ++it) {
+ KexiDBDbg << "* field name=" <<it.key()<<" val="
+ << (it.data().isNull() ? QString("<NULL>") : it.data().toString()) <<endl;
+ }
+}
diff --git a/kexi/kexidb/roweditbuffer.h b/kexi/kexidb/roweditbuffer.h
new file mode 100644
index 000000000..edf482065
--- /dev/null
+++ b/kexi/kexidb/roweditbuffer.h
@@ -0,0 +1,136 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_ROWEDITBUFFER_H
+#define KEXIDB_ROWEDITBUFFER_H
+
+#include <qmap.h>
+
+#include <kexidb/field.h>
+#include <kexidb/queryschema.h>
+
+namespace KexiDB {
+
+/*! @short provides data for single edited database row
+ KexiDB::RowEditBuffer provides data for single edited row,
+ needed to perform update at the database backend.
+ Its advantage over pasing e.g. KexiDB::FieldList object is that
+ EditBuffer contains only changed values.
+
+ EditBuffer offers two modes: db-aware and not-db-aware.
+ Db-aware buffer addresses a field using references to QueryColumnInfo object,
+ while not-db-aware buffer addresses a field using its name.
+
+ Example usage of not-db-aware buffer:
+ <code>
+ QuerySchema *query = .....
+ EditBuffer buf;
+ buf.insert("name", "Joe");
+ buf.insert("surname", "Black");
+ buf.at("name"); //returns "Joe"
+ buf.at("surname"); //returns "Black"
+ buf.at(query->field("surname")); //returns "Black" too
+ // Now you can use buf to add or edit records using
+ // KexiDB::Connection::updateRow(), KexiDB::Connection::insertRow()
+ </code>
+
+ Example usage of db-aware buffer:
+ <code>
+ QuerySchema *query = .....
+ QueryColumnInfo *ci1 = ....... //e.g. can be obtained from QueryScehma::fieldsExpanded()
+ QueryColumnInfo *ci2 = .......
+ EditBuffer buf;
+ buf.insert(*ci1, "Joe");
+ buf.insert(*ci2, "Black");
+ buf.at(*ci1); //returns "Joe"
+ buf.at(*ci2); //returns "Black"
+ // Now you can use buf to add or edit records using
+ // KexiDB::Connection::updateRow(), KexiDB::Connection::insertRow()
+ </code>
+
+ You can use QMap::clear() to clear buffer contents,
+ QMap::isEmpty() to see if buffer is empty.
+ For more, see QMap documentation.
+
+ Notes: added fields should come from the same (common) QuerySchema object.
+ However, this isn't checked at QValue& EditBuffer::operator[]( const Field& f ) level.
+*/
+class KEXI_DB_EXPORT RowEditBuffer {
+public:
+ typedef QMap<QString,QVariant> SimpleMap;
+ typedef QMap<QueryColumnInfo*,QVariant> DBMap;
+
+ RowEditBuffer(bool dbAwareBuffer);
+ ~RowEditBuffer();
+
+ inline bool isDBAware() const { return m_dbBuffer!=0; }
+
+ void clear();
+
+ bool isEmpty() const;
+
+ //! Inserts value \a val for db-aware buffer's column \a ci
+ inline void insert( QueryColumnInfo& ci, QVariant &val ) {
+ if (m_dbBuffer) {
+ m_dbBuffer->insert(&ci, val);
+ m_defaultValuesDbBuffer->remove(&ci);
+ }
+ }
+
+ //! Inserts value \a val for not-db-aware buffer's column \a fname
+ inline void insert( const QString& fname, QVariant &val )
+ { if (m_simpleBuffer) m_simpleBuffer->insert(fname,val); }
+
+ /*! Useful only for db-aware buffer. \return value for column \a ci
+ If there is no value assigned for the buffer, this method tries to remember and return
+ default value obtained from \a ci if \a useDefaultValueIfPossible is true.
+ Note that if the column is declared as unique (especially: primary key),
+ default value will not be used. */
+ const QVariant* at( QueryColumnInfo& ci, bool useDefaultValueIfPossible = true ) const;
+
+ //! Useful only for not-db-aware buffer. \return value for field \a f
+ const QVariant* at( Field& f ) const;
+
+ //! Useful only for not-db-aware buffer. \return value for field \a fname
+ const QVariant* at( const QString& fname ) const;
+
+ //! Useful only for db-aware buffer: \return true if the value available as
+ //! at( ci ) is obtained from column's default value
+ inline bool hasDefaultValueAt( QueryColumnInfo& ci ) const {
+ return m_defaultValuesDbBuffer->contains(&ci) && (*m_defaultValuesDbBuffer)[ &ci ];
+ }
+
+ inline const SimpleMap simpleBuffer() { return *m_simpleBuffer; }
+ inline const DBMap dbBuffer() { return *m_dbBuffer; }
+
+ //! For debugging purposes
+ void debug();
+
+protected:
+ SimpleMap *m_simpleBuffer;
+ SimpleMap::ConstIterator *m_simpleBufferIt;
+ DBMap *m_dbBuffer;
+ DBMap::Iterator *m_dbBufferIt;
+ QMap<QueryColumnInfo*,bool> *m_defaultValuesDbBuffer;
+ QMap<QueryColumnInfo*,bool>::ConstIterator *m_defaultValuesDbBufferIt;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/schemadata.cpp b/kexi/kexidb/schemadata.cpp
new file mode 100644
index 000000000..0a0c2124a
--- /dev/null
+++ b/kexi/kexidb/schemadata.cpp
@@ -0,0 +1,55 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/schemadata.h>
+#include <kexidb/connection.h>
+
+#include <kdebug.h>
+
+using namespace KexiDB;
+
+SchemaData::SchemaData(int obj_type)
+ : m_type(obj_type)
+ , m_id(-1)
+ , m_native(false)
+{
+}
+
+SchemaData::~SchemaData()
+{
+}
+
+void SchemaData::clear()
+{
+ m_id = -1;
+ m_name = QString::null;
+ m_caption = QString::null;
+ m_desc = QString::null;
+}
+
+QString SchemaData::schemaDataDebugString() const
+{
+ QString desc = m_desc;
+ if (desc.length()>40) {
+ desc.truncate(40);
+ desc+="...";
+ }
+ return QString("id=%1 name='%2' caption='%3' desc='%4'")
+ .arg(m_id).arg(m_name).arg(m_caption).arg(desc);
+}
diff --git a/kexi/kexidb/schemadata.h b/kexi/kexidb/schemadata.h
new file mode 100644
index 000000000..615f8602e
--- /dev/null
+++ b/kexi/kexidb/schemadata.h
@@ -0,0 +1,92 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_SCHEMADATA_H
+#define KEXIDB_SCHEMADATA_H
+
+#include <qvaluelist.h>
+#include <qstring.h>
+
+#include <kexidb/global.h>
+#include <kexidb/field.h>
+
+namespace KexiDB {
+
+/*! Container class that stores common kexi object schema's properties like
+ id, name, caption, help text.
+ By kexi object we mean in-db storable object like table schema or query schema.
+*/
+
+class KEXI_DB_EXPORT SchemaData
+{
+ public:
+ SchemaData(int obj_type = KexiDB::UnknownObjectType);
+ virtual ~SchemaData();
+
+ int type() const { return m_type; }
+ int id() const { return m_id; }
+ QString name() const { return m_name; }
+ /*! The same as name(). Added to avoid conflict with QObject::name() */
+ QString objectName() const { return m_name; }
+ void setName(const QString& n) { m_name=n; }
+ QString caption() const { return m_caption; }
+ void setCaption(const QString& c) { m_caption=c; }
+ QString description() const { return m_desc; }
+ void setDescription(const QString& desc) { m_desc=desc; }
+
+ /*! \return debug string useful for debugging */
+ virtual QString schemaDataDebugString() const;
+
+ /*! \return true if this is schema of native database object,
+ like, for example like, native table. This flag
+ is set when object schema (currently -- database table)
+ is not retrieved using kexi__* schema storage system,
+ but just based on the information about native table.
+
+ By native object we mean the one that has no additional
+ data like caption, description, etc. properties (no kexidb extensions).
+
+ Native objects schemas are used mostly for representing
+ kexi system (kexi__*) tables in memory for later reference;
+ see Connection::tableNames().
+
+ By default (on allocation) SchemaData objects are not native.
+ */
+ virtual bool isNative() const { return m_native; }
+
+ /* Sets native flag */
+ virtual void setNative(bool set) { m_native=set; }
+
+ protected:
+ //! Clears all properties except 'type'.
+ void clear();
+
+ int m_type;
+ int m_id;
+ QString m_name;
+ QString m_caption;
+ QString m_desc;
+ bool m_native : 1;
+
+ friend class Connection;
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/simplecommandlineapp.cpp b/kexi/kexidb/simplecommandlineapp.cpp
new file mode 100644
index 000000000..ec73cde24
--- /dev/null
+++ b/kexi/kexidb/simplecommandlineapp.cpp
@@ -0,0 +1,228 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "simplecommandlineapp.h"
+
+#include <qfileinfo.h>
+
+#include <kcmdlineargs.h>
+#include <kdebug.h>
+
+#include <kexidb/connectiondata.h>
+#include <kexidb/drivermanager.h>
+
+using namespace KexiDB;
+
+static KCmdLineOptions predefinedOptions[] =
+{
+ { "drv", 0, 0 },
+ { "driver <name>", I18N_NOOP("Database driver name"), 0 },
+ { "u", 0, 0 },
+ { "user <name>", I18N_NOOP("Database user name"), 0 },
+ { "p", 0, 0 },
+ { "password", I18N_NOOP("Prompt for password"), 0 },
+ { "h", 0, 0 },
+ { "host <name>", I18N_NOOP("Host (server) name"), 0 },
+ { "port <number>", I18N_NOOP("Server's port number"), 0 },
+ { "s", 0, 0 },
+ { "local-socket <filename>", I18N_NOOP("Server's local socket filename"), 0 },
+ KCmdLineLastOption
+};
+
+//-----------------------------------------
+
+//! @internal used for SimpleCommandLineApp
+class SimpleCommandLineApp::Private
+{
+public:
+ Private()
+ : conn(0)
+ {}
+ ~Private()
+ {
+ if (conn) {
+ conn->disconnect();
+ delete (Connection*)conn;
+ }
+ delete instance;
+
+ for (KCmdLineOptions *optionsPtr = allOptions; optionsPtr->name; optionsPtr++) {
+ delete optionsPtr->name;
+ delete optionsPtr->description;
+ delete optionsPtr->def;
+ }
+ delete allOptions;
+ }
+
+ KexiDB::DriverManager manager;
+ KCmdLineOptions *allOptions;
+ KInstance* instance;
+ ConnectionData connData;
+ QGuardedPtr<Connection> conn;
+};
+
+//-----------------------------------------
+
+SimpleCommandLineApp::SimpleCommandLineApp(
+ int argc, char** argv, KCmdLineOptions *options,
+ const char *programName, const char *version,
+ const char *shortDescription, int licenseType,
+ const char *copyrightStatement, const char *text,
+ const char *homePageAddress, const char *bugsEmailAddress)
+ : Object()
+ , d( new Private() )
+{
+ QFileInfo fi(argv[0]);
+ QCString appName( fi.baseName().latin1() );
+ KCmdLineArgs::init(argc, argv,
+ new KAboutData( appName, programName,
+ version, shortDescription, licenseType, copyrightStatement, text,
+ homePageAddress, bugsEmailAddress));
+
+ int predefinedOptionsCount = 0;
+ for (KCmdLineOptions *optionsPtr = predefinedOptions; optionsPtr->name; optionsPtr++, predefinedOptionsCount++)
+ ;
+ int userOptionsCount = 0;
+ for (KCmdLineOptions *optionsPtr = options; optionsPtr->name; optionsPtr++, userOptionsCount++)
+ ;
+
+ d->instance = new KInstance(appName);
+
+ // join the predefined options and user options
+ d->allOptions = new KCmdLineOptions[predefinedOptionsCount + userOptionsCount + 1];
+ KCmdLineOptions *allOptionsPtr = d->allOptions;
+ for (KCmdLineOptions *optionsPtr = predefinedOptions; optionsPtr->name; optionsPtr++, allOptionsPtr++) {
+ allOptionsPtr->name = qstrdup(optionsPtr->name);
+ allOptionsPtr->description = qstrdup(optionsPtr->description);
+ if (optionsPtr == predefinedOptions) //first row == drv
+ allOptionsPtr->def = qstrdup(KexiDB::Driver::defaultFileBasedDriverName().latin1());
+ else
+ allOptionsPtr->def = qstrdup(optionsPtr->def);
+ }
+ for (KCmdLineOptions *optionsPtr = options; optionsPtr->name; optionsPtr++, allOptionsPtr++) {
+ allOptionsPtr->name = qstrdup(optionsPtr->name);
+ allOptionsPtr->description = qstrdup(optionsPtr->description);
+ allOptionsPtr->def = qstrdup(optionsPtr->def);
+ }
+ allOptionsPtr->name = 0; //end
+ allOptionsPtr->description = 0;
+ allOptionsPtr->def = 0;
+ KCmdLineArgs::addCmdLineOptions( d->allOptions );
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+
+ d->connData.driverName = args->getOption("driver");
+ d->connData.userName = args->getOption("user");
+ d->connData.hostName = args->getOption("host");
+ d->connData.localSocketFileName = args->getOption("local-socket");
+ d->connData.port = args->getOption("port").toInt();
+ d->connData.useLocalSocketFile = args->isSet("local-socket");
+
+ if (args->isSet("password")) {
+ QString userAtHost = d->connData.userName;
+ if (!d->connData.userName.isEmpty())
+ userAtHost += "@";
+ userAtHost += (d->connData.hostName.isEmpty() ? "localhost" : d->connData.hostName);
+ QTextStream cout(stdout,IO_WriteOnly);
+ cout << i18n("Enter password for %1: ").arg(userAtHost);
+//! @todo make use of pty/tty here! (and care about portability)
+ QTextStream cin(stdin,IO_ReadOnly);
+ cin >> d->connData.password;
+ KexiDBDbg << d->connData.password << endl;
+ }
+}
+
+SimpleCommandLineApp::~SimpleCommandLineApp()
+{
+ closeDatabase();
+ delete d;
+}
+
+bool SimpleCommandLineApp::openDatabase(const QString& databaseName)
+{
+ if (!d->conn) {
+ if (d->manager.error()) {
+ setError(&d->manager);
+ return false;
+ }
+
+ //get the driver
+ KexiDB::Driver *driver = d->manager.driver(d->connData.driverName);
+ if (!driver || d->manager.error()) {
+ setError(&d->manager);
+ return false;
+ }
+
+ if (driver->isFileDriver())
+ d->connData.setFileName( databaseName );
+
+ d->conn = driver->createConnection(d->connData);
+ if (!d->conn || driver->error()) {
+ setError(driver);
+ return false;
+ }
+ }
+ if (d->conn->isConnected()) {
+ // db already opened
+ if (d->conn->isDatabaseUsed() && d->conn->currentDatabase()==databaseName) //the same: do nothing
+ return true;
+ if (!closeDatabase()) // differs: close the first
+ return false;
+ }
+ if (!d->conn->connect()) {
+ setError(d->conn);
+ delete d->conn;
+ d->conn = 0;
+ return false;
+ }
+
+ if (!d->conn->useDatabase( databaseName )) {
+ setError(d->conn);
+ delete d->conn;
+ d->conn = 0;
+ return false;
+ }
+ return true;
+}
+
+bool SimpleCommandLineApp::closeDatabase()
+{
+ if (!d->conn)
+ return true;
+ if (!d->conn->disconnect()) {
+ setError(d->conn);
+ return false;
+ }
+ return true;
+}
+
+KInstance* SimpleCommandLineApp::instance() const
+{
+ return d->instance;
+}
+
+KexiDB::ConnectionData* SimpleCommandLineApp::connectionData() const
+{
+ return &d->connData;
+}
+
+KexiDB::Connection* SimpleCommandLineApp::connection() const
+{
+ return d->conn;
+}
diff --git a/kexi/kexidb/simplecommandlineapp.h b/kexi/kexidb/simplecommandlineapp.h
new file mode 100644
index 000000000..13d1f1158
--- /dev/null
+++ b/kexi/kexidb/simplecommandlineapp.h
@@ -0,0 +1,86 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_SIMPLECMDLINEAPP_H
+#define KEXIDB_SIMPLECMDLINEAPP_H
+
+#include <kexidb/connection.h>
+#include <kexidb/driver.h>
+
+#include <kaboutdata.h>
+
+struct KCmdLineOptions;
+
+namespace KexiDB
+{
+ //! @short A skeleton for creating a simple command line database application.
+ /*! This class creates a KInstance object and automatically handles the following
+ command line options:
+ - --driver \<name\> (Database driver name) or -drv
+ - --user \<name\> (Database user name) or -u
+ - --password (Prompt for password) or -p
+ - --host \<name\> (Server (host) name) or -h
+ - --port \<number\> (Server's port number)
+ - --local-socket \<filename\> (Server's local socket filename, if needed) or -s
+
+ You can use this helper class to create test applications or small tools that open
+ a KexiDB-compatible database using command line arguments, do some data processing
+ and close the database.
+ */
+ class KEXI_DB_EXPORT SimpleCommandLineApp : public KexiDB::Object
+ {
+ public:
+ SimpleCommandLineApp(
+ int argc, char** argv, KCmdLineOptions *options, const char *programName,
+ const char *version, const char *shortDescription=0,
+ int licenseType=KAboutData::License_Unknown,
+ const char *copyrightStatement=0, const char *text=0,
+ const char *homePageAddress=0, const char *bugsEmailAddress="submit@bugs.kde.org");
+
+ ~SimpleCommandLineApp();
+
+ //! \return program instance
+ KInstance* instance() const;
+
+ /*! Opens database \a databaseName for connection data
+ specified via the command line. \return true in success.
+ In details: the database driver is loaded, the connection is opened
+ and the database is used.
+ Use KexiDB::Object methods to get status of the operation on failure. */
+ bool openDatabase(const QString& databaseName);
+
+ /*! Closes database connection previously opened using openDatabase()
+ \return true on success. This method is called on destruction.
+ Use KexiDB::Object methods to get status of the operation on failure. */
+ bool closeDatabase();
+
+ /*! \return connection data for this application. */
+ KexiDB::ConnectionData* connectionData() const;
+
+ /*! \return connection object for this application or 0 if there is no properly
+ opened connection. */
+ KexiDB::Connection* connection() const;
+
+ protected:
+ class Private;
+ Private * const d;
+ };
+}
+
+#endif
diff --git a/kexi/kexidb/tableschema.cpp b/kexi/kexidb/tableschema.cpp
new file mode 100644
index 000000000..8c0f5e07c
--- /dev/null
+++ b/kexi/kexidb/tableschema.cpp
@@ -0,0 +1,453 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "tableschema.h"
+#include "driver.h"
+#include "connection.h"
+#include "lookupfieldschema.h"
+
+#include <assert.h>
+#include <kdebug.h>
+
+namespace KexiDB {
+//! @internal
+class TableSchema::Private
+{
+public:
+ Private()
+ : anyNonPKField(0)
+ {
+ }
+
+ ~Private()
+ {
+ clearLookupFields();
+ }
+
+ void clearLookupFields()
+ {
+ for (QMap<const Field*, LookupFieldSchema*>::ConstIterator it = lookupFields.constBegin();
+ it!=lookupFields.constEnd(); ++it)
+ {
+ delete it.data();
+ }
+ lookupFields.clear();
+ }
+
+ Field *anyNonPKField;
+ QMap<const Field*, LookupFieldSchema*> lookupFields;
+ QPtrVector<LookupFieldSchema> lookupFieldsList;
+};
+}
+//-------------------------------------
+
+
+using namespace KexiDB;
+
+TableSchema::TableSchema(const QString& name)
+ : FieldList(true)
+ , SchemaData(KexiDB::TableObjectType)
+ , m_query(0)
+ , m_isKexiDBSystem(false)
+{
+ m_name = name.lower();
+ init();
+}
+
+TableSchema::TableSchema(const SchemaData& sdata)
+ : FieldList(true)
+ , SchemaData(sdata)
+ , m_query(0)
+ , m_isKexiDBSystem(false)
+{
+ init();
+}
+
+TableSchema::TableSchema()
+ : FieldList(true)
+ , SchemaData(KexiDB::TableObjectType)
+ , m_query(0)
+ , m_isKexiDBSystem(false)
+{
+ init();
+}
+
+TableSchema::TableSchema(const TableSchema& ts, bool copyId)
+ : FieldList(static_cast<const FieldList&>(ts))
+ , SchemaData(static_cast<const SchemaData&>(ts))
+{
+ init(ts, copyId);
+}
+
+TableSchema::TableSchema(const TableSchema& ts, int setId)
+ : FieldList(static_cast<const FieldList&>(ts))
+ , SchemaData(static_cast<const SchemaData&>(ts))
+{
+ init(ts, false);
+ m_id = setId;
+}
+
+// used by Connection
+TableSchema::TableSchema(Connection *conn, const QString & name)
+ : FieldList(true)
+ , SchemaData(KexiDB::TableObjectType)
+ , m_conn( conn )
+ , m_query(0)
+ , m_isKexiDBSystem(false)
+{
+ d = new Private();
+ assert(conn);
+ m_name = name;
+ m_indices.setAutoDelete( true );
+ m_pkey = new IndexSchema(this);
+ m_indices.append(m_pkey);
+}
+
+TableSchema::~TableSchema()
+{
+ if (m_conn)
+ m_conn->removeMe( this );
+ delete m_query;
+ delete d;
+}
+
+void TableSchema::init()
+{
+ d = new Private();
+ m_indices.setAutoDelete( true );
+ m_pkey = new IndexSchema(this);
+ m_indices.append(m_pkey);
+}
+
+void TableSchema::init(const TableSchema& ts, bool copyId)
+{
+ m_conn = ts.m_conn;
+ m_query = 0; //not cached
+ m_isKexiDBSystem = false;
+ d = new Private();
+ m_name = ts.m_name;
+ m_indices.setAutoDelete( true );
+ m_pkey = 0; //will be copied
+ if (!copyId)
+ m_id = -1;
+
+ //deep copy all members
+ IndexSchema::ListIterator idx_it(ts.m_indices);
+ for (;idx_it.current();++idx_it) {
+ IndexSchema *idx = new IndexSchema(
+ *idx_it.current(), *this /*fields from _this_ table will be assigned to the index*/);
+ if (idx->isPrimaryKey()) {//assign pkey
+ m_pkey = idx;
+ }
+ m_indices.append(idx);
+ }
+}
+
+void TableSchema::setPrimaryKey(IndexSchema *pkey)
+{
+ if (m_pkey && m_pkey!=pkey) {
+ if (m_pkey->fieldCount()==0) {//this is empty key, probably default - remove it
+ m_indices.remove(m_pkey);
+ }
+ else {
+ m_pkey->setPrimaryKey(false); //there can be only one pkey..
+ //thats ok, the old pkey is still on indices list, if not empty
+ }
+// m_pkey=0;
+ }
+
+ if (!pkey) {//clearing - set empty pkey
+ pkey = new IndexSchema(this);
+ }
+ m_pkey = pkey; //todo
+ m_pkey->setPrimaryKey(true);
+ d->anyNonPKField = 0; //for safety
+}
+
+FieldList& TableSchema::insertField(uint index, Field *field)
+{
+ assert(field);
+ FieldList::insertField(index, field);
+ if (!field || index>m_fields.count())
+ return *this;
+ field->setTable(this);
+ field->m_order = index; //m_fields.count();
+ //update order for next next fields
+ Field *f = m_fields.at(index+1);
+ for (int i=index+1; f; i++, f = m_fields.next())
+ f->m_order = i;
+
+ //Check for auto-generated indices:
+ IndexSchema *idx = 0;
+ if (field->isPrimaryKey()) {// this is auto-generated single-field unique index
+ idx = new IndexSchema(this);
+ idx->setAutoGenerated(true);
+ idx->addField( field );
+ setPrimaryKey(idx);
+ }
+ if (field->isUniqueKey()) {
+ if (!idx) {
+ idx = new IndexSchema(this);
+ idx->setAutoGenerated(true);
+ idx->addField( field );
+ }
+ idx->setUnique(true);
+ }
+ if (field->isIndexed()) {// this is auto-generated single-field
+ if (!idx) {
+ idx = new IndexSchema(this);
+ idx->setAutoGenerated(true);
+ idx->addField( field );
+ }
+ }
+ if (idx)
+ m_indices.append(idx);
+ return *this;
+}
+
+void TableSchema::removeField(KexiDB::Field *field)
+{
+ if (d->anyNonPKField && field == d->anyNonPKField) //d->anyNonPKField will be removed!
+ d->anyNonPKField = 0;
+ delete d->lookupFields[field];
+ d->lookupFields.remove(field);
+ FieldList::removeField(field);
+}
+
+#if 0 //original
+KexiDB::FieldList& TableSchema::addField(KexiDB::Field* field)
+{
+ assert(field);
+ FieldList::addField(field);
+ field->setTable(this);
+ field->m_order = m_fields.count();
+ //Check for auto-generated indices:
+
+ IndexSchema *idx = 0;
+ if (field->isPrimaryKey()) {// this is auto-generated single-field unique index
+ idx = new IndexSchema(this);
+ idx->setAutoGenerated(true);
+ idx->addField( field );
+ setPrimaryKey(idx);
+ }
+ if (field->isUniqueKey()) {
+ if (!idx) {
+ idx = new IndexSchema(this);
+ idx->setAutoGenerated(true);
+ idx->addField( field );
+ }
+ idx->setUnique(true);
+ }
+ if (field->isIndexed()) {// this is auto-generated single-field
+ if (!idx) {
+ idx = new IndexSchema(this);
+ idx->setAutoGenerated(true);
+ idx->addField( field );
+ }
+ }
+ if (idx)
+ m_indices.append(idx);
+ return *this;
+}
+#endif
+
+void TableSchema::clear()
+{
+ m_indices.clear();
+ d->clearLookupFields();
+ FieldList::clear();
+ SchemaData::clear();
+ m_conn = 0;
+}
+
+/*
+void TableSchema::addPrimaryKey(const QString& key)
+{
+ m_primaryKeys.append(key);
+}*/
+
+/*QStringList TableSchema::primaryKeys() const
+{
+ return m_primaryKeys;
+}
+
+bool TableSchema::hasPrimaryKeys() const
+{
+ return !m_primaryKeys.isEmpty();
+}
+*/
+
+//const QString& TableSchema::name() const
+//{
+// return m_name;
+//}
+
+//void TableSchema::setName(const QString& name)
+//{
+// m_name=name;
+/* ListIterator it( m_fields );
+ Field *field;
+ for (; (field = it.current())!=0; ++it) {
+
+ int fcnt=m_fields.count();
+ for (int i=0;i<fcnt;i++) {
+ m_fields[i].setTable(name);
+ }*/
+//}
+
+/*KexiDB::Field TableSchema::field(unsigned int id) const
+{
+ if (id<m_fields.count()) return m_fields[id];
+ return KexiDB::Field();
+}
+
+unsigned int TableSchema::fieldCount() const
+{
+ return m_fields.count();
+}*/
+
+QString TableSchema::debugString()
+{
+ return debugString(true);
+}
+
+QString TableSchema::debugString(bool includeTableName)
+{
+ QString s;
+ if (includeTableName)
+ s = QString("TABLE ") + schemaDataDebugString() + "\n";
+ s.append( FieldList::debugString() );
+
+ Field *f;
+ for (Field::ListIterator it(m_fields); (f = it.current()); ++it) {
+ LookupFieldSchema *lookupSchema = lookupFieldSchema( *f );
+ if (lookupSchema)
+ s.append( QString("\n") + lookupSchema->debugString() );
+ }
+ return s;
+}
+
+void TableSchema::setKexiDBSystem(bool set)
+{
+ if (set)
+ m_native=true;
+ m_isKexiDBSystem = set;
+}
+
+void TableSchema::setNative(bool set)
+{
+ if (m_isKexiDBSystem && !set) {
+ KexiDBWarn << "TableSchema::setNative(): cannot set native off"
+ " when KexiDB system flag is set on!" << endl;
+ return;
+ }
+ m_native=set;
+}
+
+QuerySchema* TableSchema::query()
+{
+ if (m_query)
+ return m_query;
+ m_query = new QuerySchema( *this ); //it's owned by me
+ return m_query;
+}
+
+Field* TableSchema::anyNonPKField()
+{
+ if (!d->anyNonPKField) {
+ Field *f;
+ Field::ListIterator it(m_fields);
+ it.toLast(); //from the end (higher chances to find)
+ for (; (f = it.current()); --it) {
+ if (!f->isPrimaryKey() && (!m_pkey || !m_pkey->hasField(f)))
+ break;
+ }
+ d->anyNonPKField = f;
+ }
+ return d->anyNonPKField;
+}
+
+bool TableSchema::setLookupFieldSchema( const QString& fieldName, LookupFieldSchema *lookupFieldSchema )
+{
+ Field *f = field(fieldName);
+ if (!f) {
+ KexiDBWarn << "TableSchema::setLookupFieldSchema(): no such field '" << fieldName
+ << "' in table " << name() << endl;
+ return false;
+ }
+ if (lookupFieldSchema)
+ d->lookupFields.replace( f, lookupFieldSchema );
+ else {
+ delete d->lookupFields[f];
+ d->lookupFields.remove( f );
+ }
+ d->lookupFieldsList.clear(); //this will force to rebuid the internal cache
+ return true;
+}
+
+LookupFieldSchema *TableSchema::lookupFieldSchema( const Field& field ) const
+{
+ return d->lookupFields[ &field ];
+}
+
+LookupFieldSchema *TableSchema::lookupFieldSchema( const QString& fieldName )
+{
+ Field *f = TableSchema::field(fieldName);
+ if (!f)
+ return 0;
+ return lookupFieldSchema( *f );
+}
+
+const QPtrVector<LookupFieldSchema>& TableSchema::lookupFieldsList()
+{
+ if (d->lookupFields.isEmpty())
+ return d->lookupFieldsList;
+ if (!d->lookupFields.isEmpty() && !d->lookupFieldsList.isEmpty())
+ return d->lookupFieldsList; //already updated
+ //update
+ d->lookupFieldsList.clear();
+ d->lookupFieldsList.resize( d->lookupFields.count() );
+ uint i = 0;
+ for (Field::ListIterator it(m_fields); it.current(); ++it) {
+ QMap<const Field*, LookupFieldSchema*>::ConstIterator itMap = d->lookupFields.find( it.current() );
+ if (itMap != d->lookupFields.constEnd()) {
+ d->lookupFieldsList.insert( i, itMap.data() );
+ i++;
+ }
+ }
+ return d->lookupFieldsList;
+}
+
+//--------------------------------------
+
+InternalTableSchema::InternalTableSchema(const QString& name)
+ : TableSchema(name)
+{
+}
+
+InternalTableSchema::InternalTableSchema(const TableSchema& ts)
+ : TableSchema(ts, false)
+{
+}
+
+InternalTableSchema::~InternalTableSchema()
+{
+}
+
diff --git a/kexi/kexidb/tableschema.h b/kexi/kexidb/tableschema.h
new file mode 100644
index 000000000..7584d7037
--- /dev/null
+++ b/kexi/kexidb/tableschema.h
@@ -0,0 +1,210 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_TABLE_H
+#define KEXIDB_TABLE_H
+
+#include <qvaluelist.h>
+#include <qptrlist.h>
+#include <qstring.h>
+#include <qguardedptr.h>
+
+#include <kexidb/fieldlist.h>
+#include <kexidb/schemadata.h>
+#include <kexidb/indexschema.h>
+#include <kexidb/relationship.h>
+
+namespace KexiDB {
+
+class Connection;
+class LookupFieldSchema;
+
+/*! KexiDB::TableSchema provides information about native database table
+ that can be stored using KexiDB database engine.
+*/
+class KEXI_DB_EXPORT TableSchema : public FieldList, public SchemaData
+{
+ public:
+ typedef QPtrList<TableSchema> List; //!< Type of tables list
+ typedef QPtrListIterator<TableSchema> ListIterator; //!< Iterator for tables list
+
+ TableSchema(const QString & name);
+ TableSchema(const SchemaData& sdata);
+ TableSchema();
+
+ /*! Copy constructor.
+ if \a copyId is true, it's copied as well, otherwise the table id becomes -1,
+ what is usable when we want to store the copy as an independent table. */
+ TableSchema(const TableSchema& ts, bool copyId = true);
+
+ /*! Copy constructor like \ref TableSchema(const TableSchema&, bool).
+ \a setId is set as the table identifier. This is rarely usable, e.g.
+ in project and data migration routines when we need to need deal with unique identifiers;
+ @see KexiMigrate::performImport(). */
+ TableSchema(const TableSchema& ts, int setId);
+
+ virtual ~TableSchema();
+
+ /*! Inserts \a field into a specified position (\a index).
+ 'order' property of \a field is set automatically. */
+ virtual FieldList& insertField(uint index, Field *field);
+
+ /*! Reimplemented for internal reasons. */
+ virtual void removeField(KexiDB::Field *field);
+
+ /*! \return list of fields that are primary key of this table.
+ This method never returns 0 value,
+ if there is no primary key, empty IndexSchema object is returned.
+ IndexSchema object is owned by the table schema. */
+ IndexSchema* primaryKey() const { return m_pkey; }
+
+ /*! Sets table's primary key index to \a pkey. Pass pkey==0 if you want to unassign
+ existing primary key ("primary" property of given IndexSchema object will be
+ cleared then so this index becomes ordinary index, still existing on table indeices list).
+
+ If this table already has primary key assigned,
+ it is unassigned using setPrimaryKey(0) call.
+
+ Before assigning as primary key, you should add the index to indices list
+ with addIndex() (this is not done automatically!).
+ */
+ void setPrimaryKey(IndexSchema *pkey);
+
+ const IndexSchema::ListIterator indicesIterator() const
+ { return IndexSchema::ListIterator(m_indices); }
+
+ const IndexSchema::List* indices() { return &m_indices; }
+
+ /*! Removes all fields from the list, clears name and all other properties.
+ \sa FieldList::clear() */
+ virtual void clear();
+
+ /*! \return String for debugging purposes, if \a includeTableName is true,
+ table name, caption, etc. is prepended, else only debug string for
+ the fields are returned. */
+ QString debugString(bool includeTableName);
+
+ /*! \return String for debugging purposes. Equal to debugString(true). */
+ virtual QString debugString();
+
+ /*! \return connection object if table was created/retrieved using a connection,
+ otherwise 0. */
+ Connection* connection() const { return m_conn; }
+
+ /*! \return true if this is KexiDB storage system's table
+ (used internally by KexiDB). This helps in hiding such tables
+ in applications (if desired) and will also enable lookup of system
+ tables for schema export/import functionality.
+
+ Any internal KexiDB system table's schema (kexi__*) has
+ cleared its SchemaData part, e.g. id=-1 for such table,
+ and no description, caption and so on. This is because
+ it represents a native database table rather that extended Kexi table.
+
+ isKexiDBSystem()==true implies isNative()==true.
+
+ By default (after allocation), TableSchema object
+ has this property set to false. */
+ bool isKexiDBSystem() const { return m_isKexiDBSystem; }
+
+ /*! Sets KexiDBSystem flag to on or off. When on, native flag is forced to be on.
+ When off, native flag is not affected.
+ \sa isKexiDBSystem() */
+ void setKexiDBSystem(bool set);
+
+ /*! \return true if this is schema of native database object,
+ When this is kexiDBSystem table, native flag is forced to be on. */
+ virtual bool isNative() const { return m_native || m_isKexiDBSystem; }
+
+ /* Sets native flag. Does not allow to set this off for system KexiDB table. */
+ virtual void setNative(bool set);
+
+ /*! \return query schema object that is defined by "select * from <this_table_name>"
+ This query schema object is owned by the table schema object.
+ It is convenient way to get such a query when it is not available otherwise.
+ Always returns non-0. */
+ QuerySchema* query();
+
+ /*! \return any field not being a part of primary key of this table.
+ If there is no such field, returns 0. */
+ Field* anyNonPKField();
+
+ /*! Sets lookup field schema \a lookupFieldSchema for \a fieldName.
+ Passing null \a lookupFieldSchema will remove the previously set lookup field.
+ \return true if \a lookupFieldSchema has been added,
+ or false if there is no such field \a fieldName. */
+ bool setLookupFieldSchema( const QString& fieldName, LookupFieldSchema *lookupFieldSchema );
+
+ /*! \return lookup field schema for \a field.
+ 0 is returned if there is no such field in the table or this field has no lookup schema.
+ Note that even id non-zero is returned here, you may want to check whether lookup field's
+ rowSource().name() is empty (if so, the field should behave as there was no lookup field
+ defined at all). */
+ LookupFieldSchema *lookupFieldSchema( const Field& field ) const;
+
+ /*! \overload LookupFieldSchema *TableSchema::lookupFieldSchema( Field& field ) const */
+ LookupFieldSchema *lookupFieldSchema( const QString& fieldName );
+
+ /*! \return list of lookup field schemas for this table.
+ The order is the same as the order of fields within the table. */
+ const QPtrVector<LookupFieldSchema>& lookupFieldsList();
+
+ protected:
+ /*! Automatically retrieves table schema via connection. */
+ TableSchema(Connection *conn, const QString & name = QString::null);
+
+ IndexSchema::List m_indices;
+
+ QGuardedPtr<Connection> m_conn;
+
+ IndexSchema *m_pkey;
+
+ QuerySchema *m_query; //!< cached query schema that is defined by "select * from <this_table_name>"
+
+ class Private;
+ Private *d;
+
+ private:
+ //! Used by some ctors.
+ void init();
+
+ //! Used by some ctors.
+ void init(const TableSchema& ts, bool copyId);
+
+ bool m_isKexiDBSystem : 1;
+
+ friend class Connection;
+};
+
+/*! Internal table with a name \a name. Rarely used.
+ Use Connection::createTable() to create a table using this schema.
+ The table will not be visible as user table.
+ For example, 'kexi__blobs' table is created this way by Kexi application. */
+class KEXI_DB_EXPORT InternalTableSchema : public TableSchema
+{
+ public:
+ InternalTableSchema(const QString& name);
+ InternalTableSchema(const TableSchema& ts);
+ virtual ~InternalTableSchema();
+};
+
+} //namespace KexiDB
+
+#endif
diff --git a/kexi/kexidb/transaction.cpp b/kexi/kexidb/transaction.cpp
new file mode 100644
index 000000000..c06074488
--- /dev/null
+++ b/kexi/kexidb/transaction.cpp
@@ -0,0 +1,165 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexidb/transaction.h>
+#include <kexidb/connection.h>
+
+#include <kdebug.h>
+
+#include <assert.h>
+
+//remove debug
+#undef KexiDBDbg
+#define KexiDBDbg if (0) kdDebug()
+
+using namespace KexiDB;
+
+//helper for debugging
+KEXI_DB_EXPORT int Transaction::globalcount = 0;
+KEXI_DB_EXPORT int Transaction::globalCount() { return Transaction::globalcount; }
+KEXI_DB_EXPORT int TransactionData::globalcount = 0;
+KEXI_DB_EXPORT int TransactionData::globalCount() { return TransactionData::globalcount; }
+
+TransactionData::TransactionData(Connection *conn)
+ : m_conn(conn)
+ , m_active(true)
+ , refcount(1)
+{
+ assert(conn);
+ Transaction::globalcount++; //because refcount(1) init.
+ TransactionData::globalcount++;
+ KexiDBDbg << "-- TransactionData::globalcount == " << TransactionData::globalcount << endl;
+}
+
+TransactionData::~TransactionData()
+{
+ TransactionData::globalcount--;
+ KexiDBDbg << "-- TransactionData::globalcount == " << TransactionData::globalcount << endl;
+}
+
+//---------------------------------------------------
+
+const Transaction Transaction::null;
+
+Transaction::Transaction()
+ : QObject(0,"kexidb_transaction")
+ , m_data(0)
+{
+}
+
+Transaction::Transaction( const Transaction& trans )
+ : QObject(0,"kexidb_transaction")
+ , m_data(trans.m_data)
+{
+ if (m_data) {
+ m_data->refcount++;
+ Transaction::globalcount++;
+ }
+}
+
+Transaction::~Transaction()
+{
+ if (m_data) {
+ m_data->refcount--;
+ Transaction::globalcount--;
+ KexiDBDbg << "~Transaction(): m_data->refcount==" << m_data->refcount << endl;
+ if (m_data->refcount==0)
+ delete m_data;
+ }
+ else {
+ KexiDBDbg << "~Transaction(): null" << endl;
+ }
+ KexiDBDbg << "-- Transaction::globalcount == " << Transaction::globalcount << endl;
+}
+
+Transaction& Transaction::operator=(const Transaction& trans)
+{
+ if (m_data) {
+ m_data->refcount--;
+ Transaction::globalcount--;
+ KexiDBDbg << "Transaction::operator=: m_data->refcount==" << m_data->refcount << endl;
+ if (m_data->refcount==0)
+ delete m_data;
+ }
+ m_data = trans.m_data;
+ if (m_data) {
+ m_data->refcount++;
+ Transaction::globalcount++;
+ }
+ return *this;
+}
+
+bool Transaction::operator==(const Transaction& trans) const
+{
+ return m_data==trans.m_data;
+}
+
+Connection* Transaction::connection() const
+{
+ return m_data ? m_data->m_conn : 0;
+}
+
+bool Transaction::active() const
+{
+ return m_data && m_data->m_active;
+}
+
+bool Transaction::isNull() const
+{
+ return m_data==0;
+}
+
+//---------------------------------------------------
+
+TransactionGuard::TransactionGuard( Connection& conn )
+ : m_trans( conn.beginTransaction() )
+ , m_doNothing(false)
+{
+}
+
+TransactionGuard::TransactionGuard( const Transaction& trans )
+ : m_trans(trans)
+ , m_doNothing(false)
+{
+}
+
+TransactionGuard::TransactionGuard()
+ : m_doNothing(false)
+{
+}
+
+TransactionGuard::~TransactionGuard()
+{
+ if (!m_doNothing && m_trans.active() && m_trans.connection())
+ m_trans.connection()->rollbackTransaction(m_trans);
+}
+
+bool TransactionGuard::commit()
+{
+ if (m_trans.active() && m_trans.connection()) {
+ return m_trans.connection()->commitTransaction(m_trans);
+ }
+ return false;
+}
+
+void TransactionGuard::doNothing()
+{
+ m_doNothing = true;
+}
+
diff --git a/kexi/kexidb/transaction.h b/kexi/kexidb/transaction.h
new file mode 100644
index 000000000..2ec065d90
--- /dev/null
+++ b/kexi/kexidb/transaction.h
@@ -0,0 +1,159 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_TRANSACTION_H
+#define KEXIDB_TRANSACTION_H
+
+#include <qguardedptr.h>
+
+#include <kexidb/kexidb_export.h>
+
+namespace KexiDB {
+
+class Connection;
+
+/*! Internal prototype for storing transaction handles for Transaction object.
+ Only for driver developers: reimplement this class for driver that
+ support transaction handles.
+*/
+class KEXI_DB_EXPORT TransactionData
+{
+ public:
+ TransactionData(Connection *conn);
+ ~TransactionData();
+
+ //helper for debugging
+ static int globalcount;
+ //helper for debugging
+ static int globalCount();
+
+ Connection *m_conn;
+ bool m_active : 1;
+ uint refcount;
+};
+
+//! This class encapsulates transaction handle.
+/*! Transaction handle is sql driver-dependent,
+ but outside Transaction is visible as universal container
+ for any handler implementation.
+
+ Transaction object is value-based, internal data (handle) structure,
+ reference-counted.
+*/
+class KEXI_DB_EXPORT Transaction : public QObject
+{
+ public:
+ /*! Constructs uninitialised (null) transaction.
+ Only in Conenction code it can be initialised */
+ Transaction();
+
+ //! Copy ctor.
+ Transaction( const Transaction& trans );
+
+ virtual ~Transaction();
+
+ Transaction& operator=(const Transaction& trans);
+
+ bool operator==(const Transaction& trans ) const;
+
+ Connection* connection() const;
+
+ /*! \return true if transaction is avtive (ie. started)
+ Returns false also if transaction is uninitialised (null). */
+ bool active() const;
+
+ /*! \return true if transaction is uinitialised (null). */
+ bool isNull() const;
+
+ /*! shortcut that offers uinitialised (null) transaction */
+ static const Transaction null;
+
+ //helper for debugging
+ static int globalCount();
+ static int globalcount;
+ protected:
+
+ TransactionData *m_data;
+
+ friend class Connection;
+};
+
+//! Helper class for using inside methods for given connection.
+/*! It can be used in two ways:
+ - start new transaction in constructor and rollback on destruction (1st constructor),
+ - use already started transaction and rollback on destruction (2nd constructor).
+ In any case, if transaction is committed or rolled back outside this TransactionGuard
+ object in the meantime, nothing happens on TransactionGuard destruction.
+ <code>
+ Example usage:
+ void myclas::my_method()
+ {
+ Transaction *transaction = connection->beginTransaction();
+ TransactionGuard tg(transaction);
+ ...some code that operates inside started transaction...
+ if (something)
+ return //after return from this code block: tg will call
+ //connection->rollbackTransaction() automatically
+ if (something_else)
+ transaction->commit();
+ //for now tg won't do anything because transaction does not exist
+ }
+ </code>
+*/
+class KEXI_DB_EXPORT TransactionGuard
+{
+ public:
+ /*! Constructor #1: Starts new transaction constructor for \a connection.
+ Started transaction handle is available via transaction().*/
+ TransactionGuard( Connection& conn );
+
+ /*! Constructor #2: Uses already started transaction. */
+ TransactionGuard( const Transaction& trans );
+
+ /*! Constructor #3: Creates TransactionGuard without transaction assinged.
+ setTransaction() can be used later to do so. */
+ TransactionGuard();
+
+ /*! Rollbacks not committed transaction. */
+ ~TransactionGuard();
+
+ /*! Assigns transaction \a trans to this guard.
+ Previously assigned transaction will be unassigned from this guard. */
+ void setTransaction( const Transaction& trans ) { m_trans = trans; }
+
+ /*! Comits the guarded transaction.
+ It is convenient shortcut to connection->commitTransaction(this->transaction()) */
+ bool commit();
+
+ /*! Makes guarded transaction not guarded, so nothing will be performed on guard's desctruction. */
+ void doNothing();
+
+ /*! Transaction that are controlled by this guard. */
+ const Transaction transaction() const { return m_trans; }
+
+ protected:
+ Transaction m_trans;
+ bool m_doNothing : 1;
+};
+
+} //namespace KexiDB
+
+#endif
+
+
diff --git a/kexi/kexidb/utils.cpp b/kexi/kexidb/utils.cpp
new file mode 100644
index 000000000..78b7e4162
--- /dev/null
+++ b/kexi/kexidb/utils.cpp
@@ -0,0 +1,1262 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "utils.h"
+#include "cursor.h"
+#include "drivermanager.h"
+#include "lookupfieldschema.h"
+
+#include <qmap.h>
+#include <qthread.h>
+#include <qdom.h>
+#include <qintdict.h>
+#include <qbuffer.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kstaticdeleter.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kiconloader.h>
+
+#include "utils_p.h"
+
+using namespace KexiDB;
+
+//! Cache
+struct TypeCache
+{
+ QMap< uint, TypeGroupList > tlist;
+ QMap< uint, QStringList > nlist;
+ QMap< uint, QStringList > slist;
+ QMap< uint, Field::Type > def_tlist;
+};
+
+static KStaticDeleter<TypeCache> KexiDB_typeCacheDeleter;
+TypeCache *KexiDB_typeCache = 0;
+
+static void initList()
+{
+ KexiDB_typeCacheDeleter.setObject( KexiDB_typeCache, new TypeCache() );
+
+ for (uint t=0; t<=KexiDB::Field::LastType; t++) {
+ const uint tg = KexiDB::Field::typeGroup( t );
+ TypeGroupList list;
+ QStringList name_list, str_list;
+ if (KexiDB_typeCache->tlist.find( tg )!=KexiDB_typeCache->tlist.end()) {
+ list = KexiDB_typeCache->tlist[ tg ];
+ name_list = KexiDB_typeCache->nlist[ tg ];
+ str_list = KexiDB_typeCache->slist[ tg ];
+ }
+ list+= t;
+ name_list += KexiDB::Field::typeName( t );
+ str_list += KexiDB::Field::typeString( t );
+ KexiDB_typeCache->tlist[ tg ] = list;
+ KexiDB_typeCache->nlist[ tg ] = name_list;
+ KexiDB_typeCache->slist[ tg ] = str_list;
+ }
+
+ KexiDB_typeCache->def_tlist[ Field::InvalidGroup ] = Field::InvalidType;
+ KexiDB_typeCache->def_tlist[ Field::TextGroup ] = Field::Text;
+ KexiDB_typeCache->def_tlist[ Field::IntegerGroup ] = Field::Integer;
+ KexiDB_typeCache->def_tlist[ Field::FloatGroup ] = Field::Double;
+ KexiDB_typeCache->def_tlist[ Field::BooleanGroup ] = Field::Boolean;
+ KexiDB_typeCache->def_tlist[ Field::DateTimeGroup ] = Field::Date;
+ KexiDB_typeCache->def_tlist[ Field::BLOBGroup ] = Field::BLOB;
+}
+
+const TypeGroupList KexiDB::typesForGroup(KexiDB::Field::TypeGroup typeGroup)
+{
+ if (!KexiDB_typeCache)
+ initList();
+ return KexiDB_typeCache->tlist[ typeGroup ];
+}
+
+QStringList KexiDB::typeNamesForGroup(KexiDB::Field::TypeGroup typeGroup)
+{
+ if (!KexiDB_typeCache)
+ initList();
+ return KexiDB_typeCache->nlist[ typeGroup ];
+}
+
+QStringList KexiDB::typeStringsForGroup(KexiDB::Field::TypeGroup typeGroup)
+{
+ if (!KexiDB_typeCache)
+ initList();
+ return KexiDB_typeCache->slist[ typeGroup ];
+}
+
+KexiDB::Field::Type KexiDB::defaultTypeForGroup(KexiDB::Field::TypeGroup typeGroup)
+{
+ if (!KexiDB_typeCache)
+ initList();
+ return (typeGroup <= Field::LastTypeGroup) ? KexiDB_typeCache->def_tlist[ typeGroup ] : Field::InvalidType;
+}
+
+void KexiDB::getHTMLErrorMesage(Object* obj, QString& msg, QString &details)
+{
+ Connection *conn = 0;
+ if (!obj || !obj->error()) {
+ if (dynamic_cast<Cursor*>(obj)) {
+ conn = dynamic_cast<Cursor*>(obj)->connection();
+ obj = conn;
+ }
+ else {
+ return;
+ }
+ }
+// if (dynamic_cast<Connection*>(obj)) {
+ // conn = dynamic_cast<Connection*>(obj);
+ //}
+ if (!obj || !obj->error())
+ return;
+ //lower level message is added to the details, if there is alread message specified
+ if (!obj->msgTitle().isEmpty())
+ msg += "<p>" + obj->msgTitle();
+
+ if (msg.isEmpty())
+ msg = "<p>" + obj->errorMsg();
+ else
+ details += "<p>" + obj->errorMsg();
+
+ if (!obj->serverErrorMsg().isEmpty())
+ details += "<p><b><nobr>" +i18n("Message from server:") + "</nobr></b><br>" + obj->serverErrorMsg();
+ if (!obj->recentSQLString().isEmpty())
+ details += "<p><b><nobr>" +i18n("SQL statement:") + QString("</nobr></b><br><tt>%1</tt>").arg(obj->recentSQLString());
+ int serverResult;
+ QString serverResultName;
+ if (obj->serverResult()!=0) {
+ serverResult = obj->serverResult();
+ serverResultName = obj->serverResultName();
+ }
+ else {
+ serverResult = obj->previousServerResult();
+ serverResultName = obj->previousServerResultName();
+ }
+ if (!serverResultName.isEmpty())
+ details += (QString("<p><b><nobr>")+i18n("Server result name:")+"</nobr></b><br>"+serverResultName);
+ if (!details.isEmpty()
+ && (!obj->serverErrorMsg().isEmpty() || !obj->recentSQLString().isEmpty() || !serverResultName.isEmpty() || serverResult!=0) )
+ {
+ details += (QString("<p><b><nobr>")+i18n("Server result number:")+"</nobr></b><br>"+QString::number(serverResult));
+ }
+
+ if (!details.isEmpty() && !details.startsWith("<qt>")) {
+ if (details.startsWith("<p>"))
+ details = QString::fromLatin1("<qt>")+details;
+ else
+ details = QString::fromLatin1("<qt><p>")+details;
+ }
+}
+
+void KexiDB::getHTMLErrorMesage(Object* obj, QString& msg)
+{
+ getHTMLErrorMesage(obj, msg, msg);
+}
+
+void KexiDB::getHTMLErrorMesage(Object* obj, ResultInfo *result)
+{
+ getHTMLErrorMesage(obj, result->msg, result->desc);
+}
+
+int KexiDB::idForObjectName( Connection &conn, const QString& objName, int objType )
+{
+ RowData data;
+ if (true!=conn.querySingleRecord(QString("select o_id from kexi__objects where lower(o_name)='%1' and o_type=%2")
+ .arg(objName.lower()).arg(objType), data))
+ return 0;
+ bool ok;
+ int id = data[0].toInt(&ok);
+ return ok ? id : 0;
+}
+
+//-----------------------------------------
+
+TableOrQuerySchema::TableOrQuerySchema(Connection *conn, const QCString& name)
+ : m_name(name)
+{
+ m_table = conn->tableSchema(QString(name));
+ m_query = m_table ? 0 : conn->querySchema(QString(name));
+ if (!m_table && !m_query)
+ KexiDBWarn << "TableOrQuery(FieldList &tableOrQuery) : "
+ " tableOrQuery is neither table nor query!" << endl;
+}
+
+TableOrQuerySchema::TableOrQuerySchema(Connection *conn, const QCString& name, bool table)
+ : m_name(name)
+ , m_table(table ? conn->tableSchema(QString(name)) : 0)
+ , m_query(table ? 0 : conn->querySchema(QString(name)))
+{
+ if (table && !m_table)
+ KexiDBWarn << "TableOrQuery(Connection *conn, const QCString& name, bool table) : "
+ "no table specified!" << endl;
+ if (!table && !m_query)
+ KexiDBWarn << "TableOrQuery(Connection *conn, const QCString& name, bool table) : "
+ "no query specified!" << endl;
+}
+
+TableOrQuerySchema::TableOrQuerySchema(FieldList &tableOrQuery)
+ : m_table(dynamic_cast<TableSchema*>(&tableOrQuery))
+ , m_query(dynamic_cast<QuerySchema*>(&tableOrQuery))
+{
+ if (!m_table && !m_query)
+ KexiDBWarn << "TableOrQuery(FieldList &tableOrQuery) : "
+ " tableOrQuery is nether table nor query!" << endl;
+}
+
+TableOrQuerySchema::TableOrQuerySchema(Connection *conn, int id)
+{
+ m_table = conn->tableSchema(id);
+ m_query = m_table ? 0 : conn->querySchema(id);
+ if (!m_table && !m_query)
+ KexiDBWarn << "TableOrQuery(Connection *conn, int id) : no table or query found for id=="
+ << id << "!" << endl;
+}
+
+TableOrQuerySchema::TableOrQuerySchema(TableSchema* table)
+ : m_table(table)
+ , m_query(0)
+{
+ if (!m_table)
+ KexiDBWarn << "TableOrQuery(TableSchema* table) : no table specified!" << endl;
+}
+
+TableOrQuerySchema::TableOrQuerySchema(QuerySchema* query)
+ : m_table(0)
+ , m_query(query)
+{
+ if (!m_query)
+ KexiDBWarn << "TableOrQuery(QuerySchema* query) : no query specified!" << endl;
+}
+
+uint TableOrQuerySchema::fieldCount() const
+{
+ if (m_table)
+ return m_table->fieldCount();
+ if (m_query)
+ return m_query->fieldsExpanded().size();
+ return 0;
+}
+
+const QueryColumnInfo::Vector TableOrQuerySchema::columns(bool unique)
+{
+ if (m_table)
+ return m_table->query()->fieldsExpanded(unique ? QuerySchema::Unique : QuerySchema::Default);
+
+ if (m_query)
+ return m_query->fieldsExpanded(unique ? QuerySchema::Unique : QuerySchema::Default);
+
+ KexiDBWarn << "TableOrQuerySchema::column() : no query or table specified!" << endl;
+ return QueryColumnInfo::Vector();
+}
+
+QCString TableOrQuerySchema::name() const
+{
+ if (m_table)
+ return m_table->name().latin1();
+ if (m_query)
+ return m_query->name().latin1();
+ return m_name;
+}
+
+QString TableOrQuerySchema::captionOrName() const
+{
+ SchemaData *sdata = m_table ? static_cast<SchemaData *>(m_table) : static_cast<SchemaData *>(m_query);
+ if (!sdata)
+ return m_name;
+ return sdata->caption().isEmpty() ? sdata->name() : sdata->caption();
+}
+
+Field* TableOrQuerySchema::field(const QString& name)
+{
+ if (m_table)
+ return m_table->field(name);
+ if (m_query)
+ return m_query->field(name);
+
+ return 0;
+}
+
+QueryColumnInfo* TableOrQuerySchema::columnInfo(const QString& name)
+{
+ if (m_table)
+ return m_table->query()->columnInfo(name);
+
+ if (m_query)
+ return m_query->columnInfo(name);
+
+ return 0;
+}
+
+QString TableOrQuerySchema::debugString()
+{
+ if (m_table)
+ return m_table->debugString();
+ else if (m_query)
+ return m_query->debugString();
+ return QString::null;
+}
+
+void TableOrQuerySchema::debug()
+{
+ if (m_table)
+ return m_table->debug();
+ else if (m_query)
+ return m_query->debug();
+}
+
+Connection* TableOrQuerySchema::connection() const
+{
+ if (m_table)
+ return m_table->connection();
+ else if (m_query)
+ return m_query->connection();
+ return 0;
+}
+
+
+//------------------------------------------
+
+class ConnectionTestThread : public QThread {
+ public:
+ ConnectionTestThread(ConnectionTestDialog *dlg, const KexiDB::ConnectionData& connData);
+ virtual void run();
+ protected:
+ ConnectionTestDialog* m_dlg;
+ KexiDB::ConnectionData m_connData;
+};
+
+ConnectionTestThread::ConnectionTestThread(ConnectionTestDialog* dlg, const KexiDB::ConnectionData& connData)
+ : m_dlg(dlg), m_connData(connData)
+{
+}
+
+void ConnectionTestThread::run()
+{
+ KexiDB::DriverManager manager;
+ KexiDB::Driver* drv = manager.driver(m_connData.driverName);
+// KexiGUIMessageHandler msghdr;
+ if (!drv || manager.error()) {
+//move msghdr.showErrorMessage(&Kexi::driverManager());
+ m_dlg->error(&manager);
+ return;
+ }
+ KexiDB::Connection * conn = drv->createConnection(m_connData);
+ if (!conn || drv->error()) {
+//move msghdr.showErrorMessage(drv);
+ delete conn;
+ m_dlg->error(drv);
+ return;
+ }
+ if (!conn->connect() || conn->error()) {
+//move msghdr.showErrorMessage(conn);
+ m_dlg->error(conn);
+ delete conn;
+ return;
+ }
+ // SQL database backends like PostgreSQL require executing "USE database"
+ // if we really want to know connection to the server succeeded.
+ QString tmpDbName;
+ if (!conn->useTemporaryDatabaseIfNeeded( tmpDbName )) {
+ m_dlg->error(conn);
+ delete conn;
+ return;
+ }
+ delete conn;
+ m_dlg->error(0);
+}
+
+ConnectionTestDialog::ConnectionTestDialog(QWidget* parent,
+ const KexiDB::ConnectionData& data,
+ KexiDB::MessageHandler& msgHandler)
+ : KProgressDialog(parent, "testconn_dlg",
+ i18n("Test Connection"), i18n("<qt>Testing connection to <b>%1</b> database server...</qt>")
+ .arg(data.serverInfoString(true)), true /*modal*/)
+ , m_thread(new ConnectionTestThread(this, data))
+ , m_connData(data)
+ , m_msgHandler(&msgHandler)
+ , m_elapsedTime(0)
+ , m_errorObj(0)
+ , m_stopWaiting(false)
+{
+ showCancelButton(true);
+ progressBar()->setPercentageVisible(false);
+ progressBar()->setTotalSteps(0);
+ connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
+ adjustSize();
+ resize(250, height());
+}
+
+ConnectionTestDialog::~ConnectionTestDialog()
+{
+ m_wait.wakeAll();
+ m_thread->terminate();
+ delete m_thread;
+}
+
+int ConnectionTestDialog::exec()
+{
+ m_timer.start(20);
+ m_thread->start();
+ const int res = KProgressDialog::exec();
+ m_thread->wait();
+ m_timer.stop();
+ return res;
+}
+
+void ConnectionTestDialog::slotTimeout()
+{
+// KexiDBDbg << "ConnectionTestDialog::slotTimeout() " << m_errorObj << endl;
+ bool notResponding = false;
+ if (m_elapsedTime >= 1000*5) {//5 seconds
+ m_stopWaiting = true;
+ notResponding = true;
+ }
+ if (m_stopWaiting) {
+ m_timer.disconnect(this);
+ m_timer.stop();
+ slotCancel();
+// reject();
+// close();
+ if (m_errorObj) {
+ m_msgHandler->showErrorMessage(m_errorObj);
+ m_errorObj = 0;
+ }
+ else if (notResponding) {
+ KMessageBox::sorry(0,
+ i18n("<qt>Test connection to <b>%1</b> database server failed. The server is not responding.</qt>")
+ .arg(m_connData.serverInfoString(true)),
+ i18n("Test Connection"));
+ }
+ else {
+ KMessageBox::information(0,
+ i18n("<qt>Test connection to <b>%1</b> database server established successfully.</qt>")
+ .arg(m_connData.serverInfoString(true)),
+ i18n("Test Connection"));
+ }
+// slotCancel();
+// reject();
+ m_wait.wakeAll();
+ return;
+ }
+ m_elapsedTime += 20;
+ progressBar()->setProgress( m_elapsedTime );
+}
+
+void ConnectionTestDialog::error(KexiDB::Object *obj)
+{
+ KexiDBDbg << "ConnectionTestDialog::error()" << endl;
+ m_stopWaiting = true;
+ m_errorObj = obj;
+/* reject();
+ m_msgHandler->showErrorMessage(obj);
+ if (obj) {
+ }
+ else {
+ accept();
+ }*/
+ m_wait.wait();
+}
+
+void ConnectionTestDialog::slotCancel()
+{
+// m_wait.wakeAll();
+ m_thread->terminate();
+ m_timer.disconnect(this);
+ m_timer.stop();
+ KProgressDialog::slotCancel();
+}
+
+void KexiDB::connectionTestDialog(QWidget* parent, const KexiDB::ConnectionData& data,
+ KexiDB::MessageHandler& msgHandler)
+{
+ ConnectionTestDialog dlg(parent, data, msgHandler);
+ dlg.exec();
+}
+
+int KexiDB::rowCount(Connection &conn, const QString& sql)
+{
+ int count = -1; //will be changed only on success of querySingleNumber()
+ QString selectSql( QString::fromLatin1("SELECT COUNT() FROM (") + sql + ")" );
+ conn.querySingleNumber(selectSql, count);
+ return count;
+}
+
+int KexiDB::rowCount(const KexiDB::TableSchema& tableSchema)
+{
+//! @todo does not work with non-SQL data sources
+ if (!tableSchema.connection()) {
+ KexiDBWarn << "KexiDB::rowsCount(const KexiDB::TableSchema&): no tableSchema.connection() !" << endl;
+ return -1;
+ }
+ int count = -1; //will be changed only on success of querySingleNumber()
+ tableSchema.connection()->querySingleNumber(
+ QString::fromLatin1("SELECT COUNT(*) FROM ")
+ + tableSchema.connection()->driver()->escapeIdentifier(tableSchema.name()),
+ count
+ );
+ return count;
+}
+
+int KexiDB::rowCount(KexiDB::QuerySchema& querySchema)
+{
+//! @todo does not work with non-SQL data sources
+ if (!querySchema.connection()) {
+ KexiDBWarn << "KexiDB::rowsCount(const KexiDB::QuerySchema&): no querySchema.connection() !" << endl;
+ return -1;
+ }
+ int count = -1; //will be changed only on success of querySingleNumber()
+ querySchema.connection()->querySingleNumber(
+ QString::fromLatin1("SELECT COUNT(*) FROM (")
+ + querySchema.connection()->selectStatement(querySchema) + ")",
+ count
+ );
+ return count;
+}
+
+int KexiDB::rowCount(KexiDB::TableOrQuerySchema& tableOrQuery)
+{
+ if (tableOrQuery.table())
+ return rowCount( *tableOrQuery.table() );
+ if (tableOrQuery.query())
+ return rowCount( *tableOrQuery.query() );
+ return -1;
+}
+
+int KexiDB::fieldCount(KexiDB::TableOrQuerySchema& tableOrQuery)
+{
+ if (tableOrQuery.table())
+ return tableOrQuery.table()->fieldCount();
+ if (tableOrQuery.query())
+ return tableOrQuery.query()->fieldsExpanded().count();
+ return -1;
+}
+
+QMap<QString,QString> KexiDB::toMap( const ConnectionData& data )
+{
+ QMap<QString,QString> m;
+ m["caption"] = data.caption;
+ m["description"] = data.description;
+ m["driverName"] = data.driverName;
+ m["hostName"] = data.hostName;
+ m["port"] = QString::number(data.port);
+ m["useLocalSocketFile"] = QString::number((int)data.useLocalSocketFile);
+ m["localSocketFileName"] = data.localSocketFileName;
+ m["password"] = data.password;
+ m["savePassword"] = QString::number((int)data.savePassword);
+ m["userName"] = data.userName;
+ m["fileName"] = data.fileName();
+ return m;
+}
+
+void KexiDB::fromMap( const QMap<QString,QString>& map, ConnectionData& data )
+{
+ data.caption = map["caption"];
+ data.description = map["description"];
+ data.driverName = map["driverName"];
+ data.hostName = map["hostName"];
+ data.port = map["port"].toInt();
+ data.useLocalSocketFile = map["useLocalSocketFile"].toInt()==1;
+ data.localSocketFileName = map["localSocketFileName"];
+ data.password = map["password"];
+ data.savePassword = map["savePassword"].toInt()==1;
+ data.userName = map["userName"];
+ data.setFileName(map["fileName"]);
+}
+
+bool KexiDB::splitToTableAndFieldParts(const QString& string,
+ QString& tableName, QString& fieldName,
+ SplitToTableAndFieldPartsOptions option)
+{
+ const int id = string.find('.');
+ if (option & SetFieldNameIfNoTableName && id==-1) {
+ tableName = QString::null;
+ fieldName = string;
+ return !fieldName.isEmpty();
+ }
+ if (id<=0 || id==int(string.length()-1))
+ return false;
+ tableName = string.left(id);
+ fieldName = string.mid(id+1);
+ return !tableName.isEmpty() && !fieldName.isEmpty();
+}
+
+bool KexiDB::supportsVisibleDecimalPlacesProperty(Field::Type type)
+{
+//! @todo add check for decimal type as well
+ return Field::isFPNumericType(type);
+}
+
+QString KexiDB::formatNumberForVisibleDecimalPlaces(double value, int decimalPlaces)
+{
+//! @todo round?
+ if (decimalPlaces < 0) {
+ QString s( QString::number(value, 'f', 10 /*reasonable precision*/));
+ uint i = s.length()-1;
+ while (i>0 && s[i]=='0')
+ i--;
+ if (s[i]=='.') //remove '.'
+ i--;
+ s = s.left(i+1).replace('.', KGlobal::locale()->decimalSymbol());
+ return s;
+ }
+ if (decimalPlaces == 0)
+ return QString::number((int)value);
+ return KGlobal::locale()->formatNumber(value, decimalPlaces);
+}
+
+KexiDB::Field::Type KexiDB::intToFieldType( int type )
+{
+ if (type<(int)KexiDB::Field::InvalidType || type>(int)KexiDB::Field::LastType) {
+ KexiDBWarn << "KexiDB::intToFieldType(): invalid type " << type << endl;
+ return KexiDB::Field::InvalidType;
+ }
+ return (KexiDB::Field::Type)type;
+}
+
+static bool setIntToFieldType( Field& field, const QVariant& value )
+{
+ bool ok;
+ const int intType = value.toInt(&ok);
+ if (!ok || KexiDB::Field::InvalidType == intToFieldType(intType)) {//for sanity
+ KexiDBWarn << "KexiDB::setFieldProperties(): invalid type" << endl;
+ return false;
+ }
+ field.setType((KexiDB::Field::Type)intType);
+ return true;
+}
+
+//! for KexiDB::isBuiltinTableFieldProperty()
+static KStaticDeleter< QAsciiDict<char> > KexiDB_builtinFieldPropertiesDeleter;
+//! for KexiDB::isBuiltinTableFieldProperty()
+QAsciiDict<char>* KexiDB_builtinFieldProperties = 0;
+
+bool KexiDB::isBuiltinTableFieldProperty( const QCString& propertyName )
+{
+ if (!KexiDB_builtinFieldProperties) {
+ KexiDB_builtinFieldPropertiesDeleter.setObject( KexiDB_builtinFieldProperties, new QAsciiDict<char>(499) );
+#define ADD(name) KexiDB_builtinFieldProperties->insert(name, (char*)1)
+ ADD("type");
+ ADD("primaryKey");
+ ADD("indexed");
+ ADD("autoIncrement");
+ ADD("unique");
+ ADD("notNull");
+ ADD("allowEmpty");
+ ADD("unsigned");
+ ADD("name");
+ ADD("caption");
+ ADD("description");
+ ADD("length");
+ ADD("precision");
+ ADD("defaultValue");
+ ADD("width");
+ ADD("visibleDecimalPlaces");
+//! @todo always update this when new builtins appear!
+#undef ADD
+ }
+ return KexiDB_builtinFieldProperties->find( propertyName );
+}
+
+bool KexiDB::setFieldProperties( Field& field, const QMap<QCString, QVariant>& values )
+{
+ QMapConstIterator<QCString, QVariant> it;
+ if ( (it = values.find("type")) != values.constEnd() ) {
+ if (!setIntToFieldType(field, *it))
+ return false;
+ }
+
+#define SET_BOOLEAN_FLAG(flag, value) { \
+ constraints |= KexiDB::Field::flag; \
+ if (!value) \
+ constraints ^= KexiDB::Field::flag; \
+ }
+
+ uint constraints = field.constraints();
+ bool ok = true;
+ if ( (it = values.find("primaryKey")) != values.constEnd() )
+ SET_BOOLEAN_FLAG(PrimaryKey, (*it).toBool());
+ if ( (it = values.find("indexed")) != values.constEnd() )
+ SET_BOOLEAN_FLAG(Indexed, (*it).toBool());
+ if ( (it = values.find("autoIncrement")) != values.constEnd()
+ && KexiDB::Field::isAutoIncrementAllowed(field.type()) )
+ SET_BOOLEAN_FLAG(AutoInc, (*it).toBool());
+ if ( (it = values.find("unique")) != values.constEnd() )
+ SET_BOOLEAN_FLAG(Unique, (*it).toBool());
+ if ( (it = values.find("notNull")) != values.constEnd() )
+ SET_BOOLEAN_FLAG(NotNull, (*it).toBool());
+ if ( (it = values.find("allowEmpty")) != values.constEnd() )
+ SET_BOOLEAN_FLAG(NotEmpty, !(*it).toBool());
+ field.setConstraints( constraints );
+
+ uint options = 0;
+ if ( (it = values.find("unsigned")) != values.constEnd()) {
+ options |= KexiDB::Field::Unsigned;
+ if (!(*it).toBool())
+ options ^= KexiDB::Field::Unsigned;
+ }
+ field.setOptions( options );
+
+ if ( (it = values.find("name")) != values.constEnd())
+ field.setName( (*it).toString() );
+ if ( (it = values.find("caption")) != values.constEnd())
+ field.setCaption( (*it).toString() );
+ if ( (it = values.find("description")) != values.constEnd())
+ field.setDescription( (*it).toString() );
+ if ( (it = values.find("length")) != values.constEnd())
+ field.setLength( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) );
+ if (!ok)
+ return false;
+ if ( (it = values.find("precision")) != values.constEnd())
+ field.setPrecision( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) );
+ if (!ok)
+ return false;
+ if ( (it = values.find("defaultValue")) != values.constEnd())
+ field.setDefaultValue( *it );
+ if ( (it = values.find("width")) != values.constEnd())
+ field.setWidth( (*it).isNull() ? 0/*default*/ : (*it).toUInt(&ok) );
+ if (!ok)
+ return false;
+ if ( (it = values.find("visibleDecimalPlaces")) != values.constEnd()
+ && KexiDB::supportsVisibleDecimalPlacesProperty(field.type()) )
+ field.setVisibleDecimalPlaces( (*it).isNull() ? -1/*default*/ : (*it).toInt(&ok) );
+ if (!ok)
+ return false;
+
+ // set custom properties
+ typedef QMap<QCString, QVariant> PropertiesMap;
+ foreach( PropertiesMap::ConstIterator, it, values ) {
+ if (!isBuiltinTableFieldProperty( it.key() ) && !isExtendedTableFieldProperty( it.key() )) {
+ field.setCustomProperty( it.key(), it.data() );
+ }
+ }
+ return true;
+#undef SET_BOOLEAN_FLAG
+}
+
+//! for KexiDB::isExtendedTableFieldProperty()
+static KStaticDeleter< QAsciiDict<char> > KexiDB_extendedPropertiesDeleter;
+//! for KexiDB::isExtendedTableFieldProperty()
+QAsciiDict<char>* KexiDB_extendedProperties = 0;
+
+bool KexiDB::isExtendedTableFieldProperty( const QCString& propertyName )
+{
+ if (!KexiDB_extendedProperties) {
+ KexiDB_extendedPropertiesDeleter.setObject( KexiDB_extendedProperties, new QAsciiDict<char>(499, false) );
+#define ADD(name) KexiDB_extendedProperties->insert(name, (char*)1)
+ ADD("visibleDecimalPlaces");
+ ADD("rowSource");
+ ADD("rowSourceType");
+ ADD("rowSourceValues");
+ ADD("boundColumn");
+ ADD("visibleColumn");
+ ADD("columnWidths");
+ ADD("showColumnHeaders");
+ ADD("listRows");
+ ADD("limitToList");
+ ADD("displayWidget");
+#undef ADD
+ }
+ return KexiDB_extendedProperties->find( propertyName );
+}
+
+bool KexiDB::setFieldProperty( Field& field, const QCString& propertyName, const QVariant& value )
+{
+#define SET_BOOLEAN_FLAG(flag, value) { \
+ constraints |= KexiDB::Field::flag; \
+ if (!value) \
+ constraints ^= KexiDB::Field::flag; \
+ field.setConstraints( constraints ); \
+ return true; \
+ }
+#define GET_INT(method) { \
+ const uint ival = value.toUInt(&ok); \
+ if (!ok) \
+ return false; \
+ field.method( ival ); \
+ return true; \
+ }
+
+ if (propertyName.isEmpty())
+ return false;
+
+ bool ok;
+ if (KexiDB::isExtendedTableFieldProperty(propertyName)) {
+ //a little speedup: identify extended property in O(1)
+ if ( "visibleDecimalPlaces" == propertyName
+ && KexiDB::supportsVisibleDecimalPlacesProperty(field.type()) )
+ {
+ GET_INT( setVisibleDecimalPlaces );
+ }
+ else {
+ if (!field.table()) {
+ KexiDBWarn << QString("KexiDB::setFieldProperty() Cannot set \"%1\" property - no table assinged for field!")
+ .arg(propertyName) << endl;
+ }
+ else {
+ LookupFieldSchema *lookup = field.table()->lookupFieldSchema(field);
+ const bool hasLookup = lookup != 0;
+ if (!hasLookup)
+ lookup = new LookupFieldSchema();
+ if (LookupFieldSchema::setProperty( *lookup, propertyName, value )) {
+ if (!hasLookup && lookup)
+ field.table()->setLookupFieldSchema( field.name(), lookup );
+ return true;
+ }
+ delete lookup;
+ }
+ }
+ }
+ else {//non-extended
+ if ( "type" == propertyName )
+ return setIntToFieldType(field, value);
+
+ uint constraints = field.constraints();
+ if ( "primaryKey" == propertyName )
+ SET_BOOLEAN_FLAG(PrimaryKey, value.toBool());
+ if ( "indexed" == propertyName )
+ SET_BOOLEAN_FLAG(Indexed, value.toBool());
+ if ( "autoIncrement" == propertyName
+ && KexiDB::Field::isAutoIncrementAllowed(field.type()) )
+ SET_BOOLEAN_FLAG(AutoInc, value.toBool());
+ if ( "unique" == propertyName )
+ SET_BOOLEAN_FLAG(Unique, value.toBool());
+ if ( "notNull" == propertyName )
+ SET_BOOLEAN_FLAG(NotNull, value.toBool());
+ if ( "allowEmpty" == propertyName )
+ SET_BOOLEAN_FLAG(NotEmpty, !value.toBool());
+
+ uint options = 0;
+ if ( "unsigned" == propertyName ) {
+ options |= KexiDB::Field::Unsigned;
+ if (!value.toBool())
+ options ^= KexiDB::Field::Unsigned;
+ field.setOptions( options );
+ return true;
+ }
+
+ if ( "name" == propertyName ) {
+ if (value.toString().isEmpty())
+ return false;
+ field.setName( value.toString() );
+ return true;
+ }
+ if ( "caption" == propertyName ) {
+ field.setCaption( value.toString() );
+ return true;
+ }
+ if ( "description" == propertyName ) {
+ field.setDescription( value.toString() );
+ return true;
+ }
+ if ( "length" == propertyName )
+ GET_INT( setLength );
+ if ( "precision" == propertyName )
+ GET_INT( setPrecision );
+ if ( "defaultValue" == propertyName ) {
+ field.setDefaultValue( value );
+ return true;
+ }
+ if ( "width" == propertyName )
+ GET_INT( setWidth );
+
+ // last chance that never fails: custom field property
+ field.setCustomProperty(propertyName, value);
+ }
+
+ KexiDBWarn << "KexiDB::setFieldProperty() property \"" << propertyName << "\" not found!" << endl;
+ return false;
+#undef SET_BOOLEAN_FLAG
+#undef GET_INT
+}
+
+int KexiDB::loadIntPropertyValueFromDom( const QDomNode& node, bool* ok )
+{
+ QCString valueType = node.nodeName().latin1();
+ if (valueType.isEmpty() || valueType!="number") {
+ if (ok)
+ *ok = false;
+ return 0;
+ }
+ const QString text( QDomNode(node).toElement().text() );
+ int val = text.toInt(ok);
+ return val;
+}
+
+QString KexiDB::loadStringPropertyValueFromDom( const QDomNode& node, bool* ok )
+{
+ QCString valueType = node.nodeName().latin1();
+ if (valueType!="string") {
+ if (ok)
+ *ok = false;
+ return 0;
+ }
+ return QDomNode(node).toElement().text();
+}
+
+QVariant KexiDB::loadPropertyValueFromDom( const QDomNode& node )
+{
+ QCString valueType = node.nodeName().latin1();
+ if (valueType.isEmpty())
+ return QVariant();
+ const QString text( QDomNode(node).toElement().text() );
+ bool ok;
+ if (valueType == "string") {
+ return text;
+ }
+ else if (valueType == "cstring") {
+ return QCString(text.latin1());
+ }
+ else if (valueType == "number") { // integer or double
+ if (text.find('.')!=-1) {
+ double val = text.toDouble(&ok);
+ if (ok)
+ return val;
+ }
+ else {
+ const int val = text.toInt(&ok);
+ if (ok)
+ return val;
+ const Q_LLONG valLong = text.toLongLong(&ok);
+ if (ok)
+ return valLong;
+ }
+ }
+ else if (valueType == "bool") {
+ return QVariant(text.lower()=="true" || text=="1", 1);
+ }
+//! @todo add more QVariant types
+ KexiDBWarn << "loadPropertyValueFromDom(): unknown type '" << valueType << "'" << endl;
+ return QVariant();
+}
+
+QDomElement KexiDB::saveNumberElementToDom(QDomDocument& doc, QDomElement& parentEl,
+ const QString& elementName, int value)
+{
+ QDomElement el( doc.createElement(elementName) );
+ parentEl.appendChild( el );
+ QDomElement numberEl( doc.createElement("number") );
+ el.appendChild( numberEl );
+ numberEl.appendChild( doc.createTextNode( QString::number(value) ) );
+ return el;
+}
+
+QDomElement KexiDB::saveBooleanElementToDom(QDomDocument& doc, QDomElement& parentEl,
+ const QString& elementName, bool value)
+{
+ QDomElement el( doc.createElement(elementName) );
+ parentEl.appendChild( el );
+ QDomElement numberEl( doc.createElement("bool") );
+ el.appendChild( numberEl );
+ numberEl.appendChild( doc.createTextNode(
+ value ? QString::fromLatin1("true") : QString::fromLatin1("false") ) );
+ return el;
+}
+
+//! Used in KexiDB::emptyValueForType()
+static KStaticDeleter< QValueVector<QVariant> > KexiDB_emptyValueForTypeCacheDeleter;
+QValueVector<QVariant> *KexiDB_emptyValueForTypeCache = 0;
+
+QVariant KexiDB::emptyValueForType( KexiDB::Field::Type type )
+{
+ if (!KexiDB_emptyValueForTypeCache) {
+ KexiDB_emptyValueForTypeCacheDeleter.setObject( KexiDB_emptyValueForTypeCache,
+ new QValueVector<QVariant>(int(Field::LastType)+1) );
+#define ADD(t, value) (*KexiDB_emptyValueForTypeCache)[t]=value;
+ ADD(Field::Byte, 0);
+ ADD(Field::ShortInteger, 0);
+ ADD(Field::Integer, 0);
+ ADD(Field::BigInteger, 0);
+ ADD(Field::Boolean, QVariant(false, 0));
+ ADD(Field::Float, 0.0);
+ ADD(Field::Double, 0.0);
+//! @todo ok? we have no better defaults
+ ADD(Field::Text, QString(" "));
+ ADD(Field::LongText, QString(" "));
+ ADD(Field::BLOB, QByteArray());
+#undef ADD
+ }
+ const QVariant val( KexiDB_emptyValueForTypeCache->at(
+ (type<=Field::LastType) ? type : Field::InvalidType) );
+ if (!val.isNull())
+ return val;
+ else { //special cases
+ if (type==Field::Date)
+ return QDate::currentDate();
+ if (type==Field::DateTime)
+ return QDateTime::currentDateTime();
+ if (type==Field::Time)
+ return QTime::currentTime();
+ }
+ KexiDBWarn << "KexiDB::emptyValueForType() no value for type "
+ << Field::typeName(type) << endl;
+ return QVariant();
+}
+
+//! Used in KexiDB::notEmptyValueForType()
+static KStaticDeleter< QValueVector<QVariant> > KexiDB_notEmptyValueForTypeCacheDeleter;
+QValueVector<QVariant> *KexiDB_notEmptyValueForTypeCache = 0;
+
+QVariant KexiDB::notEmptyValueForType( KexiDB::Field::Type type )
+{
+ if (!KexiDB_notEmptyValueForTypeCache) {
+ KexiDB_notEmptyValueForTypeCacheDeleter.setObject( KexiDB_notEmptyValueForTypeCache,
+ new QValueVector<QVariant>(int(Field::LastType)+1) );
+#define ADD(t, value) (*KexiDB_notEmptyValueForTypeCache)[t]=value;
+ // copy most of the values
+ for (int i = int(Field::InvalidType) + 1; i<=Field::LastType; i++) {
+ if (i==Field::Date || i==Field::DateTime || i==Field::Time)
+ continue; //'current' value will be returned
+ if (i==Field::Text || i==Field::LongText) {
+ ADD(i, QVariant(QString("")));
+ continue;
+ }
+ if (i==Field::BLOB) {
+//! @todo blobs will contain other mime types too
+ QByteArray ba;
+ QBuffer buffer( ba );
+ buffer.open( IO_WriteOnly );
+ QPixmap pm(SmallIcon("filenew"));
+ pm.save( &buffer, "PNG"/*! @todo default? */ );
+ ADD(i, ba);
+ continue;
+ }
+ ADD(i, KexiDB::emptyValueForType((Field::Type)i));
+ }
+#undef ADD
+ }
+ const QVariant val( KexiDB_notEmptyValueForTypeCache->at(
+ (type<=Field::LastType) ? type : Field::InvalidType) );
+ if (!val.isNull())
+ return val;
+ else { //special cases
+ if (type==Field::Date)
+ return QDate::currentDate();
+ if (type==Field::DateTime)
+ return QDateTime::currentDateTime();
+ if (type==Field::Time)
+ return QTime::currentTime();
+ }
+ KexiDBWarn << "KexiDB::notEmptyValueForType() no value for type "
+ << Field::typeName(type) << endl;
+ return QVariant();
+}
+
+QString KexiDB::escapeBLOB(const QByteArray& array, BLOBEscapingType type)
+{
+ const int size = array.size();
+ if (size==0)
+ return QString::null;
+ int escaped_length = size*2;
+ if (type == BLOBEscape0xHex || type == BLOBEscapeOctal)
+ escaped_length += 2/*0x or X'*/;
+ else if (type == BLOBEscapeXHex)
+ escaped_length += 3; //X' + '
+ QString str;
+ str.reserve(escaped_length);
+ if (str.capacity() < (uint)escaped_length) {
+ KexiDBWarn << "KexiDB::Driver::escapeBLOB(): no enough memory (cannot allocate "<<
+ escaped_length<<" chars)" << endl;
+ return QString::null;
+ }
+ if (type == BLOBEscapeXHex)
+ str = QString::fromLatin1("X'");
+ else if (type == BLOBEscape0xHex)
+ str = QString::fromLatin1("0x");
+ else if (type == BLOBEscapeOctal)
+ str = QString::fromLatin1("'");
+
+ int new_length = str.length(); //after X' or 0x, etc.
+ if (type == BLOBEscapeOctal) {
+ // only escape nonprintable characters as in Table 8-7:
+ // http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html
+ // i.e. escape for bytes: < 32, >= 127, 39 ('), 92(\).
+ for (int i = 0; i < size; i++) {
+ const unsigned char val = array[i];
+ if (val<32 || val>=127 || val==39 || val==92) {
+ str[new_length++] = '\\';
+ str[new_length++] = '\\';
+ str[new_length++] = '0' + val/64;
+ str[new_length++] = '0' + (val % 64) / 8;
+ str[new_length++] = '0' + val % 8;
+ }
+ else {
+ str[new_length++] = val;
+ }
+ }
+ }
+ else {
+ for (int i = 0; i < size; i++) {
+ const unsigned char val = array[i];
+ str[new_length++] = (val/16) < 10 ? ('0'+(val/16)) : ('A'+(val/16)-10);
+ str[new_length++] = (val%16) < 10 ? ('0'+(val%16)) : ('A'+(val%16)-10);
+ }
+ }
+ if (type == BLOBEscapeXHex || type == BLOBEscapeOctal)
+ str[new_length++] = '\'';
+ return str;
+}
+
+QByteArray KexiDB::pgsqlByteaToByteArray(const char* data, int length)
+{
+ QByteArray array;
+ int output=0;
+ for (int pass=0; pass<2; pass++) {//2 passes to avoid allocating buffer twice:
+ // 0: count #of chars; 1: copy data
+ const char* s = data;
+ const char* end = s + length;
+ if (pass==1) {
+ KexiDBDbg << "processBinaryData(): real size == " << output << endl;
+ array.resize(output);
+ output=0;
+ }
+ for (int input=0; s < end; output++) {
+ // KexiDBDbg<<(int)s[0]<<" "<<(int)s[1]<<" "<<(int)s[2]<<" "<<(int)s[3]<<" "<<(int)s[4]<<endl;
+ if (s[0]=='\\' && (s+1)<end) {
+ //special cases as in http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html
+ if (s[1]=='\'') {// \'
+ if (pass==1)
+ array[output] = '\'';
+ s+=2;
+ }
+ else if (s[1]=='\\') { // 2 backslashes
+ if (pass==1)
+ array[output] = '\\';
+ s+=2;
+ }
+ else if ((input+3)<length) {// \\xyz where xyz are 3 octal digits
+ if (pass==1)
+ array[output] = char( (int(s[1]-'0')*8+int(s[2]-'0'))*8+int(s[3]-'0') );
+ s+=4;
+ }
+ else {
+ KexiDBDrvWarn << "processBinaryData(): no octal value after backslash" << endl;
+ s++;
+ }
+ }
+ else {
+ if (pass==1)
+ array[output] = s[0];
+ s++;
+ }
+ // KexiDBDbg<<output<<": "<<(int)array[output]<<endl;
+ }
+ }
+ return array;
+}
+
+QString KexiDB::variantToString( const QVariant& v )
+{
+ if (v.type()==QVariant::ByteArray)
+ return KexiDB::escapeBLOB(v.toByteArray(), KexiDB::BLOBEscapeHex);
+ return v.toString();
+}
+
+QVariant KexiDB::stringToVariant( const QString& s, QVariant::Type type, bool &ok )
+{
+ if (s.isNull()) {
+ ok = true;
+ return QVariant();
+ }
+ if (QVariant::Invalid==type) {
+ ok = false;
+ return QVariant();
+ }
+ if (type==QVariant::ByteArray) {//special case: hex string
+ const uint len = s.length();
+ QByteArray ba(len/2 + len%2);
+ for (uint i=0; i<(len-1); i+=2) {
+ int c = s.mid(i,2).toInt(&ok, 16);
+ if (!ok) {
+ KexiDBWarn << "KexiDB::stringToVariant(): Error in digit " << i << endl;
+ return QVariant();
+ }
+ ba[i/2] = (char)c;
+ }
+ ok = true;
+ return ba;
+ }
+ QVariant result(s);
+ if (!result.cast( type )) {
+ ok = false;
+ return QVariant();
+ }
+ ok = true;
+ return result;
+}
+
+bool KexiDB::isDefaultValueAllowed( KexiDB::Field* field )
+{
+ return field && !field->isUniqueKey();
+}
+
+void KexiDB::getLimitsForType(Field::Type type, int &minValue, int &maxValue)
+{
+ switch (type) {
+ case Field::Byte:
+//! @todo always ok?
+ minValue = 0;
+ maxValue = 255;
+ break;
+ case Field::ShortInteger:
+ minValue = -32768;
+ maxValue = 32767;
+ break;
+ case Field::Integer:
+ case Field::BigInteger: //cannot return anything larger
+ default:
+ minValue = (int)-0x07FFFFFFF;
+ maxValue = (int)(0x080000000-1);
+ }
+}
+
+void KexiDB::debugRowData(const RowData& rowData)
+{
+ KexiDBDbg << QString("ROW DATA (%1 columns):").arg(rowData.count()) << endl;
+ foreach(RowData::ConstIterator, it, rowData)
+ KexiDBDbg << "- " << (*it) << endl;
+}
+
+Field::Type KexiDB::maximumForIntegerTypes(Field::Type t1, Field::Type t2)
+{
+ if (!Field::isIntegerType(t1) || !Field::isIntegerType(t2))
+ return Field::InvalidType;
+ if (t1==t2)
+ return t2;
+ if (t1==Field::ShortInteger && t2!=Field::Integer && t2!=Field::BigInteger)
+ return t1;
+ if (t1==Field::Integer && t2!=Field::BigInteger)
+ return t1;
+ if (t1==Field::BigInteger)
+ return t1;
+ return KexiDB::maximumForIntegerTypes(t2, t1); //swap
+}
+
+QString KexiDB::simplifiedTypeName(const Field& field)
+{
+ if (field.isNumericType())
+ return i18n("Number"); //simplify
+ else if (field.type() == Field::BLOB)
+//! @todo support names of other BLOB subtypes
+ return i18n("Image"); //simplify
+
+ return field.typeGroupName();
+}
+
+#include "utils_p.moc"
diff --git a/kexi/kexidb/utils.h b/kexi/kexidb/utils.h
new file mode 100644
index 000000000..a455f5b8e
--- /dev/null
+++ b/kexi/kexidb/utils.h
@@ -0,0 +1,476 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+
+#ifndef KEXIDB_UTILS_H
+#define KEXIDB_UTILS_H
+
+#include <qvaluelist.h>
+#include <qvariant.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/driver.h>
+
+class QDomNode;
+class QDomElement;
+class QDomDocument;
+
+namespace KexiDB
+{
+ //! for convenience
+ inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, TableSchema *table,
+ const QString &keyname, const QString &keyval)
+ {
+ return table!=0 && conn.executeSQL("DELETE FROM " + table->name() + " WHERE "
+ + keyname + "=" + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) ));
+ }
+
+ inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName,
+ const QString &keyname, const QString &keyval)
+ {
+ return conn.executeSQL("DELETE FROM " + tableName + " WHERE "
+ + keyname + "=" + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) ));
+ }
+
+ inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, TableSchema *table,
+ const QString &keyname, int keyval)
+ {
+ return table!=0 && conn.executeSQL("DELETE FROM " + table->name() + " WHERE "
+ + keyname + "=" + conn.driver()->valueToSQL( Field::Integer, QVariant(keyval) ));
+ }
+
+ inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName,
+ const QString &keyname, int keyval)
+ {
+ return conn.executeSQL("DELETE FROM " + tableName + " WHERE "
+ + keyname + "=" + conn.driver()->valueToSQL( Field::Integer, QVariant(keyval) ));
+ }
+
+ /*! Delete row with two generic criterias. */
+ inline KEXI_DB_EXPORT bool deleteRow(Connection &conn, const QString &tableName,
+ const QString &keyname1, Field::Type keytype1, const QVariant& keyval1,
+ const QString &keyname2, Field::Type keytype2, const QVariant& keyval2)
+ {
+ return conn.executeSQL("DELETE FROM " + tableName + " WHERE "
+ + keyname1 + "=" + conn.driver()->valueToSQL( keytype1, keyval1 )
+ + " AND " + keyname2 + "=" + conn.driver()->valueToSQL( keytype2, keyval2 ));
+ }
+
+ inline KEXI_DB_EXPORT bool replaceRow(Connection &conn, TableSchema *table,
+ const QString &keyname, const QString &keyval, const QString &valname, QVariant val, int ftype)
+ {
+ if (!table || !KexiDB::deleteRow(conn, table, keyname, keyval))
+ return false;
+ return conn.executeSQL("INSERT INTO " + table->name()
+ + " (" + keyname + "," + valname + ") VALUES ("
+ + conn.driver()->valueToSQL( Field::Text, QVariant(keyval) ) + ","
+ + conn.driver()->valueToSQL( ftype, val) + ")");
+ }
+
+ typedef QValueList<uint> TypeGroupList;
+
+ /*! \return list of types for type group \a typeGroup. */
+ KEXI_DB_EXPORT const TypeGroupList typesForGroup(Field::TypeGroup typeGroup);
+
+ /*! \return list of i18n'd type names for type group \a typeGroup. */
+ KEXI_DB_EXPORT QStringList typeNamesForGroup(Field::TypeGroup typeGroup);
+
+ /*! \return list of (not-i18n'd) type names for type group \a typeGroup. */
+ KEXI_DB_EXPORT QStringList typeStringsForGroup(Field::TypeGroup typeGroup);
+
+ /*! \return default field type for type group \a typeGroup,
+ for example, Field::Integer for Field::IntegerGroup.
+ It is used e.g. in KexiAlterTableDialog, to properly fill
+ 'type' property when user selects type group for a field. */
+ KEXI_DB_EXPORT Field::Type defaultTypeForGroup(Field::TypeGroup typeGroup);
+
+ /*! \return a slightly simplified type name for \a field.
+ For BLOB type it returns i18n'd "Image" string or other, depending on the mime type.
+ For numbers (either floating-point or integer) it returns i18n'd "Number: string.
+ For other types it the same string as Field::typeGroupName() is returned. */
+//! @todo support names of other BLOB subtypes
+ KEXI_DB_EXPORT QString simplifiedTypeName(const Field& field);
+
+ /*! \return true if \a v represents an empty (but not null) value.
+ Values of some types (as for strings) can be both empty and not null. */
+ inline bool isEmptyValue(Field *f, const QVariant &v) {
+ if (f->hasEmptyProperty() && v.toString().isEmpty() && !v.toString().isNull())
+ return true;
+ return v.isNull();
+ }
+
+ /*! Sets \a msg to an error message retrieved from object \a obj, and \a details
+ to details of this error (server message and result number).
+ Does nothing if \a obj is null or no error occurred.
+ \a msg and \a details strings are not overwritten.
+ If \a msg is not empty, \a obj's error message is appended to \a details.
+ */
+ KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, QString& msg, QString &details);
+
+ /*! This methods works like above, but appends both a message and a description
+ to \a msg. */
+ KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, QString& msg);
+
+ /*! This methods works like above, but works on \a result's members instead. */
+ KEXI_DB_EXPORT void getHTMLErrorMesage(Object* obj, ResultInfo *result);
+
+ /*! Function useful for building WHERE parts of sql statements.
+ Constructs an sql string like "fielname = value" for specific \a drv driver,
+ field type \a t, \a fieldName and \a value. If \a value is null, "fieldname is NULL"
+ string is returned. */
+ inline KEXI_DB_EXPORT QString sqlWhere(Driver *drv, Field::Type t,
+ const QString fieldName, const QVariant value)
+ {
+ if (value.isNull())
+ return fieldName + " is NULL";
+ return fieldName + "=" + drv->valueToSQL( t, value );
+ }
+
+ /*! \return identifier for object \a objName of type \a objType
+ or 0 if such object does not exist. */
+ KEXI_DB_EXPORT int idForObjectName( Connection &conn, const QString& objName, int objType );
+
+ /*! Variant class providing a pointer to table or query. */
+ class KEXI_DB_EXPORT TableOrQuerySchema {
+ public:
+ /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema
+ using \a conn connection and \a name. If both table and query exists for \a name,
+ table has priority over query.
+ You should check whether a query or table has been found by testing
+ (query() || table()) expression. */
+ TableOrQuerySchema(Connection *conn, const QCString& name);
+
+ /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema
+ using \a conn connection and \a name. If \a table is true, \a name is assumed
+ to be a table name, otherwise \a name is assumed to be a query name.
+ You should check whether a query or table has been found by testing
+ (query() || table()) expression. */
+ TableOrQuerySchema(Connection *conn, const QCString& name, bool table);
+
+ /*! Creates a new TableOrQuerySchema variant object. \a tableOrQuery must be of
+ class TableSchema or QuerySchema.
+ You should check whether a query or table has been found by testing
+ (query() || table()) expression. */
+ TableOrQuerySchema(FieldList &tableOrQuery);
+
+ /*! Creates a new TableOrQuerySchema variant object, retrieving table or query schema
+ using \a conn connection and \a id.
+ You should check whether a query or table has been found by testing
+ (query() || table()) expression. */
+ TableOrQuerySchema(Connection *conn, int id);
+
+ /*! Creates a new TableOrQuerySchema variant object, keeping a pointer so \a table
+ object. */
+ TableOrQuerySchema(TableSchema* table);
+
+ /*! Creates a new TableOrQuerySchema variant object, keeping a pointer so \a query
+ object. */
+ TableOrQuerySchema(QuerySchema* query);
+
+ //! \return a pointer to the query if it's provided
+ QuerySchema* query() const { return m_query; }
+
+ //! \return a pointer to the table if it's provided
+ TableSchema* table() const { return m_table; }
+
+ //! \return name of a query or table
+ QCString name() const;
+
+ //! \return caption (if present) or name of the table/query
+ QString captionOrName() const;
+
+ //! \return number of fields
+ uint fieldCount() const;
+
+ //! \return all columns for the table or the query
+ const QueryColumnInfo::Vector columns(bool unique = false);
+
+ /*! \return a field of the table or the query schema for name \a name
+ or 0 if there is no such field. */
+ Field* field(const QString& name);
+
+ /*! Like Field* field(const QString& name);
+ but returns all information associated with field/column \a name. */
+ QueryColumnInfo* columnInfo(const QString& name);
+
+ /*! \return connection object, for table or query or 0 if there's no table or query defined. */
+ Connection* connection() const;
+
+ /*! \return String for debugging purposes. */
+ QString debugString();
+
+ /*! Shows debug information about table or query. */
+ void debug();
+
+ protected:
+ QCString m_name; //!< the name is kept here because m_table and m_table can be 0
+ //! and we still want name() and acptionOrName() work.
+ TableSchema* m_table;
+ QuerySchema* m_query;
+ };
+
+//! @todo perhaps use Q_ULLONG here?
+ /*! \return number of rows that can be retrieved after executing \a sql statement
+ within a connection \a conn. The statement should be of type SELECT.
+ For SQL data sources it does not fetch any records, only "COUNT(*)"
+ SQL aggregation is used at the backed.
+ -1 is returned if error occured. */
+ int rowCount(Connection &conn, const QString& sql);
+
+//! @todo perhaps use Q_ULLONG here?
+ /*! \return number of rows that can be retrieved from \a tableSchema.
+ The table must be created or retrieved using a Connection object,
+ i.e. tableSchema.connection() must not return 0.
+ For SQL data sources it does not fetch any records, only "COUNT(*)"
+ SQL aggregation is used at the backed.
+ -1 is returned if error occurred. */
+ KEXI_DB_EXPORT int rowCount(const TableSchema& tableSchema);
+
+//! @todo perhaps use Q_ULLONG here?
+ /*! Like above but operates on a query schema. */
+ KEXI_DB_EXPORT int rowCount(QuerySchema& querySchema);
+
+//! @todo perhaps use Q_ULLONG here?
+ /*! Like above but operates on a table or query schema variant. */
+ KEXI_DB_EXPORT int rowCount(TableOrQuerySchema& tableOrQuery);
+
+ /*! \return a number of columns that can be retrieved from table or query schema.
+ In case of query, expanded fields are counted. Can return -1 if \a tableOrQuery
+ has neither table or query assigned. */
+ KEXI_DB_EXPORT int fieldCount(TableOrQuerySchema& tableOrQuery);
+
+ /*! shows connection test dialog with a progress bar indicating connection testing
+ (within a second thread).
+ \a data is used to perform a (temporary) test connection. \a msgHandler is used to display errors.
+ On successful connecting, a message is displayed. After testing, temporary connection is closed. */
+ KEXI_DB_EXPORT void connectionTestDialog(QWidget* parent, const ConnectionData& data,
+ MessageHandler& msgHandler);
+
+ /*! Saves connection data \a data into \a map. */
+ KEXI_DB_EXPORT QMap<QString,QString> toMap( const ConnectionData& data );
+
+ /*! Restores connection data \a data from \a map. */
+ KEXI_DB_EXPORT void fromMap( const QMap<QString,QString>& map, ConnectionData& data );
+
+ //! Used in splitToTableAndFieldParts().
+ enum SplitToTableAndFieldPartsOptions {
+ FailIfNoTableOrFieldName = 0, //!< default value for splitToTableAndFieldParts()
+ SetFieldNameIfNoTableName = 1 //!< see splitToTableAndFieldParts()
+ };
+
+ /*! Splits \a string like "table.field" into "table" and "field" parts.
+ On success, a table name is passed to \a tableName and a field name is passed to \a fieldName.
+ The function fails if either:
+ - \a string is empty, or
+ - \a string does not contain '.' character and \a option is FailIfNoTableOrFieldName
+ (the default), or
+ - '.' character is the first of last character of \a string (in this case table name
+ or field name could become empty what is not allowed).
+
+ If \a option is SetFieldNameIfNoTableName and \a string does not contain '.',
+ \a string is passed to \a fieldName and \a tableName is set to QString::null
+ without failure.
+
+ If function fails, \a tableName and \a fieldName remain unchanged.
+ \return true on success. */
+ KEXI_DB_EXPORT bool splitToTableAndFieldParts(const QString& string,
+ QString& tableName, QString& fieldName,
+ SplitToTableAndFieldPartsOptions option = FailIfNoTableOrFieldName);
+
+ /*! \return true if \a type supports "visibleDecimalPlaces" property. */
+ KEXI_DB_EXPORT bool supportsVisibleDecimalPlacesProperty(Field::Type type);
+
+ /*! \return string constructed by converting \a value.
+ * If \a decimalPlaces is < 0, all meaningful fractional digits are returned.
+ * If \a automatically is 0, just integer part is returned.
+ * If \a automatically is > 0, fractional part should take exactly
+ N digits: if the fractional part is shorter than N, additional zeros are appended.
+ For example, "12.345" becomes "12.345000" if N=6.
+
+ No rounding is actually performed.
+ KLocale::formatNumber() and KLocale::decimalSymbol() are used to get locale settings.
+
+ @see KexiDB::Field::visibleDecimalPlaces() */
+ KEXI_DB_EXPORT QString formatNumberForVisibleDecimalPlaces(double value, int decimalPlaces);
+
+ //! \return true if \a propertyName is a builtin field property.
+ KEXI_DB_EXPORT bool isBuiltinTableFieldProperty( const QCString& propertyName );
+
+ //! \return true if \a propertyName is an extended field property.
+ KEXI_DB_EXPORT bool isExtendedTableFieldProperty( const QCString& propertyName );
+
+ /*! \return type of field for integer value \a type.
+ If \a type cannot be casted to KexiDB::Field::Type, KexiDB::Field::InvalidType is returned.
+ This can be used when type information is deserialized from a string or QVariant. */
+ KEXI_DB_EXPORT Field::Type intToFieldType( int type );
+
+ /*! Sets property values for \a field. \return true if all the values are valid and allowed.
+ On failure contents of \a field is undefined.
+ Properties coming from extended schema are also supported.
+ This function is used e.g. by AlterTableHandler when property information comes in form of text.
+ */
+ KEXI_DB_EXPORT bool setFieldProperties( Field& field, const QMap<QCString, QVariant>& values );
+
+ /*! Sets property value for \a field. \return true if the property has been found and
+ the value is valid for this property. On failure contents of \a field is undefined.
+ Properties coming from extended schema are also supported as well as
+ QVariant customProperty(const QString& propertyName) const;
+
+ This function is used e.g. by AlterTableHandler when property information comes in form of text.
+ */
+ KEXI_DB_EXPORT bool setFieldProperty(Field& field, const QCString& propertyName,
+ const QVariant& value);
+
+ /*! @return property value loaded from a DOM \a node, written in a QtDesigner-like
+ notation: &lt;number&gt;int&lt;/number&gt; or &lt;bool&gt;bool&lt;/bool&gt;, etc. Supported types are
+ "string", "cstring", "bool", "number". For invalid values null QVariant is returned.
+ You can check the validity of the returned value using QVariant::type(). */
+ KEXI_DB_EXPORT QVariant loadPropertyValueFromDom( const QDomNode& node );
+
+ /*! Convenience version of loadPropertyValueFromDom(). \return int value. */
+ KEXI_DB_EXPORT int loadIntPropertyValueFromDom( const QDomNode& node, bool* ok );
+
+ /*! Convenience version of loadPropertyValueFromDom(). \return QString value. */
+ KEXI_DB_EXPORT QString loadStringPropertyValueFromDom( const QDomNode& node, bool* ok );
+
+ /*! Saves integer element for value \a value to \a doc document within parent element
+ \a parentEl. The value will be enclosed in "number" element and "elementName" element.
+ Example: saveNumberElementToDom(doc, parentEl, "height", 15) will create
+ \code
+ <height><number>15</number></height>
+ \endcode
+ \return the reference to element created with tag elementName. */
+ KEXI_DB_EXPORT QDomElement saveNumberElementToDom(QDomDocument& doc, QDomElement& parentEl,
+ const QString& elementName, int value);
+
+ /*! Saves boolean element for value \a value to \a doc document within parent element
+ \a parentEl. Like saveNumberElementToDom() but creates "bool" tags. True/false values will be
+ saved as "true"/"false" strings.
+ \return the reference to element created with tag elementName. */
+ KEXI_DB_EXPORT QDomElement saveBooleanElementToDom(QDomDocument& doc, QDomElement& parentEl,
+ const QString& elementName, bool value);
+
+ /*! \return an empty value that can be set for a database field of type \a type having
+ "null" property set. Empty string is returned for text type, 0 for integer
+ or floating-point types, false for boolean type, empty null byte array for BLOB type.
+ For date, time and date/time types current date, time, date+time is returned, respectively.
+ Returns null QVariant for unsupported values like KexiDB::Field::InvalidType.
+ This function is efficient (uses a cache) and is heavily used by the AlterTableHandler
+ for filling new columns. */
+ KEXI_DB_EXPORT QVariant emptyValueForType( Field::Type type );
+
+ /*! \return a value that can be set for a database field of type \a type having
+ "notEmpty" property set. It works in a similar way as
+ @ref QVariant emptyValueForType( KexiDB::Field::Type type ) with the following differences:
+ - " " string (a single space) is returned for Text and LongText types
+ - a byte array with saved "filenew" PNG image (icon) for BLOB type
+ Returns null QVariant for unsupported values like KexiDB::Field::InvalidType.
+ This function is efficient (uses a cache) and is heavily used by the AlterTableHandler
+ for filling new columns. */
+ KEXI_DB_EXPORT QVariant notEmptyValueForType( Field::Type type );
+
+ //! Escaping types used in escapeBLOB().
+ enum BLOBEscapingType {
+ BLOBEscapeXHex = 1, //!< escaping like X'1FAD', used by sqlite (hex numbers)
+ BLOBEscape0xHex, //!< escaping like 0x1FAD, used by mysql (hex numbers)
+ BLOBEscapeHex, //!< escaping like 1FAD without quotes or prefixes
+ BLOBEscapeOctal //!< escaping like 'zk\\000$x', used by pgsql
+ //!< (only non-printable characters are escaped using octal numbers)
+ //!< See http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html
+ };
+
+//! @todo reverse function for BLOBEscapeOctal is available: processBinaryData() in pqxxcursor.cpp - move it here
+ /*! \return a string containing escaped, printable representation of \a array.
+ Escaping is controlled by \a type. For empty array QString::null is returned,
+ so if you want to use this function in an SQL statement, empty arrays should be
+ detected and "NULL" string should be put instead.
+ This is helper, used in Driver::escapeBLOB() and KexiDB::variantToString(). */
+ KEXI_DB_EXPORT QString escapeBLOB(const QByteArray& array, BLOBEscapingType type);
+
+ /*! \return byte array converted from \a data of length \a length.
+ \a data is escaped in format used by PostgreSQL's bytea datatype
+ described at http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html
+ This function is used by PostgreSQL KexiDB and migration drivers. */
+ KEXI_DB_EXPORT QByteArray pgsqlByteaToByteArray(const char* data, int length);
+
+ /*! \return string value serialized from a variant value \a v.
+ This functions works like QVariant::toString() except the case when \a v is of type ByteArray.
+ In this case KexiDB::escapeBLOB(v.toByteArray(), KexiDB::BLOBEscapeHex) is used.
+ This function is needed for handling values of random type, for example "defaultValue"
+ property of table fields can contain value of any type.
+ Note: the returned string is an unescaped string. */
+ KEXI_DB_EXPORT QString variantToString( const QVariant& v );
+
+ /*! \return variant value of type \a type for a string \a s that was previously serialized using
+ \ref variantToString( const QVariant& v ) function.
+ \a ok is set to the result of the operation. */
+ KEXI_DB_EXPORT QVariant stringToVariant( const QString& s, QVariant::Type type, bool &ok );
+
+ /*! \return true if setting default value for \a field field is allowed. Fields with unique
+ (and thus primary key) flags set do not accept default values.
+ False is returned aslo if \a field is 0. */
+ KEXI_DB_EXPORT bool isDefaultValueAllowed( Field* field );
+
+ /*! Gets limits for values of type \a type. The result is put into \a minValue and \a maxValue.
+ Supported types are Byte, ShortInteger, Integer and BigInteger
+ Results for BigInteger or non-integer types are the same as for Integer due
+ to limitation of int type.
+ Signed integers are assumed. */
+//! @todo add support for unsigned flag
+ KEXI_DB_EXPORT void getLimitsForType(Field::Type type, int &minValue, int &maxValue);
+
+ /*! Shows debug information about \a rowData row data. */
+ KEXI_DB_EXPORT void debugRowData(const RowData& rowData);
+
+ /*! \return type that's maximum of two integer types \a t1 and \a t2, e.g. Integer for (Byte, Integer).
+ If one of the types is not of the integer group, Field::InvalidType is returned. */
+ KEXI_DB_EXPORT Field::Type maximumForIntegerTypes(Field::Type t1, Field::Type t2);
+
+ /*! \return QVariant value converted from null-terminated \a data string.
+ In case of BLOB type, \a data is not nul lterminated, so passing length is needed. */
+ inline QVariant cstringToVariant(const char* data, KexiDB::Field* f, int length = -1)
+ {
+ if (!data)
+ return QVariant();
+ // from mo st to least frequently used types:
+
+ if (!f || f->isTextType())
+ return QString::fromUtf8(data, length);
+ if (f->isIntegerType()) {
+ if (f->type()==KexiDB::Field::BigInteger)
+ return QVariant( QString::fromLatin1(data, length).toLongLong() );
+ return QVariant( QString::fromLatin1(data, length).toInt() );
+ }
+ if (f->isFPNumericType())
+ return QString::fromLatin1(data, length).toDouble();
+ if (f->type()==KexiDB::Field::BLOB) {
+ QByteArray ba;
+ ba.duplicate(data, length);
+ return ba;
+ }
+ // the default
+//! @todo date/time?
+ QVariant result(QString::fromUtf8(data, length));
+ if (!result.cast( KexiDB::Field::variantType(f->type()) ))
+ return QVariant();
+ return result;
+ }
+}
+
+#endif
diff --git a/kexi/kexidb/utils_p.h b/kexi/kexidb/utils_p.h
new file mode 100644
index 000000000..6129196be
--- /dev/null
+++ b/kexi/kexidb/utils_p.h
@@ -0,0 +1,59 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDB_UTILS_P_H
+#define KEXIDB_UTILS_P_H
+
+#include <qtimer.h>
+#include <qwaitcondition.h>
+
+#include <kprogress.h>
+
+#include "msghandler.h"
+
+class ConnectionTestThread;
+
+class ConnectionTestDialog : protected KProgressDialog
+{
+ Q_OBJECT
+ public:
+ ConnectionTestDialog(QWidget* parent,
+ const KexiDB::ConnectionData& data, KexiDB::MessageHandler& msgHandler);
+ virtual ~ConnectionTestDialog();
+
+ int exec();
+
+ void error(KexiDB::Object *obj);
+
+ protected slots:
+ void slotTimeout();
+ virtual void slotCancel();
+
+ protected:
+ ConnectionTestThread* m_thread;
+ KexiDB::ConnectionData m_connData;
+ QTimer m_timer;
+ KexiDB::MessageHandler* m_msgHandler;
+ uint m_elapsedTime;
+ KexiDB::Object *m_errorObj;
+ QWaitCondition m_wait;
+ bool m_stopWaiting : 1;
+};
+
+#endif
diff --git a/kexi/kexiutils/Makefile.am b/kexi/kexiutils/Makefile.am
new file mode 100644
index 000000000..602e23218
--- /dev/null
+++ b/kexi/kexiutils/Makefile.am
@@ -0,0 +1,24 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexiutils.la
+libkexiutils_la_SOURCES = identifier.cpp validator.cpp utils.cpp debuggui.cpp \
+ styleproxy.cpp longlongvalidator.cpp transliteration_table.cpp
+
+libkexiutilsincludedir=$(includedir)/kexiutils
+libkexiutilsinclude_HEADERS = tristate.h identifier.h validator.h utils.h kexiutils_export.h \
+ styleproxy.h longlongvalidator.h
+
+noinst_HEADERS = utils_p.h
+
+libkexiutils_la_LDFLAGS = \
+ $(KDE_RPATH) $(all_libraries) \
+ $(VER_INFO) -Wnounresolved -no-undefined
+
+libkexiutils_la_LIBADD = $(LIB_QT) $(LIB_KDEUI) $(LIB_KIO)
+
+INCLUDES = -I$(top_srcdir)/kexi $(all_includes)
+
+METASOURCES = AUTO
+
+transliteration_table.cpp: transliteration_table.cpp.bz2
+ bunzip2 -ck $(srcdir)/transliteration_table.cpp.bz2 > ./transliteration_table.cpp
diff --git a/kexi/kexiutils/debuggui.cpp b/kexi/kexiutils/debuggui.cpp
new file mode 100644
index 000000000..72948fd26
--- /dev/null
+++ b/kexi/kexiutils/debuggui.cpp
@@ -0,0 +1,172 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "utils.h"
+#include "utils_p.h"
+
+#include <qheader.h>
+#include <qlayout.h>
+
+#include <ktabwidget.h>
+#include <klistview.h>
+#include <kiconloader.h>
+#include <kdialogbase.h>
+#include <kpushbutton.h>
+#include <kguiitem.h>
+
+#ifdef KEXI_DEBUG_GUI
+
+static DebugWindowDialog* debugWindow = 0;
+static KTabWidget* debugWindowTab = 0;
+static KListView* kexiDBDebugPage = 0;
+static KListView* kexiAlterTableActionDebugPage = 0;
+
+QWidget *KexiUtils::createDebugWindow(QWidget *parent)
+{
+ // (this is internal code - do not use i18n() here)
+ debugWindow = new DebugWindowDialog(parent);
+ debugWindow->setSizeGripEnabled( true );
+ QBoxLayout *lyr = new QVBoxLayout(debugWindow, KDialogBase::marginHint());
+ debugWindowTab = new KTabWidget(debugWindow, "debugWindowTab");
+ lyr->addWidget( debugWindowTab );
+ debugWindow->resize(900, 600);
+ debugWindow->setIcon( DesktopIcon("info") );
+ debugWindow->setCaption("Kexi Internal Debugger");
+ debugWindow->show();
+ return debugWindow;
+}
+
+void KexiUtils::addKexiDBDebug(const QString& text)
+{
+ // (this is internal code - do not use i18n() here)
+ if (!debugWindowTab)
+ return;
+ if (!kexiDBDebugPage) {
+ QWidget *page = new QWidget(debugWindowTab);
+ QVBoxLayout *vbox = new QVBoxLayout(page);
+ QHBoxLayout *hbox = new QHBoxLayout(page);
+ vbox->addLayout(hbox);
+ hbox->addStretch(1);
+ KPushButton *btn_clear = new KPushButton(KGuiItem("Clear", "clear_left"), page);
+ hbox->addWidget(btn_clear);
+
+ kexiDBDebugPage = new KListView(page, "kexiDbDebugPage");
+ QObject::connect(btn_clear, SIGNAL(clicked()), kexiDBDebugPage, SLOT(clear()));
+ vbox->addWidget(kexiDBDebugPage);
+ kexiDBDebugPage->addColumn("");
+ kexiDBDebugPage->header()->hide();
+ kexiDBDebugPage->setSorting(-1);
+ kexiDBDebugPage->setAllColumnsShowFocus ( true );
+ kexiDBDebugPage->setColumnWidthMode( 0, QListView::Maximum );
+ kexiDBDebugPage->setRootIsDecorated( true );
+ debugWindowTab->addTab( page, "KexiDB" );
+ debugWindowTab->showPage(page);
+ kexiDBDebugPage->show();
+ }
+ //add \n after (about) every 30 characters
+//TODO QString realText
+
+ KListViewItem * li = new KListViewItem( kexiDBDebugPage, kexiDBDebugPage->lastItem(), text );
+ li->setMultiLinesEnabled( true );
+}
+
+void KexiUtils::addAlterTableActionDebug(const QString& text, int nestingLevel)
+{
+ // (this is internal code - do not use i18n() here)
+ if (!debugWindowTab)
+ return;
+ if (!kexiAlterTableActionDebugPage) {
+ QWidget *page = new QWidget(debugWindowTab);
+ QVBoxLayout *vbox = new QVBoxLayout(page);
+ QHBoxLayout *hbox = new QHBoxLayout(page);
+ vbox->addLayout(hbox);
+ hbox->addStretch(1);
+ KPushButton *btn_exec = new KPushButton(KGuiItem("Real Alter Table", "filesave"), page);
+ btn_exec->setName("executeRealAlterTable");
+ hbox->addWidget(btn_exec);
+ KPushButton *btn_clear = new KPushButton(KGuiItem("Clear", "clear_left"), page);
+ hbox->addWidget(btn_clear);
+ KPushButton *btn_sim = new KPushButton(KGuiItem("Simulate Execution", "exec"), page);
+ btn_sim->setName("simulateAlterTableExecution");
+ hbox->addWidget(btn_sim);
+
+ kexiAlterTableActionDebugPage = new KListView(page, "kexiAlterTableActionDebugPage");
+ QObject::connect(btn_clear, SIGNAL(clicked()), kexiAlterTableActionDebugPage, SLOT(clear()));
+ vbox->addWidget(kexiAlterTableActionDebugPage);
+ kexiAlterTableActionDebugPage->addColumn("");
+ kexiAlterTableActionDebugPage->header()->hide();
+ kexiAlterTableActionDebugPage->setSorting(-1);
+ kexiAlterTableActionDebugPage->setAllColumnsShowFocus ( true );
+ kexiAlterTableActionDebugPage->setColumnWidthMode( 0, QListView::Maximum );
+ kexiAlterTableActionDebugPage->setRootIsDecorated( true );
+ debugWindowTab->addTab( page, "AlterTable Actions" );
+ debugWindowTab->showPage(page);
+ page->show();
+ }
+ if (text.isEmpty()) //don't move up!
+ return;
+ KListViewItem * li;
+ int availableNestingLevels = 0;
+ // compute availableNestingLevels
+ QListViewItem * lastItem = kexiAlterTableActionDebugPage->lastItem();
+ //kdDebug() << "lastItem: " << (lastItem ? lastItem->text(0) : QString::null) << endl;
+ while (lastItem) {
+ lastItem = lastItem->parent();
+ availableNestingLevels++;
+ }
+ //kdDebug() << "availableNestingLevels: " << availableNestingLevels << endl;
+ //go up (availableNestingLevels-levelsToGoUp) levels
+ lastItem = kexiAlterTableActionDebugPage->lastItem();
+ int levelsToGoUp = availableNestingLevels - nestingLevel;
+ while (levelsToGoUp > 0 && lastItem) {
+ lastItem = lastItem->parent();
+ levelsToGoUp--;
+ }
+ //kdDebug() << "lastItem2: " << (lastItem ? lastItem->text(0) : QString::null) << endl;
+ if (lastItem) {
+ QListViewItem *after = lastItem->firstChild(); //find last child so we can insert a new item after it
+ while (after && after->nextSibling())
+ after = after->nextSibling();
+ if (after)
+ li = new KListViewItem( lastItem, after, text ); //child, after
+ else
+ li = new KListViewItem( lastItem, text ); //1st child
+ }
+ else {
+ lastItem = kexiAlterTableActionDebugPage->lastItem();
+ while (lastItem && lastItem->parent())
+ lastItem = lastItem->parent();
+ //kdDebug() << "lastItem2: " << (lastItem ? lastItem->text(0) : QString::null) << endl;
+ li = new KListViewItem( kexiAlterTableActionDebugPage, lastItem, text ); //after
+ }
+ li->setOpen(true);
+ li->setMultiLinesEnabled( true );
+}
+
+void KexiUtils::connectPushButtonActionForDebugWindow(const char* actionName,
+ const QObject *receiver, const char* slot)
+{
+ if (debugWindow) {
+ KPushButton* btn = findFirstChild<KPushButton>(debugWindow, "KPushButton", actionName);
+ if (btn)
+ QObject::connect(btn, SIGNAL(clicked()), receiver, slot);
+ }
+}
+
+#endif //KEXI_DEBUG_GUI
diff --git a/kexi/kexiutils/generate_transliteration_table.sh b/kexi/kexiutils/generate_transliteration_table.sh
new file mode 100755
index 000000000..05470772b
--- /dev/null
+++ b/kexi/kexiutils/generate_transliteration_table.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+#
+# Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+#
+# Based on the original script by Michal Svec <rebel@atrey.karlin.mff.cuni.cz>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the 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; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+#
+# Generates a transliteration_table.{h|cpp} files using recode's "flat" character set
+#
+
+out_cpp="transliteration_table.cpp"
+out_h="transliteration_table.h"
+max=65534
+
+decl="const char *const transliteration_table[TRANSLITERATION_TABLE_SIZE + 1]"
+
+header=\
+"/* Transliteration table of `expr $max + 1` unicode characters
+ Do not edit this file, it is generated
+ by $0 script. */
+
+"
+echo "$header
+#define TRANSLITERATION_TABLE_SIZE `expr $max + 1`
+extern $decl;
+" > $out_h
+
+echo "$header
+#include \"$out_h\"
+$decl = {
+" > $out_cpp
+
+for i in `seq 0 $max` ; do
+ f=`printf "%04x" $i`
+ if [ "$i" -lt 16 -o "$i" -eq 92 ] ; then
+ printf "$i\n/*$f*/\n_\n" $i
+ elif [ "$i" -lt 128 ] ; then
+ ch=`printf "%03o" $i`
+ printf "$i\n/*$f*/\n\\"$ch"\n"
+ else
+ { /usr/bin/printf "${i}\n/*${f}*/\n\u${f}\n" 2>&- || echo "_"; }
+ fi
+done | \
+while read i && read f && read ch; do
+ if ! expr "$i" % 8 > /dev/null ; then
+ expr "$i" % 320 > /dev/null || echo -n ..`expr "$i" \* 100 / $max `% >&2 #progress
+ echo
+ else
+ f= # <-- comment to add /*numbers*/ everywhere
+ fi
+ r=`echo -n "$ch" | recode -f utf-8..flat | \
+ sed -r -e 's/[^[:alnum:]]//g;s/_+/_/g'`
+ if [ -z "$r" -o "$r" == "_" ] ; then
+ echo -n "${f}0/*${ch}*/,"
+ else
+ echo -n "${f}\"$r\"/*${ch}*/,"
+ fi
+done >> $out_cpp
+
+echo "0};" >> $out_cpp;
diff --git a/kexi/kexiutils/identifier.cpp b/kexi/kexiutils/identifier.cpp
new file mode 100644
index 000000000..12c5b4da2
--- /dev/null
+++ b/kexi/kexiutils/identifier.cpp
@@ -0,0 +1,131 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "identifier.h"
+#include "transliteration_table.h"
+
+using namespace KexiUtils;
+
+bool KexiUtils::isIdentifier(const QString& s)
+{
+ uint i;
+ for (i=0; i<s.length(); i++) {
+ QChar c = s.at(i).lower();
+ if (!(c=='_' || c>='a' && c<='z' || i>0 && c>='0' && c<='9'))
+ break;
+ }
+ return i>0 && i==s.length();
+}
+
+QString KexiUtils::string2FileName(const QString &s)
+{
+ QString fn( s.simplifyWhiteSpace() );
+ fn.replace(' ',"_"); fn.replace('$',"_");
+ fn.replace('\\',"-"); fn.replace('/',"-");
+ fn.replace(':',"-"); fn.replace('*',"-");
+ return fn;
+}
+
+inline QString char2Identifier(const QChar& c)
+{
+ if (c.unicode() >= TRANSLITERATION_TABLE_SIZE)
+ return QString(QChar('_'));
+ const char *const s = transliteration_table[c.unicode()];
+ return s ? QString::fromLatin1(s) : QString(QChar('_'));
+}
+
+QString KexiUtils::string2Identifier(const QString &s)
+{
+ if (s.isEmpty())
+ return QString::null;
+ QString r, id = s.simplifyWhiteSpace();
+ if (id.isEmpty())
+ return QString::null;
+ r.reserve(id.length());
+ id.replace(' ',"_");
+ QChar c = id[0];
+ QString add;
+ bool wasUnderscore = false;
+
+ if (c>='0' && c<='9') {
+ r+='_';
+ r+=c;
+ } else {
+ add = char2Identifier(c);
+ r+=add;
+ wasUnderscore = add == "_";
+ }
+
+ for (uint i=1; i<id.length(); i++) {
+ add = char2Identifier(id.at(i));
+ if (wasUnderscore && add == "_")
+ continue;
+ wasUnderscore = add == "_";
+ r+=add;
+ }
+ return r;
+}
+
+//--------------------------------------------------------------------------------
+
+QString KexiUtils::identifierExpectedMessage(const QString &valueName, const QVariant& v)
+{
+ return "<p>"+i18n("Value of \"%1\" column must be an identifier.").arg(valueName)
+ +"</p><p>"+i18n("\"%1\" is not a valid identifier.").arg(v.toString())+"</p>";
+}
+
+//--------------------------------------------------------------------------------
+
+IdentifierValidator::IdentifierValidator(QObject * parent, const char * name)
+: Validator(parent,name)
+{
+}
+
+IdentifierValidator::~IdentifierValidator()
+{
+}
+
+QValidator::State IdentifierValidator::validate( QString& input, int& pos ) const
+{
+ uint i;
+ for (i=0; i<input.length() && input.at(i)==' '; i++)
+ ;
+ pos -= i; //i chars will be removed from beginning
+ if (i<input.length() && input.at(i)>='0' && input.at(i)<='9')
+ pos++; //_ will be added at the beginning
+ bool addspace = (input.right(1)==" ");
+ input = string2Identifier(input);
+ if (addspace)
+ input += "_";
+ if((uint)pos>input.length())
+ pos=input.length();
+ return input.isEmpty() ? Valid : Acceptable;
+}
+
+Validator::Result IdentifierValidator::internalCheck(
+ const QString &valueName, const QVariant& v,
+ QString &message, QString & /*details*/)
+{
+ if (isIdentifier(v.toString()))
+ return Validator::Ok;
+ message = identifierExpectedMessage(valueName, v);
+ return Validator::Error;
+}
+
diff --git a/kexi/kexiutils/identifier.h b/kexi/kexiutils/identifier.h
new file mode 100644
index 000000000..a1d9f8ba6
--- /dev/null
+++ b/kexi/kexiutils/identifier.h
@@ -0,0 +1,61 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIUTILS_IDENTIFIER_H
+#define KEXIUTILS_IDENTIFIER_H
+
+#include "validator.h"
+#include <qstring.h>
+
+namespace KexiUtils {
+
+ /*! \return true if \a s is a valid identifier, ie. starts with a letter or '_' character
+ and contains only letters, numbers and '_' character. */
+ KEXIUTILS_EXPORT bool isIdentifier(const QString& s);
+
+ /*! \return valid identifier based on \a s.
+ Non-alphanumeric characters (or spaces) are replaced with '_'.
+ If a number is at the beginning, '_' is added at start.
+ Empty strings are not changed. Case remains unchanged. */
+ KEXIUTILS_EXPORT QString string2Identifier(const QString &s);
+
+ /*! \return useful message "Value of "valueName" column must be an identifier.
+ "v" is not a valid identifier.". It is also used by IdentifierValidator. */
+ KEXIUTILS_EXPORT QString identifierExpectedMessage(const QString &valueName,
+ const QVariant& v);
+
+ //! \return Valid filename based on \a s
+ KEXIUTILS_EXPORT QString string2FileName(const QString &s);
+
+ //! Validates input for identifier name.
+ class KEXIUTILS_EXPORT IdentifierValidator : public Validator
+ {
+ public:
+ IdentifierValidator(QObject * parent = 0, const char * name = 0);
+ virtual ~IdentifierValidator();
+ virtual State validate( QString & input, int & pos) const;
+
+ protected:
+ virtual Validator::Result internalCheck(const QString &valueName, const QVariant& v,
+ QString &message, QString &details);
+ };
+}
+
+#endif
diff --git a/kexi/kexiutils/kexiutils_export.h b/kexi/kexiutils/kexiutils_export.h
new file mode 100644
index 000000000..f3d4d2960
--- /dev/null
+++ b/kexi/kexiutils/kexiutils_export.h
@@ -0,0 +1,33 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KEXIUTILS_EXPORT_H_
+#define _KEXIUTILS_EXPORT_H_
+
+#include <kdemacros.h>
+
+#ifdef MAKE_KEXIUTILS_LIB
+# define KEXIUTILS_EXPORT KDE_EXPORT
+#elif defined(KDE_MAKE_LIB)
+# define KEXIUTILS_EXPORT KDE_IMPORT
+#else
+# define KEXIUTILS_EXPORT //for apps
+#endif
+
+#endif
diff --git a/kexi/kexiutils/longlongvalidator.cpp b/kexi/kexiutils/longlongvalidator.cpp
new file mode 100644
index 000000000..6e7c0f7e4
--- /dev/null
+++ b/kexi/kexiutils/longlongvalidator.cpp
@@ -0,0 +1,136 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ Based on KIntValidator code by Glen Parker <glenebob@nwlink.com>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "longlongvalidator.h"
+
+#include <qwidget.h>
+
+using namespace KexiUtils;
+
+LongLongValidator::LongLongValidator( QWidget * parent, int base, const char * name )
+ : QValidator(parent, name)
+ , m_min(0), m_max(0)
+{
+ setBase(base);
+}
+
+LongLongValidator::LongLongValidator( Q_LLONG bottom, Q_LLONG top, QWidget * parent, int base, const char * name )
+ : QValidator(parent, name)
+{
+ setBase(base);
+ setRange( bottom, top );
+}
+
+LongLongValidator::~LongLongValidator()
+{
+}
+
+QValidator::State LongLongValidator::validate( QString &str, int & ) const
+{
+ bool ok;
+ Q_LLONG val = 0;
+ QString newStr;
+
+ newStr = str.stripWhiteSpace();
+ if (m_base > 10)
+ newStr = newStr.upper();
+
+ if (newStr == QString::fromLatin1("-")) {// a special case
+ if ((m_min || m_max) && m_min >= 0)
+ ok = false;
+ else
+ return QValidator::Acceptable;
+ }
+ else if (!newStr.isEmpty())
+ val = newStr.toLongLong(&ok, m_base);
+ else {
+ val = 0;
+ ok = true;
+ }
+
+ if (! ok)
+ return QValidator::Invalid;
+
+ if ((! m_min && ! m_max) || (val >= m_min && val <= m_max))
+ return QValidator::Acceptable;
+
+ if (m_max && m_min >= 0 && val < 0)
+ return QValidator::Invalid;
+
+ return QValidator::Valid;
+}
+
+void LongLongValidator::fixup( QString &str ) const
+{
+ int dummy;
+ Q_LLONG val;
+ QValidator::State state;
+
+ state = validate(str, dummy);
+
+ if (state == QValidator::Invalid || state == QValidator::Acceptable)
+ return;
+
+ if (! m_min && ! m_max)
+ return;
+
+ val = str.toLongLong(0, m_base);
+
+ if (val < m_min)
+ val = m_min;
+ if (val > m_max)
+ val = m_max;
+
+ str.setNum(val, m_base);
+}
+
+void LongLongValidator::setRange( Q_LLONG bottom, Q_LLONG top )
+{
+ m_min = bottom;
+ m_max = top;
+
+ if (m_max < m_min)
+ m_max = m_min;
+}
+
+void LongLongValidator::setBase( int base )
+{
+ m_base = base;
+ if (m_base < 2)
+ m_base = 2;
+ if (m_base > 36)
+ m_base = 36;
+}
+
+Q_LLONG LongLongValidator::bottom() const
+{
+ return m_min;
+}
+
+Q_LLONG LongLongValidator::top() const
+{
+ return m_max;
+}
+
+int LongLongValidator::base() const
+{
+ return m_base;
+}
diff --git a/kexi/kexiutils/longlongvalidator.h b/kexi/kexiutils/longlongvalidator.h
new file mode 100644
index 000000000..e20d2f954
--- /dev/null
+++ b/kexi/kexiutils/longlongvalidator.h
@@ -0,0 +1,73 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_LLONGVALIDATOR_H
+#define KEXI_LLONGVALIDATOR_H
+
+#include "kexiutils_export.h"
+
+#include <qvalidator.h>
+class QWidget;
+
+namespace KexiUtils {
+
+//! @short A validator for longlong data type.
+/*!
+ This can be used by QLineEdit or subclass to provide validated
+ text entry. Can be provided with a base value (default is 10), to allow
+ the proper entry of hexadecimal, octal, or any other base numeric data.
+
+ Based on KIntValidator code by Glen Parker <glenebob@nwlink.com>
+*/
+class KEXIUTILS_EXPORT LongLongValidator : public QValidator
+{
+ public:
+ LongLongValidator( QWidget * parent, int base = 10, const char * name = 0 );
+ LongLongValidator( Q_LLONG bottom, Q_LLONG top, QWidget * parent, int base = 10, const char * name = 0 );
+ virtual ~LongLongValidator();
+
+ //! Validates the text, and returns the result. Does not modify the parameters.
+ virtual State validate( QString &, int & ) const;
+
+ //! Fixes the text if possible, providing a valid string. The parameter may be modified.
+ virtual void fixup( QString & ) const;
+
+ //! Sets the minimum and maximum values allowed.
+ virtual void setRange( Q_LLONG bottom, Q_LLONG top );
+
+ //! Sets the numeric base value.
+ virtual void setBase( int base );
+
+ //! \return the current minimum value allowed
+ virtual Q_LLONG bottom() const;
+
+ //! \return the current maximum value allowed
+ virtual Q_LLONG top() const;
+
+ //! \return the current numeric base
+ virtual int base () const;
+
+ private:
+ Q_LLONG m_base;
+ Q_LLONG m_min;
+ Q_LLONG m_max;
+};
+
+}
+#endif
diff --git a/kexi/kexiutils/styleproxy.cpp b/kexi/kexiutils/styleproxy.cpp
new file mode 100644
index 000000000..e638cac79
--- /dev/null
+++ b/kexi/kexiutils/styleproxy.cpp
@@ -0,0 +1,43 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "styleproxy.h"
+
+using namespace KexiUtils;
+
+StyleProxy::StyleProxy(QStyle* parentStyle)
+ : QStyle()
+{
+ setParentStyle(parentStyle);
+}
+
+StyleProxy::~StyleProxy()
+{
+ delete m_style;
+}
+
+void StyleProxy::setParentStyle(QStyle* style)
+{
+ m_style = QStyleFactory::create(style->name());
+}
+
+QStyle* StyleProxy::parentStyle() const
+{
+ return m_style;
+}
diff --git a/kexi/kexiutils/styleproxy.h b/kexi/kexiutils/styleproxy.h
new file mode 100644
index 000000000..667e20b12
--- /dev/null
+++ b/kexi/kexiutils/styleproxy.h
@@ -0,0 +1,175 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIUTILS_STYLEPROXY_H
+#define KEXIUTILS_STYLEPROXY_H
+
+#include <qstyle.h>
+#include <qstylefactory.h>
+#include <qpixmap.h>
+
+#include "kexiutils_export.h"
+
+namespace KexiUtils {
+
+//! @short a QStyle proxy allowing to customizing the currently used style
+/*! All you need is to reimplement one or more of the methods.
+ For example, you can reimpelmente drawPrimitive() and temporary
+ change the color in color group.
+
+ You can change even the smallest part of the style for a selected widget
+ using the following code:
+ \code
+ class MyStyle : public KexiUtils::StyleProxy {
+ //reimplement method(s) here...
+ };
+ QWidget *w = .....
+ w->setStyle( new MyStyle(&w->style(), w) ); //this will alter w's style a bit
+ \endcode
+
+ More info at http://doc.trolltech.com/qq/qq09-q-and-a.html#style
+*/
+class KEXIUTILS_EXPORT StyleProxy : public QStyle
+{
+ public:
+ /*! Creates a new style proxy object. \a parentStyle pointer will not be kept
+ (because it's most likely owned by the application: a new QStyle instance
+ for this name will be created internally. */
+ StyleProxy(QStyle* parentStyle);
+ virtual ~StyleProxy();
+
+ QStyle* parentStyle() const;
+ void setParentStyle(QStyle* style);
+
+ virtual void polish( QWidget *w ) { m_style->polish(w); }
+ virtual void unPolish( QWidget *w ) { m_style->unPolish(w); }
+
+ virtual void polish( QApplication *a ) { m_style->polish(a); }
+ virtual void unPolish( QApplication *a ) { m_style->unPolish(a); }
+
+ virtual void polish( QPalette &p ) { m_style->polish(p); };
+
+ virtual void polishPopupMenu( QPopupMenu* p ) { m_style->polishPopupMenu(p); }
+
+ virtual QRect itemRect( QPainter *p, const QRect &r,
+ int flags, bool enabled, const QPixmap *pixmap, const QString &text, int len = -1 ) const
+ {
+ return m_style->itemRect( p, r, flags, enabled, pixmap, text, len );
+ }
+
+ virtual void drawItem( QPainter *p, const QRect &r,
+ int flags, const QColorGroup &g, bool enabled, const QPixmap *pixmap, const QString &text,
+ int len = -1, const QColor *penColor = 0 ) const
+ {
+ m_style->drawItem( p, r, flags, g, enabled, pixmap, text, len, penColor );
+ }
+
+ virtual void drawPrimitive( PrimitiveElement pe,
+ QPainter *p, const QRect &r, const QColorGroup &cg, SFlags flags = Style_Default,
+ const QStyleOption& option = QStyleOption::Default ) const
+ {
+ m_style->drawPrimitive( pe, p, r, cg, flags, option );
+ }
+
+ virtual void drawControl( ControlElement element,
+ QPainter *p, const QWidget *widget, const QRect &r, const QColorGroup &cg,
+ SFlags how = Style_Default, const QStyleOption& option = QStyleOption::Default ) const
+ {
+ m_style->drawControl( element, p, widget, r, cg, how, option );
+ }
+
+ virtual void drawControlMask( ControlElement element,
+ QPainter *p, const QWidget *widget, const QRect &r,
+ const QStyleOption& option = QStyleOption::Default ) const
+ {
+ m_style->drawControlMask( element, p, widget, r, option );
+ }
+
+ virtual QRect subRect( SubRect r, const QWidget *widget ) const
+ {
+ return m_style->subRect( r, widget );
+ }
+
+ virtual void drawComplexControl( ComplexControl control,
+ QPainter *p, const QWidget *widget, const QRect &r,
+ const QColorGroup &cg, SFlags how = Style_Default,
+#ifdef Q_QDOC
+ SCFlags sub = SC_All,
+#else
+ SCFlags sub = (uint)SC_All,
+#endif
+ SCFlags subActive = SC_None, const QStyleOption& option = QStyleOption::Default ) const
+ {
+ drawComplexControl( control, p, widget, r, cg, how, sub, subActive, option );
+ }
+
+ virtual void drawComplexControlMask( ComplexControl control,
+ QPainter *p, const QWidget *widget, const QRect &r,
+ const QStyleOption& option = QStyleOption::Default ) const
+ {
+ m_style->drawComplexControlMask( control, p, widget, r, option );
+ }
+
+ virtual QRect querySubControlMetrics( ComplexControl control,
+ const QWidget *widget, SubControl sc,
+ const QStyleOption& option = QStyleOption::Default ) const
+ {
+ return m_style->querySubControlMetrics( control, widget, sc, option );
+ }
+
+ virtual SubControl querySubControl( ComplexControl control,
+ const QWidget *widget, const QPoint &pos,
+ const QStyleOption& option = QStyleOption::Default ) const
+ {
+ return m_style->querySubControl( control, widget, pos, option );
+ }
+
+ virtual int pixelMetric( PixelMetric metric,
+ const QWidget *widget = 0 ) const
+ {
+ return m_style->pixelMetric( metric, widget );
+ }
+
+ virtual QSize sizeFromContents( ContentsType contents,
+ const QWidget *widget, const QSize &contentsSize,
+ const QStyleOption& option = QStyleOption::Default ) const
+ {
+ return m_style->sizeFromContents( contents, widget, contentsSize, option );
+ }
+
+ virtual int styleHint( StyleHint stylehint,
+ const QWidget *widget = 0, const QStyleOption& option = QStyleOption::Default,
+ QStyleHintReturn* returnData = 0 ) const
+ {
+ return m_style->styleHint( stylehint, widget, option, returnData );
+ }
+
+ virtual QPixmap stylePixmap( StylePixmap stylepixmap,
+ const QWidget *widget = 0,
+ const QStyleOption& option = QStyleOption::Default ) const
+ {
+ return m_style->stylePixmap( stylepixmap, widget, option );
+ }
+
+ protected:
+ QStyle *m_style;
+};
+}
+
+#endif
diff --git a/kexi/kexiutils/transliteration_table.cpp.bz2 b/kexi/kexiutils/transliteration_table.cpp.bz2
new file mode 100644
index 000000000..56e7aa992
--- /dev/null
+++ b/kexi/kexiutils/transliteration_table.cpp.bz2
Binary files differ
diff --git a/kexi/kexiutils/transliteration_table.cpp.patch b/kexi/kexiutils/transliteration_table.cpp.patch
new file mode 100644
index 000000000..1f40936fc
--- /dev/null
+++ b/kexi/kexiutils/transliteration_table.cpp.patch
@@ -0,0 +1,87 @@
+--- transliteration_table.cpp.orig 2007-01-03 13:28:28.000000000 +0100
++++ transliteration_table.cpp 2007-01-03 13:19:45.000000000 +0100
+@@ -27,18 +27,18 @@
+ /*0088*/0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,
+ /*0090*/0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,
+ /*0098*/0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,0/*_*/,
+-/*00a0*/0/* */,0/*¡*/,0/*¢*/,0/*£*/,0/*¤*/,0/*¥*/,0/*¦*/,0/*§*/,
+-/*00a8*/0/*¨*/,0/*©*/,0/*ª*/,0/*«*/,0/*¬*/,0/*­*/,0/*®*/,0/*¯*/,
++/*00a0*/0/* */,"i"/*¡*/,"c"/*¢*/,"L"/*£*/,0/*¤*/,"Y"/*¥*/,0/*¦*/,0/*§*/,
++/*00a8*/0/*¨*/,"c"/*©*/,0/*ª*/,0/*«*/,0/*¬*/,0/*­*/,0/*®*/,0/*¯*/,
+ /*00b0*/0/*°*/,0/*±*/,0/*²*/,0/*³*/,0/*´*/,0/*µ*/,0/*¶*/,0/*·*/,
+-/*00b8*/0/*¸*/,0/*¹*/,0/*º*/,0/*»*/,0/*¼*/,0/*½*/,0/*¾*/,0/*¿*/,
+-/*00c0*/"A"/*À*/,"A"/*Á*/,"A"/*Â*/,"A"/*Ã*/,"A"/*Ä*/,0/*Å*/,0/*Æ*/,"C"/*Ç*/,
++/*00b8*/0/*¸*/,"1"/*¹*/,"o"/*º*/,0/*»*/,0/*¼*/,0/*½*/,0/*¾*/,0/*¿*/,
++/*00c0*/"A"/*À*/,"A"/*Á*/,"A"/*Â*/,"A"/*Ã*/,"Ae"/*Ä*/,0/*Å*/,0/*Æ*/,"C"/*Ç*/,
+ /*00c8*/"E"/*È*/,"E"/*É*/,"E"/*Ê*/,"E"/*Ë*/,"I"/*Ì*/,"I"/*Í*/,"I"/*Î*/,"I"/*Ï*/,
+-/*00d0*/0/*Ð*/,"N"/*Ñ*/,"O"/*Ò*/,"O"/*Ó*/,"O"/*Ô*/,"O"/*Õ*/,"O"/*Ö*/,0/*×*/,
+-/*00d8*/"O"/*Ø*/,"U"/*Ù*/,"U"/*Ú*/,"U"/*Û*/,"U"/*Ü*/,"Y"/*Ý*/,0/*Þ*/,"s"/*ß*/,
+-/*00e0*/"a"/*à*/,"a"/*á*/,"a"/*â*/,"a"/*ã*/,"a"/*ä*/,0/*å*/,0/*æ*/,"c"/*ç*/,
++/*00d0*/0/*Ð*/,"N"/*Ñ*/,"O"/*Ò*/,"O"/*Ó*/,"O"/*Ô*/,"O"/*Õ*/,"Oe"/*Ö*/,0/*×*/,
++/*00d8*/"O"/*Ø*/,"U"/*Ù*/,"U"/*Ú*/,"U"/*Û*/,"Ue"/*Ü*/,"Y"/*Ý*/,0/*Þ*/,"ss"/*ß*/,
++/*00e0*/"a"/*à*/,"a"/*á*/,"a"/*â*/,"a"/*ã*/,"ae"/*ä*/,0/*å*/,0/*æ*/,"c"/*ç*/,
+ /*00e8*/"e"/*è*/,"e"/*é*/,"e"/*ê*/,"e"/*ë*/,"i"/*ì*/,"i"/*í*/,"i"/*î*/,"i"/*ï*/,
+-/*00f0*/0/*ð*/,"n"/*ñ*/,"o"/*ò*/,"o"/*ó*/,"o"/*ô*/,"o"/*õ*/,"o"/*ö*/,0/*÷*/,
+-/*00f8*/"o"/*ø*/,"u"/*ù*/,"u"/*ú*/,"u"/*û*/,"u"/*ü*/,"y"/*ý*/,0/*þ*/,"y"/*ÿ*/,
++/*00f0*/0/*ð*/,"n"/*ñ*/,"o"/*ò*/,"o"/*ó*/,"o"/*ô*/,"o"/*õ*/,"oe"/*ö*/,0/*÷*/,
++/*00f8*/"o"/*ø*/,"u"/*ù*/,"u"/*ú*/,"u"/*û*/,"ue"/*ü*/,"y"/*ý*/,0/*þ*/,"y"/*ÿ*/,
+ /*0100*/"A"/*Ā*/,"a"/*ā*/,"A"/*Ă*/,"a"/*ă*/,"A"/*Ą*/,"a"/*ą*/,"C"/*Ć*/,"c"/*ć*/,
+ /*0108*/"C"/*Ĉ*/,"c"/*ĉ*/,"C"/*Ċ*/,"c"/*ċ*/,"C"/*Č*/,"c"/*č*/,"D"/*Ď*/,"d"/*ď*/,
+ /*0110*/"D"/*Đ*/,"d"/*đ*/,"E"/*Ē*/,"e"/*ē*/,"E"/*Ĕ*/,"e"/*ĕ*/,"E"/*Ė*/,"e"/*ė*/,
+@@ -119,34 +119,34 @@
+ /*0368*/0/*ͨ*/,0/*ͩ*/,0/*ͪ*/,0/*ͫ*/,0/*ͬ*/,0/*ͭ*/,0/*ͮ*/,0/*ͯ*/,
+ /*0370*/0/*Ͱ*/,0/*ͱ*/,0/*Ͳ*/,0/*ͳ*/,0/*ʹ*/,0/*͵*/,0/*Ͷ*/,0/*ͷ*/,
+ /*0378*/0/*͸*/,0/*͹*/,0/*ͺ*/,0/*ͻ*/,0/*ͼ*/,0/*ͽ*/,0/*;*/,0/*Ϳ*/,
+-/*0380*/0/*΀*/,0/*΁*/,0/*΂*/,0/*΃*/,0/*΄*/,0/*΅*/,0/*Ά*/,0/*·*/,
+-/*0388*/0/*Έ*/,0/*Ή*/,0/*Ί*/,0/*΋*/,0/*Ό*/,0/*΍*/,0/*Ύ*/,0/*Ώ*/,
+-/*0390*/0/*ΐ*/,0/*Α*/,0/*Β*/,0/*Γ*/,0/*Δ*/,0/*Ε*/,0/*Ζ*/,0/*Η*/,
+-/*0398*/0/*Θ*/,0/*Ι*/,0/*Κ*/,0/*Λ*/,0/*Μ*/,0/*Ν*/,0/*Ξ*/,0/*Ο*/,
+-/*03a0*/0/*Π*/,0/*Ρ*/,0/*΢*/,0/*Σ*/,0/*Τ*/,0/*Υ*/,0/*Φ*/,0/*Χ*/,
+-/*03a8*/0/*Ψ*/,0/*Ω*/,0/*Ϊ*/,0/*Ϋ*/,0/*ά*/,0/*έ*/,0/*ή*/,0/*ί*/,
+-/*03b0*/0/*ΰ*/,0/*α*/,0/*β*/,0/*γ*/,0/*δ*/,0/*ε*/,0/*ζ*/,0/*η*/,
+-/*03b8*/0/*θ*/,0/*ι*/,0/*κ*/,0/*λ*/,0/*μ*/,0/*ν*/,0/*ξ*/,0/*ο*/,
+-/*03c0*/0/*π*/,0/*ρ*/,0/*ς*/,0/*σ*/,0/*τ*/,0/*υ*/,0/*φ*/,0/*χ*/,
+-/*03c8*/0/*ψ*/,0/*ω*/,0/*ϊ*/,0/*ϋ*/,0/*ό*/,0/*ύ*/,0/*ώ*/,0/*Ϗ*/,
++/*0380*/0/*΀*/,0/*΁*/,0/*΂*/,0/*΃*/,0/*΄*/,0/*΅*/,"A"/*Ά*/,0/*·*/,
++/*0388*/"E"/*Έ*/,"H"/*Ή*/,"I"/*Ί*/,0/*΋*/,"O"/*Ό*/,0/*΍*/,"G"/*Ύ*/,"W"/*Ώ*/,
++/*0390*/0/*ΐ*/,"A"/*Α*/,"B"/*Β*/,"G"/*Γ*/,"D"/*Δ*/,"E"/*Ε*/,"Z"/*Ζ*/,"H"/*Η*/,
++/*0398*/"Q"/*Θ*/,"I"/*Ι*/,"K"/*Κ*/,"L"/*Λ*/,"M"/*Μ*/,"N"/*Ν*/,"C"/*Ξ*/,"O"/*Ο*/,
++/*03a0*/"P"/*Π*/,"R"/*Ρ*/,"S"/*΢*/,"S"/*Σ*/,"T"/*Τ*/,"G"/*Υ*/,"F"/*Φ*/,"X"/*Χ*/,
++/*03a8*/"Y"/*Ψ*/,"W"/*Ω*/,"I"/*Ϊ*/,"U"/*Ϋ*/,"a"/*ά*/,"e"/*έ*/,"h"/*ή*/,0/*ί*/,
++/*03b0*/"u"/*ΰ*/,"a"/*α*/,"b"/*β*/,"g"/*γ*/,"d"/*δ*/,"e"/*ε*/,"z"/*ζ*/,"h"/*η*/,
++/*03b8*/"q"/*θ*/,"i"/*ι*/,"k"/*κ*/,"l"/*λ*/,"m"/*μ*/,"n"/*ν*/,"c"/*ξ*/,"o"/*ο*/,
++/*03c0*/"p"/*π*/,"r"/*ρ*/,"s"/*ς*/,"s"/*σ*/,"t"/*τ*/,"u"/*υ*/,"f"/*φ*/,"x"/*χ*/,
++/*03c8*/"y"/*ψ*/,"w"/*ω*/,"i"/*ϊ*/,"u"/*ϋ*/,"o"/*ό*/,"u"/*ύ*/,"w"/*ώ*/,0/*Ϗ*/,
+ /*03d0*/0/*ϐ*/,0/*ϑ*/,0/*ϒ*/,0/*ϓ*/,0/*ϔ*/,0/*ϕ*/,0/*ϖ*/,0/*ϗ*/,
+ /*03d8*/0/*Ϙ*/,0/*ϙ*/,0/*Ϛ*/,0/*ϛ*/,0/*Ϝ*/,0/*ϝ*/,0/*Ϟ*/,0/*ϟ*/,
+ /*03e0*/0/*Ϡ*/,0/*ϡ*/,0/*Ϣ*/,0/*ϣ*/,0/*Ϥ*/,0/*ϥ*/,0/*Ϧ*/,0/*ϧ*/,
+ /*03e8*/0/*Ϩ*/,0/*ϩ*/,0/*Ϫ*/,0/*ϫ*/,0/*Ϭ*/,0/*ϭ*/,0/*Ϯ*/,0/*ϯ*/,
+ /*03f0*/0/*ϰ*/,0/*ϱ*/,0/*ϲ*/,0/*ϳ*/,0/*ϴ*/,0/*ϵ*/,0/*϶*/,0/*Ϸ*/,
+ /*03f8*/0/*ϸ*/,0/*Ϲ*/,0/*Ϻ*/,0/*ϻ*/,0/*ϼ*/,0/*Ͻ*/,0/*Ͼ*/,0/*Ͽ*/,
+-/*0400*/0/*Ѐ*/,0/*Ё*/,0/*Ђ*/,0/*Ѓ*/,0/*Є*/,0/*Ѕ*/,0/*І*/,0/*Ї*/,
+-/*0408*/0/*Ј*/,0/*Љ*/,0/*Њ*/,0/*Ћ*/,0/*Ќ*/,0/*Ѝ*/,0/*Ў*/,0/*Џ*/,
+-/*0410*/0/*А*/,0/*Б*/,0/*В*/,0/*Г*/,0/*Д*/,0/*Е*/,0/*Ж*/,0/*З*/,
+-/*0418*/0/*И*/,0/*Й*/,0/*К*/,0/*Л*/,0/*М*/,0/*Н*/,0/*О*/,0/*П*/,
+-/*0420*/0/*Р*/,0/*С*/,0/*Т*/,0/*У*/,0/*Ф*/,0/*Х*/,0/*Ц*/,0/*Ч*/,
+-/*0428*/0/*Ш*/,0/*Щ*/,0/*Ъ*/,0/*Ы*/,0/*Ь*/,0/*Э*/,0/*Ю*/,0/*Я*/,
+-/*0430*/0/*а*/,0/*б*/,0/*в*/,0/*г*/,0/*д*/,0/*е*/,0/*ж*/,0/*з*/,
+-/*0438*/0/*и*/,0/*й*/,0/*к*/,0/*л*/,0/*м*/,0/*н*/,0/*о*/,0/*п*/,
+-/*0440*/0/*р*/,0/*с*/,0/*т*/,0/*у*/,0/*ф*/,0/*х*/,0/*ц*/,0/*ч*/,
+-/*0448*/0/*ш*/,0/*щ*/,0/*ъ*/,0/*ы*/,0/*ь*/,0/*э*/,0/*ю*/,0/*я*/,
+-/*0450*/0/*ѐ*/,0/*ё*/,0/*ђ*/,0/*ѓ*/,0/*є*/,0/*ѕ*/,0/*і*/,0/*ї*/,
+-/*0458*/0/*ј*/,0/*љ*/,0/*њ*/,0/*ћ*/,0/*ќ*/,0/*ѝ*/,0/*ў*/,0/*џ*/,
++/*0400*/"E"/*Ѐ*/,"E"/*Ё*/,"Dj"/*Ђ*/,"G"/*Ѓ*/,"E"/*Є*/,"Dz"/*Ѕ*/,"I"/*І*/,"I"/*Ї*/,
++/*0408*/"J"/*Ј*/,"Lj"/*Љ*/,"Nj"/*Њ*/,"C"/*Ћ*/,"K"/*Ќ*/,"J"/*Ѝ*/,"U"/*Ў*/,"Dz"/*Џ*/,
++/*0410*/"A"/*А*/,"B"/*Б*/,"V"/*В*/,"G"/*Г*/,"D"/*Д*/,"E"/*Е*/,"Z"/*Ж*/,"Z"/*З*/,
++/*0418*/"I"/*И*/,"J"/*Й*/,"K"/*К*/,"L"/*Л*/,"M"/*М*/,"N"/*Н*/,"O"/*О*/,"P"/*П*/,
++/*0420*/"R"/*Р*/,"S"/*С*/,"T"/*Т*/,"U"/*У*/,"F"/*Ф*/,"H"/*Х*/,"C"/*Ц*/,"C"/*Ч*/,
++/*0428*/"S"/*Ш*/,"S"/*Щ*/,""/*Ъ*/,"Y"/*Ы*/,""/*Ь*/,"E"/*Э*/,"Ju"/*Ю*/,"Ja"/*Я*/,
++/*0430*/"a"/*а*/,"b"/*б*/,"v"/*в*/,"g"/*г*/,"d"/*д*/,"e"/*е*/,"z"/*ж*/,"z"/*з*/,
++/*0438*/"i"/*и*/,"j"/*й*/,"k"/*к*/,"l"/*л*/,"m"/*м*/,"n"/*н*/,"o"/*о*/,"p"/*п*/,
++/*0440*/"r"/*р*/,"s"/*с*/,"t"/*т*/,"u"/*у*/,"f"/*ф*/,"h"/*х*/,"c"/*ц*/,"c"/*ч*/,
++/*0448*/"c"/*ш*/,"c"/*щ*/,""/*ъ*/,"y"/*ы*/,""/*ь*/,"e"/*э*/,"ju"/*ю*/,"ja"/*я*/,
++/*0450*/"e"/*ѐ*/,"e"/*ё*/,"dj"/*ђ*/,"g"/*ѓ*/,"e"/*є*/,"dz"/*ѕ*/,"i"/*і*/,"i"/*ї*/,
++/*0458*/"j"/*ј*/,"lj"/*љ*/,"nj"/*њ*/,"c"/*ћ*/,"k"/*ќ*/,"j"/*ѝ*/,"u"/*ў*/,"dz"/*џ*/,
+ /*0460*/0/*Ѡ*/,0/*ѡ*/,0/*Ѣ*/,0/*ѣ*/,0/*Ѥ*/,0/*ѥ*/,0/*Ѧ*/,0/*ѧ*/,
+ /*0468*/0/*Ѩ*/,0/*ѩ*/,0/*Ѫ*/,0/*ѫ*/,0/*Ѭ*/,0/*ѭ*/,0/*Ѯ*/,0/*ѯ*/,
+ /*0470*/0/*Ѱ*/,0/*ѱ*/,0/*Ѳ*/,0/*ѳ*/,0/*Ѵ*/,0/*ѵ*/,0/*Ѷ*/,0/*ѷ*/,
diff --git a/kexi/kexiutils/transliteration_table.h b/kexi/kexiutils/transliteration_table.h
new file mode 100644
index 000000000..5be7dadc9
--- /dev/null
+++ b/kexi/kexiutils/transliteration_table.h
@@ -0,0 +1,8 @@
+/* Transliteration table of 65535 unicode characters
+ Do not edit this file, it is generated
+ by ./generate_transliteration_table.sh script. */
+
+
+#define TRANSLITERATION_TABLE_SIZE 65535
+extern const char *const transliteration_table[TRANSLITERATION_TABLE_SIZE + 1];
+
diff --git a/kexi/kexiutils/transliteration_table.readme b/kexi/kexiutils/transliteration_table.readme
new file mode 100644
index 000000000..ff00d8abc
--- /dev/null
+++ b/kexi/kexiutils/transliteration_table.readme
@@ -0,0 +1,54 @@
+Transliteration Table README
+----------------------------
+
+1. Rationale: Identifiers within the database or programming languages
+only accept latin-1 characters, numbers and '_' character.
+
+Application developers can enter captions (titles) to give
+objects or variables a meaningful name using full unicode set.
+
+Transliteration is used to convert unicode captions to identifiers
+without loosing meaning of the names.
+
+More info:
+ http://en.wikipedia.org/wiki/Transliteration
+ http://en.wikipedia.org/wiki/Romanization
+
+2. We use special kind of romanization as we only allow characters
+described in 1.
+
+3. Implementation: transliteration table, was generated by
+generate_transliteration_table.sh shell script is used
+to transliterate any unicode character (having code < 65535)
+to an identifier, what gives constant time for converting
+single character.
+
+The resulting generated code is kept in transliteration_table.{h|cpp} files,
+included by identifier.cpp for use in public utility functions.
+
+For each item, the table (basically a table of c-strings) contains:
+- a NULL string it the resulting conversion have to be "_" string;
+- a c-string of size 1 or more containing a valid transliteration
+ as described in 1;
+- an empty string "" if the transliteration should return empty string
+ (can be useful e.g. for soft signs in Cyrillic)
+
+4. Fixes: Because iconv/recode tools are not fully implemented in regards
+to transliteration to latin-1 (e.g. no good support
+for Greek and Cyrillic/Serbian characters),
+the transliteration_table.cpp file is patched with
+transliteration_table.cpp.patch which provides fixes written by hand.
+
+If you find invalid or missing transliterations:
+ a) edit transliteration_table.cpp (using UTF-8-compliant text editor!)
+ - if transliteration_table.cpp file does not exist,
+ extract it from transliteration_table.bz2 archive
+ b) run update_transliteration_table_patch.sh shell script,
+ what will update the transliteration_table.cpp.patch file
+ c) send the transliteration_table.cpp.patch file to the Kexi team
+
+5. Credits
+ Jaroslaw Staniek <js at iidea.pl>
+ Michael Drueing <michael at drueing.de>
+ Chusslove Illich <caslav.ilic at gmx.net>
+ Michal Svec <rebel at atrey.karlin.mff.cuni.cz>
diff --git a/kexi/kexiutils/tristate.h b/kexi/kexiutils/tristate.h
new file mode 100644
index 000000000..a3de22bb4
--- /dev/null
+++ b/kexi/kexiutils/tristate.h
@@ -0,0 +1,238 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _TRISTATE_TYPE_H_
+#define _TRISTATE_TYPE_H_
+
+#include <qstring.h>
+
+/**
+ * \e cancelled value, in most cases usable if there is a need for returning
+ * \e cancelled value explicitly. Example use:
+ * \code
+ * tristate myFunctionThatCanBeCancelled() {
+ * doSomething();
+ * if (userCancelledOperation())
+ * return cancelled; //neither success or failure is returned here
+ * return operationSucceeded(); //return success or failure
+ * }
+ * \endcode
+ * Even though ~ operator of tristate class can be used, it is also possible to test:
+ * \code
+ * if (cancelled == myFunctionThatCanBeCancelled()) { .... }
+ * \endcode
+ */
+static const char cancelled = 2;
+
+/**
+ * Convenience name, the same as cancelled value.
+ */
+static const char dontKnow = cancelled;
+
+/**
+ * 3-state logical type with three values: \e true, \e false and \e cancelled and convenient operators.
+ *
+ * \e cancelled state can be also called \e dontKnow, it behaves as \e null in SQL.
+ * A main goal of this class is to improve readibility when there's a need
+ * for storing third, \e cancelled, state, especially in case C++ exceptions are not in use.
+ * With it, developer can forget about declaring a specific enum type
+ * having just three values: \e true, \e false, \e cancelled.
+ *
+ * Objects of this class can be used with similar convenience as standard bool type:
+ * - use as return value when 'cancelled'
+ * \code
+ * tristate doSomething();
+ * \endcode
+ * - convert from bool (1) or to bool (2)
+ * \code
+ * tristate t = true; //(1)
+ * setVisible(t); //(2)
+ * \endcode
+ * - clear comparisons
+ * \code
+ * tristate t = doSomething();
+ * if (t) doSomethingIfTrue();
+ * if (!t) doSomethingIfFalse();
+ * if (~t) doSomethingIfCancelled();
+ * \endcode
+ *
+ * "! ~" can be used as "not cancelled".
+ *
+ * With tristate class, developer can also forget about
+ * it's additional meaning and treat it just as a bool, if the third state
+ * is irrelevant to the current situation.
+ *
+ * Other use for tristate class could be to allow cancellation within
+ * a callback function or a Qt slot. Example:
+ * \code
+ * public slots:
+ * void validateData(tristate& result);
+ * \endcode
+ * Having the single parameter, signals and slots have still simple look.
+ * Developers can alter their code (by replacing 'bool& result' with 'tristate& result')
+ * in case when a possibility of canceling of, say, data provessing needs to be implemented.
+ * Let's say \e validateData() function uses a QDialog to get some validation from a user.
+ * While QDialog::Rejected is returned after cancellation of the validation process,
+ * the information about cancellation needs to be transferred up to a higher level of the program.
+ * Storing values of type QDialog::DialogCode there could be found as unreadable, and
+ * casting these to int is not typesafe. With tristate class it's easier to make it obvious that
+ * cancellation should be taken into account.
+ *
+ * @author Jaroslaw Staniek
+ */
+class tristate
+{
+public:
+ /**
+ * Default constructor, object has \e cancelled value set.
+ */
+ tristate()
+ : m_value(Cancelled)
+ {
+ }
+
+ /**
+ * Constructor accepting a boolean value.
+ */
+ tristate(bool boolValue)
+ : m_value(boolValue ? True : False)
+ {
+ }
+
+ /**
+ * Constructor accepting a char value.
+ * It is converted in the following way:
+ * - 2 -> cancelled
+ * - 1 -> true
+ * - other -> false
+ */
+ tristate(char c)
+ : m_value(c==cancelled ? tristate::Cancelled : (c==1 ? True : False))
+ {
+ }
+
+ /** Constructor accepting an integer value.
+ * It is converted in the following way:
+ * - 2 -> cancelled
+ * - 1 -> true
+ * - other -> false
+ */
+ tristate(int intValue)
+ : m_value(intValue==(int)cancelled ? tristate::Cancelled : (intValue==1 ? True : False))
+ {
+ }
+
+ /**
+ * Casting to bool type with negation: true is only returned
+ * if the original tristate value is equal to false.
+ */
+ bool operator!() const { return m_value==False; }
+
+ /**
+ * Special casting to bool type: true is only returned
+ * if the original tristate value is equal to \e cancelled.
+ */
+ bool operator~() const { return m_value==Cancelled; }
+
+ tristate& operator=(const tristate& tsValue) { m_value = tsValue.m_value; return *this; }
+
+ friend inline bool operator==(bool boolValue, tristate tsValue);
+
+ friend inline bool operator==(tristate tsValue, bool boolValue);
+
+ friend inline bool operator!=(bool boolValue, tristate tsValue);
+
+ friend inline bool operator!=(tristate tsValue, bool boolValue);
+
+ /**
+ * \return text representation of the value: "true", "false" or "cancelled".
+ */
+ QString toString() const {
+ if (m_value==False)
+ return QString::fromLatin1("false");
+ return m_value==True ? QString::fromLatin1("true") : QString::fromLatin1("cancelled");
+ }
+
+private:
+ /**
+ * @internal
+ * States used internally.
+ */
+ enum Value {
+ False = 0,
+ True = 1,
+ Cancelled = 2
+ };
+
+ /**
+ * @internal
+ */
+ Value m_value;
+};
+
+/**
+ * Inequality operator comparing a bool value @p boolValue and a tristate value @p tsValue.
+ *
+ * @return false if both @p boolValue and @p tsValue are true
+ * or if both @p boolValue and @p tsValue are false.
+ * Else, returns true.
+*/
+inline bool operator!=(bool boolValue, tristate tsValue)
+{
+ return !( (tsValue.m_value==tristate::True && boolValue)
+ || (tsValue.m_value==tristate::False && !boolValue) );
+}
+
+/**
+ * Inequality operator comparing a tristate value @p tsValue and a bool value @p boolValue.
+ * @see bool operator!=(bool boolValue, tristate tsValue)
+*/
+inline bool operator!=(tristate tsValue, bool boolValue)
+{
+ return !( (tsValue.m_value==tristate::True && boolValue)
+ || (tsValue.m_value==tristate::False && !boolValue) );
+}
+
+/**
+ * Equality operator comparing a tristate value @p tsValue and a bool value @p boolValue.
+ * \return true if both
+ * - both @p tsValue value and @p boolValue are true, or
+ * - both @p tsValue value and @p boolValue are false
+ * If the tristate value has value of cancelled, false is returned.
+ */
+inline bool operator==(tristate tsValue, bool boolValue)
+{
+ return (tsValue.m_value==tristate::True && boolValue)
+ || (tsValue.m_value==tristate::False && !boolValue);
+}
+
+/**
+ * Equality operator comparing a bool value @p boolValue and a tristate value @p tsValue.
+ * \return true if both
+ * - both @p tsValue value and @p boolValue are true, or
+ * - both @p tsValue value and @p boolValue are false
+ * If the tristate value has value of cancelled, false is returned.
+ */
+inline bool operator==(bool boolValue, tristate tsValue)
+{
+ return (tsValue.m_value==tristate::True && boolValue)
+ || (tsValue.m_value==tristate::False && !boolValue);
+}
+
+#endif
diff --git a/kexi/kexiutils/update_transliteration_table_patch.sh b/kexi/kexiutils/update_transliteration_table_patch.sh
new file mode 100755
index 000000000..e295d95f8
--- /dev/null
+++ b/kexi/kexiutils/update_transliteration_table_patch.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 Jaroslaw Staniek <js@iidea.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 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; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+#
+# Updates transliteration_table.h.patch file using the original
+# transliteration_table.h.bz2 file.
+#
+
+# 1. Create an original transliteration file by reversing changes
+
+bzcat transliteration_table.cpp.bz2 > transliteration_table.cpp.orig || exit 1
+patch -p0 -R transliteration_table.cpp.orig < transliteration_table.cpp.patch || exit 1
+
+# 2. Update transliteration_table.cpp.patch file
+
+diff -u transliteration_table.cpp.orig transliteration_table.cpp \
+ > transliteration_table.cpp.patch || exit 1
+
diff --git a/kexi/kexiutils/utils.cpp b/kexi/kexiutils/utils.cpp
new file mode 100644
index 000000000..f96465a6d
--- /dev/null
+++ b/kexi/kexiutils/utils.cpp
@@ -0,0 +1,434 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "utils.h"
+#include "utils_p.h"
+
+#include <qregexp.h>
+#include <qpainter.h>
+#include <qimage.h>
+#include <qwmatrix.h>
+#include <qiconset.h>
+#include <qbitmap.h>
+#include <qfile.h>
+
+#include <kdebug.h>
+#include <kcursor.h>
+#include <kapplication.h>
+#include <kpixmap.h>
+#include <kiconeffect.h>
+#include <kpixmapeffect.h>
+#include <kiconloader.h>
+
+#if defined(Q_WS_WIN)
+# include <win32_utils.h>
+#endif
+
+using namespace KexiUtils;
+
+DelayedCursorHandler::DelayedCursorHandler()
+ : startedOrActive(false)
+{
+ connect(&timer, SIGNAL(timeout()), this, SLOT(show()));
+}
+void DelayedCursorHandler::start(bool noDelay) {
+ startedOrActive = true;
+ timer.start(noDelay ? 0 : 1000, true);
+}
+void DelayedCursorHandler::stop() {
+ startedOrActive = false;
+ timer.stop();
+ QApplication::restoreOverrideCursor();
+}
+void DelayedCursorHandler::show() {
+ QApplication::setOverrideCursor( KCursor::waitCursor() );
+}
+
+DelayedCursorHandler _delayedCursorHandler;
+
+void KexiUtils::setWaitCursor(bool noDelay) {
+ if (kapp->guiEnabled())
+ _delayedCursorHandler.start(noDelay);
+}
+void KexiUtils::removeWaitCursor() {
+ if (kapp->guiEnabled())
+ _delayedCursorHandler.stop();
+}
+
+WaitCursor::WaitCursor(bool noDelay)
+{
+ setWaitCursor(noDelay);
+}
+
+WaitCursor::~WaitCursor()
+{
+ removeWaitCursor();
+}
+
+WaitCursorRemover::WaitCursorRemover()
+{
+ m_reactivateCursor = _delayedCursorHandler.startedOrActive;
+ _delayedCursorHandler.stop();
+}
+
+WaitCursorRemover::~WaitCursorRemover()
+{
+ _delayedCursorHandler.start(true);
+}
+
+//--------------------------------------------------------------------------------
+
+QString KexiUtils::fileDialogFilterString(const KMimeType::Ptr& mime, bool kdeFormat)
+{
+ if (mime==0)
+ return QString::null;
+
+ QString str;
+ if (kdeFormat) {
+ if (mime->patterns().isEmpty())
+ str = "*";
+ else
+ str = mime->patterns().join(" ");
+ str += "|";
+ }
+ str += mime->comment();
+ if (!mime->patterns().isEmpty() || !kdeFormat) {
+ str += " (";
+ if (mime->patterns().isEmpty())
+ str += "*";
+ else
+ str += mime->patterns().join("; ");
+ str += ")";
+ }
+ if (kdeFormat)
+ str += "\n";
+ else
+ str += ";;";
+ return str;
+}
+
+QString KexiUtils::fileDialogFilterString(const QString& mimeString, bool kdeFormat)
+{
+ KMimeType::Ptr ptr = KMimeType::mimeType(mimeString);
+ return fileDialogFilterString( ptr, kdeFormat );
+}
+
+QString KexiUtils::fileDialogFilterStrings(const QStringList& mimeStrings, bool kdeFormat)
+{
+ QString ret;
+ QStringList::ConstIterator endIt = mimeStrings.constEnd();
+ for(QStringList::ConstIterator it = mimeStrings.constBegin(); it != endIt; ++it)
+ ret += fileDialogFilterString(*it, kdeFormat);
+ return ret;
+}
+
+QColor KexiUtils::blendedColors(const QColor& c1, const QColor& c2, int factor1, int factor2)
+{
+ return QColor(
+ int( (c1.red()*factor1+c2.red()*factor2)/(factor1+factor2) ),
+ int( (c1.green()*factor1+c2.green()*factor2)/(factor1+factor2) ),
+ int( (c1.blue()*factor1+c2.blue()*factor2)/(factor1+factor2) ) );
+}
+
+QColor KexiUtils::contrastColor(const QColor& c)
+{
+ int g = qGray( c.rgb() );
+ if (g>110)
+ return c.dark(200);
+ else if (g>80)
+ return c.light(150);
+ else if (g>20)
+ return c.light(300);
+ return Qt::gray;
+}
+
+QColor KexiUtils::bleachedColor(const QColor& c, int factor)
+{
+ int h, s, v;
+ c.getHsv( &h, &s, &v );
+ QColor c2;
+ if (factor < 100)
+ factor = 100;
+ if (s>=250 && v>=250) //for colors like cyan or red, make the result more white
+ s = QMAX(0, s - factor - 50);
+ else if (s<=5 && s<=5)
+ v += factor-50;
+ c2.setHsv(h, s, QMIN(255,v + factor-100));
+ return c2;
+}
+
+QIconSet KexiUtils::colorizeIconToTextColor(const QPixmap& icon, const QPalette& palette)
+{
+ QPixmap pm(
+ KIconEffect().apply( icon, KIconEffect::Colorize, 1.0f, palette.active().buttonText(), false ) );
+
+ KPixmap kpm(pm);
+ return QIconSet(
+ KPixmapEffect::fade( kpm, 0.33, palette.active().button() ) );
+}
+
+QPixmap KexiUtils::emptyIcon(KIcon::Group iconGroup)
+{
+ QPixmap noIcon( IconSize( iconGroup ), IconSize( iconGroup ) );
+ QBitmap bmpNoIcon(noIcon.size());
+ bmpNoIcon.fill(Qt::color0);
+ noIcon.setMask(bmpNoIcon);
+ return noIcon;
+}
+
+void KexiUtils::serializeMap(const QMap<QString,QString>& map, const QByteArray& array)
+{
+ QDataStream ds(array, IO_WriteOnly);
+ ds << map;
+}
+
+void KexiUtils::serializeMap(const QMap<QString,QString>& map, QString& string)
+{
+ QByteArray array;
+ QDataStream ds(array, IO_WriteOnly);
+ ds << map;
+ kdDebug() << array[3] << " " << array[4] << " " << array[5] << endl;
+ const uint size = array.size();
+ string = QString::null;
+ string.reserve(size);
+ for (uint i=0; i<size; i++) {
+ string[i]=QChar(ushort(array[i]+1));
+ }
+}
+
+QMap<QString,QString> KexiUtils::deserializeMap(const QByteArray& array)
+{
+ QMap<QString,QString> map;
+ QDataStream ds(array, IO_ReadOnly);
+ ds >> map;
+ return map;
+}
+
+QMap<QString,QString> KexiUtils::deserializeMap(const QString& string)
+{
+ const uint size = string.length();
+ QCString cstr(string.latin1());
+ QByteArray array( size );
+ for (uint i=0; i<size; i++) {
+ array[i] = char(string[i].unicode()-1);
+ }
+ QMap<QString,QString> map;
+ QDataStream ds(array, IO_ReadOnly);
+ ds >> map;
+ return map;
+}
+
+QString KexiUtils::stringToFileName(const QString& string)
+{
+ QString _string(string);
+ _string.replace(QRegExp("[\\\\/:\\*?\"<>|]"), " ");
+ return _string.simplifyWhiteSpace();
+}
+
+void KexiUtils::simpleCrypt(QString& string)
+{
+ for (uint i=0; i<string.length(); i++)
+ string[i] = QChar( string[i].unicode() + 47 + i );
+}
+
+void KexiUtils::simpleDecrypt(QString& string)
+{
+ for (uint i=0; i<string.length(); i++)
+ string[i] = QChar( string[i].unicode() - 47 - i );
+}
+
+void KexiUtils::drawPixmap( QPainter& p, int lineWidth, const QRect& rect,
+ const QPixmap& pixmap, int alignment, bool scaledContents, bool keepAspectRatio)
+{
+ if (pixmap.isNull())
+ return;
+
+ const bool fast = pixmap.width()>1000 && pixmap.height()>800; //fast drawing needed
+ const int w = rect.width()-lineWidth-lineWidth;
+ const int h = rect.height()-lineWidth-lineWidth;
+//! @todo we can optimize drawing by drawing rescaled pixmap here
+//! and performing detailed painting later (using QTimer)
+ QPixmap pixmapBuffer;
+ QPainter p2;
+ QPainter *target;
+ if (fast) {
+ target = &p;
+ }
+ else {
+//moved pixmapBuffer.resize(rect.size()-QSize(lineWidth, lineWidth));
+//moved p2.begin(&pm, p.device());
+ target = &p2;
+ }
+//! @todo only create buffered pixmap of the minimum size and then do not fillRect()
+// target->fillRect(0,0,rect.width(),rect.height(), backgroundColor);
+
+ QPoint pos;
+ if (scaledContents) {
+ if (keepAspectRatio) {
+ QImage img(pixmap.convertToImage());
+ img = img.smoothScale(w, h, QImage::ScaleMin);
+ pos = rect.topLeft(); //0, 0);
+ if (img.width() < w) {
+ int hAlign = QApplication::horizontalAlignment( alignment );
+ if ( hAlign & Qt::AlignRight )
+ pos.setX(pos.x() + w-img.width());
+ else if ( hAlign & Qt::AlignHCenter )
+ pos.setX(pos.x() + w/2-img.width()/2);
+ }
+ else if (img.height() < h) {
+ if ( alignment & Qt::AlignBottom )
+ pos.setY(pos.y() + h-img.height());
+ else if ( alignment & Qt::AlignVCenter )
+ pos.setY(pos.y() + h/2-img.height()/2);
+ }
+ pixmapBuffer.convertFromImage(img);
+ if (!fast) {
+ p2.begin(&pixmapBuffer, p.device());
+ }
+ else
+ target->drawPixmap(pos, pixmapBuffer);
+ }
+ else {
+ if (!fast) {
+ pixmapBuffer.resize(rect.size()-QSize(lineWidth, lineWidth));
+ p2.begin(&pixmapBuffer, p.device());
+ p2.drawPixmap(QRect(rect.x(), rect.y(), w, h), pixmap);
+ }
+ else
+ target->drawPixmap(QRect(rect.x() + lineWidth, rect.y() + lineWidth, w, h), pixmap);
+ }
+ }
+ else {
+ int hAlign = QApplication::horizontalAlignment( alignment );
+ if ( hAlign & Qt::AlignRight )
+ pos.setX(pos.x() + w-pixmap.width());
+ else if ( hAlign & Qt::AlignHCenter )
+ pos.setX(pos.x() + w/2-pixmap.width()/2);
+ else //left, etc.
+ pos.setX(pos.x());
+
+ if ( alignment & Qt::AlignBottom )
+ pos.setY(pos.y() + h-pixmap.height());
+ else if ( alignment & Qt::AlignVCenter )
+ pos.setY(pos.y() + h/2-pixmap.height()/2);
+ else //top, etc.
+ pos.setY(pos.y());
+// target->drawPixmap(pos, pixmap);
+// if (!fast)
+// p2.begin(&pixmapBuffer, p.device());
+ p.drawPixmap(lineWidth+pos.x(), lineWidth+pos.y(), pixmap);
+ }
+ if (scaledContents && !fast && p.isActive()) {
+ p2.end();
+ bitBlt( p.device(),
+// pos.x(),
+// pos.y(),
+ (int)p.worldMatrix().dx() + rect.x() + lineWidth + pos.x(),
+ (int)p.worldMatrix().dy() + rect.y() + lineWidth + pos.y(),
+ &pixmapBuffer);
+ }
+}
+
+QString KexiUtils::ptrToStringInternal(void* ptr, uint size)
+{
+ QString str;
+ unsigned char* cstr_ptr = (unsigned char*)&ptr;
+ for (uint i=0; i<size; i++) {
+ QString s;
+ s.sprintf("%2.2x", cstr_ptr[i]);
+ str.append( s );
+ }
+ return str;
+}
+
+void* KexiUtils::stringToPtrInternal(const QString& str, uint size)
+{
+ QByteArray array(size);
+ if ((str.length()/2)<size)
+ return 0;
+ bool ok;
+ for (uint i=0; i<size; i++) {
+ array[i]=(unsigned char)(str.mid(i*2, 2).toUInt(&ok, 16));
+ if (!ok)
+ return 0;
+ }
+ return *(void**)(array.data());
+}
+
+void KexiUtils::setFocusWithReason(QWidget* widget, QFocusEvent::Reason reason)
+{
+ QEvent fe( QEvent::FocusIn );
+ QFocusEvent::setReason(reason);
+ QApplication::sendEvent( widget, &fe );
+ QFocusEvent::resetReason();
+}
+
+void KexiUtils::unsetFocusWithReason(QWidget* widget, QFocusEvent::Reason reason)
+{
+ QEvent fe( QEvent::FocusOut );
+ QFocusEvent::setReason(reason);
+ QApplication::sendEvent( widget, &fe );
+ QFocusEvent::resetReason();
+}
+
+CopyFileResult KexiUtils::copyFile(const QString& src, const QString& dest)
+{
+#ifdef Q_WS_WIN
+ int res = fcopy( QFile::encodeName( src ), QFile::encodeName( dest ) );
+ if (res == fcopy_src_err)
+ return CopyReadError;
+ else if (res == fcopy_dest_err)
+ return CopyWriteError;
+
+ return CopySuccess;
+#else
+# define _fcopy_BUFLEN 1024*32
+ char _fcopy_buf[_fcopy_BUFLEN];
+ FILE *in, *out;
+ int c_in=0, c_out=0;
+ CopyFileResult res=CopySuccess;
+
+ in=fopen(QFile::encodeName( src ), "rb");
+ if (!in)
+ return CopyReadError;
+ out=fopen(QFile::encodeName( dest ), "wb");
+ if (!out)
+ return CopyWriteError;
+ while (!feof(in) && !ferror(in) && !ferror(out)) {
+ c_in=fread(_fcopy_buf, 1, _fcopy_BUFLEN, in);
+ if (ferror(in) || c_in==0)
+ break;
+ c_out=fwrite(_fcopy_buf, 1, c_in, out);
+ if (ferror(out) || c_in!=c_out)
+ break;
+ }
+
+ if (ferror(in))
+ res=CopyReadError;
+ else if (ferror(out))
+ res=CopyWriteError;
+ else if (c_in!=c_out)
+ res=CopyWriteError;
+ fclose(in);
+ fclose(out);
+ return res;
+#endif
+}
+
+#include "utils_p.moc"
diff --git a/kexi/kexiutils/utils.h b/kexi/kexiutils/utils.h
new file mode 100644
index 000000000..03b775c72
--- /dev/null
+++ b/kexi/kexiutils/utils.h
@@ -0,0 +1,267 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIUTILS_UTILS_H
+#define KEXIUTILS_UTILS_H
+
+#include "kexiutils_export.h"
+
+#include <qguardedptr.h>
+#include <qobjectlist.h>
+#include <kmimetype.h>
+class QColor;
+
+//! @short General Utils
+namespace KexiUtils
+{
+ //! \return true if \a o has parent \a par.
+ inline bool hasParent(QObject* par, QObject* o)
+ {
+ if (!o || !par)
+ return false;
+ while (o && o!=par)
+ o = o->parent();
+ return o==par;
+ }
+
+ //! \return parent object of \a o that inherits \a className or NULL if no such parent
+ template<class type>
+ inline type* findParent(QObject* o, const char* className)
+ {
+ if (!o || !className || className[0]=='\0')
+ return 0;
+ while ( ((o=o->parent())) && !o->inherits(className) )
+ ;
+ return static_cast<type*>(o);
+ }
+
+ //! Const version of findParent()
+ template<class type>
+ inline const type* findParentConst(const QObject* const o, const char* className)
+ {
+ const QObject * obj = o;
+ if (!obj || !className || className[0]=='\0')
+ return 0;
+ while ( ((obj=obj->parent())) && !obj->inherits(className) )
+ ;
+ return static_cast<const type*>(obj);
+ }
+
+ /*! \return first found child of \a o, inheriting \a className.
+ If objName is 0 (the default), all object names match.
+ Returned pointer type is casted. */
+ template<class type>
+ type* findFirstChild(QObject *o, const char* className, const char* objName = 0)
+ {
+ if (!o || !className || className[0]=='\0')
+ return 0;
+ QObjectList *l = o->queryList( className, objName );
+ QObject *result = l->first();
+ delete l;
+ return static_cast<type*>(result);
+ }
+
+ //! QDateTime - a hack needed because QVariant(QTime) has broken isNull()
+ inline QDateTime stringToHackedQTime(const QString& s)
+ {
+ if (s.isEmpty())
+ return QDateTime();
+ // kdDebug() << QDateTime( QDate(0,1,2), QTime::fromString( s, Qt::ISODate ) ).toString(Qt::ISODate) << endl;;
+ return QDateTime( QDate(0,1,2), QTime::fromString( s, Qt::ISODate ) );
+ }
+
+ /*! Sets "wait" cursor with 1 second delay (or 0 seconds if noDelay is true).
+ Does nothing if the application has no GUI enabled. (see KApplication::guiEnabled()) */
+ KEXIUTILS_EXPORT void setWaitCursor(bool noDelay = false);
+
+ /*! Remove "wait" cursor previously set with \a setWaitCursor(),
+ even if it's not yet visible.
+ Does nothing if the application has no GUI enabled. (see KApplication::guiEnabled()) */
+ KEXIUTILS_EXPORT void removeWaitCursor();
+
+ /*! Helper class. Allocate it in your code block as follows:
+ <code>
+ KexiUtils::WaitCursor wait;
+ </code>
+ .. and wait cursor will be visible (with one second delay) until you're in this block, without
+ a need to call removeWaitCursor() before exiting the block.
+ Does nothing if the application has no GUI enabled. (see KApplication::guiEnabled()) */
+ class KEXIUTILS_EXPORT WaitCursor
+ {
+ public:
+ WaitCursor(bool noDelay = false);
+ ~WaitCursor();
+ };
+
+ /*! Helper class. Allocate it in your code block as follows:
+ <code>
+ KexiUtils::WaitCursorRemover remover;
+ </code>
+ .. and the wait cursor will be hidden unless you leave this block, without
+ a need to call setWaitCursor() before exiting the block. After leaving the codee block,
+ the cursor will be visible again, if it was visible before creating the WaitCursorRemover object.
+ Does nothing if the application has no GUI enabled. (see KApplication::guiEnabled()) */
+ class KEXIUTILS_EXPORT WaitCursorRemover
+ {
+ public:
+ WaitCursorRemover();
+ ~WaitCursorRemover();
+ private:
+ bool m_reactivateCursor : 1;
+ };
+
+ /*! \return filter string in QFileDialog format for a mime type pointed by \a mime
+ If \a kdeFormat is true, QFileDialog-compatible filter string is generated,
+ eg. "Image files (*.png *.xpm *.jpg)", otherwise KFileDialog -compatible
+ filter string is generated, eg. "*.png *.xpm *.jpg|Image files (*.png *.xpm *.jpg)".
+ "\\n" is appended if \a kdeFormat is true, otherwise ";;" is appended. */
+ KEXIUTILS_EXPORT QString fileDialogFilterString(const KMimeType::Ptr& mime, bool kdeFormat = true);
+
+ /*! @overload QString fileDialogFilterString(const KMimeType::Ptr& mime, bool kdeFormat = true) */
+ KEXIUTILS_EXPORT QString fileDialogFilterString(const QString& mimeString, bool kdeFormat = true);
+
+ /*! Like QString fileDialogFilterString(const KMimeType::Ptr& mime, bool kdeFormat = true)
+ but returns a list of filter strings. */
+ KEXIUTILS_EXPORT QString fileDialogFilterStrings(const QStringList& mimeStrings, bool kdeFormat);
+
+ /*! \return a color being a result of blending \a c1 with \a c2 with \a factor1
+ and \a factor1 factors: (c1*factor1+c2*factor2)/(factor1+factor2). */
+ KEXIUTILS_EXPORT QColor blendedColors(const QColor& c1, const QColor& c2, int factor1 = 1, int factor2 = 1);
+
+ /*! \return a contrast color for a color \a c:
+ If \a c is light color, darker color created using c.dark(200) is returned;
+ otherwise lighter color created using c.light(200) is returned. */
+ KEXIUTILS_EXPORT QColor contrastColor(const QColor& c);
+
+ /*! \return a lighter color for a color \a c and a factor \a factor.
+ For colors like Qt::red or Qt::green where hue and saturation are near to 255,
+ hue is decreased so the result will be more bleached.
+ For black color the result is dark gray rather than black. */
+ KEXIUTILS_EXPORT QColor bleachedColor(const QColor& c, int factor);
+
+ /*! \return icon set computed as a result of colorizing \a icon pixmap with "buttonText"
+ color of \a palette palette. This function is useful for displaying monochromed icons
+ on the list view or table view header, to avoid bloat, but still have the color compatible
+ with accessibility settings. */
+ KEXIUTILS_EXPORT QIconSet colorizeIconToTextColor(const QPixmap& icon, const QPalette& palette);
+
+ /*! \return empty (fully transparent) pixmap that can be used as a place for icon of size \a iconGroup */
+ KEXIUTILS_EXPORT QPixmap emptyIcon(KIcon::Group iconGroup);
+
+ /*! Serializes \a map to \a array.
+ KexiUtils::deserializeMap() can be used to deserialize this array back to map. */
+ KEXIUTILS_EXPORT void serializeMap(const QMap<QString,QString>& map, const QByteArray& array);
+ KEXIUTILS_EXPORT void serializeMap(const QMap<QString,QString>& map, QString& string);
+
+ /*! \return a map deserialized from a byte array \a array.
+ \a array need to contain data previously serialized using KexiUtils::serializeMap(). */
+ KEXIUTILS_EXPORT QMap<QString,QString> deserializeMap(const QByteArray& array);
+
+ /*! \return a map deserialized from \a string.
+ \a string need to contain data previously serialized using KexiUtils::serializeMap(). */
+ KEXIUTILS_EXPORT QMap<QString,QString> deserializeMap(const QString& string);
+
+ /*! \return a valid filename converted from \a string by:
+ - replacing \\, /, :, *, ?, ", <, >, |, \n \\t characters with a space
+ - simplifing whitespace by removing redundant space characters using QString::simplifyWhiteSpace()
+ Do not pass full paths here, but only filename strings. */
+ KEXIUTILS_EXPORT QString stringToFileName(const QString& string);
+
+ /*! Performs a simple \a string encryption using rot47-like algorithm.
+ Each character's unicode value is increased by 47 + i (where i is index of the character).
+ The resulting string still contains redable characters.
+ Do not use this for data that can be accessed by attackers! */
+ KEXIUTILS_EXPORT void simpleCrypt(QString& string);
+
+ /*! Performs a simple \a string decryption using rot47-like algorithm,
+ using opposite operations to KexiUtils::simpleCrypt(). */
+ KEXIUTILS_EXPORT void simpleDecrypt(QString& string);
+
+#ifdef KEXI_DEBUG_GUI
+ //! Creates debug window for convenient debugging output
+ KEXIUTILS_EXPORT QWidget *createDebugWindow(QWidget *parent);
+
+ //! Adds debug line for for KexiDB database
+ KEXIUTILS_EXPORT void addKexiDBDebug(const QString& text);
+
+ //! Adds debug line for for Table Designer (Alter Table actions)
+ KEXIUTILS_EXPORT void addAlterTableActionDebug(const QString& text, int nestingLevel = 0);
+
+ //! Connects push button action to \a receiver and its \a slot. This allows to execute debug-related actions
+ //! using buttons displayed in the debug window.
+ KEXIUTILS_EXPORT void connectPushButtonActionForDebugWindow(const char* actionName,
+ const QObject *receiver, const char* slot);
+#endif
+
+ //! Draws pixmap on painter \a p using predefined parameters.
+ //! Used in KexiDBImageBox and KexiBlobTableEdit.
+ KEXIUTILS_EXPORT void drawPixmap( QPainter& p, int lineWidth, const QRect& rect,
+ const QPixmap& pixmap, int alignment, bool scaledContents, bool keepAspectRatio);
+
+ //! @internal
+ KEXIUTILS_EXPORT QString ptrToStringInternal(void* ptr, uint size);
+ //! @internal
+ KEXIUTILS_EXPORT void* stringToPtrInternal(const QString& str, uint size);
+
+ //! \return a pointer \a ptr safely serialized to string
+ template<class type>
+ QString ptrToString(type *ptr)
+ {
+ return ptrToStringInternal(ptr, sizeof(type*));
+ }
+
+ //! \return a pointer of type \a type safely deserialized from \a str
+ template<class type>
+ type* stringToPtr(const QString& str)
+ {
+ return static_cast<type*>( stringToPtrInternal(str, sizeof(type*)) );
+ }
+
+ //! Sets focus for widget \a widget with reason \a reason.
+ KEXIUTILS_EXPORT void setFocusWithReason(QWidget* widget, QFocusEvent::Reason reason);
+
+ //! Unsets focus for widget \a widget with reason \a reason.
+ KEXIUTILS_EXPORT void unsetFocusWithReason(QWidget* widget, QFocusEvent::Reason reason);
+
+ //! Used by copyFile()
+ enum CopyFileResult {
+ CopySuccess = 0,
+ CopyReadError = 1,
+ CopyWriteError = 2
+ };
+
+ /*!
+ Copies @p src file to @p dest file.
+ @return CopySuccess on success, CopyReadError on source file error,
+ CopyWriteError on destination file error.
+ @todo remove: QFile in Qt4 provides this.
+ */
+ KEXIUTILS_EXPORT CopyFileResult copyFile(const QString& src, const QString& dest);
+}
+
+
+//! sometimes we leave a space in the form of empty QFrame and want to insert here
+//! a widget that must be instantiated by hand.
+//! This macro inserts a widget \a what into a frame \a where.
+#define GLUE_WIDGET(what, where) \
+ { QVBoxLayout *lyr = new QVBoxLayout(where); \
+ lyr->addWidget(what); }
+
+
+#endif //KEXIUTILS_UTILS_H
diff --git a/kexi/kexiutils/utils_p.h b/kexi/kexiutils/utils_p.h
new file mode 100644
index 000000000..821c22b10
--- /dev/null
+++ b/kexi/kexiutils/utils_p.h
@@ -0,0 +1,54 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIUTILS_P_H
+#define KEXIUTILS_P_H
+
+#include <qtimer.h>
+#include <qapplication.h>
+#include <qdialog.h>
+
+/*! @internal */
+class DelayedCursorHandler : public QObject
+{
+ Q_OBJECT
+ public:
+ DelayedCursorHandler();
+ void start(bool noDelay);
+ void stop();
+ bool startedOrActive : 1; //! true if ounting started or the cursor is active
+ protected slots:
+ void show();
+ protected:
+ QTimer timer;
+};
+
+/*! @internal */
+class DebugWindowDialog : public QDialog
+{
+ public:
+ explicit DebugWindowDialog( QWidget * parent )
+ : QDialog(parent, 0, false, Qt::WType_Dialog|Qt::WStyle_MinMax|Qt::WStyle_StaysOnTop)
+ {
+ setWindowState( Qt::WindowMinimized );
+ }
+};
+
+#endif
+
diff --git a/kexi/kexiutils/validator.cpp b/kexi/kexiutils/validator.cpp
new file mode 100644
index 000000000..e27077ec4
--- /dev/null
+++ b/kexi/kexiutils/validator.cpp
@@ -0,0 +1,118 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "validator.h"
+
+using namespace KexiUtils;
+
+Validator::Validator(QObject * parent, const char * name)
+: QValidator(parent,name)
+, m_acceptsEmptyValue(false)
+{
+}
+
+Validator::~Validator()
+{
+}
+
+Validator::Result Validator::check(const QString &valueName, const QVariant& v,
+ QString &message, QString &details)
+{
+ if (v.isNull() || v.type()==QVariant::String && v.toString().isEmpty()) {
+ if (!m_acceptsEmptyValue) {
+ message = Validator::msgColumnNotEmpty().arg(valueName);
+ return Error;
+ }
+ return Ok;
+ }
+ return internalCheck(valueName, v, message, details);
+}
+
+Validator::Result Validator::internalCheck(const QString & /*valueName*/,
+ const QVariant& /*v*/, QString & /*message*/, QString & /*details*/)
+{
+ return Error;
+}
+
+QValidator::State Validator::validate ( QString & , int & ) const
+{
+ return QValidator::Acceptable;
+}
+
+//-----------------------------------------------------------
+
+MultiValidator::MultiValidator(QObject* parent, const char * name)
+ : Validator(parent, name)
+{
+ m_ownedSubValidators.setAutoDelete(true);
+}
+
+MultiValidator::MultiValidator(QValidator *validator,
+ QObject * parent, const char * name)
+ : Validator(parent, name)
+{
+ addSubvalidator(validator);
+}
+
+
+void MultiValidator::addSubvalidator( QValidator* validator, bool owned )
+{
+ if (!validator)
+ return;
+ m_subValidators.append(validator);
+ if (owned && !validator->parent())
+ m_ownedSubValidators.append(validator);
+}
+
+QValidator::State MultiValidator::validate( QString & input, int & pos ) const
+{
+ State s;
+ foreach( QValueList<QValidator*>::ConstIterator, it, m_subValidators ) {
+ s = (*it)->validate(input, pos);
+ if (s==Intermediate || s==Invalid)
+ return s;
+ }
+ return Acceptable;
+}
+
+void MultiValidator::fixup ( QString & input ) const
+{
+ foreach( QValueList<QValidator*>::ConstIterator, it, m_subValidators )
+ (*it)->fixup(input);
+}
+
+Validator::Result MultiValidator::internalCheck(
+ const QString &valueName, const QVariant& v,
+ QString &message, QString &details)
+{
+ Result r;
+ bool warning = false;
+ foreach( QValueList<QValidator*>::ConstIterator, it, m_subValidators ) {
+ if (dynamic_cast<Validator*>(*it))
+ r = dynamic_cast<Validator*>(*it)->internalCheck(valueName, v, message, details);
+ else
+ r = Ok; //ignore
+ if (r==Error)
+ return Error;
+ else if (r==Warning)
+ warning = true;
+ }
+ return warning ? Warning : Ok;
+}
+
diff --git a/kexi/kexiutils/validator.h b/kexi/kexiutils/validator.h
new file mode 100644
index 000000000..5a33d4c6f
--- /dev/null
+++ b/kexi/kexiutils/validator.h
@@ -0,0 +1,158 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_VALIDATOR_H
+#define KEXI_VALIDATOR_H
+
+#include "kexiutils_export.h"
+
+#include <qvalidator.h>
+#include <qvariant.h>
+#include <qstring.h>
+
+#include <klocale.h>
+
+namespace KexiUtils {
+
+//! @short A validator extending QValidator with offline-checking for value's validity
+/*!
+ The offline-checking for value's validity is provided by \ref Validator::check() method.
+ The validator groups two purposes into one container:
+ - string validator for line editors (online checking, "on typing");
+ - offline-checking for QVariant values, reimplementing validate().
+
+ It also offers error and warning messages for check() method.
+ You may need to reimplement:
+ - QValidator::State IdentifierValidator::validate( QString& input, int& pos ) const;
+ - Result check(const QString &valueName, QVariant v, QString &message, QString &details);
+ */
+class KEXIUTILS_EXPORT Validator : public QValidator
+{
+ public:
+ typedef enum Result { Error = 0, Ok = 1, Warning = 2 };
+
+ Validator(QObject * parent = 0, const char * name = 0);
+ virtual ~Validator();
+
+ /*! Sets accepting empty values on (true) or off (false).
+ By default the validator does not accepts empty values. */
+ void setAcceptsEmptyValue( bool set ) { m_acceptsEmptyValue = set; }
+
+ /*! \return true if the validator accepts empty values
+ @see setAcceptsEmptyValue() */
+ bool acceptsEmptyValue() const { return m_acceptsEmptyValue; }
+
+ /*! Checks if value \a v is ok and returns one of \a Result value:
+ - \a Error is returned on error;
+ - \a Ok on success;
+ - \a Warning if there is something to warn about.
+ In any case except \a Ok, i18n'ed message will be set in \a message
+ and (optionally) datails are set in \a details, e.g. for use in a message box.
+ \a valueName can be used to contruct \a message as well, for example:
+ "[valueName] is not a valid login name".
+ Depending on acceptsEmptyValue(), immediately accepts empty values or not. */
+ Result check(const QString &valueName, const QVariant& v, QString &message,
+ QString &details);
+
+ /*! This implementation always returns value QValidator::Acceptable. */
+ virtual QValidator::State validate ( QString & input, int & pos ) const;
+
+ //! A generic error/warning message
+ static const QString msgColumnNotEmpty() {
+ return I18N_NOOP("\"%1\" value has to be entered.");
+ }
+
+ //! Adds a child validator \a v
+ void addChildValidator( Validator* v );
+
+ protected:
+ /* Used by check(), for reimplementation, by default returns \a Error.*/
+ virtual Result internalCheck(const QString &valueName, const QVariant& v,
+ QString &message, QString &details);
+
+ bool m_acceptsEmptyValue : 1;
+
+ friend class MultiValidator;
+};
+
+//! @short A validator groupping multiple QValidators
+/*! MultiValidator behaves like normal KexiUtils::Validator,
+ but it allows to add define more than one different validator.
+ Given validation is successful if every subvalidator accepted given value.
+
+ - acceptsEmptyValue() is used globally here
+ (no matter what is defined in subvalidators).
+
+ - result of calling check() depends on value of check() returned by subvalidators:
+ - Error is returned if at least one subvalidator returned Error;
+ - Warning is returned if at least one subvalidator returned Warning and
+ no validator returned error;
+ - Ok is returned only if exactly all subvalidators returned Ok.
+ - If there is no subvalidators defined, Error is always returned.
+ - If a given subvalidator is not of class Validator but ust QValidator,
+ it's assumed it's check() method returned Ok.
+
+ - result of calling validate() (a method implemented for QValidator)
+ depends on value of validate() returned by subvalidators:
+ - Invalid is returned if at least one subvalidator returned Invalid
+ - Intermediate is returned if at least one subvalidator returned Intermediate
+ - Acceptable is returned if exactly all subvalidators returned Acceptable.
+ - If there is no subvalidators defined, Invalid is always returned.
+
+ If there are no subvalidators, the multi validator always accepts the input.
+*/
+class KEXIUTILS_EXPORT MultiValidator : public Validator
+{
+ public:
+ /*! Constructs multivalidator with no subvalidators defined.
+ You can add more validators with addSubvalidator(). */
+ MultiValidator(QObject * parent = 0, const char * name = 0);
+
+ /*! Constructs multivalidator with one validator \a validator.
+ It will be owned if has no parent defined.
+ You can add more validators with addSubvalidator(). */
+ MultiValidator(QValidator *validator, QObject * parent = 0, const char * name = 0);
+
+ /*! Adds validator \a validator as another subvalidator.
+ Subvalidator will be owned by this multivalidator if \a owned is true
+ and its parent is NULL. */
+ void addSubvalidator( QValidator* validator, bool owned = true );
+
+ /*! Reimplemented to call validate() on subvalidators. */
+ virtual QValidator::State validate ( QString & input, int & pos ) const;
+
+ /*! Calls QValidator::fixup() on every subvalidator.
+ This may be senseless to use this methog in certain cases
+ (can return weir results), so think twice before.. */
+ virtual void fixup ( QString & input ) const;
+
+ private:
+ virtual Validator::Result internalCheck(
+ const QString &valueName, const QVariant& v,
+ QString &message, QString &details);
+
+
+ protected:
+ QPtrList<QValidator> m_ownedSubValidators;
+ QValueList<QValidator*> m_subValidators;
+};
+
+}
+
+#endif
diff --git a/kexi/main.cpp b/kexi/main.cpp
new file mode 100644
index 000000000..d299e3cba
--- /dev/null
+++ b/kexi/main.cpp
@@ -0,0 +1,37 @@
+/* This file is part of the KDE project
+
+ begin : Sun Jun 9 12:15:11 CEST 2002
+
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qapplication.h>
+#include <main/keximainwindowimpl.h>
+
+extern "C" int kdemain(int argc, char *argv[])
+{
+ int result = KexiMainWindowImpl::create(argc, argv);
+ if (!qApp)
+ return result;
+
+ result = qApp->exec();
+ delete qApp;
+ return result;
+}
diff --git a/kexi/main/Makefile.am b/kexi/main/Makefile.am
new file mode 100644
index 000000000..8786c7cf3
--- /dev/null
+++ b/kexi/main/Makefile.am
@@ -0,0 +1,35 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkeximain.la
+libkeximain_la_SOURCES = \
+ keximainwindowimpl.cpp \
+ kexistatusbar.cpp \
+ kexinamewidget.cpp kexinamedialog.cpp \
+ kexinewstuff.cpp kexifinddialogbase.ui kexifinddialog.cpp
+
+libkeximain_la_LDFLAGS = -no-undefined $(KDE_RPATH) $(all_libraries) \
+ $(VER_INFO) -Wnounresolved
+
+SUBDIRS = startup printing .
+
+libkeximain_la_LIBADD = $(top_builddir)/kexi/kexidb/libkexidb.la $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/kexiutils/libkexiutils.la \
+ $(top_builddir)/kexi/main/startup/libkeximainstartup.la \
+ $(top_builddir)/kexi/main/printing/libkeximainprinting.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(LIB_KFEEDBACK) $(LIB_KEXI_KMDI) $(LIB_KNEWSTUFF) $(LIB_KIO) $(LIB_KDEPRINT)
+
+#disabled ../migration/libkeximigrate.la
+
+INCLUDES = $(INC_KFEEDBACK) $(LIB_KEXI_KMDI_INCLUDES) \
+ -I$(top_builddir)/lib/kofficeui -I$(top_srcdir)/lib/kofficeui \
+ -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/kexi/main \
+ -I$(top_srcdir)/kexi/main/startup -I$(top_srcdir)/kexi/widget \
+ -I$(top_srcdir)/kexi/migration -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore \
+ $(all_includes)
+
+noinst_HEADERS = ksplitter.h keximainwindowimpl_p.h
+
+METASOURCES = AUTO
diff --git a/kexi/main/configure.in.in b/kexi/main/configure.in.in
new file mode 100644
index 000000000..00c25f388
--- /dev/null
+++ b/kexi/main/configure.in.in
@@ -0,0 +1,102 @@
+
+dnl ======================================
+dnl KNewStuff Configuration
+dnl ======================================
+dnl
+dnl Copyright (C) 2004 Josef Spillner <spillner@kde.org>
+dnl This file is to be used within KDE's build system.
+dnl It defines $(LIB_KNEWSTUFF) if knewstuff has been found,
+dnl and a HAVE_KNEWSTUFF #define statement is added.
+dnl
+
+AC_MSG_CHECKING([for KDE library: knewstuff])
+
+ac_knewstuff_includes=NO ac_knewstuff_libraries=NO
+knewstuff_libraries=""
+knewstuff_includes=""
+
+AC_CACHE_VAL(ac_cv_have_knewstuff,
+[
+AC_FIND_FILE(knewstuff/downloaddialog.h, $kde_incdirs, knewstuff_incdir)
+ac_knewstuff_includes="$knewstuff_incdir"
+
+AC_FIND_FILE(libknewstuff.so, $kde_libdirs, knewstuff_libdir)
+ac_knewstuff_libraries="$knewstuff_libdir"
+
+if test "$ac_knewstuff_includes" = NO || test "$ac_knewstuff_libraries" = NO; then
+ ac_cv_have_knewstuff="have_knewstuff=no"
+ ac_knewstuff_notfound=""
+else
+ have_knewstuff="yes"
+fi
+])
+
+eval "$ac_cv_have_knewstuff"
+
+if test "$have_knewstuff" != yes; then
+ AC_MSG_RESULT([$have_knewstuff])
+else
+ AC_MSG_RESULT([$have_knewstuff (libraries $ac_knewstuff_libraries, headers $ac_knewstuff_includes)])
+
+dnl AC_DEFINE_UNQUOTED(HAVE_KNEWSTUFF, 1, [Add KNewStuff functionality.])
+ CXXFLAGS="$CXXFLAGS -DHAVE_KNEWSTUFF"
+
+ LIB_KNEWSTUFF='-lknewstuff'
+ AC_SUBST(LIB_KNEWSTUFF)
+fi
+
+AC_CHECK_FILE([kexi/3rdparty/kexifeedbackwizard/lib/kexifeedbackwizard.cpp],
+ have_internal_feedback="yes"
+,
+ have_internal_feedback="no"
+)
+
+AC_MSG_CHECKING([for KDE library: kfeedbackwizard])
+
+ac_kfeedback_includes=NO ac_kfeedback_libraries=NO
+kfeedback_libraries=""
+kfeedback_includes=""
+
+AC_CACHE_VAL(ac_cv_have_kfeedback,
+[
+AC_FIND_FILE(kfeedbackwizard.h, $kde_incdirs, kfeedback_incdir)
+ac_kfeedback_includes="$kfeedback_incdir"
+
+AC_FIND_FILE(libkfeedbackwizard.so, $kde_libdirs, kfeedback_libdir)
+ac_kfeedback_libraries="$kfeedback_libdir"
+
+if test "$ac_kfeedback_includes" = NO || test "$ac_kfeedback_libraries" = NO; then
+ ac_cv_have_kfeedback="have_kfeedback=no"
+ ac_kfeedback_notfound=""
+else
+ have_kfeedback="yes"
+fi
+])
+
+eval "$ac_cv_have_kfeedback"
+
+INC_KFEEDBACK=''
+LIB_KFEEDBACK=''
+if test "$have_kfeedback" != yes; then
+ if test "$have_internal_feedback" = yes; then
+ CXXFLAGS="$CXXFLAGS -DFEEDBACK_CLASS=KexiFeedbackWizard -DFEEDBACK_INCLUDE=\"<kexifeedbackwizard.h>\""
+ use_kexifb="yes"
+ AC_MSG_RESULT([using internal])
+ INC_KFEEDBACK='-I../3rdparty/kexifeedbackwizard/lib'
+ LIB_KFEEDBACK='../3rdparty/kexifeedbackwizard/lib/libkexifeedbackwizard.la'
+ else
+ use_kexifb="no"
+ AC_MSG_RESULT([dont use])
+ fi
+else
+ use_kexifb="no"
+ AC_MSG_RESULT([$have_kfeedback (libraries $ac_kfeedback_libraries, headers $ac_kfeedback_includes)])
+
+dnl AC_DEFINE_UNQUOTED(HAVE_KFEEDBACK, 1, [Add KNewStuff functionality.])
+ CXXFLAGS="$CXXFLAGS -DFEEDBACK_CLASS=KFeedbackWizard -DFEEDBACK_INCLUDE=\"<kfeedbackwizard.h>\""
+
+ LIB_KFEEDBACK='-lkfeedbackwizard'
+fi
+AC_SUBST(LIB_KFEEDBACK)
+AC_SUBST(INC_KFEEDBACK)
+AM_CONDITIONAL(use_kexifeedback, test "$use_kexifb" = "yes")
diff --git a/kexi/main/kde2_closebutton.xpm b/kexi/main/kde2_closebutton.xpm
new file mode 100644
index 000000000..5c05e7149
--- /dev/null
+++ b/kexi/main/kde2_closebutton.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+#ifndef _KDE2_CLOSEBUTTON_XPM_
+#define _KDE2_CLOSEBUTTON_XPM_
+
+static const char* kde2_closebutton[]={
+"12 12 2 1",
+". s None c None",
+"# c #000000",
+"............",
+"............",
+"...#....#...",
+"..###..###..",
+"...######...",
+"....####....",
+"....####....",
+"...######...",
+"..###..###..",
+"...#....#...",
+"............",
+"............"};
+
+#endif
diff --git a/kexi/main/kexifinddialog.cpp b/kexi/main/kexifinddialog.cpp
new file mode 100644
index 000000000..58f693c06
--- /dev/null
+++ b/kexi/main/kexifinddialog.cpp
@@ -0,0 +1,279 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexifinddialog.h"
+
+#include <kstdguiitem.h>
+#include <kstdaction.h>
+#include <kpushbutton.h>
+#include <kcombobox.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kdialog.h>
+#include <kaction.h>
+#include <kiconloader.h>
+
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qguardedptr.h>
+#include <qlayout.h>
+#include <qaccel.h>
+
+//! @internal
+class KexiFindDialog::Private
+{
+ public:
+ Private()
+ {
+ accels.setAutoDelete(true);
+ }
+ ~Private()
+ {
+ }
+ //! Connects action \a action with appropriate signal \a member
+ //! and optionally adds accel that will receive shortcut for \a action
+ //! at global scope of the dialog \a parent.
+ void setActionAndAccel(KAction *action, QWidget* parent, const char* member)
+ {
+ if (!action)
+ return;
+ QObject::connect(parent, member, action, SLOT(activate()));
+ if (action->shortcut().isNull())
+ return;
+ QAccel *accel = new QAccel(parent); // we want to handle dialog-wide shortcut as well
+ accels.append( accel );
+ accel->connectItem(
+ accel->insertItem( action->shortcut() ), parent, member );
+ }
+
+ QStringList lookInColumnNames;
+ QStringList lookInColumnCaptions;
+ QString objectName; //!< for caption
+ QGuardedPtr<KAction> findnextAction;
+ QGuardedPtr<KAction> findprevAction;
+ QGuardedPtr<KAction> replaceAction;
+ QGuardedPtr<KAction> replaceallAction;
+ QPtrList<QAccel> accels;
+ bool replaceMode : 1;
+};
+
+//------------------------------------------
+
+KexiFindDialog::KexiFindDialog( QWidget* parent )
+ : KexiFindDialogBase(parent, "KexiFindDialog", false/*!modal*/,
+ Qt::WType_Dialog|Qt::WStyle_NormalBorder|Qt::WStyle_Title
+ |Qt::WStyle_SysMenu|Qt::WStyle_Customize|Qt::WStyle_Tool)
+ , d( new Private() )
+{
+ m_search->setCurrentItem((int)KexiSearchAndReplaceViewInterface::Options::SearchDown);
+ layout()->setMargin( KDialog::marginHint() );
+ layout()->setSpacing( KDialog::spacingHint() );
+ KAction *a = KStdAction::findNext(0, 0, 0);
+ m_btnFind->setText(a->text());
+ m_btnFind->setIconSet(a->iconSet());
+ delete a;
+ m_btnClose->setText(KStdGuiItem::close().text());
+ m_btnClose->setIconSet(KStdGuiItem::close().iconSet());
+ connect(m_btnFind, SIGNAL(clicked()), this, SIGNAL(findNext()));
+ connect(m_btnClose, SIGNAL(clicked()), this, SLOT(slotCloseClicked()));
+ connect(m_btnReplace, SIGNAL(clicked()), this, SIGNAL(replaceNext()));
+ connect(m_btnReplaceAll, SIGNAL(clicked()), this, SIGNAL(replaceAll()));
+ // clear message after the text is changed
+ connect(m_textToFind, SIGNAL(textChanged()), this, SIGNAL(updateMessage()));
+ connect(m_textToReplace, SIGNAL(textChanged()), this, SIGNAL(updateMessage()));
+
+ d->replaceMode = true; //to force updating by setReplaceMode()
+ setReplaceMode(false);
+
+ setLookInColumnList(QStringList(), QStringList());
+}
+
+KexiFindDialog::~KexiFindDialog()
+{
+ delete d;
+}
+
+void KexiFindDialog::setActions( KAction *findnext, KAction *findprev,
+ KAction *replace, KAction *replaceall )
+{
+ d->findnextAction = findnext;
+ d->findprevAction = findprev;
+ d->replaceAction = replace;
+ d->replaceallAction = replaceall;
+ d->accels.clear();
+ d->setActionAndAccel(d->findnextAction, this, SIGNAL(findNext()));
+ d->setActionAndAccel(d->findprevAction, this, SIGNAL(findPrevious()));
+ d->setActionAndAccel(d->replaceAction, this, SIGNAL(replaceNext()));
+ d->setActionAndAccel(d->replaceallAction, this, SIGNAL(replaceAll()));
+}
+
+QStringList KexiFindDialog::lookInColumnNames() const
+{
+ return d->lookInColumnNames;
+}
+
+QStringList KexiFindDialog::lookInColumnCaptions() const
+{
+ return d->lookInColumnCaptions;
+}
+
+QString KexiFindDialog::currentLookInColumnName() const
+{
+ int index = m_lookIn->currentItem();
+ if (index <= 0 || index >= (int)d->lookInColumnNames.count())
+ return QString::null;
+ else if (index == 1)
+ return "(field)";
+ return d->lookInColumnNames[index - 1/*"(All fields)"*/ - 1/*"(Current field)"*/];
+}
+
+QVariant KexiFindDialog::valueToFind() const
+{
+ return m_textToFind->currentText();
+}
+
+QVariant KexiFindDialog::valueToReplaceWith() const
+{
+ return m_textToReplace->currentText();
+}
+
+void KexiFindDialog::setLookInColumnList(const QStringList& columnNames,
+ const QStringList& columnCaptions)
+{
+ d->lookInColumnNames = columnNames;
+ d->lookInColumnCaptions = columnCaptions;
+ m_lookIn->clear();
+ m_lookIn->insertItem(i18n("(All fields)"));
+ m_lookIn->insertItem(i18n("(Current field)"));
+ m_lookIn->insertStringList(d->lookInColumnCaptions);
+}
+
+void KexiFindDialog::setCurrentLookInColumnName(const QString& columnName)
+{
+ int index;
+ if (columnName.isEmpty())
+ index = 0;
+ else if (columnName == "(field)")
+ index = 1;
+ else {
+ index = d->lookInColumnNames.findIndex( columnName );
+ if (index == -1) {
+ kdWarning() << QString("KexiFindDialog::setCurrentLookInColumn(%1) column name not found on the list")
+ .arg(columnName) << endl;
+ return;
+ }
+ index = index + 1/*"(All fields)"*/ + 1/*"(Current field)"*/;
+ }
+ m_lookIn->setCurrentItem(index);
+}
+
+void KexiFindDialog::setReplaceMode(bool set)
+{
+ if (d->replaceMode == set)
+ return;
+ d->replaceMode = set;
+ if (d->replaceMode) {
+ m_promptOnReplace->show();
+ m_replaceLbl->show();
+ m_textToReplace->show();
+ m_btnReplace->show();
+ m_btnReplaceAll->show();
+ }
+ else {
+ m_promptOnReplace->hide();
+ m_replaceLbl->hide();
+ m_textToReplace->hide();
+ m_btnReplace->hide();
+ m_btnReplaceAll->hide();
+ resize(width(),height()-30);
+ }
+ setObjectNameForCaption(d->objectName);
+ updateGeometry();
+}
+
+void KexiFindDialog::setObjectNameForCaption(const QString& name)
+{
+ d->objectName = name;
+ if (d->replaceMode) {
+ if (name.isEmpty())
+ setCaption(i18n("Replace"));
+ else
+ setCaption(i18n("Replace in \"%1\"").arg(name));
+ }
+ else {
+ if (name.isEmpty())
+ setCaption(i18n("Find"));
+ else
+ setCaption(i18n("Find in \"%1\"").arg(name));
+ }
+}
+
+void KexiFindDialog::setButtonsEnabled(bool enable)
+{
+ m_btnFind->setEnabled(enable);
+ m_btnReplace->setEnabled(enable);
+ m_btnReplaceAll->setEnabled(enable);
+ if (!enable)
+ setObjectNameForCaption(QString::null);
+}
+
+void KexiFindDialog::setMessage(const QString& message)
+{
+ m_messageLabel->setText(message);
+}
+
+void KexiFindDialog::updateMessage( bool found )
+{
+ if (found)
+ setMessage(QString::null);
+ else
+ setMessage(i18n("The search item was not found"));
+}
+
+void KexiFindDialog::slotCloseClicked()
+{
+ reject();
+}
+
+void KexiFindDialog::show()
+{
+ m_textToFind->setFocus();
+ QDialog::show();
+}
+
+KexiSearchAndReplaceViewInterface::Options KexiFindDialog::options() const
+{
+ KexiSearchAndReplaceViewInterface::Options options;
+ if (m_lookIn->currentItem() <= 0) //"(All fields)"
+ options.columnNumber = KexiSearchAndReplaceViewInterface::Options::AllColumns;
+ else if (m_lookIn->currentItem() == 1) //"(Current field)"
+ options.columnNumber = KexiSearchAndReplaceViewInterface::Options::CurrentColumn;
+ else
+ options.columnNumber = m_lookIn->currentItem() - 1/*"(All fields)"*/ - 1/*"(Current field)"*/;
+ options.textMatching
+ = (KexiSearchAndReplaceViewInterface::Options::TextMatching)m_match->currentItem();
+ options.searchDirection
+ = (KexiSearchAndReplaceViewInterface::Options::SearchDirection)m_search->currentItem();
+ options.caseSensitive = m_caseSensitive->isChecked();
+ options.wholeWordsOnly = m_wholeWords->isChecked();
+ options.promptOnReplace = m_promptOnReplace->isChecked();
+ return options;
+}
+
+#include "kexifinddialog.moc"
diff --git a/kexi/main/kexifinddialog.h b/kexi/main/kexifinddialog.h
new file mode 100644
index 000000000..ea7777e06
--- /dev/null
+++ b/kexi/main/kexifinddialog.h
@@ -0,0 +1,130 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFINDDIALOG_H
+#define KEXIFINDDIALOG_H
+
+#include "kexifinddialogbase.h"
+#include <core/kexisearchandreplaceiface.h>
+
+class KAction;
+
+//! @short A Kexi-specific "Find text" dialog.
+/*! Also used for as replace dialog.
+
+ @todo replace m_textToFind and m_textToReplace KComboBoxes with Kexi's db-aware comboboxes,
+ so we ca adapt to datatype being searched, e.g. date, time and numbers
+*/
+class KexiFindDialog : public KexiFindDialogBase
+{
+ Q_OBJECT
+ public:
+ //! Creates a new find dialog. Replace mode is off by default.
+ KexiFindDialog(QWidget* parent);
+ virtual ~KexiFindDialog();
+
+ //! Sets actions that receive button clicks and shortcuts within the dialog. Should be called once.
+ void setActions( KAction *findnext, KAction *findprev,
+ KAction *replace, KAction *replaceall );
+
+ //! Shows the dialog as a modal dialog.
+ virtual void show();
+
+ //! \return current find and replace options set within the dialog
+//! @todo should we have setOptions() too?
+ KexiSearchAndReplaceViewInterface::Options options() const;
+
+ /*! \return a list of column names for 'look in column' combo box.
+ Neither "(All fields)" nor "(Current field)" items are prepended. */
+ QStringList lookInColumnNames() const;
+
+ /*! \return a list of column captions (i.e. visible values) for 'look in column' combo box.
+ Neither "(All fields)" nor "(Current field)" items are prepended. */
+ QStringList lookInColumnCaptions() const;
+
+ /*! \return column name selected in "look in column" combo box.
+ If "(All fields)" item is selected, empty string is returned.
+ If "(Current field)" item is selected, "(field)" string is returned. */
+ QString currentLookInColumnName() const;
+
+ //! \return value that to be used for searching
+ QVariant valueToFind() const;
+
+ //! \return value that to be used as a replacement
+ QVariant valueToReplaceWith() const;
+
+ public slots:
+ /*! Sets \a columnNames list and \a columnCaptions for 'look in column' combo box.
+ \a columnCaptions are visible values, while \a columnNames are used for returning
+ in currentLookInColumn().
+ "(All fields)" and "(Current field)" items are also prepended. */
+ void setLookInColumnList(const QStringList& columnNames,
+ const QStringList& columnCaptions);
+
+ /*! Selects \a columnName to be selected 'look in column'.
+ By default "(All fields)" item is selected. To select this item,
+ pass empty string as \a columnName.
+ To select "(Current field)" item, "(field)" string should be passed
+ as \a columnName. */
+ void setCurrentLookInColumnName(const QString& columnName);
+
+ /*! Sets or clears replace mode.
+ For replace mode 'prompt or replace' option is visible. */
+ void setReplaceMode(bool set);
+
+ /*! Sets object name for caption, so for example it will be set
+ to i18n("Find \"Persons\"")). */
+ void setObjectNameForCaption(const QString& name);
+
+ /*! Enables of disables the find/replace/replace all buttons.
+ This is used if for the current context the dialog could not be used.
+ If \a enable is false, object name for caption is cleared
+ using setObjectNameForCaption() too. */
+ void setButtonsEnabled(bool enable);
+
+ /*! Sets message at the bottom to \a message. */
+ void setMessage(const QString& message);
+
+ /*! Updates message at the bottom; "The search item was not found" is set if \a found is true,
+ else the message is cleared. */
+//! @todo add "Search again" hyperlink
+ void updateMessage( bool found = true );
+
+ signals:
+ //! Emitted after clicking "Find next" button or pressing appropriate shortcut set by setActions()
+ void findNext();
+
+ //! Emitted after pressing appropriate shortcut set by setActions()
+ void findPrevious();
+
+ //! Emitted after clicking "Replace" button or pressing appropriate shortcut set by setActions()
+ void replaceNext();
+
+ //! Emitted after clicking "Replace All" button or pressing appropriate shortcut set by setActions()
+ void replaceAll();
+
+ protected slots:
+ void slotCloseClicked();
+
+ protected:
+ class Private;
+ Private * const d;
+};
+
+#endif
diff --git a/kexi/main/kexifinddialogbase.ui b/kexi/main/kexifinddialogbase.ui
new file mode 100644
index 000000000..3ba82bb50
--- /dev/null
+++ b/kexi/main/kexifinddialogbase.ui
@@ -0,0 +1,357 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiFindDialogBase</class>
+<widget class="QDialog">
+ <property name="name">
+ <cstring>KexiFindDialogBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>472</width>
+ <height>247</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Find</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Fi&amp;nd:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_textToFind</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>m_replaceLbl</cstring>
+ </property>
+ <property name="text">
+ <string>Re&amp;place with:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_textToReplace</cstring>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="2" column="1">
+ <property name="name">
+ <cstring>m_lookIn</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>StrongFocus</enum>
+ </property>
+ <property name="insertionPolicy">
+ <enum>NoInsertion</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="0">
+ <property name="name">
+ <cstring>textLabel2_2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Look in:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_lookIn</cstring>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="5" column="2">
+ <property name="name">
+ <cstring>m_wholeWords</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="text">
+ <string>&amp;Whole words only</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="6" column="2">
+ <property name="name">
+ <cstring>m_promptOnReplace</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="text">
+ <string>Prompt on replace</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget" row="0" column="4" rowspan="7" colspan="1">
+ <property name="name">
+ <cstring>layout_btn</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>m_btnFind</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ <property name="autoDefault">
+ <bool>true</bool>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>m_btnClose</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>m_btnReplace</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Replace</string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>m_btnReplaceAll</cstring>
+ </property>
+ <property name="text">
+ <string>Replace All</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer8</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </vbox>
+ </widget>
+ <widget class="QLabel" row="4" column="0">
+ <property name="name">
+ <cstring>textLabel2_2_3</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Match:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_match</cstring>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="4" column="1">
+ <item>
+ <property name="text">
+ <string>Any Part of Field</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Whole Field</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Start of Field</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>m_match</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>StrongFocus</enum>
+ </property>
+ <property name="insertionPolicy">
+ <enum>NoInsertion</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="3" column="0">
+ <property name="name">
+ <cstring>textLabel2_2_2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Search:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_search</cstring>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="3" column="1">
+ <item>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Down</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>All Rows</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>m_search</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>StrongFocus</enum>
+ </property>
+ <property name="insertionPolicy">
+ <enum>NoInsertion</enum>
+ </property>
+ </widget>
+ <spacer row="7" column="2">
+ <property name="name">
+ <cstring>spacer3</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>2</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QCheckBox" row="4" column="2">
+ <property name="name">
+ <cstring>m_caseSensitive</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="text">
+ <string>C&amp;ase sensitive</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="8" column="0" rowspan="1" colspan="5">
+ <property name="name">
+ <cstring>m_messageLabel</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <spacer row="4" column="3">
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>70</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KHistoryCombo" row="0" column="1" rowspan="1" colspan="3">
+ <property name="name">
+ <cstring>m_textToFind</cstring>
+ </property>
+ <property name="insertionPolicy">
+ <enum>AtTop</enum>
+ </property>
+ <property name="autoCompletion">
+ <bool>true</bool>
+ </property>
+ <property name="duplicatesEnabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="KHistoryCombo" row="1" column="1" rowspan="1" colspan="3">
+ <property name="name">
+ <cstring>m_textToReplace</cstring>
+ </property>
+ <property name="insertionPolicy">
+ <enum>AtTop</enum>
+ </property>
+ <property name="autoCompletion">
+ <bool>true</bool>
+ </property>
+ <property name="duplicatesEnabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<connections>
+ <connection>
+ <sender>m_btnClose</sender>
+ <signal>clicked()</signal>
+ <receiver>KexiFindDialogBase</receiver>
+ <slot>reject()</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>m_textToFind</tabstop>
+ <tabstop>m_textToReplace</tabstop>
+ <tabstop>m_btnFind</tabstop>
+ <tabstop>m_btnClose</tabstop>
+ <tabstop>m_btnReplace</tabstop>
+ <tabstop>m_btnReplaceAll</tabstop>
+ <tabstop>m_lookIn</tabstop>
+ <tabstop>m_search</tabstop>
+ <tabstop>m_match</tabstop>
+ <tabstop>m_caseSensitive</tabstop>
+ <tabstop>m_wholeWords</tabstop>
+ <tabstop>m_promptOnReplace</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>kcombobox.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>klineedit.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/main/keximainwindowimpl.cpp b/kexi/main/keximainwindowimpl.cpp
new file mode 100644
index 000000000..d5ce9939c
--- /dev/null
+++ b/kexi/main/keximainwindowimpl.cpp
@@ -0,0 +1,4641 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "keximainwindowimpl.h"
+
+#include <unistd.h>
+
+#include <qapplication.h>
+#include <qeventloop.h>
+#include <qfile.h>
+#include <qtimer.h>
+#include <qobjectlist.h>
+#include <qprocess.h>
+#include <qtoolbutton.h>
+#include <qtooltip.h>
+#include <qmutex.h>
+#include <qwaitcondition.h>
+#include <qfiledialog.h>
+#include <qdockwindow.h>
+#include <qdockarea.h>
+
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <kaction.h>
+#include <klocale.h>
+#include <kstdaccel.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kdebug.h>
+#include <kkeydialog.h>
+#include <kedittoolbar.h>
+#include <kdeversion.h>
+#include <kglobalsettings.h>
+#include <kparts/componentfactory.h>
+#include <ktip.h>
+#include <kstandarddirs.h>
+#include <kpushbutton.h>
+#include <ktextbrowser.h>
+#include <kiconloader.h>
+#include <ktabwidget.h>
+#include <kimageio.h>
+#include <khelpmenu.h>
+#include <kfiledialog.h>
+#include <krecentdocument.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+#include <kexidb/cursor.h>
+#include <kexidb/dbobjectnamevalidator.h>
+#include <kexidb/admin.h>
+#include <kexiutils/utils.h>
+
+//#include "projectsettingsui.h"
+#include "kexiactionproxy.h"
+#include "kexidialogbase.h"
+#include "kexipartmanager.h"
+#include "kexipart.h"
+#include "kexipartinfo.h"
+#include "kexipartguiclient.h"
+#include "kexiproject.h"
+#include "kexiprojectdata.h"
+#include "kexiprojectset.h"
+#include "kexi.h"
+#include "kexistatusbar.h"
+#include "kexiinternalpart.h"
+#include "kexiactioncategories.h"
+#include "kexifinddialog.h"
+#include "kexisearchandreplaceiface.h"
+
+#include "kde2_closebutton.xpm"
+
+#include <widget/kexibrowser.h>
+#include <widget/kexipropertyeditorview.h>
+#include <widget/utils/kexirecordnavigator.h>
+#include <koproperty/editor.h>
+#include <koproperty/set.h>
+
+#include "startup/KexiStartup.h"
+#include "startup/KexiNewProjectWizard.h"
+#include "startup/KexiStartupDialog.h"
+#include "startup/KexiStartupFileDialog.h"
+#include "kexinamedialog.h"
+#include "printing/kexisimpleprintingpart.h"
+#include "printing/kexisimpleprintingpagesetup.h"
+
+//Extreme verbose debug
+#if defined(Q_WS_WIN)
+# include <krecentdirs.h>
+# include <win32_utils.h>
+//# define KexiVDebug kdDebug()
+#endif
+
+#if !defined(KexiVDebug)
+# define KexiVDebug if (0) kdDebug()
+#endif
+
+//first fix the geometry
+//#define KEXI_NO_CTXT_HELP 1
+
+#ifndef KEXI_NO_CTXT_HELP
+#include <kexicontexthelp.h>
+#endif
+
+#ifdef HAVE_KNEWSTUFF
+#include <knewstuff/downloaddialog.h>
+#include "kexinewstuff.h"
+#endif
+
+//! @todo REENABLE when blinking and dock
+//! width changes will be removed in KMDI
+//#define PROPEDITOR_VISIBILITY_CHANGES
+
+//temporary fix to manage layout
+#include "ksplitter.h"
+#define KDOCKWIDGET_P 1
+
+#ifndef KEXI_NO_FEEDBACK_AGENT
+#ifdef FEEDBACK_INCLUDE
+#include FEEDBACK_INCLUDE
+#endif
+#include <kapplication.h>
+#include <kaboutdata.h>
+#endif
+
+#include "keximainwindowimpl_p.h"
+
+//-------------------------------------------------
+
+//static
+int KexiMainWindowImpl::create(int argc, char *argv[], KAboutData* aboutdata)
+{
+ Kexi::initCmdLineArgs( argc, argv, aboutdata );
+
+ bool GUIenabled = true;
+ QWidget *dummyWidget = 0; //needed to have icon for dialogs before KexiMainWindowImpl is created
+//! @todo switch GUIenabled off when needed
+ KApplication* app = new KApplication(true, GUIenabled);
+
+#ifdef KEXI_STANDALONE
+ KGlobal::locale()->removeCatalogue("kexi");
+ KGlobal::locale()->insertCatalogue("standalone_kexi");
+#endif
+ KGlobal::locale()->insertCatalogue("koffice");
+ KGlobal::locale()->insertCatalogue("koproperty");
+
+#ifdef CUSTOM_VERSION
+# include "custom_exec.h"
+#endif
+
+#ifdef KEXI_DEBUG_GUI
+ QWidget* debugWindow = 0;
+#endif
+ if (GUIenabled) {
+ dummyWidget = new QWidget();
+ dummyWidget->setIcon( DesktopIcon( "kexi" ) );
+ app->setMainWidget(dummyWidget);
+#ifdef KEXI_DEBUG_GUI
+ app->config()->setGroup("General");
+ if (app->config()->readBoolEntry("showInternalDebugger", false)) {
+ debugWindow = KexiUtils::createDebugWindow(0);
+ }
+#endif
+ }
+
+ tristate res = Kexi::startupHandler().init(argc, argv);
+ if (!res || ~res) {
+#ifdef KEXI_DEBUG_GUI
+ delete debugWindow;
+#endif
+ delete app;
+ return (~res) ? 0 : 1;
+ }
+
+ kdDebug() << "startupActions OK" <<endl;
+
+ /* Exit requested, e.g. after database removing. */
+ if (Kexi::startupHandler().action() == KexiStartupData::Exit) {
+#ifdef KEXI_DEBUG_GUI
+ delete debugWindow;
+#endif
+ delete app;
+ return 0;
+ }
+
+ KexiMainWindowImpl *win = new KexiMainWindowImpl();
+ app->setMainWidget(win);
+#ifdef KEXI_DEBUG_GUI
+ //if (debugWindow)
+ //debugWindow->reparent(win, QPoint(1,1));
+#endif
+ delete dummyWidget;
+
+ if (true != win->startup()) {
+ delete win;
+ delete app;
+ return 1;
+ }
+
+ win->show();
+ app->processEvents();//allow refresh our app
+
+//#ifdef KEXI_DEBUG_GUI
+// delete debugWindow;
+//#endif
+ return 0;
+}
+
+//-------------------------------------------------
+
+KexiMainWindowImpl::KexiMainWindowImpl()
+ : KexiMainWindow()
+ , KexiGUIMessageHandler(this)
+ , d(new KexiMainWindowImpl::Private(this) )
+{
+ KImageIO::registerFormats();
+ KexiProjectData *pdata = Kexi::startupHandler().projectData();
+ d->userMode = Kexi::startupHandler().forcedUserMode() /* <-- simply forced the user mode */
+ /* project has 'user mode' set as default and not 'design mode' override is found: */
+ || (pdata && pdata->userMode() && !Kexi::startupHandler().forcedDesignMode());
+ d->isProjectNavigatorVisible = Kexi::startupHandler().isProjectNavigatorVisible();
+
+ if(userMode())
+ kdDebug() << "KexiMainWindowImpl::KexiMainWindowImpl(): starting up in the User Mode" << endl;
+
+ d->config = kapp->config();
+
+ if ( !initialGeometrySet() ) {
+ int scnum = QApplication::desktop()->screenNumber(parentWidget());
+ QRect desk = QApplication::desktop()->screenGeometry(scnum);
+ d->config->setGroup("MainWindow");
+ QSize s ( d->config->readNumEntry( QString::fromLatin1("Width %1").arg(desk.width()), 700 ),
+ d->config->readNumEntry( QString::fromLatin1("Height %1").arg(desk.height()), 480 ) );
+ resize (kMin (s.width(), desk.width()), kMin(s.height(), desk.height()));
+ }
+
+ setManagedDockPositionModeEnabled(true);//TODO(js): remove this if will be default in kmdi :)
+ manager()->setSplitterHighResolution(true);
+ manager()->setSplitterKeepSize(true);
+ setStandardMDIMenuEnabled(false);
+ setAsDefaultHost(); //this is default host now.
+ KGlobal::iconLoader()->addAppDir("kexi");
+ KGlobal::iconLoader()->addAppDir("koffice");
+
+ //get informed
+ connect(&Kexi::partManager(),SIGNAL(partLoaded(KexiPart::Part*)),this,SLOT(slotPartLoaded(KexiPart::Part*)));
+ connect( m_pMdi, SIGNAL(nowMaximized(bool)), this, SLOT(slotCaptionForCurrentMDIChild(bool)) );
+ connect( m_pMdi, SIGNAL(noMaximizedChildFrmLeft(KMdiChildFrm*)), this, SLOT(slotNoMaximizedChildFrmLeft(KMdiChildFrm*)));
+// connect( this, SIGNAL(lastChildFrmClosed()), this, SLOT(slotLastChildFrmClosed()));
+ connect( this, SIGNAL(lastChildViewClosed()), this, SLOT(slotLastChildViewClosed()));
+
+ connect( this, SIGNAL(childViewIsDetachedNow(QWidget*)), this, SLOT(slotChildViewIsDetachedNow(QWidget*)));
+ connect( this, SIGNAL(mdiModeHasBeenChangedTo(KMdi::MdiMode)),
+ this, SLOT(slotMdiModeHasBeenChangedTo(KMdi::MdiMode)));
+
+
+ //if (!userMode()) {
+ setXMLFile("kexiui.rc");
+ setAcceptDrops(true);
+ initActions();
+ createShellGUI(true);
+ //}
+
+ d->statusBar = new KexiStatusBar(this, "status_bar");
+
+ d->origAppCaption = caption();
+
+ restoreSettings();
+ (void)Kexi::smallFont(this/*init*/);
+
+ if (!userMode()) {
+ initContextHelp();
+ initPropertyEditor();
+ }
+
+ {//store menu popups list
+ QObjectList *l = queryList( "QPopupMenu" );
+ for (QObjectListIt it( *l ); it.current(); ++it ) {
+ //kdDebug() << "name=" <<it.current()->name() << " cname="<<it.current()->className()<<endl;
+ //KexiMainWindowImpl::eventFilter() will filter our popups:
+ it.current()->installEventFilter(this);
+ d->popups.insert(it.current()->name(), static_cast<QPopupMenu*>(it.current()));
+ }
+ delete l;
+ d->createMenu = d->popups["create"];
+
+#ifdef KEXI_NO_REPORTBUG_COMMAND
+ //remove "bug report" action to avoid confusion for supported with commercial technical support
+ QPopupMenu *helpMenu = d->popups["help"];
+ if (helpMenu) {
+ //const int idx = helpMenu->indexOf( (int)KHelpMenu::menuReportBug );
+ helpMenu->removeItemAt(int(KHelpMenu::menuReportBug)-1);
+ helpMenu->removeItemAt(int(KHelpMenu::menuReportBug)-1); //separator
+ }
+#endif
+ }
+
+ //fix menus a bit more:
+#ifndef KEXI_SHOW_UNIMPLEMENTED
+//disabled (possible crash) d->hideMenuItem("file", i18n("&Import"), true);
+//disabled (possible crash) d->hideMenuItem("help", i18n( "&Report Bug..." ), true);
+#endif
+ KAction *kmdi_tooldock_menu_action = childClients()->getFirst() ? childClients()->getFirst()->actionCollection()->action("kmdi_tooldock_menu") : 0;
+ if (kmdi_tooldock_menu_action) {
+ kmdi_tooldock_menu_action->setEnabled(false);
+ }
+
+ if (!isFakingSDIApplication()/* && !userMode()*/) {
+// QPopupMenu *menu = (QPopupMenu*) child( "window", "KPopupMenu" );
+ QPopupMenu *menu = d->popups["window"];
+ unsigned int count = menuBar()->count();
+ if (menu)
+ setWindowMenu(menu);
+ else
+ menuBar()->insertItem( i18n("&Window"), windowMenu(), -1, count-2); // standard position is left to the last ('Help')
+ }
+ if (userMode()) {
+ //hide "insert" menu and disable "project_import", "edit_paste_special" menus
+ QPopupMenu *menu = d->popups["insert"];
+ if (menu) {
+ for (uint i=0; i < menuBar()->count(); i++) {
+ if (menuBar()->text( menuBar()->idAt(i) ) == i18n("&Insert")) {
+ menuBar()->setItemVisible( menuBar()->idAt(i), false );
+ break;
+ }
+ }
+ }
+ d->disableMenuItem("file", i18n("&Import"));
+ d->disableMenuItem("edit", i18n("Paste &Special"));
+ }
+
+ m_pTaskBar->setCaption(i18n("Task Bar")); //js TODO: move this to KMDIlib
+
+// if (!userMode()) {
+ invalidateActions();
+ d->timer.singleShot(0,this,SLOT(slotLastActions()));
+// }
+
+ setTabWidgetVisibility(KMdi::AlwaysShowTabs);
+ if (mdiMode()==KMdi::IDEAlMode) {
+ d->config->setGroup("MainWindow");
+ tabWidget()->setHoverCloseButton(d->config->readBoolEntry("HoverCloseButtonForTabs", false));
+ //create special close button as corner widget for IDEAl mode
+ QToolButton *closeButton = new QToolButton( tabWidget() );
+ closeButton->setAutoRaise( true );
+ closeButton->setPixmap( QPixmap( kde2_closebutton ) );
+ closeButton->setPaletteBackgroundColor(closeButton->palette().active().background());
+// closeButton->setIconSet(SmallIconSet("tab_remove"));
+ tabWidget()->setCornerWidget( closeButton, Qt::TopRight );
+ closeButton->hide(); // hide until it's needed to avoid problems in "user mode"
+ // when initially the main window is empty
+ QToolTip::add(closeButton,
+ i18n("Close the current tab page in Kexi tab interface", "Close the current tab"));
+ QObject::connect( closeButton, SIGNAL( clicked() ), this, SLOT( closeActiveView() ) );
+ }
+
+#ifdef KEXI_ADD_CUSTOM_KEXIMAINWINDOWIMPL
+# include "keximainwindowimpl_ctor.h"
+#endif
+}
+
+KexiMainWindowImpl::~KexiMainWindowImpl()
+{
+ d->forceDialogClosing=true;
+ closeProject();
+ delete d;
+}
+
+KexiProject *KexiMainWindowImpl::project()
+{
+ return d->prj;
+}
+
+void KexiMainWindowImpl::setWindowMenu(QPopupMenu *menu)
+{
+ delete m_pWindowMenu;
+ m_pWindowMenu = menu;
+ int count = menuBar()->count();
+ //try to move "window" menu just before "Settings" menu (count-3)
+ const QString txt = i18n("&Window");
+ int i;
+ for (i=0; i<count; i++) {
+ //kdDebug() << menuBar()->text( menuBar()->idAt(i) ) << endl;
+ if (txt==menuBar()->text( menuBar()->idAt(i) ))
+ break;
+ }
+ if (i<count) {
+ const int id = menuBar()->idAt(i);
+ menuBar()->removeItemAt(i);
+ menuBar()->insertItem(txt, m_pWindowMenu, id, count-3);
+ }
+ m_pWindowMenu->setCheckable(true);
+ QObject::connect( m_pWindowMenu, SIGNAL(aboutToShow()), this, SLOT(fillWindowMenu()) );
+}
+
+void KexiMainWindowImpl::fillWindowMenu()
+{
+ KexiMainWindow::fillWindowMenu();
+
+/* int i;
+ for (i=0; i < (int)m_pWindowMenu->count(); i++) {
+ if (m_pWindowMenu->text( m_pWindowMenu->idAt( i ) ) == i18n( "&MDI Mode" )) {
+// kdDebug() << m_pWindowMenu->text( m_pWindowMenu->idAt( i ) ) << endl;
+ m_pWindowMenu->removeItem( m_pWindowMenu->idAt( i ) );
+ break;
+ }
+ }*/
+
+ m_pMdiModeMenu->removeItem( m_pMdiModeMenu->idAt( 0 ) ); //hide toplevel mode
+ m_pMdiModeMenu->removeItem( m_pMdiModeMenu->idAt( 1 ) ); //hide tabbed mode
+ //update
+ if (d->mdiModeToSwitchAfterRestart != (KMdi::MdiMode)0) {
+ m_pMdiModeMenu->setItemChecked( m_pMdiModeMenu->idAt( 0 ),
+ d->mdiModeToSwitchAfterRestart == KMdi::ChildframeMode );
+ m_pMdiModeMenu->setItemChecked( m_pMdiModeMenu->idAt( 1 ),
+ d->mdiModeToSwitchAfterRestart == KMdi::IDEAlMode );
+ }
+
+ //insert window_next, window_previous actions:
+// const QString t = i18n("&Dock/Undock...");
+ int i = m_pWindowMenu->count()-1;
+ for (int index;; i--) {
+ index = m_pWindowMenu->idAt(i);
+ if (index==-1 || m_pWindowMenu->text(index).isNull())
+ break;
+ }
+ i++;
+ d->action_window_next->plug( m_pWindowMenu, i++ );
+ d->action_window_previous->plug( m_pWindowMenu, i++ );
+ if (!m_pDocumentViews->isEmpty())
+ m_pWindowMenu->insertSeparator( i++ );
+}
+
+void KexiMainWindowImpl::switchToIDEAlMode()
+{
+ switchToIDEAlMode(true);
+}
+
+void KexiMainWindowImpl::switchToIDEAlMode(bool showMessage)
+{
+ if (showMessage) {
+ if ((int)d->mdiModeToSwitchAfterRestart == 0 && mdiMode()==KMdi::IDEAlMode)
+ return;
+ if (d->mdiModeToSwitchAfterRestart == KMdi::IDEAlMode)
+ return;
+ if (mdiMode()==KMdi::IDEAlMode) {//current mode
+ d->mdiModeToSwitchAfterRestart = (KMdi::MdiMode)0;
+ }
+ else {
+ KMessageBox::information(this,
+ i18n("User interface mode will be switched to IDEAl at next %1 application startup.")
+ .arg(KEXI_APP_NAME));
+ //delayed
+ d->mdiModeToSwitchAfterRestart = KMdi::IDEAlMode;
+ }
+ }
+ else
+ KexiMainWindow::switchToIDEAlMode();
+}
+
+void KexiMainWindowImpl::switchToChildframeMode()
+{
+ switchToChildframeMode(true);
+}
+
+void KexiMainWindowImpl::switchToChildframeMode(bool showMessage)
+{
+ if (showMessage) {
+ if ((int)d->mdiModeToSwitchAfterRestart == 0 && mdiMode()==KMdi::ChildframeMode)
+ return;
+ if (d->mdiModeToSwitchAfterRestart == KMdi::ChildframeMode)
+ return;
+ if (mdiMode()==KMdi::ChildframeMode) {//current mode
+ d->mdiModeToSwitchAfterRestart = (KMdi::MdiMode)0;
+ }
+ else {
+ KMessageBox::information(this,
+ i18n("User interface mode will be switched to Childframe at next %1 application startup.")
+ .arg(KEXI_APP_NAME));
+ //delayed
+ d->mdiModeToSwitchAfterRestart = KMdi::ChildframeMode;
+ }
+ }
+ else
+ KexiMainWindow::switchToChildframeMode();
+}
+
+QPopupMenu* KexiMainWindowImpl::findPopupMenu(const char *popupName)
+{
+ return d->popups[popupName];
+}
+
+KActionPtrList KexiMainWindowImpl::allActions() const
+{
+ return actionCollection()->actions();
+}
+
+KexiDialogBase* KexiMainWindowImpl::currentDialog() const
+{
+ return d->curDialog;
+}
+
+void KexiMainWindowImpl::initActions()
+{
+// setupGUI(KMainWindow::Keys|KMainWindow::StatusBar|KMainWindow::Save|KMainWindow::Create);
+
+// d->actionMapper = new QSignalMapper(this, "act_map");
+// connect(d->actionMapper, SIGNAL(mapped(const QString &)), this, SLOT(slotAction(const QString &)));
+
+ // PROJECT MENU
+ KAction *action = new KAction(i18n("&New..."), "filenew", KStdAccel::shortcut(KStdAccel::New),
+ this, SLOT(slotProjectNew()), actionCollection(), "project_new");
+ action->setToolTip(i18n("Create a new project"));
+ action->setWhatsThis(i18n("Creates a new project. Currently opened project is not affected."));
+
+ action = KStdAction::open( this, SLOT( slotProjectOpen() ), actionCollection(), "project_open" );
+ action->setToolTip(i18n("Open an existing project"));
+ action->setWhatsThis(i18n("Opens an existing project. Currently opened project is not affected."));
+
+#ifdef HAVE_KNEWSTUFF
+ action = new KAction(i18n("&Download Example Databases..."), "kget", KShortcut(0),
+ this, SLOT(slotGetNewStuff()), actionCollection(), "project_download_examples");
+ action->setToolTip(i18n("Download example databases from the Internet"));
+ action->setWhatsThis(i18n("Downloads example databases from the Internet."));
+#endif
+
+// d->action_open_recent = KStdAction::openRecent( this, SLOT(slotProjectOpenRecent(const KURL&)), actionCollection(), "project_open_recent" );
+
+//#ifdef KEXI_SHOW_UNIMPLEMENTED
+#ifndef KEXI_NO_UNFINISHED
+ d->action_open_recent = new KActionMenu(i18n("Open Recent"),
+ actionCollection(), "project_open_recent");
+ connect(d->action_open_recent->popupMenu(),SIGNAL(activated(int)),
+ this,SLOT(slotProjectOpenRecent(int)));
+ connect(d->action_open_recent->popupMenu(), SIGNAL(aboutToShow()),
+ this,SLOT(slotProjectOpenRecentAboutToShow()));
+//moved down d->action_open_recent_projects_title_id =
+// d->action_open_recent->popupMenu()->insertTitle(i18n("Recently Opened Databases"));
+//moved down d->action_open_recent_connections_title_id =
+// d->action_open_recent->popupMenu()->insertTitle(i18n("Recently Connected Database Servers"));
+// d->action_open_recent->popupMenu()->insertSeparator();
+// d->action_open_recent_more_id = d->action_open_recent->popupMenu()
+// ->insertItem(i18n("&More Projects..."), this, SLOT(slotProjectOpenRecentMore()), 0, 1000);
+#else
+ d->action_open_recent = d->dummy_action;
+#endif
+
+ d->action_save = KStdAction::save(
+ this, SLOT( slotProjectSave() ), actionCollection(), "project_save" );
+// d->action_save = new KAction(i18n("&Save"), "filesave", KStdAccel::shortcut(KStdAccel::Save),
+// this, SLOT(slotProjectSave()), actionCollection(), "project_save");
+ d->action_save->setToolTip(i18n("Save object changes"));
+ d->action_save->setWhatsThis(i18n("Saves object changes from currently selected window."));
+
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+ d->action_save_as = new KAction(i18n("Save &As..."), "filesaveas", 0,
+ this, SLOT(slotProjectSaveAs()), actionCollection(), "project_saveas");
+ d->action_save_as->setToolTip(i18n("Save object as"));
+ d->action_save_as->setWhatsThis(
+ i18n("Saves object changes from currently selected window under a new name (within the same project)."));
+
+ d->action_project_properties = new KAction(i18n("Project Properties"), "info", 0,
+ this, SLOT(slotProjectProperties()), actionCollection(), "project_properties");
+#else
+ d->action_save_as = d->dummy_action;
+ d->action_project_properties = d->dummy_action;
+#endif
+
+ d->action_close = new KAction(i18n("&Close Project"), "fileclose", 0,
+ this, SLOT(slotProjectClose()), actionCollection(), "project_close" );
+ d->action_close->setToolTip(i18n("Close the current project"));
+ d->action_close->setWhatsThis(i18n("Closes the current project."));
+
+ KStdAction::quit( this, SLOT(slotProjectQuit()), actionCollection(), "quit");
+
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+ d->action_project_relations = new KAction(i18n("&Relationships..."), "relation", Qt::CTRL + Qt::Key_R,
+ this, SLOT(slotProjectRelations()), actionCollection(), "project_relations");
+ d->action_project_relations->setToolTip(i18n("Project relationships"));
+ d->action_project_relations->setWhatsThis(i18n("Shows project relationships."));
+
+#else
+ d->action_project_relations = d->dummy_action;
+#endif
+ d->action_tools_data_migration = new KAction(
+ i18n("&Import Database..."), "database_import", 0,
+ this, SLOT(slotToolsProjectMigration()), actionCollection(), "tools_import_project");
+ d->action_tools_data_migration->setToolTip(i18n("Import entire database as a Kexi project"));
+ d->action_tools_data_migration->setWhatsThis(i18n("Imports entire database as a Kexi project."));
+
+ d->action_tools_compact_database = new KAction(
+ i18n("&Compact Database..."), "", 0,
+ this, SLOT(slotToolsCompactDatabase()), actionCollection(), "tools_compact_database");
+ d->action_tools_compact_database->setToolTip(i18n("Compact the current database project"));
+ d->action_tools_compact_database->setWhatsThis(
+ i18n("Compacts the current database project, so it will take less space and work faster."));
+
+ if (userMode())
+ d->action_project_import_data_table = 0;
+ else {
+ d->action_project_import_data_table = new KAction(
+ i18n("Import->Table Data From File...", "Table Data From &File..."),
+ "table"/*! @todo: change to "file_import" or so*/,
+ 0, this, SLOT(slotProjectImportDataTable()), actionCollection(),
+ "project_import_data_table");
+ d->action_project_import_data_table->setToolTip(i18n("Import table data from a file"));
+ d->action_project_import_data_table->setWhatsThis(i18n("Imports table data from a file."));
+ }
+
+ d->action_project_export_data_table = new KAction(i18n("Export->Table or Query Data to File...",
+ "Table or Query Data to &File..."),
+ "table"/*! @todo: change to "file_export" or so*/,
+ 0, this, SLOT(slotProjectExportDataTable()), actionCollection(),
+ "project_export_data_table");
+ d->action_project_export_data_table->setToolTip(
+ i18n("Export data from the active table or query data to a file"));
+ d->action_project_export_data_table->setWhatsThis(
+ i18n("Exports data from the active table or query data to a file."));
+
+//TODO new KAction(i18n("From File..."), "fileopen", 0,
+//TODO this, SLOT(slotImportFile()), actionCollection(), "project_import_file");
+//TODO new KAction(i18n("From Server..."), "server", 0,
+//TODO this, SLOT(slotImportServer()), actionCollection(), "project_import_server");
+
+ d->action_project_print = KStdAction::print(this, SLOT(slotProjectPrint()),
+ actionCollection(), "project_print" );
+ d->action_project_print->setToolTip(i18n("Print data from the active table or query"));
+ d->action_project_print->setWhatsThis(i18n("Prints data from the active table or query."));
+
+ d->action_project_print_preview = KStdAction::printPreview(
+ this, SLOT(slotProjectPrintPreview()),
+ actionCollection(), "project_print_preview" );
+ d->action_project_print_preview->setToolTip(
+ i18n("Show print preview for the active table or query"));
+ d->action_project_print_preview->setWhatsThis(
+ i18n("Shows print preview for the active table or query."));
+
+ d->action_project_print_setup = new KAction(i18n("Page Set&up..."),
+ "", 0, this, SLOT(slotProjectPageSetup()), actionCollection(),
+ "project_print_setup");
+ d->action_project_print_setup->setToolTip(
+ i18n("Show page setup for printing the active table or query"));
+ d->action_project_print_setup->setWhatsThis(
+ i18n("Shows page setup for printing the active table or query."));
+
+ //EDIT MENU
+ d->action_edit_cut = createSharedAction( KStdAction::Cut, "edit_cut");
+ d->action_edit_copy = createSharedAction( KStdAction::Copy, "edit_copy");
+ d->action_edit_paste = createSharedAction( KStdAction::Paste, "edit_paste");
+
+ if (userMode())
+ d->action_edit_paste_special_data_table = 0;
+ else {
+ d->action_edit_paste_special_data_table =
+ new KAction(i18n("Paste Special->As Data &Table...", "As Data &Table..."),
+ "table", 0, this, SLOT(slotEditPasteSpecialDataTable()),
+ actionCollection(), "edit_paste_special_data_table");
+ d->action_edit_paste_special_data_table->setToolTip(
+ i18n("Paste clipboard data as a table"));
+ d->action_edit_paste_special_data_table->setWhatsThis(
+ i18n("Pastes clipboard data to a table."));
+ }
+
+ d->action_edit_copy_special_data_table =
+ new KAction(i18n("Copy Special->Table or Query Data...",
+ "Table or Query as Data Table..."),
+ "table", 0, this, SLOT(slotEditCopySpecialDataTable()),
+ actionCollection(), "edit_copy_special_data_table");
+ d->action_edit_copy_special_data_table->setToolTip(
+ i18n("Copy selected table or query data to clipboard"));
+ d->action_edit_copy_special_data_table->setWhatsThis(
+ i18n("Copies selected table or query data to clipboard."));
+
+ d->action_edit_undo = createSharedAction( KStdAction::Undo, "edit_undo");
+ d->action_edit_undo->setWhatsThis(i18n("Reverts the most recent editing action."));
+ d->action_edit_redo = createSharedAction( KStdAction::Redo, "edit_redo");
+ d->action_edit_redo->setWhatsThis(i18n("Reverts the most recent undo action."));
+
+#if 0 //old
+ d->action_edit_find = createSharedAction( KStdAction::Find, "edit_find");
+ d->action_edit_findnext = createSharedAction( KStdAction::FindNext, "edit_findnext");
+ d->action_edit_findprev = createSharedAction( KStdAction::FindPrev, "edit_findprevious");
+//! @todo d->action_edit_paste = createSharedAction( KStdAction::Replace, "edit_replace");
+#endif
+
+ d->action_edit_find = KStdAction::find(
+ this, SLOT(slotEditFind()), actionCollection(), "edit_find" );
+// d->action_edit_find = createSharedAction( KStdAction::Find, "edit_find");
+ d->action_edit_findnext = KStdAction::findNext(
+ this, SLOT(slotEditFindNext()), actionCollection(), "edit_findnext");
+ d->action_edit_findprev = KStdAction::findPrev(
+ this, SLOT(slotEditFindPrevious()), actionCollection(), "edit_findprevious");
+ d->action_edit_replace = 0;
+//! @todo d->action_edit_replace = KStdAction::replace(
+//! this, SLOT(slotEditReplace()), actionCollection(), "project_print_preview" );
+ d->action_edit_replace_all = 0;
+//! @todo d->action_edit_replace_all = new KAction( i18n("Replace All"), "", 0,
+//! this, SLOT(slotEditReplaceAll()), actionCollection(), "edit_replaceall");
+
+ d->action_edit_select_all = createSharedAction( KStdAction::SelectAll, "edit_select_all");
+
+ d->action_edit_delete = createSharedAction(i18n("&Delete"), "editdelete",
+ 0/*Qt::Key_Delete*/, "edit_delete");
+ d->action_edit_delete->setToolTip(i18n("Delete selected object"));
+ d->action_edit_delete->setWhatsThis(i18n("Deletes currently selected object."));
+
+ d->action_edit_delete_row = createSharedAction(i18n("Delete Row"), "delete_table_row",
+ Qt::CTRL+Qt::Key_Delete, "edit_delete_row");
+ d->action_edit_delete_row->setToolTip(i18n("Delete currently selected row"));
+ d->action_edit_delete_row->setWhatsThis(i18n("Deletes currently selected row."));
+
+ d->action_edit_clear_table = createSharedAction(i18n("Clear Table Contents"), "clear_table_contents",
+ 0, "edit_clear_table");
+ d->action_edit_clear_table->setToolTip(i18n("Clear table contents"));
+ d->action_edit_clear_table->setWhatsThis(i18n("Clears table contents."));
+ setActionVolatile( d->action_edit_clear_table, true );
+
+ d->action_edit_edititem = createSharedAction(i18n("Edit Item"), 0, 0, /* CONFLICT in TV: Qt::Key_F2, */
+ "edit_edititem");
+ d->action_edit_edititem->setToolTip(i18n("Edit currently selected item"));
+ d->action_edit_edititem->setWhatsThis(i18n("Edits currently selected item."));
+
+ d->action_edit_insert_empty_row = createSharedAction(i18n("&Insert Empty Row"), "insert_table_row",
+ Qt::SHIFT | Qt::CTRL | Qt::Key_Insert, "edit_insert_empty_row");
+ setActionVolatile( d->action_edit_insert_empty_row, true );
+ d->action_edit_insert_empty_row->setToolTip(i18n("Insert one empty row above"));
+ d->action_edit_insert_empty_row->setWhatsThis(i18n("Inserts one empty row above currently selected table row."));
+
+ //VIEW MENU
+ if (!userMode()) {
+ d->action_view_data_mode = new KRadioAction(i18n("&Data View"), "state_data", Qt::Key_F6,
+ this, SLOT(slotViewDataMode()), actionCollection(), "view_data_mode");
+ d->actions_for_view_modes.insert( Kexi::DataViewMode, d->action_view_data_mode );
+ d->action_view_data_mode->setExclusiveGroup("view_mode");
+ d->action_view_data_mode->setToolTip(i18n("Switch to data view"));
+ d->action_view_data_mode->setWhatsThis(i18n("Switches to data view."));
+ }
+ else
+ d->action_view_data_mode = 0;
+
+ if (!userMode()) {
+ d->action_view_design_mode = new KRadioAction(i18n("D&esign View"), "state_edit", Qt::Key_F7,
+ this, SLOT(slotViewDesignMode()), actionCollection(), "view_design_mode");
+ d->actions_for_view_modes.insert( Kexi::DesignViewMode, d->action_view_design_mode );
+ d->action_view_design_mode->setExclusiveGroup("view_mode");
+ d->action_view_design_mode->setToolTip(i18n("Switch to design view"));
+ d->action_view_design_mode->setWhatsThis(i18n("Switches to design view."));
+ }
+ else
+ d->action_view_design_mode = 0;
+
+ if (!userMode()) {
+ d->action_view_text_mode = new KRadioAction(i18n("&Text View"), "state_sql", Qt::Key_F8,
+ this, SLOT(slotViewTextMode()), actionCollection(), "view_text_mode");
+ d->actions_for_view_modes.insert( Kexi::TextViewMode, d->action_view_text_mode );
+ d->action_view_text_mode->setExclusiveGroup("view_mode");
+ d->action_view_text_mode->setToolTip(i18n("Switch to text view"));
+ d->action_view_text_mode->setWhatsThis(i18n("Switches to text view."));
+ }
+ else
+ d->action_view_text_mode = 0;
+
+ if (d->isProjectNavigatorVisible) {
+ d->action_view_nav = new KAction(i18n("Project Navigator"), "", Qt::ALT + Qt::Key_1,
+ this, SLOT(slotViewNavigator()), actionCollection(), "view_navigator");
+ d->action_view_nav->setToolTip(i18n("Go to project navigator panel"));
+ d->action_view_nav->setWhatsThis(i18n("Goes to project navigator panel."));
+ }
+ else
+ d->action_view_nav = 0;
+
+ d->action_view_mainarea = new KAction(i18n("Main Area"), "", Qt::ALT + Qt::Key_2,
+ this, SLOT(slotViewMainArea()), actionCollection(), "view_mainarea");
+ d->action_view_mainarea->setToolTip(i18n("Go to main area"));
+ d->action_view_mainarea->setWhatsThis(i18n("Goes to main area."));
+
+ if (!userMode()) {
+ d->action_view_propeditor = new KAction(i18n("Property Editor"), "", Qt::ALT + Qt::Key_3,
+ this, SLOT(slotViewPropertyEditor()), actionCollection(), "view_propeditor");
+ d->action_view_propeditor->setToolTip(i18n("Go to property editor panel"));
+ d->action_view_propeditor->setWhatsThis(i18n("Goes to property editor panel."));
+ }
+ else
+ d->action_view_propeditor = 0;
+
+ //DATA MENU
+ d->action_data_save_row = createSharedAction(i18n("&Save Row"), "button_ok",
+ Qt::SHIFT | Qt::Key_Return, "data_save_row");
+ d->action_data_save_row->setToolTip(i18n("Save changes made to the current row"));
+ d->action_data_save_row->setWhatsThis(i18n("Saves changes made to the current row."));
+//temp. disable because of problems with volatile actions setActionVolatile( d->action_data_save_row, true );
+
+ d->action_data_cancel_row_changes = createSharedAction(i18n("&Cancel Row Changes"),
+ "button_cancel", 0 , "data_cancel_row_changes");
+ d->action_data_cancel_row_changes->setToolTip(i18n("Cancel changes made to the current row"));
+ d->action_data_cancel_row_changes->setWhatsThis(i18n("Cancels changes made to the current row."));
+//temp. disable because of problems with volatile actions setActionVolatile( d->action_data_cancel_row_changes, true );
+
+ d->action_data_execute = createSharedAction(i18n("&Execute"), "player_play", 0 , "data_execute");
+ //d->action_data_execute->setToolTip(i18n("")); //TODO
+ //d->action_data_execute->setWhatsThis(i18n("")); //TODO
+
+#ifndef KEXI_NO_UNFINISHED
+ action = createSharedAction(i18n("&Filter"), "filter", 0, "data_filter");
+ setActionVolatile( action, true );
+#endif
+// action->setToolTip(i18n("")); //todo
+// action->setWhatsThis(i18n("")); //todo
+
+// setSharedMenu("data_sort");
+ action = createSharedAction(i18n("&Ascending"), "sort_az", 0, "data_sort_az");
+//temp. disable because of problems with volatile actions setActionVolatile( action, true );
+ action->setToolTip(i18n("Sort data in ascending order"));
+ action->setWhatsThis(i18n("Sorts data in ascending order (from A to Z and from 0 to 9). Data from selected column is used for sorting."));
+
+ action = createSharedAction(i18n("&Descending"), "sort_za", 0, "data_sort_za");
+//temp. disable because of problems with volatile actions setActionVolatile( action, true );
+ action->setToolTip(i18n("Sort data in descending order"));
+ action->setWhatsThis(i18n("Sorts data in descending (from Z to A and from 9 to 0). Data from selected column is used for sorting."));
+
+ // - record-navigation related actions
+ createSharedAction( KexiRecordNavigator::Actions::moveToFirstRecord(), 0, "data_go_to_first_record");
+ createSharedAction( KexiRecordNavigator::Actions::moveToPreviousRecord(), 0, "data_go_to_previous_record");
+ createSharedAction( KexiRecordNavigator::Actions::moveToNextRecord(), 0, "data_go_to_next_record");
+ createSharedAction( KexiRecordNavigator::Actions::moveToLastRecord(), 0, "data_go_to_last_record");
+ createSharedAction( KexiRecordNavigator::Actions::moveToNewRecord(), 0, "data_go_to_new_record");
+
+ //FORMAT MENU
+ d->action_format_font = createSharedAction(i18n("&Font..."), "fonts", 0, "format_font");
+ d->action_format_font->setToolTip(i18n("Change font for selected object"));
+ d->action_format_font->setWhatsThis(i18n("Changes font for selected object."));
+
+ //TOOLS MENU
+
+ //WINDOW MENU
+#ifndef Q_WS_WIN
+ //KMDI <= 3.5.1 has no shortcut here:
+ KAction *closeWindowAction = actionCollection()->action("window_close");
+ if (closeWindowAction)
+ closeWindowAction->setShortcut(KStdAccel::close());
+#endif
+
+ //additional 'Window' menu items
+ d->action_window_next = new KAction( i18n("&Next Window"), "",
+#ifdef Q_WS_WIN
+ Qt::CTRL+Qt::Key_Tab,
+#else
+ Qt::ALT+Qt::Key_Right,
+#endif
+ this, SLOT(activateNextWin()), actionCollection(), "window_next");
+ d->action_window_next->setToolTip( i18n("Next window") );
+ d->action_window_next->setWhatsThis(i18n("Switches to the next window."));
+
+ d->action_window_previous = new KAction( i18n("&Previous Window"), "",
+#ifdef Q_WS_WIN
+ Qt::CTRL+Qt::SHIFT+Qt::Key_Tab,
+#else
+ Qt::ALT+Qt::Key_Left,
+#endif
+ this, SLOT(activatePrevWin()), actionCollection(), "window_previous");
+ d->action_window_previous->setToolTip( i18n("Previous window") );
+ d->action_window_previous->setWhatsThis(i18n("Switches to the previous window."));
+
+ //SETTINGS MENU
+ setStandardToolBarMenuEnabled( true );
+ action = KStdAction::keyBindings(this, SLOT( slotConfigureKeys() ), actionCollection() );
+ action->setWhatsThis(i18n("Lets you configure shortcut keys."));
+
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+ action = KStdAction::configureToolbars( this, SLOT( slotConfigureToolbars() ), actionCollection() );
+ action->setWhatsThis(i18n("Lets you configure toolbars."));
+
+ d->action_show_other = new KActionMenu(i18n("Other"),
+ actionCollection(), "options_show_other");
+#endif
+
+#ifndef KEXI_NO_CTXT_HELP
+ d->action_show_helper = new KToggleAction(i18n("Show Context Help"), "", Qt::CTRL + Qt::Key_H,
+ actionCollection(), "options_show_contexthelp");
+#if KDE_IS_VERSION(3,2,90)
+ d->action_show_helper->setCheckedState(i18n("Hide Context Help"));
+#endif
+#endif
+
+#ifdef KEXI_FORMS_SUPPORT
+ slotOptionsEnableForms(true, true);
+#else
+ slotOptionsEnableForms(false, true);
+#endif
+
+#ifdef KEXI_REPORTS_SUPPORT
+ Kexi::tempShowReports() = true;
+#else
+ Kexi::tempShowReports() = false;
+#endif
+
+#ifdef KEXI_MACROS_SUPPORT
+ Kexi::tempShowMacros() = true;
+#else
+ Kexi::tempShowMacros() = false;
+#endif
+
+#ifdef KEXI_SCRIPTS_SUPPORT
+ Kexi::tempShowScripts() = true;
+#else
+ Kexi::tempShowScripts() = false;
+#endif
+
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+ d->action_configure = KStdAction::preferences(this, SLOT(slotShowSettings()), actionCollection());
+ action->setWhatsThis(i18n("Lets you configure Kexi."));
+#endif
+
+ //HELP MENU
+#if 0//js: todo reenable later
+ KStdAction::tipOfDay( this, SLOT( slotTipOfTheDayAction() ), actionCollection() )
+ ->setWhatsThis(i18n("This shows useful tips on the use of this application."));
+#endif
+#if 0 //we don't have a time for updating info text for each new version
+ new KAction(i18n("Important Information"), "messagebox_info", 0,
+ this, SLOT(slotImportantInfo()), actionCollection(), "help_show_important_info");
+#endif
+//TODO: UNCOMMENT TO REMOVE MDI MODES SETTING m_pMdiModeMenu->hide();
+
+#ifndef KEXI_NO_FEEDBACK_AGENT
+#ifdef FEEDBACK_CLASS
+ new KAction(i18n("Give Feedback..."), "messagebox_info", 0,
+ this, SLOT(slotStartFeedbackAgent()), actionCollection(), "help_start_feedback_agent");
+#endif
+#endif
+// KAction *actionSettings = new KAction(i18n("Configure Kexi..."), "configure", 0,
+// actionCollection(), "kexi_settings");
+// actionSettings->setWhatsThis(i18n("Lets you configure Kexi."));
+// connect(actionSettings, SIGNAL(activated()), this, SLOT(slotShowSettings()));
+
+ // -- add a few missing tooltips (usable especially in Form's "Assign action" dialog)
+ if ((action = actionCollection()->action("window_close")))
+ action->setToolTip(i18n("Close the current window"));
+
+ // ----- declare action categories, so form's "assign action to button"
+ // (and macros in the future) will be able to recognize category
+ // of actions and filter them -----------------------------------
+//! @todo shouldn't we move this to core?
+ Kexi::ActionCategories *acat = Kexi::actionCategories();
+ acat->addAction("data_execute", Kexi::PartItemActionCategory);
+
+ //! @todo unused for now
+ acat->addWindowAction("data_filter",
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addWindowAction("data_save_row",
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addWindowAction("data_cancel_row_changes",
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addWindowAction("delete_table_row",
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ //! @todo support this in KexiPart::FormObjectType as well
+ acat->addWindowAction("data_sort_az",
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ //! @todo support this in KexiPart::FormObjectType as well
+ acat->addWindowAction("data_sort_za",
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ //! @todo support this in KexiPart::FormObjectType as well
+ acat->addWindowAction("edit_clear_table",
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ //! @todo support this in KexiPart::FormObjectType as well
+ acat->addWindowAction("edit_copy_special_data_table",
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ // GlobalActions, etc.
+ acat->addAction("edit_copy", Kexi::GlobalActionCategory|Kexi::PartItemActionCategory);
+
+ acat->addAction("edit_cut", Kexi::GlobalActionCategory|Kexi::PartItemActionCategory);
+
+ acat->addAction("edit_paste", Kexi::GlobalActionCategory|Kexi::PartItemActionCategory);
+
+ acat->addAction("edit_delete", Kexi::GlobalActionCategory|Kexi::PartItemActionCategory|Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addAction("edit_delete_row", Kexi::GlobalActionCategory|Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addAction("edit_edititem", Kexi::PartItemActionCategory|Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ acat->addAction("edit_find", Kexi::GlobalActionCategory|Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addAction("edit_findnext", Kexi::GlobalActionCategory|Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addAction("edit_findprevious", Kexi::GlobalActionCategory|Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addAction("edit_replace", Kexi::GlobalActionCategory|Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ acat->addAction("edit_paste_special_data_table", Kexi::GlobalActionCategory);
+
+ acat->addAction("help_about_app", Kexi::GlobalActionCategory);
+
+ acat->addAction("help_about_kde", Kexi::GlobalActionCategory);
+
+ acat->addAction("help_contents", Kexi::GlobalActionCategory);
+
+ acat->addAction("help_report_bug", Kexi::GlobalActionCategory);
+
+ acat->addAction("help_whats_this", Kexi::GlobalActionCategory);
+
+ acat->addAction("options_configure_keybinding", Kexi::GlobalActionCategory);
+
+ acat->addAction("project_close", Kexi::GlobalActionCategory);
+
+ //! @todo support this in FormObjectType as well
+ acat->addAction("project_export_data_table", Kexi::GlobalActionCategory|Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ acat->addAction("project_import_data_table", Kexi::GlobalActionCategory);
+
+ acat->addAction("project_new", Kexi::GlobalActionCategory);
+
+ acat->addAction("project_open", Kexi::GlobalActionCategory);
+
+ //! @todo support this in FormObjectType, ReportObjectType as well as others
+ acat->addAction("project_print", Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ //! @todo support this in FormObjectType, ReportObjectType as well as others
+ acat->addAction("project_print_preview", Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ //! @todo support this in FormObjectType, ReportObjectType as well as others
+ acat->addAction("project_print_setup", Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType);
+
+ acat->addAction("quit", Kexi::GlobalActionCategory);
+
+ acat->addAction("tools_compact_database", Kexi::GlobalActionCategory);
+
+ acat->addAction("tools_import_project", Kexi::GlobalActionCategory);
+
+ acat->addAction("view_data_mode", Kexi::GlobalActionCategory);
+
+ acat->addAction("view_design_mode", Kexi::GlobalActionCategory);
+
+ acat->addAction("view_text_mode", Kexi::GlobalActionCategory);
+
+ acat->addAction("view_mainarea", Kexi::GlobalActionCategory);
+
+ acat->addAction("view_navigator", Kexi::GlobalActionCategory);
+
+ acat->addAction("view_propeditor", Kexi::GlobalActionCategory);
+
+ acat->addAction("window_close", Kexi::GlobalActionCategory | Kexi::WindowActionCategory);
+ acat->setAllObjectTypesSupported("window_close", true);
+
+ acat->addAction("window_next", Kexi::GlobalActionCategory);
+
+ acat->addAction("window_previous", Kexi::GlobalActionCategory);
+
+ //skipped - design view only
+ acat->addAction("format_font", Kexi::NoActionCategory);
+ acat->addAction("project_save", Kexi::NoActionCategory);
+ acat->addAction("edit_insert_empty_row", Kexi::NoActionCategory);
+ //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType later
+ acat->addAction("edit_select_all", Kexi::NoActionCategory);
+ //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType later
+ acat->addAction("edit_redo", Kexi::NoActionCategory);
+ //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType later
+ acat->addAction("edit_undo", Kexi::NoActionCategory);
+
+ //record-navigation related actions
+ acat->addAction("data_go_to_first_record", Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+ acat->addAction("data_go_to_previous_record", Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+ acat->addAction("data_go_to_next_record", Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+ acat->addAction("data_go_to_last_record", Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+ acat->addAction("data_go_to_new_record", Kexi::WindowActionCategory,
+ KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType);
+
+ //skipped - internal:
+ acat->addAction("tablepart_create", Kexi::NoActionCategory);
+ acat->addAction("querypart_create", Kexi::NoActionCategory);
+ acat->addAction("formpart_create", Kexi::NoActionCategory);
+ acat->addAction("reportpart_create", Kexi::NoActionCategory);
+ acat->addAction("macropart_create", Kexi::NoActionCategory);
+ acat->addAction("scriptpart_create", Kexi::NoActionCategory);
+}
+
+void KexiMainWindowImpl::invalidateActions()
+{
+ invalidateProjectWideActions();
+ invalidateSharedActions();
+}
+
+void KexiMainWindowImpl::invalidateSharedActions(QObject *o)
+{
+ //TODO: enabling is more complex...
+/* d->action_edit_cut->setEnabled(true);
+ d->action_edit_copy->setEnabled(true);
+ d->action_edit_paste->setEnabled(true);*/
+
+ if (!o)
+ o = focusWindow();
+ KexiSharedActionHost::invalidateSharedActions(o);
+}
+
+void KexiMainWindowImpl::invalidateSharedActions()
+{
+ invalidateSharedActions(0);
+}
+
+// unused, I think
+void KexiMainWindowImpl::invalidateSharedActionsLater()
+{
+ QTimer::singleShot(1, this, SLOT(invalidateSharedActions()));
+}
+
+void KexiMainWindowImpl::invalidateProjectWideActions()
+{
+// stateChanged("project_opened",d->prj ? StateNoReverse : StateReverse);
+
+ const bool have_dialog = d->curDialog;
+ const bool dialog_dirty = d->curDialog && d->curDialog->dirty();
+ const bool readOnly = d->prj && d->prj->dbConnection() && d->prj->dbConnection()->isReadOnly();
+
+ //PROJECT MENU
+ d->action_save->setEnabled(have_dialog && dialog_dirty && !readOnly);
+ d->action_save_as->setEnabled(have_dialog && !readOnly);
+ d->action_project_properties->setEnabled(d->prj);
+ d->action_close->setEnabled(d->prj);
+ d->action_project_relations->setEnabled(d->prj);
+ if (d->action_project_import_data_table)
+ d->action_project_import_data_table->setEnabled(d->prj && !readOnly);
+ d->action_project_export_data_table->setEnabled(
+ d->curDialog && d->curDialog->part()->info()->isDataExportSupported()
+ && !d->curDialog->neverSaved() );
+
+ const bool printingActionsEnabled =
+ d->curDialog && d->curDialog->part()->info()->isPrintingSupported()
+ && !d->curDialog->neverSaved();
+ d->action_project_print->setEnabled( printingActionsEnabled );
+ d->action_project_print_preview->setEnabled( printingActionsEnabled );
+ d->action_project_print_setup->setEnabled( printingActionsEnabled );
+
+ //EDIT MENU
+
+ if (d->action_edit_paste_special_data_table)
+ d->action_edit_paste_special_data_table->setEnabled(d->prj && !readOnly);
+
+//! @todo "copy special" is currently enabled only for data view mode;
+//! what about allowing it to enable in design view for "kexi/table" ?
+ if (d->curDialog && d->curDialog->currentViewMode()==Kexi::DataViewMode) {
+ KexiPart::Info *activePartInfo = d->curDialog->part()->info();
+ d->action_edit_copy_special_data_table->setEnabled(
+ activePartInfo ? activePartInfo->isDataExportSupported() : false );
+ }
+ else
+ d->action_edit_copy_special_data_table->setEnabled( false );
+
+ //VIEW MENU
+ if (d->action_view_nav)
+ d->action_view_nav->setEnabled(d->prj);
+ d->action_view_mainarea->setEnabled(d->prj);
+ if (d->action_view_propeditor)
+ d->action_view_propeditor->setEnabled(d->prj);
+ if (d->action_view_data_mode) {
+ d->action_view_data_mode->setEnabled( have_dialog && d->curDialog->supportsViewMode(Kexi::DataViewMode) );
+ if (!d->action_view_data_mode->isEnabled())
+ d->action_view_data_mode->setChecked(false);
+ }
+ if (d->action_view_design_mode) {
+ d->action_view_design_mode->setEnabled( have_dialog && d->curDialog->supportsViewMode(Kexi::DesignViewMode) );
+ if (!d->action_view_design_mode->isEnabled())
+ d->action_view_design_mode->setChecked(false);
+ }
+ if (d->action_view_text_mode) {
+ d->action_view_text_mode->setEnabled( have_dialog && d->curDialog->supportsViewMode(Kexi::TextViewMode) );
+ if (!d->action_view_text_mode->isEnabled())
+ d->action_view_text_mode->setChecked(false);
+ }
+#ifndef KEXI_NO_CTXT_HELP
+ d->action_show_helper->setEnabled(d->prj);
+#endif
+
+ //CREATE MENU
+ if (d->createMenu)
+ d->createMenu->setEnabled(d->prj);
+
+ // DATA MENU
+ //d->action_data_execute->setEnabled( d->curDialog && d->curDialog->part()->info()->isExecuteSupported() );
+
+ //TOOLS MENU
+ // "compact db" supported if there's no db or the current db supports compacting and is opened r/w:
+ d->action_tools_compact_database->setEnabled(
+ !d->prj || !readOnly && d->prj && d->prj->dbConnection()
+ && (d->prj->dbConnection()->driver()->features() & KexiDB::Driver::CompactingDatabaseSupported) );
+
+ //WINDOW MENU
+ if (d->action_window_next) {
+ d->action_window_next->setEnabled(!m_pDocumentViews->isEmpty());
+ d->action_window_previous->setEnabled(!m_pDocumentViews->isEmpty());
+ }
+
+ //DOCKS
+ if (d->nav)
+ d->nav->setEnabled(d->prj);
+ if (d->propEditor)
+ d->propEditorTabWidget->setEnabled(d->prj);
+}
+
+void KexiMainWindowImpl::invalidateViewModeActions()
+{
+ if (d->curDialog) {
+ //update toggle action
+ if (d->curDialog->currentViewMode()==Kexi::DataViewMode) {
+ if (d->action_view_data_mode)
+ d->action_view_data_mode->setChecked( true );
+ }
+ else if (d->curDialog->currentViewMode()==Kexi::DesignViewMode) {
+ if (d->action_view_design_mode)
+ d->action_view_design_mode->setChecked( true );
+ }
+ else if (d->curDialog->currentViewMode()==Kexi::TextViewMode) {
+ if (d->action_view_text_mode)
+ d->action_view_text_mode->setChecked( true );
+ }
+ }
+}
+
+tristate KexiMainWindowImpl::startup()
+{
+ switch (Kexi::startupHandler().action()) {
+ case KexiStartupHandler::CreateBlankProject:
+ if (d->propEditor)
+ makeDockInvisible( manager()->findWidgetParentDock(d->propEditorTabWidget) );
+ return createBlankProject();
+ case KexiStartupHandler::CreateFromTemplate:
+ return createProjectFromTemplate(*Kexi::startupHandler().projectData());
+ case KexiStartupHandler::OpenProject:
+ return openProject(*Kexi::startupHandler().projectData());
+ case KexiStartupHandler::ImportProject:
+ return showProjectMigrationWizard(
+ Kexi::startupHandler().importActionData().mimeType,
+ Kexi::startupHandler().importActionData().fileName
+ );
+ default:;
+ if (d->propEditor)
+ makeDockInvisible( manager()->findWidgetParentDock(d->propEditorTabWidget) );
+ }
+ return true;
+}
+
+static QString internalReason(KexiDB::Object *obj)
+{
+ const QString &s = obj->errorMsg();
+ if (s.isEmpty())
+ return s;
+ return QString("<br>(%1) ").arg(i18n("reason:")+" <i>"+s+"</i>");
+}
+
+tristate KexiMainWindowImpl::openProject(const KexiProjectData& projectData)
+{
+ KexiProjectData *newProjectData = new KexiProjectData(projectData);
+// if (userMode()) {
+ //TODO: maybe also auto allow to open objects...
+// return initUserModeMode(newProjectData);
+// }
+ createKexiProject( newProjectData );
+ if (!newProjectData->connectionData()->savePassword
+ && newProjectData->connectionData()->password.isEmpty()
+ && newProjectData->connectionData()->fileName().isEmpty() //! @todo temp.: change this if there are file-based drivers requiring a password
+ )
+ {
+ //ask for password
+ KexiDBPasswordDialog pwdDlg(this, *newProjectData->connectionData(),
+ false /*!showDetailsButton*/);
+ if (QDialog::Accepted!=pwdDlg.exec()) {
+ delete d->prj;
+ d->prj = 0;
+ return cancelled;
+ }
+ }
+ bool incompatibleWithKexi;
+ tristate res = d->prj->open(incompatibleWithKexi);
+ if (~res) {
+ delete d->prj;
+ d->prj = 0;
+ return cancelled;
+ }
+ else if (!res) {
+ delete d->prj;
+ d->prj = 0;
+ if (incompatibleWithKexi) {
+ if (KMessageBox::Yes == KMessageBox::questionYesNo(this,
+ i18n("<qt>Database project %1 does not appear to have been created using Kexi.<br><br>"
+ "Do you want to import it as a new Kexi project?</qt>").arg(projectData.infoString()),
+ 0, KGuiItem(i18n("Import Database", "&Import..."), "database_import"),
+ KStdGuiItem::quit()))
+ {
+ const bool anotherProjectAlreadyOpened = d->prj;
+ tristate res = showProjectMigrationWizard("application/x-kexi-connectiondata",
+ projectData.databaseName(), projectData.constConnectionData());
+
+ if (!anotherProjectAlreadyOpened) //the project could have been opened within this instance
+ return res;
+
+ //always return cancelled because even if migration succeeded, new Kexi instance
+ //will be started if user wanted to open the imported db
+ return cancelled;
+ }
+ return cancelled;
+ }
+ return false;
+ }
+ initNavigator();
+ Kexi::recentProjects().addProjectData( newProjectData );
+ updateReadOnlyState();
+ invalidateActions();
+// d->disableErrorMessages = true;
+ enableMessages( false );
+
+ QTimer::singleShot(1, this, SLOT(slotAutoOpenObjectsLater()));
+ return true;
+}
+
+tristate KexiMainWindowImpl::createProjectFromTemplate(const KexiProjectData& projectData)
+{
+ QStringList mimetypes;
+ mimetypes.append( KexiDB::Driver::defaultFileBasedDriverMimeType() );
+ QString fname;
+ const QString startDir(":OpenExistingOrCreateNewProject"/*as in KexiNewProjectWizard*/);
+ const QString caption( i18n("Select New Project's Location") );
+
+ while (true) {
+#ifdef Q_WS_WIN
+ //! @todo remove
+ QString recentDir = KGlobalSettings::documentPath();
+ if (fname.isEmpty() && !projectData.constConnectionData()->dbFileName().isEmpty()) //propose filename from db template name
+ fname = KFileDialog::getStartURL(startDir, recentDir).path()
+ + '/' + projectData.constConnectionData()->dbFileName();
+ fname = QFileDialog::getSaveFileName(
+ KFileDialog::getStartURL(fname.isEmpty() ? startDir : fname, recentDir).path(),
+ KexiUtils::fileDialogFilterStrings(mimetypes, false),
+ this, "CreateProjectFromTemplate", caption);
+ if ( !fname.isEmpty() ) {
+ //save last visited path
+ KURL url;
+ url.setPath( fname );
+ if (url.isLocalFile())
+ KRecentDirs::add(startDir, url.directory());
+ }
+#else
+ Q_UNUSED(projectData);
+ if (fname.isEmpty() &&
+ !projectData.constConnectionData()->dbFileName().isEmpty())
+ {
+ //propose filename from db template name
+ fname = projectData.constConnectionData()->dbFileName();
+ }
+ const bool specialDir = fname.isEmpty();
+ kdDebug() << fname << "............." << endl;
+ KFileDialog dlg( specialDir ? startDir : QString::null,
+ mimetypes.join(" "), this, "filedialog", true);
+ if ( !specialDir )
+ dlg.setSelection( fname ); // may also be a filename
+ dlg.setOperationMode( KFileDialog::Saving );
+ dlg.setCaption( caption );
+ dlg.exec();
+ fname = dlg.selectedFile();
+ if (!fname.isEmpty())
+ KRecentDocument::add(fname);
+// fname = KFileDialog::getSaveFileName(fname.isEmpty() ? startDir : fname,
+ // mimetypes.join(" "), this, caption);
+#endif
+ if ( fname.isEmpty() )
+ return cancelled;
+ if (KexiStartupFileDialog::askForOverwriting(fname, this))
+ break;
+ }
+
+ if (KexiUtils::CopySuccess != KexiUtils::copyFile(
+ projectData.constConnectionData()->fileName(), fname ))
+ {
+ return false;
+ }
+
+ return openProject(fname, 0, QString::null, projectData.autoopenObjects/*copy*/);
+}
+
+void KexiMainWindowImpl::updateReadOnlyState()
+{
+ const bool readOnly = d->prj && d->prj->dbConnection() && d->prj->dbConnection()->isReadOnly();
+ d->statusBar->setReadOnlyFlag( readOnly );
+ if (d->nav)
+ d->nav->setReadOnly(readOnly);
+ // update "insert ....." actions for every part
+ KActionCollection *ac = actionCollection();
+ for (KexiPart::PartInfoListIterator it(*Kexi::partManager().partInfoList()); it.current(); ++it) {
+ KAction *a = ac->action( KexiPart::nameForCreateAction( *it.current() ) );
+ if (a)
+ a->setEnabled(!readOnly);
+ }
+}
+
+void KexiMainWindowImpl::slotAutoOpenObjectsLater()
+{
+ QString not_found_msg;
+ bool openingCancelled;
+ //ok, now open "autoopen: objects
+ if (d->prj) {
+ for (QValueList<KexiProjectData::ObjectInfo>::ConstIterator it =
+ d->prj->data()->autoopenObjects.constBegin();
+ it != d->prj->data()->autoopenObjects.constEnd(); ++it )
+ {
+ KexiProjectData::ObjectInfo info = *it;
+ KexiPart::Info *i = Kexi::partManager().infoForMimeType(
+ QCString("kexi/")+info["type"].lower().latin1() );
+ if (!i) {
+ not_found_msg += "<li>";
+ if (!info["name"].isEmpty())
+ not_found_msg += (QString("\"") + info["name"] + "\" - ");
+ if (info["action"]=="new")
+ not_found_msg += i18n("cannot create object - unknown object type \"%1\"")
+ .arg(info["type"]);
+ else
+ not_found_msg += i18n("unknown object type \"%1\"").arg(info["type"]);
+ not_found_msg += internalReason(&Kexi::partManager())+"<br></li>";
+ continue;
+ }
+ // * NEW
+ if (info["action"]=="new") {
+ if (!newObject( i, openingCancelled) && !openingCancelled) {
+ not_found_msg += "<li>";
+ not_found_msg += (i18n("cannot create object of type \"%1\"").arg(info["type"])+
+ internalReason(d->prj)+"<br></li>");
+ }
+ else
+ d->wasAutoOpen = true;
+ continue;
+ }
+
+ KexiPart::Item *item = d->prj->item(i, info["name"]);
+
+ if (!item) {
+ QString taskName;
+ if (info["action"]=="print-preview")
+ taskName = i18n("making print preview for");
+ else if (info["action"]=="print")
+ taskName = i18n("printing");
+ else if (info["action"]=="execute")
+ taskName = i18n("\"executing object\" action", "executing");
+ else
+ taskName = i18n("opening");
+
+ not_found_msg += (QString("<li>")+ taskName + " \"" + info["name"] + "\" - ");
+ if ("table"==info["type"].lower())
+ not_found_msg += i18n("table not found");
+ else if ("query"==info["type"].lower())
+ not_found_msg += i18n("query not found");
+ else if ("macro"==info["type"].lower())
+ not_found_msg += i18n("macro not found");
+ else if ("script"==info["type"].lower())
+ not_found_msg += i18n("script not found");
+ else
+ not_found_msg += i18n("object not found");
+ not_found_msg += (internalReason(d->prj)+"<br></li>");
+ continue;
+ }
+ // * EXECUTE, PRINT, PRINT PREVIEW
+ if (info["action"]=="execute") {
+ tristate res = executeItem(item);
+ if (false == res) {
+ not_found_msg += ( QString("<li>\"")+ info["name"] + "\" - " + i18n("cannot execute object")+
+ internalReason(d->prj)+"<br></li>" );
+ }
+ continue;
+ }
+ else if (info["action"]=="print") {
+ tristate res = printItem(item);
+ if (false == res) {
+ not_found_msg += ( QString("<li>\"")+ info["name"] + "\" - " + i18n("cannot print object")+
+ internalReason(d->prj)+"<br></li>" );
+ }
+ continue;
+ }
+ else if (info["action"]=="print-preview") {
+ tristate res = printPreviewForItem(item);
+ if (false == res) {
+ not_found_msg += ( QString("<li>\"")+ info["name"] + "\" - " + i18n("cannot make print preview of object")+
+ internalReason(d->prj)+"<br></li>" );
+ }
+ continue;
+ }
+
+ int viewMode;
+ if (info["action"]=="open")
+ viewMode = Kexi::DataViewMode;
+ else if (info["action"]=="design")
+ viewMode = Kexi::DesignViewMode;
+ else if (info["action"]=="edittext")
+ viewMode = Kexi::TextViewMode;
+ else
+ continue; //sanity
+
+ QString openObjectMessage;
+ if (!openObject(item, viewMode, openingCancelled, 0, &openObjectMessage)
+ && (!openingCancelled || !openObjectMessage.isEmpty()))
+ {
+ not_found_msg += (QString("<li>\"")+ info["name"] + "\" - ");
+ if (openObjectMessage.isEmpty())
+ not_found_msg += i18n("cannot open object");
+ else
+ not_found_msg += openObjectMessage;
+ not_found_msg += internalReason(d->prj) + "<br></li>";
+ continue;
+ }
+ else {
+ d->wasAutoOpen = true;
+ }
+ }
+ }
+ enableMessages( true );
+// d->disableErrorMessages = false;
+
+ if (!not_found_msg.isEmpty())
+ showErrorMessage(i18n("You have requested selected objects to be automatically opened "
+ "or processed on startup. Several objects cannot be opened or processed."),
+ QString("<ul>%1</ul>").arg(not_found_msg) );
+
+ d->updatePropEditorVisibility(d->curDialog ? d->curDialog->currentViewMode() : 0);
+#if defined(KDOCKWIDGET_P)
+ if (d->propEditor) {
+ KDockWidget *dw = (KDockWidget *)d->propEditorTabWidget->parentWidget();
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+ if(ds)
+ ds->setSeparatorPosInPercent(d->config->readNumEntry("RightDockPosition", 80/* % */));
+ }
+#endif
+
+ updateAppCaption();
+
+// d->navToolWindow->wrapperWidget()->setFixedWidth(200);
+//js TODO: make visible FOR OTHER MODES if needed
+ if (mdiMode()==KMdi::ChildframeMode || mdiMode()==KMdi::TabPageMode) {
+ //make docks visible again
+ if (!d->navToolWindow->wrapperWidget()->isVisible())
+ static_cast<KDockWidget*>(d->navToolWindow->wrapperWidget())->makeDockVisible();
+// if (!d->propEditorToolWindow->wrapperWidget()->isVisible())
+// static_cast<KDockWidget*>(d->propEditorToolWindow->wrapperWidget())->makeDockVisible();
+ }
+
+ // if (!d->prj->data()->autoopenObjects.isEmpty())
+ d->restoreNavigatorWidth();
+
+#ifndef PROPEDITOR_VISIBILITY_CHANGES
+// KDockWidget *dw = (KDockWidget *)d->nav->parentWidget();
+// KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+// const int pos = ds->separatorPos();
+
+ //if (!d->curDialog || d->curDialog->currentViewMode()==Kexi::DataViewMode)
+// d->propEditorToolWindow->hide();
+
+// ds->setSeparatorPos( pos, true );
+#endif
+ if (d->nav) {
+ d->nav->updateGeometry();
+ }
+ qApp->processEvents();
+ emit projectOpened();
+}
+
+tristate KexiMainWindowImpl::closeProject()
+{
+#ifndef KEXI_NO_PENDING_DIALOGS
+ if (d->pendingDialogsExist()) {
+ kdDebug() << "KexiMainWindowImpl::closeProject() pendingDialogsExist..." << endl;
+ d->actionToExecuteWhenPendingJobsAreFinished = Private::CloseProjectAction;
+ return cancelled;
+ }
+#endif
+
+ //only save nav. visibility setting is project is opened
+ d->saveSettingsForShowProjectNavigator = d->prj && d->isProjectNavigatorVisible;
+
+ if (!d->prj)
+ return true;
+
+ {
+ // make sure the project can be closed
+ bool cancel = false;
+ emit acceptProjectClosingRequested(cancel);
+ if (cancel)
+ return cancelled;
+ }
+
+ d->dialogExistedBeforeCloseProject = !d->curDialog.isNull();
+
+#if defined(KDOCKWIDGET_P)
+ //remember docks position - will be used on storeSettings()
+ if (d->propEditor) {
+ KDockWidget *dw = (KDockWidget *)d->propEditorTabWidget->parentWidget();
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+ if (ds)
+ d->propEditorDockSeparatorPos = ds->separatorPosInPercent();
+ }
+ if (d->nav) {
+// makeDockInvisible( manager()->findWidgetParentDock(d->propEditor) );
+
+ if (d->propEditor) {
+ if (d->openedDialogsCount() == 0)
+ makeWidgetDockVisible(d->propEditorTabWidget);
+ KDockWidget *dw = (KDockWidget *)d->propEditorTabWidget->parentWidget();
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+ if(ds)
+ ds->setSeparatorPosInPercent(80);
+ }
+
+ KDockWidget *dw = (KDockWidget *)d->nav->parentWidget();
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+ int dwWidth = dw->width();
+ if (ds) {
+ if (d->openedDialogsCount()!=0 && d->propEditorTabWidget && d->propEditorTabWidget->isVisible())
+ d->navDockSeparatorPos = ds->separatorPosInPercent();
+ else
+ d->navDockSeparatorPos = (100 * dwWidth) / width();
+
+// int navDockSeparatorPosWithAutoOpen = (100 * dw->width()) / width() + 4;
+// d->navDockSeparatorPos = (100 * dw->width()) / width() + 1;
+ }
+ }
+#endif
+
+ //close each window, optionally asking if user wants to close (if data changed)
+ while (!d->curDialog.isNull()) {
+ tristate res = closeDialog( d->curDialog );
+ if (!res || ~res)
+ return res;
+ }
+
+ // now we will close for sure
+ emit beforeProjectClosing();
+
+ if (!d->prj->closeConnection())
+ return false;
+
+ if(d->nav)
+ {
+ d->navWasVisibleBeforeProjectClosing = manager()->findWidgetParentDock(d->nav)->isVisible();
+ d->nav->clear();
+#if 0 //do not confuse users
+ d->navToolWindow->hide();
+#endif
+ }
+
+ if (d->propEditor)
+ makeDockInvisible( manager()->findWidgetParentDock(d->propEditorTabWidget) );
+
+// if(d->propEditorToolWindow)
+ // d->propEditorToolWindow->hide();
+
+ d->clearDialogs(); //sanity!
+ delete d->prj;
+ d->prj=0;
+
+// Kexi::partManager().unloadAllParts();
+
+ updateReadOnlyState();
+ invalidateActions();
+// if (!userMode())
+ updateAppCaption();
+
+ emit projectClosed();
+ return true;
+}
+
+void KexiMainWindowImpl::initContextHelp() {
+#ifndef KEXI_NO_CTXT_HELP
+ d->ctxHelp=new KexiContextHelp(this,this);
+/*todo
+ d->ctxHelp->setContextHelp(i18n("Welcome"),i18n("The <B>KEXI team</B> wishes you a lot of productive work, "
+ "with this product. <BR><HR><BR>If you have found a <B>bug</B> or have a <B>feature</B> request, please don't "
+ "hesitate to report it at our <A href=\"http://www.kexi-project.org/cgi-bin/bug.pl\"> issue "
+ "tracking system </A>.<BR><HR><BR>If you would like to <B>join</B> our effort, the <B>development</B> documentation "
+ "at <A href=\"http://www.kexi-project.org\">www.kexi-project.org</A> is a good starting point."),0);
+*/
+ addToolWindow(d->ctxHelp,KDockWidget::DockBottom | KDockWidget::DockLeft,getMainDockWidget(),20);
+#endif
+}
+
+void KexiMainWindowImpl::initNavigator()
+{
+ if (!d->isProjectNavigatorVisible)
+ return;
+
+ if(!d->nav)
+ {
+ d->nav = new KexiBrowser(this, this);
+ d->nav->installEventFilter(this);
+ d->navToolWindow = addToolWindow(d->nav, KDockWidget::DockLeft, getMainDockWidget(), 20/*, lv, 35, "2"*/);
+// d->navToolWindow->hide();
+
+ connect(d->nav,SIGNAL(openItem(KexiPart::Item*,int)),this,SLOT(openObject(KexiPart::Item*,int)));
+ connect(d->nav,SIGNAL(openOrActivateItem(KexiPart::Item*,int)),
+ this,SLOT(openObjectFromNavigator(KexiPart::Item*,int)));
+ connect(d->nav,SIGNAL(newItem( KexiPart::Info* )),
+ this,SLOT(newObject(KexiPart::Info*)));
+ connect(d->nav,SIGNAL(removeItem(KexiPart::Item*)),
+ this,SLOT(removeObject(KexiPart::Item*)));
+ connect(d->nav,SIGNAL(renameItem(KexiPart::Item*,const QString&, bool&)),
+ this,SLOT(renameObject(KexiPart::Item*,const QString&, bool&)));
+ connect(d->nav,SIGNAL(executeItem(KexiPart::Item*)),
+ this,SLOT(executeItem(KexiPart::Item*)));
+ connect(d->nav,SIGNAL(exportItemAsDataTable(KexiPart::Item*)),
+ this,SLOT(exportItemAsDataTable(KexiPart::Item*)));
+ connect(d->nav,SIGNAL(printItem( KexiPart::Item* )),
+ this,SLOT(printItem(KexiPart::Item*)));
+ connect(d->nav,SIGNAL(pageSetupForItem( KexiPart::Item*)),
+ this,SLOT(showPageSetupForItem(KexiPart::Item*)));
+ if (d->prj) {//connect to the project
+ connect(d->prj, SIGNAL(itemRemoved(const KexiPart::Item&)),
+ d->nav, SLOT(slotRemoveItem(const KexiPart::Item&)));
+ }
+ connect(d->nav,SIGNAL(selectionChanged(KexiPart::Item*)),
+ this,SLOT(slotPartItemSelectedInNavigator(KexiPart::Item*)));
+
+// d->restoreNavigatorWidth();
+ }
+ if(d->prj->isConnected()) {
+ QString partManagerErrorMessages;
+ d->nav->setProject( d->prj, QString::null/*all mimetypes*/, &partManagerErrorMessages );
+ if (!partManagerErrorMessages.isEmpty()) {
+ showWarningContinueMessage(partManagerErrorMessages, QString::null,
+ "dontShowWarningsRelatedToPluginsLoading");
+ }
+ }
+ connect(d->prj, SIGNAL(newItemStored(KexiPart::Item&)), d->nav, SLOT(addItem(KexiPart::Item&)));
+ d->nav->setFocus();
+
+ if (d->forceShowProjectNavigatorOnCreation) {
+ slotViewNavigator();
+ d->forceShowProjectNavigatorOnCreation = false;
+ }
+ else if (d->forceHideProjectNavigatorOnCreation) {
+ d->navToolWindow->hide();
+// makeDockInvisible( manager()->findWidgetParentDock(d->nav) );
+ d->forceHideProjectNavigatorOnCreation = false;
+ }
+
+ invalidateActions();
+}
+
+void KexiMainWindowImpl::slotLastActions()
+{
+#if defined(KDOCKWIDGET_P)
+ if (mdiMode()==KMdi::ChildframeMode || mdiMode()==KMdi::TabPageMode) {
+// KDockWidget *dw = (KDockWidget *)d->propEditor->parentWidget();
+ //KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+// Q_UNUSED(ds);
+//1 ds->resize(ds->width()*3, ds->height());
+//1 ds->setSeparatorPos(30, true);
+//1 ds->setForcedFixedWidth( dw, 200 );
+ }
+#endif
+#ifdef Q_WS_WIN
+ showMaximized();//js: workaround for not yet completed layout settings storage on win32
+#endif
+}
+
+void KexiMainWindowImpl::initPropertyEditor()
+{
+ if (!d->propEditor) {
+//TODO: FIX LAYOUT PROBLEMS
+ d->propEditorTabWidget = new KTabWidget(this);
+ d->propEditorTabWidget->hide();
+ d->propEditor = new KexiPropertyEditorView(this, d->propEditorTabWidget);
+ d->propEditorTabWidget->setCaption(d->propEditor->caption());
+ d->propEditorTabWidget->addTab(d->propEditor, i18n("Properties"));
+ d->propEditor->installEventFilter(this);
+ d->propEditorToolWindow = addToolWindow(d->propEditorTabWidget,
+ KDockWidget::DockRight, getMainDockWidget(), 20);
+
+ d->config->setGroup("PropertyEditor");
+ int size = d->config->readNumEntry("FontSize", -1);
+ QFont f( Kexi::smallFont() );
+ if (size>0)
+ f.setPixelSize( size );
+ d->propEditorTabWidget->setFont(f);
+
+ if (mdiMode()==KMdi::ChildframeMode || mdiMode()==KMdi::TabPageMode) {
+ KDockWidget *dw = (KDockWidget *)d->propEditorTabWidget->parentWidget();
+ #if defined(KDOCKWIDGET_P)
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+// ds->setKeepSize(true);
+ makeWidgetDockVisible(d->propEditorTabWidget);
+ // ds->show();
+ // ds->resize(400, ds->height());
+ // ds->setSeparatorPos(400, true);
+ // ds->setForcedFixedWidth( dw, 400 );
+ // ds->setSeparatorPos(600, true);
+
+
+ d->config->setGroup("MainWindow");
+ ds->setSeparatorPosInPercent(d->config->readNumEntry("RightDockPosition", 80/* % */));
+// makeDockInvisible( manager()->findWidgetParentDock(d->propEditor) );
+
+ // ds->setForcedFixedWidth( dw, d->config->readNumEntry("RightDockPosition", 80) );
+ // ds->resize(400, ds->height());
+ // dw->resize(400, dw->height());
+ #endif
+
+ //1 dw->setMinimumWidth(200);
+
+ // ds->setSeparatorPos(d->propEditor->sizeHint().width(), true);
+
+ //heh, this is for IDEAl only, I suppose?
+ //js if (m_rightContainer) {
+ //js m_rightContainer->setForcedFixedWidth( 400 );
+ //js }
+ }
+
+ // int w = d->propEditor->width();
+ /* KMdiToolViewAccessor *tmp=createToolWindow();
+ tmp->setWidgetToWrap(d->propEditor);
+ d->propEditor->show(); // I'm not sure, if this is a bug in kdockwidget, which I would better fix there
+ tmp->show(KDockWidget::DockRight,getMainDockWidget(),20);
+ */
+ }
+// makeDockInvisible(manager()->findWidgetParentDock(d->propEditorTabWidget));
+}
+
+void KexiMainWindowImpl::slotPartLoaded(KexiPart::Part* p)
+{
+ if (!p)
+ return;
+ connect(p, SIGNAL(newObjectRequest(KexiPart::Info*)),
+ this, SLOT(newObject(KexiPart::Info*)));
+ p->createGUIClients(this);
+}
+
+//! internal
+void KexiMainWindowImpl::slotCaptionForCurrentMDIChild(bool childrenMaximized)
+{
+ //js todo: allow to set custom "static" app caption
+
+ KMdiChildView *view = 0L;
+ if (!d->curDialog)
+ view = 0;
+ else if (d->curDialog->isAttached()) {
+ view = d->curDialog;
+ } else {
+ //current dialog isn't attached! - find top level child
+ if (m_pMdi->topChild()) {
+ view = m_pMdi->topChild()->m_pClient;
+ childrenMaximized = view->mdiParent()->state()==KMdiChildFrm::Maximized;
+ }
+ else
+ view = 0;
+ }
+
+ if (childrenMaximized && view) {
+ setCaption( d->curDialog->caption()
+ + (d->appCaptionPrefix.isEmpty() ? QString::null : (QString::fromLatin1(" - ") + d->appCaptionPrefix)) );
+ }
+ else {
+ setCaption( (d->appCaptionPrefix.isEmpty() ? QString::null : (d->appCaptionPrefix + QString::fromLatin1(" - ")))
+ + d->origAppCaption );
+ }
+}
+
+void KexiMainWindowImpl::updateAppCaption()
+{
+ //js todo: allow to set custom "static" app caption
+
+ d->appCaptionPrefix = "";
+ if (d->prj && d->prj->data()) {//add project name
+ d->appCaptionPrefix = d->prj->data()->caption();
+ if (d->appCaptionPrefix.isEmpty())
+ d->appCaptionPrefix = d->prj->data()->databaseName();
+ }
+// if (!d->appCaptionPrefix.isEmpty())
+// d->appCaptionPrefix = d->appCaptionPrefix;
+
+ bool max = false;
+ if (d->curDialog && d->curDialog->mdiParent())
+ max = d->curDialog->mdiParent()->state()==KMdiChildFrm::Maximized;
+
+ slotCaptionForCurrentMDIChild(max);
+/*
+ KMdiChildView *view;
+ if (!d->curDialog)
+ view = 0;
+ else if (d->curDialog->isAttached()) {
+ view = d->curDialog;
+ } else {
+ //current dialog isn't attached! - find top level child
+ if (m_pMdi->topChild()) {
+ view = m_pMdi->topChild()->m_pClient;
+ }
+ else
+ view = 0;
+ }
+
+ kApp->setCaption( d->appCaption );
+ if (view && view->mdiParent()->state()==KMdiChildFrm::Maximized) {
+ setCaption( view->caption() );
+ }
+ else {
+ setCaption( d->appCaption );
+ }*/
+}
+
+void KexiMainWindowImpl::slotNoMaximizedChildFrmLeft(KMdiChildFrm*)
+{
+ slotCaptionForCurrentMDIChild(false);
+}
+
+void KexiMainWindowImpl::slotLastChildViewClosed() //slotLastChildFrmClosed()
+{
+ if (m_pDocumentViews->count()>0) //a fix for KMDI bug (will be fixed in KDE 3.4)
+ return;
+
+ slotCaptionForCurrentMDIChild(false);
+ activeWindowChanged(0);
+
+//js: too WEIRD if (d->propEditor)
+//js: too WEIRD makeDockInvisible( manager()->findWidgetParentDock(d->propEditorTabWidget) );
+// if (d->propEditorToolWindow)
+ // d->propEditorToolWindow->hide();
+}
+
+void KexiMainWindowImpl::slotChildViewIsDetachedNow(QWidget*)
+{
+ slotCaptionForCurrentMDIChild(false);
+}
+
+/*void
+KexiMainWindowImpl::closeEvent(QCloseEvent *ev)
+{
+ storeSettings();
+
+ bool cancelled = false;
+ if (!closeProject(cancelled)) {
+ //todo: error message
+ return;
+ }
+ if (cancelled) {
+ ev->ignore();
+ return;
+ }
+
+ ev->accept();
+}*/
+
+bool
+KexiMainWindowImpl::queryClose()
+{
+#ifndef KEXI_NO_PENDING_DIALOGS
+ if (d->pendingDialogsExist()) {
+ kdDebug() << "KexiMainWindowImpl::queryClose() pendingDialogsExist..." << endl;
+ d->actionToExecuteWhenPendingJobsAreFinished = Private::QuitAction;
+ return false;
+ }
+#endif
+// storeSettings();
+ const tristate res = closeProject();
+ if (~res)
+ return false;
+
+ if (res==true)
+ storeSettings();
+
+ return ! ~res;
+}
+
+bool
+KexiMainWindowImpl::queryExit()
+{
+// storeSettings();
+ return true;
+}
+
+void
+KexiMainWindowImpl::restoreSettings()
+{
+ d->config->setGroup("MainWindow");
+
+ // Saved settings
+ applyMainWindowSettings( d->config, "MainWindow" );
+
+ //small hack - set the default -- bottom
+// d->config->setGroup(QString(name()) + " KMdiTaskBar Toolbar style");
+ d->config->setGroup("MainWindow Toolbar KMdiTaskBar");
+ const bool tbe = d->config->readEntry("Position").isEmpty();
+ if (tbe || d->config->readEntry("Position")=="Bottom") {
+ if (tbe)
+ d->config->writeEntry("Position","Bottom");
+ moveDockWindow(m_pTaskBar, DockBottom);
+ }
+
+ d->config->setGroup("MainWindow");
+ int mdimode = d->config->readNumEntry("MDIMode", -1);//KMdi::TabPageMode);
+
+ const bool showProjectNavigator = d->config->readBoolEntry("ShowProjectNavigator", true);
+
+ switch(mdimode)
+ {
+/* case KMdi::ToplevelMode:
+ switchToToplevelMode();
+ m_pTaskBar->switchOn(true);
+ break;*/
+ case KMdi::ChildframeMode:
+ switchToChildframeMode(false);
+ m_pTaskBar->switchOn(true);
+
+ // restore a possible maximized Childframe mode,
+ // will be used in KexiMainWindowImpl::addWindow()
+ d->maximizeFirstOpenedChildFrm = d->config->readBoolEntry("maximized childframes", true);
+ setEnableMaximizedChildFrmMode(d->maximizeFirstOpenedChildFrm);
+
+ if (!showProjectNavigator) {
+ //it's visible by default but we want to hide it on navigator creation
+ d->forceHideProjectNavigatorOnCreation = true;
+ }
+
+ break;
+
+#define DEFAULT_MDI_MODE KMdi::IDEAlMode
+
+ case DEFAULT_MDI_MODE:
+ default:
+ switchToIDEAlMode(false);
+ if (showProjectNavigator) {
+ //it's invisible by default but we want to show it on navigator creation
+ d->forceShowProjectNavigatorOnCreation = true;
+ }
+ break;
+/* case KMdi::TabPageMode:
+ switchToTabPageMode();
+ break;
+*/
+ }
+
+#if 0
+ if ( !initialGeometrySet() ) {
+ // Default size
+// int restoredWidth, restoredHeight;
+ int scnum = QApplication::desktop()->screenNumber(parentWidget());
+ QRect desk = QApplication::desktop()->screenGeometry(scnum);
+//#if KDE_IS_VERSION(3,1,90)
+// restoredWidth = KGlobalSettings::screenGeometry(scnum).width();
+ // restoredHeight = KGlobalSettings::screenGeometry(scnum).height();
+//#else
+// restoredWidth = QApplication::desktop()->width();
+// restoredHeight = QApplication::desktop()->height();
+//#endif
+/* if (restoredWidth > 1100) {// very big desktop ?
+ restoredWidth = 1000;
+ restoredHeight = 800;
+ }
+ if (restoredWidth > 850) {// big desktop ?
+ restoredWidth = 800;
+ restoredHeight = 600;
+ }
+ else {// small (800x600, 640x480) desktop
+ restoredWidth = QMIN( restoredWidth, 600 );
+ restoredHeight = QMIN( restoredHeight, 400 );
+ }*/
+
+ config->setGroup("MainWindow");
+ QSize s ( config->readNumEntry( QString::fromLatin1("Width %1").arg(desk.width()), 700 ),
+ config->readNumEntry( QString::fromLatin1("Height %1").arg(desk.height()), 480 ) );
+ resize (kMin (s.width(), desk.width()), kMin(s.height(), desk.height()));
+ }
+#endif
+}
+
+void
+KexiMainWindowImpl::storeSettings()
+{
+ kdDebug() << "KexiMainWindowImpl::storeSettings()" << endl;
+
+// saveWindowSize( d->config ); //instance()->config() );
+ saveMainWindowSettings( d->config, "MainWindow" );
+ d->config->setGroup("MainWindow");
+ KMdi::MdiMode modeToSave = mdiMode();
+ if (d->mdiModeToSwitchAfterRestart!=(KMdi::MdiMode)0)
+ modeToSave = d->mdiModeToSwitchAfterRestart;
+ if (modeToSave == DEFAULT_MDI_MODE)
+ d->config->deleteEntry("MDIMode");
+ else
+ d->config->writeEntry("MDIMode", modeToSave);
+ d->config->writeEntry("maximized childframes", isInMaximizedChildFrmMode());
+
+// if (manager()->findWidgetParentDock(d->nav)->isVisible())
+ if (d->saveSettingsForShowProjectNavigator) {
+ if (d->navWasVisibleBeforeProjectClosing)
+ d->config->deleteEntry("ShowProjectNavigator");
+ else
+ d->config->writeEntry("ShowProjectNavigator", false);
+ }
+
+ if (modeToSave==KMdi::ChildframeMode || modeToSave==KMdi::TabPageMode) {
+ if (d->propEditor && d->propEditorDockSeparatorPos >= 0 && d->propEditorDockSeparatorPos <= 100) {
+ d->config->setGroup("MainWindow");
+ d->config->writeEntry("RightDockPosition", d->propEditorDockSeparatorPos);
+ }
+ else
+ d->propEditorDockSeparatorPos = 80;
+ if (d->nav && d->navDockSeparatorPos >= 0 && d->navDockSeparatorPos <= 100) {
+ d->config->setGroup("MainWindow");
+ //KDockWidget *dw = (KDockWidget *)d->nav->parentWidget();
+ //int w = dw->width();
+ //int ww = width();
+ //int d1 = (100 * dw->width()) / width() + 1;
+ //KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+ //int d2 = ds->separatorPosInPercent();
+ if (d->wasAutoOpen && d->dialogExistedBeforeCloseProject) {
+#ifdef Q_WS_WIN
+ d->config->writeEntry("LeftDockPositionWithAutoOpen",
+ d->navDockSeparatorPos);
+#endif
+// d->config->writeEntry("LeftDockPosition", dw->width());
+// d->config->writeEntry("LeftDockPosition", d->nav->width());
+ } else {
+#ifdef Q_WS_WIN
+ if (d->dialogExistedBeforeCloseProject)
+ d->config->writeEntry("LeftDockPosition", d->navDockSeparatorPos);
+ else
+ d->config->writeEntry("LeftDockPosition", qRound(double(d->navDockSeparatorPos) / 0.77
+ / (double(d->propEditorDockSeparatorPos) / 80) ));
+#endif
+ }
+ }
+ }
+
+ if (d->propEditor) {
+ d->config->setGroup("PropertyEditor");
+ d->config->writeEntry("FontSize", d->propEditorTabWidget->font().pixelSize());
+ }
+}
+
+void
+KexiMainWindowImpl::restoreWindowConfiguration(KConfig *config)
+{
+ kdDebug()<<"preparing session restoring"<<endl;
+
+ config->setGroup("MainWindow");
+
+ QString dockGrp;
+
+ if (kapp->isRestored())
+ dockGrp=config->group()+"-Docking";
+ else
+ dockGrp="MainWindow0-Docking";
+
+ if (config->hasGroup(dockGrp))
+ readDockConfig(config,dockGrp);
+}
+
+void
+KexiMainWindowImpl::storeWindowConfiguration(KConfig *config)
+{
+ kdDebug()<<"preparing session saving"<<endl;
+ config->setGroup("MainWindow");
+ QString dockGrp;
+
+#if KDE_IS_VERSION(3,1,9) && !defined(Q_WS_WIN)
+ if (kapp->sessionSaving())
+ dockGrp=config->group()+"-Docking";
+ else
+#endif
+ dockGrp="MainWindow0-Docking";
+
+ kdDebug()<<"Before write dock config"<<endl;
+ writeDockConfig(config,dockGrp);
+ kdDebug()<<"After write dock config"<<endl;
+}
+
+void
+KexiMainWindowImpl::readProperties(KConfig *config) {
+ restoreWindowConfiguration(config);
+}
+
+void
+KexiMainWindowImpl::saveProperties(KConfig *config)
+{
+ storeWindowConfiguration(config);
+// m_docManager->saveDocumentList (config);
+ // m_projectManager->saveProjectList (config);
+}
+
+void
+KexiMainWindowImpl::saveGlobalProperties( KConfig* sessionConfig ) {
+ storeWindowConfiguration(sessionConfig);
+}
+
+void
+KexiMainWindowImpl::registerChild(KexiDialogBase *dlg)
+{
+ kdDebug() << "KexiMainWindowImpl::registerChild()" << endl;
+ connect(dlg, SIGNAL(activated(KMdiChildView *)),
+ this, SLOT(activeWindowChanged(KMdiChildView *)));
+ connect(dlg, SIGNAL(dirtyChanged(KexiDialogBase*)),
+ this, SLOT(slotDirtyFlagChanged(KexiDialogBase*)));
+
+// connect(dlg, SIGNAL(childWindowCloseRequest(KMdiChildView *)), this, SLOT(childClosed(KMdiChildView *)));
+ if(dlg->id() != -1) {
+ d->insertDialog(dlg);
+ }
+ kdDebug() << "KexiMainWindowImpl::registerChild() ID = " << dlg->id() << endl;
+
+ if (m_mdiMode==KMdi::ToplevelMode || m_mdiMode==KMdi::ChildframeMode) {//kmdi fix
+ //js TODO: check if taskbar is switched in menu
+ if (m_pTaskBar && !m_pTaskBar->isSwitchedOn())
+ m_pTaskBar->switchOn(true);
+ }
+ //KMdiChildFrm *frm = dlg->mdiParent();
+ //if (frm) {
+// dlg->setMargin(20);
+ //dlg->setLineWidth(20);
+ //}
+}
+
+void
+KexiMainWindowImpl::updateDialogViewGUIClient(KXMLGUIClient *viewClient)
+{
+ if (viewClient!=d->curDialogViewGUIClient) {
+ //view clients differ
+ kdDebug()<<"KexiMainWindowImpl::activeWindowChanged(): old view gui client:"
+ <<(d->curDialogViewGUIClient ? d->curDialogViewGUIClient->xmlFile() : "")
+ <<" new view gui client: "<<( viewClient ? viewClient->xmlFile() : "") <<endl;
+ if (d->curDialogViewGUIClient) {
+ guiFactory()->removeClient(d->curDialogViewGUIClient);
+ }
+ if (viewClient) {
+ if (d->closedDialogViewGUIClient) {
+ //ooh, there is a client which dialog is already closed -- BUT it is the same client as our
+ //so: give up
+ }
+ else {
+ guiFactory()->addClient(viewClient);
+ }
+ }
+ }
+}
+
+void KexiMainWindowImpl::updateCustomPropertyPanelTabs(KexiDialogBase *prevDialog, int prevViewMode)
+{
+ updateCustomPropertyPanelTabs(
+ prevDialog ? prevDialog->part() : 0,
+ prevDialog ? prevDialog->currentViewMode() : prevViewMode,
+ d->curDialog ? d->curDialog->part() : 0,
+ d->curDialog ? d->curDialog->currentViewMode() : Kexi::NoViewMode
+ );
+}
+
+void KexiMainWindowImpl::updateCustomPropertyPanelTabs(
+ KexiPart::Part *prevDialogPart, int prevViewMode, KexiPart::Part *curDialogPart, int curViewMode )
+{
+ if (!d->propEditorTabWidget)
+ return;
+
+ if (!curDialogPart
+ || (/*prevDialogPart &&*/ curDialogPart
+ && (prevDialogPart!=curDialogPart || prevViewMode!=curViewMode)
+ ))
+ {
+ if (d->partForPreviouslySetupPropertyPanelTabs) {
+ //remember current page number for this part
+ if (prevViewMode==Kexi::DesignViewMode &&
+ ((KexiPart::Part*)d->partForPreviouslySetupPropertyPanelTabs != curDialogPart) //part changed
+ || curViewMode!=Kexi::DesignViewMode) //..or switching to other view mode
+ {
+ d->recentlySelectedPropertyPanelPages.insert( d->partForPreviouslySetupPropertyPanelTabs,
+ d->propEditorTabWidget->currentPageIndex() );
+ }
+ }
+
+ //delete old custom tabs (other than 'property' tab)
+ const uint count = d->propEditorTabWidget->count();
+ for (uint i=1; i < count; i++)
+ d->propEditorTabWidget->removePage( d->propEditorTabWidget->page(1) );
+ }
+
+ //don't change anything if part is not switched nor view mode changed
+ if ((!prevDialogPart && !curDialogPart)
+ || (prevDialogPart == curDialogPart && prevViewMode==curViewMode)
+ || (curDialogPart && curViewMode!=Kexi::DesignViewMode))
+ {
+ //new part for 'previously setup tabs'
+ d->partForPreviouslySetupPropertyPanelTabs = curDialogPart;
+ return;
+ }
+
+ if (curDialogPart) {
+ //recreate custom tabs
+ curDialogPart->setupCustomPropertyPanelTabs(d->propEditorTabWidget, this);
+
+ //restore current page number for this part
+ if (d->recentlySelectedPropertyPanelPages.contains( curDialogPart )) {
+ d->propEditorTabWidget->setCurrentPage(
+ d->recentlySelectedPropertyPanelPages[ curDialogPart ]
+ );
+ }
+ }
+
+ //new part for 'previously setup tabs'
+ d->partForPreviouslySetupPropertyPanelTabs = curDialogPart;
+}
+
+void KexiMainWindowImpl::activeWindowChanged(KMdiChildView *v)
+{
+ KexiDialogBase *dlg = static_cast<KexiDialogBase *>(v);
+ kdDebug() << "KexiMainWindowImpl::activeWindowChanged() to = " << (dlg ? dlg->caption() : "<none>") << endl;
+
+ KXMLGUIClient *client=0; //common for all views
+ KXMLGUIClient *viewClient=0; //specific for current dialog's view
+ KexiDialogBase* prevDialog = d->curDialog;
+
+ if (!dlg)
+ client=0;
+ else if ( dlg->isRegistered()) {
+// client=dlg->guiClient();
+ client=dlg->commonGUIClient();
+ viewClient=dlg->guiClient();
+ if (d->closedDialogGUIClient) {
+ if (client!=d->closedDialogGUIClient) {
+ //ooh, there is a client which dialog is already closed -- and we don't want it
+ guiFactory()->removeClient(d->closedDialogGUIClient);
+ d->closedDialogGUIClient=0;
+ }
+ }
+ if (d->closedDialogViewGUIClient) {
+ if (viewClient!=d->closedDialogViewGUIClient) {
+ //ooh, there is a client which dialog is already closed -- and we don't want it
+ guiFactory()->removeClient(d->closedDialogViewGUIClient);
+ d->closedDialogViewGUIClient=0;
+ }
+ }
+ if (client!=d->curDialogGUIClient) {
+ //clients differ
+ kdDebug()<<"KexiMainWindowImpl::activeWindowChanged(): old gui client:"
+ <<(d->curDialogGUIClient ? d->curDialogGUIClient->xmlFile() : "")
+ <<" new gui client: "<<( client ? client->xmlFile() : "") <<endl;
+ if (d->curDialogGUIClient) {
+ guiFactory()->removeClient(d->curDialogGUIClient);
+ d->curDialog->detachFromGUIClient();
+ }
+ if (client) {
+ if (d->closedDialogGUIClient) {
+ //ooh, there is a client which dialog is already closed -- BUT it is the same client as our
+ //so: give up
+ }
+ else {
+ guiFactory()->addClient(client);
+ }
+ dlg->attachToGUIClient();
+ }
+ } else {
+ //clients are the same
+ if ((KexiDialogBase*)d->curDialog!=dlg) {
+ if (d->curDialog)
+ d->curDialog->detachFromGUIClient();
+ if (dlg)
+ dlg->attachToGUIClient();
+ }
+ }
+ updateDialogViewGUIClient(viewClient);
+/* if (viewClient!=d->curDialogViewGUIClient) {
+ //view clients differ
+ kdDebug()<<"KexiMainWindowImpl::activeWindowChanged(): old view gui client:"
+ <<d->curDialogViewGUIClient<<" new view gui client: "<<viewClient<<endl;
+ if (d->curDialogViewGUIClient) {
+ guiFactory()->removeClient(d->curDialogViewGUIClient);
+ }
+ if (viewClient) {
+ if (d->closedDialogViewGUIClient) {
+ //ooh, there is a client which dialog is already closed -- BUT it is the same client as our
+ //so: give up
+ }
+ else {
+ guiFactory()->addClient(viewClient);
+ }
+ }
+ }*/
+ }
+ bool update_dlg_caption = dlg && dlg!=(KexiDialogBase*)d->curDialog && dlg->mdiParent();
+
+ if (d->curDialogGUIClient && !client)
+ guiFactory()->removeClient(d->curDialogGUIClient);
+ d->curDialogGUIClient=client;
+
+ if (d->curDialogViewGUIClient && !viewClient)
+ guiFactory()->removeClient(d->curDialogViewGUIClient);
+ d->curDialogViewGUIClient=viewClient;
+
+ bool dialogChanged = ((KexiDialogBase*)d->curDialog)!=dlg;
+
+ if (dialogChanged) {
+ if (d->curDialog) {
+ //inform previously activated dialog about deactivation
+ d->curDialog->deactivate();
+ }
+ }
+ d->curDialog=dlg;
+
+//moved below: propertySetSwitched(d->curDialog);
+
+ updateCustomPropertyPanelTabs(prevDialog, prevDialog ? prevDialog->currentViewMode() : Kexi::NoViewMode);
+
+ // inform the current view of the new dialog about property switching
+ // (this will also call KexiMainWindowImpl::propertySetSwitched() to update the current property editor's set
+ if (dialogChanged && d->curDialog)
+ d->curDialog->selectedView()->propertySetSwitched();
+
+ if (dialogChanged) {
+// invalidateSharedActions();
+ //update property editor's contents...
+// if ((KexiPropertyBuffer*)d->propBuffer!=d->curDialog->propertyBuffer()) {
+// propertyBufferSwitched();//d->curDialog);
+// d->propBuffer = d->curDialog->propertyBuffer();
+// d->propEditor->editor()->setBuffer( d->propBuffer );
+// }
+ if (d->curDialog && d->curDialog->currentViewMode()!=0) //on opening new dialog it can be 0; we don't want this
+ d->updatePropEditorVisibility(d->curDialog->currentViewMode());
+ }
+
+ //update caption...
+ if (update_dlg_caption) {//d->curDialog is != null for sure
+ slotCaptionForCurrentMDIChild(d->curDialog->mdiParent()->state()==KMdiChildFrm::Maximized);
+ }
+// if (!d->curDialog.isNull())
+// d->last_checked_mode = d->actions_for_view_modes[ d->curDialog->currentViewMode() ];
+ invalidateViewModeActions();
+ invalidateActions();
+ d->updateFindDialogContents();
+ if (dlg)
+ dlg->setFocus();
+}
+
+bool
+KexiMainWindowImpl::activateWindow(int id)
+{
+ kdDebug() << "KexiMainWindowImpl::activateWindow()" << endl;
+#ifndef KEXI_NO_PENDING_DIALOGS
+ Private::PendingJobType pendingType;
+ return activateWindow( d->openedDialogFor( id, pendingType ) );
+#else
+ return activateWindow( d->openedDialogFor( id ) );
+#endif
+}
+
+bool
+KexiMainWindowImpl::activateWindow(KexiDialogBase *dlg)
+{
+ kdDebug() << "KexiMainWindowImpl::activateWindow(KexiDialogBase *)" << endl;
+ if(!dlg)
+ return false;
+
+ d->focus_before_popup = dlg;
+ dlg->activate();
+ return true;
+}
+
+void
+KexiMainWindowImpl::childClosed(KMdiChildView *v)
+{
+ KexiDialogBase *dlg = static_cast<KexiDialogBase *>(v);
+ d->removeDialog(dlg->id());
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->removePendingDialog(dlg->id());
+#endif
+
+ //focus navigator if nothing else available
+ if (d->openedDialogsCount() == 0)
+ d->nav->setFocus();
+}
+
+void
+KexiMainWindowImpl::slotShowSettings()
+{
+ KEXI_UNFINISHED(d->action_configure->text());
+//TODO KexiSettings s(this);
+// s.exec();
+}
+
+void
+KexiMainWindowImpl::slotConfigureKeys()
+{
+/* KKeyDialog dlg;
+ dlg.insert( actionCollection() );
+ dlg.configure();*/
+ KKeyDialog::configure( actionCollection(), false/*bAllowLetterShortcuts*/, this );
+}
+
+void
+KexiMainWindowImpl::slotConfigureToolbars()
+{
+ KEditToolbar edit(factory());
+// connect(&edit,SIGNAL(newToolbarConfig()),this,SLOT(slotNewToolbarConfig()));
+ (void) edit.exec();
+}
+
+void
+KexiMainWindowImpl::slotProjectNew()
+{
+ if (!d->prj) {
+ //create within this instance
+ createBlankProject();
+ return;
+ }
+//TODO use KexiStartupDialog(KexiStartupDialog::Templates...)
+
+ bool cancel;
+ QString fileName;
+ KexiProjectData *new_data = createBlankProjectData(
+ cancel,
+ false, /* do not confirm prj overwrites: user will be asked on process startup */
+ &fileName //shortcut fname
+ );
+ if (!new_data)
+ return;
+
+ QStringList args;
+ args << qApp->applicationFilePath() << "-create-opendb";
+ if (new_data->connectionData()->fileName().isEmpty()) {
+ //server based - pass .kexic file
+ if (fileName.isEmpty())
+ return;
+ args << new_data->databaseName() << fileName;
+ //args << "--skip-conn-dialog"; //user does not expect conn. dialog to be shown here
+ }
+ else {
+ //file based
+ fileName = new_data->connectionData()->fileName();
+ args << fileName;
+ }
+//todo: pass new_data->caption()
+ //start new instance
+//! @todo use KProcess?
+ QProcess proc(args, this, "process");
+ proc.setCommunication((QProcess::Communication)0);
+// proc.setWorkingDirectory( QFileInfo(new_data->connectionData()->fileName()).dir(true) );
+ proc.setWorkingDirectory( QFileInfo(fileName).dir(true) );
+ if (!proc.start()) {
+ d->showStartProcessMsg(args);
+ }
+ delete new_data;
+}
+
+void
+KexiMainWindowImpl::createKexiProject(KexiProjectData* new_data)
+{
+ d->prj = new KexiProject( new_data, this );
+// d->prj = ::createKexiProject(new_data);
+//provided by KexiMessageHandler connect(d->prj, SIGNAL(error(const QString&,KexiDB::Object*)), this, SLOT(showErrorMessage(const QString&,KexiDB::Object*)));
+//provided by KexiMessageHandler connect(d->prj, SIGNAL(error(const QString&,const QString&)), this, SLOT(showErrorMessage(const QString&,const QString&)));
+ connect(d->prj, SIGNAL(itemRenamed(const KexiPart::Item&, const QCString&)), this, SLOT(slotObjectRenamed(const KexiPart::Item&, const QCString&)));
+
+ if (d->nav)
+ connect(d->prj, SIGNAL(itemRemoved(const KexiPart::Item&)), d->nav, SLOT(slotRemoveItem(const KexiPart::Item&)));
+}
+
+KexiProjectData*
+KexiMainWindowImpl::createBlankProjectData(bool &cancelled, bool confirmOverwrites,
+ QString* shortcutFileName)
+{
+ cancelled = false;
+ KexiNewProjectWizard wiz(Kexi::connset(), 0, "KexiNewProjectWizard", true);
+ wiz.setConfirmOverwrites(confirmOverwrites);
+ if (wiz.exec() != QDialog::Accepted) {
+ cancelled=true;
+ return 0;
+ }
+
+ KexiProjectData *new_data;
+
+ if (shortcutFileName)
+ *shortcutFileName = QString::null;
+ if (wiz.projectConnectionData()) {
+ //server-based project
+ KexiDB::ConnectionData *cdata = wiz.projectConnectionData();
+ kdDebug() << "DBNAME: " << wiz.projectDBName() << " SERVER: " << cdata->serverInfoString() << endl;
+ new_data = new KexiProjectData( *cdata, wiz.projectDBName(), wiz.projectCaption() );
+ if (shortcutFileName)
+ *shortcutFileName = Kexi::connset().fileNameForConnectionData(cdata);
+ }
+ else if (!wiz.projectDBName().isEmpty()) {
+ //file-based project
+ KexiDB::ConnectionData cdata;
+ cdata.caption = wiz.projectCaption();
+ cdata.driverName = KexiDB::Driver::defaultFileBasedDriverName();
+ cdata.setFileName( wiz.projectDBName() );
+ new_data = new KexiProjectData( cdata, wiz.projectDBName(), wiz.projectCaption() );
+ }
+ else {
+ cancelled = true;
+ return 0;
+ }
+ return new_data;
+}
+
+tristate
+KexiMainWindowImpl::createBlankProject()
+{
+ bool cancel;
+ KexiProjectData *new_data = createBlankProjectData(cancel);
+ if (cancel)
+ return cancelled;
+ if (!new_data)
+ return false;
+
+ createKexiProject( new_data );
+
+ tristate res = d->prj->create(true /*overwrite*/ );
+ if (res != true) {
+ delete d->prj;
+ d->prj = 0;
+ return res;
+ }
+ kdDebug() << "KexiMainWindowImpl::slotProjectNew(): new project created --- " << endl;
+ initNavigator();
+ Kexi::recentProjects().addProjectData( new_data );
+
+ invalidateActions();
+ updateAppCaption();
+ return true;
+}
+
+void
+KexiMainWindowImpl::slotProjectOpen()
+{
+ KexiStartupDialog dlg(
+ KexiStartupDialog::OpenExisting, 0, Kexi::connset(), Kexi::recentProjects(),
+ this, "KexiOpenDialog");
+
+ if (dlg.exec()!=QDialog::Accepted)
+ return;
+
+ openProject(dlg.selectedFileName(), dlg.selectedExistingConnection());
+}
+
+tristate KexiMainWindowImpl::openProject(const QString& aFileName,
+ const QString& fileNameForConnectionData, const QString& dbName)
+{
+ if (d->prj)
+ return openProjectInExternalKexiInstance(aFileName, fileNameForConnectionData, dbName);
+
+ KexiDB::ConnectionData *cdata = 0;
+ if (!fileNameForConnectionData.isEmpty()) {
+ cdata = Kexi::connset().connectionDataForFileName( fileNameForConnectionData );
+ if (!cdata) {
+ kdWarning() << "KexiMainWindowImpl::openProject() cdata?" << endl;
+ return false;
+ }
+ }
+ return openProject(aFileName, cdata, dbName);
+}
+
+tristate KexiMainWindowImpl::openProject(const QString& aFileName,
+ KexiDB::ConnectionData *cdata, const QString& dbName,
+ const QValueList<KexiProjectData::ObjectInfo>& autoopenObjects)
+{
+ if (d->prj) {
+ return openProjectInExternalKexiInstance(aFileName, cdata, dbName);
+ }
+
+ KexiProjectData* projectData = 0;
+ bool deleteAfterOpen = false;
+ if (cdata) {
+ //server-based project
+ if (dbName.isEmpty()) {//no database name given, ask user
+ bool cancel;
+ projectData = Kexi::startupHandler().selectProject( cdata, cancel, this );
+ if (cancel)
+ return cancelled;
+ }
+ else {
+//! @todo caption arg?
+ projectData = new KexiProjectData( *cdata, dbName );
+ deleteAfterOpen = true;
+ }
+ }
+ else {
+// QString selFile = dlg.selectedExistingFile();
+ if (aFileName.isEmpty()) {
+ kdWarning() << "KexiMainWindowImpl::openProject(): aFileName.isEmpty()" << endl;
+ return false;
+ }
+ //file-based project
+ kdDebug() << "Project File: " << aFileName << endl;
+ KexiDB::ConnectionData cdata;
+ cdata.setFileName( aFileName );
+// cdata.driverName = KexiStartupHandler::detectDriverForFile( cdata.driverName, fileName, this );
+ QString detectedDriverName;
+ KexiStartupData::Import importActionData;
+ const tristate res = KexiStartupHandler::detectActionForFile(
+ importActionData, detectedDriverName, cdata.driverName, aFileName, this );
+ if (true != res)
+ return res;
+
+ if (importActionData) { //importing requested
+ return showProjectMigrationWizard( importActionData.mimeType, importActionData.fileName );
+ }
+ cdata.driverName = detectedDriverName;
+
+ if (cdata.driverName.isEmpty())
+ return false;
+
+ //opening requested
+ projectData = new KexiProjectData(cdata, aFileName);
+ deleteAfterOpen = true;
+ }
+ if (!projectData)
+ return false;
+ projectData->autoopenObjects = autoopenObjects;
+ const tristate res = openProject(*projectData);
+ if (deleteAfterOpen) //projectData object has been copied
+ delete projectData;
+ return res;
+}
+
+tristate KexiMainWindowImpl::openProjectInExternalKexiInstance(const QString& aFileName,
+ KexiDB::ConnectionData *cdata, const QString& dbName)
+{
+ QString fileNameForConnectionData;
+ if (aFileName.isEmpty()) { //try .kexic file
+ if (cdata)
+ fileNameForConnectionData = Kexi::connset().fileNameForConnectionData(cdata);
+ }
+ return openProjectInExternalKexiInstance(aFileName, fileNameForConnectionData, dbName);
+}
+
+tristate KexiMainWindowImpl::openProjectInExternalKexiInstance(const QString& aFileName,
+ const QString& fileNameForConnectionData, const QString& dbName)
+{
+ QString fileName(aFileName);
+ QStringList args;
+ args << qApp->applicationFilePath();
+ // open a file-based project or a server connection provided as a .kexic file
+ // (we have no other simple way to provide the startup data to a new process)
+ if (fileName.isEmpty()) { //try .kexic file
+ if (!fileNameForConnectionData.isEmpty())
+ args << "--skip-conn-dialog"; //user does not expect conn. dialog to be shown here
+
+ if (dbName.isEmpty()) { //use 'kexi --skip-conn-dialog file.kexic'
+ fileName = fileNameForConnectionData;
+ }
+ else { //use 'kexi --skip-conn-dialog --connection file.kexic dbName'
+ args << "--connection" << fileNameForConnectionData;
+ fileName = dbName;
+ }
+ }
+ if (fileName.isEmpty()) {
+ kdWarning() << "KexiMainWindowImpl::openProjectInExternalKexiInstance() fileName?" << endl;
+ return false;
+ }
+//! @todo use KRun
+ args << fileName;
+ QProcess proc(args, this, "process");
+ proc.setWorkingDirectory( QFileInfo(fileName).dir(true) );
+ const bool ok = proc.start();
+ if (!ok) {
+ d->showStartProcessMsg(args);
+ }
+ return ok;
+}
+
+void
+KexiMainWindowImpl::slotProjectOpenRecentAboutToShow()
+{
+ /*
+ //setup
+ KPopupMenu *popup = d->action_open_recent->popupMenu();
+ const int cnt = popup->count();
+ //remove older
+ for (int i = 0; i<cnt; i++) {
+ int id = popup->idAt(0);
+ if (id==d->action_open_recent_more_id)
+ break;
+ if (id>=0) {
+ popup->removeItem(id);
+ }
+ }
+ //insert current items
+ int cur_id = 0, cur_idx = 0;
+ //TODO:
+ cur_id = popup->insertItem("My example project 1", ++cur_id, cur_idx++);
+ cur_id = popup->insertItem("My example project 2", ++cur_id, cur_idx++);
+ cur_id = popup->insertItem("My example project 3", ++cur_id, cur_idx++);
+ */
+
+ //show recent databases
+ KPopupMenu *popup = d->action_open_recent->popupMenu();
+ popup->clear();
+#if 0
+ d->action_open_recent_projects_title_id = popup->insertTitle(i18n("Recently Opened Databases"));
+#endif
+// int action_open_recent_projects_title_index = popup->indexOf(d->action_open_recent_projects_title_id);
+// int count = popup->count();
+// int action_open_recent_connections_title_index = popup->indexOf(d->action_open_recent_connections_title_id);
+// for (int i=action_open_recent_projects_title_index+1;
+// i<action_open_recent_connections_title_index; i++)
+// {
+// popup->removeItemAt(action_open_recent_projects_title_index+1);
+// }
+
+// int cur_idx = action_open_recent_projects_title_index+1;
+ popup->insertItem(SmallIconSet("kexiproject_sqlite"), "My project 1");
+ popup->insertItem(SmallIconSet("kexiproject_sqlite"), "My project 2");
+ popup->insertItem(SmallIconSet("kexiproject_sqlite"), "My project 3");
+
+#if 0
+ //show recent connections
+ d->action_open_recent_connections_title_id =
+ d->action_open_recent->popupMenu()->insertTitle(i18n("Recently Connected Database Servers"));
+
+// cur_idx = popup->indexOf(d->action_open_recent_connections_title_id) + 1;
+// for (int i=cur_idx; i<count; i++) {
+// popup->removeItemAt(cur_idx);
+// }
+ popup->insertItem(SmallIconSet("socket"), "My connection 1");
+ popup->insertItem(SmallIconSet("socket"), "My connection 2");
+ popup->insertItem(SmallIconSet("socket"), "My connection 3");
+ popup->insertItem(SmallIconSet("socket"), "My connection 4");
+#endif
+}
+
+void
+KexiMainWindowImpl::slotProjectOpenRecent(int id)
+{
+ if (id<0) // || id==d->action_open_recent_more_id)
+ return;
+ kdDebug() << "KexiMainWindowImpl::slotProjectOpenRecent("<<id<<")"<<endl;
+}
+
+void
+KexiMainWindowImpl::slotProjectOpenRecentMore()
+{
+ KEXI_UNFINISHED(i18n("Open Recent"));
+}
+
+void
+KexiMainWindowImpl::slotProjectSave()
+{
+ if (!d->curDialog)
+ return;
+ saveObject( d->curDialog );
+ updateAppCaption();
+ invalidateActions();
+}
+
+void
+KexiMainWindowImpl::slotProjectSaveAs()
+{
+ KEXI_UNFINISHED(i18n("Save object as"));
+}
+
+void
+KexiMainWindowImpl::slotProjectPrint()
+{
+ if (d->curDialog && d->curDialog->partItem())
+ printItem(d->curDialog->partItem());
+}
+
+void
+KexiMainWindowImpl::slotProjectPrintPreview()
+{
+ if (d->curDialog && d->curDialog->partItem())
+ printPreviewForItem(d->curDialog->partItem());
+}
+
+void
+KexiMainWindowImpl::slotProjectPageSetup()
+{
+ if (d->curDialog && d->curDialog->partItem())
+ showPageSetupForItem(d->curDialog->partItem());
+}
+
+void KexiMainWindowImpl::slotProjectExportDataTable()
+{
+ if (d->curDialog && d->curDialog->partItem())
+ exportItemAsDataTable(d->curDialog->partItem());
+}
+
+void
+KexiMainWindowImpl::slotProjectProperties()
+{
+ //TODO: load the implementation not the ui :)
+// ProjectSettingsUI u(this);
+// u.exec();
+}
+
+void
+KexiMainWindowImpl::slotProjectClose()
+{
+ closeProject();
+}
+
+void KexiMainWindowImpl::slotProjectRelations()
+{
+ if (!d->prj)
+ return;
+ KexiDialogBase *d = KexiInternalPart::createKexiDialogInstance("relation", this, this);
+ activateWindow(d);
+}
+
+void KexiMainWindowImpl::slotImportFile()
+{
+ KEXI_UNFINISHED("Import: " + i18n("From File..."));
+}
+
+void KexiMainWindowImpl::slotImportServer()
+{
+ KEXI_UNFINISHED("Import: " + i18n("From Server..."));
+}
+
+void
+KexiMainWindowImpl::slotProjectQuit()
+{
+ if (~ closeProject())
+ return;
+ close();
+}
+
+void KexiMainWindowImpl::slotViewNavigator()
+{
+ if (!d->nav || !d->navToolWindow)
+ return;
+ if (!d->nav->isVisible())
+ makeWidgetDockVisible(d->nav);
+// makeDockVisible(dynamic_cast<KDockWidget*>(d->navToolWindow->wrapperWidget()));
+// d->navToolWindow->wrapperWidget()->show();
+// d->navToolWindow->show(KDockWidget::DockLeft, getMainDockWidget());
+
+ d->navToolWindow->wrapperWidget()->raise();
+//
+ d->block_KMdiMainFrm_eventFilter=true;
+ d->nav->setFocus();
+ d->block_KMdiMainFrm_eventFilter=false;
+}
+
+void KexiMainWindowImpl::slotViewMainArea()
+{
+ if (d->curDialog)
+ d->curDialog->setFocus();
+}
+
+void KexiMainWindowImpl::slotViewPropertyEditor()
+{
+ if (!d->propEditor || !d->propEditorToolWindow)
+ return;
+
+//js d->config->setGroup("MainWindow");
+//js ds->setSeparatorPos(d->config->readNumEntry("RightDockPosition", 80/* % */), true);
+
+ if (!d->propEditorTabWidget->isVisible())
+ makeWidgetDockVisible(d->propEditorTabWidget);
+
+
+ d->propEditorToolWindow->wrapperWidget()->raise();
+
+ d->block_KMdiMainFrm_eventFilter=true;
+ if (d->propEditorTabWidget->currentPage())
+ d->propEditorTabWidget->currentPage()->setFocus();
+ d->block_KMdiMainFrm_eventFilter=false;
+
+/*#if defined(KDOCKWIDGET_P)
+ KDockWidget *dw = (KDockWidget *)d->propEditor->parentWidget();
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+ ds->setSeparatorPos(80,true);//d->config->readNumEntry("RightDockPosition", 80), true);
+#endif*/
+}
+
+bool KexiMainWindowImpl::switchToViewMode(int viewMode)
+{
+ if (!d->curDialog) {
+ d->toggleLastCheckedMode();
+ return false;
+ }
+ if (!d->curDialog->supportsViewMode( viewMode )) {
+ showErrorMessage(i18n("Selected view is not supported for \"%1\" object.")
+ .arg(d->curDialog->partItem()->name()),
+ i18n("Selected view (%1) is not supported by this object type (%2).")
+ .arg(Kexi::nameForViewMode(viewMode))
+ .arg(d->curDialog->part()->instanceCaption()) );
+ d->toggleLastCheckedMode();
+ return false;
+ }
+ int prevViewMode = d->curDialog->currentViewMode();
+ updateCustomPropertyPanelTabs(d->curDialog->part(), prevViewMode,
+ d->curDialog->part(), viewMode );
+ tristate res = d->curDialog->switchToViewMode( viewMode );
+ if (!res) {
+ updateCustomPropertyPanelTabs(0, Kexi::NoViewMode); //revert
+ showErrorMessage(i18n("Switching to other view failed (%1).").arg(Kexi::nameForViewMode(viewMode)),
+ d->curDialog);
+ d->toggleLastCheckedMode();
+ return false;
+ }
+ if (~res) {
+ updateCustomPropertyPanelTabs(0, Kexi::NoViewMode); //revert
+ d->toggleLastCheckedMode();
+ return false;
+ }
+
+ //view changed: switch to this view's gui client
+ KXMLGUIClient *viewClient=d->curDialog->guiClient();
+ updateDialogViewGUIClient(viewClient);
+ if (d->curDialogViewGUIClient && !viewClient)
+ guiFactory()->removeClient(d->curDialogViewGUIClient);
+ d->curDialogViewGUIClient=viewClient; //remember
+
+ d->updatePropEditorVisibility(viewMode);
+ invalidateProjectWideActions();
+ invalidateSharedActions();
+ d->updateFindDialogContents();
+ return true;
+}
+
+
+void KexiMainWindowImpl::slotViewDataMode()
+{
+ switchToViewMode(Kexi::DataViewMode);
+}
+
+void KexiMainWindowImpl::slotViewDesignMode()
+{
+ switchToViewMode(Kexi::DesignViewMode);
+}
+
+void KexiMainWindowImpl::slotViewTextMode()
+{
+ switchToViewMode(Kexi::TextViewMode);
+}
+
+void KexiMainWindowImpl::closeWindow(KMdiChildView *pWnd, bool layoutTaskBar)
+{
+ if (d->insideCloseDialog && dynamic_cast<KexiDialogBase *>(pWnd)) {
+ d->windowsToClose.append(dynamic_cast<KexiDialogBase *>(pWnd));
+ return;
+ }
+ /*moved to closeDialog()
+ if (pWnd == d->curDialog && !pWnd->isAttached()) {
+ if (d->propEditor) {
+ // ah, closing detached window - better switch off property buffer right now...
+ d->propBuffer = 0;
+ d->propEditor->editor()->setBuffer( 0, false );
+ }
+ }
+ */
+ closeDialog(dynamic_cast<KexiDialogBase *>(pWnd), layoutTaskBar);
+}
+
+tristate KexiMainWindowImpl::getNewObjectInfo(
+ KexiPart::Item *partItem, KexiPart::Part *part,
+ bool& allowOverwriting, const QString& messageWhenAskingForName )
+{
+ //data was never saved in the past -we need to create a new object at the backend
+ KexiPart::Info *info = part->info();
+#ifdef KEXI_ADD_CUSTOM_OBJECT_CREATION
+# include "keximainwindowimpl_customobjcreation.h"
+#endif
+ if (!d->nameDialog) {
+ d->nameDialog = new KexiNameDialog(
+ messageWhenAskingForName, this, "nameDialog");
+ //check if that name is allowed
+ d->nameDialog->widget()->addNameSubvalidator(
+ new KexiDB::ObjectNameValidator(project()->dbConnection()->driver(), 0, "sub"));
+ }
+ else {
+ d->nameDialog->widget()->setMessageText( messageWhenAskingForName );
+ }
+ d->nameDialog->widget()->setCaptionText(partItem->caption());
+ d->nameDialog->widget()->setNameText(partItem->name());
+ d->nameDialog->setCaption(i18n("Save Object As"));
+ d->nameDialog->setDialogIcon( DesktopIcon( info->itemIcon(), KIcon::SizeMedium ) );
+ allowOverwriting = false;
+ bool found;
+ do {
+ if (d->nameDialog->exec()!=QDialog::Accepted)
+ return cancelled;
+ //check if that name already exists
+ KexiDB::SchemaData tmp_sdata;
+ tristate result = project()->dbConnection()->loadObjectSchemaData(
+ info->projectPartID(),
+ d->nameDialog->widget()->nameText(), tmp_sdata );
+ if (!result)
+ return false;
+ found = result==true;
+ if (found) {
+ if (allowOverwriting) {
+ int res = KMessageBox::warningYesNoCancel(this,
+ "<p>"+part->i18nMessage("Object \"%1\" already exists.", 0)
+ .arg(d->nameDialog->widget()->nameText())
+ +"</p><p>"+i18n("Do you want to replace it?")+"</p>", 0,
+ KGuiItem(i18n("&Replace"), "button_yes"),
+ KGuiItem(i18n("&Choose Other Name...")),
+ QString::null, KMessageBox::Notify|KMessageBox::Dangerous);
+ if (res == KMessageBox::No)
+ continue;
+ else if (res == KMessageBox::Cancel)
+ return cancelled;
+ else {//yes
+ allowOverwriting = true;
+ break;
+ }
+ }
+ else {
+ KMessageBox::information(this,
+ "<p>"+part->i18nMessage("Object \"%1\" already exists.", 0)
+ .arg(d->nameDialog->widget()->nameText())
+ +"</p><p>"+i18n("Please choose other name.")+"</p>");
+// " For example: Table \"my_table\" already exists" ,
+// "%1 \"%2\" already exists.\nPlease choose other name.")
+// .arg(dlg->part()->instanceName()).arg(d->nameDialog->widget()->nameText()));
+ continue;
+ }
+ }
+ }
+ while (found);
+
+ //update name and caption
+ partItem->setName( d->nameDialog->widget()->nameText() );
+ partItem->setCaption( d->nameDialog->widget()->captionText() );
+ return true;
+}
+
+tristate KexiMainWindowImpl::saveObject( KexiDialogBase *dlg, const QString& messageWhenAskingForName,
+ bool dontAsk)
+{
+ tristate res;
+ if (!dlg->neverSaved()) {
+ //data was saved in the past -just save again
+ res = dlg->storeData(dontAsk);
+ if (!res)
+ showErrorMessage(i18n("Saving \"%1\" object failed.").arg(dlg->partItem()->name()),
+ d->curDialog);
+ return res;
+ }
+
+ const int oldItemID = dlg->partItem()->identifier();
+
+ bool allowOverwriting = false;
+ res = getNewObjectInfo( dlg->partItem(), dlg->part(), allowOverwriting,
+ messageWhenAskingForName );
+ if (res != true)
+ return res;
+
+ res = dlg->storeNewData();
+ if (~res)
+ return cancelled;
+ if (!res) {
+ showErrorMessage(i18n("Saving new \"%1\" object failed.").arg(dlg->partItem()->name()),
+ d->curDialog);
+ return false;
+ }
+
+ //update navigator
+//this is alreday done in KexiProject::addStoredItem(): d->nav->addItem(dlg->partItem());
+ //item id changed to final one: update association in dialogs' dictionary
+// d->dialogs.take(oldItemID);
+ d->updateDialogId(dlg, oldItemID);
+ invalidateProjectWideActions();
+ return true;
+}
+
+tristate KexiMainWindowImpl::closeDialog(KexiDialogBase *dlg)
+{
+ return closeDialog(dlg, true);
+}
+
+tristate KexiMainWindowImpl::closeDialog(KexiDialogBase *dlg, bool layoutTaskBar, bool doNotSaveChanges)
+{
+ if (!dlg)
+ return true;
+ if (d->insideCloseDialog)
+ return true;
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->addItemToPendingDialogs(dlg->partItem(), Private::DialogClosingJob);
+#endif
+
+ d->insideCloseDialog = true;
+
+ if (dlg == d->curDialog && !dlg->isAttached()) {
+ if (d->propEditor) {
+ // ah, closing detached window - better switch off property buffer right now...
+ d->propBuffer = 0;
+ d->propEditor->editor()->changeSet( 0, false );
+ }
+ }
+
+ bool remove_on_closing = dlg->partItem() ? dlg->partItem()->neverSaved() : false;
+ if (dlg->dirty() && !d->forceDialogClosing && !doNotSaveChanges) {
+ //more accurate tool tips and what's this
+ KGuiItem saveChanges( KStdGuiItem::save() );
+ saveChanges.setToolTip(i18n("Save changes"));
+ saveChanges.setWhatsThis(
+ i18n( "Pressing this button will save all recent changes made in \"%1\" object." )
+ .arg(dlg->partItem()->name()) );
+ KGuiItem discardChanges( KStdGuiItem::discard() );
+ discardChanges.setWhatsThis(
+ i18n( "Pressing this button will discard all recent changes made in \"%1\" object." )
+ .arg(dlg->partItem()->name()) );
+
+ //dialog's data is dirty:
+ //--adidional message, e.g. table designer will return
+ // "Note: This table is already filled with data which will be removed."
+ // if the dlg is in design view mode.
+ QString additionalMessage = dlg->part()->i18nMessage(
+ ":additional message before saving design", dlg);
+ if (additionalMessage.startsWith(":"))
+ additionalMessage = QString::null;
+ if (!additionalMessage.isEmpty())
+ additionalMessage = "<p>"+additionalMessage+"</p>";
+
+ const int questionRes = KMessageBox::warningYesNoCancel( this,
+ "<p>"+dlg->part()->i18nMessage("Design of object \"%1\" has been modified.", dlg)
+ .arg(dlg->partItem()->name())+"</p><p>"+i18n("Do you want to save changes?")+"</p>"
+ + additionalMessage /*may be empty*/,
+ QString::null,
+ saveChanges,
+ discardChanges);
+ if (questionRes==KMessageBox::Cancel) {
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->removePendingDialog(dlg->id());
+#endif
+ d->insideCloseDialog = false;
+ d->windowsToClose.clear(); //give up with 'close all'
+ return cancelled;
+ }
+ if (questionRes==KMessageBox::Yes) {
+ //save it
+// if (!dlg->storeData())
+ tristate res = saveObject( dlg, QString::null, true /*dontAsk*/ );
+ if (!res || ~res) {
+//js:TODO show error info; (retry/ignore/cancel)
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->removePendingDialog(dlg->id());
+#endif
+ d->insideCloseDialog = false;
+ d->windowsToClose.clear(); //give up with 'close all'
+ return res;
+ }
+ remove_on_closing = false;
+ }
+ }
+
+ const int dlg_id = dlg->id(); //remember now, because removeObject() can destruct partitem object
+
+ if (remove_on_closing) {
+ //we won't save this object, and it was never saved -remove it
+ if (!removeObject( dlg->partItem(), true )) {
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->removePendingDialog(dlg->id());
+#endif
+ //msg?
+ //TODO: ask if we'd continue and return true/false
+ d->insideCloseDialog = false;
+ d->windowsToClose.clear(); //give up with 'close all'
+ return false;
+ }
+ }
+ else {
+ //not dirty now
+ if(d->nav)
+ d->nav->updateItemName( *dlg->partItem(), false );
+ }
+
+ d->removeDialog(dlg_id); //don't remove -KMDI will do that
+ //also remove from 'print setup dialogs' cache, if needed
+ int printedObjectID = 0;
+ if (d->pageSetupDialogItemID2dataItemID_map.contains(dlg_id))
+ printedObjectID = d->pageSetupDialogItemID2dataItemID_map[ dlg_id ];
+ d->pageSetupDialogs.take(printedObjectID);
+
+ KXMLGUIClient *client = dlg->commonGUIClient();
+ KXMLGUIClient *viewClient = dlg->guiClient();
+ if (d->curDialogGUIClient==client) {
+ d->curDialogGUIClient=0;
+ }
+ if (d->curDialogViewGUIClient==viewClient) {
+ d->curDialogViewGUIClient=0;
+ }
+ if (client) {
+ //sanity: ouch, it is not removed yet? - do it now
+ if (d->closedDialogGUIClient && d->closedDialogGUIClient!=client)
+ guiFactory()->removeClient(d->closedDialogGUIClient);
+ if (d->openedDialogsCount()==0) {//now there is no dialogs - remove client RIGHT NOW!
+ d->closedDialogGUIClient=0;
+ guiFactory()->removeClient(client);
+ }
+ else {
+ //remember this - and MAYBE remove later, if needed
+ d->closedDialogGUIClient=client;
+ }
+ }
+ if (viewClient) {
+ //sanity: ouch, it is not removed yet? - do it now
+ if (d->closedDialogViewGUIClient && d->closedDialogViewGUIClient!=viewClient)
+ guiFactory()->removeClient(d->closedDialogViewGUIClient);
+ if (d->openedDialogsCount()==0) {//now there is no dialogs - remove client RIGHT NOW!
+ d->closedDialogViewGUIClient=0;
+ guiFactory()->removeClient(viewClient);
+ }
+ else {
+ //remember this - and MAYBE remove later, if needed
+ d->closedDialogViewGUIClient=viewClient;
+ }
+ }
+
+ const bool isInMaximizedChildFrmMode = this->isInMaximizedChildFrmMode();
+
+ KMdiMainFrm::closeWindow(dlg, layoutTaskBar);
+
+ //focus navigator if nothing else available
+ if (d->openedDialogsCount()==0) {
+ d->maximizeFirstOpenedChildFrm = isInMaximizedChildFrmMode;
+ if (d->nav)
+ d->nav->setFocus();
+ d->updatePropEditorVisibility(0);
+ }
+
+ invalidateActions();
+ d->insideCloseDialog = false;
+ if (!d->windowsToClose.isEmpty()) //continue 'close all'
+ closeDialog(d->windowsToClose.take(0), true);
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->removePendingDialog( dlg_id );
+
+ //perform pending global action that was suspended:
+ if (!d->pendingDialogsExist()) {
+ d->executeActionWhenPendingJobsAreFinished();
+ }
+#endif
+ return true;
+}
+
+void KexiMainWindowImpl::detachWindow(KMdiChildView *pWnd,bool bShow)
+{
+ KMdiMainFrm::detachWindow(pWnd,bShow);
+ // update icon - from small to large
+ pWnd->setIcon( DesktopIcon( static_cast<KexiDialogBase *>(pWnd)->itemIcon() ) );
+// pWnd->setIcon( DesktopIcon( static_cast<KexiDialogBase *>(pWnd)->part()->info()->itemIcon() ) );
+ if (dynamic_cast<KexiDialogBase*>(pWnd))
+ dynamic_cast<KexiDialogBase*>(pWnd)->sendDetachedStateToCurrentView();
+}
+
+void KexiMainWindowImpl::attachWindow(KMdiChildView *pWnd, bool /*bShow*/, bool bAutomaticResize)
+{
+// if (bAutomaticResize || w->size().isEmpty() || (w->size() == QSize(1,1))) {
+ KMdiMainFrm::attachWindow(pWnd,true,bAutomaticResize);
+ //for dialogs in normal state: decrease dialog's height if it exceeds area contents
+ if (pWnd->mdiParent()->state() == KMdiChildFrm::Normal
+ && pWnd->geometry().bottom() > pWnd->mdiParent()->mdiAreaContentsRect().bottom())
+ {
+ QRect r = pWnd->geometry();
+ r.setBottom( pWnd->mdiParent()->mdiAreaContentsRect().bottom() - 5 );
+ pWnd->setGeometry( r );
+ }
+ // update icon - from large to small
+ pWnd->mdiParent()->setIcon( SmallIcon( static_cast<KexiDialogBase *>(pWnd)->itemIcon() ) );
+ if (dynamic_cast<KexiDialogBase*>(pWnd))
+ dynamic_cast<KexiDialogBase*>(pWnd)->sendAttachedStateToCurrentView();
+}
+
+QWidget* KexiMainWindowImpl::findWindow(QWidget *w)
+{
+ while (w && !acceptsSharedActions(w))
+ w = w->parentWidget();
+ return w;
+}
+
+bool KexiMainWindowImpl::acceptsSharedActions(QObject *w)
+{
+ return w->inherits("KexiDialogBase") || w->inherits("KexiViewBase");
+}
+
+bool KexiMainWindowImpl::eventFilter( QObject *obj, QEvent * e )
+{
+ //KexiVDebug << "eventFilter: " <<e->type() << " " <<obj->name()<<endl;
+ if (e->type()==QEvent::KeyPress) {
+ KexiVDebug << "KEY EVENT " << QString::number(static_cast<QKeyEvent*>(e)->key(), 16) << endl;
+ KexiVDebug << endl;
+ }
+ if (e->type()==QEvent::AccelOverride) {
+ //KexiVDebug << "AccelOverride EVENT " << static_cast<QKeyEvent*>(e)->key() << " " << static_cast<QKeyEvent*>(e)->state() == ControlButton << endl;
+
+ //avoid sending CTRL+Tab key twice for tabbed/ideal mode, epecially for win32
+ if (static_cast<QKeyEvent*>(e)->key()==Qt::Key_Tab && static_cast<QKeyEvent*>(e)->state() == ControlButton) {
+ if (d->action_window_next->shortcut().keyCodeQt()==Qt::Key_Tab+Qt::CTRL && d->action_window_next->shortcut().count()==1
+ && (mdiMode()==KMdi::TabPageMode || mdiMode()==KMdi::IDEAlMode))
+ {
+ static_cast<QKeyEvent*>(e)->accept();
+ }
+ }
+ }
+ if (e->type()==QEvent::Close) {
+ KexiVDebug << "Close EVENT" << endl;
+ }
+ if (e->type()==QEvent::Resize) {
+ KexiVDebug << "Resize EVENT" << endl;
+ }
+ if (e->type()==QEvent::ShowMaximized) {
+ KexiVDebug << "ShowMaximized EVENT" << endl;
+ }
+
+/* if (obj==d->propEditor) {
+ if (e->type()==QEvent::Resize) {
+ d->updatePropEditorDockWidthInfo();
+ }
+ }*/
+
+ QWidget *focus_w = 0;
+ if (obj->inherits("QPopupMenu")) {
+ /* Fixes for popup menus behaviour:
+ For hiding/showing: focus previously (d->focus_before_popup)
+ focused window, if known, otherwise focus currently focused one.
+ And: just invalidate actions.
+ */
+ if (e->type()==QEvent::Hide || e->type()==QEvent::Show) {
+ KexiVDebug << e->type() << endl;
+ focus_w = focusWindow();
+ if (!d->focus_before_popup.isNull()) {
+ d->focus_before_popup->setFocus();
+ d->focus_before_popup=0;
+ invalidateSharedActions();
+ } else {
+ if (focus_w) {
+ focus_w->setFocus();
+ invalidateSharedActions();
+ }
+ }
+ }
+ return false;
+ }
+
+ /*! On mouse click on the findow, make sure it's focused and actions are invalidated */
+ if (e->type()==QEvent::MouseButtonPress) {
+ QWidget *w = findWindow(static_cast<QWidget*>(obj));
+ KexiVDebug << "MouseButtonPress EVENT " << (w ? w->name() : 0) << endl;
+ if (w) {
+ w->setFocus();
+ invalidateSharedActions(d->curDialog);
+ }
+ }
+ QWidget *w = findWindow(static_cast<QWidget*>(obj));
+ if (e->type()==QEvent::FocusIn) {
+ focus_w = focusWindow();
+ KexiVDebug << "Focus EVENT" << endl;
+ KexiVDebug << (focus_w ? focus_w->name() : "" ) << endl;
+ KexiVDebug << "eventFilter: " <<e->type() << " " <<obj->name() <<endl;
+#ifdef KEXI_STATUSBAR_DEBUG
+ QWidget *focus_widget = focus_w ? focus_w->focusWidget() : 0;
+ d->statusBar->setStatus(QString("FOCUS VIEW: %1 %2, FOCUS WIDGET: %3 %4")
+ .arg(focus_w ? focus_w->className() : "").arg(focus_w ? focus_w->name() : "")
+ .arg(focus_widget ? focus_widget->className() : "").arg(focus_widget ? focus_widget->name() : "")
+ );
+#endif
+ }
+ else if (e->type()==QEvent::FocusOut) {
+ focus_w = focusWindow();
+ KexiVDebug << "Focus OUT EVENT" << endl;
+ KexiVDebug << (focus_w ? focus_w->name() : "" ) << endl;
+ KexiVDebug << "eventFilter: " <<e->type() << " " <<obj->name() <<endl;
+#ifdef KEXI_STATUSBAR_DEBUG
+ QWidget *focus_widget = focus_w ? focus_w->focusWidget() : 0;
+ d->statusBar->setStatus(QString("FOCUS VIEW: %1 %2, FOCUS WIDGET: %3 %4")
+ .arg(focus_w ? focus_w->className() : "").arg(focus_w ? focus_w->name() : "")
+ .arg(focus_widget ? focus_widget->className() : "").arg(focus_widget ? focus_widget->name() : "")
+ );
+#endif
+ }
+ if (e->type()==QEvent::WindowActivate) {
+ KexiVDebug << "WindowActivate EVENT" << endl;
+ KexiVDebug << "eventFilter: " <<e->type() << " " <<obj->name()<<endl;
+ }
+#if 0
+ if (e->type()==QEvent::FocusIn) {
+ if (focus_w) {
+// if (d->actionProxies[ w ])
+// if (d->actionProxies[ focus_w ]) {
+ if (actionProxyFor( focus_w )) {
+// invalidateSharedActions();
+ }
+ else {
+/* QObject* o = focusWidget();
+ while (o && !o->inherits("KexiDialogBase") && !o->inherits("KexiDockBase"))
+ o = o->parent();*/
+//js invalidateSharedActions(focus_w);
+ }
+ }
+// /*|| e->type()==QEvent::FocusOut*/) && /*(!obj->inherits("KexiDialogBase")) &&*/ d->actionProxies[ obj ]) {
+// invalidateSharedActions();
+ }
+ if (e->type()==QEvent::FocusOut && focus_w && focus_w==d->curDialog && actionProxyFor( obj )) {
+ invalidateSharedActions(d->curDialog);
+ }
+#endif
+
+ if (!d->focus_before_popup.isNull() && e->type()==QEvent::FocusOut && obj->inherits("KMenuBar")) {
+ //d->nav->setFocus();
+ d->focus_before_popup->setFocus();
+ d->focus_before_popup=0;
+ invalidateSharedActions(d->curDialog);
+ return true;
+ }
+
+ //remember currently focued window invalidate act.
+ if (e->type()==QEvent::FocusOut) {
+ if (static_cast<QFocusEvent*>(e)->reason()==QFocusEvent::Popup) {
+ if (KexiUtils::hasParent(d->curDialog, focus_w)) {
+ invalidateSharedActions(d->curDialog);
+ d->focus_before_popup=d->curDialog;
+ }
+ else {
+//not needed??? invalidateSharedActions(focus_w);
+ d->focus_before_popup=focus_w;
+ }
+ }
+ }
+
+ //keep focus in main window:
+ if (w && w==d->nav) {
+// kdDebug() << "NAV" << endl;
+ if (e->type()==QEvent::FocusIn) {
+ return true;
+ } else if (e->type()==QEvent::WindowActivate && w==d->focus_before_popup) {
+// d->nav->setFocus();
+ d->focus_before_popup=0;
+ return true;
+ } else if (e->type()==QEvent::FocusOut) {
+ if (static_cast<QFocusEvent*>(e)->reason()==QFocusEvent::Tab) {
+ //activate current child:
+ if (d->curDialog) {
+ d->curDialog->activate();
+ return true;
+ }
+ }
+ else if (static_cast<QFocusEvent*>(e)->reason()==QFocusEvent::Popup) {
+ d->focus_before_popup=w;
+ }
+ //invalidateSharedActions();
+ } else if (e->type()==QEvent::Hide) {
+ setFocus();
+ return false;
+ }
+ }
+ if (d->block_KMdiMainFrm_eventFilter)//we don't want KMDI to eat our event!
+ return false;
+ return KMdiMainFrm::eventFilter(obj,e);//let KMDI do its work
+}
+
+bool KexiMainWindowImpl::openingAllowed(KexiPart::Item* item, int viewMode)
+{
+ //! @todo this can be more complex once we deliver ACLs...
+ if (!userMode())
+ return true;
+ KexiPart::Part * part = Kexi::partManager().partForMimeType(item->mimeType());
+ return part && (part->supportedUserViewModes() & viewMode);
+}
+
+KexiDialogBase *
+KexiMainWindowImpl::openObject(const QCString& mimeType, const QString& name,
+ int viewMode, bool &openingCancelled, QMap<QString,QString>* staticObjectArgs)
+{
+ KexiPart::Item *item = d->prj->itemForMimeType(mimeType,name);
+ if (!item)
+ return 0;
+ return openObject(item, viewMode, openingCancelled, staticObjectArgs);
+}
+
+KexiDialogBase *
+KexiMainWindowImpl::openObject(KexiPart::Item* item, int viewMode, bool &openingCancelled,
+ QMap<QString,QString>* staticObjectArgs, QString* errorMessage)
+{
+ if (!openingAllowed(item, viewMode)) {
+ if (errorMessage)
+ *errorMessage = i18n("opening is not allowed in \"data view/design view/text view\" mode",
+ "opening is not allowed in \"%1\" mode").arg(Kexi::nameForViewMode(viewMode));
+ openingCancelled = true;
+ return 0;
+ }
+
+ if (!d->prj || !item)
+ return 0;
+ KexiUtils::WaitCursor wait;
+#ifndef KEXI_NO_PENDING_DIALOGS
+ Private::PendingJobType pendingType;
+ KexiDialogBase *dlg = d->openedDialogFor( item, pendingType );
+ if (pendingType != Private::NoJob) {
+ openingCancelled = true;
+ return 0;
+ }
+#else
+ KexiDialogBase *dlg = d->openedDialogFor( item );
+#endif
+ openingCancelled = false;
+
+ bool needsUpdateViewGUIClient = true;
+ if (dlg) {
+ dlg->activate();
+ if (viewMode!=dlg->currentViewMode()) {
+ if (!switchToViewMode(viewMode))
+ return 0;
+ }
+ needsUpdateViewGUIClient = false;
+ }
+ else {
+ d->updatePropEditorVisibility(viewMode);
+ KexiPart::Part *part = Kexi::partManager().partForMimeType(item->mimeType());
+ //update tabs before opening
+ updateCustomPropertyPanelTabs(d->curDialog ? d->curDialog->part() : 0,
+ d->curDialog ? d->curDialog->currentViewMode() : Kexi::NoViewMode,
+ part, viewMode);
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->addItemToPendingDialogs(item, Private::DialogOpeningJob);
+#endif
+ dlg = d->prj->openObject(this, *item, viewMode, staticObjectArgs);
+ }
+
+ if (!dlg || !activateWindow(dlg)) {
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->removePendingDialog(item->identifier());
+#endif
+ updateCustomPropertyPanelTabs(0, Kexi::NoViewMode); //revert
+ //js TODO: add error msg...
+ return 0;
+ }
+
+ if (needsUpdateViewGUIClient /*&& !userMode()*/) {
+ //view changed: switch to this view's gui client
+ KXMLGUIClient *viewClient=dlg->guiClient();
+ updateDialogViewGUIClient(viewClient);
+ if (d->curDialogViewGUIClient && !viewClient)
+ guiFactory()->removeClient(d->curDialogViewGUIClient);
+ d->curDialogViewGUIClient=viewClient; //remember
+ }
+
+ invalidateViewModeActions();
+ if (viewMode!=dlg->currentViewMode())
+ invalidateSharedActions();
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ d->removePendingDialog( dlg->id() );
+
+ //perform pending global action that was suspended:
+ if (!d->pendingDialogsExist()) {
+ d->executeActionWhenPendingJobsAreFinished();
+ }
+#endif
+ return dlg;
+}
+
+KexiDialogBase *
+KexiMainWindowImpl::openObjectFromNavigator(KexiPart::Item* item, int viewMode)
+{
+ bool openingCancelled;
+ return openObjectFromNavigator(item, viewMode, openingCancelled);
+}
+
+KexiDialogBase *
+KexiMainWindowImpl::openObjectFromNavigator(KexiPart::Item* item, int viewMode,
+ bool &openingCancelled)
+{
+ if (!openingAllowed(item, viewMode)) {
+ openingCancelled = true;
+ return 0;
+ }
+ if (!d->prj || !item)
+ return false;
+#ifndef KEXI_NO_PENDING_DIALOGS
+ Private::PendingJobType pendingType;
+ KexiDialogBase *dlg = d->openedDialogFor( item, pendingType );
+ if (pendingType!=Private::NoJob) {
+ openingCancelled = true;
+ return 0;
+ }
+#else
+ KexiDialogBase *dlg = d->openedDialogFor( item );
+#endif
+ openingCancelled = false;
+ if (dlg) {
+ if (activateWindow(dlg)) {//item->identifier())) {//just activate
+ invalidateViewModeActions();
+ return dlg;
+ }
+ }
+ //if DataViewMode is not supported, try Design, then Text mode (currently useful for script part)
+ KexiPart::Part *part = Kexi::partManager().partForMimeType(item->mimeType());
+ if (!part)
+ return 0;
+ if (viewMode == Kexi::DataViewMode && !(part->supportedViewModes() & Kexi::DataViewMode)) {
+ if (part->supportedViewModes() & Kexi::DesignViewMode)
+ return openObjectFromNavigator( item, Kexi::DesignViewMode, openingCancelled );
+ else if (part->supportedViewModes() & Kexi::TextViewMode)
+ return openObjectFromNavigator( item, Kexi::TextViewMode, openingCancelled );
+ }
+ //do the same as in openObject()
+ return openObject(item, viewMode, openingCancelled);
+}
+
+tristate KexiMainWindowImpl::closeObject(KexiPart::Item* item)
+{
+#ifndef KEXI_NO_PENDING_DIALOGS
+ Private::PendingJobType pendingType;
+ KexiDialogBase *dlg = d->openedDialogFor( item, pendingType );
+ if (pendingType == Private::DialogClosingJob)
+ return true;
+ else if (pendingType == Private::DialogOpeningJob)
+ return cancelled;
+#else
+ KexiDialogBase *dlg = d->openedDialogFor( item );
+#endif
+ if (!dlg)
+ return cancelled;
+ return closeDialog(dlg);
+}
+
+bool KexiMainWindowImpl::newObject( KexiPart::Info *info, bool& openingCancelled )
+{
+ if (userMode()) {
+ openingCancelled = true;
+ return false;
+ }
+ openingCancelled = false;
+ if (!d->prj || !info)
+ return false;
+ KexiPart::Part *part = Kexi::partManager().partForMimeType(info->mimeType());
+ if(!part)
+ return false;
+
+#ifdef KEXI_ADD_CUSTOM_OBJECT_CREATION
+# include "keximainwindowimpl_customobjcreation.h"
+#endif
+
+ KexiPart::Item *it = d->prj->createPartItem(info); //this, *item, viewMode);
+ if (!it) {
+ //js: todo: err
+ return false;
+ }
+
+ if (!it->neverSaved()) //only add stored objects to the browser
+ d->nav->addItem(*it);
+ return openObject(it, Kexi::DesignViewMode, openingCancelled);
+}
+
+tristate KexiMainWindowImpl::removeObject( KexiPart::Item *item, bool dontAsk )
+{
+ if (userMode())
+ return cancelled;
+ if (!d->prj || !item)
+ return false;
+
+ KexiPart::Part *part = Kexi::partManager().partForMimeType(item->mimeType());
+ if (!part)
+ return false;
+
+ if (!dontAsk) {
+ if (KMessageBox::No == KMessageBox::warningYesNo(this,
+ "<p>"+i18n("Do you want to permanently delete:\n"
+ "%1\n"
+ "If you click \"Delete\", you will not be able to undo the deletion.")
+ .arg( "</p><p>"+part->instanceCaption()+" \""+ item->name() + "\"?</p>" ),
+ 0, KGuiItem(i18n("Delete"), "editdelete"), KStdGuiItem::no()))
+ return cancelled;
+ }
+
+ //also close 'print setup' dialog for this item, if any
+ tristate res;
+// int printedObjectID = 0;
+// if (d->pageSetupDialogItemID2dataItemID_map.contains(item->identifier()))
+// printedObjectID = d->pageSetupDialogItemID2dataItemID_map[ item->identifier() ];
+ KexiDialogBase * pageSetupDlg = d->pageSetupDialogs[ item->identifier() ];
+ const bool oldInsideCloseDialog = d->insideCloseDialog;
+ d->insideCloseDialog = false;
+ res = closeDialog(pageSetupDlg);
+ d->insideCloseDialog = oldInsideCloseDialog;
+ if (!res || ~res) {
+ return res;
+ }
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ Private::PendingJobType pendingType;
+ KexiDialogBase *dlg = d->openedDialogFor( item, pendingType );
+ if (pendingType!=Private::NoJob) {
+ return cancelled;
+ }
+#else
+ KexiDialogBase *dlg = d->openedDialogFor( item );
+#endif
+
+ if (dlg) {//close existing window
+// if (!dlg->tryClose(true))
+ const bool tmp = d->forceDialogClosing;
+ /*const bool remove_on_closing = */dlg->partItem()->neverSaved();
+ d->forceDialogClosing = true;
+ res = closeDialog(dlg);
+ d->forceDialogClosing = tmp; //restore
+ if (!res || ~res) {
+ return res;
+ }
+// if (remove_on_closing) //already removed
+ // return true;
+// if (!dlg->close(true))
+// return true; //ok - close cancelled
+ }
+
+ //in case the dialog is a 'print setup' dialog, also update d->pageSetupDialogs
+ int dataItemID = d->pageSetupDialogItemID2dataItemID_map[item->identifier()];
+ d->pageSetupDialogItemID2dataItemID_map.remove(item->identifier());
+ d->pageSetupDialogs.take( dataItemID );
+
+ if (!d->prj->removeObject(this, *item)) {
+ //TODO(js) better msg
+ showSorryMessage( i18n("Could not remove object.") );
+ return false;
+ }
+ return true;
+}
+
+void KexiMainWindowImpl::renameObject( KexiPart::Item *item, const QString& _newName, bool &success )
+{
+ if (userMode()) {
+ success = false;
+ return;
+ }
+ d->pendingDialogsExist();
+ QString newName = _newName.stripWhiteSpace();
+ if (newName.isEmpty()) {
+ showSorryMessage( i18n("Could not set empty name for this object.") );
+ success = false;
+ return;
+ }
+ enableMessages(false); //to avoid double messages
+ const bool res = d->prj->renameObject(this, *item, newName);
+ enableMessages(true);
+ if (!res) {
+ showErrorMessage( d->prj, i18n("Renaming object \"%1\" failed.").arg(newName) );
+ success = false;
+ return;
+ }
+ d->pendingDialogsExist();
+}
+
+void KexiMainWindowImpl::slotObjectRenamed(const KexiPart::Item &item, const QCString& /*oldName*/)
+{
+#ifndef KEXI_NO_PENDING_DIALOGS
+ Private::PendingJobType pendingType;
+ KexiDialogBase *dlg = d->openedDialogFor( &item, pendingType );
+ if (pendingType!=Private::NoJob)
+ return;
+#else
+ KexiDialogBase *dlg = d->openedDialogFor( &item );
+#endif
+ if (!dlg)
+ return;
+
+ //change item
+ dlg->updateCaption();
+ if (static_cast<KexiDialogBase*>(d->curDialog)==dlg)//optionally, update app. caption
+ updateAppCaption();
+}
+
+int KexiMainWindowImpl::generatePrivateID()
+{
+ return --d->privateIDCounter;
+}
+
+void KexiMainWindowImpl::acceptPropertySetEditing()
+{
+ if (d->propEditor)
+ d->propEditor->editor()->acceptInput();
+}
+
+void KexiMainWindowImpl::propertySetSwitched(KexiDialogBase *dlg, bool force,
+ bool preservePrevSelection, const QCString& propertyToSelect)
+{
+ kdDebug() << "KexiMainWindowImpl::propertySetSwitched() d->curDialog: "
+ << (d->curDialog ? d->curDialog->caption() : QString("NULL")) << " dlg: " << (dlg ? dlg->caption() : QString("NULL"))<< endl;
+ if ((KexiDialogBase*)d->curDialog!=dlg) {
+ d->propBuffer = 0; //we'll need to move to another prop. set
+ return;
+ }
+ if (d->propEditor) {
+ KoProperty::Set *newBuf = d->curDialog ? d->curDialog->propertySet() : 0;
+ if (!newBuf || (force || static_cast<KoProperty::Set*>(d->propBuffer) != newBuf)) {
+ d->propBuffer = newBuf;
+ if (preservePrevSelection) {
+ if (propertyToSelect.isEmpty())
+ d->propEditor->editor()->changeSet( d->propBuffer, preservePrevSelection );
+ else
+ d->propEditor->editor()->changeSet( d->propBuffer, propertyToSelect );
+ }
+ }
+ }
+}
+
+void KexiMainWindowImpl::slotDirtyFlagChanged(KexiDialogBase* dlg)
+{
+ KexiPart::Item *item = dlg->partItem();
+ //update text in navigator and app. caption
+ if(!userMode())
+ d->nav->updateItemName( *item, dlg->dirty() );
+
+ invalidateActions();
+ updateAppCaption();
+}
+
+void KexiMainWindowImpl::slotMdiModeHasBeenChangedTo(KMdi::MdiMode)
+{
+ //after switching to other MDI mode, pointer to current dialog needs to be updated
+ activateFirstWin();
+ activeWindowChanged(activeWindow());
+}
+
+void KexiMainWindowImpl::slotTipOfTheDay()
+{
+ //todo
+}
+
+void KexiMainWindowImpl::slotImportantInfo()
+{
+ importantInfo(false);
+}
+
+void KexiMainWindowImpl::slotStartFeedbackAgent()
+{
+#ifndef KEXI_NO_FEEDBACK_AGENT
+#ifdef FEEDBACK_CLASS
+ const KAboutData* about = KApplication::kApplication()->aboutData();
+ FEEDBACK_CLASS* wizard = new FEEDBACK_CLASS( about->programName(),
+ about->version(), 0, 0, 0, FEEDBACK_CLASS::AllPages );
+
+ if ( wizard->exec() )
+ {
+ KApplication::kApplication()->invokeMailer( "kexi-reports-dummy@kexi.org",
+ QString::null, QString::null,
+ about->appName() + QCString( " [feedback]" ),
+ wizard->feedbackDocument().toString( 2 ).local8Bit() );
+ }
+
+ delete wizard;
+#endif
+#endif
+}
+
+void KexiMainWindowImpl::importantInfo(bool /*onStartup*/)
+{
+#if 0
+ if (onStartup && !d->showImportantInfoOnStartup)
+ return;
+
+ QString key = QString("showImportantInfo %1").arg(KEXI_VERSION_STRING);
+ d->config->setGroup("Startup");
+ bool show = d->config->readBoolEntry(key,true);
+
+ if (show || !onStartup) { //if !onStartup - dialog is always shown
+ d->config->setGroup("TipOfDay");
+ if (!d->config->hasKey("RunOnStart"))
+ d->config->writeEntry("RunOnStart",true);
+
+ QString lang = KGlobal::locale()->language();
+ QString fname = locate("data", QString("kexi/readme_")+lang);
+ if (fname.isEmpty())//back to default
+ fname = locate("data", "kexi/readme_en");
+ KTipDialog tipDialog(new KTipDatabase(QString::null), 0);
+ tipDialog.setCaption(i18n("Important Information"));
+ QObjectList *l = tipDialog.queryList( "KPushButton" );//hack: hide <- -> buttons
+ int i=0;
+ for (QObjectListIt it( *l ); it.current() && i<2; ++it, i++ )
+ static_cast<KPushButton*>(it.current())->hide();
+ QFile f(fname);
+ if ( f.open( IO_ReadOnly ) ) {
+ QTextStream ts(&f);
+ ts.setCodec( KGlobal::locale()->codecForEncoding() );
+ QTextBrowser *tb = KexiUtils::findFirstChild<KTextBrowser>(&tipDialog,"KTextBrowser");
+ if (tb) {
+ tb->setText( QString("<qt>%1</qt>").arg(ts.read()) );
+ }
+ f.close();
+ }
+
+ tipDialog.adjustSize();
+ QRect desk = QApplication::desktop()->screenGeometry( QApplication::desktop()->screenNumber(this) );
+ tipDialog.resize( QMAX(tipDialog.width(),desk.width()*3/5), QMAX(tipDialog.height(),desk.height()*3/5) );
+ KDialog::centerOnScreen(&tipDialog);
+ tipDialog.setModal ( true );
+ tipDialog.exec();
+ //a hack: get user's settings
+ d->config->setGroup("TipOfDay");
+ show = d->config->readBoolEntry("RunOnStart", show);
+ }
+
+ //write our settings back
+ d->config->setGroup("Startup");
+ d->config->writeEntry(key,show);
+ d->showImportantInfoOnStartup = false;
+#endif
+}
+
+void KexiMainWindowImpl::slotOptionsEnableForms(bool show, bool noMessage)
+{
+ Q_UNUSED(noMessage);
+ Kexi::tempShowForms() = show;
+}
+
+bool KexiMainWindowImpl::userMode() const
+{
+ return d->userMode;
+}
+
+bool
+KexiMainWindowImpl::initUserMode(KexiProjectData *projectData)
+{
+// Kexi::tempShowForms() = true;
+// Kexi::tempShowReports() = true;
+// Kexi::tempShowMacros() = true;
+// Kexi::tempShowScripts() = true;
+ if(!projectData)
+ return false;
+
+ createKexiProject(projectData); //initialize project
+// d->prj->setFinal(true); //announce that we are in fianl mode
+
+ tristate res = d->prj->open(); //try to open database
+ if (!res || ~res) {
+ delete d->prj;
+ d->prj = 0;
+ return false;
+ }
+
+#if 0 //todo reenable; autoopen objects are handled elsewhere
+ KexiDB::TableSchema *sch = d->prj->dbConnection()->tableSchema("kexi__final");
+ QString err_msg = i18n("Could not start project \"%1\" in Final Mode.")
+ .arg(static_cast<KexiDB::SchemaData*>(projectData)->name());
+ if(!sch)
+ {
+ hide();
+ showErrorMessage( err_msg, i18n("No Final Mode data found.") );
+ return false;
+ }
+
+ KexiDB::Cursor *c = d->prj->dbConnection()->executeQuery(*sch);
+ if(!c)
+ {
+ hide();
+ showErrorMessage( err_msg, i18n("Error reading Final Mode data.") );
+ return false;
+ }
+
+ QString startupPart;
+ QString startupItem;
+ while(c->moveNext())
+ {
+ kdDebug() << "KexiMainWinImpl::initFinalMode(): property: [" << c->value(1).toString() << "] " << c->value(2).toString() << endl;
+ if(c->value(1).toString() == "startup-part")
+ startupPart = c->value(2).toString();
+ else if(c->value(1).toString() == "startup-item")
+ startupItem = c->value(2).toString();
+ else if(c->value(1).toString() == "mainxmlui")
+ setXML(c->value(2).toString());
+ }
+ d->prj->dbConnection()->deleteCursor(c);
+
+ kdDebug() << "KexiMainWinImpl::initFinalMode(): part: " << startupPart << endl;
+ kdDebug() << "KexiMainWinImpl::initFinalMode(): item: " << startupItem << endl;
+
+ initActions();
+ initUserActions();
+ guiFactory()->addClient(this);
+ setStandardToolBarMenuEnabled(false);
+ setHelpMenuEnabled(false);
+
+ KexiPart::Info *i = Kexi::partManager().infoForMimeType(startupPart.latin1());
+ if (!i) {
+ hide();
+ showErrorMessage( err_msg, i18n("Specified plugin does not exist.") );
+ return false;
+ }
+
+ Kexi::partManager().part(i);
+ KexiPart::Item *item = d->prj->item(i, startupItem);
+ bool openingCancelled;
+ if(!openObject(item, Kexi::DataViewMode, openingCancelled) && !openingCancelled) {
+ hide();
+ showErrorMessage( err_msg, i18n("Specified object could not be opened.") );
+ return false;
+ }
+
+ QWidget::setCaption("MyApp");//TODO
+#endif
+ return true;
+}
+
+void
+KexiMainWindowImpl::initUserActions()
+{
+#if 0 //unused for now
+ KexiDB::Cursor *c = d->prj->dbConnection()->executeQuery("SELECT p_id, name, text, icon, method, arguments FROM kexi__useractions WHERE scope = 0");
+ if(!c)
+ return;
+
+ while(c->moveNext())
+ {
+ KexiUserAction::fromCurrentRecord(this, actionCollection(), c);
+ }
+ d->prj->dbConnection()->deleteCursor(c);
+/*
+ KexiUserAction *a1 = new KexiUserAction(this, actionCollection(), "user_dataview", "Change to dataview", "table");
+ Arguments args;
+ args.append(QVariant("kexi/table"));
+ args.append(QVariant("persons"));
+ a1->setMethod(KexiUserAction::OpenObject, args);
+*/
+#endif
+}
+
+void KexiMainWindowImpl::slotToolsProjectMigration()
+{
+ showProjectMigrationWizard(QString::null, QString::null);
+}
+
+void KexiMainWindowImpl::slotToolsCompactDatabase()
+{
+ KexiProjectData *data = 0;
+ KexiDB::Driver *drv = 0;
+ const bool projectWasOpened = d->prj;
+
+ if (!d->prj) {
+ KexiStartupDialog dlg(
+ KexiStartupDialog::OpenExisting, 0, Kexi::connset(), Kexi::recentProjects(),
+ this, "KexiOpenDialog");
+
+ if (dlg.exec()!=QDialog::Accepted)
+ return;
+
+ if (dlg.selectedFileName().isEmpty()) {
+//! @todo add support for server based if needed?
+ return;
+ }
+ KexiDB::ConnectionData cdata;
+ cdata.setFileName( dlg.selectedFileName() );
+
+ //detect driver name for the selected file
+ KexiStartupData::Import detectedImportAction;
+ tristate res = KexiStartupHandler::detectActionForFile(
+ detectedImportAction, cdata.driverName,
+ "" /*suggestedDriverName*/, cdata.fileName(), 0,
+ KexiStartupHandler::SkipMessages | KexiStartupHandler::ThisIsAProjectFile
+ | KexiStartupHandler::DontConvert);
+
+ if (true==res && !detectedImportAction)
+ drv = Kexi::driverManager().driver( cdata.driverName );
+ if (!drv || !(drv->features() & KexiDB::Driver::CompactingDatabaseSupported)) {
+ KMessageBox::information(this, "<qt>"+
+ i18n("Compacting database file <nobr>\"%1\"</nobr> is not supported.")
+ .arg(QDir::convertSeparators(cdata.fileName())));
+ return;
+ }
+ data = new KexiProjectData( cdata, cdata.fileName() );
+ }
+ else {
+ //sanity
+ if ( !(d->prj && d->prj->dbConnection()
+ && (d->prj->dbConnection()->driver()->features() & KexiDB::Driver::CompactingDatabaseSupported) ))
+ return;
+
+ if (KMessageBox::Continue != KMessageBox::warningContinueCancel(this,
+ i18n("The current project has to be closed before compacting the database. "
+ "It will be open again after compacting.\n\nDo you want to continue?")))
+ return;
+
+ data = new KexiProjectData(*d->prj->data()); // a copy
+ drv = d->prj->dbConnection()->driver();
+ const tristate res = closeProject();
+ if (~res || !res)
+ return;
+ }
+
+ if (!drv->adminTools().vacuum(*data->connectionData(), data->databaseName())) {
+ //err msg
+ showErrorMessage( &drv->adminTools() );
+ }
+
+ if (data && projectWasOpened) {
+ openProject(*data);
+ delete data;
+ }
+}
+
+tristate KexiMainWindowImpl::showProjectMigrationWizard(
+ const QString& mimeType, const QString& databaseName, const KexiDB::ConnectionData *cdata)
+{
+ //pass arguments
+ QMap<QString,QString> args;
+ args.insert("mimeType", mimeType);
+ args.insert("databaseName", databaseName);
+ if (cdata) { //pass ConnectionData serialized as a string...
+ QString str;
+ KexiUtils::serializeMap( KexiDB::toMap( *cdata ), str );
+ args.insert("connectionData", str);
+ }
+
+ QDialog *dlg = KexiInternalPart::createModalDialogInstance("migration", this, this, 0, &args);
+ if (!dlg)
+ return false; //error msg has been shown by KexiInternalPart
+
+ const int result = dlg->exec();
+ delete dlg;
+ //raise();
+ if (result!=QDialog::Accepted)
+ return cancelled;
+
+ //open imported project in a new Kexi instance
+ QString destinationDatabaseName( args["destinationDatabaseName"] );
+ QString fileName, destinationConnectionShortcut, dbName;
+ if (!destinationDatabaseName.isEmpty()) {
+ if (args.contains("destinationConnectionShortcut")) {
+ // server-based
+ destinationConnectionShortcut = args["destinationConnectionShortcut"];
+ }
+ else {
+ // file-based
+ fileName = destinationDatabaseName;
+ destinationDatabaseName = QString::null;
+ }
+ tristate res = openProject(fileName, destinationConnectionShortcut,
+ destinationDatabaseName);
+ raise();
+ return res;
+// KexiDB::ConnectionData *connData = new KexiDB::ConnectionData();
+// KexiDB::fromMap( KexiUtils::deserializeMap( args["destinationConnectionData"] ), *connData );
+// return openProject(destinationFileName, 0);
+ }
+ return true;
+}
+
+tristate KexiMainWindowImpl::executeItem(KexiPart::Item* item)
+{
+ KexiPart::Info *info = item ? Kexi::partManager().infoForMimeType(item->mimeType()) : 0;
+ if ( (! info) || (! info->isExecuteSupported()) )
+ return false;
+ KexiPart::Part *part = Kexi::partManager().part(info);
+ if (!part)
+ return false;
+ return part->execute(item);
+}
+
+void KexiMainWindowImpl::slotProjectImportDataTable()
+{
+//! @todo allow data appending (it is not possible now)
+ if (userMode())
+ return;
+ QMap<QString,QString> args;
+ args.insert("sourceType", "file");
+ QDialog *dlg = KexiInternalPart::createModalDialogInstance(
+ "csv_importexport", "KexiCSVImportDialog", this, this, 0, &args);
+ if (!dlg)
+ return; //error msg has been shown by KexiInternalPart
+ dlg->exec();
+ delete dlg;
+}
+
+tristate KexiMainWindowImpl::executeCustomActionForObject(KexiPart::Item* item,
+ const QString& actionName)
+{
+ if (actionName == "exportToCSV")
+ return exportItemAsDataTable(item);
+ else if (actionName == "copyToClipboardAsCSV")
+ return copyItemToClipboardAsDataTable(item);
+
+ kexiwarn << "KexiMainWindowImpl::executeCustomActionForObject(): no such action: "
+ << actionName << endl;
+ return false;
+}
+
+tristate KexiMainWindowImpl::exportItemAsDataTable(KexiPart::Item* item)
+{
+ if (!item)
+ return false;
+//! @todo: check if changes to this are saved, if not: ask for saving
+//! @todo: accept row changes...
+
+ QMap<QString,QString> args;
+ args.insert("destinationType", "file");
+ args.insert("itemId", QString::number(item->identifier()));
+ QDialog *dlg = KexiInternalPart::createModalDialogInstance(
+ "csv_importexport", "KexiCSVExportWizard", this, this, 0, &args);
+ if (!dlg)
+ return false; //error msg has been shown by KexiInternalPart
+ int result = dlg->exec();
+ delete dlg;
+ return result == QDialog::Rejected ? cancelled : true;
+}
+
+bool KexiMainWindowImpl::printItem(KexiPart::Item* item, const QString& titleText)
+{
+ return printItem(item, KexiSimplePrintingSettings::load(), titleText);
+}
+
+tristate KexiMainWindowImpl::printItem(KexiPart::Item* item)
+{
+ return printItem(item, QString::null);
+}
+
+bool KexiMainWindowImpl::printItem(KexiPart::Item* item, const KexiSimplePrintingSettings& settings,
+ const QString& titleText)
+{
+//! @todo: check if changes to this object's design are saved, if not: ask for saving
+//! @todo: accept row changes...
+ KexiSimplePrintingCommand cmd(this, item->identifier());
+ //modal
+ return cmd.print(settings, titleText);
+}
+
+bool KexiMainWindowImpl::printPreviewForItem(KexiPart::Item* item, const QString& titleText, bool reload)
+{
+ return printPreviewForItem(item, KexiSimplePrintingSettings::load(), titleText, reload);
+}
+
+tristate KexiMainWindowImpl::printPreviewForItem(KexiPart::Item* item)
+{
+ return printPreviewForItem(item, QString::null,
+//! @todo store cached row data?
+ true/*reload*/);
+}
+
+bool KexiMainWindowImpl::printPreviewForItem(KexiPart::Item* item,
+ const KexiSimplePrintingSettings& settings, const QString& titleText, bool reload)
+{
+//! @todo: check if changes to this object's design are saved, if not: ask for saving
+//! @todo: accept row changes...
+ KexiSimplePrintingCommand* cmd = d->openedCustomObjectsForItem<KexiSimplePrintingCommand>(
+ item, "KexiSimplePrintingCommand");
+ if (!cmd) {
+ d->addOpenedCustomObjectForItem(
+ item,
+ cmd = new KexiSimplePrintingCommand(this, item->identifier()),
+ "KexiSimplePrintingCommand"
+ );
+ }
+ return cmd->showPrintPreview(settings, titleText, reload);
+}
+
+tristate KexiMainWindowImpl::showPageSetupForItem(KexiPart::Item* item)
+{
+//! @todo: check if changes to this object's design are saved, if not: ask for saving
+//! @todo: accept row changes...
+ return printActionForItem(item, PageSetupForItem);
+}
+
+tristate KexiMainWindowImpl::printActionForItem(KexiPart::Item* item, PrintActionType action)
+{
+ if (!item)
+ return false;
+ KexiPart::Info *info = Kexi::partManager().infoForMimeType( item->mimeType() );
+ if (!info->isPrintingSupported())
+ return false;
+
+ KexiDialogBase *printingDialog = d->pageSetupDialogs[ item->identifier() ];
+ if (printingDialog) {
+ if (!activateWindow(printingDialog))
+ return false;
+ if (action == PreviewItem || action == PrintItem) {
+ QTimer::singleShot(0,printingDialog->selectedView(),
+ (action == PreviewItem) ? SLOT(printPreview()) : SLOT(print()));
+ }
+ return true;
+ }
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ Private::PendingJobType pendingType;
+ KexiDialogBase *dlg = d->openedDialogFor( item, pendingType );
+ if (pendingType!=Private::NoJob)
+ return cancelled;
+#else
+ KexiDialogBase *dlg = d->openedDialogFor( item );
+#endif
+
+ if (dlg) {
+ // accept row changes
+ QWidget *prevFocusWidget = focusWidget();
+ dlg->setFocus();
+ d->action_data_save_row->activate();
+ if (prevFocusWidget)
+ prevFocusWidget->setFocus();
+
+ // opened: check if changes made to this dialog are saved, if not: ask for saving
+ if (dlg->neverSaved()) //sanity check
+ return false;
+ if (dlg->dirty()) {
+ KGuiItem saveChanges( KStdGuiItem::save() );
+ saveChanges.setToolTip(i18n("Save changes"));
+ saveChanges.setWhatsThis(
+ i18n( "Pressing this button will save all recent changes made in \"%1\" object." )
+ .arg(item->name()) );
+ KGuiItem doNotSave( KStdGuiItem::no() );
+ doNotSave.setWhatsThis(
+ i18n( "Pressing this button will ignore all unsaved changes made in \"%1\" object." )
+ .arg(dlg->partItem()->name()) );
+
+ QString question;
+ if (action == PrintItem)
+ question = i18n("Do you want to save changes before printing?");
+ else if (action == PreviewItem)
+ question = i18n("Do you want to save changes before making print preview?");
+ else if (action == PageSetupForItem)
+ question = i18n("Do you want to save changes before showing page setup?");
+ else
+ return false;
+
+ const int questionRes = KMessageBox::warningYesNoCancel( this,
+ "<p>"+dlg->part()->i18nMessage("Design of object \"%1\" has been modified.", dlg)
+ .arg(item->name()) + "</p><p>" + question + "</p>",
+ QString::null,
+ saveChanges,
+ doNotSave);
+ if (KMessageBox::Cancel == questionRes)
+ return cancelled;
+ if (KMessageBox::Yes == questionRes) {
+ tristate savingRes = saveObject( dlg, QString::null, true /*dontAsk*/ );
+ if (true != savingRes)
+ return savingRes;
+ }
+ }
+ }
+ KexiPart::Part * printingPart = Kexi::partManager().partForMimeType("kexi/simpleprinting");
+ if (!printingPart)
+ printingPart = new KexiSimplePrintingPart(); //hardcoded as there're no .desktop file
+ KexiPart::Item* printingPartItem = d->prj->createPartItem(
+ printingPart, item->name() //<-- this will look like "table1 : printing" on the window list
+ );
+ QMap<QString,QString> staticObjectArgs;
+ staticObjectArgs["identifier"] = QString::number(item->identifier());
+ if (action == PrintItem)
+ staticObjectArgs["action"] = "print";
+ else if (action == PreviewItem)
+ staticObjectArgs["action"] = "printPreview";
+ else if (action == PageSetupForItem)
+ staticObjectArgs["action"] = "pageSetup";
+ else
+ return false;
+ bool openingCancelled;
+ printingDialog = openObject(printingPartItem, Kexi::DesignViewMode,
+ openingCancelled, &staticObjectArgs);
+ if (openingCancelled)
+ return cancelled;
+ if (!printingDialog) //sanity
+ return false;
+ d->pageSetupDialogs.insert(item->identifier(), printingDialog);
+ d->pageSetupDialogItemID2dataItemID_map.insert(
+ printingDialog->partItem()->identifier(), item->identifier());
+
+ return true;
+}
+
+void KexiMainWindowImpl::slotEditCopySpecialDataTable()
+{
+ KexiPart::Item* item = d->nav->selectedPartItem();
+ if (item)
+ exportItemAsDataTable(item);
+}
+
+tristate KexiMainWindowImpl::copyItemToClipboardAsDataTable(KexiPart::Item* item)
+{
+ if (!item)
+ return false;
+
+ QMap<QString,QString> args;
+ args.insert("destinationType", "clipboard");
+ args.insert("itemId", QString::number(item->identifier()));
+ QDialog *dlg = KexiInternalPart::createModalDialogInstance(
+ "csv_importexport", "KexiCSVExportWizard", this, this, 0, &args);
+ if (!dlg)
+ return false; //error msg has been shown by KexiInternalPart
+ const int result = dlg->exec();
+ delete dlg;
+ return result == QDialog::Rejected ? cancelled : true;
+}
+
+void KexiMainWindowImpl::slotEditPasteSpecialDataTable()
+{
+//! @todo allow data appending (it is not possible now)
+ if (userMode())
+ return;
+ QMap<QString,QString> args;
+ args.insert("sourceType", "clipboard");
+ QDialog *dlg = KexiInternalPart::createModalDialogInstance(
+ "csv_importexport", "KexiCSVImportDialog", this, this, 0, &args);
+ if (!dlg)
+ return; //error msg has been shown by KexiInternalPart
+ dlg->exec();
+ delete dlg;
+}
+
+void KexiMainWindowImpl::slotEditFind()
+{
+// KexiViewBase *view = d->currentViewSupportingAction("edit_findnext");
+ KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface();
+ if (!iface)
+ return;
+ d->updateFindDialogContents(true/*create if does not exist*/);
+ d->findDialog()->setReplaceMode(false);
+
+ d->findDialog()->show();
+ d->findDialog()->setActiveWindow();
+ d->findDialog()->raise();
+}
+
+void KexiMainWindowImpl::slotEditFind(bool next)
+{
+ KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface();
+ if (!iface)
+ return;
+ tristate res = iface->find(
+ d->findDialog()->valueToFind(), d->findDialog()->options(), next);
+ if (~res)
+ return;
+ d->findDialog()->updateMessage( true == res );
+//! @todo result
+}
+
+void KexiMainWindowImpl::slotEditFindNext()
+{
+ slotEditFind( true );
+}
+
+void KexiMainWindowImpl::slotEditFindPrevious()
+{
+ slotEditFind( false );
+}
+
+void KexiMainWindowImpl::slotEditReplace()
+{
+ KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface();
+ if (!iface)
+ return;
+ d->updateFindDialogContents(true/*create if does not exist*/);
+ d->findDialog()->setReplaceMode(true);
+//! @todo slotEditReplace()
+ d->findDialog()->show();
+ d->findDialog()->setActiveWindow();
+}
+
+void KexiMainWindowImpl::slotEditReplaceNext()
+{
+ slotEditReplace( false );
+}
+
+void KexiMainWindowImpl::slotEditReplace(bool all)
+{
+ KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface();
+ if (!iface)
+ return;
+//! @todo add question: "Do you want to replace every occurence of \"%1\" with \"%2\"?
+//! You won't be able to undo this." + "Do not ask again".
+ tristate res = iface->findNextAndReplace(
+ d->findDialog()->valueToFind(), d->findDialog()->valueToReplaceWith(),
+ d->findDialog()->options(), all);
+ d->findDialog()->updateMessage( true == res );
+//! @todo result
+}
+
+void KexiMainWindowImpl::slotEditReplaceAll()
+{
+ slotEditReplace( true );
+}
+
+void KexiMainWindowImpl::addWindow( KMdiChildView* pView, int flags )
+{
+ //maximize this window, if it's
+//!@todo Certain windows' sizes, e.g. forms could have own size configation specified!
+//! Query for this, and if so: give up.
+ if (d->maximizeFirstOpenedChildFrm) {
+ flags |= KMdi::Maximize;
+ d->maximizeFirstOpenedChildFrm = false;
+ }
+ KexiMainWindow::addWindow( pView, flags );
+}
+
+/// TMP (until there's true template support)
+void KexiMainWindowImpl::slotGetNewStuff()
+{
+#ifdef HAVE_KNEWSTUFF
+ if(!d->newStuff)
+ d->newStuff = new KexiNewStuff(this);
+ d->newStuff->download();
+
+ //KNS::DownloadDialog::open(newstuff->customEngine(), "kexi/template");
+#endif
+}
+
+void KexiMainWindowImpl::highlightObject(const QCString& mime, const QCString& name)
+{
+ slotViewNavigator();
+ if (!d->prj)
+ return;
+ KexiPart::Item *item = d->prj->itemForMimeType(mime, name);
+ if (!item)
+ return;
+ if (d->nav) {
+ d->nav->selectItem(*item);
+ }
+}
+
+void KexiMainWindowImpl::slotPartItemSelectedInNavigator(KexiPart::Item* item)
+{
+ Q_UNUSED(item);
+}
+
+#include "keximainwindowimpl.moc"
diff --git a/kexi/main/keximainwindowimpl.h b/kexi/main/keximainwindowimpl.h
new file mode 100644
index 000000000..935454394
--- /dev/null
+++ b/kexi/main/keximainwindowimpl.h
@@ -0,0 +1,538 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIMAINWINDOWIMPL_H
+#define KEXIMAINWINDOWIMPL_H
+
+#include <kmessagebox.h>
+#include "core/keximainwindow.h"
+#include "core/kexiguimsghandler.h"
+
+class KexiProjectData;
+class KexiActionProxy;
+class KMdiChildView;
+class KexiSimplePrintingSettings;
+
+namespace KexiDB {
+ class Object;
+ class ConnectionData;
+}
+namespace KexiPart {
+ class Info;
+ class Part;
+}
+
+/**
+ * @short Kexi's main window implementation
+ */
+class KEXIMAIN_EXPORT KexiMainWindowImpl : public KexiMainWindow, public KexiGUIMessageHandler
+{
+ Q_OBJECT
+
+ public:
+ /*! Creates an empty mainwindow. */
+ KexiMainWindowImpl();
+ virtual ~KexiMainWindowImpl();
+
+ /*! Used by the main kexi routine. Creates a new Kexi main window and a new KApplication object.
+ kdemain() has to destroy the latter on exit.
+ \return result 1 on error and 0 on success (the result can be used as a result of kdemain()) */
+ static int create(int argc, char *argv[], KAboutData* aboutdata = 0);
+
+ //! \return KexiMainWindowImpl singleton (if it is instantiated)
+ static KexiMainWindowImpl* self() { return dynamic_cast<KexiMainWindowImpl*>(qApp->mainWidget()); }
+
+ //! Project data of currently opened project or NULL if no project here yet.
+ virtual KexiProject *project();
+
+ /*! Registers dialog \a dlg for watching and adds it to the main window's stack. */
+ virtual void registerChild(KexiDialogBase *dlg);
+
+ /*! Activates a window by it's document identifier.
+ \return false if doc couldn't be raised or isn't opened. */
+ bool activateWindow(int id);
+
+ /*! Like above, using \a dlg passed explicitly. Above method just calls this one. */
+ bool activateWindow(KexiDialogBase *dlg);
+
+ /*! Performs startup actions. \return false if application should exit immediately
+ with an error status. */
+ tristate startup();
+
+ /*! \return true if the application window is in the User Mode. */
+ virtual bool userMode() const;
+
+ /*! \return true if opening of item \a item in \a viewMode mode is allowed.
+ userMode() is taken into account as well
+ as KexiPart::Part::supportedUserViewModes() for \a item. */
+ bool openingAllowed(KexiPart::Item* item, int viewMode);
+
+ virtual bool eventFilter( QObject *obj, QEvent * e );
+
+ //! \return popup menu for \a popupName name.
+ virtual QPopupMenu* findPopupMenu(const char *popupName);
+
+ /*! Implemented for KexiMainWindow. */
+ virtual KActionPtrList allActions() const;
+
+ /*! \return currently active dialog (window) od 0 if there is no active dialog.
+ Implemented for KexiMainWindow. */
+ virtual KexiDialogBase* currentDialog() const;
+
+//! @todo move to kexiproject
+ /*! Generates ID for private "document" like Relations window.
+ Private IDs are negative numbers (while ID regular part instance's IDs are >0)
+ Private means that the object is not stored as-is in the project but is somewhat
+ generated and in most cases there is at most one unique instance document of such type (part).
+ To generate this ID, just app-wide internal counter is used. */
+ virtual int generatePrivateID();
+
+ /*! Reimplemented */
+ virtual void readProperties(KConfig *config);
+ virtual void saveProperties(KConfig *config);
+ virtual void saveGlobalProperties( KConfig* sessionConfig );
+
+ public slots:
+ /*! Inherited from KMdiMainFrm: we need to do some tasks before child is closed.
+ Just calls closeDialog(). Use closeDialog() if you need, not this one. */
+ virtual void closeWindow(KMdiChildView *pWnd, bool layoutTaskBar = true);
+
+ /*! Reimplemented for internal reasons. */
+ virtual void addWindow( KMdiChildView* pView, int flags = KMdi::StandardAdd );
+
+ /*! Implemented for KexiMainWindow */
+ virtual tristate closeDialog(KexiDialogBase *dlg);
+
+ /*! Internal implementation. If \a doNotSaveChanges is true,
+ messages asking for saving the will be skipped and the changes will be dropped.
+ This should not be usually used, maybe except for test suites
+ (see kexi/tests/altertable/ directory). */
+ tristate closeDialog(KexiDialogBase *dlg, bool layoutTaskBar, bool doNotSaveChanges = false);
+
+ virtual void detachWindow(KMdiChildView *pWnd,bool bShow=true);
+ virtual void attachWindow(KMdiChildView *pWnd,bool bShow=true,bool bAutomaticResize=false);
+
+//! @todo move part of this to KexiProject, because currently KexiProject::openObject() allows multiple opens!
+ /*! Opens object pointed by \a item in a view \a viewMode.
+ \a staticObjectArgs can be passed for static object
+ (only works when part for this item is of type KexiPart::StaticPart).
+ \a openingCancelled is set to true is opening has been cancelled.
+ \a errorMessage, if not 0, points to a string that can be set to error message
+ if one encountered. */
+ virtual KexiDialogBase* openObject(KexiPart::Item *item, int viewMode,
+ bool &openingCancelled, QMap<QString,QString>* staticObjectArgs = 0,
+ QString* errorMessage = 0);
+
+ //! For convenience
+ virtual KexiDialogBase* openObject(const QCString& mime, const QString& name,
+ int viewMode, bool &openingCancelled, QMap<QString,QString>* staticObjectArgs = 0);
+
+ /*! Closes the object for \a item.
+ \return true on success (closing can be dealyed though), false on failure and cancelled
+ if the object has "opening" job assigned. */
+ virtual tristate closeObject(KexiPart::Item* item);
+
+ /*! Implemented for KexiMainWindow */
+ virtual tristate saveObject( KexiDialogBase *dlg,
+ const QString& messageWhenAskingForName = QString::null, bool dontAsk = false );
+
+ /*! Implemented for KexiMainWindow */
+ virtual tristate getNewObjectInfo( KexiPart::Item *partItem, KexiPart::Part *part,
+ bool& allowOverwriting, const QString& messageWhenAskingForName = QString::null );
+
+ /*! Implemented for KexiMainWindow */
+ virtual void highlightObject(const QCString& mime, const QCString& name);
+
+ /*! Opens project pointed by \a projectData.
+ Application state (e.g. actions) is updated.
+ \a projectData is copied into a project structures.
+ \return true on success */
+ tristate openProject(const KexiProjectData& projectData);
+
+ /*! Helper. Opens project pointed by \a aFileName.
+ If \a aFileName is empty, a connection shortcut (.kexic file name) is obtained from
+ global connection set using \a cdata (if present).
+ In this case:
+ * If connection shortcut has been found and \a dbName (a server database name) is provided
+ 'kexi --skip-dialog --connection file.kexic dbName' is executed (or the project
+ is opened directly if there's no project opened in the current Kexi main window.
+ * If connection shortcut has been found and \a dbName is not provided,
+ 'kexi --skip-dialog file.kexic' is executed (or the connection is opened
+ directly if there's no porject opened in the current Kexi main window. */
+ tristate openProject(const QString& aFileName, KexiDB::ConnectionData *cdata,
+ const QString& dbName = QString::null,
+ const QValueList<KexiProjectData::ObjectInfo>& autoopenObjects = QValueList<KexiProjectData::ObjectInfo>());
+
+ /*! Helper. Opens project pointed by \a aFileName.
+ Like above but \a fileNameForConnectionData can be passed instead of
+ a pointer to connection data itself.
+ \return false if \a fileNameForConnectionData is not empty but there is no such
+ connection in Kexi::connset() for this filename.
+ \a fileNameForConnectionData can be empty. */
+ tristate openProject(const QString& aFileName,
+ const QString& fileNameForConnectionData, const QString& dbName = QString::null);
+
+ /*! Creates a new project usign template pointed by \a projectData.
+ Application state (e.g. actions) is updated.
+ New project data is copied into a project structures.
+ \return true on success */
+ tristate createProjectFromTemplate(const KexiProjectData& projectData);
+
+ /*! Closes current project, \return true on success.
+ Application state (e.g. actions) is updated.
+ \return true on success.
+ If closing was cancelled by user, cancelled is returned. */
+ tristate closeProject();
+
+ //! Shows "print" dialog for \a item.
+ //! \return true on success.
+ virtual tristate printItem(KexiPart::Item* item);
+
+ //! Shows "print preview" dialog.
+ //! \return true on success.
+ virtual tristate printPreviewForItem(KexiPart::Item* item);
+
+ //! Shows "page setup" dialog for \a item.
+ //! \return true on success and cancelled when the action was cancelled.
+ virtual tristate showPageSetupForItem(KexiPart::Item* item);
+
+ /*! Executes custom action for the main window, usually provided by a plugin.
+ Also used by KexiFormEventAction. */
+ virtual tristate executeCustomActionForObject(KexiPart::Item* item, const QString& actionName);
+
+ signals:
+ //! Emitted after opening a project, even after slotAutoOpenObjectsLater().
+ void projectOpened();
+
+ protected:
+ /*! Initialises the User Mode: constructs window according to kexi__final database
+ and loads the specified part.
+ \return true on success or false if e.g. kexi__final does not exist
+ or a fatal exception happened */
+ bool initUserMode(KexiProjectData *projectData);
+
+ /*!
+ Creates navigator (if it's not yet created),
+ lookups items for current project and fills the nav. with not-opened items
+ */
+ void initNavigator();
+
+ void initContextHelp();
+
+ void initPropertyEditor();
+
+ //! reimplementation of events
+// virtual void closeEvent(QCloseEvent *);
+
+ /*! Creates standard actions like new, open, save ... */
+ void initActions();
+
+ /*! Creates user project-wide actions */
+ void initUserActions();
+
+ /*! Sets up the window from user settings (e.g. mdi mode). */
+ void restoreSettings();
+
+ /*! Writes user settings back. */
+ void storeSettings();
+
+ /*! Invalidates availability of all actions for current application state. */
+ void invalidateActions();
+
+ /*! Invalidates action availability for current application state.
+ These actions are dependent on curently selected dialog. */
+ virtual void invalidateSharedActions(QObject *o);
+
+ /*! Invalidates action availability for current application state.
+ These actions only depend on project availability, not on curently selected dialog. */
+ void invalidateProjectWideActions();
+
+ /*! Invalidates action availability for current application state.
+ These actions only depend on curently selected dialog and currently selected view
+ (KexiViewBase derived object) within this dialog. */
+ void invalidateViewModeActions();
+
+ /*! Shows dialog for creating new blank project,
+ and creates one. Dialog is not shown if option for automatic creation
+ is checked or Kexi::startupHandler().projectData() was provided from command line.
+ \a cancelled is set to true if creation has been cancelled (e.g. user answered
+ no when asked for database overwriting, etc.
+ \return true if database was created, false on error or when cancel was pressed */
+ tristate createBlankProject();
+
+ /*! Shows dialog for creating new blank project,
+ and return a data describing it. It the dialog was cancelled,
+ \a cancelled will be set to true (false otherwise).
+ \a shortcutFileName, if not 0, will be set to a shortcut filename
+ (in case when server database project was selected). */
+ KexiProjectData* createBlankProjectData(bool &cancelled, bool confirmOverwrites = true,
+ QString *shortcutFileName = 0);
+
+ void setWindowMenu(QPopupMenu *menu);
+
+ /*! \return focused kexi window (KexiDialogBase or KexiDockBase subclass) */
+// QWidget* focusWindow() const;
+
+ /*! Reimplemented from KexiSharedActionHost:
+ accepts only KexiDockBase and KexiDialogBase subclasses. */
+ virtual bool acceptsSharedActions(QObject *w);
+
+ /*! Performs lookup like in KexiSharedActionHost::focusWindow()
+ but starting from \a w instead of a widget returned by QWidget::focusWidget().
+ \return NULL if no widget matches acceptsSharedActions() or if \a w is NULL. */
+ QWidget* findWindow(QWidget *w);
+
+ /*! Updates application's caption - also shows project's name. */
+ void updateAppCaption();
+
+ void restoreWindowConfiguration(KConfig *config);
+ void storeWindowConfiguration(KConfig *config);
+
+ virtual bool queryClose();
+ virtual bool queryExit();
+
+ /*! Helper: switches to view \a mode. */
+ bool switchToViewMode(int viewMode);
+
+ /*! Helper. Removes and/or adds GUI client for current dialog's view;
+ on switching to other dialog (activeWindowChanged())
+ or on switching to other view within the same dialog (switchToViewMode()). */
+ void updateDialogViewGUIClient(KXMLGUIClient *viewClient);
+
+ /*! Helper. Updates setup of property panel's tabs. Used when switching
+ from \a prevDialog dialog to a current dialog. */
+ void updateCustomPropertyPanelTabs(KexiDialogBase *prevDialog, int prevViewMode);
+
+ /*! @overload void updateCustomPropertyPanelTabs(KexiDialogBase *prevDialog, int prevViewMode) */
+ void updateCustomPropertyPanelTabs(
+ KexiPart::Part *prevDialogPart, int prevViewMode, KexiPart::Part *curDialogPart, int curViewMode );
+
+ /*! Used in openProject when running another Kexi process is required. */
+ tristate openProjectInExternalKexiInstance(const QString& aFileName,
+ KexiDB::ConnectionData *cdata, const QString& dbName);
+
+ /*! Used in openProject when running another Kexi process is required. */
+ tristate openProjectInExternalKexiInstance(const QString& aFileName,
+ const QString& fileNameForConnectionData, const QString& dbName);
+
+ protected slots:
+
+ /*! Called once after timeout (after ctors are executed). */
+ void slotAutoOpenObjectsLater();
+
+ /*! This slot is called if a window changes */
+ void activeWindowChanged(KMdiChildView *dlg);
+
+ /*! Tthis slot is called if a window gets colsed and will unregister stuff */
+ void childClosed(KMdiChildView *dlg);
+
+ void slotPartLoaded(KexiPart::Part* p);
+
+ void slotCaptionForCurrentMDIChild(bool childrenMaximized);
+ void slotNoMaximizedChildFrmLeft(KMdiChildFrm*);
+ void slotLastChildViewClosed();
+ void slotChildViewIsDetachedNow(QWidget*);
+
+ //! internal - creates and initializes kexi project
+ void createKexiProject(KexiProjectData* new_data);
+
+ /*! Handles event when user double clicked (or single -depending on settings)
+ or pressed Return key on the part item in the navigator.
+ This differs from openObject() signal in that if the object is already opened
+ in view mode other than \a viewMode, the mode is not changed.
+ \sa KexiBrowser::openOrActivateItem() */
+ KexiDialogBase* openObjectFromNavigator(KexiPart::Item* item, int viewMode,
+ bool &openingCancelled);
+
+ //! For convenience
+ KexiDialogBase* openObjectFromNavigator(KexiPart::Item* item, int viewMode);
+
+ /*! Creates new object of type defined by \a info part info.
+ \a openingCancelled is set to true is opening has been cancelled.
+ \return true on success. */
+ virtual bool newObject( KexiPart::Info *info, bool& openingCancelled );
+
+ //! For convenience
+ bool newObject( KexiPart::Info *info ) {
+ bool openingCancelled;
+ return newObject(info, openingCancelled);
+ }
+
+ //! For convenience
+ KexiDialogBase* openObject(KexiPart::Item *item, int viewMode,
+ QMap<QString,QString>* staticObjectArgs = 0)
+ {
+ bool openingCancelled;
+ return openObject(item, viewMode, openingCancelled, staticObjectArgs);
+ }
+
+ /*! Removes object pointed by \a item from current project.
+ Asks for confirmation. \return true on success
+ or cancelled if removing was cancelled (only possible if \a dontAsk is false). */
+ tristate removeObject( KexiPart::Item *item, bool dontAsk = false );
+
+ /*! Renames object pointed by \a item to a new name \a _newName.
+ Sets \a success to false on failure. Used as a slot connected
+ to KexiBrowser::renameItem() signal. */
+ void renameObject( KexiPart::Item *item, const QString& _newName, bool &succes );
+
+ /*! Reaction for object rename (signalled by KexiProject).
+ If this item has opened dialog, it's caption is updated,
+ and also optionally application's caption. */
+ virtual void slotObjectRenamed(const KexiPart::Item &item, const QCString& oldName);
+
+ virtual void fillWindowMenu();
+
+ void invalidateSharedActions();
+ void invalidateSharedActionsLater();
+
+ //! Updates the statusbar, navigator and "Insert->....." actions, dependent on read-only state.
+ //! Only called on project opening and closing.
+ void updateReadOnlyState();
+
+ void slotProjectNew();
+ void slotProjectOpen();
+ void slotProjectOpenRecentAboutToShow();
+ void slotProjectOpenRecent(int id);
+ void slotProjectOpenRecentMore();
+ void slotProjectSave();
+ void slotProjectSaveAs();
+ void slotProjectPrint();
+ void slotProjectPrintPreview();
+ void slotProjectPageSetup();
+ void slotProjectProperties();
+ void slotProjectClose();
+ void slotProjectRelations();
+ void slotProjectImportDataTable();
+ void slotProjectExportDataTable();
+ void slotProjectQuit();
+ void slotEditPasteSpecialDataTable();
+ void slotEditCopySpecialDataTable();
+ void slotEditFind();
+ void slotEditFind(bool next); //!< helper
+ void slotEditFindNext();
+ void slotEditFindPrevious();
+ void slotEditReplace(bool all); //!< helper
+ void slotEditReplace();
+ void slotEditReplaceNext();
+ void slotEditReplaceAll();
+ void slotViewNavigator();
+ void slotViewMainArea();
+ void slotViewPropertyEditor();
+ void slotViewDataMode();
+ void slotViewDesignMode();
+ void slotViewTextMode(); //!< sometimes called "SQL View"
+ void slotShowSettings();
+ void slotConfigureKeys();
+ void slotConfigureToolbars();
+ void slotToolsProjectMigration();
+ void slotToolsCompactDatabase();
+
+ /// TMP: Display a dialog to download db examples from internet
+ void slotGetNewStuff();
+
+ void slotTipOfTheDay();
+
+ //! Shows 'important info' dialog, is \a onStartup is false, it's always shown
+ void importantInfo(bool onStartup);
+ void slotImportantInfo(); //!< just importantInfo(false);
+ void slotStartFeedbackAgent();
+
+ void slotOptionsEnableForms(bool show, bool noMessage = false); //temp.
+
+ void slotImportFile();
+ void slotImportServer();
+
+ //! There are performed all actions that need to be done immediately after ctro (using timer)
+ void slotLastActions();
+
+ virtual void acceptPropertySetEditing();
+
+ virtual void propertySetSwitched(KexiDialogBase *dlg, bool force=false,
+ bool preservePrevSelection = true, const QCString& propertyToSelect = QCString());
+
+ /*! Handles changes in 'dirty' flag for dialogs. */
+ void slotDirtyFlagChanged(KexiDialogBase*);
+
+ void slotMdiModeHasBeenChangedTo(KMdi::MdiMode);
+
+ //! reimplemented to add "restart is required" message box
+ virtual void switchToIDEAlMode();
+ void switchToIDEAlMode(bool showMessage);
+ virtual void switchToChildframeMode();
+ void switchToChildframeMode(bool showMessage);
+
+ /*! Shows Project Migration Wizard. \return true on successful migration,
+ cancelled on cancellation, and false on failure.
+ If \a mimeType and \a databaseName are not empty, the wizard will only ask about
+ parameters of destination project and skip pages related to source project.
+ \a cdata connection data can be also provided to preselect server-based connections. */
+ tristate showProjectMigrationWizard(const QString& mimeType, const QString& databaseName,
+ const KexiDB::ConnectionData *cdata = 0);
+
+ //! Receives "selectionChanged()" signal from navigator to update some actions.
+ void slotPartItemSelectedInNavigator(KexiPart::Item* item);
+
+ /*! Receives the "executeItem" signal from navigator to perform "execute" action
+ on \a item. \return true on success */
+ tristate executeItem(KexiPart::Item* item);
+
+ //! Shows "exports as data table" dialog for \a item.
+ tristate exportItemAsDataTable(KexiPart::Item* item);
+
+ //! Shows "copy special as data table" dialog for \a item.
+ tristate copyItemToClipboardAsDataTable(KexiPart::Item* item);
+
+ //! Shows "print" dialog for \a item.
+ //! \return true on success.
+ bool printItem(KexiPart::Item* item, const QString& titleText);
+
+ //! Shows "print" dialog for \a item and \a settings.
+ //! \return true on success.
+ bool printItem(KexiPart::Item* item, const KexiSimplePrintingSettings& settings,
+ const QString& titleText = QString::null);
+
+ /*! Shows "print preview" dialog for \a item.
+ The preview dialog is cached, so \a reload == true is sometimes needed
+ if data or print settings have changed in the meantime.
+ \return true on success. */
+ bool printPreviewForItem(KexiPart::Item* item, const QString& titleText,
+ bool reload);
+
+ //! Shows "print preview" dialog.
+ //! \return true on success.
+ bool printPreviewForItem(KexiPart::Item* item, const KexiSimplePrintingSettings& settings,
+ const QString& titleText = QString::null, bool reload = false);
+
+ /*! Implemented for KexiMainWindow. Helper for printItem() and printPreviewForItem().
+ Also used by KexiFormEventAction.
+ \return true on success and cancelled when the action was cancelled. */
+ tristate printActionForItem(KexiPart::Item* item, PrintActionType action);
+
+ private:
+ class MessageHandler;
+ class Private;
+ Private *d;
+
+ friend class KexiDialogBase;
+};
+
+#endif
+
diff --git a/kexi/main/keximainwindowimpl_p.h b/kexi/main/keximainwindowimpl_p.h
new file mode 100644
index 000000000..b48c7ca00
--- /dev/null
+++ b/kexi/main/keximainwindowimpl_p.h
@@ -0,0 +1,599 @@
+/* This file is part of the KDE projec
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifdef KEXI_NO_PROCESS_EVENTS
+# define KEXI_NO_PENDING_DIALOGS
+#endif
+
+//! @internal safer dictionary
+typedef QMap< int, QGuardedPtr<KexiDialogBase> > KexiDialogDict;
+
+//! @internal
+class KexiMainWindowImpl::Private
+{
+public:
+ Private(KexiMainWindowImpl* w)
+// : dialogs(401)
+ : wnd(w)
+ , m_openedCustomObjectsForItem(1019, true)
+ {
+ propEditor=0;
+ propEditorToolWindow=0;
+ propEditorTabWidget=0;
+ userMode = false;
+ nav=0;
+ navToolWindow=0;
+ prj = 0;
+ curDialogGUIClient=0;
+ curDialogViewGUIClient=0;
+ closedDialogGUIClient=0;
+ closedDialogViewGUIClient=0;
+ nameDialog=0;
+ curDialog=0;
+ m_findDialog=0;
+ block_KMdiMainFrm_eventFilter=false;
+ focus_before_popup=0;
+// relationPart=0;
+ privateIDCounter=0;
+ action_view_nav=0;
+ action_view_propeditor=0;
+ action_view_mainarea=0;
+ action_open_recent_projects_title_id = -1;
+ action_open_recent_connections_title_id = -1;
+ forceDialogClosing=false;
+ insideCloseDialog=false;
+#ifndef KEXI_NO_PENDING_DIALOGS
+ actionToExecuteWhenPendingJobsAreFinished = NoAction;
+#endif
+// callSlotLastChildViewClosedAfterCloseDialog=false;
+ createMenu=0;
+ showImportantInfoOnStartup=true;
+// disableErrorMessages=false;
+// last_checked_mode=0;
+ propEditorDockSeparatorPos=-1;
+ navDockSeparatorPos=-1;
+// navDockSeparatorPosWithAutoOpen=-1;
+ wasAutoOpen = false;
+ dialogExistedBeforeCloseProject = false;
+#ifndef KEXI_SHOW_UNIMPLEMENTED
+ dummy_action = new KActionMenu("", wnd);
+#endif
+ maximizeFirstOpenedChildFrm = false;
+#ifdef HAVE_KNEWSTUFF
+ newStuff = 0;
+#endif
+ mdiModeToSwitchAfterRestart = (KMdi::MdiMode)0;
+ forceShowProjectNavigatorOnCreation = false;
+ forceHideProjectNavigatorOnCreation = false;
+ navWasVisibleBeforeProjectClosing = false;
+ saveSettingsForShowProjectNavigator = true;
+ m_openedCustomObjectsForItem.setAutoDelete(true);
+ }
+ ~Private() {
+ }
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ //! Job type. Currently used for marking items as being opened or closed.
+ enum PendingJobType {
+ NoJob = 0,
+ DialogOpeningJob,
+ DialogClosingJob
+ };
+
+ KexiDialogBase *openedDialogFor( const KexiPart::Item* item, PendingJobType &pendingType )
+ {
+ return openedDialogFor( item->identifier(), pendingType );
+ }
+
+ KexiDialogBase *openedDialogFor( int identifier, PendingJobType &pendingType )
+ {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ QMap<int, PendingJobType>::ConstIterator it = pendingDialogs.find( identifier );
+ if (it==pendingDialogs.constEnd())
+ pendingType = NoJob;
+ else
+ pendingType = it.data();
+
+ if (pendingType == DialogOpeningJob) {
+ return 0;
+ }
+ return (KexiDialogBase*)dialogs[ identifier ];
+ }
+#else
+ KexiDialogBase *openedDialogFor( const KexiPart::Item* item )
+ {
+ return openedDialogFor( item->identifier() );
+ }
+
+ KexiDialogBase *openedDialogFor( int identifier )
+ {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ return (KexiDialogBase*)dialogs[ identifier ];
+ }
+#endif
+
+ void insertDialog(KexiDialogBase *dlg) {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ dialogs.insert(dlg->id(), QGuardedPtr<KexiDialogBase>(dlg));
+#ifndef KEXI_NO_PENDING_DIALOGS
+ pendingDialogs.remove(dlg->id());
+#endif
+ }
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ void addItemToPendingDialogs(const KexiPart::Item* item, PendingJobType jobType) {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ pendingDialogs.replace( item->identifier(), jobType );
+ }
+
+ bool pendingDialogsExist() {
+ if (pendingDialogs.constBegin()!=pendingDialogs.constEnd())
+ kdDebug() << pendingDialogs.constBegin().key() << " " << (int)pendingDialogs.constBegin().data() << endl;
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ return !pendingDialogs.isEmpty();
+ }
+#endif
+
+ void updateDialogId(KexiDialogBase *dlg, int oldItemID) {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ dialogs.remove(oldItemID);
+#ifndef KEXI_NO_PENDING_DIALOGS
+ pendingDialogs.remove(oldItemID);
+#endif
+ dialogs.insert(dlg->id(), QGuardedPtr<KexiDialogBase>(dlg));
+ }
+
+ void removeDialog(int identifier) {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ dialogs.remove(identifier);
+ }
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ void removePendingDialog(int identifier) {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ pendingDialogs.remove(identifier);
+ }
+#endif
+
+ uint openedDialogsCount() {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ return dialogs.count();
+ }
+
+ //! Used in KexiMainWindowImple::closeProject()
+ void clearDialogs() {
+//todo(threads) QMutexLocker dialogsLocker( &dialogsMutex );
+ dialogs.clear();
+#ifndef KEXI_NO_PENDING_DIALOGS
+ pendingDialogs.clear();
+#endif
+ }
+
+ /*! Toggles last checked view mode radio action, if available. */
+ void toggleLastCheckedMode()
+ {
+ if (curDialog.isNull())
+ return;
+ KRadioAction *ra = actions_for_view_modes[ curDialog->currentViewMode() ];
+ if (ra)
+ ra->setChecked(true);
+// if (!last_checked_mode)
+// return;
+// last_checked_mode->setChecked(true);
+ }
+
+/*
+void updatePropEditorDockWidthInfo() {
+ if (propEditor) {
+ KDockWidget *dw = (KDockWidget *)propEditor->parentWidget();
+#if defined(KDOCKWIDGET_P)
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+ if (ds) {
+ propEditorDockSeparatorPos = ds->separatorPosInPercent();*/
+/* if (propEditorDockSeparatorPos<=0) {
+ config->setGroup("MainWindow");
+ propEditorDockSeparatorPos = config->readNumEntry("RightDockPosition", 80);
+ ds->setSeparatorPos(propEditorDockSeparatorPos, true);
+ }*/
+ /*}
+#endif
+ }
+ }*/
+
+ void showStartProcessMsg(const QStringList& args)
+ {
+ wnd->showErrorMessage(i18n("Could not start %1 application.").arg(KEXI_APP_NAME),
+ i18n("Command \"%1\" failed.").arg(args.join(" ")));
+ }
+
+ void hideMenuItem(const QString& menuName, const QString& itemText, bool alsoSeparator)
+ {
+ QPopupMenu *pm = popups[menuName.ascii()];
+ if (!pm)
+ return;
+ uint i=0;
+ const uint c = pm->count();
+ for (;i<c;i++) {
+ kdDebug() << pm->text( pm->idAt(i) ) <<endl;
+ if (pm->text( pm->idAt(i) ).lower().stripWhiteSpace()==itemText.lower().stripWhiteSpace())
+ break;
+ }
+ if (i<c) {
+ pm->setItemVisible( pm->idAt(i), false );
+ if (alsoSeparator)
+ pm->setItemVisible( pm->idAt(i+1), false ); //also separator
+ }
+ }
+
+ void disableMenuItem(const QString& menuName, const QString& itemText)
+ {
+ QPopupMenu *pm = popups[menuName.ascii()];
+ if (!pm)
+ return;
+ uint i=0;
+ const uint c = pm->count();
+ for (;i<c;i++) {
+ if (pm->text( pm->idAt(i) ).lower().stripWhiteSpace()==itemText.lower().stripWhiteSpace())
+ break;
+ }
+ if (i<c)
+ pm->setItemEnabled( pm->idAt(i), false );
+ }
+
+ void updatePropEditorVisibility(int viewMode)
+ {
+ if (propEditorToolWindow) {
+ if (viewMode==0 || viewMode==Kexi::DataViewMode) {
+#ifdef PROPEDITOR_VISIBILITY_CHANGES
+ wnd->makeDockInvisible( wnd->manager()->findWidgetParentDock(propEditor) );
+// propEditorToolWindow->hide();
+#endif
+ } else {
+ //propEditorToolWindow->show();
+ QWidget *origFocusWidget = qApp->focusWidget();
+ wnd->makeWidgetDockVisible(propEditorTabWidget);
+ if (origFocusWidget)
+ origFocusWidget->setFocus();
+/*moved
+#if defined(KDOCKWIDGET_P)
+ KDockWidget *dw = (KDockWidget *)propEditor->parentWidget();
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+ ds->setSeparatorPosInPercent(config->readNumEntry("RightDockPosition", 80));//%
+#endif*/
+ }
+ }
+ }
+
+ void restoreNavigatorWidth()
+ {
+#if defined(KDOCKWIDGET_P)
+ if (wnd->mdiMode()==KMdi::ChildframeMode || wnd->mdiMode()==KMdi::TabPageMode) {
+ KDockWidget *dw = (KDockWidget *)nav->parentWidget();
+ KDockSplitter *ds = (KDockSplitter *)dw->parentWidget();
+// ds->setKeepSize(true);
+
+ config->setGroup("MainWindow");
+# if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0)
+
+ if (wasAutoOpen) //(dw2->isVisible())
+// ds->setSeparatorPosInPercent( 100 * nav->width() / wnd->width() );
+ ds->setSeparatorPosInPercent(
+ QMAX(QMAX( config->readNumEntry("LeftDockPositionWithAutoOpen",20),
+ config->readNumEntry("LeftDockPosition",20)),20)
+ );
+ else
+ ds->setSeparatorPosInPercent(
+ QMAX(20, config->readNumEntry("LeftDockPosition", 20/* % */)));
+
+ // dw->resize( d->config->readNumEntry("LeftDockPosition", 115/* % */), dw->height() );
+# else
+ //there were problems on KDE < 3.4
+ ds->setSeparatorPosInPercent( 20 );
+# endif
+ //if (!wasAutoOpen) //(dw2->isVisible())
+// ds->setSeparatorPos( ds->separatorPos(), true );
+ }
+#endif
+ }
+
+ template<class type>
+ type *openedCustomObjectsForItem(KexiPart::Item* item, const char* name)
+ {
+ if (!item || !name) {
+ kdWarning() <<
+ "KexiMainWindowImpl::Private::openedCustomObjectsForItem(): !item || !name" << endl;
+ return 0;
+ }
+ QString key( QString::number(item->identifier()) + name );
+ return dynamic_cast<type*>( m_openedCustomObjectsForItem.find( key.latin1() ) );
+ }
+
+ void addOpenedCustomObjectForItem(KexiPart::Item* item, QObject* object, const char* name)
+ {
+ QString key = QString::number(item->identifier()) + name;
+ m_openedCustomObjectsForItem.insert( key.latin1(), object );
+ }
+
+ KexiFindDialog *findDialog() {
+ if (!m_findDialog) {
+ m_findDialog = new KexiFindDialog(wnd);
+ m_findDialog->setActions( action_edit_findnext, action_edit_findprev,
+ action_edit_replace, action_edit_replace_all );
+/* connect(m_findDialog, SIGNAL(findNext()), action_edit_findnext, SLOT(activate()));
+ connect(m_findDialog, SIGNAL(find()), wnd, SLOT(slotEditFindNext()));
+ connect(m_findDialog, SIGNAL(replace()), wnd, SLOT(slotEditReplaceNext()));
+ connect(m_findDialog, SIGNAL(replaceAll()), wnd, SLOT(slotEditReplaceAll()));*/
+ }
+ return m_findDialog;
+ }
+
+ /*! Updates the find/replace dialog depending on the active view.
+ Nothing is performed if the dialog is not instantiated yet or is invisible. */
+ void updateFindDialogContents(bool createIfDoesNotExist = false) {
+ if (!createIfDoesNotExist && (!m_findDialog || !m_findDialog->isVisible()))
+ return;
+ KexiSearchAndReplaceViewInterface* iface = currentViewSupportingSearchAndReplaceInterface();
+ if (!iface) {
+ if (m_findDialog) {
+ m_findDialog->setButtonsEnabled(false);
+ m_findDialog->setLookInColumnList(QStringList(), QStringList());
+ }
+ return;
+ }
+//! @todo use ->caption() here, depending on global settings related to displaying captions
+ findDialog()->setObjectNameForCaption(curDialog->partItem()->name());
+
+ QStringList columnNames;
+ QStringList columnCaptions;
+ QString currentColumnName; // for 'look in'
+ if (!iface->setupFindAndReplace(columnNames, columnCaptions, currentColumnName)) {
+ m_findDialog->setButtonsEnabled(false);
+ m_findDialog->setLookInColumnList(QStringList(), QStringList());
+ return;
+ }
+ m_findDialog->setButtonsEnabled(true);
+
+ /* //update "look in" list
+ KexiTableViewColumn::List columns( dataAwareObject()->data()->columns );
+ QStringList columnNames;
+ QStringList columnCaptions;
+ for (KexiTableViewColumn::ListIterator it(columns); it.current(); ++it) {
+ if (!it.current()->visible())
+ continue;
+ columnNames.append( it.current()->field()->name() );
+ columnCaptions.append( it.current()->captionAliasOrName() );
+ }*/
+ const QString prevColumnName( m_findDialog->currentLookInColumnName());
+ m_findDialog->setLookInColumnList(columnNames, columnCaptions);
+ m_findDialog->setCurrentLookInColumnName( prevColumnName );
+ }
+
+ //! \return the current view if it supports \a actionName, otherwise returns 0.
+ KexiViewBase *currentViewSupportingAction(const char* actionName) const
+ {
+ if (!curDialog)
+ return 0;
+ KexiViewBase *view = curDialog->selectedView();
+ if (!view)
+ return 0;
+ KAction *action = view->sharedAction(actionName);
+ if (!action || !action->isEnabled())
+ return 0;
+ return view;
+ }
+
+ //! \return the current view if it supports KexiSearchAndReplaceViewInterface.
+ KexiSearchAndReplaceViewInterface* currentViewSupportingSearchAndReplaceInterface() const
+ {
+ if (!curDialog)
+ return 0;
+ KexiViewBase *view = curDialog->selectedView();
+ if (!view)
+ return 0;
+ return dynamic_cast<KexiSearchAndReplaceViewInterface*>(view);
+ }
+
+ KexiMainWindowImpl *wnd;
+ KexiStatusBar *statusBar;
+ KexiProject *prj;
+ KConfig *config;
+#ifndef KEXI_NO_CTXT_HELP
+ KexiContextHelp *ctxHelp;
+#endif
+ KexiBrowser *nav;
+ KTabWidget *propEditorTabWidget;
+ //! poits to kexi part which has been previously used to setup proppanel's tabs using
+ //! KexiPart::setupCustomPropertyPanelTabs(), in updateCustomPropertyPanelTabs().
+ QGuardedPtr<KexiPart::Part> partForPreviouslySetupPropertyPanelTabs;
+ QMap<KexiPart::Part*, int> recentlySelectedPropertyPanelPages;
+ QGuardedPtr<KexiPropertyEditorView> propEditor;
+ QGuardedPtr<KoProperty::Set> propBuffer;
+
+ KXMLGUIClient *curDialogGUIClient, *curDialogViewGUIClient,
+ *closedDialogGUIClient, *closedDialogViewGUIClient;
+ QGuardedPtr<KexiDialogBase> curDialog;
+
+ KexiNameDialog *nameDialog;
+
+ QTimer timer; //helper timer
+// QSignalMapper *actionMapper;
+
+ QAsciiDict<QPopupMenu> popups; //list of menu popups
+ QPopupMenu *createMenu;
+
+ QString origAppCaption; //<! original application's caption (without project name)
+ QString appCaptionPrefix; //<! application's caption prefix - prj name (if opened), else: null
+
+#ifndef KEXI_SHOW_UNIMPLEMENTED
+ KActionMenu *dummy_action;
+#endif
+
+ //! project menu
+ KAction *action_save, *action_save_as, *action_close,
+ *action_project_properties, *action_open_recent_more,
+ *action_project_relations, *action_project_import_data_table,
+ *action_project_export_data_table,
+ *action_project_print, *action_project_print_preview,
+ *action_project_print_setup;
+// KRecentFilesAction *action_open_recent;
+ KActionMenu *action_open_recent, *action_show_other;
+// int action_open_recent_more_id;
+ int action_open_recent_projects_title_id,
+ action_open_recent_connections_title_id;
+
+ //! edit menu
+ KAction *action_edit_delete, *action_edit_delete_row,
+ *action_edit_cut, *action_edit_copy, *action_edit_paste,
+ *action_edit_find, *action_edit_findnext, *action_edit_findprev,
+ *action_edit_replace, *action_edit_replace_all,
+ *action_edit_select_all,
+ *action_edit_undo, *action_edit_redo,
+ *action_edit_insert_empty_row,
+ *action_edit_edititem, *action_edit_clear_table,
+ *action_edit_paste_special_data_table,
+ *action_edit_copy_special_data_table;
+
+ //! view menu
+ KAction *action_view_nav, *action_view_propeditor, *action_view_mainarea;
+ KRadioAction *action_view_data_mode, *action_view_design_mode, *action_view_text_mode;
+ QIntDict<KRadioAction> actions_for_view_modes;
+// KRadioAction *last_checked_mode;
+#ifndef KEXI_NO_CTXT_HELP
+ KToggleAction *action_show_helper;
+#endif
+ //! data menu
+ KAction *action_data_save_row;
+ KAction *action_data_cancel_row_changes;
+ KAction *action_data_execute;
+
+ //! format menu
+ KAction *action_format_font;
+
+ //! tools menu
+ KAction *action_tools_data_migration, *action_tools_compact_database;
+ KActionMenu *action_tools_scripts;
+
+ //! window menu
+ KAction *action_window_next, *action_window_previous;
+
+ //! settings menu
+ KAction *action_configure;
+
+ //! for dock windows
+ KMdiToolViewAccessor* navToolWindow;
+ KMdiToolViewAccessor* propEditorToolWindow;
+
+ QGuardedPtr<QWidget> focus_before_popup;
+// KexiRelationPart *relationPart;
+
+ int privateIDCounter; //!< counter: ID for private "document" like Relations window
+
+ bool block_KMdiMainFrm_eventFilter : 1;
+
+ //! Set to true only in destructor, used by closeDialog() to know if
+ //! user can cancel dialog closing. If true user even doesn't see any messages
+ //! before closing a dialog. This is for extremely sanity... and shouldn't be even needed.
+ bool forceDialogClosing : 1;
+
+ //! Indicates that we're inside closeDialog() method - to avoid inf. recursion
+ //! on dialog removing
+ bool insideCloseDialog : 1;
+
+#ifndef KEXI_NO_PENDING_DIALOGS
+ //! Used in executeActionWhenPendingJobsAreFinished().
+ enum ActionToExecuteWhenPendingJobsAreFinished {
+ NoAction,
+ QuitAction,
+ CloseProjectAction
+ };
+ ActionToExecuteWhenPendingJobsAreFinished actionToExecuteWhenPendingJobsAreFinished;
+
+ void executeActionWhenPendingJobsAreFinished() {
+ ActionToExecuteWhenPendingJobsAreFinished a = actionToExecuteWhenPendingJobsAreFinished;
+ actionToExecuteWhenPendingJobsAreFinished = NoAction;
+ switch (a) {
+ case QuitAction:
+ qApp->quit();
+ break;
+ case CloseProjectAction:
+ wnd->closeProject();
+ break;
+ default:;
+ }
+ }
+#endif
+
+ //! Used for delayed dialogs closing for 'close all'
+ QPtrList<KexiDialogBase> windowsToClose;
+
+ //! Opened page setup dialogs, used by printOrPrintPreviewForItem().
+ QIntDict<KexiDialogBase> pageSetupDialogs;
+
+ /*! A map from Kexi dialog to "print setup" part item's ID of the data item
+ used by closeDialog() to find an ID of the data item, so the entry
+ can be removed from pageSetupDialogs dictionary. */
+ QMap<int, int> pageSetupDialogItemID2dataItemID_map;
+
+ //! Used in several places to show info dialog at startup (only once per session)
+ //! before displaying other stuff
+ bool showImportantInfoOnStartup : 1;
+
+// //! Used sometimes to block showErrorMessage()
+// bool disableErrorMessages : 1;
+
+ //! Indicates if project is started in User Mode
+ bool userMode : 1;
+
+ //! Indicates if project navigator should be visible
+ bool isProjectNavigatorVisible : 1;
+
+ //! Used on opening 1st child window
+ bool maximizeFirstOpenedChildFrm : 1;
+
+ //! Set in restoreSettings() and used in initNavigator()
+ //! to customize navigator visibility on startup
+ bool forceShowProjectNavigatorOnCreation : 1;
+ bool forceHideProjectNavigatorOnCreation : 1;
+
+ bool navWasVisibleBeforeProjectClosing : 1;
+ bool saveSettingsForShowProjectNavigator : 1;
+#ifdef HAVE_KNEWSTUFF
+ KexiNewStuff *newStuff;
+#endif
+
+ //! Used by openedCustomObjectsForItem() and addOpenedCustomObjectForItem()
+ QAsciiDict<QObject> m_openedCustomObjectsForItem;
+
+ int propEditorDockSeparatorPos, navDockSeparatorPos;
+// int navDockSeparatorPosWithAutoOpen;
+ bool wasAutoOpen;
+ bool dialogExistedBeforeCloseProject;
+
+ KMdi::MdiMode mdiModeToSwitchAfterRestart;
+
+ private:
+ //! @todo move to KexiProject
+ KexiDialogDict dialogs;
+#ifndef KEXI_NO_PROCESS_EVENTS
+ QMap<int, PendingJobType> pendingDialogs; //!< part item identifiers for dialogs whoose opening has been started
+ //todo(threads) QMutex dialogsMutex; //!< used for locking dialogs and pendingDialogs dicts
+#endif
+ KexiFindDialog *m_findDialog;
+};
diff --git a/kexi/main/kexinamedialog.cpp b/kexi/main/kexinamedialog.cpp
new file mode 100644
index 000000000..85f7f04af
--- /dev/null
+++ b/kexi/main/kexinamedialog.cpp
@@ -0,0 +1,111 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexinamedialog.h"
+
+KexiNameDialog::KexiNameDialog(const QString& message,
+ QWidget * parent, const char * name)
+ : KDialogBase(KDialogBase::Plain, QString::null,
+ KDialogBase::Ok|KDialogBase::Cancel|KDialogBase::Help,
+ KDialogBase::Ok,
+ parent, name)
+{
+// QHBox ( QWidget * parent = 0, const char * name = 0, WFlags f = 0 )
+ m_widget= new KexiNameWidget(message, plainPage(), "KexiNameWidget");
+ init();
+}
+
+KexiNameDialog::KexiNameDialog(const QString& message,
+ const QString& nameLabel, const QString& nameText,
+ const QString& captionLabel, const QString& captionText,
+ QWidget * parent, const char * name)
+ : KDialogBase(KDialogBase::Plain, QString::null,
+ KDialogBase::Ok|KDialogBase::Cancel,
+ KDialogBase::Ok,
+ parent, name)
+{
+ m_widget= new KexiNameWidget(message, nameLabel, nameText,
+ captionLabel, captionText, plainPage(), "KexiNameWidget");
+ init();
+}
+
+KexiNameDialog::~KexiNameDialog()
+{
+}
+
+void KexiNameDialog::init()
+{
+ QGridLayout *lyr = new QGridLayout(plainPage(), 2, 3);
+ m_icon = new QLabel( plainPage(), "icon" );
+ m_icon->setAlignment( int( AlignTop | AlignLeft ) );
+ m_icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred,1,0));
+ m_icon->setFixedWidth(50);
+ lyr->addWidget(m_icon,0,0);
+
+ m_widget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred,1,0));
+ lyr->addWidget(m_widget,0,1);
+ lyr->addItem(new QSpacerItem( 25, 10, QSizePolicy::Expanding, QSizePolicy::Minimum ), 0, 2);
+ lyr->addItem(new QSpacerItem( 5, 10, QSizePolicy::Minimum, QSizePolicy::Expanding ), 1, 1);
+// m_widget->captionLineEdit()->selectAll();
+// m_widget->captionLineEdit()->setFocus();
+ connect(m_widget,SIGNAL(messageChanged()),this, SLOT(updateSize()));
+ updateSize();
+ enableButtonOK( true );
+ slotTextChanged();
+ connect(m_widget, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
+}
+
+void KexiNameDialog::updateSize()
+{
+// resize( QSize(400, 140 + (m_widget->lbl_message->isVisible()?m_widget->lbl_message->height():0) )
+ resize( QSize(400, 140 + (!m_widget->lbl_message->text().isEmpty()?m_widget->lbl_message->height():0) )
+ .expandedTo(minimumSizeHint()) );
+// updateGeometry();
+}
+
+void KexiNameDialog::slotTextChanged()
+{
+ bool enable = true;
+ if (m_widget->isNameRequired() && m_widget->nameText().isEmpty()
+ || m_widget->isCaptionRequired() && m_widget->captionText().isEmpty())
+ enable = false;
+ enableButtonOK( enable );
+}
+
+void KexiNameDialog::accept()
+{
+ if (!m_widget->checkValidity())
+ return;
+ KDialogBase::accept();
+}
+
+void KexiNameDialog::setDialogIcon(const QPixmap& icon)
+{
+ m_icon->setPixmap(icon);
+}
+
+void KexiNameDialog::show()
+{
+ m_widget->captionLineEdit()->selectAll();
+ m_widget->captionLineEdit()->setFocus();
+ KDialogBase::show();
+}
+
+#include "kexinamedialog.moc"
+
diff --git a/kexi/main/kexinamedialog.h b/kexi/main/kexinamedialog.h
new file mode 100644
index 000000000..27707c0d7
--- /dev/null
+++ b/kexi/main/kexinamedialog.h
@@ -0,0 +1,61 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXINAMEDIALOG_H
+#define KEXINAMEDIALOG_H
+
+#include <kdialogbase.h>
+
+#include "kexinamewidget.h"
+
+class KEXIMAIN_EXPORT KexiNameDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ KexiNameDialog(const QString& message,
+ QWidget * parent = 0, const char * name = 0);
+
+ KexiNameDialog(const QString& message,
+ const QString& nameLabel, const QString& nameText,
+ const QString& captionLabel, const QString& captionText,
+ QWidget * parent = 0, const char * name = 0);
+
+ virtual ~KexiNameDialog();
+
+ KexiNameWidget* widget() const { return m_widget; }
+
+ virtual void show();
+
+ public slots:
+ virtual void setDialogIcon(const QPixmap& icon);
+
+ protected slots:
+ void slotTextChanged();
+ virtual void accept();
+ void updateSize();
+
+ protected:
+ void init();
+
+ QLabel *m_icon;
+ KexiNameWidget* m_widget;
+};
+
+#endif
diff --git a/kexi/main/kexinamewidget.cpp b/kexi/main/kexinamewidget.cpp
new file mode 100644
index 000000000..39cb084a2
--- /dev/null
+++ b/kexi/main/kexinamewidget.cpp
@@ -0,0 +1,236 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexinamewidget.h"
+
+#include <qlabel.h>
+#include <qlayout.h>
+
+#include <klineedit.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+
+#include <kexiutils/validator.h>
+#include <kexiutils/identifier.h>
+#include <core/kexi.h>
+
+using namespace KexiUtils;
+
+KexiNameWidget::KexiNameWidget( const QString& message,
+ QWidget* parent, const char* name, WFlags fl )
+ : QWidget(parent, name, fl)
+{
+ init(message, QString::null, QString::null, QString::null, QString::null);
+}
+
+KexiNameWidget::KexiNameWidget(const QString& message,
+ const QString& nameLabel, const QString& nameText,
+ const QString& captionLabel, const QString& captionText,
+ QWidget * parent, const char * name, WFlags fl)
+{
+ Q_UNUSED( parent );
+ Q_UNUSED( name );
+ Q_UNUSED( fl );
+
+ init(message, nameLabel, nameText, captionLabel, captionText);
+}
+
+void KexiNameWidget::init(
+ const QString& message,
+ const QString& nameLabel, const QString& nameText,
+ const QString& captionLabel, const QString& captionText)
+{
+ Q_UNUSED( captionText );
+
+ m_le_name_txtchanged_disable = false;
+ m_le_name_autofill = true;
+ m_caption_required = false;
+
+ lyr = new QGridLayout( this, 1, 1, 0, 6, "lyr");
+
+ lbl_message = new QLabel( this, "message" );
+ setMessageText( message );
+ lbl_message->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ lbl_message->setAlignment( QLabel::AlignTop | QLabel::WordBreak );
+ lyr->addMultiCellWidget( lbl_message, 0, 0, 0, 1 );
+
+ lbl_caption = new QLabel( captionLabel.isEmpty() ? i18n( "Caption:" ) : captionLabel,
+ this, "lbl_caption" );
+ lyr->addWidget( lbl_caption, 1, 0 );
+
+ lbl_name = new QLabel( nameLabel.isEmpty() ? tr( "Name:" ) : nameLabel,
+ this, "lbl_name" );
+ lyr->addWidget( lbl_name, 2, 0 );
+
+ le_caption = new KLineEdit( nameText, this, "le_caption" );
+ le_caption->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed, 1, 0));
+ lyr->addWidget( le_caption, 1, 1 );
+
+ le_name = new KLineEdit( nameText, this, "le_name" );
+ le_name->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed,1,0));
+ Validator *idValidator = new IdentifierValidator(0, "id_val");
+ le_name->setValidator( m_validator = new MultiValidator(idValidator, this, "val") );
+ lyr->addWidget( le_name, 2, 1 );
+
+ setFocusProxy(le_caption);
+ resize( QSize(342, 123).expandedTo(minimumSizeHint()) );
+
+ m_nameWarning = i18n("Please enter the name.");
+ m_captionWarning = i18n("Please enter the caption.");
+
+ connect(le_caption, SIGNAL(textChanged(const QString&)),
+ this,SLOT(slotCaptionTxtChanged(const QString&)));
+ connect(le_name, SIGNAL(textChanged(const QString&)),
+ this,SLOT(slotNameTxtChanged(const QString&)));
+ connect(le_caption, SIGNAL(returnPressed()),
+ this,SIGNAL(returnPressed()));
+ connect(le_name, SIGNAL(returnPressed()),
+ this,SIGNAL(returnPressed()));
+}
+
+KexiNameWidget::~KexiNameWidget()
+{
+}
+
+void KexiNameWidget::slotCaptionTxtChanged(const QString &capt)
+{
+ emit textChanged();
+ if (le_name->text().isEmpty())
+ m_le_name_autofill=true;
+ if (m_le_name_autofill) {
+ m_le_name_txtchanged_disable = true;
+ le_name->setText( string2Identifier(capt).lower() );
+ m_le_name_txtchanged_disable = false;
+ }
+}
+
+void KexiNameWidget::slotNameTxtChanged(const QString &)
+{
+ emit textChanged();
+ if (m_le_name_txtchanged_disable)
+ return;
+ m_le_name_autofill = false;
+}
+
+void KexiNameWidget::clear()
+{
+ le_name->clear();
+ le_caption->clear();
+}
+
+bool KexiNameWidget::empty() const
+{
+ return le_name->text().isEmpty() || le_caption->text().stripWhiteSpace().isEmpty();
+}
+
+void KexiNameWidget::setNameRequired( bool set )
+{ m_validator->setAcceptsEmptyValue(!set); }
+
+bool KexiNameWidget::isNameRequired() const
+{ return !m_validator->acceptsEmptyValue(); }
+
+void KexiNameWidget::setCaptionText(const QString& capt)
+{
+ le_caption->setText(capt);
+ m_le_name_autofill = true;
+}
+
+void KexiNameWidget::setNameText(const QString& name)
+{
+ le_name->setText(name);
+ m_le_name_autofill = true;
+}
+
+void KexiNameWidget::setMessageText(const QString& msg)
+{
+ if (msg.stripWhiteSpace().isEmpty()) {
+ lbl_message->setText("");
+ lbl_message->hide();
+ } else {
+ lbl_message->setText(msg.stripWhiteSpace()+"<br>");
+ lbl_message->show();
+ }
+ messageChanged();
+}
+
+QString KexiNameWidget::captionText() const
+{
+ return le_caption->text();
+}
+
+QString KexiNameWidget::nameText() const
+{
+ return le_name->text().lower();
+}
+
+bool KexiNameWidget::checkValidity()
+{
+ if (isNameRequired() && le_name->text().stripWhiteSpace().isEmpty()) {
+ KMessageBox::sorry(0, m_nameWarning);
+ le_name->setFocus();
+ return false;
+ }
+ if (isCaptionRequired() && le_caption->text().stripWhiteSpace().isEmpty()) {
+ KMessageBox::sorry(0, m_captionWarning);
+ le_caption->setFocus();
+ return false;
+ }
+ QString dummy, message, details;
+ if (m_validator->check(dummy, le_name->text(), message, details)
+ ==Validator::Error) {
+ KMessageBox::detailedSorry(0, message, details);
+ le_name->setFocus();
+ return false;
+ }
+ return true;
+}
+
+Validator *KexiNameWidget::nameValidator() const
+{
+ return m_validator;
+}
+
+void KexiNameWidget::addNameSubvalidator( Validator* validator, bool owned )
+{
+ m_validator->addSubvalidator( validator, owned );
+}
+
+/*bool KexiNameWidget::eventFilter( QObject *obj, QEvent *ev )
+{
+ if (ev->type()==QEvent::FocusOut && !acceptsEmptyValue()) {
+ if (obj==le_name) {
+ if (le_name->text().isEmpty()) {
+ KMessageBox::information(0, m_nameWarning);
+ le_name->setFocus();
+ return true;
+ }
+ }
+ else if (obj==le_caption) {
+ if (le_caption->text().isEmpty()) {
+ KMessageBox::information(0, m_captionWarning);
+ le_caption->setFocus();
+ return true;
+ }
+ }
+ }
+ return false;
+}*/
+
+#include "kexinamewidget.moc"
+
diff --git a/kexi/main/kexinamewidget.h b/kexi/main/kexinamewidget.h
new file mode 100644
index 000000000..07962b6c6
--- /dev/null
+++ b/kexi/main/kexinamewidget.h
@@ -0,0 +1,142 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXINAMEWIDGET_H
+#define KEXINAMEWIDGET_H
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <klineedit.h>
+
+namespace KexiUtils {
+class Validator;
+class MultiValidator;
+}
+
+class KEXIMAIN_EXPORT KexiNameWidget : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiNameWidget(const QString& message,
+ QWidget * parent = 0, const char * name = 0, WFlags fl = 0);
+
+ KexiNameWidget(const QString& message,
+ const QString& nameLabel, const QString& nameText,
+ const QString& captionLabel, const QString& captionText,
+ QWidget * parent = 0, const char * name = 0, WFlags fl = 0);
+
+ virtual ~KexiNameWidget();
+
+ QLabel* captionLabel() const { return lbl_caption; }
+ QLabel* nameLabel() const { return lbl_name; }
+ KLineEdit* captionLineEdit() const { return le_caption; }
+ KLineEdit* nameLineEdit() const { return le_name; }
+
+ QString messageText() const { return lbl_message->text(); }
+
+ void setMessageText(const QString& msg);
+
+ //! \return entered caption text
+ QString captionText() const;
+
+ void setCaptionText(const QString& capt);
+ //! \return entered name text, always in lower case
+
+ QString nameText() const;
+
+ void setNameText(const QString& name);
+
+ /*! Sets i18n'ed warning message displayed when user leaves 'name' field
+ without filling it (if acceptsEmptyValue() is false).
+ By default the message is equal "Please enter the name.". */
+ void setWarningForName( const QString& txt ) { m_nameWarning = txt; }
+
+ /*! Sets i18n'ed warning message displayed when user leaves 'name' field
+ without filling it (if acceptsEmptyValue() is false).
+ By default the message is equal "Please enter the caption." */
+ void setWarningForCaption( const QString& txt ) { m_captionWarning = txt; }
+
+ /*! \return true if name or caption is empty. */
+ bool empty() const;
+
+ KexiUtils::Validator *nameValidator() const;
+
+ /*! Adds subvalidator for name field. In fact it's is added to internal
+ multivalidator. If \a owned is true, \a validator will be owned by the object.
+ \sa MultiValidator::addSubvalidator(). */
+ void addNameSubvalidator( KexiUtils::Validator* validator, bool owned = true );
+
+ /*! \return true if name text cannot be empty (true by default). */
+ bool isNameRequired() const;
+
+ void setNameRequired( bool set );
+
+ /*! \return true if caption text cannot be empty (false by default). */
+ bool isCaptionRequired() const { return m_caption_required; }
+
+ void setCaptionRequired(bool set) { m_caption_required = set; }
+
+ public slots:
+ /*! Clears both name and caption. */
+ virtual void clear();
+
+ /*! Checks if both fields have valid values
+ (i.e. not empty if acceptsEmptyValue() is false).
+ If not, warning message is shown and false is returned. */
+ bool checkValidity();
+
+ signals:
+ /*! Emitted whenever return key is pressed on name or caption label. */
+ void returnPressed();
+
+ /*! Emitted whenever the caption or the name text changes */
+ void textChanged();
+
+ /*! Emitted whenever the message changes */
+ void messageChanged();
+
+ protected slots:
+ void slotNameTxtChanged(const QString&);
+ void slotCaptionTxtChanged(const QString&);
+// bool eventFilter( QObject *obj, QEvent *ev );
+
+ protected:
+ void init(
+ const QString& message,
+ const QString& nameLabel, const QString& nameText,
+ const QString& captionLabel, const QString& captionText);
+
+ QLabel* lbl_message;
+ QLabel* lbl_caption;
+ QLabel* lbl_name;
+ KLineEdit* le_caption;
+ KLineEdit* le_name;
+ QGridLayout* lyr;
+ KexiUtils::MultiValidator *m_validator;
+ QString m_nameWarning, m_captionWarning;
+
+ bool m_le_name_txtchanged_disable : 1;
+ bool m_le_name_autofill : 1;
+ bool m_caption_required : 1;
+
+ friend class KexiNameDialog;
+};
+
+#endif
diff --git a/kexi/main/kexinewstuff.cpp b/kexi/main/kexinewstuff.cpp
new file mode 100644
index 000000000..adc0fc833
--- /dev/null
+++ b/kexi/main/kexinewstuff.cpp
@@ -0,0 +1,81 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifdef HAVE_KNEWSTUFF
+
+#include <kdebug.h>
+#include <ktar.h>
+#include <qdir.h>
+#include <kaction.h>
+#include <kapplication.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kdeversion.h>
+
+#include "kexinewstuff.h"
+
+KexiNewStuff::KexiNewStuff(QWidget *parent)
+ : KNewStuff( "kexi/template"
+#if KDE_IS_VERSION(3,3,0)
+ , "http://download.kde.org/khotnewstuff/kexi-providers.xml"
+#endif
+ , parent)
+{
+ // Prevent GHNS to deny downloading a second time. If GHNS
+ // fails to download something, it still marks the thing as
+ // successfully downloaded and therefore we arn't able to
+ // download it again :-/
+ KGlobal::config()->deleteGroup("KNewStuffStatus");
+}
+
+KexiNewStuff::~KexiNewStuff()
+{
+}
+
+bool
+KexiNewStuff::install(const QString &fileName)
+{
+ kdDebug() << "KexiNewStuff::install(): " << fileName << endl;
+
+ KTar archive( fileName );
+ if ( !archive.open( IO_ReadOnly ) ) {
+ kdDebug() << QString("KexiNewStuff::install: Failed to open archivefile \"%1\"").arg(fileName) << endl;
+ return false;
+ }
+ const KArchiveDirectory *archiveDir = archive.directory();
+ const QString destDir = KFileDialog::getExistingDirectory(
+ ":DownloadExampleDatabases", parentWidget(),
+ i18n("Choose Directory Where to Install Example Database"));
+ if (destDir.isEmpty()) {
+ kdDebug() << QString("KexiNewStuff::install: Destination-directory is empty.") << endl;
+ return false;
+ }
+ archiveDir->copyTo(destDir);
+ archive.close();
+
+ return true;
+}
+
+bool
+KexiNewStuff::createUploadFile(const QString &)
+{
+ return true;
+}
+
+#endif
diff --git a/kexi/main/kexinewstuff.h b/kexi/main/kexinewstuff.h
new file mode 100644
index 000000000..0af25276f
--- /dev/null
+++ b/kexi/main/kexinewstuff.h
@@ -0,0 +1,41 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifdef HAVE_KNEWSTUFF
+
+#ifndef KEXINEWSTUFF_H
+#define KEXINEWSTUFF_H
+
+#include "knewstuff/knewstuff.h"
+
+class KexiNewStuff : public KNewStuff
+{
+ public:
+ KexiNewStuff(QWidget *parent);
+ virtual ~KexiNewStuff();
+
+ virtual bool install( const QString &fileName );
+ virtual bool createUploadFile( const QString &fileName );
+
+ //KNS::Engine* customEngine() { return KNewStuff::engine(); }
+};
+
+#endif
+
+#endif //HAVE_KNEWSTUFF
diff --git a/kexi/main/kexistatusbar.cpp b/kexi/main/kexistatusbar.cpp
new file mode 100644
index 000000000..c05f0eeb3
--- /dev/null
+++ b/kexi/main/kexistatusbar.cpp
@@ -0,0 +1,145 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Loosely based on kdevelop/src/statusbar.cpp
+ Copyright (C) 2001 by Bernd Gehrmann <bernd@kdevelop.org>
+*/
+
+#include "kexistatusbar.h"
+
+#include <qlayout.h>
+#include <qlineedit.h>
+#include <qpainter.h>
+#include <qtimer.h>
+#include <qfontmetrics.h>
+
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <klocale.h>
+#include <kparts/part.h>
+
+#if KexiStatusBar_KTEXTEDITOR_USED
+#include <ktexteditor/viewcursorinterface.h>
+#include <ktexteditor/viewstatusmsginterface.h>
+#endif
+
+KexiStatusBar::KexiStatusBar(QWidget *parent, const char *name)
+ : KStatusBar(parent, name)
+#if KexiStatusBar_KTEXTEDITOR_USED
+ , m_cursorIface(0)
+#endif
+ , m_activePart(0)
+{
+ int id = 0;
+ m_msgID = id++;
+ insertItem("", m_msgID, 1, true);
+
+ m_readOnlyID = id++;
+ insertFixedItem(i18n("Read only"), m_readOnlyID, true);
+ setReadOnlyFlag(false);
+
+// @todo
+// connect(PartController::getInstance(), SIGNAL(activePartChanged(KParts::Part*)),
+// this, SLOT(activePartChanged(KParts::Part*)));
+
+ /// @todo remove parts from the map on PartRemoved() ?
+}
+
+
+KexiStatusBar::~KexiStatusBar()
+{
+}
+
+void KexiStatusBar::activePartChanged(KParts::Part *part)
+{
+ if ( m_activePart && m_activePart->widget() )
+ disconnect( m_activePart->widget(), 0, this, 0 );
+
+ m_activePart = part;
+#if KexiStatusBar_KTEXTEDITOR_USED
+ m_cursorIface = 0;
+ m_viewmsgIface = 0;
+// @todo
+ if (part && part->widget()) {
+ if ((m_viewmsgIface = dynamic_cast<KTextEditor::ViewStatusMsgInterface*>(part->widget()))) {
+ connect( part->widget(), SIGNAL( viewStatusMsg( const QString & ) ),
+ this, SLOT( setStatus( const QString & ) ) );
+
+# if KDE_VERSION < KDE_MAKE_VERSION(3,1,90)
+ changeItem(m_map[ m_activePart ], m_msgID);
+// m_status->setText( m_map[ m_activePart ] );
+# endif
+ }
+ else if ((m_cursorIface = dynamic_cast<KTextEditor::ViewCursorInterface*>(part->widget()))) {
+ connect(part->widget(), SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged()));
+ cursorPositionChanged();
+ }
+ else {
+ // we can't produce any status data, hide the status box
+ changeItem("", m_msgID);
+ }
+ }
+#endif
+}
+
+
+void KexiStatusBar::cursorPositionChanged()
+{
+#if KexiStatusBar_KTEXTEDITOR_USED
+ if (m_cursorIface)
+ {
+ uint line, col;
+ m_cursorIface->cursorPosition(&line, &col);
+ setCursorPosition(line, col);
+ }
+#endif
+}
+
+void KexiStatusBar::setStatus(const QString &str)
+{
+ kdDebug() << "KexiStatusBar::setStatus(" << str << ")" << endl;
+// m_status->setText(str);
+ changeItem(str, m_msgID);
+
+#if defined(KDE_MAKE_VERSION)
+# if KDE_VERSION < KDE_MAKE_VERSION(3,1,90)
+ m_map[m_activePart] = str;
+# endif
+#endif
+}
+
+void KexiStatusBar::setCursorPosition(int line, int col)
+{
+// m_status->setText(i18n(" Line: %1 Col: %2 ").arg(line+1).arg(col));
+ changeItem(i18n(" Line: %1 Col: %2 ").arg(line+1).arg(col), m_msgID);
+}
+
+/*void KexiStatusBar::addWidget ( QWidget *widget, int stretch, bool permanent)
+{
+ KStatusBar::addWidget(widget,stretch,permanent);
+
+ if(widget->sizeHint().height() + 4 > height())
+ setFixedHeight(widget->sizeHint().height() + 4);
+}*/
+
+void KexiStatusBar::setReadOnlyFlag(bool readOnly)
+{
+ changeItem(readOnly ? i18n("Read only") : QString::null, m_readOnlyID);
+}
+
+#include "kexistatusbar.moc"
diff --git a/kexi/main/kexistatusbar.h b/kexi/main/kexistatusbar.h
new file mode 100644
index 000000000..eec249325
--- /dev/null
+++ b/kexi/main/kexistatusbar.h
@@ -0,0 +1,77 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Loosely based on kdevelop/src/statusbar.h
+ Copyright (C) 2001 by Bernd Gehrmann <bernd@kdevelop.org>
+*/
+
+#ifndef KEXISTATUSBAR_H
+#define KEXISTATUSBAR_H
+
+//temporary
+#define KexiStatusBar_KTEXTEDITOR_USED 0
+
+#include <kdeversion.h>
+#include <kstatusbar.h>
+#include <qmap.h>
+
+class QLabel;
+
+#if KexiStatusBar_KTEXTEDITOR_USED
+namespace KTextEditor { class ViewStatusMsgInterface; }
+namespace KTextEditor { class ViewCursorInterface; }
+#endif
+namespace KParts { class Part; }
+
+class KexiStatusBar : public KStatusBar
+{
+ Q_OBJECT
+ public:
+ KexiStatusBar( QWidget *parent=0, const char *name=0 );
+ virtual ~KexiStatusBar();
+// virtual void addWidget( QWidget *widget, int stretch = 0, bool permanent = false);
+
+ public slots:
+ virtual void setStatus(const QString &str);
+ virtual void setReadOnlyFlag(bool readOnly);
+
+ protected slots:
+ virtual void cursorPositionChanged();
+ virtual void activePartChanged(KParts::Part *part);
+ virtual void setCursorPosition(int line, int col);
+
+ protected:
+ int m_msgID, m_readOnlyID;
+// QLabel *m_status, *m_readOnlyStatus;
+
+#if KexiStatusBar_KTEXTEDITOR_USED
+ KTextEditor::ViewCursorInterface * m_cursorIface;
+ KTextEditor::ViewStatusMsgInterface * m_viewmsgIface;
+#endif
+ KParts::Part *m_activePart;
+
+// still hoping for a fix for KDE-3.1
+#if defined(KDE_MAKE_VERSION)
+# if KDE_VERSION < KDE_MAKE_VERSION(3,1,90)
+ QMap<KParts::Part*, QString> m_map;
+# endif
+#endif
+};
+
+#endif
+
diff --git a/kexi/main/ksplitter.h b/kexi/main/ksplitter.h
new file mode 100644
index 000000000..db72ec880
--- /dev/null
+++ b/kexi/main/ksplitter.h
@@ -0,0 +1,256 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2000 Max Judin <novaprint@mtu-net.ru>
+ Copyright (C) 2005 Dominik Haumann <dhdev@gmx.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+/*
+ IMPORTANT Note: This file compiles also in Qt-only mode by using the NO_KDE2 precompiler definition
+*/
+
+#ifndef KDOCKWIDGET_PRIVATE_H
+#define KDOCKWIDGET_PRIVATE_H
+
+#include <qwidget.h>
+#include <qpushbutton.h>
+
+#include <kdeversion.h>
+
+#ifndef NO_KDE2
+#include <netwm_def.h>
+#endif
+
+class QFrame;
+class KDockContainer;
+class KDockWidget;
+
+
+/**
+ * Like QSplitter but specially designed for dockwidgets stuff.
+ * @internal
+ *
+ * @author Max Judin.
+ */
+class KDockSplitter : public QWidget
+{
+ //Q_OBJECT
+public:
+ /**
+ * Constructor.
+ * @param parent parent widget
+ * @param name name
+ * @param orient orientation. Either @p Vertical or @p Horizontal
+ * @param pos procentual position of the splitter. Must be int [0...100].
+ */
+ KDockSplitter(QWidget *parent= 0, const char *name= 0, Orientation orient= Vertical, int pos= 50);
+ virtual ~KDockSplitter(){};
+
+ /**
+ * Initialize the splitter. If @p c0 or @p c1 is 0L the child will not
+ * be replaced. So if you want to change @p c1 and not change c0, you'd
+ * call @p activate(0L,new_widget);
+ *
+ * @param c0 the widget on top/left
+ * @param c1 the widget on borrom/right
+ */
+ void activate(QWidget *c0, QWidget *c1 = 0L);
+ /**
+ * Disables the splitter.
+ */
+ void deactivate();
+
+ /**
+ * Return the separator position in percent (%), so the range is [0..100]
+ * @return separator position in percent
+ */
+ int separatorPosInPercent()
+#if KDE_IS_VERSION(3,4,89)
+ ;
+#else
+ {
+// kdDebug() << "^^^^^^^^^^^^^^^^^^^^^ separatorPosInPercent() " << separatorPos() / (factor/100) << " " << separatorPos() / 100 << endl;
+ return separatorPos() / 100;
+ }
+#endif
+ /**
+ * Set the separator position in percent (%), so the range must be [0..100]
+ * @param percent separator position in percent
+ */
+ void setSeparatorPosInPercent(int percent)
+#if KDE_IS_VERSION(3,4,89)
+ ;
+#else
+ {
+// kdDebug() << "^^^^^^^^^^^^^^^^^^^^^ setSeparatorPosInPercent() " << percent << " " << separatorPos() / 100 << endl;
+ setSeparatorPos( percent * 100, false );
+ }
+#endif
+
+ /**
+ * Return the separator position in the range [0..100000]
+ * To get the separator position in procent (%), call
+ * @p separatorPositionInPercent()!
+ *
+ * @return high resolution separator position in range [0..100000],
+ * where 100000 is 100%.
+ */
+ int separatorPos() const;
+ /**
+ * set separator position.
+ * @param pos the separator position in range [0..100000]. 100000 is 100%.
+ * @param do_resize true by default
+ */
+ void setSeparatorPos(int pos, bool do_resize = true);
+ /**
+ * For usage from outside.
+ * If the splitter is in fixed position when called,
+ * the value of @p pos will be saved and used when the splitter
+ * is restored.
+ * If @p do_resize is true, the size will be changed unless the splitter
+ * is in fixed mode.
+ */
+ // ### please come up with a nicer name
+ void setSeparatorPosX(int pos, bool do_resize=false);
+
+ /**
+ * The eventfilter installed on the @p divider processes
+ * all splitter resizing events.
+ */
+ virtual bool eventFilter(QObject *, QEvent *);
+ virtual bool event( QEvent * );
+
+ /**
+ * @return the top/left child widget.
+ */
+ QWidget* getFirst() const { return child0; }
+ /**
+ * @return the bottom/right child widget.
+ */
+ QWidget* getLast() const { return child1; }
+ /**
+ * If @p w is child0, return child1, otherwise child0.
+ * @return the other child widget
+ */
+ QWidget* getAnother( QWidget* w ) const;
+ void updateName();
+
+ /**
+ * Set opaque flag.
+ * @param b if true, both child widgets are resized immediately,
+ * if false, the widgets only resize on MouseUpEvent.
+ */
+ void setOpaqueResize(bool b=true);
+ bool opaqueResize() const;
+
+ /**
+ * If @p b is true, the splitter will keep its size on resize events.
+ * If no @p KDockContainer is around, always the left child0 will be fixed size.
+ */
+ void setKeepSize(bool b=true);
+ bool keepSize() const;
+
+
+ void setForcedFixedWidth(KDockWidget *dw,int w);
+ void setForcedFixedHeight(KDockWidget *dw,int h);
+ void restoreFromForcedFixedSize(KDockWidget *dw);
+
+ /**
+ * The orientation is either @p Horizontal or @p Vertical.
+ */
+ Orientation orientation(){return m_orientation;}
+
+protected:
+ friend class KDockContainer;
+ /**
+ * Make sure the splitter position is not out of bounds.
+ * @param position the current position
+ * @return a (new) valid splitter position.
+ */
+ int checkValue(int position) const;
+ /**
+ * Make sure the splitter position is not out of bounds. It has
+ * to honor all child widgets' mimimumSize.
+ * @param position current divider position
+ * @param child the overlapping child
+ * @return the (new) splitter position.
+ */
+ int checkValueOverlapped(int position, QWidget* child) const;
+
+ /**
+ * The resize event resizes @p child0, @p child1 and the @p divider.
+ * The new sizes are dependant of
+ * - whether @p child0 or @p child1 is a KDockContainer
+ * - the current mode which may be
+ * - Closed
+ * - Overlapped (opened)
+ * - Nonoverlap (opened)
+ * .
+ * .
+ * So there are 3*2=6 different modes we have to face.
+ * @param ev the resize Event. If @p ev=0L the user changed
+ * the mode (for example from overlap to nonoverlap mode).
+ */
+ virtual void resizeEvent(QResizeEvent *ev);
+
+/*
+protected slots:
+ void delayedResize();*/
+
+private:
+ /**
+ * updates the minimum and maximun sizes for the KDockSplitter.
+ * The sizes depend on the minimum and maximum sizes of the two child
+ * widgets.
+ */
+ void setupMinMaxSize();
+ /**
+ * child0 and child1 contain the embedded widgets. They are always valid
+ * so no need to make pointer checks.
+ * child[01]->getWidget() may be KDockContainer.
+ */
+ QWidget *child0, *child1;
+ Orientation m_orientation;
+ /**
+ * If initialised is true, the divider!=0L. If false, the divider==0L!
+ */
+ bool initialised;
+ /**
+ * The splitter controller which is between child0 and child1.
+ * Its size is 4 pixel.
+ */
+ QFrame* divider;
+ /**
+ * @p xpos and @p savedXPos represent the current divider position.
+ * If the orientation is Horizontal @p xpos actually is "ypos". So
+ * do not get confused only because of the 'x'.
+ *
+ * xpos and savedXPos are internally high resolution. So *not* 0..100%
+ * but 0..100000=100%. This fixes rounding bugs. In fact, this should
+ * be a double, but due to binary compatibility we can not change this
+ * as we would have to change it in all kdockwidgets.
+ */
+ int xpos, savedXPos;
+ bool mOpaqueResize, mKeepSize;
+ int fixedWidth0,fixedWidth1;
+ int fixedHeight0,fixedHeight1;
+ bool m_dontRecalc;
+ /**
+ * resolution factor, 0 = 0%, 100000=100%
+ */
+ static const int factor = 100000;
+};
+
+#endif
diff --git a/kexi/main/printing/Makefile.am b/kexi/main/printing/Makefile.am
new file mode 100644
index 000000000..a9d49a545
--- /dev/null
+++ b/kexi/main/printing/Makefile.am
@@ -0,0 +1,32 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_LTLIBRARIES = libkeximainprinting.la
+
+libkeximainprinting_la_SOURCES = \
+kexisimpleprintingengine.cpp \
+kexisimpleprintingpart.cpp \
+kexisimpleprintingpagesetupbase.ui \
+kexisimpleprintingpagesetup.cpp \
+kexisimpleprintpreviewwindow.cpp
+
+noinst_HEADERS = kexisimpleprintpreviewwindow_p.h
+
+libkeximainprinting_la_LDFLAGS = $(all_libraries) -Wnounresolved
+libkeximainprinting_la_LIBADD = \
+ $(top_builddir)/lib/kofficecore/libkofficecore.la \
+ $(top_builddir)/lib/kofficeui/libkofficeui.la \
+ ../../widget/libkexiextendedwidgets.la
+
+libkeximainprinting_la_METASOURCES = AUTO
+
+SUBDIRS = .
+
+# set the include path for X, qt and KDE
+
+INCLUDES= -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/main/printing \
+ -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi/widget \
+ -I$(top_builddir)/lib/kofficeui -I$(top_srcdir)/lib/kofficeui \
+ -I$(top_srcdir)/lib/kofficecore \
+ -I$(top_builddir)/kexi/widget $(all_includes)
+
+#KDE_CXXFLAGS += -DSIMPLE_KOLIBS -DKOFFICECORE_EXPORT= -DKOFFICEUI_EXPORT=
diff --git a/kexi/main/printing/kexisimpleprintingengine.cpp b/kexi/main/printing/kexisimpleprintingengine.cpp
new file mode 100644
index 000000000..e021c912d
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintingengine.cpp
@@ -0,0 +1,558 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexisimpleprintingengine.h"
+
+#include <core/keximainwindow.h>
+#include <kexiutils/utils.h>
+
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kfontdialog.h>
+#include <kurllabel.h>
+#include <kdebug.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qcheckbox.h>
+#include <qwhatsthis.h>
+#include <qpaintdevicemetrics.h>
+#include <qimage.h>
+
+#include <kexiutils/tristate.h>
+#include <kexidb/connection.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/cursor.h>
+#include <kexidb/utils.h>
+#include <kexidb/queryschema.h>
+
+KexiSimplePrintingSettings::KexiSimplePrintingSettings()
+{
+ pageLayout = KoPageLayout::standardLayout();
+ addPageNumbers = true;
+ addDateAndTime = true;
+ addTableBorders = false;
+ pageTitleFont = kapp->font();
+ pageTitleFont.setPointSizeFloat( (double)QFontInfo(pageTitleFont).pointSize()*1.5 );
+ pageTitleFont.setBold(true);
+}
+
+KexiSimplePrintingSettings::~KexiSimplePrintingSettings()
+{
+}
+
+KexiSimplePrintingSettings KexiSimplePrintingSettings::load()
+{
+ KexiSimplePrintingSettings settings; //this will set defaults
+
+ KConfig *config = kapp->config();
+ config->setGroup("Simple Printing");
+ if (config->hasKey("pageTitleFont"))
+ settings.pageTitleFont = config->readFontEntry("pageTitleFont");
+//! @todo system default?
+ if (config->hasKey("pageFormat"))
+ settings.pageLayout.format = KoPageFormat::formatFromString(
+ config->readEntry("pageFormat" ) );
+ if (config->readEntry("pageOrientation", "portrait").lower()=="landscape")
+ settings.pageLayout.orientation = PG_LANDSCAPE;
+ else
+ settings.pageLayout.orientation = PG_PORTRAIT;
+ if (config->hasKey("pageWidth"))
+ settings.pageLayout.ptWidth = config->readDoubleNumEntry("pageWidth");
+ if (config->hasKey("pageHeight"))
+ settings.pageLayout.ptHeight = config->readDoubleNumEntry("pageHeight");
+ if (config->hasKey("pageLeftMargin"))
+ settings.pageLayout.ptLeft = config->readDoubleNumEntry("pageLeftMargin");
+ if (config->hasKey("pageRightMargin"))
+ settings.pageLayout.ptRight = config->readDoubleNumEntry("pageRightMargin");
+ if (config->hasKey("pageTopMargin"))
+ settings.pageLayout.ptTop = config->readDoubleNumEntry("pageTopMargin");
+ if (config->hasKey("pageBottomMargin"))
+ settings.pageLayout.ptBottom = config->readDoubleNumEntry("pageBottomMargin");
+ settings.addPageNumbers = config->readBoolEntry("addPageNumbersToPage", true);
+ settings.addDateAndTime = config->readBoolEntry("addDateAndTimePage", true);
+ settings.addTableBorders = config->readBoolEntry("addTableBorders", false);
+ return settings;
+}
+
+void KexiSimplePrintingSettings::save()
+{
+ KConfig *config = kapp->config();
+ config->setGroup("Simple Printing");
+ config->writeEntry( "pageTitleFont", pageTitleFont );
+ config->writeEntry( "pageFormat", KoPageFormat::formatString( pageLayout.format ) );
+ config->writeEntry("pageOrientation",
+ pageLayout.orientation == PG_PORTRAIT ? "portrait" : "landscape");
+ config->writeEntry("pageWidth", pageLayout.ptWidth);
+ config->writeEntry("pageHeight", pageLayout.ptHeight);
+ config->writeEntry("pageLeftMargin", pageLayout.ptLeft);
+ config->writeEntry("pageRightMargin", pageLayout.ptRight);
+ config->writeEntry("pageTopMargin", pageLayout.ptTop);
+ config->writeEntry("pageBottomMargin", pageLayout.ptBottom);
+ config->writeEntry("addPageNumbersToPage", addPageNumbers);
+ config->writeEntry("addDateAndTimePage", addDateAndTime);
+ config->writeEntry("addTableBorders", addTableBorders);
+ config->sync();
+}
+
+//------------------------
+
+KexiSimplePrintingEngine::KexiSimplePrintingEngine(
+ const KexiSimplePrintingSettings& settings, QObject* parent)
+ : QObject(parent, "KexiSimplePrintingEngine")
+ , m_settings(&settings)
+ , m_pdm(0)
+{
+ m_cursor = 0;
+ m_data = 0;
+ m_visibleFieldsCount = 0;
+ m_dataOffsets.setAutoDelete(true);
+ clear();
+}
+
+KexiSimplePrintingEngine::~KexiSimplePrintingEngine()
+{
+ done();
+}
+
+bool KexiSimplePrintingEngine::init(KexiDB::Connection& conn,
+ KexiDB::TableOrQuerySchema& tableOrQuery, const QString& titleText, QString& errorMessage)
+{
+ errorMessage = QString::null;
+ done();
+ m_headerText = titleText; //tableOrQuery.captionOrName();
+
+ //open data source
+ KexiDB::QuerySchema *query = 0;
+ if (tableOrQuery.table())
+ query = tableOrQuery.table()->query(); //all rows
+ else
+ query = tableOrQuery.query();
+ if (!query) {
+ errorMessage = i18n("Could not load data from table or query.");
+ return false;
+ }
+
+ m_cursor = conn.executeQuery(*query);
+ if (!m_cursor) {
+ conn.debugError();
+ return false;
+ }
+ bool ok = !m_cursor->error();
+ if (ok) {
+ m_data = new KexiTableViewData(m_cursor);
+//! @todo primitive: data should be loaded on demand
+ m_data->preloadAllRows();
+ m_fieldsExpanded = query->fieldsExpanded( KexiDB::QuerySchema::WithInternalFields );
+ m_visibleFieldsCount = m_cursor->query()->fieldsExpanded().count(); //real fields count without internals
+ }
+ else {
+ conn.debugError();
+ }
+ m_eof = !ok || m_data->count() == 0;
+ conn.deleteCursor(m_cursor);
+ m_cursor = 0;
+ return ok;
+}
+
+bool KexiSimplePrintingEngine::done()
+{
+ bool result = true;
+ if (m_cursor && (m_cursor->error() || !m_cursor->connection()->deleteCursor(m_cursor))) {
+ m_cursor->debugError();
+ result = false;
+ }
+ m_cursor = 0;
+ delete m_data;
+ m_data = 0;
+ m_pagesCount = 0;
+ m_paintInitialized = false;
+ m_fieldsExpanded.clear();
+ m_visibleFieldsCount = 0;
+ return result;
+}
+
+void KexiSimplePrintingEngine::clear()
+{
+ m_eof = false;
+ m_pagesCount = 0;
+ m_dataOffsets.clear();
+ m_dataOffsets.append(new uint(0));
+ m_paintInitialized = false;
+}
+
+void KexiSimplePrintingEngine::paintPage(int pageNumber, QPainter& painter, bool paint)
+{
+ uint offset = 0;
+ if (pageNumber < (int)m_dataOffsets.count()) {
+ offset = *m_dataOffsets.at(pageNumber);
+ }
+
+ double y = 0.0;
+
+ const bool printing = painter.device()->devType() == QInternal::Printer;
+ m_SCALE = printing ? 1 : 20;
+
+ double w, h;
+ m_pdm = QPaintDeviceMetrics( painter.device() );
+
+ if (dynamic_cast<QWidget*>(painter.device())) {
+ w = dynamic_cast<QWidget*>(painter.device())->width() * m_SCALE;
+ h = dynamic_cast<QWidget*>(painter.device())->height() * m_SCALE;
+ }
+ else if (dynamic_cast<QPixmap*>(painter.device())) {
+ w = dynamic_cast<QPixmap*>(painter.device())->width() * m_SCALE;
+ h = dynamic_cast<QPixmap*>(painter.device())->height() * m_SCALE;
+ }
+ else {//KPrinter...
+ w = m_pdm.widthMM();
+ h = m_pdm.heightMM();
+ }
+
+ if (!m_paintInitialized) {
+ m_paintInitialized = true;
+
+ double widthMM = KoPageFormat::width(
+ m_settings->pageLayout.format, m_settings->pageLayout.orientation);
+ double heightMM = KoPageFormat::height(
+ m_settings->pageLayout.format, m_settings->pageLayout.orientation);
+
+ m_dpiY = m_pdm.logicalDpiY();
+ m_dpiX = m_pdm.logicalDpiX();
+#ifdef Q_WS_WIN //fix for 120dpi
+ if (!printing) {
+ m_dpiY = 96;
+ m_dpiX = 96;
+// m_dpiY = 86;
+// m_dpiX = 86;
+ }
+#endif
+ double pdWidthMM = m_pdm.widthMM();
+ double pdHeightMM = m_pdm.heightMM();
+
+// double screenF;
+// screenF = 1.0;
+
+ m_leftMargin = POINT_TO_INCH(m_settings->pageLayout.ptLeft)*m_dpiX * (double)m_SCALE;//* screenF;
+ m_rightMargin = POINT_TO_INCH(m_settings->pageLayout.ptRight)*m_dpiX * (double)m_SCALE;//* screenF;
+ m_topMargin = POINT_TO_INCH(m_settings->pageLayout.ptTop)*m_dpiY * (double)m_SCALE;//* screenF;
+ m_bottomMargin = POINT_TO_INCH(m_settings->pageLayout.ptBottom)*m_dpiY * (double)m_SCALE;//* screenF;
+
+ m_fx = widthMM / (pdWidthMM);// * screenF);
+ m_fy = heightMM / (pdHeightMM);// * screenF);
+
+ //screen only
+ // painter.fillRect(QRect(0,0,w,h), QBrush(white));
+ m_pageWidth = uint( m_fx*double(m_pdm.width()) * m_SCALE - m_leftMargin - m_rightMargin );
+ m_pageHeight = uint( m_fy*double(m_pdm.height()) * m_SCALE - m_topMargin - m_bottomMargin );
+ m_headerFont = m_settings->pageTitleFont;
+ if(!printing) {
+ int pixelSize = int( POINT_TO_INCH((double)QFontInfo(m_headerFont).pointSize())*m_dpiX ) * m_SCALE;
+ m_headerFont.setPixelSize(pixelSize);
+ }
+
+//! @todo add setting
+ m_mainFont = kapp->font();
+ if(!printing) {
+ int pixelSize = int( POINT_TO_INCH(m_mainFont.pointSizeFloat())*m_dpiX )
+ * m_SCALE;
+ m_mainFont.setPixelSize(pixelSize);
+ }
+ painter.setFont(m_mainFont);
+
+ m_dateTimeText = KGlobal::locale()->formatDateTime(QDateTime::currentDateTime(),
+ true, false);
+ m_dateTimeWidth = painter.fontMetrics().width(m_dateTimeText+" ");
+ m_mainLineSpacing = painter.fontMetrics().lineSpacing();
+ m_footerHeight = m_mainLineSpacing * 2; //2 lines
+ painter.setFont(m_headerFont);
+ m_headerTextRect = painter.fontMetrics().boundingRect(
+ (int)m_leftMargin, (int)m_topMargin,
+ m_pageWidth - m_dateTimeWidth,
+ m_pageHeight, Qt::AlignAuto|Qt::WordBreak, m_headerText);
+ m_headerTextRect.setRight(m_headerTextRect.right()+10);
+ m_headerTextRect.setWidth(
+ QMIN(int(m_pageWidth - m_dateTimeWidth), m_headerTextRect.width()));
+
+ //--compute max width of field names
+ m_maxFieldNameWidth = 0;
+
+ painter.setFont(m_mainFont);
+ for (uint i=0; i < m_visibleFieldsCount; i++) {
+ const int newW =
+ painter.fontMetrics().width(m_fieldsExpanded[i]->captionOrAliasOrName()+":");
+// kdDebug() << "row"<<i<<": "<<m_fieldsExpanded[i]->captionOrAliasOrName()<<" "
+// << newW <<endl;
+ if (m_maxFieldNameWidth < newW)
+ m_maxFieldNameWidth = newW;
+ }
+ m_maxFieldNameWidth += painter.fontMetrics().width("ww"); //more space
+ }
+
+ //screen only
+ if(!printing) {
+ painter.setWindow(0, 0, int(w*m_fx), int(h*m_fy));
+ }
+
+ //paint header
+ painter.setFont(m_headerFont);
+ if (paint) {
+ painter.drawText(m_headerTextRect, Qt::AlignAuto|Qt::WordBreak, m_headerText);
+ }
+ painter.setFont(m_mainFont);
+ if (paint) {
+ painter.drawText((int)m_leftMargin + m_pageWidth - m_dateTimeWidth,
+ (int)m_topMargin, m_dateTimeWidth,
+ m_headerTextRect.height(), Qt::AlignRight, m_dateTimeText);
+ //footer
+ QString pageNumString;
+ if (m_pagesCount>0)
+ pageNumString = i18n("Page (number) of (total)", "Page %1 of %2")
+ .arg(pageNumber+1).arg(m_pagesCount);
+ else
+ pageNumString = i18n("Page %1").arg(pageNumber+1);
+ painter.drawText((int)m_leftMargin,
+ (int)m_topMargin + m_pageHeight - m_mainLineSpacing,
+ m_pageWidth, m_mainLineSpacing,
+ Qt::AlignRight | Qt::AlignBottom, pageNumString);
+ painter.drawLine((int)m_leftMargin,
+ (int)m_topMargin + m_pageHeight - m_mainLineSpacing*3/2,
+ (int)m_leftMargin + m_pageWidth,
+ (int)m_topMargin + m_pageHeight - m_mainLineSpacing*3/2);
+ }
+ y = (double)m_topMargin + (double)m_headerTextRect.height() + double(m_mainLineSpacing)/2;
+ if (!m_settings->addTableBorders) {
+ //separator line
+ if (paint)
+ painter.drawLine((int)m_leftMargin, (int)y, (int)m_leftMargin + m_pageWidth-1, (int)y);
+ y += (double)m_mainLineSpacing;
+ }
+
+ //--print records
+ KexiDB::RowData row;
+ KexiTableItem *item;
+// const uint count = m_fieldsExpanded.count();
+// const uint count = m_cursor->query()->fieldsExpanded().count(); //real fields count without internals
+ const uint rows = m_data->count();
+ const int cellMargin = m_settings->addTableBorders ?
+ painter.fontMetrics().width("i") : 0;
+ uint paintedRows = 0;
+ for (;offset < rows; ++offset) {
+ item = m_data->at(offset);
+
+ //compute height of this record
+ double newY = y;
+ paintRecord(painter, item, cellMargin, newY, paintedRows, false, printing);
+// if ((int(m_topMargin + m_pageHeight-newY-m_footerHeight)) < 0 /*(1)*/ && paintedRows > 0/*(2)*/) {
+ if (newY > (m_topMargin + m_pageHeight - m_mainLineSpacing*2 + m_mainLineSpacing) /*(1)*/ && paintedRows > 0/*(2)*/) {
+ //(1) do not break records between pages
+ //(2) but paint at least one record
+//! @todo break large records anyway...
+ break;
+ }
+/* if (int(count * m_mainLineSpacing) > int(m_topMargin + m_pageHeight-(int)y-m_footerHeight))
+ {
+ //do not break records between pages
+ break;
+ }*/
+// kdDebug() << " -------- " << y << " / " << m_pageHeight << endl;
+ if (paint)
+ paintRecord(painter, item, cellMargin, y, paintedRows, paint, printing);
+ else
+ y = newY; //speedup
+ paintedRows++;
+ }
+
+ if (int(m_dataOffsets.count()-1)==pageNumber) {//this was next page
+ m_dataOffsets.append(new uint(offset));
+ }
+ m_eof = offset == rows;
+}
+
+void KexiSimplePrintingEngine::paintRecord(QPainter& painter, KexiTableItem *item,
+ int cellMargin, double &y, uint paintedRows, bool paint, bool printing)
+{
+ if (paintedRows>0 && !m_settings->addTableBorders) {//separator
+ if (paint) {
+ painter.setPen(Qt::darkGray);
+ painter.drawLine(
+ int(m_leftMargin), int( y-(double)m_mainLineSpacing ),
+ int(m_leftMargin)+m_pageWidth-1, int(y-(double)m_mainLineSpacing));
+ painter.setPen(Qt::black);
+ }
+ }
+
+ for (uint i=0; i<m_visibleFieldsCount; i++) {
+// kdDebug() << "row"<<i<<": "<<row.at(i).toString()<<endl;
+ if (paint) {
+ painter.drawText(
+ (int)m_leftMargin+cellMargin, (int)y, m_maxFieldNameWidth-cellMargin*2, m_mainLineSpacing,
+ Qt::AlignTop, m_fieldsExpanded[i]->captionOrAliasOrName()
+ + (m_settings->addTableBorders ? "" : ":"));
+ }
+ QString text;
+//! @todo optimize like in KexiCSVExport::exportData()
+ //get real column and real index to get the visible value
+ KexiDB::QueryColumnInfo* ci;
+ int indexForVisibleLookupValue = m_fieldsExpanded[i]->indexForVisibleLookupValue();
+ if (-1 != indexForVisibleLookupValue && indexForVisibleLookupValue < (int)item->count()/*sanity*/)
+ ci = m_fieldsExpanded[ indexForVisibleLookupValue ];
+ else {
+ ci = m_fieldsExpanded[ i ];
+ indexForVisibleLookupValue = i;
+ }
+
+ QVariant v(item->at( indexForVisibleLookupValue ));
+ KexiDB::Field::Type ftype = ci->field->type();
+ QRect rect( (int)m_leftMargin + m_maxFieldNameWidth + cellMargin, (int)y,
+ m_pageWidth - m_maxFieldNameWidth - cellMargin*2, m_pageHeight - (int)y);
+
+ if (v.isNull() || !v.isValid()) {
+ //nothing to do
+ }
+//! todo inherit format
+ else if (ftype==KexiDB::Field::DateTime) {
+ QDateTime dt(v.toDateTime());
+ if (dt.isValid())
+ text = KGlobal::locale()->formatDateTime(dt);
+ }
+//! todo inherit format
+ else if (ftype==KexiDB::Field::Date) {
+ QDate date(v.toDate());
+ if (date.isValid())
+ text = KGlobal::locale()->formatDate(date, true/*short*/);
+ }
+//! todo inherit format
+ else if (ftype==KexiDB::Field::Time) {
+ QTime time(v.toTime());
+ if (time.isValid())
+ text = KGlobal::locale()->formatTime(time);
+ }
+//! todo currency, decimal...
+ else if (ci->field->isFPNumericType())
+ text = KGlobal::locale()->formatNumber(v.toDouble());
+ else if (ftype==KexiDB::Field::Boolean)
+ text = v.toBool()
+ ? i18n("Boolean Yes (true)","Yes") : i18n("Boolean No (false)", "No");
+ else if (ftype==KexiDB::Field::BLOB) {
+ const QByteArray ba( v.toByteArray() );
+ if (!ba.isEmpty()) {
+ QPixmap pixmap(ba);
+#define MAX_PIXMAP_HEIGHT (m_mainLineSpacing * 5)
+ double pixmapHeight = MAX_PIXMAP_HEIGHT;
+ double pixmapWidth = double(MAX_PIXMAP_HEIGHT) * pixmap.width() / (double)pixmap.height();
+ if (pixmapWidth > (double)rect.width()) { //too wide
+ pixmapHeight = pixmapHeight * (double)rect.width() / pixmapWidth;
+ pixmapWidth = rect.width();
+ }
+ rect.setHeight( int( pixmapHeight + m_mainLineSpacing / 2 ) );
+ if (paint && !pixmap.isNull()) {
+ if (printing) {
+ painter.drawPixmap(
+ QRect(rect.x(), rect.y()+m_mainLineSpacing/4,
+ int(pixmapWidth), int(pixmapHeight)), pixmap );
+ }
+ else {// we're just previewing the pixmap, so let's resize it and cache
+ // so redrawing will be faster
+ painter.save();
+ painter.setWindow( // set 1:1 scale to avoid unnecessary image scaling
+ QRect(painter.window().topLeft(),
+ painter.window().size() / (int)m_SCALE ) );
+ painter.drawImage(
+ int(rect.x() / m_SCALE),
+ int( (rect.y()+m_mainLineSpacing/4) / m_SCALE),
+ pixmap.convertToImage().smoothScale(
+ int(pixmapWidth / m_SCALE), int(pixmapHeight / m_SCALE),
+ QImage::ScaleMin ));
+ painter.restore(); // back to m_SCALE:1 scale
+ }
+ }
+ }
+ }
+ else
+ text = v.toString();
+
+ if (ftype!=KexiDB::Field::BLOB || v.isNull() || !v.isValid())
+ rect = QRect( painter.fontMetrics().boundingRect(
+ rect.x(), rect.y(), rect.width(), rect.height(),
+ Qt::AlignAuto|Qt::WordBreak, text) );
+ if (!text.isEmpty() && paint) {
+// kdDebug() << "print engine: painter.drawText: "
+// << rect.x() <<" "<< rect.y() <<" "<< m_pageWidth - m_maxFieldNameWidth - cellMargin*2
+// <<" "<< m_topMargin + m_pageHeight - (int)y <<" "<<m_pageHeight<<" "<<y<<" "<< text << endl;
+ painter.drawText(
+// rect.x(), rect.y(), rect.width(), rect.height(),
+ rect.x(), rect.y(), m_pageWidth - m_maxFieldNameWidth - cellMargin*2,
+ int(m_topMargin + m_pageHeight - (int)y),
+ Qt::AlignTop|Qt::WordBreak, text);
+ }
+ if (m_settings->addTableBorders) {
+ if (paint) {
+ painter.setPen(Qt::darkGray);
+ painter.drawLine(
+ (int)m_leftMargin, rect.top(), (int)m_leftMargin+m_pageWidth-1, rect.top());
+ painter.drawLine(
+ (int)m_leftMargin, rect.top(), (int)m_leftMargin, rect.bottom());
+ painter.drawLine(
+ (int)m_leftMargin+m_pageWidth-1, rect.top(),
+ (int)m_leftMargin+m_pageWidth-1, rect.bottom());
+ painter.drawLine(
+ (int)m_leftMargin+m_maxFieldNameWidth, rect.top(),
+ (int)m_leftMargin+m_maxFieldNameWidth, rect.bottom());
+ painter.setPen(Qt::black);
+ }
+ }
+ y += (double)rect.height();
+ }
+ if (m_settings->addTableBorders) {
+ if (paint) {
+ painter.setPen(Qt::darkGray);
+ painter.drawLine(
+ (int)m_leftMargin, (int)y, (int)m_leftMargin+m_pageWidth-1, (int)y);
+ painter.setPen(Qt::black);
+ }
+ }
+ //record spacing
+ y += double(m_mainLineSpacing)*3.0/2.0;
+// if (m_settings->addTableBorders)
+// y -= m_mainLineSpacing; //a bit less
+}
+
+void KexiSimplePrintingEngine::calculatePagesCount(QPainter& painter)
+{
+ if (m_eof || !m_data) {
+ m_pagesCount = 0;
+ return;
+ }
+
+ uint pageNumber = 0;
+ for(;!m_eof; ++pageNumber) {
+ paintPage(pageNumber, painter, false /* !paint */);
+ }
+ m_pagesCount = pageNumber;
+}
+
+void KexiSimplePrintingEngine::setTitleText(const QString& titleText)
+{
+ m_headerText = titleText;
+}
+
+#include "kexisimpleprintingengine.moc"
diff --git a/kexi/main/printing/kexisimpleprintingengine.h b/kexi/main/printing/kexisimpleprintingengine.h
new file mode 100644
index 000000000..551ed2480
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintingengine.h
@@ -0,0 +1,130 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXISIMPLEPRINTINGENGINE_H
+#define KEXISIMPLEPRINTINGENGINE_H
+
+class KexiSimplePrintingSettings;
+
+#include <kexidb/connection.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/cursor.h>
+#include <kexidb/utils.h>
+#include <kexidb/queryschema.h>
+#include <widget/tableview/kexitableviewdata.h>
+#include <KoPageLayoutDia.h>
+
+#include <qpaintdevicemetrics.h>
+#include <qfontmetrics.h>
+#include <qfont.h>
+
+//! @short Settings data for simple printing engine.
+class KexiSimplePrintingSettings
+{
+ public:
+ KexiSimplePrintingSettings();
+ ~KexiSimplePrintingSettings();
+
+ static KexiSimplePrintingSettings load();
+ void save();
+
+ KoPageLayout pageLayout;
+ KoUnit::Unit unit;
+ QFont pageTitleFont;
+ bool addPageNumbers : 1;
+ bool addDateAndTime : 1;
+ bool addTableBorders : 1;
+};
+
+/*! @short An engine painting data on pages using QPainter.
+ The engine allows for random access to any page. */
+class KexiSimplePrintingEngine : public QObject
+{
+ Q_OBJECT
+
+ public:
+ KexiSimplePrintingEngine( const KexiSimplePrintingSettings& settings, QObject* parent );
+ ~KexiSimplePrintingEngine();
+
+ bool init(KexiDB::Connection& conn, KexiDB::TableOrQuerySchema& tableOrQuery,
+ const QString& titleText, QString& errorMessage);
+
+ void setTitleText(const QString& titleText);
+
+ //! Calculates pafe count that can be later obtained using pagesCount().
+ //! Page count can depend on \a painter (printer/screen) and on printing settings.
+ void calculatePagesCount(QPainter& painter);
+
+ bool done();
+ void clear();
+ const KexiSimplePrintingSettings* settings() const { return m_settings; }
+
+ //! \return true when all records has been painted
+ bool eof() const { return m_eof; }
+
+ //! \return number of pages. Can be used after calculatePagesCount().
+ uint pagesCount() const { return m_pagesCount; }
+
+ //! \return number of painted pages so far.
+ //! If eof() is true, this number is equal to total page count.
+ uint paintedPages() const { return m_dataOffsets.count(); }
+
+ public slots:
+ /*! Paints a page number \a pageNumber (counted from 0) on \a painter.
+ If \a paint is false, drawings are only computed but not painted,
+ so this can be used for calculating page number before printing or previewing. */
+ void paintPage(int pageNumber, QPainter& painter, bool paint = true);
+
+ protected:
+ void paintRecord(QPainter& painter, KexiTableItem *item,
+ int cellMargin, double &y, uint paintedRows, bool paint, bool printing);
+
+ const KexiSimplePrintingSettings* m_settings;
+
+// QPainter* m_painter;
+ QFont m_mainFont, m_headerFont;
+ QPaintDeviceMetrics m_pdm;
+ double m_dpiX, m_dpiY;
+ uint m_pageWidth, m_pageHeight;
+ uint m_SCALE;
+ //QFontMetrics m_headerFM, m_mainFM;
+ KexiDB::Cursor *m_cursor;
+ KexiTableViewData *m_data;
+// KexiTableViewData::Iterator *m_dataIterator;
+ QPtrList<uint> m_dataOffsets;
+ QString m_headerText;
+ QString m_dateTimeText;
+ uint m_dateTimeWidth;
+ QRect m_headerTextRect;
+ int m_maxFieldNameWidth;
+ int m_mainLineSpacing;
+ int m_footerHeight;
+ KexiDB::QueryColumnInfo::Vector m_fieldsExpanded;
+ uint m_visibleFieldsCount;
+ uint m_pagesCount;
+ bool m_eof;
+ bool m_paintInitialized; //!< used by paintPage()
+ double m_leftMargin;
+ double m_rightMargin;
+ double m_topMargin;
+ double m_bottomMargin;
+ double m_fx, m_fy;
+};
+
+#endif
diff --git a/kexi/main/printing/kexisimpleprintingpagesetup.cpp b/kexi/main/printing/kexisimpleprintingpagesetup.cpp
new file mode 100644
index 000000000..5d20f36e1
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintingpagesetup.cpp
@@ -0,0 +1,550 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexisimpleprintingpagesetup.h"
+#include "kexisimpleprintingpagesetupbase.h"
+#include "kexisimpleprintpreviewwindow.h"
+
+#include <core/keximainwindow.h>
+#include <kexiutils/utils.h>
+#include <kexi_version.h>
+
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kfontdialog.h>
+#include <kurllabel.h>
+#include <kdebug.h>
+#include <klineedit.h>
+#include <kprinter.h>
+#include <kpushbutton.h>
+#include <kdeversion.h>
+
+#include <qlabel.h>
+#include <qtimer.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qcheckbox.h>
+#include <qwhatsthis.h>
+#include <qtooltip.h>
+
+#include <kexiutils/tristate.h>
+
+KexiSimplePrintingCommand::KexiSimplePrintingCommand(
+ KexiMainWindow* mainWin, int objectId, QObject* parent)
+ : QObject(parent, "KexiSimplePrintCommand")
+ , m_previewEngine(0)
+ , m_mainWin(mainWin)
+ , m_objectId(objectId)
+ , m_previewWindow(0)
+ , m_printPreviewNeedsReloading(false)
+{
+ connect(this, SIGNAL(showPageSetupRequested(KexiPart::Item*)),
+ m_mainWin, SLOT(showPageSetupForItem(KexiPart::Item*)));
+}
+
+KexiSimplePrintingCommand::~KexiSimplePrintingCommand()
+{
+ delete m_previewWindow;
+ delete m_previewEngine;
+// delete m_settings;
+}
+
+
+bool KexiSimplePrintingCommand::print(const KexiSimplePrintingSettings& settings,
+ const QString& aTitleText)
+{
+ m_settings = settings;
+ return print(aTitleText);
+}
+
+bool KexiSimplePrintingCommand::print(const QString& aTitleText)
+{
+ KexiDB::Connection *conn = m_mainWin->project()->dbConnection();
+ KexiDB::TableOrQuerySchema tableOrQuery(conn, m_objectId);
+ if (!tableOrQuery.table() && !tableOrQuery.query()) {
+//! @todo item not found
+ return false;
+ }
+ QString titleText(aTitleText.stripWhiteSpace());
+ if (titleText.isEmpty())
+ titleText = tableOrQuery.captionOrName();
+
+ KexiSimplePrintingEngine engine(m_settings, this);
+ QString errorMessage;
+ if (!engine.init(*conn, tableOrQuery, titleText, errorMessage)) {
+ if (!errorMessage.isEmpty())
+ KMessageBox::sorry(m_mainWin, errorMessage, i18n("Printing"));
+ return false;
+ }
+
+ //setup printing
+#ifdef Q_WS_WIN
+ QPrinter printer(QPrinter::HighResolution);
+ printer.setOrientation( m_settings.pageLayout.orientation == PG_PORTRAIT
+ ? QPrinter::Portrait : QPrinter::Landscape );
+ printer.setPageSize(
+ (QPrinter::PageSize)KoPageFormat::printerPageSize( m_settings.pageLayout.format ) );
+
+ // "chicken-egg" problem:
+ // we cannot use real from/to values in setMinMax() and setFromTo()
+ // because page count is known after obtaining print settings
+ printer.setFromTo(1,1);
+#else
+ KPrinter printer;
+ printer.setOrientation( m_settings.pageLayout.orientation == PG_PORTRAIT
+ ? KPrinter::Portrait : KPrinter::Landscape );
+ printer.setPageSize(
+ (KPrinter::PageSize)KoPageFormat::printerPageSize( m_settings.pageLayout.format ) );
+#endif
+
+ printer.setFullPage(true);
+ QString docName( titleText );
+ printer.setDocName( docName );
+ printer.setCreator(KEXI_APP_NAME);
+ if ( !printer.setup( m_mainWin ) ) {
+ return true;
+ }
+
+ // now we have final settings
+
+//! @todo get printer.pageOrder() (for reversed order requires improved engine)
+ QPainter painter;
+
+ if (!painter.begin(&printer)) {
+//! @todo msg
+ return false;
+ }
+ engine.calculatePagesCount(painter);
+
+ uint loops, loopsPerPage;
+ QValueList<int> pagesToPrint;
+ int fromPage = 0;
+#ifdef Q_WS_WIN
+ int toPage = 0;
+ if (QPrinter::PageRange == printer.printRange()) {
+ fromPage = printer.fromPage();
+ toPage = printer.toPage();
+ }
+ if (fromPage==0 || toPage==0) {
+ fromPage = 0;
+ toPage = (int)engine.pagesCount()-1;
+ }
+ else {
+ fromPage--;
+ if (toPage > (int)engine.pagesCount())
+ toPage = (int)engine.pagesCount();
+ toPage--;
+ }
+ // win32 only supports one range, build the list
+ for (int i = fromPage; i<=toPage; i++) {
+ pagesToPrint.append(i);
+ }
+ // on win32 the OS does perform buffering (only when collation is off, each copy needs to be repeated)
+ loops = 1;
+ loopsPerPage = printer.collateCopies() ? 1 : printer.numCopies();
+#else
+ // on !win32 print QPrinter::numCopies() times (the OS does not perform buffering)
+ pagesToPrint = printer.pageList();
+ kdDebug() << pagesToPrint << endl;
+ if (pagesToPrint.isEmpty()) {
+ fromPage = 0;
+ for (int i = 0; i<(int)engine.pagesCount(); i++) {
+ pagesToPrint.append(i);
+ }
+ }
+ else
+ fromPage = pagesToPrint.first();
+ if (printer.collate()==KPrinter::Collate) {
+ //collation: p1, p2,..pn; p1, p2,..pn; ......; p1, p2,..pn
+ loops = printer.numCopies();
+ loopsPerPage = 1;
+ }
+ else {
+ //no collation: p1, p1, ..., p1; p2, p2, ..., p2; ......; pn, pn,..pn
+ loops = 1;
+ loopsPerPage = printer.numCopies();
+ }
+//! @todo also look at printer.pageSet() option : all/odd/even pages
+#endif
+ // now, total number of printed pages is printer.numCopies()*printer.pageList().count()
+
+ kdDebug() << "printing..." << endl;
+ bool firstPage = true;
+ for (uint copy = 0;copy < loops; copy++) {
+ kdDebug() << "copy " << (copy+1) << " of " << loops << endl;
+ uint pageNumber = fromPage;
+ QValueList<int>::ConstIterator pagesIt = pagesToPrint.constBegin();
+ for(;(int)pageNumber == fromPage || !engine.eof(); ++pageNumber) {
+ kdDebug() << "printing..." << endl;
+ if (pagesIt == pagesToPrint.constEnd()) //no more pages to print
+ break;
+ if ((int)pageNumber < *pagesIt) { //skip pages without printing (needed for computation)
+ engine.paintPage(pageNumber, painter, false);
+ continue;
+ }
+ if (*pagesIt < (int)pageNumber) { //sanity
+ ++pagesIt;
+ continue;
+ }
+ for (uint onePageCounter = 0; onePageCounter < loopsPerPage; onePageCounter++) {
+ if (!firstPage)
+ printer.newPage();
+ else
+ firstPage = false;
+ kdDebug() << "page #" << pageNumber << endl;
+ engine.paintPage(pageNumber, painter);
+ }
+ ++pagesIt;
+ }
+ }
+ kdDebug() << "end of printing." << endl;
+
+ // stop painting, this will automatically send the print data to the printer
+ if (!painter.end())
+ return false;
+
+ if (!engine.done())
+ return false;
+
+ return true;
+}
+
+bool KexiSimplePrintingCommand::showPrintPreview(const KexiSimplePrintingSettings& settings,
+ const QString& aTitleText, bool reload)
+{
+ m_settings = settings;
+ if (!m_previewEngine)
+ m_previewEngine = new KexiSimplePrintingEngine(m_settings, this);
+
+ if (reload)
+ m_printPreviewNeedsReloading = true;
+
+ bool backToPage0 = true;
+ QString titleText(aTitleText.stripWhiteSpace());
+ KexiDB::Connection *conn = m_mainWin->project()->dbConnection();
+ KexiDB::TableOrQuerySchema tableOrQuery(conn, m_objectId);
+ if (!tableOrQuery.table() && !tableOrQuery.query()) {
+//! @todo item not found
+ return false;
+ }
+ if (titleText.isEmpty())
+ titleText = tableOrQuery.captionOrName();
+ if (!m_previewWindow || m_printPreviewNeedsReloading) {
+ QString errorMessage;
+ if (!m_previewEngine->init(
+ *conn, tableOrQuery, titleText, errorMessage)) {
+ if (!errorMessage.isEmpty())
+ KMessageBox::sorry(m_mainWin, errorMessage, i18n("Print Preview"));
+ return false;
+ }
+ }
+ if (!m_previewWindow) {
+ backToPage0 = false;
+ m_previewWindow = new KexiSimplePrintPreviewWindow(
+ *m_previewEngine, tableOrQuery.captionOrName(), 0,
+ Qt::WStyle_Customize|Qt::WStyle_NormalBorder|Qt::WStyle_Title|
+ Qt::WStyle_SysMenu|Qt::WStyle_MinMax|Qt::WStyle_ContextHelp);
+ connect(m_previewWindow, SIGNAL(printRequested()), this, SLOT(print()));
+ connect(m_previewWindow, SIGNAL(pageSetupRequested()), this, SLOT(slotShowPageSetupRequested()));
+ m_previewWindow->show();
+ KDialog::centerOnScreen(m_previewWindow);
+ m_printPreviewNeedsReloading = false;
+ }
+
+ if (m_printPreviewNeedsReloading) {//dirty
+ m_previewEngine->clear();
+//! @todo progress bar...
+ m_previewEngine->setTitleText( titleText );
+ m_previewWindow->setFullWidth();
+ m_previewWindow->updatePagesCount();
+ m_printPreviewNeedsReloading = false;
+ }
+ if (backToPage0)
+ m_previewWindow->goToPage(0);
+ m_previewWindow->show();
+ m_previewWindow->raise();
+// m_previewWindow->setPagesCount(INT_MAX); //will be properly set on demand
+ return true;
+}
+
+void KexiSimplePrintingCommand::slotShowPageSetupRequested()
+{
+ m_mainWin->raise();
+ emit showPageSetupRequested( m_mainWin->project()->item( m_objectId ) );
+}
+
+/*void KexiSimplePrintingCommand::setPrintPreviewNeedsReloading()
+{
+ m_printPreviewNeedsReloading = true;
+}*/
+
+//----------------------------------------------------------
+
+KexiSimplePrintingPageSetup::KexiSimplePrintingPageSetup( KexiMainWindow *mainWin, QWidget *parent,
+ QMap<QString,QString>* args )
+ : KexiViewBase( mainWin, parent, "KexiSimplePrintingPageSetup" )
+ , m_settings( KexiSimplePrintingSettings::load() )
+// , m_command(0)
+{
+ // object to print
+ bool ok = args;
+ int objectId;
+ if (ok)
+ objectId = (*args)["identifier"].toInt();
+ ok = objectId<=0;
+ m_item = mainWin->project()->item( objectId );
+ ok = m_item;
+
+ bool printPreview = false;
+ bool print = false;
+ bool pageSetup = false;
+ if (ok) {
+ printPreview = (*args)["action"]=="printPreview";
+ print = (*args)["action"]=="print";
+ pageSetup = (*args)["action"]=="pageSetup";
+ ok = printPreview || print || pageSetup;
+ }
+
+ // settings
+//! @todo default?
+ m_unit = KLocale::Metric == KGlobal::locale()->measureSystem() ? KoUnit::U_CM : KoUnit::U_INCH;
+
+ // GUI
+ QVBoxLayout *lyr = new QVBoxLayout(this);
+ m_contents = new KexiSimplePrintingPageSetupBase(this, "KexiSimplePrintingPageSetupBase");
+ lyr->addWidget(m_contents);
+
+ setViewWidget(m_contents, true);
+// setFocusPolicy(WheelFocus);
+ m_contents->setFocusProxy(m_contents->headerTitleLineEdit);
+
+ m_contents->printButton->setIconSet( KStdGuiItem::print().iconSet(KIcon::Small) );
+ m_contents->printButton->setText( KStdGuiItem::print().text() );
+ connect(m_contents->printButton, SIGNAL(clicked()), this, SLOT(print()));
+
+ m_contents->printPreviewButton->setIconSet( SmallIconSet("filequickprint") );
+ m_contents->printPreviewButton->setText( i18n("Print Previe&w...") );
+ connect(m_contents->printPreviewButton, SIGNAL(clicked()), this, SLOT(printPreview()));
+
+ m_contents->iconLabel->setFixedWidth(32+6);
+ m_contents->iconLabel->setPixmap( DesktopIcon("document", 32) );
+ QWhatsThis::add(m_contents->headerTitleFontButton, i18n("Changes font for title text."));
+ connect(m_contents->headerTitleFontButton, SIGNAL(clicked()),
+ this, SLOT(slotChangeTitleFont()));
+
+ if (m_item) {
+ m_origCaptionLabelText = m_contents->captionLabel->text();
+ m_contents->headerTitleLineEdit->setText( m_item->captionOrName() );
+ if (m_item->mimeType()=="kexi/query") {
+ m_contents->openDataLink->setText( i18n("Open This Query") );
+ m_origCaptionLabelText = i18n("<h2>Page setup for printing \"%1\" query data</h2>");
+ }
+ m_contents->captionLabel->setText( m_origCaptionLabelText.arg(m_item->name()) );
+ }
+ connect(m_contents->headerTitleLineEdit,SIGNAL(textChanged(const QString&)),
+ this, SLOT(slotTitleTextChanged(const QString&)));
+ m_contents->headerTitleLineEdit->setFont( m_settings.pageTitleFont );
+
+ QWhatsThis::add(m_contents->openDataLink,
+ i18n("Shows data for table or query associated with this page setup."));
+ QToolTip::add(m_contents->openDataLink,
+ i18n("Shows data for table or query associated with this page setup."));
+ connect(m_contents->openDataLink, SIGNAL(leftClickedURL()), this, SLOT(slotOpenData()));
+
+ QWhatsThis::add(m_contents->saveSetupLink, i18n("Saves settings for this setup as default."));
+ connect(m_contents->saveSetupLink, SIGNAL(leftClickedURL()), this, SLOT(slotSaveSetup()));
+#if !KDE_IS_VERSION(3,5,1) && !defined(Q_WS_WIN)
+ //a fix for problems with focusable KUrlLabel on KDElibs<=3.5.0
+ m_contents->openDataLink->setFocusPolicy(NoFocus);
+ m_contents->saveSetupLink->setFocusPolicy(NoFocus);
+#endif
+
+ QWhatsThis::add(m_contents->addDateTimeCheckbox, i18n("Adds date and time to the header."));
+ QWhatsThis::add(m_contents->addPageNumbersCheckbox, i18n("Adds page numbers to the footer."));
+ QWhatsThis::add(m_contents->addTableBordersCheckbox, i18n("Adds table borders."));
+
+#ifdef KEXI_NO_UNFINISHED
+ m_contents->addDateTimeCheckbox->hide();
+ m_contents->addPageNumbersCheckbox->hide();
+#endif
+
+ updatePageLayoutAndUnitInfo();
+ QWhatsThis::add(m_contents->changePageSizeAndMarginsButton,
+ i18n("Changes page size and margins."));
+ connect(m_contents->changePageSizeAndMarginsButton, SIGNAL(clicked()),
+ this, SLOT(slotChangePageSizeAndMargins()));
+
+ connect(m_contents->addPageNumbersCheckbox, SIGNAL(toggled(bool)),
+ this, SLOT(slotAddPageNumbersCheckboxToggled(bool)));
+ connect(m_contents->addDateTimeCheckbox, SIGNAL(toggled(bool)),
+ this, SLOT(slotAddDateTimeCheckboxToggled(bool)));
+ connect(m_contents->addTableBordersCheckbox, SIGNAL(toggled(bool)),
+ this, SLOT(slotAddTableBordersCheckboxToggled(bool)));
+
+ if (!ok) {
+ // no data!
+ setEnabled(false);
+ }
+
+ m_contents->addPageNumbersCheckbox->setChecked( m_settings.addPageNumbers );
+ m_contents->addDateTimeCheckbox->setChecked( m_settings.addDateAndTime );
+ m_contents->addTableBordersCheckbox->setChecked( m_settings.addTableBorders );
+ setDirty(false);
+
+// m_engine = new KexiSimplePrintingEngine(m_settings, this);
+
+ //clear it back to false after widgets initialization
+ m_printPreviewNeedsReloading = false;
+
+/* if (printPreview)
+ QTimer::singleShot(50, this, SLOT(printPreview()));
+ else if (print)
+ QTimer::singleShot(50, this, SLOT(print()));*/
+ connect(this, SIGNAL(printItemRequested(KexiPart::Item*,const KexiSimplePrintingSettings&,
+ const QString&)),
+ m_mainWin, SLOT(printItem(KexiPart::Item*,const KexiSimplePrintingSettings&,
+ const QString&)));
+ connect(this, SIGNAL(printPreviewForItemRequested(KexiPart::Item*,
+ const KexiSimplePrintingSettings&,const QString&,bool)),
+ m_mainWin, SLOT(printPreviewForItem(KexiPart::Item*,
+ const KexiSimplePrintingSettings&,const QString&,bool)));
+}
+
+KexiSimplePrintingPageSetup::~KexiSimplePrintingPageSetup()
+{
+}
+
+void KexiSimplePrintingPageSetup::slotSaveSetup()
+{
+ m_settings.save();
+ setDirty(false);
+}
+
+void KexiSimplePrintingPageSetup::updatePageLayoutAndUnitInfo()
+{
+ QString s;
+ if (m_settings.pageLayout.format == PG_CUSTOM) {
+ s += QString(" (%1 %2 x %3 %4)")
+ .arg(m_settings.pageLayout.ptWidth).arg(KoUnit::unitName(m_unit))
+ .arg(m_settings.pageLayout.ptHeight).arg(KoUnit::unitName(m_unit));
+ }
+ else
+ s += KoPageFormat::name(m_settings.pageLayout.format);
+ s += QString(", ")
+ + (m_settings.pageLayout.orientation == PG_PORTRAIT ? i18n("Portrait") : i18n("Landscape"))
+ + ", " + i18n("margins:")
+ + " " + KoUnit::toUserStringValue(m_settings.pageLayout.ptLeft, m_unit)
+ + "/" + KoUnit::toUserStringValue(m_settings.pageLayout.ptRight, m_unit)
+ + "/" + KoUnit::toUserStringValue(m_settings.pageLayout.ptTop, m_unit)
+ + "/" + KoUnit::toUserStringValue(m_settings.pageLayout.ptBottom, m_unit)
+ + " " + KoUnit::unitName(m_unit);
+ m_contents->pageSizeAndMarginsLabel->setText( s );
+}
+
+/*void KexiSimplePrintingPageSetup::setupPrintingCommand()
+{
+ if (!m_command) {
+ m_command = new KexiSimplePrintingCommand(
+ m_mainWin, m_item->identifier(), m_settings, false/!owned/, this);
+ }
+}*/
+
+void KexiSimplePrintingPageSetup::print()
+{
+// setupPrintingCommand();
+// m_command->print(m_contents->headerTitleLineEdit->text());
+ emit printItemRequested(m_item, m_settings, m_contents->headerTitleLineEdit->text());
+}
+
+void KexiSimplePrintingPageSetup::printPreview()
+{
+// setupPrintingCommand();
+// m_command->showPrintPreview(m_contents->headerTitleLineEdit->text());
+ emit printPreviewForItemRequested(m_item, m_settings,
+ m_contents->headerTitleLineEdit->text(), m_printPreviewNeedsReloading);
+ m_printPreviewNeedsReloading = false;
+}
+
+void KexiSimplePrintingPageSetup::slotOpenData()
+{
+ bool openingCancelled;
+ m_mainWin->openObject(m_item, Kexi::DataViewMode, openingCancelled);
+}
+
+void KexiSimplePrintingPageSetup::slotTitleTextChanged(const QString&)
+{
+ if (m_contents->headerTitleLineEdit->isModified()) {
+ m_printPreviewNeedsReloading = true;
+// if (m_command)
+// m_command->setPrintPreviewNeedsReloading();
+ }
+
+ m_contents->headerTitleLineEdit->clearModified();
+}
+
+void KexiSimplePrintingPageSetup::slotChangeTitleFont()
+{
+ if (QDialog::Accepted != KFontDialog::getFont(m_settings.pageTitleFont, false, this))
+ return;
+ m_contents->headerTitleLineEdit->setFont( m_settings.pageTitleFont );
+ setDirty(true);
+}
+
+void KexiSimplePrintingPageSetup::slotChangePageSizeAndMargins()
+{
+ KoHeadFoot headfoot; //dummy
+
+ if (int(QDialog::Accepted) != KoPageLayoutDia::pageLayout(
+ m_settings.pageLayout, headfoot, FORMAT_AND_BORDERS | DISABLE_UNIT, m_unit, this ))
+ return;
+
+ //update
+ updatePageLayoutAndUnitInfo();
+ setDirty(true);
+}
+
+void KexiSimplePrintingPageSetup::setDirty(bool set)
+{
+ m_contents->saveSetupLink->setEnabled(set);
+// if (m_command)
+// m_command->setPrintPreviewNeedsReloading();
+ if (set)
+ m_printPreviewNeedsReloading = true;
+}
+
+void KexiSimplePrintingPageSetup::slotAddPageNumbersCheckboxToggled(bool set)
+{
+ m_settings.addPageNumbers = set;
+ setDirty(true);
+}
+
+void KexiSimplePrintingPageSetup::slotAddDateTimeCheckboxToggled(bool set)
+{
+ m_settings.addDateAndTime = set;
+ setDirty(true);
+}
+
+void KexiSimplePrintingPageSetup::slotAddTableBordersCheckboxToggled(bool set)
+{
+ m_settings.addTableBorders = set;
+ setDirty(true);
+}
+
+#include "kexisimpleprintingpagesetup.moc"
diff --git a/kexi/main/printing/kexisimpleprintingpagesetup.h b/kexi/main/printing/kexisimpleprintingpagesetup.h
new file mode 100644
index 000000000..e4e99bd51
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintingpagesetup.h
@@ -0,0 +1,117 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXISIMPLEPRINTINGPAGESETUP_H
+#define KEXISIMPLEPRINTINGPAGESETUP_H
+
+#include "kexisimpleprintingengine.h"
+#include <kexiviewbase.h>
+
+class KexiSimplePrintingPageSetupBase;
+class KoPageLayoutSize;
+class KexiSimplePrintPreviewWindow;
+
+/*! @short A command for simple printing and print preview.
+ This class is instantiated in KexiMainWindowImpl so there's:
+ - a single print preview window per part item regardless of a way how user invoked
+ the 'print preview' command (using 'File->Print Preview' command or 'Print Preview' button
+ of the 'Page Setup' dialog)
+ - a single printing engine per part item regardless of a way how user started
+ (using 'File->Print' command or 'Print' button of the 'Page Setup' dialog)
+*/
+class KexiSimplePrintingCommand : public QObject
+{
+ Q_OBJECT
+
+ public:
+ KexiSimplePrintingCommand(KexiMainWindow* mainWin, int objectId,
+ QObject* parent = 0);
+ ~KexiSimplePrintingCommand();
+
+ public slots:
+ bool print(const KexiSimplePrintingSettings& settings,
+ const QString& aTitleText = QString::null);
+ bool print(const QString& aTitleText = QString::null);
+ bool showPrintPreview(const KexiSimplePrintingSettings& settings,
+ const QString& aTitleText = QString::null, bool reload = false);
+// void setPrintPreviewNeedsReloading();
+
+ signals:
+ //! connected to Kexi Main Window
+ void showPageSetupRequested(KexiPart::Item* item);
+
+ protected slots:
+ void slotShowPageSetupRequested();
+
+ protected:
+ KexiSimplePrintingEngine* m_previewEngine;
+ KexiMainWindow* m_mainWin;
+ int m_objectId;
+ KexiSimplePrintingSettings m_settings;
+ KexiSimplePrintPreviewWindow *m_previewWindow;
+ bool m_printPreviewNeedsReloading : 1;
+};
+
+//! @short A window for displaying settings for simple printing.
+class KexiSimplePrintingPageSetup : public KexiViewBase
+{
+ Q_OBJECT
+
+ public:
+ KexiSimplePrintingPageSetup( KexiMainWindow *mainWin, QWidget *parent, QMap<QString,QString>* args );
+ ~KexiSimplePrintingPageSetup();
+
+ public slots:
+ void print();
+ void printPreview();
+
+ signals:
+ void printItemRequested(KexiPart::Item* item,
+ const KexiSimplePrintingSettings& settings, const QString& titleText);
+ void printPreviewForItemRequested(KexiPart::Item* item,
+ const KexiSimplePrintingSettings& settings, const QString& titleText, bool reload);
+
+ protected slots:
+ void slotOpenData();
+ void slotSaveSetup();
+ void slotChangeTitleFont();
+ void slotChangePageSizeAndMargins();
+ void slotAddPageNumbersCheckboxToggled(bool set);
+ void slotAddDateTimeCheckboxToggled(bool set);
+ void slotAddTableBordersCheckboxToggled(bool set);
+ void slotTitleTextChanged(const QString&);
+
+ protected:
+ void setupPrintingCommand();
+ void updatePageLayoutAndUnitInfo();
+ void setDirty(bool set);
+
+ KexiSimplePrintingSettings m_settings;
+// KexiSimplePrintingEngine *m_engine;
+ KoUnit::Unit m_unit;
+ KexiSimplePrintingPageSetupBase *m_contents;
+ KoPageLayoutSize *m_pageLayoutWidget;
+ KexiPart::Item *m_item;
+// KexiSimplePrintingCommand *m_command;
+ QString m_origCaptionLabelText;
+ bool m_printPreviewNeedsReloading : 1;
+
+};
+
+#endif
diff --git a/kexi/main/printing/kexisimpleprintingpagesetupbase.ui b/kexi/main/printing/kexisimpleprintingpagesetupbase.ui
new file mode 100644
index 000000000..13c29ab0d
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintingpagesetupbase.ui
@@ -0,0 +1,447 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiSimplePrintingPageSetupBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiSimplePrintingPageSetupBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>338</width>
+ <height>451</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>.</string>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLayoutWidget" row="0" column="0" rowspan="1" colspan="6">
+ <property name="name">
+ <cstring>layout15</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>iconLabel</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter</set>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>captionLabel</cstring>
+ </property>
+ <property name="text">
+ <string>&lt;h2&gt;Page Setup for Printing "%1" Table Data&lt;/h2&gt;</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>headerTitleLineEdit</cstring>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="KLineEdit" row="5" column="0" rowspan="1" colspan="5">
+ <property name="name">
+ <cstring>headerTitleLineEdit</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>2</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ <widget class="KPushButton" row="5" column="5">
+ <property name="name">
+ <cstring>headerTitleFontButton</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Set Font...</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="4" column="0" rowspan="1" colspan="5">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Page title:</string>
+ </property>
+ <property name="alignment">
+ <set>AlignBottom</set>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>headerTitleLineEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget" row="2" column="0" rowspan="1" colspan="6">
+ <property name="name">
+ <cstring>layout19</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>printButton</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>printPreviewButton</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>230</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </hbox>
+ </widget>
+ <widget class="QCheckBox" row="8" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>addPageNumbersCheckbox</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Add page numbers</string>
+ </property>
+ </widget>
+ <widget class="QGroupBox" row="7" column="0" rowspan="1" colspan="5">
+ <property name="name">
+ <cstring>groupBox1</cstring>
+ </property>
+ <property name="title">
+ <string>Page Size &amp;&amp; Margins</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>pageSizeAndMarginsLabel</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="KPushButton" row="7" column="5">
+ <property name="name">
+ <cstring>changePageSizeAndMarginsButton</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Change...</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget" row="12" column="0" rowspan="1" colspan="6">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer row="0" column="1">
+ <property name="name">
+ <cstring>spacer23</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>80</width>
+ <height>6</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KURLLabel" row="2" column="0">
+ <property name="name">
+ <cstring>saveSetupLink</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="margin">
+ <number>3</number>
+ </property>
+ <property name="text">
+ <string>Save This Setup as Default</string>
+ </property>
+ <property name="indent">
+ <number>6</number>
+ </property>
+ <property name="url" stdset="0">
+ <string></string>
+ </property>
+ <property name="tipText">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="KURLLabel" row="1" column="0">
+ <property name="name">
+ <cstring>openDataLink</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="margin">
+ <number>3</number>
+ </property>
+ <property name="text">
+ <string>Open This Table</string>
+ </property>
+ <property name="indent">
+ <number>6</number>
+ </property>
+ <property name="url" stdset="0">
+ <string></string>
+ </property>
+ <property name="tipText">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel6</cstring>
+ </property>
+ <property name="text">
+ <string>Related actions:</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QCheckBox" row="9" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>addDateTimeCheckbox</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Add date and time</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="10" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>addTableBordersCheckbox</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Add table borders</string>
+ </property>
+ </widget>
+ <spacer row="13" column="4">
+ <property name="name">
+ <cstring>spacer9</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ <spacer row="1" column="3">
+ <property name="name">
+ <cstring>spacer15</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>4</height>
+ </size>
+ </property>
+ </spacer>
+ <spacer row="6" column="0">
+ <property name="name">
+ <cstring>spacer10</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>4</height>
+ </size>
+ </property>
+ </spacer>
+ <spacer row="3" column="1">
+ <property name="name">
+ <cstring>spacer12</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>4</height>
+ </size>
+ </property>
+ </spacer>
+ <spacer row="11" column="2">
+ <property name="name">
+ <cstring>spacer14</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>4</height>
+ </size>
+ </property>
+ </spacer>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<tabstops>
+ <tabstop>printButton</tabstop>
+ <tabstop>printPreviewButton</tabstop>
+ <tabstop>headerTitleLineEdit</tabstop>
+ <tabstop>headerTitleFontButton</tabstop>
+ <tabstop>changePageSizeAndMarginsButton</tabstop>
+ <tabstop>addPageNumbersCheckbox</tabstop>
+ <tabstop>addDateTimeCheckbox</tabstop>
+ <tabstop>addTableBordersCheckbox</tabstop>
+ <tabstop>openDataLink</tabstop>
+ <tabstop>saveSetupLink</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klineedit.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kurllabel.h</includehint>
+ <includehint>kurllabel.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/main/printing/kexisimpleprintingpart.cpp b/kexi/main/printing/kexisimpleprintingpart.cpp
new file mode 100644
index 000000000..765f6db26
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintingpart.cpp
@@ -0,0 +1,63 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexisimpleprintingpart.h"
+#include "kexisimpleprintingpagesetup.h"
+
+#include <kdebug.h>
+#include <kgenericfactory.h>
+
+#include <keximainwindow.h>
+#include <kexidialogbase.h>
+#include <kexiproject.h>
+#include <kexipartinfo.h>
+
+KexiSimplePrintingPart::KexiSimplePrintingPart()
+ : KexiPart::StaticPart("kexi/simpleprinting", "fileprint", i18n("Printing"))
+{
+ // REGISTERED ID:
+//?? m_registeredPartID = (int)KexiPart::QueryObjectType;
+
+/* m_names["instanceName"]
+ = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). "
+ "Use '_' character instead of spaces. First character should be a..z character. "
+ "If you cannot use latin characters in your language, use english word.",
+ "query");*/
+ m_names["instanceCaption"] = i18n("Printing");
+ m_supportedViewModes = Kexi::DesignViewMode;
+ m_supportedUserViewModes = Kexi::DesignViewMode;
+}
+
+KexiSimplePrintingPart::~KexiSimplePrintingPart()
+{
+}
+
+KexiViewBase* KexiSimplePrintingPart::createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode, QMap<QString,QString>* args)
+{
+ Q_UNUSED( item );
+ if (viewMode == Kexi::DesignViewMode) {
+ KexiSimplePrintingPageSetup *w = new KexiSimplePrintingPageSetup( dialog->mainWin(), parent, args );
+ return w;
+ }
+
+ return 0;
+}
+
+#include "kexisimpleprintingpart.moc"
diff --git a/kexi/main/printing/kexisimpleprintingpart.h b/kexi/main/printing/kexisimpleprintingpart.h
new file mode 100644
index 000000000..1d2df3be2
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintingpart.h
@@ -0,0 +1,51 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISIMPLEPRINTINGPART_H
+#define KEXISIMPLEPRINTINGPART_H
+
+#include <kexidialogbase.h>
+#include <kexistaticpart.h>
+#include <kexipartitem.h>
+
+class KexiMainWin;
+namespace KexiDB
+{
+ class QuerySchema;
+ class Connection;
+}
+
+class KexiProject;
+
+//! @short Internal Kexi Simple Printing Plugin.
+class KexiSimplePrintingPart : public KexiPart::StaticPart
+{
+ Q_OBJECT
+
+ public:
+ KexiSimplePrintingPart();
+ virtual ~KexiSimplePrintingPart();
+
+ protected:
+ virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode, QMap<QString,QString>* staticObjectArgs);
+};
+
+#endif
+
diff --git a/kexi/main/printing/kexisimpleprintpreviewwindow.cpp b/kexi/main/printing/kexisimpleprintpreviewwindow.cpp
new file mode 100644
index 000000000..4ba487d9a
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintpreviewwindow.cpp
@@ -0,0 +1,381 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexisimpleprintpreviewwindow.h"
+#include "kexisimpleprintingengine.h"
+#include "kexisimpleprintpreviewwindow_p.h"
+#include <kexi_version.h>
+
+#include <qlayout.h>
+#include <qaccel.h>
+#include <qtimer.h>
+#include <qlabel.h>
+
+#include <kdialogbase.h>
+#include <ktoolbarbutton.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <kpushbutton.h>
+#include <kapplication.h>
+
+KexiSimplePrintPreviewView::KexiSimplePrintPreviewView(
+ QWidget *parent, KexiSimplePrintPreviewWindow *window)
+ : QWidget(parent, "KexiSimplePrintPreviewView", WStaticContents)//|WNoAutoErase)
+ , m_window(window)
+{
+ enablePainting = false;
+// resize(300,400);
+// resizeContents(200, 400);
+}
+
+void KexiSimplePrintPreviewView::paintEvent( QPaintEvent *pe )
+{
+ Q_UNUSED(pe);
+ if (!enablePainting)
+ return;
+ QPixmap pm(size()); //dbl buffered
+ QPainter p;
+ p.begin(&pm, this);
+//! @todo only for screen!
+ p.fillRect(QRect(QPoint(0,0),pm.size()), QBrush(white));//pe->rect(), QBrush(white));
+ if (m_window->currentPage()>=0)
+ m_window->m_engine.paintPage(m_window->currentPage(), p);
+// emit m_window->paintingPageRequested(m_window->currentPage(), p);
+ p.end();
+ bitBlt(this, 0, 0, &pm);
+}
+
+//--------------------------
+
+#define KexiSimplePrintPreviewScrollView_MARGIN KDialogBase::marginHint()
+
+KexiSimplePrintPreviewScrollView::KexiSimplePrintPreviewScrollView(
+ KexiSimplePrintPreviewWindow *window)
+ : QScrollView(window, "scrollview", WStaticContents|WNoAutoErase)
+ , m_window(window)
+{
+// this->settings = settings;
+ widget = new KexiSimplePrintPreviewView(viewport(), m_window);
+
+/* int widthMM = KoPageFormat::width(
+ settings.pageLayout.format, settings.pageLayout.orientation);
+ int heightMM = KoPageFormat::height(
+ settings.pageLayout.format, settings.pageLayout.orientation);
+// int constantHeight = 400;
+// widget->resize(constantHeight * widthMM / heightMM, constantHeight ); //keep aspect
+*/
+ addChild(widget);
+}
+
+void KexiSimplePrintPreviewScrollView::resizeEvent( QResizeEvent *re )
+{
+ QScrollView::resizeEvent(re);
+// kdDebug() << re->size().width() << " " << re->size().height() << endl;
+// kdDebug() << contentsWidth() << " " << contentsHeight() << endl;
+// kdDebug() << widget->width() << " " << widget->height() << endl;
+ setUpdatesEnabled(false);
+ if (re->size().width() > (widget->width()+2*KexiSimplePrintPreviewScrollView_MARGIN)
+ || re->size().height() > (widget->height()+2*KexiSimplePrintPreviewScrollView_MARGIN))
+ {
+ resizeContents(
+ QMAX(re->size().width(), widget->width()+2*KexiSimplePrintPreviewScrollView_MARGIN),
+ QMAX(re->size().height(), widget->height()+2*KexiSimplePrintPreviewScrollView_MARGIN));
+ int vscrbarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0;
+ int newContentsWidth
+ = QMAX(re->size().width(), widget->width()+2*KexiSimplePrintPreviewScrollView_MARGIN);
+ int newContentsHeight
+ = QMAX(re->size().height(), widget->height()+2*KexiSimplePrintPreviewScrollView_MARGIN);
+ moveChild(widget, (newContentsWidth - vscrbarWidth - widget->width())/2,
+ (newContentsHeight - widget->height())/2);
+ resizeContents( newContentsWidth, newContentsHeight );
+ }
+ setUpdatesEnabled(true);
+}
+
+void KexiSimplePrintPreviewScrollView::setFullWidth()
+{
+ viewport()->setUpdatesEnabled(false);
+ double widthMM = KoPageFormat::width(
+ m_window->settings().pageLayout.format,
+ m_window->settings().pageLayout.orientation);
+ double heightMM = KoPageFormat::height(
+ m_window->settings().pageLayout.format, m_window->settings().pageLayout.orientation);
+// int constantWidth = m_window->width()- KexiSimplePrintPreviewScrollView_MARGIN*6;
+ double constantWidth = width()- KexiSimplePrintPreviewScrollView_MARGIN*6;
+ double heightForWidth = constantWidth * heightMM / widthMM;
+// heightForWidth = QMIN(kapp->desktop()->height()*4/5, heightForWidth);
+ kdDebug() << "1: " << heightForWidth << endl;
+#if 0 //todo we can use this if we want to fix the height to width of the page
+ heightForWidth = QMIN(height(), heightForWidth);
+ kdDebug() << "2: " << heightForWidth << endl;
+#endif
+ constantWidth = heightForWidth * widthMM / heightMM;
+ widget->resize((int)constantWidth, (int)heightForWidth); //keep aspect
+ resizeContents(int(widget->width() + 2*KexiSimplePrintPreviewScrollView_MARGIN),
+ int(widget->height() + 2*KexiSimplePrintPreviewScrollView_MARGIN));
+ moveChild(widget, (contentsWidth()-widget->width())/2,
+ (contentsHeight()-widget->height())/2);
+ viewport()->setUpdatesEnabled(true);
+ resize(size()+QSize(1,1)); //to update pos.
+ widget->enablePainting = true;
+ widget->repaint();
+}
+
+void KexiSimplePrintPreviewScrollView::setContentsPos(int x, int y)
+{
+// kdDebug() << "############" << x << " " << y << " " << contentsX()<< " " <<contentsY() << endl;
+ if (x<0 || y<0) //to avoid endless loop on Linux
+ return;
+ QScrollView::setContentsPos(x,y);
+}
+
+//------------------
+
+KexiSimplePrintPreviewWindow::KexiSimplePrintPreviewWindow(
+ KexiSimplePrintingEngine &engine, const QString& previewName,
+ QWidget *parent, WFlags f)
+ : QWidget(parent, "KexiSimplePrintPreviewWindow", f)
+ , m_engine(engine)
+ , m_settings(*m_engine.settings())
+ , m_pageNumber(-1)
+ , m_pagesCount(-1)
+{
+ setCaption(i18n("%1 - Print Preview - %2").arg(previewName).arg(KEXI_APP_NAME));
+ setIcon(DesktopIcon("filequickprint"));
+ QVBoxLayout *lyr = new QVBoxLayout(this, 6);
+
+ int id;
+ m_toolbar = new KToolBar(0, this);
+ m_toolbar->setLineWidth(0);
+ m_toolbar->setFrameStyle(QFrame::NoFrame);
+ m_toolbar->setIconText(KToolBar::IconTextRight);
+ lyr->addWidget(m_toolbar);
+
+ id = m_toolbar->insertWidget( -1, 0, new KPushButton(KStdGuiItem::print(), m_toolbar) );
+ m_toolbar->addConnection(id, SIGNAL(clicked()), this, SLOT(slotPrintClicked()));
+ static_cast<KPushButton*>(m_toolbar->getWidget(id))->setAccel(Qt::CTRL|Qt::Key_P);
+ m_toolbar->insertSeparator();
+
+ id = m_toolbar->insertWidget(-1, 0, new KPushButton(i18n("Page Set&up..."), m_toolbar));
+ m_toolbar->addConnection(id, SIGNAL(clicked()), this, SLOT(slotPageSetup()));
+ m_toolbar->insertSeparator();
+
+
+#ifndef KEXI_NO_UNFINISHED
+//! @todo unfinished
+ id = m_toolbar->insertWidget( -1, 0, new KPushButton(BarIconSet("viewmag+"), i18n("Zoom In"), m_toolbar));
+ m_toolbar->addConnection(id, SIGNAL(clicked()), this, SLOT(slotZoomInClicked()));
+ m_toolbar->insertSeparator();
+
+ id = m_toolbar->insertWidget( -1, 0, new KPushButton(BarIconSet("viewmag-"), i18n("Zoom Out"), m_toolbar));
+ m_toolbar->addConnection(id, SIGNAL(clicked()), this, SLOT(slotZoomOutClicked()));
+ m_toolbar->insertSeparator();
+#endif
+
+ id = m_toolbar->insertWidget(-1, 0, new KPushButton(KStdGuiItem::close(), m_toolbar));
+ m_toolbar->addConnection(id, SIGNAL(clicked()), this, SLOT(close()));
+ m_toolbar->alignItemRight(id);
+
+ m_scrollView = new KexiSimplePrintPreviewScrollView(this);
+ m_scrollView->setUpdatesEnabled(false);
+ m_view = m_scrollView->widget;
+ m_scrollView->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ lyr->addWidget(m_scrollView);
+
+ QWidget* navToolbarWidget = new QWidget(this); //widget used to center the navigator toolbar
+ navToolbarWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ QHBoxLayout *navToolbarLyr = new QHBoxLayout(navToolbarWidget);
+ lyr->addWidget(navToolbarWidget);
+
+ m_navToolbar = new KToolBar(0, navToolbarWidget);
+ navToolbarLyr->addStretch(1);
+ navToolbarLyr->addWidget(m_navToolbar);
+ navToolbarLyr->addStretch(1);
+// m_navToolbar->setFullWidth(true);
+ m_navToolbar->setLineWidth(0);
+ m_navToolbar->setFrameStyle(QFrame::NoFrame);
+ m_navToolbar->setIconText(KToolBar::IconTextRight);
+
+ m_idFirst = m_navToolbar->insertWidget( -1, 0, new KPushButton(BarIconSet("start"), i18n("First Page"), m_navToolbar));
+ m_navToolbar->addConnection(m_idFirst, SIGNAL(clicked()), this, SLOT(slotFirstClicked()));
+ m_navToolbar->insertSeparator();
+
+ m_idPrevious = m_navToolbar->insertWidget( -1, 0, new KPushButton(BarIconSet("previous"), i18n("Previous Page"), m_navToolbar));
+ m_navToolbar->addConnection(m_idPrevious, SIGNAL(clicked()), this, SLOT(slotPreviousClicked()));
+ m_navToolbar->insertSeparator();
+
+ m_idPageNumberLabel = m_navToolbar->insertWidget( -1, 0, new QLabel(m_navToolbar));
+ m_navToolbar->insertSeparator();
+
+ m_idNext = m_navToolbar->insertWidget( -1, 0, new KPushButton(BarIconSet("next"), i18n("Next Page"), m_navToolbar));
+ m_navToolbar->addConnection(m_idNext, SIGNAL(clicked()), this, SLOT(slotNextClicked()));
+ m_navToolbar->insertSeparator();
+
+ m_idLast = m_navToolbar->insertWidget( -1, 0, new KPushButton(BarIconSet("finish"), i18n("Last Page"), m_navToolbar));
+ m_navToolbar->addConnection(m_idLast, SIGNAL(clicked()), this, SLOT(slotLastClicked()));
+ m_navToolbar->insertSeparator();
+
+ resize(width(), kapp->desktop()->height()*4/5);
+
+//! @todo progress bar...
+
+ QTimer::singleShot(50, this, SLOT(initLater()));
+}
+
+void KexiSimplePrintPreviewWindow::initLater()
+{
+ setFullWidth();
+ updatePagesCount();
+ goToPage(0);
+}
+
+KexiSimplePrintPreviewWindow::~KexiSimplePrintPreviewWindow()
+{
+}
+
+/*void KexiSimplePrintPreviewWindow::setPagesCount(int pagesCount)
+{
+ m_pagesCount = pagesCount;
+ goToPage(0);
+}*/
+
+void KexiSimplePrintPreviewWindow::slotPrintClicked()
+{
+ hide();
+ emit printRequested();
+ show();
+ raise();
+}
+
+void KexiSimplePrintPreviewWindow::slotPageSetup()
+{
+ lower();
+ emit pageSetupRequested();
+}
+
+void KexiSimplePrintPreviewWindow::slotZoomInClicked()
+{
+ //! @todo
+}
+
+void KexiSimplePrintPreviewWindow::slotZoomOutClicked()
+{
+ //! @todo
+}
+
+void KexiSimplePrintPreviewWindow::slotFirstClicked()
+{
+ goToPage(0);
+}
+
+void KexiSimplePrintPreviewWindow::slotPreviousClicked()
+{
+ goToPage(m_pageNumber-1);
+}
+
+void KexiSimplePrintPreviewWindow::slotNextClicked()
+{
+ goToPage(m_pageNumber+1);
+}
+
+void KexiSimplePrintPreviewWindow::slotLastClicked()
+{
+ goToPage(m_engine.pagesCount()-1);
+}
+
+void KexiSimplePrintPreviewWindow::goToPage(int pageNumber)
+{
+ if ((pageNumber==m_pageNumber && m_pagesCount == (int)m_engine.pagesCount())
+ || pageNumber < 0 || pageNumber > ((int)m_engine.pagesCount()-1))
+ return;
+ m_pageNumber = pageNumber;
+ m_pagesCount = m_engine.pagesCount();
+
+ m_view->repaint(); //this will automatically paint a new page
+
+ m_navToolbar->setItemEnabled(m_idNext, pageNumber < ((int)m_engine.pagesCount()-1));
+ m_navToolbar->setItemEnabled(m_idLast, pageNumber < ((int)m_engine.pagesCount()-1));
+ m_navToolbar->setItemEnabled(m_idPrevious, pageNumber > 0);
+ m_navToolbar->setItemEnabled(m_idFirst, pageNumber > 0);
+ static_cast<QLabel*>(m_navToolbar->getWidget(m_idPageNumberLabel))->setText(
+ i18n("Page (number) of (total)", "Page %1 of %2").arg(m_pageNumber+1).arg(m_engine.pagesCount()));
+}
+
+void KexiSimplePrintPreviewWindow::setFullWidth()
+{
+ m_scrollView->setFullWidth();
+}
+
+void KexiSimplePrintPreviewWindow::updatePagesCount()
+{
+ QPixmap pm(m_view->size()); //dbl buffered
+ QPainter p(m_view);
+ //p.begin(&pm, this);
+////! @todo only for screen!
+// p.fillRect(pe->rect(), QBrush(white));
+ m_engine.calculatePagesCount(p);
+ p.end();
+}
+
+bool KexiSimplePrintPreviewWindow::event( QEvent * e )
+{
+ QEvent::Type t = e->type();
+ if (t==QEvent::KeyPress) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ const int k = ke->key();
+ bool ok = true;
+ if (k==Qt::Key_Equal || k==Qt::Key_Plus)
+ slotZoomInClicked();
+ else if (k==Qt::Key_Minus)
+ slotZoomOutClicked();
+ else if (k==Qt::Key_Home)
+ slotFirstClicked();
+ else if (k==Qt::Key_End)
+ slotLastClicked();
+ else
+ ok = false;
+
+ if (ok) {
+ ke->accept();
+ return true;
+ }
+ }
+ else if (t==QEvent::AccelOverride) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ const int k = ke->key();
+ bool ok = true;
+ if (k==Qt::Key_PageUp)
+ slotPreviousClicked();
+ else if (k==Qt::Key_PageDown)
+ slotNextClicked();
+ else
+ ok = false;
+
+ if (ok) {
+ ke->accept();
+ return true;
+ }
+ }
+ return QWidget::event(e);
+}
+
+
+#include "kexisimpleprintpreviewwindow.moc"
+#include "kexisimpleprintpreviewwindow_p.moc"
diff --git a/kexi/main/printing/kexisimpleprintpreviewwindow.h b/kexi/main/printing/kexisimpleprintpreviewwindow.h
new file mode 100644
index 000000000..c91fa2f26
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintpreviewwindow.h
@@ -0,0 +1,83 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISIMPLEPRINTPREVIEWWINDOW_H
+#define KEXISIMPLEPRINTPREVIEWWINDOW_H
+
+#include <qpainter.h>
+#include <qscrollview.h>
+#include <ktoolbar.h>
+#include <KoPageLayoutDia.h>
+
+class KexiSimplePrintPreviewScrollView;
+class KexiSimplePrintPreviewView;
+class KexiSimplePrintingSettings;
+class KexiSimplePrintingEngine;
+
+//! @short A window for displaying print preview for simple printing.
+class KexiSimplePrintPreviewWindow : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiSimplePrintPreviewWindow(KexiSimplePrintingEngine &engine,
+ const QString& previewName, QWidget *parent, WFlags f = 0);
+ ~KexiSimplePrintPreviewWindow();
+
+ int currentPage() const { return m_pageNumber; }
+
+ const KexiSimplePrintingSettings& settings() const { return m_settings; }
+
+ public slots:
+ void updatePagesCount();
+// void setPagesCount(int pagesCount);
+ void goToPage(int pageNumber);
+ void setFullWidth();
+
+ signals:
+ void printRequested();
+ void pageSetupRequested();
+
+ protected slots:
+ void slotPageSetup();
+ void slotPrintClicked();
+ void slotZoomInClicked();
+ void slotZoomOutClicked();
+ void slotFirstClicked();
+ void slotPreviousClicked();
+ void slotNextClicked();
+ void slotLastClicked();
+ void initLater();
+
+ protected:
+ virtual bool event( QEvent * e );
+
+ KexiSimplePrintingEngine &m_engine;
+ const KexiSimplePrintingSettings& m_settings;
+ KToolBar *m_toolbar, *m_navToolbar;
+ int m_pageNumber;
+ int m_pagesCount; //!< needed to know that pages could has been changed
+ int m_idFirst, m_idLast, m_idPrevious, m_idNext, m_idPageNumberLabel;
+ KexiSimplePrintPreviewScrollView *m_scrollView;
+ KexiSimplePrintPreviewView *m_view;
+
+ friend class KexiSimplePrintPreviewView;
+};
+
+#endif
diff --git a/kexi/main/printing/kexisimpleprintpreviewwindow_p.h b/kexi/main/printing/kexisimpleprintpreviewwindow_p.h
new file mode 100644
index 000000000..e7a3804d5
--- /dev/null
+++ b/kexi/main/printing/kexisimpleprintpreviewwindow_p.h
@@ -0,0 +1,55 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISIMPLEPRINTPREVIEWWINDOW_P_H
+#define KEXISIMPLEPRINTPREVIEWWINDOW_P_H
+
+#include <kexisimpleprintpreviewwindow.h>
+
+class KexiSimplePrintPreviewView : public QWidget
+{
+ public:
+ KexiSimplePrintPreviewView(QWidget *parent, KexiSimplePrintPreviewWindow *window);
+
+ virtual void paintEvent( QPaintEvent *pe );
+
+ bool enablePainting;
+ protected:
+ KexiSimplePrintPreviewWindow *m_window;
+};
+
+class KexiSimplePrintPreviewScrollView : public QScrollView
+{
+ Q_OBJECT
+
+ public:
+ KexiSimplePrintPreviewScrollView(KexiSimplePrintPreviewWindow *window);
+
+ KexiSimplePrintPreviewView *widget;
+
+ public slots:
+ void setFullWidth();
+ void setContentsPos(int x, int y);
+
+ protected:
+ virtual void resizeEvent( QResizeEvent *re );
+ KexiSimplePrintPreviewWindow *m_window;
+};
+
+#endif
diff --git a/kexi/main/startup/KexiConnSelector.cpp b/kexi/main/startup/KexiConnSelector.cpp
new file mode 100644
index 000000000..454f1e085
--- /dev/null
+++ b/kexi/main/startup/KexiConnSelector.cpp
@@ -0,0 +1,432 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003,2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "KexiConnSelector.h"
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/connectiondata.h>
+
+#include <kexi.h>
+#include "KexiConnSelectorBase.h"
+//#include "KexiOpenExistingFile.h"
+#include <widget/kexiprjtypeselector.h>
+#include <widget/kexidbconnectionwidget.h>
+
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kmimetype.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kurlcombobox.h>
+#include <ktoolbar.h>
+#include <kpopupmenu.h>
+#include <ktoolbarbutton.h>
+#include <kactionclasses.h>
+
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qtooltip.h>
+#include <qtextedit.h>
+#include <qgroupbox.h>
+#include <qwidgetstack.h>
+#include <qbuttongroup.h>
+
+ConnectionDataLVItem::ConnectionDataLVItem(KexiDB::ConnectionData *data,
+ const KexiDB::Driver::Info& info, QListView *list)
+ : QListViewItem(list)
+ , m_data(data)
+{
+ update(info);
+}
+
+ConnectionDataLVItem::~ConnectionDataLVItem()
+{
+}
+
+void ConnectionDataLVItem::update(const KexiDB::Driver::Info& info)
+{
+ setText(0, m_data->caption+" ");
+ const QString &sfile = i18n("File");
+ QString drvname = info.caption.isEmpty() ? m_data->driverName : info.caption;
+ if (info.fileBased)
+ setText(1, sfile + " ("+drvname+") " );
+ else
+ setText(1, drvname+" " );
+ setText(2, (info.fileBased ? (QString("<")+sfile.lower()+">") : m_data->serverInfoString(true))+" " );
+}
+
+/*================================================================*/
+
+//! @internal
+class KexiConnSelectorWidgetPrivate
+{
+public:
+ KexiConnSelectorWidgetPrivate()
+ : conn_sel_shown(false)
+ , file_sel_shown(false)
+ , confirmOverwrites(true)
+ {
+ }
+
+ QWidget* openExistingWidget;
+ KexiPrjTypeSelector* prjTypeSelector;
+ QString startDirOrVariable;
+ QWidgetStack *stack;
+ QGuardedPtr<KexiDBConnectionSet> conn_set;
+ KexiDB::DriverManager manager;
+ bool conn_sel_shown;//! helper
+ bool file_sel_shown;
+ bool confirmOverwrites;
+};
+
+/*================================================================*/
+
+KexiConnSelectorWidget::KexiConnSelectorWidget( KexiDBConnectionSet& conn_set,
+ const QString& startDirOrVariable, QWidget* parent, const char* name )
+ : QWidget( parent, name )
+ ,d(new KexiConnSelectorWidgetPrivate())
+{
+ d->conn_set = &conn_set;
+ d->startDirOrVariable = startDirOrVariable;
+ QString none, iconname = KMimeType::mimeType( KexiDB::Driver::defaultFileBasedDriverMimeType() )->icon(none,0);
+ const QPixmap &icon = KGlobal::iconLoader()->loadIcon( iconname, KIcon::Desktop );
+ setIcon( icon );
+
+ QVBoxLayout* globalLyr = new QVBoxLayout( this );
+
+ //create header with radio buttons
+ d->openExistingWidget = new QWidget(this, "openExistingWidget");
+ QVBoxLayout* openExistingWidgetLyr = new QVBoxLayout( d->openExistingWidget );
+// QLabel* lbl = new QLabel(i18n("<b>Select existing Kexi project to open:</b>"), openExistingWidget);
+// openExistingWidgetLyr->addWidget( lbl );
+ d->prjTypeSelector = new KexiPrjTypeSelector( d->openExistingWidget );
+ connect(d->prjTypeSelector->buttonGroup,SIGNAL(clicked(int)),this,SLOT(slotPrjTypeSelected(int)));
+ openExistingWidgetLyr->addWidget( d->prjTypeSelector );
+ openExistingWidgetLyr->addSpacing( KDialogBase::spacingHint() );
+ QFrame* line = new QFrame( d->openExistingWidget, "line" );
+ line->setFrameShape( QFrame::HLine );
+ line->setFrameShadow( QFrame::Sunken );
+ openExistingWidgetLyr->addWidget( line );
+ globalLyr->addWidget(d->openExistingWidget);
+
+ d->stack = new QWidgetStack(this, "stack");
+ globalLyr->addWidget(d->stack);
+
+// m_file = new KexiOpenExistingFile( this, "KexiOpenExistingFile");
+// m_file->btn_advanced->setIconSet( SmallIconSet("1downarrow") );
+ m_fileDlg = 0;
+
+// addWidget(m_file);
+// connect(m_file->btn_advanced,SIGNAL(clicked()),this,SLOT(showAdvancedConn()));
+
+ m_remote = new KexiConnSelectorBase(d->stack, "conn_sel");
+ m_remote->icon->setPixmap( DesktopIcon("network") );
+ m_remote->icon->setFixedSize( m_remote->icon->pixmap()->size() );
+// m_remote->btn_back->setIconSet( SmallIconSet("1uparrow") );
+ connect(m_remote->btn_add, SIGNAL(clicked()), this, SLOT(slotRemoteAddBtnClicked()));
+ connect(m_remote->btn_edit, SIGNAL(clicked()), this, SLOT(slotRemoteEditBtnClicked()));
+ connect(m_remote->btn_remove, SIGNAL(clicked()), this, SLOT(slotRemoteRemoveBtnClicked()));
+ QToolTip::add(m_remote->btn_add, i18n("Add a new database connection"));
+ QToolTip::add(m_remote->btn_edit, i18n("Edit selected database connection"));
+ QToolTip::add(m_remote->btn_remove, i18n("Remove selected database connections"));
+ d->stack->addWidget(m_remote);
+ if (m_remote->layout())
+ m_remote->layout()->setMargin(0);
+// connect(m_remote->btn_back,SIGNAL(clicked()),this,SLOT(showSimpleConn()));
+ connect(m_remote->list,SIGNAL(doubleClicked(QListViewItem*)),
+ this,SLOT(slotConnectionItemExecuted(QListViewItem*)));
+ connect(m_remote->list,SIGNAL(returnPressed(QListViewItem*)),
+ this,SLOT(slotConnectionItemExecuted(QListViewItem*)));
+ connect(m_remote->list,SIGNAL(selectionChanged()),
+ this,SLOT(slotConnectionSelectionChanged()));
+}
+
+KexiConnSelectorWidget::~KexiConnSelectorWidget()
+{
+ delete d;
+}
+
+/*void KexiConnSelectorWidget::disconnectShowSimpleConnButton()
+{
+ m_remote->btn_back->disconnect(this,SLOT(showSimpleConn()));
+}*/
+
+void KexiConnSelectorWidget::showAdvancedConn()
+{
+ slotPrjTypeSelected(2);
+ d->prjTypeSelector->buttonGroup->setButton(2);
+}
+
+//void KexiConnSelectorWidget::showAdvancedConn()
+void KexiConnSelectorWidget::slotPrjTypeSelected(int id)
+{
+ if (id==1) {//file-based prj type
+ showSimpleConn();
+ }
+ else if (id==2) {//server-based prj type
+ if (!d->conn_sel_shown) {
+ d->conn_sel_shown=true;
+
+ //show connections (on demand):
+ for (KexiDB::ConnectionData::ListIterator it(d->conn_set->list()); it.current(); ++it) {
+ addConnectionData( it.current() );
+ // else {
+ //this error should be more verbose:
+ // kdWarning() << "KexiConnSelector::KexiConnSelector(): no driver found for '" << it.current()->driverName << "'!" << endl;
+ // }
+ }
+ if (m_remote->list->firstChild()) {
+ m_remote->list->setSelected(m_remote->list->firstChild(),true);
+ }
+ m_remote->descriptionEdit->setPaletteBackgroundColor(palette().active().background());
+ m_remote->descGroupBox->layout()->setMargin(2);
+ m_remote->list->setFocus();
+ slotConnectionSelectionChanged();
+ }
+ d->stack->raiseWidget(m_remote);
+ }
+}
+
+ConnectionDataLVItem* KexiConnSelectorWidget::addConnectionData( KexiDB::ConnectionData* data )
+{
+ const KexiDB::Driver::Info info( d->manager.driverInfo(data->driverName) );
+// if (!info.name.isEmpty()) {
+ return new ConnectionDataLVItem(data, info, m_remote->list);
+// }
+}
+
+void KexiConnSelectorWidget::showSimpleConn()
+{
+ d->prjTypeSelector->buttonGroup->setButton(1);
+ if (!d->file_sel_shown) {
+ d->file_sel_shown=true;
+ m_fileDlg = new KexiStartupFileDialog( d->startDirOrVariable, KexiStartupFileDialog::Opening,
+ d->stack, "openExistingFileDlg");
+ m_fileDlg->setConfirmOverwrites( d->confirmOverwrites );
+// static_cast<QVBoxLayout*>(m_file->layout())->insertWidget( 2, m_fileDlg );
+ d->stack->addWidget(m_fileDlg);
+
+ for (QWidget *w = parentWidget(true);w;w=w->parentWidget(true)) {
+ if (w->isDialog()) {
+//#ifndef Q_WS_WIN
+ connect(m_fileDlg,SIGNAL(rejected()),static_cast<QDialog*>(w),SLOT(reject()));
+//#endif
+// connect(m_fileDlg,SIGNAL(cancelled()),static_cast<QDialog*>(w),SLOT(reject()));
+ break;
+ }
+ }
+ }
+ d->stack->raiseWidget(m_fileDlg);
+}
+
+int KexiConnSelectorWidget::selectedConnectionType() const
+{
+ return (d->stack->visibleWidget()==m_fileDlg) ? FileBased : ServerBased;
+}
+
+/*ConnectionDataLVItem* KexiConnSelectorWidget::selectedConnectionDataItem() const
+{
+ if (selectedConnectionType()!=KexiConnSelectorWidget::ServerBased)
+ return 0;
+ ConnectionDataLVItem *item = 0; // = static_cast<ConnectionDataLVItem*>(m_remote->list->selectedItem());
+ for (QListViewItemIterator it(m_remote->list); it.current(); ++it) {
+ if (it.current()->isSelected()) {
+ if (item)
+ return 0; //multiple
+ item = static_cast<ConnectionDataLVItem*>(it.current());
+ }
+ }
+ return item;
+}*/
+
+KexiDB::ConnectionData* KexiConnSelectorWidget::selectedConnectionData() const
+{
+ ConnectionDataLVItem *item = static_cast<ConnectionDataLVItem*>(m_remote->list->selectedItem()); //ConnectionDataItem();
+ if (!item)
+ return 0;
+ return item->data();
+}
+
+QString KexiConnSelectorWidget::selectedFileName()
+{
+ if (selectedConnectionType()!=KexiConnSelectorWidget::FileBased)
+ return QString::null;
+ return m_fileDlg->currentFileName();
+}
+
+void KexiConnSelectorWidget::setSelectedFileName(const QString& fileName)
+{
+ if (selectedConnectionType()!=KexiConnSelectorWidget::FileBased)
+ return;
+ return m_fileDlg->setSelection(fileName);
+}
+
+void KexiConnSelectorWidget::slotConnectionItemExecuted(QListViewItem *item)
+{
+ emit connectionItemExecuted(static_cast<ConnectionDataLVItem*>(item));
+}
+
+void KexiConnSelectorWidget::slotConnectionSelectionChanged()
+{
+ ConnectionDataLVItem* item = static_cast<ConnectionDataLVItem*>(m_remote->list->selectedItem());
+ //update buttons availability
+/* ConnectionDataLVItem *singleItem = 0;
+ bool multi = false;
+ for (QListViewItemIterator it(m_remote->list); it.current(); ++it) {
+ if (it.current()->isSelected()) {
+ if (singleItem) {
+ singleItem = 0;
+ multi = true;
+ break;
+ }
+ else
+ singleItem = static_cast<ConnectionDataLVItem*>(it.current());
+ }
+ }*/
+ m_remote->btn_edit->setEnabled(item);
+ m_remote->btn_remove->setEnabled(item);
+ m_remote->descriptionEdit->setText(item ? item->data()->description : QString::null);
+ emit connectionItemHighlighted(item);
+}
+
+QListView* KexiConnSelectorWidget::connectionsList() const
+{
+ return m_remote->list;
+}
+
+void KexiConnSelectorWidget::setFocus()
+{
+ QWidget::setFocus();
+ if (d->stack->visibleWidget()==m_fileDlg)
+ m_fileDlg->setFocus(); //m_fileDlg->locationWidget()->setFocus();
+ else
+ m_remote->list->setFocus();
+}
+
+void KexiConnSelectorWidget::hideHelpers()
+{
+ d->openExistingWidget->hide();
+
+/* m_file->lbl->hide();
+ m_file->line->hide();
+ m_file->spacer->hide();
+ m_file->label->hide();
+ m_remote->label->hide();
+ m_remote->label_back->hide();
+ m_remote->btn_back->hide();
+ m_remote->icon->hide();*/
+}
+
+void KexiConnSelectorWidget::setConfirmOverwrites(bool set)
+{
+ d->confirmOverwrites = set;
+ if (m_fileDlg)
+ m_fileDlg->setConfirmOverwrites( d->confirmOverwrites );
+}
+
+bool KexiConnSelectorWidget::confirmOverwrites() const
+{
+ return d->confirmOverwrites;
+}
+
+/*static QString msgUnfinished() {
+ return i18n("To define or change a connection, use command line options or click on .kexis file. "
+ "You can find example .kexis file at <a href=\"%1\">here</a>.").arg("") //temporary, please do not change for 0.8!
+ + "\nhttp://www.kexi-project.org/resources/testdb.kexis"; */
+// .arg("http://websvn.kde.org/*checkout*/branches/kexi/0.9/koffice/kexi/tests/startup/testdb.kexis");
+//}
+
+void KexiConnSelectorWidget::slotRemoteAddBtnClicked()
+{
+ KexiDB::ConnectionData data;
+ KexiDBConnectionDialog dlg(data, QString::null,
+ KGuiItem(i18n("&Add"), "button_ok", i18n("Add database connection")) );
+ dlg.setCaption(i18n("Add New Database Connection"));
+ if (QDialog::Accepted!=dlg.exec())
+ return;
+
+ //store this conn. data
+ KexiDB::ConnectionData *newData = new KexiDB::ConnectionData(*dlg.currentProjectData().connectionData());
+ if (!d->conn_set->addConnectionData(newData)) {
+ //! @todo msg?
+ delete newData;
+ return;
+ }
+
+ ConnectionDataLVItem* item = addConnectionData(newData);
+// m_remote->list->clearSelection();
+ m_remote->list->setSelected(item, true);
+ slotConnectionSelectionChanged();
+}
+
+void KexiConnSelectorWidget::slotRemoteEditBtnClicked()
+{
+ ConnectionDataLVItem* item = static_cast<ConnectionDataLVItem*>(m_remote->list->selectedItem());
+ if (!item)
+ return;
+ KexiDBConnectionDialog dlg(*item->data(), QString::null,
+ KGuiItem(i18n("&Save"), "filesave", i18n("Save changes made to this database connection")) );
+ dlg.setCaption(i18n("Edit Database Connection"));
+ if (QDialog::Accepted!=dlg.exec())
+ return;
+
+ KexiDB::ConnectionData *newData = new KexiDB::ConnectionData( *dlg.currentProjectData().connectionData() );
+ if (!d->conn_set->saveConnectionData(item->data(), newData)) {
+ //! @todo msg?
+ delete newData;
+ return;
+ }
+ const KexiDB::Driver::Info info( d->manager.driverInfo(item->data()->driverName) );
+ item->update(info);
+ slotConnectionSelectionChanged(); //to update descr. edit
+}
+
+void KexiConnSelectorWidget::slotRemoteRemoveBtnClicked()
+{
+ ConnectionDataLVItem* item = static_cast<ConnectionDataLVItem*>(m_remote->list->selectedItem());
+ if (!item)
+ return;
+ if (KMessageBox::Continue!=KMessageBox::warningContinueCancel(0,
+ i18n("Do you want to remove database connection \"%1\" from the list of available connections?")
+ .arg(item->data()->serverInfoString(true)), QString::null, KStdGuiItem::del(), QString::null,
+ KMessageBox::Notify|KMessageBox::Dangerous))
+ return;
+
+ QListViewItem* nextItem = item->itemBelow();
+ if (!nextItem)
+ nextItem = item->itemAbove();
+ if (!d->conn_set->removeConnectionData(item->data()))
+ return;
+
+ m_remote->list->removeItem(item);
+ if (nextItem)
+ m_remote->list->setSelected(nextItem, true);
+ slotConnectionSelectionChanged();
+}
+
+void KexiConnSelectorWidget::hideConnectonIcon()
+{
+ m_remote->icon->setFixedWidth(0);
+ m_remote->icon->setPixmap(QPixmap());
+}
+
+#include "KexiConnSelector.moc"
diff --git a/kexi/main/startup/KexiConnSelector.h b/kexi/main/startup/KexiConnSelector.h
new file mode 100644
index 000000000..a52dd8ae3
--- /dev/null
+++ b/kexi/main/startup/KexiConnSelector.h
@@ -0,0 +1,142 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003,2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXICONNSELECTOR_H
+#define KEXICONNSELECTOR_H
+
+#include <kexidbconnectionset.h>
+#include <kexidb/driver.h>
+#include "KexiStartupFileDialog.h"
+
+#include <kdialogbase.h>
+#include <klistview.h>
+
+#include <qguardedptr.h>
+
+class KexiConnSelectorBase;
+
+//! helper class
+class ConnectionDataLVItem : public QListViewItem
+{
+ public:
+ ConnectionDataLVItem(KexiDB::ConnectionData *data,
+ const KexiDB::Driver::Info& info, QListView *list);
+ ~ConnectionDataLVItem();
+
+ void update(const KexiDB::Driver::Info& info);
+ KexiDB::ConnectionData *data() const { return m_data; }
+
+ protected:
+ KexiDB::ConnectionData *m_data;
+};
+
+
+//class KexiOpenExistingFile;
+class KexiConnSelectorWidgetPrivate;
+
+/*! Widget that allows to select a database connection (without choosing database itself)
+*/
+class KEXIMAIN_EXPORT KexiConnSelectorWidget : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ enum ConnType { FileBased=1, ServerBased=2 };
+
+ /*! Constructs a KexiConnSelector which contain \a conn_set as connection set.
+ \a conn_set can be altered, because Add/Edit/Remove buttons are available
+ to users. \a startDirOrVariable can be provided to specify a start dir for file browser
+ (it can also contain a configuration variable name with ":" prefix as described
+ in KRecentDirs documentation). */
+ KexiConnSelectorWidget( KexiDBConnectionSet& conn_set,
+ const QString& startDirOrVariable,
+ QWidget* parent = 0, const char* name = 0 );
+
+ virtual ~KexiConnSelectorWidget();
+
+ /*! After accepting this dialog this method returns wherher user selected
+ file- or server- based connection (ConnType enum). */
+ int selectedConnectionType() const;
+
+ /*! \return data of selected connection, if server-based connection was selected.
+ Returns NULL if no selection has been made or file-based connection
+ has been selected.
+ @see selectedConnectionType()
+ */
+ KexiDB::ConnectionData* selectedConnectionData() const;
+
+ /*! \return the name of database file, if file-based connection was selected.
+ Returns null string if no selection has been made or server-based connection
+ has been selected.
+ @see selectedConnectionType()
+ */
+ QString selectedFileName();
+
+ /*! Sets selected filename to \a fileName.
+ Only works when selectedConnectionType()==FileBased. */
+ void setSelectedFileName(const QString& fileName);
+
+// //! Usable when we want to do other things for "back" button
+// void disconnectShowSimpleConnButton();
+
+ QListView* connectionsList() const;
+
+ KexiConnSelectorBase *m_remote;
+// KexiOpenExistingFile *m_file;
+ KexiStartupFileDialog *m_fileDlg;
+
+ /*! If true, user will be asked to accept overwriting existing project.
+ This is true by default. */
+ void setConfirmOverwrites(bool set);
+
+ bool confirmOverwrites() const;
+
+ signals:
+ void connectionItemExecuted(ConnectionDataLVItem *item);
+ void connectionItemHighlighted(ConnectionDataLVItem *item);
+
+ public slots:
+ void showSimpleConn();
+ void showAdvancedConn();
+ virtual void setFocus();
+
+ /*! Hides helpers on the server based connection page
+ (sometimes it's convenient not to have these):
+ - "Select existing database server's connection..." (label at the top)
+ - "Click "Back" button" (label at the bottom)
+ - "Back" button itself */
+ void hideHelpers();
+ void hideConnectonIcon();
+
+ protected slots:
+ void slotConnectionItemExecuted(QListViewItem *item);
+ void slotRemoteAddBtnClicked();
+ void slotRemoteEditBtnClicked();
+ void slotRemoteRemoveBtnClicked();
+ void slotConnectionSelectionChanged();
+ void slotPrjTypeSelected(int id);
+
+ private:
+ ConnectionDataLVItem* addConnectionData( KexiDB::ConnectionData* data );
+ ConnectionDataLVItem* selectedConnectionDataItem() const;
+
+ KexiConnSelectorWidgetPrivate *d;
+};
+
+#endif // KEXICONNSELECTOR_H
diff --git a/kexi/main/startup/KexiConnSelectorBase.ui b/kexi/main/startup/KexiConnSelectorBase.ui
new file mode 100644
index 000000000..dde800f37
--- /dev/null
+++ b/kexi/main/startup/KexiConnSelectorBase.ui
@@ -0,0 +1,285 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiConnSelectorBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiConnSelectorBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>628</width>
+ <height>289</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QListView" row="1" column="0" rowspan="1" colspan="6">
+ <column>
+ <property name="text">
+ <string>Name</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Type</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Server Information</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>list</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>60</height>
+ </size>
+ </property>
+ <property name="selectionMode">
+ <enum>Single</enum>
+ </property>
+ <property name="allColumnsShowFocus">
+ <bool>true</bool>
+ </property>
+ <property name="showSortIndicator">
+ <bool>true</bool>
+ </property>
+ <property name="resizeMode">
+ <enum>LastColumn</enum>
+ </property>
+ </widget>
+ <widget class="QPushButton" row="2" column="4">
+ <property name="name">
+ <cstring>btn_edit</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Edit...</string>
+ </property>
+ </widget>
+ <widget class="QPushButton" row="2" column="3">
+ <property name="name">
+ <cstring>btn_add</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Add...</string>
+ </property>
+ </widget>
+ <widget class="QPushButton" row="2" column="5">
+ <property name="name">
+ <cstring>btn_remove</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ </widget>
+ <spacer row="2" column="2">
+ <property name="name">
+ <cstring>spacer4</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <spacer row="3" column="4">
+ <property name="name">
+ <cstring>spacer4_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>41</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QGroupBox" row="2" column="0" rowspan="2" colspan="2">
+ <property name="name">
+ <cstring>descGroupBox</cstring>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32767</width>
+ <height>80</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Description</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>2</number>
+ </property>
+ <widget class="QTextEdit">
+ <property name="name">
+ <cstring>descriptionEdit</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32767</width>
+ <height>50</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="tabChangesFocus">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QLabel" row="0" column="1" rowspan="1" colspan="5">
+ <property name="name">
+ <cstring>label</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;b&gt;Select Existing Database Server's Connection From the List Below&lt;/b&gt;
+&lt;p&gt;You will see existing Kexi projects available for the selected connection. Here you may also add, edit or remove connections from the list.
+</string>
+ </property>
+ <property name="alignment">
+ <set>WordBreak|AlignTop</set>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>list</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>icon</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="alignment">
+ <set>AlignTop</set>
+ </property>
+ </widget>
+ </grid>
+</widget>
+<tabstops>
+ <tabstop>list</tabstop>
+ <tabstop>btn_add</tabstop>
+ <tabstop>btn_edit</tabstop>
+ <tabstop>btn_remove</tabstop>
+ <tabstop>descriptionEdit</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kexi/main/startup/KexiDBTitlePage.cpp b/kexi/main/startup/KexiDBTitlePage.cpp
new file mode 100644
index 000000000..3b0487938
--- /dev/null
+++ b/kexi/main/startup/KexiDBTitlePage.cpp
@@ -0,0 +1,35 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "KexiDBTitlePage.h"
+
+#include <qlabel.h>
+
+KexiDBTitlePage::KexiDBTitlePage( const QString& labelText, QWidget* parent, const char* name )
+ : KexiDBTitlePageBase( parent, name )
+{
+ if (!labelText.isEmpty())
+ label->setText(labelText);
+}
+
+KexiDBTitlePage::~KexiDBTitlePage()
+{
+}
+
+#include "KexiDBTitlePage.moc"
diff --git a/kexi/main/startup/KexiDBTitlePage.h b/kexi/main/startup/KexiDBTitlePage.h
new file mode 100644
index 000000000..983419462
--- /dev/null
+++ b/kexi/main/startup/KexiDBTitlePage.h
@@ -0,0 +1,42 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDBTITLEPAGE_H
+#define KEXIDBTITLEPAGE_H
+
+#include "KexiDBTitlePageBase.h"
+
+//! @short A helper widget used to displaying a line edit with a label and layout
+class KEXIMAIN_EXPORT KexiDBTitlePage : public KexiDBTitlePageBase
+{
+ Q_OBJECT
+
+public:
+ //! Constructs title page. \a labelText can be provided to change default
+ //! "Project caption:" label.
+ KexiDBTitlePage( const QString& labelText, QWidget* parent = 0, const char* name = 0 );
+ ~KexiDBTitlePage();
+
+protected slots:
+ virtual void languageChange() { KexiDBTitlePageBase::languageChange(); }
+
+};
+
+#endif // KEXIDBTITLEPAGE_H
+
diff --git a/kexi/main/startup/KexiDBTitlePageBase.ui b/kexi/main/startup/KexiDBTitlePageBase.ui
new file mode 100644
index 000000000..991bc73f4
--- /dev/null
+++ b/kexi/main/startup/KexiDBTitlePageBase.ui
@@ -0,0 +1,94 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiDBTitlePageBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiDBTitlePageBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>379</width>
+ <height>87</height>
+ </rect>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>label</cstring>
+ </property>
+ <property name="text">
+ <string>Project caption: </string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>le_caption</cstring>
+ </property>
+ </widget>
+ <spacer row="1" column="1">
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>111</height>
+ </size>
+ </property>
+ </spacer>
+ <spacer row="0" column="2">
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Minimum</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KLineEdit" row="0" column="1">
+ <property name="name">
+ <cstring>le_caption</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klineedit.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/main/startup/KexiNewPrjTypeSelector.ui b/kexi/main/startup/KexiNewPrjTypeSelector.ui
new file mode 100644
index 000000000..30adfb063
--- /dev/null
+++ b/kexi/main/startup/KexiNewPrjTypeSelector.ui
@@ -0,0 +1,94 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiNewPrjTypeSelector</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiNewPrjTypeSelector</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>328</width>
+ <height>203</height>
+ </rect>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <spacer row="2" column="0">
+ <property name="name">
+ <cstring>spacer9</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KListView" row="1" column="0">
+ <column>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>lv_types</cstring>
+ </property>
+ <property name="allColumnsShowFocus">
+ <bool>true</bool>
+ </property>
+ <property name="resizeMode">
+ <enum>LastColumn</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>lbl</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Kexi will create a new database project. Select a storage method which will be used to store the new project.
+</string>
+ </property>
+ <property name="alignment">
+ <set>WordBreak|AlignTop</set>
+ </property>
+ </widget>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<tabstops>
+</tabstops>
+<functions>
+ <function access="private" specifier="non virtual">init()</function>
+</functions>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klistview.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/main/startup/KexiNewProjectWizard.cpp b/kexi/main/startup/KexiNewProjectWizard.cpp
new file mode 100644
index 000000000..15102864e
--- /dev/null
+++ b/kexi/main/startup/KexiNewProjectWizard.cpp
@@ -0,0 +1,422 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "KexiNewProjectWizard.h"
+
+#include "KexiConnSelector.h"
+#include "KexiConnSelectorBase.h"
+#include "KexiNewPrjTypeSelector.h"
+#include "KexiOpenExistingFile.h"
+#include "KexiDBTitlePage.h"
+#include "KexiServerDBNamePage.h"
+#include "KexiProjectSelector.h"
+#include "kexi.h"
+
+#include <kexiutils/identifier.h>
+#include <kexiutils/utils.h>
+#include <kexiguimsghandler.h>
+
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kmimetype.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <klistview.h>
+#include <kurlcombobox.h>
+#include <kmessagebox.h>
+#include <klineedit.h>
+
+#include <qobjectlist.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qheader.h>
+
+//! @internal
+class KexiNewProjectWizardPrivate
+{
+ public:
+ KexiNewProjectWizardPrivate()
+ {
+ le_dbname_txtchanged_disable = false;
+ le_dbname_autofill = true;
+// conndata_to_show = 0;
+// project_set_to_show = 0;
+ }
+ ~KexiNewProjectWizardPrivate()
+ {
+// delete conndata_to_show;
+// delete project_set_to_show;
+ delete msgHandler;
+ }
+// KListView *lv_types;
+ KListViewItem *lvi_file, *lvi_server;
+ QString chk_file_txt, chk_server_txt; //!< helper
+
+ QString server_db_name_dblist_lbl_txt; //!< helper
+
+ //for displaying db list of the selected conn.
+ QGuardedPtr<KexiDB::ConnectionData> conndata_to_show;
+ KexiProjectSet *project_set_to_show;
+
+ KexiGUIMessageHandler* msgHandler;
+
+ bool le_dbname_txtchanged_disable : 1;
+ bool le_dbname_autofill : 1;
+};
+
+KexiNewProjectWizard::KexiNewProjectWizard(KexiDBConnectionSet& conn_set,
+ QWidget *parent, const char *name, bool modal, WFlags f)
+: KWizard(parent, name, modal, f)
+, d(new KexiNewProjectWizardPrivate() )
+{
+ d->msgHandler = new KexiGUIMessageHandler(this);
+ setIcon( DesktopIcon("filenew") );
+ setCaption( i18n("Creating New Project") );
+ finishButton()->setText(i18n("Create"));
+
+ //page: type selector
+ m_prjtype_sel = new KexiNewPrjTypeSelector(this, "KexiNewPrjTypeSelector");
+// lv_types = new KListView(m_prjtype_sel, "types listview");
+// m_prjtype_sel->lv_types->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum, 0, 2));
+#if KDE_IS_VERSION(3,3,9)
+ m_prjtype_sel->lv_types->setShadeSortColumn(false);
+#endif
+ m_prjtype_sel->lv_types->header()->hide();
+ m_prjtype_sel->lv_types->setSorting(-1);
+ m_prjtype_sel->lv_types->setAlternateBackground(QColor()); //disable altering
+ m_prjtype_sel->lv_types->setItemMargin( KDialogBase::marginHint() );
+ QString none;
+ d->lvi_file = new KListViewItem( m_prjtype_sel->lv_types, i18n("New Project Stored in File") );
+ d->lvi_file->setPixmap(0,
+ KGlobal::iconLoader()->loadIcon( KMimeType::mimeType(
+ KexiDB::Driver::defaultFileBasedDriverMimeType() )->icon(none,0), KIcon::Desktop
+ )
+ );
+ d->lvi_file->setMultiLinesEnabled( true );
+ d->lvi_server = new KListViewItem( m_prjtype_sel->lv_types, d->lvi_file,
+ i18n("New Project Stored on Database Server") );
+ d->lvi_server->setPixmap(0, DesktopIcon("network") );
+ d->lvi_server->setMultiLinesEnabled( true );
+// m_prjtype_sel->lv_types->resize(d->m_prjtype_sel->lv_types->width(), d->lvi_file->height()*3);
+ m_prjtype_sel->lv_types->setFocus();
+// QString txt_dns = i18n("Don't show me this question again.");
+// d->chk_file_txt = m_prjtype_sel->chk_always->text() +"\n"+txt_dns;
+// d->chk_server_txt = i18n("Always &use database server for creating new projects.")
+// +"\n"+txt_dns;
+
+ connect(m_prjtype_sel->lv_types,SIGNAL(executed(QListViewItem*)),this,SLOT(slotLvTypesExecuted(QListViewItem*)));
+ connect(m_prjtype_sel->lv_types,SIGNAL(returnPressed(QListViewItem*)),this,SLOT(slotLvTypesExecuted(QListViewItem*)));
+ connect(m_prjtype_sel->lv_types,SIGNAL(selectionChanged( QListViewItem*)),this,SLOT(slotLvTypesSelected(QListViewItem*)));
+
+// static_cast<QVBoxLayout*>(m_prjtype_sel->layout())->insertWidget(1,d->m_prjtype_sel->lv_types);
+// static_cast<QVBoxLayout*>(m_prjtype_sel->layout())->insertStretch(3,1);
+// updateGeometry();
+
+ addPage(m_prjtype_sel, i18n("Select Storage Method"));
+// d->m_prjtype_sel->lv_types->setMinimumHeight(QMAX(d->lvi_file->height(),d->lvi_server->height())+25);
+
+ //page: db title
+ m_db_title = new KexiDBTitlePage(QString::null, this, "KexiDBTitlePage");
+ addPage(m_db_title, i18n("Select Project's Caption"));
+
+ //page: connection selector
+ m_conn_sel_widget = new QWidget(this);
+ QVBoxLayout* conn_sel_lyr = new QVBoxLayout(m_conn_sel_widget);
+ QLabel *conn_sel_label = new QLabel(i18n("Enter a new Kexi project's file name:"), m_conn_sel_widget);
+ conn_sel_label->setAlignment(Qt::AlignAuto|Qt::AlignTop|Qt::WordBreak);
+ conn_sel_lyr->addWidget( conn_sel_label );
+ conn_sel_lyr->addSpacing(KDialogBase::spacingHint());
+
+ m_conn_sel = new KexiConnSelectorWidget(conn_set, ":OpenExistingOrCreateNewProject",
+ m_conn_sel_widget, "KexiConnSelectorWidget");
+ conn_sel_lyr->addWidget( m_conn_sel );
+
+ //"Select database server connection"
+// m_conn_sel->m_file->btn_advanced->hide();
+// m_conn_sel->m_file->label->hide();
+//TODO m_conn_sel->m_file->lbl->setText( i18n("Enter a new Kexi project's file name:") );
+ m_conn_sel->hideHelpers();
+
+ m_conn_sel->m_remote->label->setText(
+ i18n("Select database server's connection you wish to use to create a new Kexi project. "
+ "<p>Here you may also add, edit or remove connections from the list."));
+// m_conn_sel->m_remote->label_back->hide();
+// m_conn_sel->m_remote->btn_back->hide();
+
+ m_conn_sel->showSimpleConn();
+ //anyway, db files will be _saved_
+ m_conn_sel->m_fileDlg->setMode( KexiStartupFileDialog::SavingFileBasedDB );
+// m_conn_sel->m_fileDlg->setMode( KFile::LocalOnly | KFile::File );
+// m_conn_sel->m_fileDlg->setOperationMode( KFileDialog::Saving );
+////js connect(m_conn_sel->m_fileDlg,SIGNAL(rejected()),this,SLOT(reject()));
+// connect(m_conn_sel->m_fileDlg,SIGNAL(fileHighlighted(const QString&)),this,SLOT(slotFileHighlighted(const QString&)));
+ connect(m_conn_sel->m_fileDlg,SIGNAL(accepted()),this,SLOT(accept()));
+ m_conn_sel->showAdvancedConn();
+ connect(m_conn_sel,SIGNAL(connectionItemExecuted(ConnectionDataLVItem*)),
+ this,SLOT(next()));
+
+ addPage(m_conn_sel_widget, i18n("Select Project's Location"));
+
+ //page: server db name
+ m_server_db_name = new KexiServerDBNamePage(this, "KexiServerDBNamePage");
+ d->server_db_name_dblist_lbl_txt = i18n("Existing project databases on <b>%1</b> database server:");
+ connect(m_server_db_name->le_caption, SIGNAL(textChanged(const QString&)),
+ this,SLOT(slotServerDBCaptionTxtChanged(const QString&)));
+ connect(m_server_db_name->le_dbname, SIGNAL(textChanged(const QString&)),
+ this,SLOT(slotServerDBNameTxtChanged(const QString&)));
+ connect(m_server_db_name->le_caption, SIGNAL(returnPressed()),
+ this,SLOT(accept()));
+ connect(m_server_db_name->le_dbname, SIGNAL(returnPressed()),
+ this,SLOT(accept()));
+ m_server_db_name->le_caption->setText(i18n("New database"));
+ m_server_db_name->le_dbname->setValidator(new KexiUtils::IdentifierValidator(this, "id_val"));
+ m_project_selector = new KexiProjectSelectorWidget(
+ m_server_db_name->frm_dblist, "KexiProjectSelectorWidget", 0, false, false );
+ GLUE_WIDGET(m_project_selector, m_server_db_name->frm_dblist);
+ m_project_selector->setFocusPolicy(NoFocus);
+ m_project_selector->setSelectable(false);
+
+ addPage(m_server_db_name, i18n("Select Project's Caption & Database Name"));
+
+ setFinishEnabled(m_prjtype_sel,false);
+ setFinishEnabled(m_db_title,false);
+ setFinishEnabled(m_server_db_name,true);
+
+ //finish:
+ updateGeometry();
+ m_prjtype_sel->lv_types->setSelected(d->lvi_file, true);
+}
+
+KexiNewProjectWizard::~KexiNewProjectWizard()
+{
+ delete d;
+}
+
+void KexiNewProjectWizard::show()
+{
+ KDialog::centerOnScreen(this);
+ KWizard::show();
+}
+
+void KexiNewProjectWizard::slotLvTypesExecuted(QListViewItem *)
+{
+ next();
+}
+
+void KexiNewProjectWizard::slotLvTypesSelected(QListViewItem *item)
+{
+/* if (item==d->lvi_file) {
+ m_prjtype_sel->chk_always->setText(d->chk_file_txt);
+ }
+ else if (item==d->lvi_server) {
+ m_prjtype_sel->chk_always->setText(d->chk_server_txt);
+ }*/
+ setAppropriate( m_db_title, item==d->lvi_file );
+ setAppropriate( m_server_db_name, item==d->lvi_server );
+}
+
+void KexiNewProjectWizard::showPage(QWidget *page)
+{
+ if (page==m_prjtype_sel) {//p 1
+ m_prjtype_sel->lv_types->setFocus();
+ m_prjtype_sel->lv_types->setCurrentItem(m_prjtype_sel->lv_types->currentItem());
+ } else if (page==m_db_title) {//p 2
+ if (m_db_title->le_caption->text().stripWhiteSpace().isEmpty())
+ m_db_title->le_caption->setText(i18n("New database"));
+ m_db_title->le_caption->selectAll();
+ m_db_title->le_caption->setFocus();
+ } else if (page==m_conn_sel_widget) {//p 3
+ if (m_prjtype_sel->lv_types->currentItem()==d->lvi_file) {
+ m_conn_sel->showSimpleConn();
+ QString fn = KexiUtils::string2FileName( m_db_title->le_caption->text() );
+ if (!fn.endsWith(".kexi"))
+ fn += ".kexi";
+ m_conn_sel->m_fileDlg->setLocationText(fn);
+ setFinishEnabled(m_conn_sel_widget,true);
+ m_conn_sel->setFocus();
+ }
+ else {
+ m_conn_sel->showAdvancedConn();
+ setFinishEnabled(m_conn_sel_widget,false);
+ m_conn_sel->setFocus();
+ m_server_db_name->le_caption->selectAll();
+ }
+ } else if (page==m_server_db_name) {
+ if (m_conn_sel->selectedConnectionData()
+ && (static_cast<KexiDB::ConnectionData*>(d->conndata_to_show) != m_conn_sel->selectedConnectionData())) {
+ m_project_selector->setProjectSet(0);
+// delete d->project_set_to_show;
+ d->conndata_to_show = 0;
+ d->project_set_to_show = new KexiProjectSet(*m_conn_sel->selectedConnectionData(), d->msgHandler);
+ if (d->project_set_to_show->error()) {
+ delete d->project_set_to_show;
+ d->project_set_to_show = 0;
+ return;
+ }
+ d->conndata_to_show = m_conn_sel->selectedConnectionData();
+ //-refresh projects list
+ m_project_selector->setProjectSet( d->project_set_to_show );
+ }
+ }
+ KWizard::showPage(page);
+}
+
+void KexiNewProjectWizard::next()
+{
+ //let's check if move to next page is allowed:
+ if (currentPage()==m_db_title) { //pg 2
+ if (m_db_title->le_caption->text().stripWhiteSpace().isEmpty()) {
+ KMessageBox::information(this, i18n("Enter project caption."));
+ m_db_title->le_caption->setText("");
+ m_db_title->le_caption->setFocus();
+ return;
+ }
+ } else if (currentPage()==m_conn_sel_widget) {//p 3
+ if (m_prjtype_sel->lv_types->currentItem()==d->lvi_file) {
+ //test for db file selection
+ }
+ else {
+ //test for db conn selection
+ if (!m_conn_sel->selectedConnectionData()) {
+ KMessageBox::information(this, i18n("Select server connection for a new project."));
+ return;
+ }
+ m_project_selector->label->setText(
+ d->server_db_name_dblist_lbl_txt.arg(m_conn_sel->selectedConnectionData()->serverInfoString(false)) );
+ m_server_db_name->le_caption->setFocus();
+
+ }
+ }
+ KWizard::next();
+}
+
+void KexiNewProjectWizard::accept()
+{
+ if (m_prjtype_sel->lv_types->currentItem()==d->lvi_file) {//FILE:
+ //check if new db file name is ok
+ kdDebug() << "********** sender() " << sender()->className() << endl;
+ if (sender()==finishButton()) { /*(only if signal does not come from filedialog)*/
+ kdDebug() << "********** sender()==finishButton() ********" << endl;
+// if (!m_conn_sel->m_fileDlg->checkURL()) {
+ if (!m_conn_sel->m_fileDlg->checkFileName()) {
+ return;
+ }
+ }
+ } else {//SERVER:
+ //check if we have enough of data
+ if (m_server_db_name->le_caption->text().stripWhiteSpace().isEmpty()) {
+ KMessageBox::information(this, i18n("Enter project caption."));
+ m_server_db_name->le_caption->setText("");
+ m_server_db_name->le_caption->setFocus();
+ return;
+ }
+ QString dbname = m_server_db_name->le_dbname->text().stripWhiteSpace();
+ if (dbname.isEmpty()) {
+ KMessageBox::information(this, i18n("Enter project's database name."));
+ m_server_db_name->le_dbname->setText("");
+ m_server_db_name->le_dbname->setFocus();
+ return;
+ }
+ //check for duplicated dbname
+ if (m_conn_sel->confirmOverwrites() && m_project_selector->projectSet() && m_project_selector->projectSet()
+ ->findProject( m_server_db_name->le_dbname->text() )) {
+ if (KMessageBox::Continue!=KMessageBox::warningContinueCancel( this, "<qt>"
+ +i18n("<b>A project with database name \"%1\" already exists</b>"
+ "<p>Do you want to delete it and create a new one?")
+ .arg( m_server_db_name->le_dbname->text() ), QString::null, KStdGuiItem::del(),
+ QString::null, KMessageBox::Notify|KMessageBox::Dangerous ))
+ {
+ m_server_db_name->le_dbname->setFocus();
+ return;
+ }
+ }
+ }
+
+ KWizard::accept();
+}
+
+void KexiNewProjectWizard::done(int r)
+{
+/* //save state (always, no matter if dialog is accepted or not)
+ KGlobal::config()->setGroup("Startup");
+ if (!m_prjtype_sel->chk_always->isChecked())
+ KGlobal::config()->deleteEntry("DefaultStorageForNewProjects");
+ else if (m_prjtype_sel->lv_types->currentItem()==d->lvi_file)
+ KGlobal::config()->writeEntry("DefaultStorageForNewProjects","File");
+ else
+ KGlobal::config()->writeEntry("DefaultStorageForNewProjects","Server");*/
+
+ KGlobal::config()->sync();
+ KWizard::done(r);
+}
+
+QString KexiNewProjectWizard::projectDBName() const
+{
+ if (m_prjtype_sel->lv_types->currentItem()==d->lvi_server)
+ return m_server_db_name->le_dbname->text();
+ return m_conn_sel->selectedFileName();
+}
+
+QString KexiNewProjectWizard::projectCaption() const
+{
+ if (m_prjtype_sel->lv_types->currentItem()==d->lvi_server) {
+ return m_server_db_name->le_caption->text();
+ }
+ return m_db_title->le_caption->text();
+}
+
+KexiDB::ConnectionData* KexiNewProjectWizard::projectConnectionData() const
+{
+ if (m_prjtype_sel->lv_types->currentItem()==d->lvi_file)
+ return 0;
+ return m_conn_sel->selectedConnectionData();
+}
+
+void KexiNewProjectWizard::slotServerDBCaptionTxtChanged(const QString &capt)
+{
+ if (m_server_db_name->le_dbname->text().isEmpty())
+ d->le_dbname_autofill=true;
+ if (d->le_dbname_autofill) {
+ d->le_dbname_txtchanged_disable = true;
+ QString captionAsId = KexiUtils::string2Identifier(capt);
+ m_server_db_name->le_dbname->setText(captionAsId);
+ d->le_dbname_txtchanged_disable = false;
+ }
+}
+
+void KexiNewProjectWizard::slotServerDBNameTxtChanged(const QString &)
+{
+ if (d->le_dbname_txtchanged_disable)
+ return;
+ d->le_dbname_autofill = false;
+}
+
+/*! If true, user will be asked to accept overwriting existing file.
+ This is true by default. */
+void KexiNewProjectWizard::setConfirmOverwrites(bool set)
+{
+ m_conn_sel->setConfirmOverwrites(set);
+}
+
+
+#include "KexiNewProjectWizard.moc"
+
diff --git a/kexi/main/startup/KexiNewProjectWizard.h b/kexi/main/startup/KexiNewProjectWizard.h
new file mode 100644
index 000000000..9f9eaa626
--- /dev/null
+++ b/kexi/main/startup/KexiNewProjectWizard.h
@@ -0,0 +1,90 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KexiNewProjectWizard_H
+#define KexiNewProjectWizard_H
+
+#include "kexidbconnectionset.h"
+#include <kexidb/connectiondata.h>
+
+#include <kwizard.h>
+
+class QListViewItem;
+
+class KexiNewPrjTypeSelector;
+class KexiConnSelectorWidget;
+class KexiNewProjectWizardPrivate;
+class KexiDBTitlePage;
+class KexiServerDBNamePage;
+class KexiProjectSelectorWidget;
+
+class KEXIMAIN_EXPORT KexiNewProjectWizard : public KWizard
+{
+ Q_OBJECT
+ public:
+ KexiNewProjectWizard(KexiDBConnectionSet& conn_set, QWidget *parent=0,
+ const char *name=0, bool modal=false, WFlags f=0);
+ ~KexiNewProjectWizard();
+
+ /*! \return name for a new project's database if server-based project
+ type was selected. Returns file name if file-based project was selected. */
+ QString projectDBName() const;
+
+ /*! \return name for a new project. Used for both file- and serever- based projects. */
+ QString projectCaption() const;
+
+ /*! \return data of selected connection for new project,
+ if server-based project type was selected.
+ Returns NULL if no selection has been made or file-based project
+ has been selected. */
+ KexiDB::ConnectionData* projectConnectionData() const;
+
+ /*! Reimplemented for internal reasons */
+ virtual void show();
+
+ /*! If true, user will be asked to accept overwriting existing project.
+ This is true by default. */
+ void setConfirmOverwrites(bool set);
+
+ protected slots:
+ void slotLvTypesSelected(QListViewItem *);
+ void slotLvTypesExecuted(QListViewItem *);
+ void slotServerDBCaptionTxtChanged(const QString &capt);
+ void slotServerDBNameTxtChanged(const QString &n);
+
+ virtual void done(int r);
+ virtual void next();
+ virtual void accept();
+
+ protected:
+ virtual void showPage(QWidget *page);
+
+ KexiNewPrjTypeSelector *m_prjtype_sel;
+ KexiDBTitlePage *m_db_title;
+ KexiServerDBNamePage *m_server_db_name;
+ KexiProjectSelectorWidget* m_project_selector;
+
+ KexiConnSelectorWidget *m_conn_sel;
+ QWidget *m_conn_sel_widget;
+
+ KexiNewProjectWizardPrivate *d;
+};
+
+#endif
+
diff --git a/kexi/main/startup/KexiOpenExistingFile.ui b/kexi/main/startup/KexiOpenExistingFile.ui
new file mode 100644
index 000000000..ae4c05a9d
--- /dev/null
+++ b/kexi/main/startup/KexiOpenExistingFile.ui
@@ -0,0 +1,127 @@
+<!DOCTYPE UI><UI version="3.1" stdsetdef="1">
+<class>KexiOpenExistingFile</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiOpenExistingFile</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>328</width>
+ <height>108</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>lbl</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;b&gt;Select existing Kexi project file to open:&lt;/b&gt;
+</string>
+ </property>
+ <property name="alignment">
+ <set>WordBreak|AlignTop</set>
+ </property>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>line</cstring>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32767</width>
+ <height>8</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>HLine</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Sunken</enum>
+ </property>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>spacer</cstring>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>6</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32767</width>
+ <height>6</height>
+ </size>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout2</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>btn_advanced</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Advanced </string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>label</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Click "Advanced" button if you want to find an existing project on a server rather than a file.</string>
+ </property>
+ <property name="alignment">
+ <set>WordBreak|AlignTop</set>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+</widget>
+<tabstops>
+ <tabstop>btn_advanced</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kexi/main/startup/KexiProjectSelector.cpp b/kexi/main/startup/KexiProjectSelector.cpp
new file mode 100644
index 000000000..c28ab6d5c
--- /dev/null
+++ b/kexi/main/startup/KexiProjectSelector.cpp
@@ -0,0 +1,297 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "KexiProjectSelector.h"
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/connectiondata.h>
+#include "core/kexi.h"
+
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kmimetype.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qlistview.h>
+
+#include <assert.h>
+
+//! @internal
+class KexiProjectSelectorWidgetPrivate
+{
+public:
+ KexiProjectSelectorWidgetPrivate()
+ {
+ selectable = true;
+ }
+ QPixmap fileicon, dbicon;
+ bool showProjectNameColumn : 1;
+ bool showConnectionColumns : 1;
+ bool selectable : 1;
+};
+
+/*================================================================*/
+
+//! helper class
+class ProjectDataLVItem : public QListViewItem
+{
+public:
+ ProjectDataLVItem(KexiProjectData *d,
+ const KexiDB::Driver::Info& info, KexiProjectSelectorWidget *selector )
+ : QListViewItem(selector->list)
+ , data(d)
+ {
+ int colnum = 0;
+ const KexiDB::ConnectionData *cdata = data->constConnectionData();
+ if (selector->d->showProjectNameColumn)
+ setText(colnum++, data->caption()+" ");
+
+ setText(colnum++, data->databaseName()+" ");
+
+ if (selector->d->showConnectionColumns) {
+ QString drvname = info.caption.isEmpty() ? cdata->driverName : info.caption;
+ if (info.fileBased) {
+ setText(colnum++, i18n("File") + " ("+drvname+") " );
+ } else {
+ setText(colnum++, drvname+" " );
+ }
+
+ QString conn;
+ if (!cdata->caption.isEmpty())
+ conn = cdata->caption + ": ";
+ conn += cdata->serverInfoString();
+ setText(3, conn + " ");
+ }
+ }
+ ~ProjectDataLVItem() {}
+
+ KexiProjectData *data;
+};
+
+/*================================================================*/
+
+/*!
+ * Constructs a KexiProjectSelector which is a child of 'parent', with the
+ * name 'name' and widget flags set to 'f'
+ */
+KexiProjectSelectorWidget::KexiProjectSelectorWidget(
+ QWidget* parent, const char* name,
+ KexiProjectSet* prj_set, bool showProjectNameColumn,
+ bool showConnectionColumns )
+ : KexiProjectSelectorBase( parent, name )
+ ,m_prj_set(prj_set)
+ ,d(new KexiProjectSelectorWidgetPrivate())
+{
+ d->showProjectNameColumn = showProjectNameColumn;
+ d->showConnectionColumns = showConnectionColumns;
+ QString none, iconname = KMimeType::mimeType( KexiDB::Driver::defaultFileBasedDriverMimeType() )->icon(none,0);
+ d->fileicon = KGlobal::iconLoader()->loadIcon( iconname, KIcon::Desktop );
+ setIcon( d->fileicon );
+ d->dbicon = SmallIcon("database");
+// list->setHScrollBarMode( QScrollView::AlwaysOn );
+
+ if (!d->showConnectionColumns) {
+ list->removeColumn(2);
+ list->removeColumn(2);
+ }
+ if (!d->showProjectNameColumn) {
+ list->removeColumn(0);
+ }
+ setFocusProxy(list);
+
+ //show projects
+ setProjectSet( m_prj_set );
+ connect(list,SIGNAL(doubleClicked(QListViewItem*)),this,SLOT(slotItemExecuted(QListViewItem*)));
+ connect(list,SIGNAL(returnPressed(QListViewItem*)),this,SLOT(slotItemExecuted(QListViewItem*)));
+ connect(list,SIGNAL(selectionChanged()),this,SLOT(slotItemSelected()));
+}
+
+/*!
+ * Destroys the object and frees any allocated resources
+ */
+KexiProjectSelectorWidget::~KexiProjectSelectorWidget()
+{
+ delete d;
+}
+
+KexiProjectData* KexiProjectSelectorWidget::selectedProjectData() const
+{
+ ProjectDataLVItem *item = static_cast<ProjectDataLVItem*>(list->selectedItem());
+ if (item)
+ return item->data;
+ return 0;
+}
+
+void KexiProjectSelectorWidget::slotItemExecuted(QListViewItem *item)
+{
+ if (!d->selectable)
+ return;
+ ProjectDataLVItem *ditem = static_cast<ProjectDataLVItem*>(item);
+ if (ditem)
+ emit projectExecuted( ditem->data );
+}
+
+void KexiProjectSelectorWidget::slotItemSelected()
+{
+ if (!d->selectable)
+ return;
+ ProjectDataLVItem *ditem = static_cast<ProjectDataLVItem*>(list->selectedItem());
+ emit selectionChanged( ditem ? ditem->data : 0 );
+}
+
+void KexiProjectSelectorWidget::setProjectSet( KexiProjectSet* prj_set )
+{
+ if (prj_set) {
+ //old list
+ list->clear();
+ }
+ m_prj_set = prj_set;
+ if (!m_prj_set)
+ return;
+//TODO: what with project set's ownership?
+ if (m_prj_set->error()) {
+ kdDebug() << "KexiProjectSelectorWidget::setProjectSet() : m_prj_set->error() !"<<endl;
+ return;
+ }
+ KexiDB::DriverManager manager;
+ KexiProjectData::List prjlist = m_prj_set->list();
+ KexiProjectData *data = prjlist.first();
+ while (data) {
+ KexiDB::Driver::Info info = manager.driverInfo(data->constConnectionData()->driverName);
+ if (!info.name.isEmpty()) {
+ ProjectDataLVItem *item = new ProjectDataLVItem(data, info, this);
+ if (!d->selectable)
+ item->setSelectable(false);
+ if (info.fileBased)
+ item->setPixmap( 0, d->fileicon );
+ else
+ item->setPixmap( 0, d->dbicon );
+ }
+ else {
+ kdWarning() << "KexiProjectSelector::KexiProjectSelector(): no driver found for '"
+ << data->constConnectionData()->driverName << "'!" << endl;
+ }
+ data=prjlist.next();
+ }
+ if (list->firstChild()) {
+ list->setSelected(list->firstChild(),true);
+ }
+}
+
+void KexiProjectSelectorWidget::setSelectable(bool set)
+{
+ if (d->selectable == set)
+ return;
+ d->selectable = set;
+ //update items' state
+ QListViewItemIterator it( list );
+ while ( it.current() ) {
+ it.current()->setSelectable( d->selectable );
+ }
+}
+
+bool KexiProjectSelectorWidget::isSelectable() const
+{
+ return d->selectable;
+}
+
+/*================================================================*/
+
+KexiProjectSelectorDialog::KexiProjectSelectorDialog( QWidget *parent, const char *name,
+ KexiProjectSet* prj_set, bool showProjectNameColumn, bool showConnectionColumns)
+ : KDialogBase( Plain, i18n("Open Recent Project"),
+#ifndef KEXI_NO_UNFINISHED
+ //! @todo re-add Help when doc is available
+ Help |
+#endif
+ Ok | Cancel, Ok, parent, name )
+{
+ init(prj_set, showProjectNameColumn, showConnectionColumns);
+}
+
+KexiProjectSelectorDialog::KexiProjectSelectorDialog( QWidget *parent, const char *name,
+ KexiDB::ConnectionData* cdata,
+ bool showProjectNameColumn, bool showConnectionColumns)
+ : KDialogBase(
+ Plain, i18n("Open Project"),
+#ifndef KEXI_NO_UNFINISHED
+ //! @todo re-add Help when doc is available
+ Help |
+#endif
+ Ok | Cancel, Ok, parent, name, true/*modal*/, false/*sep*/ )
+{
+ setButtonGuiItem(Ok, KGuiItem(i18n("&Open"), "fileopen", i18n("Open Database Connection")));
+ assert(cdata);
+ if (!cdata)
+ return;
+ KexiProjectSet *prj_set = new KexiProjectSet( *cdata );
+ init(prj_set, showProjectNameColumn, showConnectionColumns);
+
+ m_sel->label->setText( i18n("Select a project on <b>%1</b> database server to open:")
+ .arg(cdata->serverInfoString(false)) );
+}
+
+KexiProjectSelectorDialog::~KexiProjectSelectorDialog()
+{
+}
+
+void KexiProjectSelectorDialog::init(KexiProjectSet* prj_set, bool showProjectNameColumn,
+ bool showConnectionColumns)
+{
+ setSizeGripEnabled(true);
+
+ QVBoxLayout *lyr = new QVBoxLayout(plainPage(), 0, KDialogBase::spacingHint(), "lyr");
+ m_sel = new KexiProjectSelectorWidget(plainPage(), "sel",
+ prj_set, showProjectNameColumn, showConnectionColumns);
+ lyr->addWidget(m_sel);
+ setIcon(*m_sel->icon());
+ m_sel->setFocus();
+
+ connect(m_sel,SIGNAL(projectExecuted(KexiProjectData*)),
+ this,SLOT(slotProjectExecuted(KexiProjectData*)));
+ connect(m_sel,SIGNAL(selectionChanged(KexiProjectData*)),
+ this,SLOT(slotProjectSelectionChanged(KexiProjectData*)));
+}
+
+KexiProjectData* KexiProjectSelectorDialog::selectedProjectData() const
+{
+ return m_sel->selectedProjectData();
+}
+
+void KexiProjectSelectorDialog::slotProjectExecuted(KexiProjectData*)
+{
+ accept();
+}
+
+void KexiProjectSelectorDialog::slotProjectSelectionChanged(KexiProjectData* pdata)
+{
+ enableButtonOK(pdata);
+}
+
+void KexiProjectSelectorDialog::show()
+{
+ KDialogBase::show();
+ KDialog::centerOnScreen(this);
+}
+
+#include "KexiProjectSelector.moc"
diff --git a/kexi/main/startup/KexiProjectSelector.h b/kexi/main/startup/KexiProjectSelector.h
new file mode 100644
index 000000000..3151be3b2
--- /dev/null
+++ b/kexi/main/startup/KexiProjectSelector.h
@@ -0,0 +1,134 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIPROJECTSELECTOR_H
+#define KEXIPROJECTSELECTOR_H
+
+#include "KexiProjectSelectorBase.h"
+#include "kexiprojectset.h"
+
+#include <kdialogbase.h>
+#include <qwidgetstack.h>
+
+class KexiNewFileDBWidget;
+class KexiProjectSelectorWidgetPrivate;
+
+/*! Widget that allows to select a kexi project (or database)
+*/
+class KEXIMAIN_EXPORT KexiProjectSelectorWidget : public KexiProjectSelectorBase
+{
+ Q_OBJECT
+
+public:
+// enum ConnType { FileBased=1, ServerBased=2 };
+
+ /*! Constructs a project selector widget.
+ If \a showProjectNameColumn is true (the default)
+ project names' column is visible. If \a showConnectionColumns is true (the default)
+ information about database driver and connection columns are added.
+ \a prj_set may be NULL - you can assign a set later with setProjectSet().
+ */
+ KexiProjectSelectorWidget( QWidget* parent = 0, const char* name = 0,
+ KexiProjectSet* prj_set = 0, bool showProjectNameColumn = true,
+ bool showConnectionColumns = true );
+
+ ~KexiProjectSelectorWidget();
+
+ /*! \return data of selected project. Returns NULL if no selection has been made.
+ */
+ KexiProjectData* selectedProjectData() const;
+
+ /*! Assigns a new project set \a prj_set. Old project set is not destoyed
+ - it is just left unassigned.
+ If new project set is in error state (Object::error() == true), nothing is displayed. */
+ void setProjectSet( KexiProjectSet* prj_set );
+
+ /*! \return currently assigned project set or NULL if no project set is assigned. */
+ inline KexiProjectSet *projectSet() { return m_prj_set; }
+
+ /*! Sets selectable state on or off. In this state one project item can be selected
+ and executed by mouse double clicking or return key pressing.
+ The property is true by default. */
+ void setSelectable(bool set);
+
+ /*! \return if a witget has selectable state set. */
+ bool isSelectable() const;
+
+public slots:
+
+signals:
+ void projectExecuted(KexiProjectData*);
+ void selectionChanged(KexiProjectData*);
+
+protected slots:
+ void slotItemExecuted(QListViewItem*);
+ void slotItemSelected();
+ virtual void languageChange() { KexiProjectSelectorBase::languageChange(); }
+
+protected:
+ KexiProjectSet *m_prj_set;
+
+ KexiProjectSelectorWidgetPrivate *d;
+
+ friend class ProjectDataLVItem;
+};
+
+/*! Dialog container for KexiProjectSelectorWidget */
+class KexiProjectSelectorDialog : public KDialogBase
+{
+ Q_OBJECT
+public:
+ /*! Constructor 1, used for displaying recent projects list
+ Label "there are recently opened projects" is displayed automatically
+ */
+ KexiProjectSelectorDialog( QWidget *parent, const char *name,
+ KexiProjectSet* prj_set,
+ bool showProjectNameColumn = true, bool showConnectionColumns = true);
+
+ /*! Constructor 2, used for displaying projects list for given connection
+ Label "Select one of these existing projects on server" is displayed automatically
+ You should test if project set was properly loaded using projectSet()->error().
+ */
+ KexiProjectSelectorDialog( QWidget *parent, const char *name,
+ KexiDB::ConnectionData* cdata,
+ bool showProjectNameColumn = true, bool showConnectionColumns = true);
+
+ ~KexiProjectSelectorDialog();
+
+ /*! \return data of selected project. Returns NULL if no selection has been made.
+ */
+ KexiProjectData* selectedProjectData() const;
+
+ /*! \return currently assigned project set or NULL if no project set is assigned. */
+ inline KexiProjectSet *projectSet() { return m_sel->projectSet(); }
+
+ virtual void show();
+
+protected slots:
+ void slotProjectExecuted(KexiProjectData*);
+ void slotProjectSelectionChanged(KexiProjectData*);
+
+protected:
+ void init(KexiProjectSet* prj_set, bool showProjectNameColumn, bool showConnectionColumns);
+
+ KexiProjectSelectorWidget* m_sel;
+};
+
+#endif
+
diff --git a/kexi/main/startup/KexiProjectSelectorBase.ui b/kexi/main/startup/KexiProjectSelectorBase.ui
new file mode 100644
index 000000000..4d51751bd
--- /dev/null
+++ b/kexi/main/startup/KexiProjectSelectorBase.ui
@@ -0,0 +1,128 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiProjectSelectorBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiProjectSelectorBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>405</width>
+ <height>164</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>label</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;b&gt;There are Kexi projects you have recently opened.&lt;/b&gt; Select one you wish to open:
+</string>
+ </property>
+ <property name="alignment">
+ <set>WordBreak|AlignTop</set>
+ </property>
+ </widget>
+ <widget class="QListView">
+ <column>
+ <property name="text">
+ <string>Project Name</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Database</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Type</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Connection</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>list</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>60</height>
+ </size>
+ </property>
+ <property name="allColumnsShowFocus">
+ <bool>true</bool>
+ </property>
+ <property name="showSortIndicator">
+ <bool>true</bool>
+ </property>
+ <property name="resizeMode">
+ <enum>LastColumn</enum>
+ </property>
+ </widget>
+ </vbox>
+</widget>
+<tabstops>
+ <tabstop>list</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kexi/main/startup/KexiServerDBNamePage.ui b/kexi/main/startup/KexiServerDBNamePage.ui
new file mode 100644
index 000000000..3ea61dc37
--- /dev/null
+++ b/kexi/main/startup/KexiServerDBNamePage.ui
@@ -0,0 +1,141 @@
+<!DOCTYPE UI><UI version="3.1" stdsetdef="1">
+<class>KexiServerDBNamePage</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiServerDBNamePage</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>477</width>
+ <height>299</height>
+ </rect>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Project caption: </string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>Project's database name: </string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="0" column="1">
+ <property name="name">
+ <cstring>le_caption</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="1" column="1">
+ <property name="name">
+ <cstring>le_dbname</cstring>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ <spacer row="0" column="2">
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>70</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <spacer row="1" column="2">
+ <property name="name">
+ <cstring>spacer1_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Preferred</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>70</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QFrame" row="4" column="0" rowspan="1" colspan="3">
+ <property name="name">
+ <cstring>frm_dblist</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ </widget>
+ <spacer row="2" column="1">
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>12</height>
+ </size>
+ </property>
+ </spacer>
+ </grid>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kexi/main/startup/KexiStartup.cpp b/kexi/main/startup/KexiStartup.cpp
new file mode 100644
index 000000000..19ae8d28a
--- /dev/null
+++ b/kexi/main/startup/KexiStartup.cpp
@@ -0,0 +1,965 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "KexiStartup.h"
+#ifdef Q_WS_WIN
+# include "KexiStartup_p_win.h"
+#else
+# include "KexiStartup_p.h"
+#endif
+
+#include "kexiproject.h"
+#include "kexiprojectdata.h"
+#include "kexiprojectset.h"
+#include "kexiguimsghandler.h"
+
+#include <kexidb/driver.h>
+#include <kexidb/drivermanager.h>
+#include "KexiStartupDialog.h"
+#include "KexiConnSelector.h"
+#include "KexiProjectSelectorBase.h"
+#include "KexiProjectSelector.h"
+#include "KexiNewProjectWizard.h"
+#include <kexidbconnectionwidget.h>
+#include <kexidbshortcutfile.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmimetype.h>
+#include <kmessagebox.h>
+#include <kcmdlineargs.h>
+#include <kdeversion.h>
+#include <kprogress.h>
+#include <ktextedit.h>
+#include <kstaticdeleter.h>
+#include <kuser.h>
+
+#include <unistd.h>
+
+#include <qcstring.h>
+#include <qapplication.h>
+#include <qlayout.h>
+
+namespace Kexi {
+ static KStaticDeleter<KexiStartupHandler> Kexi_startupHandlerDeleter;
+ KexiStartupHandler* _startupHandler = 0;
+
+ KexiStartupHandler& startupHandler()
+ {
+ if (!_startupHandler)
+ Kexi_startupHandlerDeleter.setObject( _startupHandler, new KexiStartupHandler() );
+ return *_startupHandler;
+ }
+}
+
+//---------------------------------
+
+//! @internal
+class KexiStartupHandlerPrivate
+{
+ public:
+ KexiStartupHandlerPrivate()
+ : passwordDialog(0)//, showConnectionDetailsExecuted(false)
+ , shortcutFile(0), connShortcutFile(0), connDialog(0), startupDialog(0)
+ {
+ }
+
+ ~KexiStartupHandlerPrivate()
+ {
+ delete passwordDialog;
+ delete connDialog;
+ delete startupDialog;
+ }
+
+ KexiDBPasswordDialog* passwordDialog;
+// bool showConnectionDetailsExecuted : 1;
+ KexiDBShortcutFile *shortcutFile;
+ KexiDBConnShortcutFile *connShortcutFile;
+ KexiDBConnectionDialog *connDialog;
+ QString shortcutFileGroupKey;
+ KexiStartupDialog *startupDialog;
+};
+
+//---------------------------------
+
+static bool stripQuotes(const QString &item, QString &name)
+{
+ if (item.left(1)=="\"" && item.right(1)=="\"") {
+ name = item.mid(1, item.length()-2);
+ return true;
+ }
+ name = item;
+ return false;
+}
+
+void updateProgressBar(KProgressDialog *pd, char *buffer, int buflen)
+{
+ char *p = buffer;
+ QCString line(80);
+ for (int i=0; i<buflen; i++, p++) {
+ if ((i==0 || buffer[i-1]=='\n') && buffer[i]=='%') {
+ bool ok;
+ int j=0;
+// char *q=++p;
+ ++i;
+ line="";
+ for (;i<buflen && *p>='0' && *p<='9'; j++, i++, p++)
+ line+=QChar(*p);
+ --i; --p;
+ int percent = line.toInt(&ok);
+ if (ok && percent>=0 && percent<=100 && pd->progressBar()->progress()<percent) {
+// kdDebug() << percent << endl;
+ pd->progressBar()->setProgress(percent);
+ qApp->processEvents(100);
+ }
+ }
+ }
+}
+
+//---------------------------------
+
+KexiDBPasswordDialog::KexiDBPasswordDialog(QWidget *parent, KexiDB::ConnectionData& cdata, bool showDetailsButton)
+ : KPasswordDialog( KPasswordDialog::Password, false/*keep*/,
+ showDetailsButton ? (int)KDialogBase::User1 : 0, parent )
+ , m_cdata(&cdata)
+ , m_showConnectionDetailsRequested(false)
+{
+ QString msg = "<H2>" + i18n("Opening database") + "</H2><p>"
+ + i18n("Please enter the password.") + "</p>";
+/* msg += cdata.userName.isEmpty() ?
+ "<p>"+i18n("Please enter the password.")
+ : "<p>"+i18n("Please enter the password for user.").arg("<b>"+cdata.userName+"</b>");*/
+
+ QString srv = cdata.serverInfoString(false);
+ if (srv.isEmpty() || srv.lower()=="localhost")
+ srv = i18n("local database server");
+
+ msg += ("</p><p>"+i18n("Database server: %1").arg(QString("<nobr>")+srv+"</nobr>")+"</p>");
+
+ QString usr;
+ if (cdata.userName.isEmpty())
+ usr = i18n("unspecified user", "(unspecified)");
+ else
+ usr = cdata.userName;
+
+ msg += ("<p>"+i18n("Username: %1").arg(usr)+"</p>");
+
+ setPrompt( msg );
+ if (showDetailsButton) {
+ connect( this, SIGNAL(user1Clicked()),
+ this, SLOT(slotShowConnectionDetails()) );
+ setButtonText(KDialogBase::User1, i18n("&Details")+ " >>");
+ }
+ setButtonOK(KGuiItem(i18n("&Open"), "fileopen"));
+}
+
+KexiDBPasswordDialog::~KexiDBPasswordDialog()
+{
+}
+
+void KexiDBPasswordDialog::done(int r)
+{
+ if (r == QDialog::Accepted) {
+ m_cdata->password = QString::fromLatin1(password());
+ }
+// if (d->showConnectionDetailsExecuted || ret == QDialog::Accepted) {
+/* } else {
+ m_action = Exit;
+ return true;
+ }
+ }*/
+ KPasswordDialog::done(r);
+}
+
+void KexiDBPasswordDialog::slotShowConnectionDetails()
+{
+ m_showConnectionDetailsRequested = true;
+ close();
+}
+
+//---------------------------------
+KexiStartupHandler::KexiStartupHandler()
+ : QObject(0,"KexiStartupHandler")
+ , KexiStartupData()
+ , d( new KexiStartupHandlerPrivate() )
+{
+}
+
+KexiStartupHandler::~KexiStartupHandler()
+{
+ delete d;
+}
+
+bool KexiStartupHandler::getAutoopenObjects(KCmdLineArgs *args, const QCString &action_name)
+{
+ QCStringList list = args->getOptionList(action_name);
+ QCStringList::ConstIterator it;
+ bool atLeastOneFound = false;
+ for ( it = list.constBegin(); it!=list.constEnd(); ++it) {
+ QString type_name, obj_name, item=*it;
+ int idx;
+ bool name_required = true;
+ if (action_name=="new") {
+ obj_name = "";
+ stripQuotes(item, type_name);
+ name_required = false;
+ }
+ else {//open, design, text...
+ QString defaultType;
+ if (action_name=="execute")
+ defaultType = "macro";
+ else
+ defaultType = "table";
+
+ //option with " " (set default type)
+ if (stripQuotes(item, obj_name)) {
+ type_name = defaultType;
+ }
+ else if ((idx = item.find(':'))!=-1) {
+ //option with type name specified:
+ type_name = item.left(idx).lower();
+ obj_name = item.mid(idx+1);
+ //optional: remove ""
+ if (obj_name.left(1)=="\"" && obj_name.right(1)=="\"")
+ obj_name = obj_name.mid(1, obj_name.length()-2);
+ }
+ else {
+ //just obj. name: set default type name
+ obj_name = item;
+ type_name = defaultType;
+ }
+ }
+ if (type_name.isEmpty())
+ continue;
+ if (name_required && obj_name.isEmpty())
+ continue;
+
+ KexiProjectData::ObjectInfo info;
+ info["name"]=obj_name;
+ info["type"]=type_name;
+ info["action"]=action_name;
+ //ok, now add info for this object
+ atLeastOneFound = true;
+ if (projectData())
+ projectData()->autoopenObjects.append( info );
+ else
+ return true; //no need to find more because we do not have projectData() anyway
+ } //for
+ return atLeastOneFound;
+}
+
+tristate KexiStartupHandler::init(int /*argc*/, char ** /*argv*/)
+{
+ m_action = DoNothing;
+// d->showConnectionDetailsExecuted = false;
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs(0);
+ if (!args)
+ return true;
+
+ KexiDB::ConnectionData cdata;
+
+ const QString connectionShortcutFileName( args->getOption("connection") );
+ if (!connectionShortcutFileName.isEmpty()) {
+ KexiDBConnShortcutFile connectionShortcut( connectionShortcutFileName );
+ if (!connectionShortcut.loadConnectionData(cdata)) {
+//! @todo Show error message from KexiDBConnShortcutFile when there's one implemented.
+//! For we're displaying generic error msg.
+ KMessageBox::sorry( 0, "<qt>"
+ +i18n("Could not read connection information from connection shortcut "
+ "file <nobr>\"%1\"</nobr>.<br><br>Check whether the file has valid contents.")
+ .arg(QDir::convertSeparators(connectionShortcut.fileName())));
+ return false;
+ }
+ }
+
+ if (!args->getOption("dbdriver").isEmpty())
+ cdata.driverName = args->getOption("dbdriver");
+
+ QString fileType( args->getOption("type").lower() );
+ if (args->count()>0 && (!fileType.isEmpty() && fileType!="project" && fileType!="shortcut" && fileType!="connection")) {
+ KMessageBox::sorry( 0,
+ i18n("You have specified invalid argument (\"%1\") for \"type\" command-line option.")
+ .arg(fileType));
+ return false;
+ }
+
+// if (cdata.driverName.isEmpty())
+// cdata.driverName = KexiDB::Driver::defaultFileBasedDriverName();
+ if (!args->getOption("host").isEmpty())
+ cdata.hostName = args->getOption("host");
+ if (!args->getOption("local-socket").isEmpty())
+ cdata.localSocketFileName = args->getOption("local-socket");
+ if (!args->getOption("user").isEmpty())
+ cdata.userName = args->getOption("user");
+// cdata.password = args->getOption("password");
+ bool fileDriverSelected;
+ if (cdata.driverName.isEmpty())
+ fileDriverSelected = true;
+ else {
+ KexiDB::DriverManager dm;
+ KexiDB::Driver::Info dinfo = dm.driverInfo(cdata.driverName);
+ if (dinfo.name.isEmpty()) {
+ //driver name provided explicitly, but not found
+ KMessageBox::sorry(0, dm.errorMsg());
+ return false;
+ }
+ fileDriverSelected = dinfo.fileBased;
+ }
+ bool projectFileExists = false;
+
+ //obfuscate the password, if present
+//removed
+/*
+ for (int i=1; i<(argc-1); i++) {
+ if (qstrcmp("--password",argv[i])==0
+ || qstrcmp("-password",argv[i])==0)
+ {
+ QCString pwd(argv[i+1]);
+ if (!pwd.isEmpty()) {
+ pwd.fill(' ');
+ pwd[0]='x';
+ qstrcpy(argv[i+1], (const char*)pwd);
+ }
+ break;
+ }
+ }
+ */
+
+ const QString portStr = args->getOption("port");
+ if (!portStr.isEmpty()) {
+ bool ok;
+ const int p = portStr.toInt(&ok);
+ if (ok && p > 0)
+ cdata.port = p;
+ else {
+ KMessageBox::sorry( 0,
+ i18n("You have specified invalid port number \"%1\"."));
+ return false;
+ }
+ }
+
+ m_forcedUserMode = args->isSet("user-mode");
+ m_forcedDesignMode = args->isSet("design-mode");
+ m_isProjectNavigatorVisible = args->isSet("show-navigator");
+ bool createDB = args->isSet("createdb");
+ const bool alsoOpenDB = args->isSet("create-opendb");
+ if (alsoOpenDB)
+ createDB = true;
+ const bool dropDB = args->isSet("dropdb");
+ const bool openExisting = !createDB && !dropDB;
+ const bool readOnly = args->isSet("readonly");
+ const QString couldnotMsg = QString::fromLatin1("\n")
+ +i18n("Could not start Kexi application this way.");
+
+ if (createDB && dropDB) {
+ KMessageBox::sorry( 0, i18n(
+ "You have used both \"createdb\" and \"dropdb\" startup options.")+couldnotMsg);
+ return false;
+ };
+
+ if (createDB || dropDB) {
+ if (args->count()<1) {
+ KMessageBox::sorry( 0, i18n("No project name specified.") );
+ return false;
+ }
+ m_action = Exit;
+ }
+
+//TODO: add option for non-gui; integrate with KWallet;
+// move to static KexiProject method
+ if (!fileDriverSelected && !cdata.driverName.isEmpty() && cdata.password.isEmpty()) {
+
+ if (cdata.password.isEmpty()) {
+ delete d->passwordDialog;
+ d->passwordDialog = new KexiDBPasswordDialog(0, cdata, true);
+// connect( d->passwordDialog, SIGNAL(user1Clicked()),
+// this, SLOT(slotShowConnectionDetails()) );
+ const int ret = d->passwordDialog->exec();
+ if (d->passwordDialog->showConnectionDetailsRequested() || ret == QDialog::Accepted) {
+// if ( ret == QDialog::Accepted ) {
+ // if (QDialog::Accepted == KPasswordDialog::getPassword(pwd, msg)) {
+//moved cdata.password = QString(pwd);
+// }
+ } else {
+ m_action = Exit;
+ return true;
+ }
+ }
+ }
+
+/* kdDebug() << "ARGC==" << args->count() << endl;
+ for (int i=0;i<args->count();i++) {
+ kdDebug() << "ARG" <<i<< "= " << args->arg(i) <<endl;
+ }*/
+
+ if (m_forcedUserMode && m_forcedDesignMode) {
+ KMessageBox::sorry( 0, i18n(
+ "You have used both \"user-mode\" and \"design-mode\" startup options.")+couldnotMsg);
+ return false;
+ }
+
+ //database filenames, shortcut filenames or db names on a server
+ if (args->count()>=1) {
+ QString prjName;
+ QString fileName;
+ if (fileDriverSelected) {
+ fileName = QFile::decodeName(args->arg(0));
+ }
+ else {
+ prjName = QString::fromLocal8Bit(args->arg(0));
+ }
+
+ if (fileDriverSelected) {
+ QFileInfo finfo(fileName);
+ prjName = finfo.fileName(); //filename only, to avoid messy names like when Kexi is started with "../../db" arg
+ cdata.setFileName( finfo.absFilePath() );
+ projectFileExists = finfo.exists();
+
+ if (dropDB && !projectFileExists) {
+ KMessageBox::sorry(0,
+ i18n("Could not remove project.\nThe file \"%1\" does not exist.")
+ .arg(QDir::convertSeparators(cdata.dbFileName())));
+ return 0;
+ }
+ }
+
+ if (createDB) {
+ if (cdata.driverName.isEmpty())
+ cdata.driverName = KexiDB::Driver::defaultFileBasedDriverName();
+ m_projectData = new KexiProjectData(cdata, prjName); //dummy
+ }
+ else {
+ if (fileDriverSelected) {
+ int detectOptions = 0;
+ if (fileType=="project")
+ detectOptions |= ThisIsAProjectFile;
+ else if (fileType=="shortcut")
+ detectOptions |= ThisIsAShortcutToAProjectFile;
+ else if (fileType=="connection")
+ detectOptions |= ThisIsAShortcutToAConnectionData;
+
+ if (dropDB)
+ detectOptions |= DontConvert;
+
+ QString detectedDriverName;
+ const tristate res = detectActionForFile( m_importActionData, detectedDriverName,
+ cdata.driverName, cdata.fileName(), 0, detectOptions );
+ if (true != res)
+ return res;
+
+ if (m_importActionData) { //importing action
+ m_action = ImportProject;
+ return true;
+ }
+
+ //opening action
+ cdata.driverName = detectedDriverName;
+ if (cdata.driverName=="shortcut") {
+ //get information for a shortcut file
+ d->shortcutFile = new KexiDBShortcutFile(cdata.fileName());
+ m_projectData = new KexiProjectData();
+ if (!d->shortcutFile->loadProjectData(*m_projectData, &d->shortcutFileGroupKey)) {
+ KMessageBox::sorry(0, i18n("Could not open shortcut file\n\"%1\".")
+ .arg(QDir::convertSeparators(cdata.fileName())));
+ delete m_projectData;
+ m_projectData = 0;
+ delete d->shortcutFile;
+ d->shortcutFile = 0;
+ return false;
+ }
+ d->connDialog = new KexiDBConnectionDialog(
+ *m_projectData, d->shortcutFile->fileName());
+ connect(d->connDialog, SIGNAL(saveChanges()),
+ this, SLOT(slotSaveShortcutFileChanges()));
+ int res = d->connDialog->exec();
+ if (res == QDialog::Accepted) {
+ //get (possibly changed) prj data
+ *m_projectData = d->connDialog->currentProjectData();
+ }
+
+ delete d->connDialog;
+ d->connDialog = 0;
+ delete d->shortcutFile;
+ d->shortcutFile = 0;
+
+ if (res == QDialog::Rejected) {
+ delete m_projectData;
+ m_projectData = 0;
+ return cancelled;
+ }
+ }
+ else if (cdata.driverName=="connection") {
+ //get information for a connection file
+ d->connShortcutFile = new KexiDBConnShortcutFile(cdata.fileName());
+ if (!d->connShortcutFile->loadConnectionData(cdata, &d->shortcutFileGroupKey)) {
+ KMessageBox::sorry(0, i18n("Could not open connection data file\n\"%1\".")
+ .arg(QDir::convertSeparators(cdata.fileName())));
+ delete d->connShortcutFile;
+ d->connShortcutFile = 0;
+ return false;
+ }
+ bool cancel = false;
+ const bool showConnectionDialog = !args->isSet("skip-conn-dialog");
+ while (true) {
+ if (showConnectionDialog) {
+ //show connection dialog, so user can change parameters
+ if (!d->connDialog) {
+ d->connDialog = new KexiDBConnectionDialog(
+ cdata, d->connShortcutFile->fileName());
+ connect(d->connDialog, SIGNAL(saveChanges()),
+ this, SLOT(slotSaveShortcutFileChanges()));
+ }
+ const int res = d->connDialog->exec();
+ if (res == QDialog::Accepted) {
+ //get (possibly changed) prj data
+ cdata = *d->connDialog->currentProjectData().constConnectionData();
+ }
+ else {
+ cancel = true;
+ break;
+ }
+ }
+ m_projectData = selectProject(&cdata, cancel);
+ if (m_projectData || cancel || !showConnectionDialog)
+ break;
+ }
+
+ delete d->connShortcutFile;
+ d->connShortcutFile = 0;
+ delete d->connDialog;
+ d->connDialog = 0;
+
+ if (cancel)
+ return cancelled;
+ }
+ else
+ m_projectData = new KexiProjectData(cdata, prjName);
+ }
+ else
+ m_projectData = new KexiProjectData(cdata, prjName);
+
+ }
+// if (!m_projectData)
+// return false;
+ }
+ if (args->count()>1) {
+ //TODO: KRun another Kexi instances
+ }
+
+ //let's show connection details, user asked for that in the "password dialog"
+ if (d->passwordDialog && d->passwordDialog->showConnectionDetailsRequested()) {
+ d->connDialog = new KexiDBConnectionDialog(*m_projectData);
+// connect(d->connDialog->tabWidget->mainWidget, SIGNAL(saveChanges()),
+// this, SLOT(slotSaveShortcutFileChanges()));
+ int res = d->connDialog->exec();
+
+ if (res == QDialog::Accepted) {
+ //get (possibly changed) prj data
+ *m_projectData = d->connDialog->currentProjectData();
+ }
+
+ delete d->connDialog;
+ d->connDialog = 0;
+
+ if (res == QDialog::Rejected) {
+ delete m_projectData;
+ m_projectData = 0;
+ return cancelled;
+ }
+ }
+
+ //---autoopen objects:
+ const bool atLeastOneAOOFound = getAutoopenObjects(args, "open")
+ || getAutoopenObjects(args, "design")
+ || getAutoopenObjects(args, "edittext")
+ || getAutoopenObjects(args, "execute")
+ || getAutoopenObjects(args, "new")
+ || getAutoopenObjects(args, "print")
+ || getAutoopenObjects(args, "print-preview");
+
+ if (atLeastOneAOOFound && !openExisting) {
+ KMessageBox::information( 0,
+ i18n("You have specified a few database objects to be opened automatically, "
+ "using startup options.\n"
+ "These options will be ignored because it is not available while creating "
+ "or dropping projects."));
+ }
+
+ if (createDB) {
+ bool creationNancelled;
+ KexiGUIMessageHandler gui;
+ KexiProject *prj = KexiProject::createBlankProject(creationNancelled, projectData(), &gui);
+ bool ok = prj!=0;
+ delete prj;
+ if (creationNancelled)
+ return cancelled;
+ if (!alsoOpenDB) {
+ if (ok) {
+ KMessageBox::information( 0, i18n("Project \"%1\" created successfully.")
+ .arg( QDir::convertSeparators(projectData()->databaseName()) ));
+ }
+ return ok;
+ }
+ }
+ else if (dropDB) {
+ KexiGUIMessageHandler gui;
+ tristate res = KexiProject::dropProject(projectData(), &gui, false/*ask*/);
+ if (res==true)
+ KMessageBox::information( 0, i18n("Project \"%1\" dropped successfully.")
+ .arg( QDir::convertSeparators(projectData()->databaseName()) ));
+ return res!=false;
+ }
+
+ //------
+
+/* if (m_forcedFinalMode || (m_projectData && projectData->finalMode())) {
+ //TODO: maybe also auto allow to open objects...
+ KexiMainWindowImpl::initFinal(m_projectData);
+ return;
+ }*/
+
+ if (!m_projectData) {
+ cdata = KexiDB::ConnectionData(); //clear
+
+ if (args->isSet("skip-startup-dialog") || !KexiStartupDialog::shouldBeShown())
+ return true;
+
+ if (!d->startupDialog) {
+ //create d->startupDialog for reuse because it can be used again after conn err.
+ d->startupDialog = new KexiStartupDialog(
+ KexiStartupDialog::Everything, KexiStartupDialog::CheckBoxDoNotShowAgain,
+ Kexi::connset(), Kexi::recentProjects(), 0, "KexiStartupDialog");
+ }
+ if (d->startupDialog->exec()!=QDialog::Accepted)
+ return true;
+
+ const int r = d->startupDialog->result();
+ if (r == KexiStartupDialog::CreateBlankResult) {
+ m_action = CreateBlankProject;
+ return true;
+ }
+ else if (r == KexiStartupDialog::ImportResult) {
+ m_action = ImportProject;
+ return true;
+ }
+ else if (r == KexiStartupDialog::CreateFromTemplateResult) {
+ const QString selFile( d->startupDialog->selectedFileName() );
+ cdata.setFileName( selFile );
+ QString detectedDriverName;
+ const tristate res = detectActionForFile( m_importActionData, detectedDriverName,
+ cdata.driverName, selFile );
+ if (true != res)
+ return res;
+ if (m_importActionData || detectedDriverName.isEmpty())
+ return false;
+ cdata.driverName = detectedDriverName;
+ m_projectData = new KexiProjectData(cdata, selFile);
+ m_projectData->autoopenObjects = d->startupDialog->autoopenObjects();
+ m_action = CreateFromTemplate;
+ return true;
+ }
+ else if (r == KexiStartupDialog::OpenExistingResult) {
+// kdDebug() << "Existing project --------" << endl;
+ const QString selFile( d->startupDialog->selectedFileName() );
+ if (!selFile.isEmpty()) {
+ //file-based project
+// kdDebug() << "Project File: " << selFile << endl;
+ cdata.setFileName( selFile );
+ QString detectedDriverName;
+ const tristate res = detectActionForFile( m_importActionData, detectedDriverName,
+ cdata.driverName, selFile );
+ if (true != res)
+ return res;
+ if (m_importActionData) { //importing action
+ m_action = ImportProject;
+ return true;
+ }
+
+ if (detectedDriverName.isEmpty())
+ return false;
+ cdata.driverName = detectedDriverName;
+ m_projectData = new KexiProjectData(cdata, selFile);
+ }
+ else if (d->startupDialog->selectedExistingConnection()) {
+// kdDebug() << "Existing connection: " <<
+// d->startupDialog->selectedExistingConnection()->serverInfoString() << endl;
+ KexiDB::ConnectionData *cdata = d->startupDialog->selectedExistingConnection();
+ //ok, now we will try to show projects for this connection to the user
+ bool cancelled;
+ m_projectData = selectProject( cdata, cancelled );
+ if (!m_projectData && !cancelled || cancelled) {
+ //try again
+ return init(0, 0);
+ }
+ //not needed anymore
+ delete d->startupDialog;
+ d->startupDialog = 0;
+ }
+ }
+ else if (r==KexiStartupDialog::OpenRecentResult) {
+// kdDebug() << "Recent project --------" << endl;
+ const KexiProjectData *data = d->startupDialog->selectedProjectData();
+ if (data) {
+// kdDebug() << "Selected project: database=" << data->databaseName()
+// << " connection=" << data->constConnectionData()->serverInfoString() << endl;
+ }
+//! @todo
+ return data!=0;
+ }
+
+ if (!m_projectData)
+ return true;
+ }
+
+ if (m_projectData && (openExisting || (createDB && alsoOpenDB))) {
+ m_projectData->setReadOnly( readOnly );
+ m_action = OpenProject;
+ }
+ //show if wasn't show yet
+// importantInfo(true);
+
+ return true;
+}
+
+tristate KexiStartupHandler::detectActionForFile(
+ KexiStartupData::Import& detectedImportAction, QString& detectedDriverName,
+ const QString& _suggestedDriverName, const QString &dbFileName, QWidget *parent, int options )
+{
+ detectedImportAction = KexiStartupData::Import(); //clear
+ QString suggestedDriverName(_suggestedDriverName); //safe
+ detectedDriverName = QString::null;
+ QFileInfo finfo(dbFileName);
+ if (dbFileName.isEmpty() || !finfo.isReadable()) {
+ if (!(options & SkipMessages))
+ KMessageBox::sorry(parent, i18n("<p>Could not open project.</p>")
+ +i18n("<p>The file <nobr>\"%1\"</nobr> does not exist or is not readable.</p>")
+ .arg(QDir::convertSeparators(dbFileName))
+ +i18n("Check the file's permissions and whether it is already opened "
+ "and locked by another application."));
+ return false;
+ }
+
+ KMimeType::Ptr ptr;
+ QString mimename;
+
+ const bool thisIsShortcut = (options & ThisIsAShortcutToAProjectFile)
+ || (options & ThisIsAShortcutToAConnectionData);
+
+ if ((options & ThisIsAProjectFile) || !thisIsShortcut) {
+ //try this detection if "project file" mode is forced or no type is forced:
+ ptr = KMimeType::findByFileContent(dbFileName);
+ mimename = ptr.data()->name();
+ kdDebug() << "KexiStartupHandler::detectActionForFile(): found mime is: "
+ << mimename << endl;
+ if (mimename.isEmpty() || mimename=="application/octet-stream" || mimename=="text/plain") {
+ //try by URL:
+ ptr = KMimeType::findByURL(dbFileName);
+ mimename = ptr.data()->name();
+ }
+ }
+ if (mimename.isEmpty() || mimename=="application/octet-stream") {
+ // perhaps the file is locked
+ QFile f(dbFileName);
+ if (!f.open(IO_ReadOnly)) {
+ // BTW: similar error msg is provided in SQLiteConnection::drv_useDatabase()
+ if (!(options & SkipMessages))
+ KMessageBox::sorry(parent, i18n("<p>Could not open project.</p>")
+ +i18n("<p>The file <nobr>\"%1\"</nobr> is not readable.</p>")
+ .arg(QDir::convertSeparators(dbFileName))
+ +i18n("Check the file's permissions and whether it is already opened "
+ "and locked by another application."));
+ return false;
+ }
+ }
+ if ((options & ThisIsAShortcutToAProjectFile) || mimename=="application/x-kexiproject-shortcut") {
+ detectedDriverName = "shortcut";
+ return true;
+ }
+
+ if ((options & ThisIsAShortcutToAConnectionData) || mimename=="application/x-kexi-connectiondata") {
+ detectedDriverName = "connection";
+ return true;
+ }
+
+ //! @todo rather check this using migration drivers'
+ //! X-KexiSupportedMimeTypes [strlist] property
+ if (ptr.data()) {
+ if (mimename=="application/x-msaccess") {
+ if ((options & SkipMessages) || KMessageBox::Yes != KMessageBox::questionYesNo(
+ parent, i18n("\"%1\" is an external file of type:\n\"%2\".\n"
+ "Do you want to import the file as a Kexi project?")
+ .arg(QDir::convertSeparators(dbFileName)).arg(ptr.data()->comment()),
+ i18n("Open External File"), KGuiItem(i18n("Import...")), KStdGuiItem::cancel() ) )
+ {
+ return cancelled;
+ }
+ detectedImportAction.mimeType = mimename;
+ detectedImportAction.fileName = dbFileName;
+ return true;
+ }
+ }
+
+ if (!finfo.isWritable()) {
+ //! @todo if file is ro: change project mode (but do not care if we're jsut importing)
+ }
+
+ // "application/x-kexiproject-sqlite", etc.:
+ QString tmpDriverName = Kexi::driverManager().lookupByMime(mimename).latin1();
+//@todo What about trying to reuse KOFFICE FILTER CHAINS here?
+ bool useDetectedDriver = suggestedDriverName.isEmpty() || suggestedDriverName.lower()==detectedDriverName.lower();
+ if (!useDetectedDriver) {
+ int res = KMessageBox::Yes;
+ if (!(options & SkipMessages))
+ res = KMessageBox::warningYesNoCancel(parent, i18n(
+ "The project file \"%1\" is recognized as compatible with \"%2\" database driver, "
+ "while you have asked for \"%3\" database driver to be used.\n"
+ "Do you want to use \"%4\" database driver?")
+ .arg(QDir::convertSeparators(dbFileName))
+ .arg(tmpDriverName).arg(suggestedDriverName).arg(tmpDriverName));
+ if (KMessageBox::Yes == res)
+ useDetectedDriver = true;
+ else if (KMessageBox::Cancel == res)
+ return cancelled;
+ }
+ if (useDetectedDriver) {
+ detectedDriverName = tmpDriverName;
+ }
+ else {//use suggested driver
+ detectedDriverName = suggestedDriverName;
+ }
+// kdDebug() << "KexiStartupHandler::detectActionForFile(): driver name: " << detectedDriverName << endl;
+//hardcoded for convenience:
+ const QString newFileFormat = "SQLite3";
+ if (!(options & DontConvert || options & SkipMessages)
+ && detectedDriverName.lower()=="sqlite2" && detectedDriverName.lower()!=suggestedDriverName.lower()
+ && KMessageBox::Yes == KMessageBox::questionYesNo(parent, i18n(
+ "Previous version of database file format (\"%1\") is detected in the \"%2\" "
+ "project file.\nDo you want to convert the project to a new \"%3\" format (recommended)?")
+ .arg(detectedDriverName).arg(QDir::convertSeparators(dbFileName)).arg(newFileFormat)) )
+ {
+// SQLite2ToSQLite3Migration *migr = new
+ SQLite2ToSQLite3Migration migr( finfo.absFilePath() );
+ tristate res = migr.run();
+// kdDebug() << "--- migr.run() END ---" <<endl;
+ if (!res) {
+ //TODO msg
+ KMessageBox::sorry(parent, i18n(
+ "Failed to convert project file \"%1\" to a new \"%2\" format.\n"
+ "The file format remains unchanged.")
+ .arg(QDir::convertSeparators(dbFileName)).arg(newFileFormat) );
+ //continue...
+ }
+ if (res==true)
+ detectedDriverName = newFileFormat;
+ }
+// action.driverName = detectedDriverName;
+ if (detectedDriverName.isEmpty()) {
+ QString possibleProblemsInfoMsg( Kexi::driverManager().possibleProblemsInfoMsg() );
+ if (!possibleProblemsInfoMsg.isEmpty()) {
+ possibleProblemsInfoMsg.prepend(QString::fromLatin1("<p>")+i18n("Possible problems:"));
+ possibleProblemsInfoMsg += QString::fromLatin1("</p>");
+ }
+ if (!(options & SkipMessages))
+ KMessageBox::detailedSorry(parent,
+ i18n( "The file \"%1\" is not recognized as being supported by Kexi.")
+ .arg(QDir::convertSeparators(dbFileName)),
+ QString::fromLatin1("<p>")
+ +i18n("Database driver for this file type not found.\nDetected MIME type: %1")
+ .arg(mimename)
+ +(ptr.data()->comment().isEmpty()
+ ? QString::fromLatin1(".") : QString::fromLatin1(" (%1).").arg(ptr.data()->comment()))
+ +QString::fromLatin1("</p>")
+ +possibleProblemsInfoMsg);
+ return false;
+ }
+ return true;
+}
+
+KexiProjectData*
+KexiStartupHandler::selectProject(KexiDB::ConnectionData *cdata, bool& cancelled, QWidget *parent)
+{
+ clearStatus();
+ cancelled = false;
+ if (!cdata)
+ return 0;
+ if (!cdata->savePassword && cdata->password.isEmpty()) {
+ if (!d->passwordDialog)
+ d->passwordDialog = new KexiDBPasswordDialog(0, *cdata, false);
+ const int ret = d->passwordDialog->exec();
+ if (d->passwordDialog->showConnectionDetailsRequested() || ret == QDialog::Accepted) {
+
+ } else {
+ cancelled = true;
+ return 0;
+ }
+ }
+ KexiProjectData* projectData = 0;
+ //dialog for selecting a project
+ KexiProjectSelectorDialog prjdlg( parent, "prjdlg", cdata, true, false );
+ if (!prjdlg.projectSet() || prjdlg.projectSet()->error()) {
+ KexiGUIMessageHandler msgh;
+ if (prjdlg.projectSet())
+ msgh.showErrorMessage(prjdlg.projectSet(),
+ i18n("Could not load list of available projects for <b>%1</b> database server.")
+ .arg(cdata->serverInfoString(true)));
+ else
+ msgh.showErrorMessage(
+ i18n("Could not load list of available projects for <b>%1</b> database server.")
+ .arg(cdata->serverInfoString(true)));
+// setStatus(i18n("Could not load list of available projects for database server \"%1\"")
+// .arg(cdata->serverInfoString(true)), prjdlg.projectSet()->errorMsg());
+ return 0;
+ }
+ if (prjdlg.exec()!=QDialog::Accepted) {
+ cancelled = true;
+ return 0;
+ }
+ if (prjdlg.selectedProjectData()) {
+ //deep copy
+ projectData = new KexiProjectData(*prjdlg.selectedProjectData());
+ }
+ return projectData;
+}
+
+void KexiStartupHandler::slotSaveShortcutFileChanges()
+{
+ bool ok = true;
+ if (d->shortcutFile)
+ ok = d->shortcutFile->saveProjectData(d->connDialog->currentProjectData(),
+ d->connDialog->savePasswordOptionSelected(),
+ &d->shortcutFileGroupKey );
+ else if (d->connShortcutFile)
+ ok = d->connShortcutFile->saveConnectionData(
+ *d->connDialog->currentProjectData().connectionData(),
+ d->connDialog->savePasswordOptionSelected(),
+ &d->shortcutFileGroupKey );
+
+ if (!ok) {
+ KMessageBox::sorry(0, i18n("Failed saving connection data to\n\"%1\" file.")
+ .arg(QDir::convertSeparators(d->shortcutFile->fileName())));
+ }
+}
+
+/*void KexiStartupHandler::slotShowConnectionDetails()
+{
+ d->passwordDialog->close();
+ d->showConnectionDetailsExecuted = true;
+}*/
+
+#include "KexiStartup.moc"
diff --git a/kexi/main/startup/KexiStartup.h b/kexi/main/startup/KexiStartup.h
new file mode 100644
index 000000000..294ad7dd0
--- /dev/null
+++ b/kexi/main/startup/KexiStartup.h
@@ -0,0 +1,136 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_STARTUPHANDLER_H
+#define KEXI_STARTUPHANDLER_H
+
+#include <qstring.h>
+#include <qwidget.h>
+#include <kpassdlg.h>
+
+#include <core/kexistartupdata.h>
+#include <core/kexi.h>
+#include <kexiutils/tristate.h>
+
+class KexiProjectData;
+class KexiProjectData;
+class KCmdLineArgs;
+class KexiStartupHandlerPrivate;
+namespace KexiDB {
+ class ConnectionData;
+}
+
+/*! */
+class KEXIMAIN_EXPORT KexiDBPasswordDialog : public KPasswordDialog
+{
+ Q_OBJECT
+ public:
+ KexiDBPasswordDialog(QWidget *parent, KexiDB::ConnectionData& cdata, bool showDetailsButton = false);
+ virtual ~KexiDBPasswordDialog();
+
+ bool showConnectionDetailsRequested() const { return m_showConnectionDetailsRequested; }
+
+ protected slots:
+ virtual void done(int r);
+ void slotShowConnectionDetails();
+
+ protected:
+ KexiDB::ConnectionData *m_cdata;
+ bool m_showConnectionDetailsRequested : 1;
+};
+
+/*! Handles startup actions for Kexi application.
+*/
+class KEXIMAIN_EXPORT KexiStartupHandler
+ : public QObject, public KexiStartupData, public Kexi::ObjectStatus
+{
+ Q_OBJECT
+
+ public:
+ KexiStartupHandler();
+ virtual ~KexiStartupHandler();
+
+ virtual tristate init(int argc, char **argv);
+
+ #if 0
+ /*! Used for opening existing projects.
+ Detects project file type by mime type and returns project data, if it can be detected,
+ otherwise - NULL. \a parent is passed as parent for potential error message boxes.
+ Also uses \a cdata connection data for server-based projects.
+ cdata.driverName is adjusted, if a file-based project has been detected.
+ */
+ static KexiProjectData* detectProjectData(
+ KexiDB::ConnectionData& cdata, const QString &dbname, QWidget *parent);
+ #endif
+
+ /*! Options for detectDriverForFile() */
+ enum DetectDriverForFileOptions {
+ DontConvert = 1, //!< skip asking for conversion (used e.g. when dropdb is called)
+ ThisIsAProjectFile = 2, //!< a hint, forces detection of the file as a project file
+ ThisIsAShortcutToAProjectFile = 4, //!< a hint, forces detection of the file
+ //!< as a shortcut to a project file
+ ThisIsAShortcutToAConnectionData = 8, //!< a hint, forces detection of the file
+ //!< as a shortcut to a connection data
+ SkipMessages = 16 //!< do not display error or warning messages
+ };
+
+ /*! Used for opening existing file-based projects.
+ Detects actions that should be performed for by looking at the file's mime type.
+ \return true if actions should be performed or cancelled if action should be cancelled
+ In this case there are two possibilities:
+ - \a detectedImportAction == true means "import action" should be performed
+ - nonempty \a detectedDriverName means "open action" should be performed.
+
+ \a detectedDriverName can contain following special strings:
+ - "shortcut" if the file looks like a shortcut to a project/connection file
+ - "connection" if the file looks like a connection data file.
+
+ \a parent is passed as a parent for potential error message boxes.
+ \a driverName is a preferred driver name.
+ \a options should be a combination of DetectDriverForFileOptions enum values. */
+ static tristate detectActionForFile(
+ KexiStartupData::Import& detectedImportAction, QString& detectedDriverName,
+ const QString& _suggestedDriverName,
+ const QString &dbFileName, QWidget *parent = 0, int options = 0 );
+
+ /*! Allows user to select a project with KexiProjectSelectorDialog.
+ \return selected project's data
+ Returns NULL and sets cancelled to true if the dialog was cancelled.
+ Returns NULL and sets cancelled to false if there was an error.
+ */
+ KexiProjectData* selectProject(KexiDB::ConnectionData *cdata, bool& cancelled, QWidget *parent = 0);
+
+ protected slots:
+ void slotSaveShortcutFileChanges();
+// void slotShowConnectionDetails();
+
+ protected:
+ bool getAutoopenObjects(KCmdLineArgs *args, const QCString &action_name);
+
+ KexiStartupHandlerPrivate *d;
+};
+
+namespace Kexi
+{
+ //! \return singleton Startup Handler singleton.
+ KEXIMAIN_EXPORT KexiStartupHandler& startupHandler();
+}
+
+#endif
+
diff --git a/kexi/main/startup/KexiStartupDialog.cpp b/kexi/main/startup/KexiStartupDialog.cpp
new file mode 100644
index 000000000..3b524492d
--- /dev/null
+++ b/kexi/main/startup/KexiStartupDialog.cpp
@@ -0,0 +1,699 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "KexiStartupDialog.h"
+#include "KexiStartupDialogTemplatesPage.h"
+#include "kexi.h"
+#include "KexiProjectSelector.h"
+#include "KexiOpenExistingFile.h"
+#include "KexiConnSelector.h"
+#include "KexiConnSelectorBase.h"
+
+#include <qlayout.h>
+#include <qtabwidget.h>
+#include <qcombobox.h>
+#include <qcheckbox.h>
+#include <qpoint.h>
+#include <qobjectlist.h>
+#include <qvgroupbox.h>
+#include <qapplication.h>
+#include <qtooltip.h>
+#include <qwidgetstack.h>
+
+#include <klocale.h>
+#include <kdeversion.h>
+#include <kinstance.h>
+#include <kdebug.h>
+#include <kpushbutton.h>
+#include <kjanuswidget.h>
+#include <kglobalsettings.h>
+#include <ktextedit.h>
+#include <kfileiconview.h>
+#include <kfileitem.h>
+#include <kmessagebox.h>
+#include <kapplication.h>
+#include <kmimetype.h>
+#include <ktextbrowser.h>
+#include <kconfig.h>
+
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+#define KEXI_STARTUP_SHOW_TEMPLATES
+#define KEXI_STARTUP_SHOW_RECENT
+#endif
+
+//! @internal
+class KexiStartupDialogPrivate {
+public:
+ KexiStartupDialogPrivate()
+ : pageTemplates(0), pageOpenExisting(0), pageOpenRecent(0)
+ , pageTemplatesID(-1)
+ , pageOpenExistingID(-1)
+ , pageOpenRecentID(-1)
+ {
+ result = 0;
+ QString none, iconname;
+ iconname = KMimeType::mimeType( KexiDB::Driver::defaultFileBasedDriverMimeType() )->icon(none,0);
+ kexi_sqlite_icon = KGlobal::iconLoader()->loadIcon( iconname, KIcon::Desktop );
+ iconname = KMimeType::mimeType("application/x-kexiproject-shortcut")->icon(none,0);
+ kexi_shortcut_icon = KGlobal::iconLoader()->loadIcon( iconname, KIcon::Desktop );
+ prj_selector = 0;
+ chkDoNotShow = 0;
+ openExistingConnWidget = 0;
+ templatesWidget = 0;
+ templatesWidget_IconListBox = 0;
+ }
+ ~KexiStartupDialogPrivate()
+ {}
+
+ int dialogType, dialogOptions;
+
+ QFrame *pageTemplates, *pageOpenExisting, *pageOpenRecent;
+ int pageTemplatesID;
+ int pageOpenExistingID, pageOpenRecentID;
+ int templatesSectionID_blank, templatesSectionID_import;
+#ifdef DB_TEMPLATES
+ int templatesSectionID_templates; //, templatesSectionID_custom2;
+#endif
+ QCheckBox *chkDoNotShow;
+
+ //widgets for template tab:
+ KJanusWidget* templatesWidget;
+ QObject *templatesWidget_IconListBox;//helper
+
+ QWidgetStack *viewBlankTempl;
+ KexiStartupDialogTemplatesPage *viewTemplates;
+ //TemplatesPage *viewBusinessTempl;
+
+ int result;
+
+ QPixmap kexi_sqlite_icon, kexi_shortcut_icon;
+
+// //! Key string of selected database template. \sa selectedTemplateKey()
+// QString selectedTemplateKey;
+
+ //! used for "open existing"
+ KexiDBConnectionSet *connSet;
+ KexiStartupFileDialog *openExistingFileDlg; //! embedded file dialog
+ KexiConnSelectorWidget *openExistingConnWidget;
+ QString existingFileToOpen; //! helper for returning a file name to open
+ KexiDB::ConnectionData* selectedExistingConnection; //! helper for returning selected connection
+
+ //! used for "open recent"
+ KexiProjectSet *recentProjects;
+ KexiProjectSelectorWidget* prj_selector;
+
+ //! true if the dialog contain single page, not tabs
+ bool singlePage : 1;
+};
+
+bool dlgSinglePage(int type)
+{
+ return (type==KexiStartupDialog::Templates)
+ || (type==KexiStartupDialog::OpenExisting)
+ || (type==KexiStartupDialog::OpenRecent);
+}
+
+QString captionForDialogType(int type)
+{
+ if (type==KexiStartupDialog::Templates)
+ return i18n("Create Project");
+ else if (type==KexiStartupDialog::OpenExisting)
+ return i18n("Open Existing Project");
+ else if (type==KexiStartupDialog::OpenRecent)
+ return i18n("Open Recent Project");
+
+ return i18n("Choose Project");
+}
+
+/*================================================================*/
+/*KexiStartupDialog::KexiStartupDialog(QWidget *parent, const char *name, KInstance* global,
+ const QCString &format, const QString &nativePattern,
+ const QString &nativeName, const DialogType &dialogType,
+ const QCString& templateType) :
+ KDialogBase(parent, name, true, i18n("Open Document"), KDialogBase::Ok | KDialogBase::Cancel,
+ KDialogBase::Ok) {
+*/
+KexiStartupDialog::KexiStartupDialog(
+ int dialogType, int dialogOptions,
+ KexiDBConnectionSet& connSet, KexiProjectSet& recentProjects,
+ QWidget *parent, const char *name )
+ : KDialogBase(
+ dlgSinglePage(dialogType) ? Plain : Tabbed
+ ,captionForDialogType(dialogType)
+ ,Help | Ok | Cancel, Ok, parent, name )
+ , d(new KexiStartupDialogPrivate())
+{
+ d->recentProjects = &recentProjects;
+ d->connSet = &connSet;
+ d->dialogType = dialogType;
+ d->dialogOptions = dialogOptions;
+ d->singlePage = dlgSinglePage(dialogType);
+
+ if (dialogType==OpenExisting) {//this dialog has "open" tab only!
+ setIcon(DesktopIcon("fileopen"));
+ } else {
+ setIcon(d->kexi_sqlite_icon);
+ }
+
+ setSizeGripEnabled(true);
+ int id=0;
+ if (d->dialogType & Templates) {
+ setupPageTemplates();
+ d->pageTemplatesID = id++;
+ d->templatesWidget->setFocus();
+ }
+ if (d->dialogType & OpenExisting) {
+ setupPageOpenExisting();
+ d->pageOpenExistingID = id++;
+ if (d->singlePage)
+ d->openExistingConnWidget->setFocus();
+ }
+#ifdef KEXI_STARTUP_SHOW_RECENT
+ if (d->dialogType & OpenRecent) {
+ setupPageOpenRecent();
+ d->pageOpenRecentID = id++;
+ if (d->singlePage)
+ d->prj_selector->setFocus();
+ }
+#endif
+
+ if (!d->singlePage) {
+ connect(this, SIGNAL(aboutToShowPage(QWidget*)), this, SLOT(slotPageShown(QWidget*)));
+ d->templatesWidget->setFocus();
+ }
+ showPage(0);
+ adjustSize();
+}
+
+KexiStartupDialog::~KexiStartupDialog()
+{
+ delete d;
+}
+
+bool KexiStartupDialog::shouldBeShown()
+{
+ KGlobal::config()->setGroup("Startup");
+ return KGlobal::config()->readBoolEntry("ShowStartupDialog",true);
+}
+
+void KexiStartupDialog::show()
+{
+ //just some cleanup
+// d->selectedTemplateKey=QString::null;
+ d->existingFileToOpen=QString::null;
+ d->result=-1;
+
+ KDialog::centerOnScreen(this);
+ KDialogBase::show();
+}
+
+int KexiStartupDialog::result() const
+{
+ return d->result;
+}
+
+void KexiStartupDialog::done(int r)
+{
+ if (d->result!=-1) //already done!
+ return;
+
+// kdDebug() << "KexiStartupDialog::done(" << r << ")" << endl;
+// updateSelectedTemplateKeyInfo();
+
+ if (r==QDialog::Rejected) {
+ d->result = CancelResult;
+ } else {
+ const int idx = activePageIndex();
+ if (idx == d->pageTemplatesID) {
+ const int templateIdx = d->templatesWidget->activePageIndex();
+ if (templateIdx == d->templatesSectionID_blank)
+ d->result = CreateBlankResult;
+#ifdef DB_TEMPLATES
+ else if (templateIdx == d->templatesSectionID_templates)
+ d->result = CreateFromTemplateResult;
+#endif
+ else if (templateIdx == d->templatesSectionID_import)
+ d->result = ImportResult;
+ }
+ else if (idx == d->pageOpenExistingID) {
+ // return file or connection:
+ if (d->openExistingConnWidget->selectedConnectionType()==KexiConnSelectorWidget::FileBased) {
+ if (!d->openExistingFileDlg->checkFileName())
+ return;
+ d->existingFileToOpen = d->openExistingFileDlg->currentFileName();
+// d->existingFileToOpen = d->openExistingFileDlg->currentURL().path();
+ d->selectedExistingConnection = 0;
+ } else {
+ d->existingFileToOpen = QString::null;
+ d->selectedExistingConnection
+ = d->openExistingConnWidget->selectedConnectionData();
+ }
+ d->result = OpenExistingResult;
+ }
+ else {
+ d->result = OpenRecentResult;
+ }
+ }
+
+ //save settings
+ KGlobal::config()->setGroup("Startup");
+ if (d->openExistingConnWidget)
+ KGlobal::config()->writeEntry("OpenExistingType",
+ (d->openExistingConnWidget->selectedConnectionType() == KexiConnSelectorWidget::FileBased)
+ ? "File" : "Server");
+ if (d->chkDoNotShow)
+ KGlobal::config()->writeEntry("ShowStartupDialog",!d->chkDoNotShow->isChecked());
+
+ KGlobal::config()->sync();
+
+ KDialogBase::done(r);
+}
+
+void KexiStartupDialog::reject()
+{
+// d->result = CancelResult;
+ KDialogBase::reject();
+}
+
+void KexiStartupDialog::setupPageTemplates()
+{
+ d->pageTemplates = addPage( i18n("&Create Project") );
+ QVBoxLayout *lyr = new QVBoxLayout( d->pageTemplates, 0, KDialogBase::spacingHint() );
+
+ d->templatesWidget = new KJanusWidget(
+ d->pageTemplates, "templatesWidget", KJanusWidget::IconList);
+ {//aaa! dirty hack
+ d->templatesWidget_IconListBox = d->templatesWidget->child(0,"KListBox");
+ if (d->templatesWidget_IconListBox)
+ d->templatesWidget_IconListBox->installEventFilter(this);
+ }
+ lyr->addWidget(d->templatesWidget);
+ connect(d->templatesWidget, SIGNAL(aboutToShowPage(QWidget*)), this, SLOT(slotPageShown(QWidget*)));
+
+ if (d->dialogOptions & CheckBoxDoNotShowAgain) {
+ d->chkDoNotShow = new QCheckBox(i18n("Don't show me this dialog again"), d->pageTemplates, "chkDoNotShow");
+ lyr->addWidget(d->chkDoNotShow);
+ }
+
+ //template groups:
+ QFrame *templPageFrame;
+ QVBoxLayout *tmplyr;
+ int itemID = 0; //used just to set up templatesSectionID_*
+
+ //- page "blank db"
+ d->templatesSectionID_blank = itemID++;
+ QString clickMsg( "\n\n" + i18n("Click \"OK\" button to proceed.") );
+ templPageFrame = d->templatesWidget->addPage(
+ i18n("Blank Database"), i18n("New Blank Database Project"), DesktopIcon("empty") );
+ tmplyr = new QVBoxLayout(templPageFrame, 0, KDialogBase::spacingHint());
+ QLabel *lbl_blank = new QLabel(
+ i18n("Kexi will create a new blank database project.")+clickMsg, templPageFrame );
+ lbl_blank->setAlignment(Qt::AlignAuto|Qt::AlignTop|Qt::WordBreak);
+ lbl_blank->setMargin(0);
+ tmplyr->addWidget( lbl_blank );
+ tmplyr->addStretch(1);
+
+#ifdef DB_TEMPLATES
+ //- page "templates"
+ d->templatesSectionID_templates = itemID++;
+ QString none;
+ QString kexi_sqlite_icon_name
+ = KMimeType::mimeType( KexiDB::Driver::defaultFileBasedDriverMimeType() )->icon(none,0);
+ templPageFrame = d->templatesWidget->addPage (
+ i18n("Keep this text narrow: split to multiple rows if needed", "Create From\nTemplate"),
+ i18n("New Database Project From Template"), DesktopIcon(kexi_sqlite_icon_name) );
+ tmplyr = new QVBoxLayout(templPageFrame, 0, KDialogBase::spacingHint());
+ QLabel *lbl_templ = new QLabel(
+ i18n("Kexi will create a new database project using selected template.\n"
+ "Select template and click \"OK\" button to proceed."), templPageFrame );
+ lbl_templ->setAlignment(Qt::AlignAuto|Qt::AlignTop|Qt::WordBreak);
+ lbl_templ->setMargin(0);
+ tmplyr->addWidget( lbl_templ );
+
+ d->viewTemplates = new KexiStartupDialogTemplatesPage( templPageFrame );
+ tmplyr->addWidget( d->viewTemplates );
+ connect(d->viewTemplates,SIGNAL(selected(const QString&)),
+ this,SLOT(templateSelected(const QString&)));
+/* connect(d->viewTemplates->templates,SIGNAL(returnPressed(QIconViewItem*)),
+ this,SLOT(templateItemExecuted(QIconViewItem*)));
+ connect(d->viewTemplates->templates,SIGNAL(currentChanged(QIconViewItem*)),
+ this,SLOT(templateItemSelected(QIconViewItem*)));*/
+/*later
+ templPageFrame = d->templatesWidget->addPage (
+ i18n("Personal Databases"), i18n("New Personal Database Project Templates"), DesktopIcon("folder_home") );
+ tmplyr = new QVBoxLayout(templPageFrame, 0, KDialogBase::spacingHint());
+ d->viewPersonalTempl = new TemplatesPage( Vertical, templPageFrame, "personal_page" );
+ tmplyr->addWidget( d->viewPersonalTempl );
+ connect(d->viewPersonalTempl->templates,SIGNAL(doubleClicked(QIconViewItem*)),this,SLOT(templateItemExecuted(QIconViewItem*)));
+ connect(d->viewPersonalTempl->templates,SIGNAL(returnPressed(QIconViewItem*)),this,SLOT(templateItemExecuted(QIconViewItem*)));
+ connect(d->viewPersonalTempl->templates,SIGNAL(currentChanged(QIconViewItem*)),this,SLOT(templateItemSelected(QIconViewItem*)));
+*/
+
+ //- page "business db"
+/*later
+ d->templatesSectionID_custom2 = itemID++;
+ templPageFrame = d->templatesWidget->addPage (
+ i18n("Business Databases"), i18n("New Business Database Project Templates"),
+ DesktopIcon( "business_user" ));
+ tmplyr = new QVBoxLayout(templPageFrame, 0, KDialogBase::spacingHint());
+ d->viewBusinessTempl = new TemplatesPage( Vertical, templPageFrame, "business_page" );
+ tmplyr->addWidget( d->viewBusinessTempl );
+ connect(d->viewBusinessTempl->templates,SIGNAL(doubleClicked(QIconViewItem*)),this,SLOT(templateItemExecuted(QIconViewItem*)));
+ connect(d->viewBusinessTempl->templates,SIGNAL(returnPressed(QIconViewItem*)),this,SLOT(templateItemExecuted(QIconViewItem*)));
+ connect(d->viewBusinessTempl->templates,SIGNAL(currentChanged(QIconViewItem*)),this,SLOT(templateItemSelected(QIconViewItem*)));
+*/
+#endif //DB_TEMPLATES
+
+ //- page "import db"
+ d->templatesSectionID_import = itemID++;
+ templPageFrame = d->templatesWidget->addPage(
+ i18n("Import Existing\nDatabase"), i18n("Import Existing Database as New Database Project"),
+ DesktopIcon("database_import") );
+ tmplyr = new QVBoxLayout(templPageFrame, 0, KDialogBase::spacingHint());
+ QLabel *lbl_import = new QLabel(
+ i18n("Kexi will import the structure and data of an existing database as a new database project.")
+ +clickMsg, templPageFrame );
+ lbl_import->setAlignment(Qt::AlignAuto|Qt::AlignTop|Qt::WordBreak);
+ lbl_import->setMargin(0);
+ tmplyr->addWidget( lbl_import );
+ tmplyr->addStretch(1);
+}
+
+void KexiStartupDialog::slotPageShown(QWidget *page)
+{
+ int idx = d->templatesWidget->pageIndex(page);
+// KIconView *templ = 0;
+ if (idx==d->templatesSectionID_blank) {//blank
+// kdDebug() << "blank" << endl;
+ }
+ else if (idx==d->templatesSectionID_import) {
+ }
+#ifdef DB_TEMPLATES
+ else if (idx==d->templatesSectionID_templates) {
+ d->viewTemplates->populate();
+ }
+/*later? KIconView *templ = d->viewTemplates->templates;
+ if (templ->count()==0) {
+ //add items (on demand):
+ d->viewTemplates->addItem("cd_catalog", i18n("CD Catalog"),
+ i18n("Easy-to-use database for storing information about your CD collection."),
+ DesktopIcon("cdrom_unmount"));
+ d->viewTemplates->addItem("expenses", i18n("Expenses"),
+ i18n("A database for managing your personal expenses."),
+ DesktopIcon("kcalc"));
+ d->viewTemplates->addItem("image_gallery", i18n("Image Gallery"),
+ i18n("A database for archiving your image collection in a form of gallery."),
+ DesktopIcon("icons"));
+ }
+ }
+ else if (idx==d->templatesSectionID_custom2) {//business
+ templ = d->viewBusinessTempl->templates;
+ if (templ->count()==0) {
+ //add items (on demand):
+ d->viewBusinessTempl->addItem("address_book", i18n("Address Book"),
+ i18n("A database that offers you a contact information"),
+ DesktopIcon("contents"));
+ }
+ }*/
+#endif
+ updateDialogOKButton(d->pageTemplates);
+}
+
+#if 0
+void KexiStartupDialog::templateItemSelected(QIconViewItem *)
+{
+ updateDialogOKButton(d->pageTemplates);
+}
+
+void KexiStartupDialog::templateItemExecuted(QIconViewItem *item)
+{
+ if (!item)
+ return;
+// updateSelectedTemplateKeyInfo();
+#ifdef DB_TEMPLATES
+ accept();
+#endif
+}
+
+void KexiStartupDialog::updateSelectedTemplateKeyInfo()
+{
+ if (activePageIndex()!=d->pageTemplatesID) {//not a 'new db' tab is selected
+ d->selectedTemplateKey=QString::null;
+ return;
+ }
+ QIconViewItem *item;
+ if (d->templatesWidget->activePageIndex()==d->templatesSectionID_blank) {
+ d->selectedTemplateKey = "blank";
+ }
+ else if (d->templatesWidget->activePageIndex()==d->templatesSectionID_import) {
+ d->selectedTemplateKey = "import";
+ }
+#ifdef DB_TEMPLATES
+ else if (d->templatesWidget->activePageIndex()==d->templatesSectionID_templates) {
+ item = d->viewTemplates->templates->currentItem();
+ if (!item) {
+ d->selectedTemplateKey=QString::null;
+ return;
+ }
+ d->selectedTemplateKey=QString("personal/")+static_cast<TemplateItem*>(item)->key;
+ }
+/*later?
+ else if (d->templatesWidget->activePageIndex()==d->templatesSectionID_custom2) {
+ item = d->viewBusinessTempl->templates->currentItem();
+ if (!item) {
+ d->selectedTemplateKey=QString::null;
+ return;
+ }
+ d->selectedTemplateKey=QString("business/")+static_cast<TemplateItem*>(item)->key;
+ }*/
+#endif
+}
+#endif // 0
+
+void KexiStartupDialog::tabShown(QWidget *w)
+{
+// kdDebug() << "KexiStartupDialog::tabShown " << (long)w << " "<< long(d->pageTemplates)<<endl;
+
+ updateDialogOKButton(w);
+
+ if (w==d->pageOpenExisting) {
+ d->openExistingConnWidget->setFocus();
+ }
+}
+
+void KexiStartupDialog::updateDialogOKButton(QWidget *w)
+{
+ if (!w) {
+ int idx = activePageIndex();
+ if (idx==d->pageTemplatesID)
+ w = d->pageTemplates;
+ else if (idx==d->pageOpenExistingID)
+ w = d->pageOpenExisting;
+ else if (idx==d->pageOpenRecentID)
+ w = d->pageOpenRecent;
+
+ if (!w)
+ return;
+ }
+ bool enable = true;
+ if (w==d->pageTemplates) {
+ int t_id = d->templatesWidget->activePageIndex();
+#ifdef DB_TEMPLATES
+ enable = (t_id==d->templatesSectionID_blank || d->templatesSectionID_import
+ || (t_id==d->templatesSectionID_templates && !d->viewTemplates->selectedFileName().isEmpty()));
+#else
+ enable = (t_id==d->templatesSectionID_blank || d->templatesSectionID_import);
+#endif
+ }
+ else if (w==d->pageOpenExisting) {
+// enable = !d->openExistingFileDlg->currentURL().path().isEmpty();
+ enable =
+ (d->openExistingConnWidget->selectedConnectionType()==KexiConnSelectorWidget::FileBased)
+ ? !d->openExistingFileDlg->currentFileName().isEmpty()
+ : (bool)d->openExistingConnWidget->selectedConnectionData();
+ }
+ else if (w==d->pageOpenRecent) {
+ enable = (d->prj_selector->selectedProjectData()!=0);
+ }
+ enableButton(Ok,enable);
+}
+
+/*QString KexiStartupDialog::selectedTemplateKey() const
+{
+ return d->selectedTemplateKey;
+}*/
+
+void KexiStartupDialog::setupPageOpenExisting()
+{
+ if (d->singlePage)
+ d->pageOpenExisting = plainPage();
+ else
+ d->pageOpenExisting = addPage( i18n("Open &Existing Project") );
+ QVBoxLayout *lyr = new QVBoxLayout( d->pageOpenExisting, 0, KDialogBase::spacingHint() );
+
+ d->openExistingConnWidget = new KexiConnSelectorWidget(*d->connSet,
+ ":OpenExistingOrCreateNewProject",
+ d->pageOpenExisting, "KexiConnSelectorWidget");
+ d->openExistingConnWidget->hideConnectonIcon();
+ lyr->addWidget( d->openExistingConnWidget );
+ if (KGlobal::config()->readEntry("OpenExistingType","File")=="File")
+ d->openExistingConnWidget->showSimpleConn();
+ else {
+ d->openExistingConnWidget->showSimpleConn();
+ d->openExistingConnWidget->showAdvancedConn();
+ }
+ d->openExistingFileDlg = d->openExistingConnWidget->m_fileDlg;
+ connect(d->openExistingFileDlg,SIGNAL(accepted()),this,SLOT(accept()));
+ connect(d->openExistingConnWidget,SIGNAL(connectionItemExecuted(ConnectionDataLVItem*)),
+ this,SLOT(connectionItemForOpenExistingExecuted(ConnectionDataLVItem*)));
+ connect(d->openExistingConnWidget,SIGNAL(connectionItemHighlighted(ConnectionDataLVItem*)),
+ this,SLOT(connectionItemForOpenExistingHighlighted(ConnectionDataLVItem*)));
+}
+
+void KexiStartupDialog::connectionItemForOpenExistingExecuted(ConnectionDataLVItem *item)
+{
+ if (!item)
+ return;
+ accept();
+}
+
+void KexiStartupDialog::connectionItemForOpenExistingHighlighted(ConnectionDataLVItem *item)
+{
+ actionButton(KDialogBase::Ok)->setEnabled(item);
+}
+
+void KexiStartupDialog::slotOk() {
+// kdDebug()<<"KexiStartupDialog::slotOk()"<<endl;
+ if (activePageIndex()==d->pageOpenExistingID) {
+ if (d->openExistingFileDlg) {
+ if (d->openExistingFileDlg->okButton())
+ d->openExistingFileDlg->okButton()->animateClick();
+// return;
+ }
+ }
+ KDialogBase::slotOk();
+}
+
+void KexiStartupDialog::showSimpleConnForOpenExisting()
+{
+// kdDebug() << "simple" << endl;
+ d->openExistingConnWidget->showSimpleConn();
+}
+
+void KexiStartupDialog::showAdvancedConnForOpenExisting()
+{
+// kdDebug() << "adv" << endl;
+ d->openExistingConnWidget->showAdvancedConn();
+}
+
+QString KexiStartupDialog::selectedFileName() const
+{
+ if (d->result == OpenExistingResult)
+ return d->existingFileToOpen;
+ else if (d->result == CreateFromTemplateResult && d->viewTemplates)
+ return d->viewTemplates->selectedFileName();
+ else
+ return QString::null;
+}
+
+KexiDB::ConnectionData* KexiStartupDialog::selectedExistingConnection() const
+{
+ return d->selectedExistingConnection;
+}
+
+void KexiStartupDialog::existingFileSelected(const QString &f)
+{
+ if (f.isEmpty())
+ return;
+ d->existingFileToOpen=f;
+ updateDialogOKButton(d->openExistingFileDlg);
+}
+
+void KexiStartupDialog::setupPageOpenRecent()
+{
+#ifdef KEXI_STARTUP_SHOW_RECENT
+ d->pageOpenRecent = addPage( i18n("Open &Recent Project") );
+ QVBoxLayout *lyr = new QVBoxLayout( d->pageOpenRecent, 0, KDialogBase::spacingHint() );
+ lyr->addWidget( d->prj_selector = new KexiProjectSelectorWidget(
+ d->pageOpenRecent, "prj_selector", d->recentProjects ) );
+ connect(d->prj_selector,SIGNAL(projectExecuted(KexiProjectData*)),
+ this,SLOT(recentProjectItemExecuted(KexiProjectData*)));
+#endif
+}
+
+KexiProjectData* KexiStartupDialog::selectedProjectData() const
+{
+ if (activePageIndex()==d->pageOpenRecentID) {
+ return d->prj_selector->selectedProjectData();
+ }
+ return 0;
+}
+
+void KexiStartupDialog::recentProjectItemExecuted(KexiProjectData *data)
+{
+ updateDialogOKButton(d->pageOpenRecent);
+ if (!data)
+ return;
+ accept();
+}
+
+//! used for accepting templates dialog with just return key press
+bool KexiStartupDialog::eventFilter( QObject *o, QEvent *e )
+{
+ if (o==d->templatesWidget_IconListBox && d->templatesWidget_IconListBox) {
+ if (e->type()==QEvent::KeyPress
+ && (static_cast<QKeyEvent*>(e)->key()==Key_Enter || static_cast<QKeyEvent*>(e)->key()==Key_Return)
+ || e->type()==QEvent::MouseButtonDblClick)
+ {
+ const int t_id = d->templatesWidget->activePageIndex();
+ if (t_id==d->templatesSectionID_blank || t_id==d->templatesSectionID_import) {
+
+ accept();
+ }
+ }
+ }
+ return KDialogBase::eventFilter(o,e);
+}
+
+// internal reimplementation
+int KexiStartupDialog::activePageIndex() const
+{
+ if (!d->singlePage) {
+// kdDebug() << "int KexiStartupDialog::activePageIndex()" << KDialogBase::activePageIndex() << endl;
+ return KDialogBase::activePageIndex();
+ }
+ kdDebug() << "int KexiStartupDialog::activePageIndex() == " << 0 << endl;
+ return 0; //there is always "plain page" #0 selected
+}
+
+void KexiStartupDialog::templateSelected(const QString& fileName)
+{
+ if (!fileName.isEmpty())
+ accept();
+}
+
+QValueList<KexiProjectData::ObjectInfo> KexiStartupDialog::autoopenObjects() const
+{
+ if (d->result != CreateFromTemplateResult || !d->viewTemplates)
+ QValueList<KexiProjectData::ObjectInfo>();
+
+ return d->viewTemplates->autoopenObjectsForSelectedTemplate();
+}
+
+#include "KexiStartupDialog.moc"
diff --git a/kexi/main/startup/KexiStartupDialog.h b/kexi/main/startup/KexiStartupDialog.h
new file mode 100644
index 000000000..41f6064b9
--- /dev/null
+++ b/kexi/main/startup/KexiStartupDialog.h
@@ -0,0 +1,185 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KexiStartupDialog_h
+#define KexiStartupDialog_h
+
+#include <kdialogbase.h>
+#include <kicondialog.h>
+#include <kiconview.h>
+#include <kfileiconview.h>
+#include <kfiledialog.h>
+
+#include <qlabel.h>
+#include <qsplitter.h>
+
+#include <kexidb/connectiondata.h>
+#include <core/kexiprojectdata.h>
+
+class KexiStartupDialogPrivate;
+class KexiProjectData;
+class KexiProjectSet;
+class KexiDBConnectionSet;
+class ConnectionDataLVItem;
+
+/*!
+ This class is used to show the template/open-existing/open-recent tabbed dialog
+ on Kexi startup. If only one page is shown, tab is no displayed, so dialog
+ becomes a normal "plain" type dialog.
+ */
+class KEXIMAIN_EXPORT KexiStartupDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ /*! The Dialog returns one of these values depending
+ on the input of the user.
+ CancelResult The user pressed 'Cancel'
+ CreateBlankResult The user selected a template
+ CreateFromTemplateResult The user selected a template
+ ImportResult The user selected a template
+ OpenExistingResult The user has chosen an existing connection or db file
+ OpenRecentResult The user selected one of recently used databases
+ */
+ enum Result {
+ CancelResult, //!< The user has pressed 'Cancel'
+ CreateBlankResult, //!< The user has selected a template
+ CreateFromTemplateResult, //!< The user has selected a template to be used for creating a new db
+ ImportResult, //!< The user has chosen to import db
+ OpenExistingResult, //!< The user has chosen an existing connection or db file
+ OpenRecentResult //!< The user has selected one of recently used databases
+ };
+
+ /*!
+ To configure the dialog you have to use this enum
+ (any !=0 or'ed value is ok)
+ - Templates Show "Templates" tab
+ - OpenExisting Show "Open existing" tab
+ - OpenRecent Show "Recent" tab
+ - Everything Show everything above
+ */
+ enum DialogType { Templates = 1, OpenExisting = 2, OpenRecent = 4, Everything = (1+2+4) };
+
+ /*! Options for a dialog
+ (any or'ed value or 0 is ok)
+ - CheckBoxDoNotShowAgain Adds "do not show this window" checkbox at the bottom
+ */
+ enum DialogOptions { CheckBoxDoNotShowAgain = 1 };
+
+ /*! Creates a dialog.
+ @param dialogType see DialogType description
+ @param dialogOptions see dialogOptions description
+ @param recentProjects a set of recent projects' info, used for "Open recent" tab
+ @param connSet conenction set used to present available conenctions
+ in "Open Existing" tab. Pass an empty object is this tab is not used.
+ @param parent parent widget, if any.
+ @param name name of this object.
+ */
+ KexiStartupDialog(
+ int dialogType,
+ int dialogOptions,
+ KexiDBConnectionSet& connSet,
+ KexiProjectSet& recentProjects,
+ QWidget *parent = 0, const char *name = 0 );
+ ~KexiStartupDialog();
+
+ /*! \return true if startup dialog should be shown (info is taken from kexi config)
+ */
+ static bool shouldBeShown();
+
+ /*! Executes dialog.
+ \return one of Result values. Use this after dialog is closed. */
+ int result() const;
+
+ /*! \return data of selected Kexi project (if "Open Recent" tab was selected).
+ Returns NULL if no selection has been made or other tab was selected.
+ */
+ KexiProjectData* selectedProjectData() const;
+
+ /*! \return name of selected Kexi project file
+ (if result() == OpenExistingResult)
+ or name of template file to be used for creating a new database.
+ (if result() == CreateFromTemplateResult).
+ Returns empty string if no such selection has been made or other tab was selected.
+ */
+ QString selectedFileName() const;
+
+ /*! \return "autoopen" objects defined for selected template.
+ Only makes sense if template was used. */
+ QValueList<KexiProjectData::ObjectInfo> autoopenObjects() const;
+
+ /*! \return a pointer to selected Kexi connection data.
+ (if "Open Existing" tab was selected and this connection data was clicked).
+ Returns NULL if no such selection has been made or other tab was selected.
+ */
+ KexiDB::ConnectionData* selectedExistingConnection() const;
+
+ /*! Reimplemented for internal reasons */
+ virtual void show();
+
+public slots:
+
+protected slots:
+ virtual void done(int r);
+ virtual void reject();
+ virtual void slotOk();
+
+ //! slot activated when one of page in templates window is shown
+ void slotPageShown(QWidget *page);
+/*
+ //! Any icon view item has been executed (dblclicked)
+ void templateItemExecuted(QIconViewItem *item);
+
+ //! Any icon view item has been selected
+ void templateItemSelected(QIconViewItem *item);*/
+
+ //! Any tab has been selected
+ void tabShown(QWidget *w);
+
+ void templateSelected(const QString& fileName);
+
+ //! helper
+ void recentProjectItemExecuted(KexiProjectData *data);
+ void existingFileSelected(const QString &f);
+ void showSimpleConnForOpenExisting();
+ void showAdvancedConnForOpenExisting();
+ void connectionItemForOpenExistingExecuted(ConnectionDataLVItem *item);
+ void connectionItemForOpenExistingHighlighted(ConnectionDataLVItem *item);
+
+protected:
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ //! helper: updates a state of dialog's OK button
+ void updateDialogOKButton(QWidget *w);
+
+ //! internal reimplementation
+ int activePageIndex() const;
+private:
+ void setupPageTemplates();
+ void setupPageOpenExisting();
+ void setupPageOpenRecent();
+
+ //! used internally on accepting templates selection
+// void updateSelectedTemplateKeyInfo();
+
+ KexiStartupDialogPrivate *d;
+};
+
+#endif
+
diff --git a/kexi/main/startup/KexiStartupDialogTemplatesPage.cpp b/kexi/main/startup/KexiStartupDialogTemplatesPage.cpp
new file mode 100644
index 000000000..3a64fbace
--- /dev/null
+++ b/kexi/main/startup/KexiStartupDialogTemplatesPage.cpp
@@ -0,0 +1,157 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "KexiStartupDialogTemplatesPage.h"
+
+#include <core/kexi.h>
+#include <core/kexitemplateloader.h>
+#include "KexiProjectSelector.h"
+#include "KexiOpenExistingFile.h"
+#include "KexiConnSelector.h"
+#include "KexiConnSelectorBase.h"
+
+#include <qheader.h>
+
+#include <kdebug.h>
+#include <kiconloader.h>
+
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+#define KEXI_STARTUP_SHOW_TEMPLATES
+#define KEXI_STARTUP_SHOW_RECENT
+#endif
+
+/*QPixmap createIcon()
+{
+
+}*/
+
+/*QString createText(const QString& name, const QString& description)
+{
+ QString txt = "<H2>" + name + "</H2>";
+ if (description.isEmpty())
+ return name + description
+}*/
+
+//! @internal
+class TemplateItem : public KListViewItem
+{
+ public:
+ TemplateItem(QListView* parent, const QString& aFilename,
+ const QString& name, const QString& description, const QPixmap& icon,
+ const QValueList<KexiProjectData::ObjectInfo>& aAutoopenObjects)
+ : KListViewItem(parent, name + "\n" + description)
+ , autoopenObjects(aAutoopenObjects)
+ , filename(aFilename)
+ {
+ setPixmap(0, icon);
+ }
+ ~TemplateItem() {}
+
+ QValueList<KexiProjectData::ObjectInfo> autoopenObjects;
+ QString filename;
+};
+
+//-----------------------
+
+KexiStartupDialogTemplatesPage::KexiStartupDialogTemplatesPage( QWidget * parent )
+ : KListView(parent, "KexiStartupDialogTemplatesPage")
+ , m_popuplated(false)
+{
+ addColumn(QString::null);
+ header()->hide();
+ setColumnWidthMode(0, Maximum);
+ setResizeMode(LastColumn);
+ setItemMargin(6);
+ connect(this,SIGNAL(executed(QListViewItem*)), this, SLOT(slotExecuted(QListViewItem*)));
+}
+
+KexiStartupDialogTemplatesPage::~KexiStartupDialogTemplatesPage()
+{
+}
+
+void KexiStartupDialogTemplatesPage::populate()
+{
+ if (m_popuplated)
+ return;
+ m_popuplated = true;
+ KexiTemplateInfo::List list = KexiTemplateLoader::loadListInfo();
+ foreach( QValueList<KexiTemplateInfo>::ConstIterator, it, list ) {
+ new TemplateItem(this, (*it).filename, (*it).name,
+ (*it).description, (*it).icon, (*it).autoopenObjects);
+ }
+ if (firstChild())
+ setSelected(firstChild(), true);
+
+// templates = new KIconView(this, "templates");
+// templates->setItemsMovable(false);
+// templates->setShowToolTips(false);
+// info = new KTextBrowser(this,"info");
+// setResizeMode(templates,KeepSize);
+// setResizeMode(info,KeepSize);
+// connect(templates,SIGNAL(selectionChanged(QIconViewItem*)),this,SLOT(itemClicked(QIconViewItem*)));
+}
+
+/*
+void TemplatesPage::addItem(const QString& key, const QString& name,
+ const QString& description, const QPixmap& icon)
+{
+ TemplateItem *item = new TemplateItem(templates, name, icon);
+ item->key=key;
+ item->name=name;
+ item->description=description;
+}
+
+void TemplatesPage::itemClicked(QIconViewItem *item) {
+ if (!item) {
+ info->setText("");
+ return;
+ }
+ QString t = QString("<h2>%1</h2><p>%2</p>")
+ .arg(static_cast<TemplateItem*>(item)->name)
+ .arg(static_cast<TemplateItem*>(item)->description);
+#ifndef DB_TEMPLATES
+ t += QString("<p>") + i18n("We are sorry, templates are not yet available.") +"</p>";
+#endif
+
+ info->setText( t );
+}*/
+
+QString KexiStartupDialogTemplatesPage::selectedFileName() const
+{
+ TemplateItem* templateItem = static_cast<TemplateItem*>(selectedItem());
+ return templateItem ? templateItem->filename : QString::null;
+}
+
+QValueList<KexiProjectData::ObjectInfo>
+KexiStartupDialogTemplatesPage::autoopenObjectsForSelectedTemplate() const
+{
+ TemplateItem* templateItem = static_cast<TemplateItem*>(selectedItem());
+ return templateItem ? templateItem->autoopenObjects : QValueList<KexiProjectData::ObjectInfo>();
+}
+
+void KexiStartupDialogTemplatesPage::slotExecuted(QListViewItem* item)
+{
+ TemplateItem* templateItem = static_cast<TemplateItem*>(item);
+ if (!templateItem)
+ return;
+
+ emit selected(templateItem->filename);
+}
+
+#include "KexiStartupDialogTemplatesPage.moc"
diff --git a/kexi/main/startup/KexiStartupDialogTemplatesPage.h b/kexi/main/startup/KexiStartupDialogTemplatesPage.h
new file mode 100644
index 000000000..8613065f6
--- /dev/null
+++ b/kexi/main/startup/KexiStartupDialogTemplatesPage.h
@@ -0,0 +1,57 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KexiStartupDialogTemplatesPage_h
+#define KexiStartupDialogTemplatesPage_h
+
+#include <klistview.h>
+#include <core/kexiprojectdata.h>
+
+/*! Helper class for displaying templates set with description. */
+class KEXIMAIN_EXPORT KexiStartupDialogTemplatesPage : public KListView
+{
+ Q_OBJECT
+
+ public:
+ KexiStartupDialogTemplatesPage( QWidget * parent = 0 );
+ ~KexiStartupDialogTemplatesPage();
+// void addItem(const QString& key, const QString& name,
+// const QString& description, const QPixmap& icon);
+
+ QString selectedFileName() const;
+
+ QValueList<KexiProjectData::ObjectInfo> autoopenObjectsForSelectedTemplate() const;
+
+ void populate();
+
+ signals:
+ void selected(const QString& filename);
+
+ protected slots:
+ void slotExecuted(QListViewItem* item);
+
+// void itemClicked(QIconViewItem *item);
+
+ private:
+ bool m_popuplated : 1;
+// KIconView *templates;
+// KTextBrowser *info;
+};
+
+#endif
diff --git a/kexi/main/startup/KexiStartupFileDialog.cpp b/kexi/main/startup/KexiStartupFileDialog.cpp
new file mode 100644
index 000000000..d28699250
--- /dev/null
+++ b/kexi/main/startup/KexiStartupFileDialog.cpp
@@ -0,0 +1,422 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "KexiStartupFileDialog.h"
+
+#include <kexidb/driver.h>
+#include <core/kexi.h>
+#include <kexiutils/utils.h>
+
+#include <qlayout.h>
+#include <qobjectlist.h>
+#include <qpushbutton.h>
+#include <qapplication.h>
+
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kmimetype.h>
+#include <kfile.h>
+#include <kurlcombobox.h>
+
+KexiStartupFileDialog::KexiStartupFileDialog(
+ const QString& startDirOrVariable, int mode,
+ QWidget *parent, const char *name)
+ : KexiStartupFileDialogBase(startDirOrVariable, "", parent, name, 0)
+ , m_confirmOverwrites(true)
+ , m_filtersUpdated(false)
+{
+ setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum);
+ setMode( mode );
+
+ QPoint point( 0, 0 );
+ reparent( parentWidget(), point );
+
+ if (layout())
+ layout()->setMargin(0);
+ setMinimumHeight(100);
+ setSizeGripEnabled ( false );
+
+ //dirty hack to customize filedialog view:
+ {
+ QObjectList *l = queryList( "QPushButton" );
+ QObjectListIt it( *l );
+ QObject *obj;
+ while ( (obj = it.current()) != 0 ) {
+ ++it;
+ static_cast<QPushButton*>(obj)->hide();
+ }
+ delete l;
+ }
+ {
+ QObjectList *l = queryList("QWidget");
+ QObjectListIt it( *l );
+ QObject *obj;
+ while ( (obj = it.current()) != 0 ) {
+ ++it;
+ static_cast<QWidget*>(obj)->installEventFilter(this);
+ }
+ delete l;
+ }
+
+#ifdef Q_WS_WIN
+ if (startDirOrVariable.startsWith(":"))
+ m_lastVisitedPathsVariable = startDirOrVariable; //store for later use
+#else
+ toggleSpeedbar(false);
+ setFocusProxy( locationEdit );//locationWidget() );
+#endif
+}
+
+KexiStartupFileDialog::~KexiStartupFileDialog()
+{
+#ifdef Q_WS_WIN
+ saveLastVisitedPath(currentFileName());
+#endif
+}
+
+void KexiStartupFileDialog::setMode(int mode)
+{
+ //delayed
+ m_mode = mode;
+ m_filtersUpdated = false;
+}
+
+QStringList KexiStartupFileDialog::additionalFilters() const
+{
+ return m_additionalMimeTypes;
+}
+
+void KexiStartupFileDialog::setAdditionalFilters(const QStringList &mimeTypes)
+{
+ //delayed
+ m_additionalMimeTypes = mimeTypes;
+ m_filtersUpdated = false;
+}
+
+QStringList KexiStartupFileDialog::excludedFilters() const
+{
+ return m_excludedMimeTypes;
+}
+
+void KexiStartupFileDialog::setExcludedFilters(const QStringList &mimeTypes)
+{
+ //delayed
+ m_excludedMimeTypes = mimeTypes;
+ m_filtersUpdated = false;
+}
+
+void KexiStartupFileDialog::updateFilters()
+{
+ if (m_filtersUpdated)
+ return;
+ m_filtersUpdated = true;
+
+ m_lastFileName = QString::null;
+// m_lastUrl = KURL();
+
+ clearFilter();
+
+ QString filter;
+ KMimeType::Ptr mime;
+ QStringList allfilters;
+
+ const bool normalOpeningMode = m_mode & Opening && !(m_mode & Custom);
+ const bool normalSavingMode = m_mode & SavingFileBasedDB && !(m_mode & Custom);
+
+ if (normalOpeningMode || normalSavingMode) {
+ mime = KMimeType::mimeType( KexiDB::Driver::defaultFileBasedDriverMimeType() );
+ if (mime && m_excludedMimeTypes.find(mime->name())==m_excludedMimeTypes.end()) {
+ filter += KexiUtils::fileDialogFilterString(mime);
+ allfilters += mime->patterns();
+ }
+ }
+ if (normalOpeningMode || m_mode & SavingServerBasedDB) {
+ mime = KMimeType::mimeType("application/x-kexiproject-shortcut");
+ if (mime && m_excludedMimeTypes.find(mime->name())==m_excludedMimeTypes.end()) {
+ filter += KexiUtils::fileDialogFilterString(mime);
+ allfilters += mime->patterns();
+ }
+ }
+ if (normalOpeningMode || m_mode & SavingServerBasedDB) {
+ mime = KMimeType::mimeType("application/x-kexi-connectiondata");
+ if (mime && m_excludedMimeTypes.find(mime->name())==m_excludedMimeTypes.end()) {
+ filter += KexiUtils::fileDialogFilterString(mime);
+ allfilters += mime->patterns();
+ }
+ }
+
+//! @todo hardcoded for MSA:
+ if (normalOpeningMode) {
+ mime = KMimeType::mimeType("application/x-msaccess");
+ if (mime && m_excludedMimeTypes.find(mime->name())==m_excludedMimeTypes.end()) {
+ filter += KexiUtils::fileDialogFilterString(mime);
+ allfilters += mime->patterns();
+ }
+ }
+
+ foreach (QStringList::ConstIterator, it, m_additionalMimeTypes) {
+ if (*it == "all/allfiles")
+ continue;
+ if (m_excludedMimeTypes.find(*it)!=m_excludedMimeTypes.end())
+ continue;
+ filter += KexiUtils::fileDialogFilterString(*it);
+ mime = KMimeType::mimeType(*it);
+ allfilters += mime->patterns();
+ }
+
+ if (m_excludedMimeTypes.find("all/allfiles")==m_excludedMimeTypes.end())
+ filter += KexiUtils::fileDialogFilterString("all/allfiles");
+// mime = KMimeType::mimeType("all/allfiles");
+// if (mime) {
+// filter += QString(mime->patterns().isEmpty() ? "*" : mime->patterns().join(" "))
+// + "|" + mime->comment()+ " (*)\n";
+// }
+ //remove duplicates made because upper- and lower-case extenstions are used:
+ QStringList allfiltersUnique;
+ QDict<char> uniqueDict(499, false);
+ foreach (QStringList::ConstIterator, it, allfilters) {
+// kdDebug() << *it << endl;
+ uniqueDict.insert(*it, (char*)1);
+ }
+ foreach_dict (QDictIterator<char>, it, uniqueDict) {
+ allfiltersUnique += it.currentKey();
+ }
+ allfiltersUnique.sort();
+
+ if (allfiltersUnique.count()>1) {//prepend "all supoported files" entry
+ filter.prepend(allfilters.join(" ")+"|" + i18n("All Supported Files")
+ +" ("+allfiltersUnique.join(" ")+")\n");
+ }
+
+ if (filter.right(1)=="\n")
+ filter.truncate(filter.length()-1);
+ setFilter(filter);
+
+ if (m_mode & Opening) {
+ KexiStartupFileDialogBase::setMode( KFile::ExistingOnly | KFile::LocalOnly | KFile::File );
+ setOperationMode( KFileDialog::Opening );
+ } else {
+ KexiStartupFileDialogBase::setMode( KFile::LocalOnly | KFile::File );
+ setOperationMode( KFileDialog::Saving );
+ }
+}
+
+void KexiStartupFileDialog::show()
+{
+ m_filtersUpdated = false;
+ updateFilters();
+ KexiStartupFileDialogBase::show();
+}
+
+//KURL KexiStartupFileDialog::currentURL()
+QString KexiStartupFileDialog::currentFileName()
+{
+ setResult( QDialog::Accepted ); // selectedURL tests for it
+
+#ifdef Q_WS_WIN
+// QString path = selectedFile();
+ //js @todo
+// kdDebug() << "selectedFile() == " << path << " '" << url().fileName() << "' " << m_lineEdit->text() << endl;
+ QString path = dir()->absPath();
+ if (!path.endsWith("/") && !path.endsWith("\\"))
+ path.append("/");
+ path += m_lineEdit->text();
+// QString path = QFileInfo(selectedFile()).dirPath(true) + "/" + m_lineEdit->text();
+#else
+// QString path = locationEdit->currentText().stripWhiteSpace(); //url.path().stripWhiteSpace(); that does not work, if the full path is not in the location edit !!!!!
+ QString path=KexiStartupFileDialogBase::selectedURL().path();
+ kdDebug() << "prev selectedURL() == " << path <<endl;
+ kdDebug() << "locationEdit == " << locationEdit->currentText().stripWhiteSpace() <<endl;
+ //make sure user-entered path is acceped:
+ setSelection( locationEdit->currentText().stripWhiteSpace() );
+
+ path=KexiStartupFileDialogBase::selectedURL().path();
+ kdDebug() << "selectedURL() == " << path <<endl;
+
+#endif
+
+ if (!currentFilter().isEmpty()) {
+ if (m_mode & SavingFileBasedDB) {
+ QStringList filters = QStringList::split(" ", currentFilter()); //.first().stripWhiteSpace();
+ kdDebug()<< " filter == " << filters << endl;
+ QString ext = QFileInfo(path).extension(false);
+ bool hasExtension = false;
+ for (QStringList::ConstIterator filterIt = filters.constBegin();
+ filterIt != filters.constEnd() && !hasExtension; ++filterIt)
+ {
+ QString f( (*filterIt).stripWhiteSpace() );
+ hasExtension = !f.mid(2).isEmpty() && ext==f.mid(2);
+ }
+ if (!hasExtension) {
+ //no extension: add one
+ QString defaultExtension( m_defaultExtension );
+ if (defaultExtension.isEmpty())
+ defaultExtension = filters.first().stripWhiteSpace().mid(2); //first one
+ path+=(QString(".")+defaultExtension);
+ kdDebug() << "KexiStartupFileDialog::checkURL(): append extension, " << path << endl;
+ setSelection( path );
+ }
+ }
+ }
+ kdDebug() << "KexiStartupFileDialog::currentFileName() == " << path <<endl;
+ return path;
+// return KFileDialog::selectedURL();
+}
+
+//bool KexiStartupFileDialog::checkURL()
+bool KexiStartupFileDialog::checkFileName()
+{
+// KURL url = currentURL();
+// QString path = url.path().stripWhiteSpace();
+ QString path = currentFileName().stripWhiteSpace();
+
+// if (url.fileName().stripWhiteSpace().isEmpty()) {
+ if (path.isEmpty()) {
+ KMessageBox::error( this, i18n( "Enter a filename." ));
+ return false;
+ }
+
+ kdDebug() << "KexiStartupFileDialog::checkURL() path: " << path << endl;
+// kdDebug() << "KexiStartupFileDialog::checkURL() fname: " << url.fileName() << endl;
+//todo if ( url.isLocalFile() ) {
+ QFileInfo fi(path);
+ if (mode() & KFile::ExistingOnly) {
+ if ( !fi.exists() ) {
+ KMessageBox::error( this, "<qt>"+i18n( "The file \"%1\" does not exist." )
+ .arg( QDir::convertSeparators(path) ) );
+ return false;
+ }
+ else if (mode() & KFile::File) {
+ if (!fi.isFile()) {
+ KMessageBox::error( this, "<qt>"+i18n( "Enter a filename." ) );
+ return false;
+ }
+ else if (!fi.isReadable()) {
+ KMessageBox::error( this, "<qt>"+i18n( "The file \"%1\" is not readable." )
+ .arg( QDir::convertSeparators(path) ) );
+ return false;
+ }
+ }
+ }
+ else if (m_confirmOverwrites && !askForOverwriting( path, this )) {
+ return false;
+ }
+// }
+ return true;
+}
+
+//static
+bool KexiStartupFileDialog::askForOverwriting(const QString& filePath, QWidget *parent)
+{
+ QFileInfo fi(filePath);
+ if (!fi.exists())
+ return true;
+ const int res = KMessageBox::warningYesNo( parent, i18n( "The file \"%1\" already exists.\n"
+ "Do you want to overwrite it?").arg( QDir::convertSeparators(filePath) ), QString::null,
+ i18n("Overwrite"), KStdGuiItem::no() );
+ if (res == KMessageBox::Yes)
+ return true;
+ return false;
+}
+
+void KexiStartupFileDialog::accept()
+{
+// locationEdit->setFocus();
+// QKeyEvent ev(QEvent::KeyPress, Qt::Key_Enter, '\n', 0);
+// QApplication::sendEvent(locationEdit, &ev);
+// QApplication::postEvent(locationEdit, &ev);
+
+// kdDebug() << "KexiStartupFileDialog::accept() m_lastUrl == " << m_lastUrl.path() << endl;
+// if (m_lastUrl.path()==currentURL().path()) {//(js) to prevent more multiple kjob signals (I do not know why this is)
+ if (m_lastFileName==currentFileName()) {//(js) to prevent more multiple kjob signals (I do not know why this is)
+// m_lastUrl=KURL();
+ m_lastFileName=QString::null;
+ kdDebug() << "m_lastFileName==currentFileName()" << endl;
+#ifdef Q_WS_WIN
+ return;
+#endif
+ }
+// kdDebug() << "KexiStartupFileDialog::accept(): url = " << currentURL().path() << " " << endl;
+ kdDebug() << "KexiStartupFileDialog::accept(): path = " << currentFileName() << endl;
+// if ( checkURL() ) {
+ if ( checkFileName() ) {
+ emit accepted();
+ }
+// else {
+// m_lastUrl = KURL();
+// }
+// m_lastUrl = currentURL();
+ m_lastFileName = currentFileName();
+
+#ifdef Q_WS_WIN
+ saveLastVisitedPath(m_lastFileName);
+#endif
+}
+
+void KexiStartupFileDialog::reject()
+{
+ kdDebug() << "KexiStartupFileDialog: reject!" << endl;
+ emit rejected();
+}
+
+/*#ifndef Q_WS_WIN
+KURLComboBox *KexiStartupFileDialog::locationWidget() const
+{
+ return locationEdit;
+}
+#endif
+*/
+
+void KexiStartupFileDialog::setLocationText(const QString& fn)
+{
+#ifdef Q_WS_WIN
+ //js @todo
+ setSelection(fn);
+#else
+ setSelection(fn);
+// locationEdit->setCurrentText(fn);
+// locationEdit->lineEdit()->setEdited( true );
+// setSelection(fn);
+#endif
+}
+
+void KexiStartupFileDialog::setFocus()
+{
+#ifdef Q_WS_WIN
+ m_lineEdit->setFocus();
+#else
+ locationEdit->setFocus();
+#endif
+}
+
+bool KexiStartupFileDialog::eventFilter ( QObject * watched, QEvent * e )
+{
+ //filter-out ESC key
+ if (e->type()==QEvent::KeyPress && static_cast<QKeyEvent*>(e)->key()==Qt::Key_Escape
+ && static_cast<QKeyEvent*>(e)->state()==Qt::NoButton) {
+ static_cast<QKeyEvent*>(e)->accept();
+ emit rejected();
+ return true;
+ }
+ return KexiStartupFileDialogBase::eventFilter(watched,e);
+}
+
+#include "KexiStartupFileDialog.moc"
+
diff --git a/kexi/main/startup/KexiStartupFileDialog.h b/kexi/main/startup/KexiStartupFileDialog.h
new file mode 100644
index 000000000..87ebf7f4d
--- /dev/null
+++ b/kexi/main/startup/KexiStartupFileDialog.h
@@ -0,0 +1,132 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KexiStartupFileDialog_h_
+#define _KexiStartupFileDialog_h_
+
+#include <kfiledialog.h>
+
+#ifdef Q_WS_WIN
+# include "KexiStartupFileDialogBase_win.h"
+#else
+ typedef KFileDialog KexiStartupFileDialogBase;
+#endif
+
+
+//! @short Widget for opening/saving files supported by Kexi
+class KEXIMAIN_EXPORT KexiStartupFileDialog : public KexiStartupFileDialogBase
+{
+ Q_OBJECT
+
+public:
+ /*! Dialog mode:
+ - Opening opens existing database (or shortcut)
+ - SavingFileBasedDB saves file-based database file
+ - SavingServerBasedDB saves server-based (shortcut) file
+ - CustomOpening can be used for opening other files, like CSV
+ */
+ typedef enum Mode {
+ Opening = 1,
+ SavingFileBasedDB = 2,
+ SavingServerBasedDB = 4,
+ Custom = 256
+ };
+
+ KexiStartupFileDialog(
+ const QString& startDirOrVariable, int mode, QWidget *parent=0, const char *name=0);
+
+ virtual ~KexiStartupFileDialog();
+
+ /*! Helper. Displays "The file %1 already exists. Do you want to overwrite it?" yes/no message box.
+ \a parent is used as a parent of the KMessageBox.
+ \return true if \a filePath file does not exists or user has agreed on overwriting,
+ false in user do not want to overwrite. */
+ static bool askForOverwriting(const QString& filePath, QWidget *parent = 0);
+
+ void setMode(int mode);
+
+ QStringList additionalFilters() const;
+
+ //! Sets additional filters list, e.g. "text/x-csv"
+ void setAdditionalFilters(const QStringList &mimeTypes);
+
+ QStringList excludedFilters() const;
+
+ //! Excludes filters list
+ void setExcludedFilters(const QStringList &mimeTypes);
+
+// KURL currentURL();
+ QString currentFileName();
+
+//#ifndef Q_WS_WIN
+// KURLComboBox *locationWidget() const;
+//#endif
+ //! just sets locationWidget()->setCurrentText(fn)
+ //! (and something similar on win32)
+ void setLocationText(const QString& fn);
+
+ //! Sets default extension which will be added after accepting
+ //! if user didn't provided one. This method is usable when there is
+ //! more than one filter so there is no rule what extension should be selected
+ //! (by default first one is selected).
+ void setDefaultExtension(const QString& ext) { m_defaultExtension = ext; }
+
+ /*! \return true if the current URL meets requies constraints
+ (i.e. exists or doesn't exist);
+ shows appropriate message box if needed. */
+ bool checkFileName();
+// bool checkURL();
+
+ /*! If true, user will be asked to accept overwriting existing file.
+ This is true by default. */
+ void setConfirmOverwrites(bool set) { m_confirmOverwrites = set; }
+
+ virtual bool eventFilter ( QObject * watched, QEvent * e );
+
+public slots:
+ virtual void show();
+
+ virtual void setFocus();
+
+ // Typing a file that doesn't exist closes the file dialog, we have to
+ // handle this case better here.
+ virtual void accept();
+
+signals:
+ //entered file name is accepted
+ void accepted();
+ void rejected();
+
+protected slots:
+ virtual void reject();
+
+private:
+ void updateFilters();
+
+// KURL m_lastUrl;
+ QString m_lastFileName;
+ int m_mode;
+ QStringList m_additionalMimeTypes, m_excludedMimeTypes;
+ QString m_defaultExtension;
+ bool m_confirmOverwrites : 1;
+ bool m_filtersUpdated : 1;
+};
+
+#endif
+
diff --git a/kexi/main/startup/KexiStartupFileDialogBase_win.h b/kexi/main/startup/KexiStartupFileDialogBase_win.h
new file mode 100644
index 000000000..7d7f56e03
--- /dev/null
+++ b/kexi/main/startup/KexiStartupFileDialogBase_win.h
@@ -0,0 +1,67 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KexiStartupFileDialogBase_win_h_
+#define _KexiStartupFileDialogBase_win_h_
+
+#include <qfiledialog.h>
+#include <qpushbutton.h>
+
+class KexiStartupFileDialogBasePrivate;
+
+/*! Wrapper for win32-like file dialog. QFileDialog is used for this.
+ Temporary moved from QKW KFileDialog implementation.
+ TODO: move to KDElibs/win32 KFileDialog wrapper
+*/
+class KexiStartupFileDialogBase : public QFileDialog
+{
+public:
+ KexiStartupFileDialogBase(const QString & dirName, const QString & filter = QString::null,
+ QWidget * parent = 0, const char * name = 0, bool modal = false );
+ ~KexiStartupFileDialogBase();
+
+ QPushButton * okButton() const { return m_okBtn; }
+
+ void clearFilter();
+ void setFilter(const QString& filter);
+ void setOperationMode( KFileDialog::OperationMode mode );
+ void setMode( KFile::Mode m );
+ void setMode( unsigned int m );
+ QString currentFilter() const;
+ void setMimeFilter( const QStringList& mimeTypes, const QString& defaultType = QString::null );
+
+ KFile::Mode mode() const;
+
+protected:
+ void init(const QString& startDir, const QString& filter, QWidget* widget);
+ void updateAutoSelectExtension() {};
+
+ //! Helper added because QFileDialog on win32 doesn't support ":" prefixes
+ //! for recent dir's storage.
+ QString realStartDir(const QString& startDir);
+
+ void saveLastVisitedPath(const QString& path);
+
+ QPushButton* m_okBtn;
+ QLineEdit* m_lineEdit;
+ QString m_lastVisitedPathsVariable; //!< Used by win32; @see realStartDir()
+ KexiStartupFileDialogBasePrivate* d;
+};
+
+#endif
diff --git a/kexi/main/startup/KexiStartupFileDialog_win.cpp b/kexi/main/startup/KexiStartupFileDialog_win.cpp
new file mode 100644
index 000000000..aa06fb9a1
--- /dev/null
+++ b/kexi/main/startup/KexiStartupFileDialog_win.cpp
@@ -0,0 +1,476 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+/*!
+ Temporary moved from QKW KFileDialog implementation.
+ TODO: move to KDElibs/win32 KFileDialog wrapper
+*/
+
+#include "KexiStartupFileDialog.h"
+#include <kexiutils/utils.h>
+
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <krecentdirs.h>
+
+#include <qobjectlist.h>
+#include <qlineedit.h>
+
+#include <win/win32_utils.h>
+
+//! @internal
+class KexiStartupFileDialogBasePrivate
+{
+ public:
+ KexiStartupFileDialogBasePrivate()
+ {}
+ KFile::Mode mode;
+ QString kde_filters;
+ QStringList mimetypes;
+};
+
+KexiStartupFileDialogBase::KexiStartupFileDialogBase(
+ const QString & dirName, const QString & filter,
+ QWidget * parent, const char * name, bool modal )
+ : QFileDialog( realStartDir(dirName), filter, parent, name, modal )
+ , d(new KexiStartupFileDialogBasePrivate())
+{
+// QString _dirName = dirName;
+ QString _dirName = dirPath();
+ //make default 'My Documents' folder
+//TODO: store changes in the app's config file?
+ if (_dirName.isEmpty())
+ _dirName = KGlobalSettings::documentPath();
+
+ init(_dirName, filter, parent);
+
+ //find "OK" button
+ QObjectList *l = queryList( "QPushButton", "OK", false );
+ m_okBtn = dynamic_cast<QPushButton*>(l->first());
+ delete l;
+ l = queryList( "QLineEdit", "name/filter editor", false );
+ m_lineEdit = dynamic_cast<QLineEdit*>(l->first());
+ delete l;
+
+ adjustSize();
+}
+
+KexiStartupFileDialogBase::~KexiStartupFileDialogBase()
+{
+}
+
+void KexiStartupFileDialogBase::init(const QString& startDir, const QString& filter, QWidget* widget)
+{
+//TODO initStatic();
+//TODO d = new KFileDialogPrivate();
+
+//(js) d->boxLayout = 0;
+//TODO d->keepLocation = false;
+//TODO d->operationMode = Opening;
+ setMode(KFile::File | KFile::ExistingOnly); //(js) default: open action
+ setIcon( KGlobal::iconLoader()->loadIcon("fileopen", KIcon::Desktop) );
+ setDir(QDir(startDir));
+//TODO d->hasDefaultFilter = false;
+//TODO d->hasView = false;
+//(js) d->mainWidget = new QWidget( this, "KFileDialog::mainWidget");
+//(js) setMainWidget( d->mainWidget );
+//(js) d->okButton = new KPushButton( KStdGuiItem::ok(), d->mainWidget );
+//(js) d->okButton->setDefault( true );
+//(js) d->cancelButton = new KPushButton(KStdGuiItem::cancel(), d->mainWidget);
+//(js) connect( d->okButton, SIGNAL( clicked() ), SLOT( slotOk() ));
+//(js) connect( d->cancelButton, SIGNAL( clicked() ), SLOT( slotCancel() ));
+//(js) d->customWidget = widget;
+//(js) d->autoSelectExtCheckBox = 0; // delayed loading
+//TODO d->autoSelectExtChecked = false;
+//(js) d->urlBar = 0; // delayed loading
+//TODO KConfig *config = KGlobal::config();
+//TODO KConfigGroupSaver cs( config, ConfigGroup );
+//TODO d->initializeSpeedbar = config->readBoolEntry( "Set speedbar defaults",
+//TODO true );
+//TODO d->completionLock = false;
+
+//TODO QtMsgHandler oldHandler = qInstallMsgHandler( silenceQToolBar );
+//TODO toolbar = 0; //(js)
+//(js) toolbar = new KToolBar( d->mainWidget, "KFileDialog::toolbar", true);
+//(js) toolbar->setFlat(true);
+//TODO qInstallMsgHandler( oldHandler );
+
+//(js) d->pathCombo = new KURLComboBox( KURLComboBox::Directories, true,
+//(js) toolbar, "path combo" );
+//(js) QToolTip::add( d->pathCombo, i18n("Often used directories") );
+//(js) QWhatsThis::add( d->pathCombo, "<qt>" + i18n("Commonly used locations are listed here. "
+//(js) "This includes standard locations, such as your home directory, as well as "
+//(js) "locations that have been visited recently.") + autocompletionWhatsThisText);
+/*
+ KURL u;
+ u.setPath( QDir::rootDirPath() );
+ QString text = i18n("Root Directory: %1").arg( u.path() );
+ d->pathCombo->addDefaultURL( u,
+ KMimeType::pixmapForURL( u, 0, KIcon::Small ),
+ text );
+
+ u.setPath( QDir::homeDirPath() );
+ text = i18n("Home Directory: %1").arg( u.path( +1 ) );
+ d->pathCombo->addDefaultURL( u, KMimeType::pixmapForURL( u, 0, KIcon::Small ),
+ text );
+
+ KURL docPath;
+ docPath.setPath( KGlobalSettings::documentPath() );
+ if ( u.path(+1) != docPath.path(+1) ) {
+ text = i18n("Documents: %1").arg( docPath.path( +1 ) );
+ d->pathCombo->addDefaultURL( u,
+ KMimeType::pixmapForURL( u, 0, KIcon::Small ),
+ text );
+ }
+
+ u.setPath( KGlobalSettings::desktopPath() );
+ text = i18n("Desktop: %1").arg( u.path( +1 ) );
+ d->pathCombo->addDefaultURL( u,
+ KMimeType::pixmapForURL( u, 0, KIcon::Small ),
+ text );
+
+ u.setPath( "/tmp" );
+
+ d->url = getStartURL( startDir, d->fileClass );
+ d->selection = d->url.url();
+
+ // If local, check it exists. If not, go up until it exists.
+ if ( d->url.isLocalFile() )
+ {
+ if ( !QFile::exists( d->url.path() ) )
+ {
+ d->url = d->url.upURL();
+ QDir dir( d->url.path() );
+ while ( !dir.exists() )
+ {
+ d->url = d->url.upURL();
+ dir.setPath( d->url.path() );
+ }
+ }
+ }
+
+ ops = new KDirOperator(d->url, d->mainWidget, "KFileDialog::ops");
+ ops->setOnlyDoubleClickSelectsFiles( true );
+ connect(ops, SIGNAL(urlEntered(const KURL&)),
+ SLOT(urlEntered(const KURL&)));
+ connect(ops, SIGNAL(fileHighlighted(const KFileItem *)),
+ SLOT(fileHighlighted(const KFileItem *)));
+ connect(ops, SIGNAL(fileSelected(const KFileItem *)),
+ SLOT(fileSelected(const KFileItem *)));
+ connect(ops, SIGNAL(finishedLoading()),
+ SLOT(slotLoadingFinished()));
+
+ ops->setupMenu(KDirOperator::SortActions |
+ KDirOperator::FileActions |
+ KDirOperator::ViewActions);
+ KActionCollection *coll = ops->actionCollection();
+
+ // plug nav items into the toolbar
+ coll->action( "up" )->plug( toolbar );
+ coll->action( "up" )->setWhatsThis(i18n("<qt>Click this button to enter the parent directory.<p>"
+ "For instance, if the current location is file:/home/%1 clicking this "
+ "button will take you to file:/home.</qt>").arg(getlogin()));
+ coll->action( "back" )->plug( toolbar );
+ coll->action( "back" )->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history."));
+ coll->action( "forward" )->plug( toolbar );
+ coll->action( "forward" )->setWhatsThis(i18n("Click this button to move forward one step in the browsing history."));
+ coll->action( "reload" )->plug( toolbar );
+ coll->action( "reload" )->setWhatsThis(i18n("Click this button to reload the contents of the current location."));
+ coll->action( "mkdir" )->setShortcut(Key_F10);
+ coll->action( "mkdir" )->plug( toolbar );
+ coll->action( "mkdir" )->setWhatsThis(i18n("Click this button to create a new directory."));
+
+ d->bookmarkHandler = new KFileBookmarkHandler( this );
+ toolbar->insertButton(QString::fromLatin1("bookmark"),
+ (int)HOTLIST_BUTTON, true,
+ i18n("Bookmarks"));
+ toolbar->getButton(HOTLIST_BUTTON)->setPopup( d->bookmarkHandler->menu(),
+ true);
+ QWhatsThis::add(toolbar->getButton(HOTLIST_BUTTON),
+ i18n("<qt>This button allows you to bookmark specific locations. "
+ "Click on this button to open the bookmark menu where you may add, "
+ "edit or select a bookmark.<p>"
+ "These bookmarks are specific to the file dialog, but otherwise operate "
+ "like bookmarks elsewhere in KDE.</qt>"));
+ connect( d->bookmarkHandler, SIGNAL( openURL( const QString& )),
+ SLOT( enterURL( const QString& )));
+
+ KToggleAction *showSidebarAction =
+ new KToggleAction(i18n("Show Quick Access Navigation Panel"), Key_F9, coll,"toggleSpeedbar");
+ connect( showSidebarAction, SIGNAL( toggled( bool ) ),
+ SLOT( toggleSpeedbar( bool )) );
+
+ KActionMenu *menu = new KActionMenu( i18n("Configure"), "configure", this, "extra menu" );
+ menu->setWhatsThis(i18n("<qt>This is the configuration menu for the file dialog. "
+ "Various options can be accessed from this menu including: <ul>"
+ "<li>how files are sorted in the list</li>"
+ "<li>types of view, including icon and list</li>"
+ "<li>showing of hidden files</li>"
+ "<li>the Quick Access navigation panel</li>"
+ "<li>file previews</li>"
+ "<li>separating directories from files</li></ul></qt>"));
+ menu->insert( coll->action( "sorting menu" ));
+ menu->insert( coll->action( "separator" ));
+ coll->action( "short view" )->setShortcut(Key_F6);
+ menu->insert( coll->action( "short view" ));
+ coll->action( "detailed view" )->setShortcut(Key_F7);
+ menu->insert( coll->action( "detailed view" ));
+ menu->insert( coll->action( "separator" ));
+ coll->action( "show hidden" )->setShortcut(Key_F8);
+ menu->insert( coll->action( "show hidden" ));
+ menu->insert( showSidebarAction );
+ coll->action( "preview" )->setShortcut(Key_F11);
+ menu->insert( coll->action( "preview" ));
+ coll->action( "separate dirs" )->setShortcut(Key_F12);
+ menu->insert( coll->action( "separate dirs" ));
+
+ menu->setDelayed( false );
+ connect( menu->popupMenu(), SIGNAL( aboutToShow() ),
+ ops, SLOT( updateSelectionDependentActions() ));
+ menu->plug( toolbar );
+*/
+ /*
+ * ugly little hack to have a 5 pixel space between the buttons
+ * and the combo box
+ */
+/* QWidget *spacerWidget = new QWidget(toolbar);
+//(js) spacerWidget->setMinimumWidth(spacingHint());
+//(js) spacerWidget->setMaximumWidth(spacingHint());
+ d->m_pathComboIndex = toolbar->insertWidget(-1, -1, spacerWidget);
+ toolbar->insertWidget(PATH_COMBO, 0, d->pathCombo);
+
+
+ toolbar->setItemAutoSized (PATH_COMBO);
+ toolbar->setIconText(KToolBar::IconOnly);
+ toolbar->setBarPos(KToolBar::Top);
+ toolbar->setMovingEnabled(false);
+ toolbar->adjustSize();
+
+ d->pathCombo->setCompletionObject( ops->dirCompletionObject(), false );
+
+ connect( d->pathCombo, SIGNAL( urlActivated( const KURL& )),
+ this, SLOT( enterURL( const KURL& ) ));
+ connect( d->pathCombo, SIGNAL( returnPressed( const QString& )),
+ this, SLOT( enterURL( const QString& ) ));
+ connect( d->pathCombo, SIGNAL(textChanged( const QString& )),
+ SLOT( pathComboChanged( const QString& ) ));
+ connect( d->pathCombo, SIGNAL( completion( const QString& )),
+ SLOT( dirCompletion( const QString& )));
+ connect( d->pathCombo, SIGNAL( textRotation(KCompletionBase::KeyBindingType) ),
+ d->pathCombo, SLOT( rotateText(KCompletionBase::KeyBindingType) ));
+
+ QString whatsThisText;
+
+ // the Location label/edit
+ d->locationLabel = new QLabel(i18n("&Location:"), d->mainWidget);
+ locationEdit = new KURLComboBox(KURLComboBox::Files, true,
+ d->mainWidget, "LocationEdit");
+ updateLocationWhatsThis ();
+ d->locationLabel->setBuddy(locationEdit);
+
+ // to get the completionbox-signals connected:
+ locationEdit->setHandleSignals( true );
+ (void) locationEdit->completionBox();
+
+ locationEdit->setFocus();
+// locationEdit->setCompletionObject( new KURLCompletion() );
+// locationEdit->setAutoDeleteCompletionObject( true );
+ locationEdit->setCompletionObject( ops->completionObject(), false );
+
+ connect( locationEdit, SIGNAL( returnPressed() ),
+ this, SLOT( slotOk()));
+ connect(locationEdit, SIGNAL( activated( const QString& )),
+ this, SLOT( locationActivated( const QString& ) ));
+ connect( locationEdit, SIGNAL( completion( const QString& )),
+ SLOT( fileCompletion( const QString& )));
+ connect( locationEdit, SIGNAL( textRotation(KCompletionBase::KeyBindingType) ),
+ locationEdit, SLOT( rotateText(KCompletionBase::KeyBindingType) ));
+
+ // the Filter label/edit
+ whatsThisText = i18n("<qt>This is the filter to apply to the file list. "
+ "File names that do not match the filter will not be shown.<p>"
+ "You may select from one of the preset filters in the "
+ "drop down menu, or you may enter a custom filter "
+ "directly into the text area.<p>"
+ "Wildcards such as * and ? are allowed.</qt>");
+ d->filterLabel = new QLabel(i18n("&Filter:"), d->mainWidget);
+ QWhatsThis::add(d->filterLabel, whatsThisText);
+ filterWidget = new KFileFilterCombo(d->mainWidget,
+ "KFileDialog::filterwidget");
+ QWhatsThis::add(filterWidget, whatsThisText);
+ setFilter(filter);
+ d->filterLabel->setBuddy(filterWidget);
+ connect(filterWidget, SIGNAL(filterChanged()), SLOT(slotFilterChanged()));
+
+ // the Automatically Select Extension checkbox
+ // (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig())
+ d->autoSelectExtCheckBox = new QCheckBox (d->mainWidget);
+ connect(d->autoSelectExtCheckBox, SIGNAL(clicked()), SLOT(slotAutoSelectExtClicked()));
+
+ initGUI(); // activate GM
+
+ readRecentFiles( config );
+
+ adjustSize();
+
+ // we set the completionLock to avoid entering pathComboChanged() when
+ // inserting the list of URLs into the combo.
+ d->completionLock = true;
+ ops->setViewConfig( config, ConfigGroup );
+ readConfig( config, ConfigGroup );
+ setSelection(d->selection);
+ d->completionLock = false;
+ */
+}
+
+void KexiStartupFileDialogBase::clearFilter()
+{
+ d->kde_filters = "";//(js)
+ QFileDialog::setFilter(""); //(js);
+//todo d->mimetypes.clear();
+//todo d->hasDefaultFilter = false;
+
+ updateAutoSelectExtension ();
+}
+
+KFile::Mode KexiStartupFileDialogBase::mode() const
+{
+ return d->mode;
+}
+
+void KexiStartupFileDialogBase::setMode( KFile::Mode m )
+{
+ //(js) translate mode for QFileDialog
+ d->mode = m;
+ QFileDialog::Mode qm = (QFileDialog::Mode)0;
+ if (m & KFile::File) qm = Mode(qm | QFileDialog::AnyFile);
+ else if (m & KFile::Directory) qm = Mode(qm | QFileDialog::DirectoryOnly);
+ if (m & KFile::Files) qm = Mode(qm | QFileDialog::ExistingFiles);
+ if (m & KFile::ExistingOnly) qm = Mode(qm | QFileDialog::ExistingFile);
+
+ QFileDialog::setMode( qm );
+/*(js) ops->setMode(m);
+ if ( ops->dirOnlyMode() ) {
+//(js) filterWidget->setDefaultFilter( i18n("*|All Directories") );
+ }
+ else {
+//(js) filterWidget->setDefaultFilter( i18n("*|All Files") );
+ }
+
+ updateAutoSelectExtension ();*/
+}
+
+void KexiStartupFileDialogBase::setMode( unsigned int m )
+{
+ setMode(static_cast<KFile::Mode>( m ));
+}
+
+void KexiStartupFileDialogBase::setOperationMode( KFileDialog::OperationMode mode )
+{
+// d->operationMode = mode;
+ // d->keepLocation = (mode == Saving);
+ if (mode == KFileDialog::Saving) {
+ setMode( KFile::File );
+ setIcon( KGlobal::iconLoader()->loadIcon("filesave", KIcon::Desktop) );
+ }
+//(js) filterWidget->setEditable( !d->hasDefaultFilter || mode != Saving );
+//(js) d->okButton->setGuiItem( (mode == Saving) ? KStdGuiItem::save() : KStdGuiItem::ok() );
+//TODO updateLocationWhatsThis ();
+ updateAutoSelectExtension ();
+}
+
+QString KexiStartupFileDialogBase::currentFilter() const
+{
+ //(js)filterWidget->currentFilter();
+
+ //we need to convert Qt filter format to KDE format
+ //Qt format: "some text (*.first *.second)" or "All (*)"
+ //KDE format: "*.first *.second" or "*"
+ QString f = selectedFilter();
+ if (f.find('(')!=-1)
+ f = f.mid(f.find('(')+1);
+ if (f.mid(f.find(')')!=-1))
+ f = f.left(f.find(')'));
+ return f;
+}
+
+void KexiStartupFileDialogBase::setFilter(const QString& filter)
+{
+ d->kde_filters = filter;
+ int pos = d->kde_filters.find('/');
+
+ // Check for an un-escaped '/', if found
+ // interpret as a MIME filter.
+
+ if (pos > 0 && filter[pos - 1] != '\\') {
+ QStringList filters = QStringList::split( " ", d->kde_filters );
+ setMimeFilter( filters );
+ return;
+ }
+ QFileDialog::setFilters( convertKFileDialogFilterToQFileDialogFilter(filter) );
+ //</js>
+//(js) ops->clearFilter();
+//(js) filterWidget->setFilter(copy);
+//(js) ops->setNameFilter(filterWidget->currentFilter());
+//(js) d->hasDefaultFilter = false;
+//(js) filterWidget->setEditable( true );
+
+ updateAutoSelectExtension ();
+}
+
+void KexiStartupFileDialogBase::setMimeFilter( const QStringList& mimeTypes,
+ const QString& defaultType )
+{
+ d->mimetypes = mimeTypes;
+//(js) filterWidget->setMimeFilter( mimeTypes, defaultType );
+
+//(js) QStringList types = QStringList::split(" ", filterWidget->currentFilter());
+//(js) types.append( QString::fromLatin1( "inode/directory" ));
+//(js) ops->clearFilter();
+//(js) ops->setMimeFilter( types );
+//(js) d->hasDefaultFilter = !defaultType.isEmpty();
+//(js) filterWidget->setEditable( !d->hasDefaultFilter ||
+//(js) d->operationMode != Saving );
+
+//TODO updateAutoSelectExtension ();
+}
+
+QString KexiStartupFileDialogBase::realStartDir(const QString& startDir)
+{
+ if (!startDir.startsWith(":"))
+ return startDir;
+ QString recentDir; //dummy
+ QString path( KFileDialog::getStartURL(startDir, recentDir).path() );
+ if (path.isEmpty())
+ return QString::null;
+ QFileInfo fi(path);
+ return fi.isDir() ? fi.absFilePath() : fi.dir(true).absPath();
+}
+
+void KexiStartupFileDialogBase::saveLastVisitedPath(const QString& path)
+{
+ if (!m_lastVisitedPathsVariable.isEmpty()) {
+ //save last visited dir path
+// QString dir = QDir(path).absPath();
+ QFileInfo fi(path);
+ QString dir( fi.isDir() ? fi.absFilePath() : fi.dir(true).absPath() );
+ if (!dir.isEmpty())
+ KRecentDirs::add(m_lastVisitedPathsVariable, dir);
+ }
+}
diff --git a/kexi/main/startup/KexiStartup_p.cpp b/kexi/main/startup/KexiStartup_p.cpp
new file mode 100644
index 000000000..df7cddc66
--- /dev/null
+++ b/kexi/main/startup/KexiStartup_p.cpp
@@ -0,0 +1,127 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "KexiStartup_p.h"
+
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <kprocess.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qapplication.h>
+
+SQLite2ToSQLite3Migration::SQLite2ToSQLite3Migration(const QString& filePath)
+: m_filePath(filePath)
+{
+ m_process = 0;
+ m_dlg = 0;
+ result = false;
+ m_run = false;
+}
+
+SQLite2ToSQLite3Migration::~SQLite2ToSQLite3Migration()
+{
+ delete m_process;
+ m_dlg->close();
+ delete m_dlg;
+}
+
+tristate SQLite2ToSQLite3Migration::run()
+{
+ if (m_run)
+ return false;
+ m_run = true;
+ const QString ksqlite2to3_app = KStandardDirs::findExe( "ksqlite2to3" );
+ if (ksqlite2to3_app.isEmpty())
+ return false;
+
+ QFileInfo fi(m_filePath);
+ if (fi.isSymLink()) {
+ m_filePath = fi.readLink();
+ fi = QFileInfo(m_filePath);
+ }
+ //remember permissions of m_filePath
+ m_restoreStat = (0==stat(QFile::encodeName(m_filePath), &m_st));
+
+ m_process = new KProcess(this, "process");
+ *m_process << ksqlite2to3_app << m_filePath;
+ m_process->setWorkingDirectory( fi.dir(true).absPath() );
+ connect( m_process, SIGNAL(receivedStderr(KProcess*,char*,int)),
+ this, SLOT(receivedStderr(KProcess*,char*,int)));
+ connect( m_process, SIGNAL(processExited(KProcess*)), this, SLOT(processExited(KProcess*)) );
+ if (!m_process->start(KProcess::NotifyOnExit, KProcess::Stderr))
+ return false;
+
+ m_dlg = new KProgressDialog(0, 0, QString::null,
+ i18n("Saving \"%1\" project file to a new \"%2\" database format...")
+ .arg(QDir::convertSeparators(QFileInfo(m_filePath).fileName())).arg("SQLite3")
+ );
+ m_dlg->setModal(true);
+ connect(m_dlg, SIGNAL(cancelClicked()), this, SLOT(cancelClicked()));
+ m_dlg->setMinimumDuration(1000);
+ m_dlg->setAutoClose(true);
+ m_dlg->progressBar()->setTotalSteps(100);
+ m_dlg->progressBar()->setProgress(0);
+ m_dlg->exec();
+
+ if (result!=true)
+ return result;
+
+ return result;
+}
+
+extern void updateProgressBar(KProgressDialog *pd, char *buffer, int buflen);
+
+void SQLite2ToSQLite3Migration::receivedStderr(KProcess *, char *buffer, int buflen)
+{
+ updateProgressBar(m_dlg, buffer, buflen);
+}
+
+void SQLite2ToSQLite3Migration::processExited(KProcess* process)
+{
+ kdDebug() << "EXIT " << process->name() << endl;
+
+ kdDebug() << process->isRunning() << " " << process->exitStatus() << endl;
+ m_dlg->close();
+ result = !process->isRunning() && 0==process->exitStatus();//m_process->normalExit();
+ kdDebug() << result.toString() << endl;
+ if (result == true) {
+ if (m_restoreStat) {
+ //restore permissions for m_filePath
+ chmod(QFile::encodeName(m_filePath), m_st.st_mode);
+ chown(QFile::encodeName(m_filePath), m_st.st_uid, m_st.st_gid);
+ }
+ }
+}
+
+void SQLite2ToSQLite3Migration::cancelClicked()
+{
+ kdDebug() << result.toString() << " cancelClicked() " <<m_process->isRunning() << " "
+ << m_process->exitStatus() << endl;
+ if (!m_process->isRunning() && 0==m_process->exitStatus())
+ return;
+ result = cancelled;
+ m_process->kill();
+}
+
+#include "KexiStartup_p.moc"
+
diff --git a/kexi/main/startup/KexiStartup_p.h b/kexi/main/startup/KexiStartup_p.h
new file mode 100644
index 000000000..1e60d7023
--- /dev/null
+++ b/kexi/main/startup/KexiStartup_p.h
@@ -0,0 +1,59 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_STARTUPHANDLER_P_H
+#define KEXI_STARTUPHANDLER_P_H
+
+#include <qobject.h>
+#include <qstring.h>
+
+#include <sys/stat.h>
+
+#include <kexiutils/tristate.h>
+
+class KProcess;
+class KProgressDialog;
+
+class SQLite2ToSQLite3Migration : public QObject
+{
+ Q_OBJECT
+ public:
+ SQLite2ToSQLite3Migration(const QString& filePath);
+ ~SQLite2ToSQLite3Migration();
+
+ tristate run();
+
+ public slots:
+ void processExited(KProcess*);
+ void receivedStderr(KProcess*,char*,int);
+ void cancelClicked();
+
+ protected:
+ QString m_filePath;
+ KProcess *m_process;
+ KProgressDialog* m_dlg;
+
+ struct stat m_st;
+ bool m_restoreStat : 1;
+ bool m_run : 1;
+
+ tristate result;
+};
+
+#endif
diff --git a/kexi/main/startup/Makefile.am b/kexi/main/startup/Makefile.am
new file mode 100644
index 000000000..a7f2f30e9
--- /dev/null
+++ b/kexi/main/startup/Makefile.am
@@ -0,0 +1,47 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_LTLIBRARIES = libkeximainstartup.la
+
+libkeximainstartup_la_SOURCES = KexiConnSelectorBase.ui KexiProjectSelectorBase.ui \
+ KexiOpenExistingFile.ui \
+ KexiNewPrjTypeSelector.ui KexiDBTitlePageBase.ui \
+ KexiServerDBNamePage.ui \
+ KexiDBTitlePage.cpp \
+ KexiConnSelector.cpp KexiProjectSelector.cpp \
+ KexiStartupDialog.cpp \
+ KexiStartupFileDialog.cpp KexiNewProjectWizard.cpp \
+ KexiStartup.cpp KexiStartup_p.cpp KexiStartupDialogTemplatesPage.cpp
+
+noinst_HEADERS = KexiStartup_p.h
+
+libkeximainstartup_la_LDFLAGS = $(all_libraries) -Wnounresolved
+libkeximainstartup_la_LIBADD = \
+ ../../widget/libkexiextendedwidgets.la
+
+libkeximainstartup_la_METASOURCES = AUTO
+
+SUBDIRS = .
+
+# kde_appsdir Where your application's menu entry (.desktop) should go to.
+# kde_icondir Where your icon should go to - better use KDE_ICON.
+# kde_sounddir Where your sounds should go to.
+# kde_htmldir Where your docs should go to. (contains lang subdirs)
+# kde_datadir Where you install application data. (Use a subdir)
+# kde_locale Where translation files should go to. (contains lang subdirs)
+# kde_cgidir Where cgi-bin executables should go to.
+# kde_confdir Where config files should go to (system-wide ones with default values).
+# kde_mimedir Where mimetypes .desktop files should go to.
+# kde_servicesdir Where services .desktop files should go to.
+# kde_servicetypesdir Where servicetypes .desktop files should go to.
+# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON).
+# kde_wallpaperdir Where general wallpapers should go to.
+# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to.
+# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS.
+# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES.
+# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES.
+# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3).
+# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3).
+
+# set the include path for X, qt and KDE
+INCLUDES= -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/main/startup -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi/widget -I$(top_builddir)/kexi/widget $(all_includes)
+
diff --git a/kexi/migration/Makefile.am b/kexi/migration/Makefile.am
new file mode 100644
index 000000000..75806ba5e
--- /dev/null
+++ b/kexi/migration/Makefile.am
@@ -0,0 +1,57 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkeximigrate.la
+
+INCLUDES = \
+ -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/kexi/widget \
+ -I$(top_builddir)/kexi/widget \
+ -I$(top_srcdir)/kexi/main/startup \
+ -I$(top_builddir)/kexi/main/startup \
+ $(all_includes)
+
+if compile_pgsql_plugin
+pgsql_dir=pqxx
+endif
+
+if compile_mysql_plugin
+mysql_dir=mysql
+endif
+
+SUBDIRS = . $(pgsql_dir) $(mysql_dir)
+
+libkeximigrate_la_METASOURCES = AUTO
+
+libkeximigrate_la_SOURCES = keximigrate.cpp importwizard.cpp migratemanager.cpp \
+ keximigratedata.cpp importoptionsdlg.cpp
+
+libkeximigrate_la_LIBADD = \
+ $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/kexi/main/libkeximain.la \
+ $(LIB_QT) $(LIB_KDECORE)
+
+libkeximigrate_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO)
+
+noinst_HEADERS = importwizard.h migratemanager_p.h
+
+keximigrateincludedir=$(includedir)/kexidb
+keximigrateinclude_HEADERS=keximigrate.h keximigratedata.h migratemanager.h
+
+kde_servicetypes_DATA = keximigration_driver.desktop
+
+KDE_OPTIONS=nofinal
+noinst_PROGRAMS = keximigratetest
+
+keximigratetest_SOURCES = keximigratetest.cpp
+keximigratetest_LDADD = libkeximigrate.la \
+ $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/kexi/main/libkeximain.la \
+ $(LIB_QT) $(LIB_KDECORE)
+
+keximigratetest_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
diff --git a/kexi/migration/configure.in.in b/kexi/migration/configure.in.in
new file mode 100644
index 000000000..6744e4319
--- /dev/null
+++ b/kexi/migration/configure.in.in
@@ -0,0 +1,7 @@
+# KexiMDB isn't built as part of Kexi right now.
+#AC_ARG_ENABLE(keximdb,
+# AC_HELP_STRING([--enable-keximdb],
+# [build KexiMDB (MS Access) plugin [default=no]]),
+# compile_keximdb_plugin=$enableval, compile_keximdb_plugin=no)
+#
+#AM_CONDITIONAL(compile_keximdb_plugin, test "x$compile_keximdb_plugin" != "xno")
diff --git a/kexi/migration/importoptionsdlg.cpp b/kexi/migration/importoptionsdlg.cpp
new file mode 100644
index 000000000..24ff3259b
--- /dev/null
+++ b/kexi/migration/importoptionsdlg.cpp
@@ -0,0 +1,111 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "importoptionsdlg.h"
+#include <widget/kexicharencodingcombobox.h>
+
+#include <qdir.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtextcodec.h>
+#include <qcheckbox.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kcombobox.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kcharsets.h>
+#include <kiconloader.h>
+
+using namespace KexiMigration;
+
+OptionsDialog::OptionsDialog( const QString& databaseFile, const QString& selectedEncoding, QWidget* parent )
+ : KDialogBase(
+ KDialogBase::Plain,
+ i18n( "Advanced Import Options" ),
+ Ok|Cancel,
+ Ok,
+ parent,
+ "KexiMigration::OptionsDialog",
+ true,
+ false
+ )
+{
+ setIcon(DesktopIcon("configure"));
+ QGridLayout *lyr = new QGridLayout( plainPage(), 4, 3, KDialogBase::marginHint(),
+ KDialogBase::spacingHint());
+
+ m_encodingComboBox = new KexiCharacterEncodingComboBox(plainPage(), selectedEncoding);
+ m_encodingComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ lyr->addWidget( m_encodingComboBox, 1, 1 );
+ QLabel* lbl = new QLabel(
+ i18n("<h3>Text encoding for Microsoft Access database</h3>\n"
+ "<p>Database file \"%1\" appears to be created by a version of Microsoft Access older than 2000.</p>"
+ "<p>In order to properly import national characters, you may need to choose a proper text encoding "
+ "if the database was created on a computer with a different character set.</p>")
+ .arg(QDir::convertSeparators(databaseFile)), plainPage());
+ lbl->setAlignment( Qt::AlignAuto | Qt::WordBreak );
+ lbl->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ lyr->addMultiCellWidget( lbl, 0, 0, 0, 2 );
+
+ QLabel* lbl2 = new QLabel( m_encodingComboBox, i18n("Text encoding:"), plainPage());
+ lyr->addWidget( lbl2, 1, 0 );
+
+ m_chkAlwaysUseThisEncoding = new QCheckBox(
+ i18n("Always use this encoding in similar situations"), plainPage());
+ lyr->addMultiCellWidget( m_chkAlwaysUseThisEncoding, 2, 2, 1,2 );
+
+ lyr->addItem( new QSpacerItem( 20, 111, QSizePolicy::Minimum, QSizePolicy::Expanding ), 3, 1 );
+ lyr->addItem( new QSpacerItem( 121, 20, QSizePolicy::Expanding, QSizePolicy::Minimum ), 1, 2 );
+
+ //read config
+ kapp->config()->setGroup("ImportExport");
+ QString defaultEncodingForMSAccessFiles = kapp->config()->readEntry("DefaultEncodingForMSAccessFiles");
+ if (!defaultEncodingForMSAccessFiles.isEmpty()) {
+ m_encodingComboBox->setSelectedEncoding(defaultEncodingForMSAccessFiles);
+ m_chkAlwaysUseThisEncoding->setChecked(true);
+ }
+
+ adjustSize();
+ m_encodingComboBox->setFocus();
+}
+
+OptionsDialog::~OptionsDialog()
+{
+}
+
+KexiCharacterEncodingComboBox* OptionsDialog::encodingComboBox() const
+{
+ return m_encodingComboBox;
+}
+
+void OptionsDialog::accept()
+{
+ kapp->config()->setGroup("ImportExport");
+ if (m_chkAlwaysUseThisEncoding->isChecked())
+ kapp->config()->writeEntry("defaultEncodingForMSAccessFiles",
+ m_encodingComboBox->selectedEncoding());
+ else
+ kapp->config()->deleteEntry("defaultEncodingForMSAccessFiles");
+
+ KDialogBase::accept();
+}
+
+#include "importoptionsdlg.moc"
diff --git a/kexi/migration/importoptionsdlg.h b/kexi/migration/importoptionsdlg.h
new file mode 100644
index 000000000..736cb174f
--- /dev/null
+++ b/kexi/migration/importoptionsdlg.h
@@ -0,0 +1,51 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIMIGRATIONOPTIONSDIALOG_H
+#define KEXIMIGRATIONOPTIONSDIALOG_H
+
+#include <kdialogbase.h>
+
+class QCheckBox;
+class KexiCharacterEncodingComboBox;
+
+namespace KexiMigration {
+
+//! @short Import Options dialog.
+//! It is currently used for MDB driver only
+//! @todo Hardcoded. Move such code to KexiMigrate drivers.
+class OptionsDialog : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ OptionsDialog( const QString& databaseFile, const QString& selectedEncoding, QWidget* parent = 0 );
+ virtual ~OptionsDialog();
+
+ KexiCharacterEncodingComboBox* encodingComboBox() const;
+
+ protected slots:
+ virtual void accept();
+
+ protected:
+ KexiCharacterEncodingComboBox *m_encodingComboBox;
+ QCheckBox *m_chkAlwaysUseThisEncoding;
+};
+}
+
+#endif
diff --git a/kexi/migration/importwizard.cpp b/kexi/migration/importwizard.cpp
new file mode 100644
index 000000000..dda3d200d
--- /dev/null
+++ b/kexi/migration/importwizard.cpp
@@ -0,0 +1,1031 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.net>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "importwizard.h"
+#include "keximigrate.h"
+#include "importoptionsdlg.h"
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qvbuttongroup.h>
+#include <qradiobutton.h>
+#include <qcheckbox.h>
+
+#include <kcombobox.h>
+#include <kmessagebox.h>
+#include <kpushbutton.h>
+#include <kdebug.h>
+#include <klineedit.h>
+#include <kiconloader.h>
+#include <kbuttonbox.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver.h>
+#include <kexidb/connectiondata.h>
+#include <kexidb/utils.h>
+#include <core/kexidbconnectionset.h>
+#include <core/kexi.h>
+#include <KexiConnSelector.h>
+#include <KexiProjectSelector.h>
+#include <KexiOpenExistingFile.h>
+#include <KexiDBTitlePage.h>
+#include <kexiutils/utils.h>
+#include <kexidbdrivercombobox.h>
+#include <kexitextmsghandler.h>
+#include <widget/kexicharencodingcombobox.h>
+#include <widget/kexiprjtypeselector.h>
+
+
+using namespace KexiMigration;
+
+//===========================================================
+//
+ImportWizard::ImportWizard(QWidget *parent, QMap<QString,QString>* args)
+ : KWizard(parent)
+ , m_args(args)
+{
+ setCaption(i18n("Import Database"));
+ setIcon(DesktopIcon("database_import"));
+ m_prjSet = 0;
+ m_fileBasedDstWasPresented = false;
+ m_setupFileBasedSrcNeeded = true;
+ m_importExecuted = false;
+ m_srcTypeCombo = 0;
+
+ setMinimumSize(400, 400);
+ parseArguments();
+ setupIntro();
+// setupSrcType();
+ setupSrcConn();
+ setupSrcDB();
+ setupDstType();
+ setupDstTitle();
+ setupDst();
+ setupImportType();
+ setupImporting();
+ setupFinish();
+
+ connect(this, SIGNAL(selected(const QString &)), this, SLOT(pageSelected(const QString &)));
+ connect(this, SIGNAL(helpClicked()), this, SLOT(helpClicked()));
+
+ if (m_predefinedConnectionData) {
+ // setup wizard for predefined server source
+ m_srcConn->showAdvancedConn();
+ setAppropriate( m_srcConnPage, false );
+ setAppropriate( m_srcDBPage, false );
+ }
+ else if (!m_predefinedDatabaseName.isEmpty()) {
+ // setup wizard for predefined source
+ // (used when external project type was opened in Kexi, e.g. mdb file)
+// MigrateManager manager;
+// QString driverName = manager.driverForMimeType( m_predefinedMimeType );
+// m_srcTypeCombo->setCurrentText( driverName );
+
+// showPage( m_srcConnPage );
+ m_srcConn->showSimpleConn();
+ m_srcConn->setSelectedFileName(m_predefinedDatabaseName);
+
+ //disable all prev pages except "welcome" page
+ for (int i=0; i<indexOf(m_dstTypePage); i++) {
+ if (page(i)!=m_introPage)
+ setAppropriate( page(i), false );
+ }
+ }
+
+ m_sourceDBEncoding = QString::fromLatin1(KGlobal::locale()->encoding()); //default
+}
+
+//===========================================================
+//
+ImportWizard::~ImportWizard()
+{
+ delete m_prjSet;
+}
+
+//===========================================================
+//
+void ImportWizard::parseArguments()
+{
+ m_predefinedConnectionData = 0;
+ if (!m_args)
+ return;
+ if (!(*m_args)["databaseName"].isEmpty() && !(*m_args)["mimeType"].isEmpty()) {
+ m_predefinedDatabaseName = (*m_args)["databaseName"];
+ m_predefinedMimeType = (*m_args)["mimeType"];
+ if (m_args->contains("connectionData")) {
+ m_predefinedConnectionData = new KexiDB::ConnectionData();
+ KexiDB::fromMap(
+ KexiUtils::deserializeMap((*m_args)["connectionData"]), *m_predefinedConnectionData
+ );
+ }
+ }
+ m_args->clear();
+}
+
+//===========================================================
+//
+void ImportWizard::setupIntro()
+{
+ m_introPage = new QWidget(this);
+ QVBoxLayout *vbox = new QVBoxLayout(m_introPage, KDialog::marginHint());
+
+ QLabel *lblIntro = new QLabel(m_introPage);
+ lblIntro->setAlignment( Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak );
+ QString msg;
+ if (m_predefinedConnectionData) { //predefined import: server source
+ msg = i18n("<qt>Database Importing wizard is about to import \"%1\" database "
+ "<nobr>(connection %2)</nobr> into a Kexi database.</qt>")
+ .arg(m_predefinedDatabaseName).arg(m_predefinedConnectionData->serverInfoString());
+ }
+ else if (!m_predefinedDatabaseName.isEmpty()) { //predefined import: file source
+//! @todo this message is currently ok for files only
+ KMimeType::Ptr mimeTypePtr = KMimeType::mimeType(m_predefinedMimeType);
+ msg = i18n("<qt>Database Importing wizard is about to import <nobr>\"%1\"</nobr> file "
+ "of type \"%2\" into a Kexi database.</qt>")
+ .arg(QDir::convertSeparators(m_predefinedDatabaseName)).arg(mimeTypePtr->comment());
+ }
+ else {
+ msg = i18n("Database Importing wizard allows you to import an existing database "
+ "into a Kexi database.");
+ }
+ lblIntro->setText(msg+"\n\n"
+ +i18n("Click \"Next\" button to continue or \"Cancel\" button to exit this wizard."));
+ vbox->addWidget( lblIntro );
+ addPage(m_introPage, i18n("Welcome to the Database Importing Wizard"));
+}
+
+//===========================================================
+//
+/*
+void ImportWizard::setupSrcType()
+{
+ m_srcTypePage = new QWidget(this);
+
+//! @todo Would be good if KexiDBDriverComboBox worked for migration drivers
+ QVBoxLayout *vbox = new QVBoxLayout(m_srcTypePage, KDialog::marginHint());
+
+ QHBoxLayout *hbox = new QHBoxLayout(vbox);
+ QLabel *lbl = new QLabel(i18n("Source database type:")+" ", m_srcTypePage);
+ hbox->addWidget(lbl);
+
+ m_srcTypeCombo = new KComboBox(m_srcTypePage);
+ hbox->addWidget(m_srcTypeCombo);
+ hbox->addStretch(1);
+ vbox->addStretch(1);
+ lbl->setBuddy(m_srcTypeCombo);
+
+ MigrateManager manager;
+ QStringList names = manager.driverNames();
+
+ m_srcTypeCombo->insertStringList(names);
+ addPage(m_srcTypePage, i18n("Select Source Database Type"));
+}
+*/
+//===========================================================
+//
+void ImportWizard::setupSrcConn()
+{
+ m_srcConnPage = new QWidget(this);
+ QVBoxLayout *vbox = new QVBoxLayout(m_srcConnPage, KDialog::marginHint());
+
+ m_srcConn = new KexiConnSelectorWidget(Kexi::connset(),
+ ":ProjectMigrationSourceDir", m_srcConnPage, "m_srcConnSelector");
+
+ m_srcConn->hideConnectonIcon();
+ m_srcConn->showSimpleConn();
+
+ QStringList excludedFilters;
+//! @todo remove when support for kexi files as source prj is added in migration
+ excludedFilters += KexiDB::Driver::defaultFileBasedDriverMimeType();
+ excludedFilters += "application/x-kexiproject-shortcut";
+ excludedFilters += "application/x-kexi-connectiondata";
+ m_srcConn->m_fileDlg->setExcludedFilters(excludedFilters);
+
+// m_srcConn->hideHelpers();
+ vbox->addWidget(m_srcConn);
+ addPage(m_srcConnPage, i18n("Select Location for Source Database"));
+}
+
+//===========================================================
+//
+void ImportWizard::setupSrcDB()
+{
+// arrivesrcdbPage creates widgets on that page
+ m_srcDBPage = new QWidget(this);
+ m_srcDBName = NULL;
+ addPage(m_srcDBPage, i18n("Select Source Database"));
+}
+
+//===========================================================
+//
+void ImportWizard::setupDstType()
+{
+ m_dstTypePage = new QWidget(this);
+
+ KexiDB::DriverManager manager;
+ KexiDB::Driver::InfoMap drvs = manager.driversInfo();
+
+ QVBoxLayout *vbox = new QVBoxLayout(m_dstTypePage, KDialog::marginHint());
+
+ QHBoxLayout *hbox = new QHBoxLayout(vbox);
+ QLabel *lbl = new QLabel(i18n("Destination database type:")+" ", m_dstTypePage);
+ lbl->setAlignment(Qt::AlignAuto|Qt::AlignTop);
+ hbox->addWidget(lbl);
+
+ m_dstPrjTypeSelector = new KexiPrjTypeSelector(m_dstTypePage);
+ hbox->addWidget(m_dstPrjTypeSelector);
+ m_dstPrjTypeSelector->option_file->setText(i18n("Database project stored in a file"));
+ m_dstPrjTypeSelector->option_server->setText(i18n("Database project stored on a server"));
+
+ QVBoxLayout *frame_server_vbox = new QVBoxLayout(m_dstPrjTypeSelector->frame_server, KDialog::spacingHint());
+ m_dstServerTypeCombo = new KexiDBDriverComboBox(m_dstPrjTypeSelector->frame_server, drvs,
+ KexiDBDriverComboBox::ShowServerDrivers);
+ frame_server_vbox->addWidget(m_dstServerTypeCombo);
+ hbox->addStretch(1);
+ vbox->addStretch(1);
+ lbl->setBuddy(m_dstServerTypeCombo);
+
+//! @todo hardcoded: find a way to preselect default engine item
+ //m_dstTypeCombo->setCurrentText("SQLite3");
+ addPage(m_dstTypePage, i18n("Select Destination Database Type"));
+}
+
+//===========================================================
+//
+void ImportWizard::setupDstTitle()
+{
+ m_dstTitlePage = new KexiDBTitlePage(i18n("Destination project's caption:"),
+ this, "KexiDBTitlePage");
+ m_dstTitlePage->layout()->setMargin( KDialog::marginHint() );
+ m_dstTitlePage->updateGeometry();
+ m_dstNewDBNameLineEdit = m_dstTitlePage->le_caption;
+ addPage(m_dstTitlePage, i18n("Select Destination Database Project's Caption"));
+}
+
+//===========================================================
+//
+void ImportWizard::setupDst()
+{
+ m_dstPage = new QWidget(this);
+ QVBoxLayout *vbox = new QVBoxLayout(m_dstPage, KDialog::marginHint());
+
+ m_dstConn = new KexiConnSelectorWidget(Kexi::connset(),
+ ":ProjectMigrationDestinationDir", m_dstPage, "m_dstConnSelector");
+ m_dstConn->hideHelpers();
+ //me: Can't connect m_dstConn->m_fileDlg here, it doesn't exist yet
+ //connect(this, SLOT(next()), m_dstConn->m_fileDlg, SIGNAL(accepted()));
+
+ vbox->addWidget( m_dstConn );
+ connect(m_dstConn,SIGNAL(connectionItemExecuted(ConnectionDataLVItem*)),
+ this,SLOT(next()));
+
+// m_dstConn->hideHelpers();
+ m_dstConn->showSimpleConn();
+ //anyway, db files will be _saved_
+ m_dstConn->m_fileDlg->setMode( KexiStartupFileDialog::SavingFileBasedDB );
+// m_dstConn->hideHelpers();
+// m_dstConn->m_file->btn_advanced->hide();
+// m_dstConn->m_file->label->hide();
+// m_dstConn->m_file->lbl->hide();
+ //m_dstConn->m_file->spacer7->hide();
+
+
+ //js dstNewDBName = new KLineEdit(dstControls);
+ // dstNewDBName->setText(i18n("Enter new database name here"));
+ addPage(m_dstPage, i18n("Select Location for Destination Database"));
+}
+
+//===========================================================
+//
+void ImportWizard::setupImportType()
+{
+ m_importTypePage = new QWidget(this);
+ QVBoxLayout *vbox = new QVBoxLayout(m_importTypePage, KDialog::marginHint());
+ m_importTypeButtonGroup = new QVButtonGroup(m_importTypePage);
+ m_importTypeButtonGroup->setLineWidth(0);
+ vbox->addWidget( m_importTypeButtonGroup );
+
+ (void)new QRadioButton(i18n("Structure and data"), m_importTypeButtonGroup);
+ (void)new QRadioButton(i18n("Structure only"), m_importTypeButtonGroup);
+
+ m_importTypeButtonGroup->setExclusive( true );
+ m_importTypeButtonGroup->setButton( 0 );
+ addPage(m_importTypePage, i18n("Select Type of Import"));
+}
+
+//===========================================================
+//
+void ImportWizard::setupImporting()
+{
+ m_importingPage = new QWidget(this);
+ m_importingPage->hide();
+ QVBoxLayout *vbox = new QVBoxLayout(m_importingPage, KDialog::marginHint());
+ m_lblImportingTxt = new QLabel(m_importingPage);
+ m_lblImportingTxt->setAlignment( Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak );
+
+ m_lblImportingErrTxt = new QLabel(m_importingPage);
+ m_lblImportingErrTxt->setAlignment( Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak );
+
+ m_progressBar = new KProgress(100, m_importingPage);
+ m_progressBar->hide();
+
+ vbox->addWidget( m_lblImportingTxt );
+ vbox->addWidget( m_lblImportingErrTxt );
+ vbox->addStretch(1);
+
+ KButtonBox *optionsBox = new KButtonBox(m_importingPage);
+ vbox->addWidget( optionsBox );
+ m_importOptionsButton = optionsBox->addButton(i18n("Advanced Options"), this, SLOT(slotOptionsButtonClicked()));
+ m_importOptionsButton->setIconSet(SmallIconSet("configure"));
+ optionsBox->addStretch(1);
+
+ vbox->addWidget( m_progressBar );
+
+ vbox->addStretch(2);
+
+ m_importingPage->show();
+
+ addPage(m_importingPage, i18n("Importing"));
+}
+
+//===========================================================
+//
+void ImportWizard::setupFinish()
+{
+ m_finishPage = new QWidget(this);
+ m_finishPage->hide();
+ QVBoxLayout *vbox = new QVBoxLayout(m_finishPage, KDialog::marginHint());
+ m_finishLbl = new QLabel(m_finishPage);
+ m_finishLbl->setAlignment( Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak );
+
+ vbox->addWidget( m_finishLbl );
+ m_openImportedProjectCheckBox = new QCheckBox(i18n("Open imported project"),
+ m_finishPage, "openImportedProjectCheckBox");
+ m_openImportedProjectCheckBox->setChecked(true);
+ vbox->addSpacing( KDialog::spacingHint() );
+ vbox->addWidget( m_openImportedProjectCheckBox );
+ vbox->addStretch(1);
+
+ addPage(m_finishPage, i18n("Success"));
+}
+
+//===========================================================
+//
+bool ImportWizard::checkUserInput()
+{
+ QString finishtxt;
+
+ if (m_dstNewDBNameLineEdit->text().isEmpty())
+ {
+ finishtxt = finishtxt + "<br>" + i18n("No new database name was entered.");
+ }
+
+ Kexi::ObjectStatus result;
+ KexiMigrate* sourceDriver = prepareImport(result);
+ if (sourceDriver && sourceDriver->isSourceAndDestinationDataSourceTheSame())
+ {
+ finishtxt = finishtxt + "<br>" + i18n("Source database is the same as destination.");
+ }
+
+ if (! finishtxt.isNull())
+ {
+ finishtxt = "<qt>" + i18n("Following problems were found with the data you entered:") +
+ "<br>" + finishtxt + "<br><br>" +
+ i18n("Please click 'Back' button and correct these errors.");
+ m_lblImportingErrTxt->setText(finishtxt);
+ }
+
+ return finishtxt.isNull();
+}
+
+void ImportWizard::arriveSrcConnPage()
+{
+ m_srcConnPage->hide();
+
+// checkIfSrcTypeFileBased(m_srcTypeCombo->currentText());
+// if (fileBasedSrcSelected()) {
+//moved m_srcConn->showSimpleConn();
+ /*! @todo KexiStartupFileDialog needs "open file" and "open server" modes
+ in addition to just "open" */
+ if (m_setupFileBasedSrcNeeded) {
+ m_setupFileBasedSrcNeeded = false;
+ QStringList additionalMimeTypes;
+ /* moved
+ if (m_srcTypeCombo->currentText().contains("Access")) {
+ //! @todo tmp: hardcoded!
+ additionalMimeTypes << "application/x-msaccess";
+ }*/
+ m_srcConn->m_fileDlg->setMode(KexiStartupFileDialog::Opening);
+ m_srcConn->m_fileDlg->setAdditionalFilters(additionalMimeTypes);
+/*moved if (m_srcTypeCombo->currentText().contains("Access")) {
+ //! @todo tmp: hardcoded!
+ #ifdef Q_WS_WIN
+ m_srcConn->m_fileDlg->setSelectedFilter("*.mdb");
+ #else
+ m_srcConn->m_fileDlg->setFilter("*.mdb");
+ #endif
+ }*/
+ //m_srcConn->m_file->label->hide();
+ //m_srcConn->m_file->btn_advanced->hide();
+ //m_srcConn->m_file->label->parentWidget()->hide();
+ }
+// } else {
+// m_srcConn->showAdvancedConn();
+// }
+ /*! @todo Support different file extensions based on MigrationDriver */
+ m_srcConnPage->show();
+}
+
+void ImportWizard::arriveSrcDBPage()
+{
+ if (fileBasedSrcSelected()) {
+ //! @todo Back button doesn't work after selecting a file to import
+ //moved showPage(m_dstTypePage);
+ }
+ else if (!m_srcDBName) {
+ m_srcDBPage->hide();
+ kdDebug() << "Looks like we need a project selector widget!" << endl;
+
+ KexiDB::ConnectionData* condata = m_srcConn->selectedConnectionData();
+ if(condata) {
+ m_prjSet = new KexiProjectSet(*condata);
+ QVBoxLayout *vbox = new QVBoxLayout(m_srcDBPage, KDialog::marginHint());
+ m_srcDBName = new KexiProjectSelectorWidget(m_srcDBPage,
+ "KexiMigrationProjectSelector", m_prjSet);
+ vbox->addWidget( m_srcDBName );
+ m_srcDBName->label->setText(i18n("Select source database you wish to import:"));
+ }
+ m_srcDBPage->show();
+ }
+}
+
+void ImportWizard::arriveDstTitlePage()
+{
+ if(fileBasedSrcSelected()) {
+ QString suggestedDBName( QFileInfo(m_srcConn->selectedFileName()).fileName() );
+ const QFileInfo fi( suggestedDBName );
+ suggestedDBName = suggestedDBName.left(suggestedDBName.length()
+ - (fi.extension().length() ? (fi.extension().length()+1) : 0));
+ m_dstNewDBNameLineEdit->setText( suggestedDBName );
+ } else {
+ if (m_predefinedConnectionData) {
+ // server source db is predefined
+ m_dstNewDBNameLineEdit->setText( m_predefinedDatabaseName );
+ }
+ else {
+ if (!m_srcDBName || !m_srcDBName->selectedProjectData()) {
+ back(); //todo!
+ return;
+ }
+ m_dstNewDBNameLineEdit->setText( m_srcDBName->selectedProjectData()->databaseName() );
+ }
+ }
+}
+
+void ImportWizard::arriveDstPage()
+{
+ m_dstPage->hide();
+
+// checkIfDstTypeFileBased(m_dstTypeCombo->currentText());
+ if(fileBasedDstSelected()) {
+ m_dstConn->showSimpleConn();
+ m_dstConn->m_fileDlg->setMode( KexiStartupFileDialog::SavingFileBasedDB );
+ if (!m_fileBasedDstWasPresented) {
+ //without extension - it will be added automatically
+ m_dstConn->m_fileDlg->setLocationText(m_dstNewDBNameLineEdit->text());
+ }
+ m_fileBasedDstWasPresented = true;
+ } else {
+ m_dstConn->showAdvancedConn();
+ }
+ m_dstPage->show();
+}
+
+void ImportWizard::arriveImportingPage() {
+// checkIfDstTypeFileBased(m_dstTypeCombo->currentText());
+/*moved if (m_fileBasedDstWasPresented) {
+ if (!m_dstConn->m_fileDlg->checkFileName()) {
+ back();
+ return;
+ }
+ }*/
+ m_importingPage->hide();
+ if (checkUserInput()) {
+ setNextEnabled(m_importingPage, true);
+ }
+ else {
+ setNextEnabled(m_importingPage, false);
+ }
+
+ m_lblImportingTxt->setText(i18n(
+ "All required information has now "
+ "been gathered. Click \"Next\" button to start importing.\n\n"
+ "Depending on size of the database this may take some time."
+ /*"Note: You may be asked for extra "
+ "information such as field types if "
+ "the wizard could not automatically "
+ "determine this for you."*/));
+
+//todo
+
+ //temp. hack for MS Access driver only
+//! @todo for other databases we will need KexiMigration::Conenction
+//! and KexiMigration::Driver classes
+ bool showOptions = false;
+ if (fileBasedSrcSelected()) {
+ Kexi::ObjectStatus result;
+ KexiMigrate* sourceDriver = prepareImport(result);
+ if (sourceDriver) {
+ showOptions = !result.error()
+ && sourceDriver->propertyValue( "source_database_has_nonunicode_encoding" ).toBool();
+ KexiMigration::Data *data = sourceDriver->data();
+ sourceDriver->setData( 0 );
+ delete data;
+ }
+ }
+ if (showOptions)
+ m_importOptionsButton->show();
+ else
+ m_importOptionsButton->hide();
+
+ m_importingPage->show();
+}
+
+void ImportWizard::arriveFinishPage() {
+// backButton()->hide();
+// cancelButton()->setEnabled(false);
+// m_finishLbl->setText( m_successText.arg(m_dstNewDBNameLineEdit->text()) );
+}
+
+bool ImportWizard::fileBasedSrcSelected() const
+{
+ if (m_predefinedConnectionData)
+ return false;
+
+// kdDebug() << (m_srcConn->selectedConnectionType()==KexiConnSelectorWidget::FileBased) << endl;
+ return m_srcConn->selectedConnectionType()==KexiConnSelectorWidget::FileBased;
+}
+
+bool ImportWizard::fileBasedDstSelected() const
+{
+// QString dstType(m_dstServerTypeCombo->currentText());
+
+ return m_dstPrjTypeSelector->buttonGroup->selectedId() == 1;
+
+/* if ((dstType == "PostgreSQL") || (dstType == "MySQL")) {
+ return false;
+ } else {
+ return true;
+ }*/
+}
+
+
+void ImportWizard::progressUpdated(int percent) {
+ m_progressBar->setProgress(percent);
+ KApplication::kApplication()->processEvents();
+}
+
+//===========================================================
+//
+QString ImportWizard::driverNameForSelectedSource()
+{
+ if (fileBasedSrcSelected()) {
+ KMimeType::Ptr ptr = KMimeType::findByFileContent( m_srcConn->selectedFileName() );
+ if (!ptr || ptr.data()->name()=="application/octet-stream" || ptr.data()->name()=="text/plain") {
+ //try by URL:
+ ptr = KMimeType::findByURL( m_srcConn->selectedFileName() );
+ }
+ return ptr ? m_migrateManager.driverForMimeType( ptr.data()->name() ) : QString::null;
+ }
+
+ //server-based
+ if (m_predefinedConnectionData) {
+ return m_predefinedConnectionData->driverName;
+ }
+
+ return m_srcConn->selectedConnectionData()
+ ? m_srcConn->selectedConnectionData()->driverName : QString::null;
+}
+
+//===========================================================
+//
+void ImportWizard::accept()
+{
+ /*moved
+ backButton()->setEnabled(false);
+ finishButton()->setEnabled(false);
+// cancelButton()->setEnabled(false);
+ acceptImport();
+ backButton()->setEnabled(true);
+ finishButton()->setEnabled(true);
+// cancelButton()->setEnabled(true);
+*/
+ if (m_args) {
+ if ((!fileBasedDstSelected() && !m_args->contains("destinationConnectionShortcut"))
+ || !m_openImportedProjectCheckBox->isChecked())
+ {
+ //do not open dest db if used didn't want it
+ //for server connections, destinationConnectionShortcut must be defined
+ m_args->remove("destinationDatabaseName");
+ }
+ }
+ KWizard::accept();
+}
+
+KexiMigrate* ImportWizard::prepareImport(Kexi::ObjectStatus& result)
+{
+ KexiUtils::WaitCursor wait;
+
+ // Start with a driver manager
+ KexiDB::DriverManager manager;
+
+ kdDebug() << "Creating destination driver..." << endl;
+
+ // Get a driver to the destination database
+ KexiDB::Driver *destDriver = manager.driver(
+ m_dstConn->selectedConnectionData() ? m_dstConn->selectedConnectionData()->driverName //server based
+ : KexiDB::Driver::defaultFileBasedDriverName()
+ // : m_dstTypeCombo->currentText() //file based
+ );
+ if (!destDriver || manager.error())
+ {
+ result.setStatus(&manager);
+ kdDebug() << "Manager error..." << endl;
+ manager.debugError();
+// result.setStatus(&manager);
+ }
+
+ // Set up destination connection data
+ KexiDB::ConnectionData *cdata;
+ bool cdataOwned = false;
+ QString dbname;
+ if (!result.error())
+ {
+ if (m_dstConn->selectedConnectionData())
+ {
+ //server-based project
+ kdDebug() << "Server destination..." << endl;
+ cdata = m_dstConn->selectedConnectionData();
+ dbname = m_dstNewDBNameLineEdit->text();
+ }
+ else // if (m_dstTypeCombo->currentText().lower() == KexiDB::Driver::defaultFileBasedDriverName())
+ {
+ //file-based project
+ kdDebug() << "File Destination..." << endl;
+ cdata = new KexiDB::ConnectionData();
+ cdataOwned = true;
+ cdata->caption = m_dstNewDBNameLineEdit->text();
+ cdata->driverName = KexiDB::Driver::defaultFileBasedDriverName();
+ dbname = m_dstConn->selectedFileName();
+ cdata->setFileName( dbname );
+ kdDebug() << "Current file name: " << dbname << endl;
+ }
+/* else
+ {
+ //TODO This needs a better message
+ //KMessageBox::error(this,
+ result.setStatus(i18n("No connection data is available. You did not select a destination filename."),"");
+ //return false;
+ } */
+ }
+
+ // Find a source (migration) driver name
+ QString sourceDriverName;
+ if (!result.error())
+ {
+ sourceDriverName = driverNameForSelectedSource();
+ if (sourceDriverName.isEmpty())
+ result.setStatus(i18n("No appropriate migration driver found."),
+ m_migrateManager.possibleProblemsInfoMsg());
+ }
+
+ // Get a source (migration) driver
+ KexiMigrate* sourceDriver = 0;
+ if (!result.error())
+ {
+ sourceDriver = m_migrateManager.driver( sourceDriverName );
+ if(!sourceDriver || m_migrateManager.error()) {
+ kdDebug() << "Import migrate driver error..." << endl;
+ result.setStatus(&m_migrateManager);
+ }
+ }
+
+ KexiUtils::removeWaitCursor();
+
+ // Set up source (migration) data required for connection
+ if (sourceDriver && !result.error())
+ {
+ // Setup progress feedback for the GUI
+ if(sourceDriver->progressSupported()) {
+ m_progressBar->updateGeometry();
+ disconnect(sourceDriver, SIGNAL(progressPercent(int)),
+ this, SLOT(progressUpdated(int)));
+ connect(sourceDriver, SIGNAL(progressPercent(int)),
+ this, SLOT(progressUpdated(int)));
+ progressUpdated(0);
+ }
+
+ bool keepData;
+ if (m_importTypeButtonGroup->selectedId() == 0)
+ {
+ kdDebug() << "Structure and data selected" << endl;
+ keepData = true;
+ }
+ else if (m_importTypeButtonGroup->selectedId() == 1)
+ {
+ kdDebug() << "structure only selected" << endl;
+ keepData = false;
+ }
+ else
+ {
+ kdDebug() << "Neither radio button is selected (not possible?) presume keep data" << endl;
+ keepData = true;
+ }
+
+ KexiMigration::Data* md = new KexiMigration::Data();
+ // delete md->destination;
+ md->destination = new KexiProjectData(*cdata, dbname);
+ if(fileBasedSrcSelected()) {
+ KexiDB::ConnectionData* conn_data = new KexiDB::ConnectionData();
+ conn_data->setFileName(m_srcConn->selectedFileName());
+ md->source = conn_data;
+ md->sourceName = "";
+ }
+ else
+ {
+ if (m_predefinedConnectionData)
+ md->source = m_predefinedConnectionData;
+ else
+ md->source = m_srcConn->selectedConnectionData();
+
+ if (!m_predefinedDatabaseName.isEmpty())
+ md->sourceName = m_predefinedDatabaseName;
+ else
+ md->sourceName = m_srcDBName->selectedProjectData()->databaseName();
+ //! @todo Aah, this is so C-like. Move to performImport().
+ }
+ md->keepData = keepData;
+ sourceDriver->setData(md);
+ return sourceDriver;
+ }
+ return 0;
+}
+
+tristate ImportWizard::import()
+{
+ m_importExecuted = true;
+
+ Kexi::ObjectStatus result;
+ KexiMigrate* sourceDriver = prepareImport(result);
+
+ bool acceptingNeeded = false;
+
+ // Perform import
+ if (sourceDriver && !result.error())
+ {
+ if (!m_sourceDBEncoding.isEmpty()) {
+ sourceDriver->setPropertyValue( "source_database_nonunicode_encoding",
+ QVariant(m_sourceDBEncoding.upper().replace(' ',"")) // "CP1250", not "cp 1250"
+ );
+ }
+
+ if (!sourceDriver->checkIfDestinationDatabaseOverwritingNeedsAccepting(&result, acceptingNeeded)) {
+ kdDebug() << "Abort import cause checkIfDestinationDatabaseOverwritingNeedsAccepting returned false." << endl;
+ return false;
+ }
+
+ kdDebug() << sourceDriver->data()->destination->databaseName() << endl;
+ kdDebug() << "Performing import..." << endl;
+ }
+
+ if (sourceDriver && !result.error() && acceptingNeeded) { // ok, the destination-db already exists...
+ if (KMessageBox::Yes != KMessageBox::warningYesNo(this,
+ "<qt>"+i18n("Database %1 already exists."
+ "<p>Do you want to replace it with a new one?")
+ .arg(sourceDriver->data()->destination->infoString()),
+ 0, KGuiItem(i18n("&Replace")), KGuiItem(i18n("No"))))
+ {
+ return cancelled;
+ }
+ }
+
+ if (sourceDriver && !result.error() && sourceDriver->progressSupported()) {
+ m_progressBar->show();
+ }
+
+ if (sourceDriver && !result.error() && sourceDriver->performImport(&result))
+ {
+ if (m_args) {
+// if (fileBasedDstSelected()) {
+ m_args->insert("destinationDatabaseName",
+ sourceDriver->data()->destination->databaseName());
+// }
+ QString destinationConnectionShortcut(
+ Kexi::connset().fileNameForConnectionData( m_dstConn->selectedConnectionData() ) );
+ if (!destinationConnectionShortcut.isEmpty()) {
+ m_args->insert("destinationConnectionShortcut", destinationConnectionShortcut);
+ }
+ }
+ setTitle(m_finishPage, i18n("Success"));
+ return true;
+ }
+
+ if (!sourceDriver || result.error())
+ {
+ m_progressBar->setProgress(0);
+ m_progressBar->hide();
+
+ QString msg, details;
+ KexiTextMessageHandler handler(msg, details);
+ handler.showErrorMessage(&result);
+
+ kdDebug() << msg << "\n" << details << endl;
+ setTitle(m_finishPage, i18n("Failure"));
+ m_finishLbl->setText(
+ i18n("<p>Import failed.</p>%1<p>%2</p><p>You can click \"Back\" button and try again.</p>")
+ .arg(msg).arg(details));
+ return false;
+ }
+// delete kexi_conn;
+ return true;
+}
+
+void ImportWizard::reject()
+{
+ KWizard::reject();
+}
+
+//===========================================================
+//
+void ImportWizard::next()
+{
+ if (currentPage() == m_srcConnPage) {
+ if (fileBasedSrcSelected()
+ && /*! @todo use KURL? */!QFileInfo(m_srcConn->selectedFileName()).isFile()) {
+
+ KMessageBox::sorry(this,i18n("Select source database filename."));
+ return;
+ }
+
+ if ( (! fileBasedSrcSelected()) && (! m_srcConn->selectedConnectionData()) ) {
+ KMessageBox::sorry(this,i18n("Select source database."));
+ return;
+ }
+
+ KexiMigrate* import = m_migrateManager.driver( driverNameForSelectedSource() );
+ if(!import || m_migrateManager.error()) {
+ QString dbname;
+ if (fileBasedSrcSelected())
+ dbname = m_srcConn->selectedFileName();
+ else
+ dbname = m_srcConn->selectedConnectionData()
+ ? m_srcConn->selectedConnectionData()->serverInfoString() : QString::null;
+ if (!dbname.isEmpty())
+ dbname = QString(" \"%1\"").arg(dbname);
+ KMessageBox::error(this, i18n("Could not import database%1. This type is not supported.")
+ .arg(dbname));
+ return;
+ }
+ }
+ else if (currentPage() == m_dstPage) {
+ if (m_fileBasedDstWasPresented) {
+ if (fileBasedDstSelected() && !m_dstConn->m_fileDlg->checkFileName())
+ return;
+ }
+ }
+ else if (currentPage() == m_importingPage) {
+ if (!m_importExecuted) {
+ m_importOptionsButton->hide();
+ nextButton()->setEnabled(false);
+ finishButton()->setEnabled(false);
+ backButton()->setEnabled(false);
+ m_lblImportingTxt->setText(i18n("Importing in progress..."));
+ tristate res = import();
+ if (true == res) {
+ m_finishLbl->setText(
+ i18n("Database has been imported into Kexi database project \"%1\".")
+ .arg(m_dstNewDBNameLineEdit->text()) );
+ cancelButton()->setEnabled(false);
+ setBackEnabled(m_finishPage, false);
+ setFinishEnabled(m_finishPage, true);
+ m_openImportedProjectCheckBox->show();
+ next();
+ return;
+ }
+
+ m_progressBar->hide();
+ cancelButton()->setEnabled(true);
+ setBackEnabled(m_finishPage, true);
+ setFinishEnabled(m_finishPage, false);
+ m_openImportedProjectCheckBox->hide();
+ if (!res)
+ next();
+ else if (~res) {
+ arriveImportingPage();
+ // back();
+ }
+ m_importExecuted = false;
+ return;
+ }
+ }
+
+ setAppropriate( m_srcDBPage, !fileBasedSrcSelected() && !m_predefinedConnectionData ); //skip m_srcDBPage
+ KWizard::next();
+}
+
+void ImportWizard::back()
+{
+ setAppropriate( m_srcDBPage, !fileBasedSrcSelected() && !m_predefinedConnectionData ); //skip m_srcDBPage
+ KWizard::back();
+}
+
+void ImportWizard::pageSelected(const QString &)
+{
+ if (currentPage() == m_introPage) {
+ }
+// else if (currentPage() == m_srcTypePage) {
+// }
+ else if (currentPage() == m_srcConnPage) {
+ arriveSrcConnPage();
+ }
+ else if (currentPage() == m_srcDBPage) {
+ arriveSrcDBPage();
+ }
+ else if (currentPage() == m_dstTypePage) {
+ }
+ else if (currentPage() == m_dstTitlePage) {
+ arriveDstTitlePage();
+ }
+ else if (currentPage() == m_dstPage) {
+ arriveDstPage();
+ }
+ else if (currentPage() == m_importingPage) {
+ arriveImportingPage();
+ }
+ else if (currentPage() == m_finishPage) {
+ arriveFinishPage();
+ }
+}
+
+void ImportWizard::helpClicked()
+{
+ if (currentPage() == m_introPage)
+ {
+ KMessageBox::information(this, i18n("No help is available for this page."), i18n("Help"));
+ }
+/* else if (currentPage() == m_srcTypePage)
+ {
+ KMessageBox::information(this, i18n("Here you can choose the type of data to import data from."), i18n("Help"));
+ }*/
+ else if (currentPage() == m_srcConnPage)
+ {
+ KMessageBox::information(this, i18n("Here you can choose the location to import data from."), i18n("Help"));
+ }
+ else if (currentPage() == m_srcDBPage)
+ {
+ KMessageBox::information(this, i18n("Here you can choose the actual database to import data from."), i18n("Help"));
+ }
+ else if (currentPage() == m_dstTypePage)
+ {
+ KMessageBox::information(this, i18n("Here you can choose the location to save the data."), i18n("Help"));
+ }
+ else if (currentPage() == m_dstPage)
+ {
+ KMessageBox::information(this, i18n("Here you can choose the location to save the data in and the new database name."), i18n("Help"));
+ }
+ else if (currentPage() == m_finishPage || currentPage() == m_importingPage)
+ {
+ KMessageBox::information(this, i18n("No help is available for this page."), i18n("Help"));
+ }
+}
+
+void ImportWizard::slotOptionsButtonClicked()
+{
+ OptionsDialog dlg(m_srcConn->selectedFileName(), m_sourceDBEncoding, this);
+ if (QDialog::Accepted != dlg.exec())
+ return;
+
+ if (m_sourceDBEncoding != dlg.encodingComboBox()->selectedEncoding()) {
+ m_sourceDBEncoding = dlg.encodingComboBox()->selectedEncoding();
+ }
+}
+
+#include "importwizard.moc"
diff --git a/kexi/migration/importwizard.h b/kexi/migration/importwizard.h
new file mode 100644
index 000000000..7d51a9341
--- /dev/null
+++ b/kexi/migration/importwizard.h
@@ -0,0 +1,153 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.net>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIMIGRATIONIMPORTWIZARD_H
+#define KEXIMIGRATIONIMPORTWIZARD_H
+
+#include <kwizard.h>
+#include <kprogress.h>
+#include <kapplication.h>
+
+#include <kexiutils/tristate.h>
+#include "migratemanager.h"
+
+class QLabel;
+class QCheckBox;
+class QPushButton;
+class QHBoxLayout;
+class QVBoxLayout;
+class QVButtonGroup;
+class KComboBox;
+class KListView;
+class KLineEdit;
+class KexiConnSelectorWidget;
+class KexiProjectSelectorWidget;
+class KexiProjectSet;
+class KexiDBTitlePage;
+class KexiDBDriverComboBox;
+class KexiPrjTypeSelector;
+
+namespace Kexi
+{
+ class ObjectStatus;
+}
+
+namespace KexiDB
+{
+ class ConnectionData;
+}
+
+namespace KexiMigration {
+
+class KexiMigrate;
+
+//! GUI for importing external databases (file-based and server-based)
+class KEXIMIGR_EXPORT ImportWizard : public KWizard
+{
+Q_OBJECT
+public:
+ /*! Creates wizard's instance.
+ \a args contains arguments that can be parsed by parseArguments().
+ \a *arg will be also set to imported project's filename on success
+ and to null value on failure or cancellation. */
+ ImportWizard(QWidget *parent = 0, QMap<QString,QString>* args = 0);
+ virtual ~ImportWizard();
+
+public slots:
+ void progressUpdated(int percent);
+
+protected slots:
+ virtual void next();
+ virtual void back();
+ void pageSelected(const QString &);
+ virtual void accept();
+ virtual void reject();
+ void helpClicked();
+ void slotOptionsButtonClicked();
+
+private:
+ void parseArguments();
+ void setupIntro();
+// void setupSrcType();
+ void setupSrcConn();
+ void setupSrcDB();
+ void setupDstType();
+ void setupDstTitle();
+ void setupDst();
+ void setupFinish();
+ void setupImportType();
+ void setupImporting();
+ bool checkUserInput();
+
+ KexiMigrate* prepareImport(Kexi::ObjectStatus& result);
+
+ /*! Performs import. \return true/false on success/faulure
+ or cancelled when user cancelled importing (mainly
+ because didn't allow overwriting an existing database by a new one). */
+ tristate import();
+
+ bool fileBasedSrcSelected() const;
+ bool fileBasedDstSelected() const;
+ QString driverNameForSelectedSource();
+// void checkIfSrcTypeFileBased(const QString& srcType);
+// void checkIfDstTypeFileBased(const QString& dstType);
+
+ void arriveSrcConnPage();
+ void arriveSrcDBPage();
+ void arriveDstTitlePage();
+ void arriveDstPage();
+ void arriveFinishPage();
+ void arriveImportingPage();
+
+ QWidget *m_introPage, /* *m_srcTypePage,*/ *m_srcConnPage, *m_srcDBPage,
+ *m_dstTypePage, *m_dstPage, *m_importTypePage, *m_importingPage, *m_finishPage;
+
+ QVButtonGroup *m_importTypeButtonGroup;
+ KexiDBTitlePage* m_dstTitlePage;
+
+ KComboBox *m_srcTypeCombo;
+ KexiDBDriverComboBox *m_dstServerTypeCombo;
+ KexiPrjTypeSelector *m_dstPrjTypeSelector;
+
+ KexiConnSelectorWidget *m_srcConn, *m_dstConn;
+ KLineEdit *m_dstNewDBNameLineEdit;
+ KexiProjectSelectorWidget *m_srcDBName;
+
+ QLabel *m_lblImportingTxt, *m_lblImportingErrTxt, *m_finishLbl;
+ QCheckBox *m_openImportedProjectCheckBox;
+ bool m_fileBasedDstWasPresented, m_setupFileBasedSrcNeeded,
+ m_importExecuted; //!< used in import()
+ KexiProjectSet* m_prjSet;
+ KProgress *m_progressBar;
+ QPushButton* m_importOptionsButton;
+ QMap<QString,QString> *m_args;
+ QString m_predefinedDatabaseName, m_predefinedMimeType;
+ KexiDB::ConnectionData *m_predefinedConnectionData;
+ MigrateManager m_migrateManager; //!< object lives here, so status messages can be globally preserved
+
+ //! Encoding for source db. Currently only used for MDB driver.
+//! @todo Hardcoded. Move to KexiMigrate driver's impl.
+ QString m_sourceDBEncoding;
+};
+
+}
+
+#endif
diff --git a/kexi/migration/keximigrate.cpp b/kexi/migration/keximigrate.cpp
new file mode 100644
index 000000000..262293238
--- /dev/null
+++ b/kexi/migration/keximigrate.cpp
@@ -0,0 +1,616 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "keximigrate.h"
+
+#include <kdebug.h>
+#include <kinputdialog.h>
+#include <kapplication.h>
+
+#include <kexiutils/identifier.h>
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <kexidb/drivermanager.h>
+
+using namespace KexiDB;
+using namespace KexiMigration;
+
+KexiMigrate::KexiMigrate(QObject *parent, const char *name,
+ const QStringList&)
+ : QObject( parent, name )
+ , m_migrateData(0)
+ , m_destPrj(0)
+{
+ m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.setAutoDelete(true);
+}
+
+//! Used for computing progress:
+//! let's assume that each table creation costs the same as inserting 20 rows
+#define NUM_OF_ROWS_PER_CREATE_TABLE 20
+
+
+//=============================================================================
+// Migration parameters
+void KexiMigrate::setData(KexiMigration::Data* migrateData)
+{
+ m_migrateData = migrateData;
+}
+
+//=============================================================================
+// Destructor
+KexiMigrate::~KexiMigrate()
+{
+ delete m_destPrj;
+}
+
+bool KexiMigrate::checkIfDestinationDatabaseOverwritingNeedsAccepting(Kexi::ObjectStatus* result,
+ bool& acceptingNeeded)
+{
+ acceptingNeeded = false;
+ if (result)
+ result->clearStatus();
+
+ KexiDB::DriverManager drvManager;
+ KexiDB::Driver *destDriver = drvManager.driver(
+ m_migrateData->destination->connectionData()->driverName);
+ if (!destDriver) {
+ result->setStatus(&drvManager,
+ i18n("Could not create database \"%1\".")
+ .arg(m_migrateData->destination->databaseName()));
+ return false;
+ }
+
+ // For file-based dest. projects, we've already asked about overwriting
+ // existing project but for server-based projects we need to ask now.
+ if (destDriver->isFileDriver())
+ return true; //nothing to check
+ KexiDB::Connection *tmpConn
+ = destDriver->createConnection( *m_migrateData->destination->connectionData() );
+ if (!tmpConn || destDriver->error() || !tmpConn->connect()) {
+ delete tmpConn;
+ return true;
+ }
+ if (tmpConn->databaseExists( m_migrateData->destination->databaseName() )) {
+ acceptingNeeded = true;
+ }
+ tmpConn->disconnect();
+ delete tmpConn;
+ return true;
+}
+
+bool KexiMigrate::isSourceAndDestinationDataSourceTheSame() const
+{
+ KexiDB::ConnectionData* sourcedata = m_migrateData->source;
+ KexiDB::ConnectionData* destinationdata = m_migrateData->destination->connectionData();
+ return (
+ sourcedata && destinationdata &&
+ m_migrateData->sourceName == m_migrateData->destination->databaseName() && // same database name
+ sourcedata->driverName == destinationdata->driverName && // same driver
+ sourcedata->hostName == destinationdata->hostName && // same host
+ sourcedata->fileName() == destinationdata->fileName() && // same filename
+ sourcedata->dbPath() == destinationdata->dbPath() && // same database path
+ sourcedata->dbFileName() == destinationdata->dbFileName() // same database filename
+ );
+}
+
+//=============================================================================
+// Perform Import operation
+bool KexiMigrate::performImport(Kexi::ObjectStatus* result)
+{
+ if (result)
+ result->clearStatus();
+
+ KexiDB::DriverManager drvManager;
+ KexiDB::Driver *destDriver = drvManager.driver(
+ m_migrateData->destination->connectionData()->driverName);
+ if (!destDriver) {
+ result->setStatus(&drvManager,
+ i18n("Could not create database \"%1\".")
+ .arg(m_migrateData->destination->databaseName()));
+ return false;
+ }
+
+ QStringList tables;
+
+ // Step 1 - connect
+ kdDebug() << "KexiMigrate::performImport() CONNECTING..." << endl;
+ if (!drv_connect()) {
+ kdDebug() << "Couldnt connect to database server" << endl;
+ if (result)
+ result->setStatus(i18n("Could not connect to data source \"%1\".")
+ .arg(m_migrateData->source->serverInfoString()), "");
+ return false;
+ }
+
+ // Step 2 - get table names
+ kdDebug() << "KexiMigrate::performImport() GETTING TABLENAMES..." << endl;
+ if (!tableNames(tables)) {
+ kdDebug() << "Couldnt get list of tables" << endl;
+ if (result)
+ result->setStatus(
+ i18n("Could not get a list of table names for data source \"%1\".")
+ .arg(m_migrateData->source->serverInfoString()), "");
+ return false;
+ }
+
+ // Check if there are any tables
+ if (tables.isEmpty()) {
+ kdDebug() << "There were no tables to import" << endl;
+ if (result)
+ result->setStatus(
+ i18n("No tables to import found in data source \"%1\".")
+ .arg(m_migrateData->source->serverInfoString()), "");
+ return false;
+ }
+
+ // Step 3 - Read table schemas
+ tables.sort();
+ m_tableSchemas.clear();
+ if (!destDriver) {
+ result->setStatus(&drvManager);
+ return false;
+ }
+ const bool kexi__objects_exists = tables.find("kexi__objects")!=tables.end();
+ QStringList kexiDBTables;
+ if (kexi__objects_exists) {
+ tristate res = drv_queryStringListFromSQL(
+ QString::fromLatin1("SELECT o_name FROM kexi__objects WHERE o_type=%1")
+ .arg((int)KexiDB::TableObjectType), 0, kexiDBTables, -1);
+ if (res == true) {
+ // prepend KexiDB-compatible tables to 'tables' list, so we'll copy KexiDB-compatible tables first,
+ // to make sure existing IDs will not be in conflict with IDs newly generated for non-KexiDB tables
+ kexiDBTables.sort();
+ foreach(QStringList::ConstIterator, it, kexiDBTables)
+ tables.remove( *it );
+//kdDebug() << "KexiDB-compat tables: " << kexiDBTables << endl;
+//kdDebug() << "non-KexiDB tables: " << tables << endl;
+ }
+ }
+
+// uint i=0;
+ // -- read table schemas and create them in memory (only for non-KexiDB-compat tables)
+ foreach (QStringList::ConstIterator, it, tables) {
+ if (destDriver->isSystemObjectName( *it ) //"kexi__objects", etc.
+ || (*it).lower().startsWith("kexi__")) //tables at KexiProject level, e.g. "kexi__blobs"
+ continue;
+ // this is a non-KexiDB table: generate schema from native data source
+ const QString tableName( KexiUtils::string2Identifier(*it) );
+ KexiDB::TableSchema *tableSchema = new KexiDB::TableSchema(tableName);
+ tableSchema->setCaption( *it ); //caption is equal to the original name
+
+ if (!drv_readTableSchema(*it, *tableSchema)) {
+ delete tableSchema;
+ if (result)
+ result->setStatus(
+ i18n("Could not import project from data source \"%1\". Error reading table \"%2\".")
+ .arg(m_migrateData->source->serverInfoString()).arg(tableName), "");
+ return false;
+ }
+ //yeah, got a table
+ //Add it to list of tables which we will create if all goes well
+ m_tableSchemas.append(tableSchema);
+ }
+
+ // Step 4 - Create a new database as we have all required info
+ // - create copies of KexiDB-compat tables
+ // - create copies of non-KexiDB tables
+ delete m_destPrj;
+ m_destPrj = new KexiProject(m_migrateData->destination,
+ result ? (KexiDB::MessageHandler*)*result : 0);
+ bool ok = true == m_destPrj->create(true /*forceOverwrite*/);
+
+ KexiDB::Connection *destConn = 0;
+
+ if (ok)
+ ok = (destConn = m_destPrj->dbConnection());
+
+ KexiDB::Transaction trans;
+ if (ok) {
+ trans = destConn->beginTransaction();
+ if (trans.isNull()) {
+ ok = false;
+ if (result)
+ result->setStatus(destConn,
+ i18n("Could not create database \"%1\".")
+ .arg(m_migrateData->destination->databaseName()));
+ //later destConn->dropDatabase(m_migrateData->destination->databaseName());
+ //don't delete prj, otherwise eror message will be deleted delete prj;
+ //later return m_destPrj;
+ }
+ }
+
+ if (ok) {
+ if (drv_progressSupported())
+ progressInitialise();
+
+ // Step 5 - Create the copies of KexiDB-compat tables in memory (to maintain the same IDs)
+ m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear();
+ foreach (QStringList::ConstIterator, it, kexiDBTables) {
+ //load the schema from kexi__objects and kexi__fields
+ TableSchema *t = new TableSchema();
+ RowData data;
+ bool firstRecord = true;
+ if (true == drv_fetchRecordFromSQL(
+ QString("SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects "
+ "WHERE o_name='%1' AND o_type=%1").arg(*it).arg((int)KexiDB::TableObjectType),
+ data, firstRecord)
+ && destConn->setupObjectSchemaData( data, *t ))
+ {
+//! @todo to reuse Connection::setupTableSchema()'s statement somehow...
+ //load schema for every field and add it
+ firstRecord = true;
+ QString sql(
+ QString::fromLatin1("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, "
+ "f_options, f_default, f_order, f_caption, f_help"
+ " FROM kexi__fields WHERE t_id=%1 ORDER BY f_order").arg(t->id()) );
+ while (ok) {
+ tristate res = drv_fetchRecordFromSQL(sql, data, firstRecord);
+ if (res != true) {
+ if (false == res)
+ ok = false;
+ break;
+ }
+ KexiDB::Field* f = destConn->setupField( data );
+ if (f)
+ t->addField(f);
+ else
+ ok = false;
+ }
+ if (ok)
+ ok = destConn->drv_createTable(*t);
+ if (ok)
+ m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.append(t);
+ }
+ if (!ok)
+ delete t;
+ }
+ }
+
+ // Step 6 - Copy kexi__objects NOW because we'll soon create new objects with new IDs (3.)...
+ if (ok) {
+ if (kexi__objects_exists)
+ ok = drv_copyTable("kexi__objects", destConn, destConn->tableSchema("kexi__objects"));
+ }
+
+ // Step 7 - Create the non-KexiDB-compatible tables: new IDs will be assigned to them
+ if (ok) {
+ KexiDB::TableSchema *ts;
+ for (QPtrListIterator<TableSchema> it (m_tableSchemas); (ts = it.current()); ++it) {
+ ok = destConn->createTable( ts );
+ if (!ok) {
+ kdDebug() << "Failed to create a table " << ts->name() << endl;
+ destConn->debugError();
+ if (result)
+ result->setStatus(destConn,
+ i18n("Could not create database \"%1\".")
+ .arg(m_migrateData->destination->databaseName()));
+ m_tableSchemas.remove(ts);
+ break;
+ }
+ updateProgress((Q_ULLONG)NUM_OF_ROWS_PER_CREATE_TABLE);
+ }
+ }
+
+ if (ok)
+ ok = destConn->commitTransaction(trans);
+
+ if (ok) {
+ //add compatible tables to the list, so data will be copied, if needed
+ if (m_migrateData->keepData) {
+ for(QPtrListIterator<TableSchema> it (m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport);
+ it.current(); ++it)
+ {
+ m_tableSchemas.append(it.current());
+ }
+ }
+ else
+ m_tableSchemas.clear();
+ }
+
+ if (ok) {
+ if (m_destPrj->error()) {
+ ok = false;
+ if (result)
+ result->setStatus(m_destPrj,
+ i18n("Could not import project from data source \"%1\".")
+ .arg(m_migrateData->source->serverInfoString()));
+ }
+ }
+
+ // Step 8 - Copy data if asked to
+ if (ok) {
+ trans = destConn->beginTransaction();
+ ok = !trans.isNull();
+ }
+ if (ok) {
+ if (m_migrateData->keepData) {
+//! @todo check detailed "copy forms/blobs/tables" flags here when we add them
+ // Copy data for "kexi__objectdata" as well, if available in the source db
+ if (tables.find("kexi__objectdata")!=tables.end())
+ m_tableSchemas.append(destConn->tableSchema("kexi__objectdata"));
+ // Copy data for "kexi__blobs" as well, if available in the source db
+ if (tables.find("kexi__blobs")!=tables.end())
+ m_tableSchemas.append(destConn->tableSchema("kexi__blobs"));
+ // Copy data for "kexi__fields" as well, if available in the source db
+ if (tables.find("kexi__fields")!=tables.end())
+ m_tableSchemas.append(destConn->tableSchema("kexi__fields"));
+ }
+
+ for(QPtrListIterator<TableSchema> ts(m_tableSchemas); ok && ts.current() != 0 ; ++ts)
+ {
+ const QString tname( ts.current()->name().lower() );
+ if (destConn->driver()->isSystemObjectName( tname )
+//! @todo what if these two tables are not compatible with tables created in destination db
+//! because newer db format was used?
+ && tname!="kexi__objectdata" //copy this too
+ && tname!="kexi__blobs" //copy this too
+ && tname!="kexi__fields" //copy this too
+ )
+ {
+ kdDebug() << "Do not copy data for system table: " << tname << endl;
+//! @todo copy kexi__db contents!
+ continue;
+ }
+ kdDebug() << "Copying data for table: " << tname << endl;
+ QString originalTableName;
+ if (kexiDBTables.find(tname)==kexiDBTables.end())
+ //caption is equal to the original name
+ originalTableName = ts.current()->caption().isEmpty() ? tname : ts.current()->caption();
+ else
+ originalTableName = tname;
+ ok = drv_copyTable(originalTableName, destConn, ts.current());
+ if (!ok) {
+ kdDebug() << "Failed to copy table " << tname << endl;
+ if (result)
+ result->setStatus(destConn,
+ i18n("Could not copy table \"%1\" to destination database.").arg(tname));
+ break;
+ }
+ }//for
+ }
+
+ // Done.
+ if (ok)
+ ok = destConn->commitTransaction(trans);
+
+ if (ok)
+ ok = drv_disconnect();
+
+ m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear();
+
+ if (ok) {
+ if (destConn)
+ ok = destConn->disconnect();
+ return ok;
+ }
+
+ // Finally: error handling
+ if (result && result->error())
+ result->setStatus(destConn,
+ i18n("Could not import data from data source \"%1\".")
+ .arg(m_migrateData->source->serverInfoString()));
+ if (destConn) {
+ destConn->debugError();
+ destConn->rollbackTransaction(trans);
+ }
+ drv_disconnect();
+ if (destConn) {
+ destConn->disconnect();
+ destConn->dropDatabase(m_migrateData->destination->databaseName());
+ }
+ return false;
+}
+//=============================================================================
+
+bool KexiMigrate::performExport(Kexi::ObjectStatus* result)
+{
+ if (result)
+ result->clearStatus();
+
+ //! @todo performExport
+
+ return false;
+}
+
+//=============================================================================
+// Functions for getting table data
+bool KexiMigrate::tableNames(QStringList & tn)
+{
+ //! @todo Cache list of table names
+ kdDebug() << "Reading list of tables..." << endl;
+ return drv_tableNames(tn);
+}
+
+//=============================================================================
+// Progress functions
+bool KexiMigrate::progressInitialise() {
+ Q_ULLONG sum = 0, size;
+ emit progressPercent(0);
+
+ //! @todo Don't copy table names here
+ QStringList tables;
+ if(!tableNames(tables))
+ return false;
+
+ // 1) Get the number of rows/bytes to import
+ int tableNumber = 1;
+ for(QStringList::Iterator it = tables.begin();
+ it != tables.end(); ++it, tableNumber++)
+ {
+ if(drv_getTableSize(*it, size)) {
+ kdDebug() << "KexiMigrate::progressInitialise() - table: " << *it
+ << "size: " << (ulong)size << endl;
+ sum += size;
+ emit progressPercent(tableNumber * 5 /* 5% */ / tables.count());
+ } else {
+ return false;
+ }
+ }
+
+ kdDebug() << "KexiMigrate::progressInitialise() - job size: " << (ulong)sum << endl;
+ m_progressTotal = sum;
+ m_progressTotal += tables.count() * NUM_OF_ROWS_PER_CREATE_TABLE;
+ m_progressTotal = m_progressTotal * 105 / 100; //add 5 percent for above task 1)
+ m_progressNextReport = sum / 100;
+ m_progressDone = m_progressTotal * 5 / 100; //5 perecent already done in task 1)
+ return true;
+}
+
+
+void KexiMigrate::updateProgress(Q_ULLONG step) {
+ m_progressDone += step;
+ if (m_progressDone >= m_progressNextReport) {
+ int percent = (m_progressDone+1) * 100 / m_progressTotal;
+ m_progressNextReport = ((percent + 1) * m_progressTotal) / 100;
+ kdDebug() << "KexiMigrate::updateProgress(): " << (ulong)m_progressDone << "/"
+ << (ulong)m_progressTotal << " (" << percent << "%) next report at "
+ << (ulong)m_progressNextReport << endl;
+ emit progressPercent(percent);
+ }
+}
+
+//=============================================================================
+// Prompt the user to choose a field type
+KexiDB::Field::Type KexiMigrate::userType(const QString& fname)
+{
+ KInputDialog *dlg;
+ QStringList types;
+ QString res;
+
+ types << "Byte";
+ types << "Short Integer";
+ types << "Integer";
+ types << "Big Integer";
+ types << "Boolean";
+ types << "Date";
+ types << "Date Time";
+ types << "Time";
+ types << "Float";
+ types << "Double";
+ types << "Text";
+ types << "Long Text";
+ types << "Binary Large Object";
+
+ res = dlg->getItem( i18n("Field Type"),
+ i18n("The data type for %1 could not be determined. "
+ "Please select one of the following data "
+ "types").arg(fname),
+ types, 0, false);
+
+//! @todo use QMap<QCString, KexiDB::Field::Type> here!
+ if (res == *types.at(0))
+ return KexiDB::Field::Byte;
+ else if (res == *types.at(1))
+ return KexiDB::Field::ShortInteger;
+ else if (res == *types.at(2))
+ return KexiDB::Field::Integer;
+ else if (res == *types.at(3))
+ return KexiDB::Field::BigInteger;
+ else if (res == *types.at(4))
+ return KexiDB::Field::Boolean;
+ else if (res == *types.at(5))
+ return KexiDB::Field::Date;
+ else if (res == *types.at(6))
+ return KexiDB::Field::DateTime;
+ else if (res == *types.at(7))
+ return KexiDB::Field::Time;
+ else if (res == *types.at(8))
+ return KexiDB::Field::Float;
+ else if (res == *types.at(9))
+ return KexiDB::Field::Double;
+ else if (res == *types.at(10))
+ return KexiDB::Field::Text;
+ else if (res == *types.at(11))
+ return KexiDB::Field::LongText;
+ else if (res == *types.at(12))
+ return KexiDB::Field::BLOB;
+ else
+ return KexiDB::Field::Text;
+}
+
+QVariant KexiMigrate::propertyValue( const QCString& propName )
+{
+ return m_properties[propName.lower()];
+}
+
+QString KexiMigrate::propertyCaption( const QCString& propName ) const
+{
+ return m_propertyCaptions[propName.lower()];
+}
+
+void KexiMigrate::setPropertyValue( const QCString& propName, const QVariant& value )
+{
+ m_properties[propName.lower()] = value;
+}
+
+QValueList<QCString> KexiMigrate::propertyNames() const
+{
+ QValueList<QCString> names = m_properties.keys();
+ qHeapSort(names);
+ return names;
+}
+
+bool KexiMigrate::isValid()
+{
+ if (KexiMigration::versionMajor() != versionMajor()
+ || KexiMigration::versionMinor() != versionMinor())
+ {
+ setError(ERR_INCOMPAT_DRIVER_VERSION,
+ i18n("Incompatible migration driver's \"%1\" version: found version %2, expected version %3.")
+ .arg(name())
+ .arg(QString("%1.%2").arg(versionMajor()).arg(versionMinor()))
+ .arg(QString("%1.%2").arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor())));
+ return false;
+ }
+ return true;
+}
+
+bool KexiMigrate::drv_queryMaxNumber(const QString& tableName,
+ const QString& columnName, int& result)
+{
+ QString string;
+ tristate r = drv_querySingleStringFromSQL(
+ QString::fromLatin1("SELECT MAX(%1) FROM %2").arg(drv_escapeIdentifier(columnName))
+ .arg(drv_escapeIdentifier(tableName)), 0, string);
+ if (r == false)
+ return false;
+ if (~r) {
+ result = 0;
+ return true;
+ }
+ bool ok;
+ int tmpResult = string.toInt(&ok);
+ if (ok)
+ result = tmpResult;
+ return ok;
+}
+
+tristate KexiMigrate::drv_querySingleStringFromSQL(
+ const QString& sqlStatement, uint columnNumber, QString& string)
+{
+ QStringList stringList;
+ const tristate res = drv_queryStringListFromSQL(sqlStatement, columnNumber, stringList, 1);
+ if (true == res)
+ string = stringList.first();
+ return res;
+}
+
+#include "keximigrate.moc"
diff --git a/kexi/migration/keximigrate.h b/kexi/migration/keximigrate.h
new file mode 100644
index 000000000..3d5ed65f5
--- /dev/null
+++ b/kexi/migration/keximigrate.h
@@ -0,0 +1,314 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.net>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_MIGRATE_H
+#define KEXI_MIGRATE_H
+
+
+#include "kexidb/tableschema.h"
+#include "kexidb/connection.h"
+#include "keximigratedata.h"
+
+#include <kgenericfactory.h>
+#include <qstringlist.h>
+#include <qguardedptr.h>
+
+class KexiProject;
+namespace Kexi
+{
+ class ObjectStatus;
+}
+
+/*! KexiMigration implementation version.
+ It is altered after every change:
+ - major number is increased after every major Kexi release,
+ - minor is increased after adding binary-incompatible change.
+ In external code: do not use this to get library version information:
+ use KexiMigration::versionMajor() and KexiMigration::versionMinor() instead to get real version.
+*/
+#define KEXI_MIGRATION_VERSION_MAJOR 1
+#define KEXI_MIGRATION_VERSION_MINOR 1
+
+/*!
+ * \namespace KexiMigration
+ * \brief Framework for importing databases into native KexiDB databases.
+ */
+namespace KexiMigration
+{
+
+//! \return KexiMigration version info (most significant part)
+KEXIMIGR_EXPORT int versionMajor();
+
+//! \return KexiMigration version info (least significant part)
+KEXIMIGR_EXPORT int versionMinor();
+
+
+//! @short Imports non-native databases into Kexi projects.
+/*! A generic API for importing schema and data from an existing
+database into a new Kexi project. Can be also used for importing native Kexi databases.
+
+Basic idea is this:
+-# User selects an existing DB and new project (file or server based)
+-# User specifies whether to import structure and data or structure only.
+-# Import tool connects to db
+-# Checks if it is already a kexi project (not implemented yet)
+-# If not, then read structure and construct new project
+-# Ask user what to do if column type is not supported
+
+See kexi/doc/dev/kexi_import.txt for more info.
+*/
+class KEXIMIGR_EXPORT KexiMigrate : public QObject, public KexiDB::Object
+{
+ Q_OBJECT
+
+ public:
+ virtual ~KexiMigrate();
+
+//! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations!
+ KexiMigration::Data* data() const { return m_migrateData; }
+
+//! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations!
+ //! Data Setup. Requires two connection objects, a name and a bool
+ void setData(KexiMigration::Data* migrateData);
+
+ /*! Checks whether the destination database exists.
+ For file-based dest. projects, we've already asked about overwriting
+ existing project but for server-based projects it's better to ask user.
+ This method should be called before performImport() or performExport().
+
+ \return true if no connection-related errors occurred.
+ \a acceptingNeeded is set to true if destination database exists.
+ In this case you should ask about accepting database overwriting.
+ Used in ImportWizard::import(). */
+ bool checkIfDestinationDatabaseOverwritingNeedsAccepting(Kexi::ObjectStatus* result,
+ bool& acceptingNeeded);
+
+ /*! Checks if the source- and the destination databases are identical.
+ \return true if they are identical else false. */
+ bool isSourceAndDestinationDataSourceTheSame() const;
+
+ //! Perform an import operation
+ bool performImport(Kexi::ObjectStatus* result = 0);
+
+ //! Perform an export operation
+ bool performExport(Kexi::ObjectStatus* result = 0);
+
+ //! Returns true if the migration driver supports progress updates.
+ inline bool progressSupported() { return drv_progressSupported(); }
+
+ virtual int versionMajor() const = 0;
+ virtual int versionMinor() const = 0;
+
+//! @todo This is copied from KexiDB::Driver. One day it will be merged with KexiDB.
+ //! \return property value for \a propeName available for this driver.
+ //! If there's no such property defined for driver, Null QVariant value is returned.
+ virtual QVariant propertyValue( const QCString& propName );
+
+//! @todo This is copied from KexiDB::Driver. One day it will be merged with KexiDB.
+ void setPropertyValue( const QCString& propName, const QVariant& value );
+
+//! @todo This is copied from KexiDB::Driver. One day it will be merged with KexiDB.
+ //! \return translated property caption for \a propeName.
+ //! If there's no such property defined for driver, empty string value is returned.
+ QString propertyCaption( const QCString& propName ) const;
+
+//! @todo This is copied from KexiDB::Driver. One day it will be merged with KexiDB.
+ //! \return a list of property names available for this driver.
+ QValueList<QCString> propertyNames() const;
+
+ /*! \return true is driver is valid. Checks if KexiMigrate::versionMajor()
+ and KexiMigrate::versionMinor() are matching.
+ You can reimplement this but always call KexiMigrate::isValid() implementation. */
+ virtual bool isValid();
+
+ signals:
+ void progressPercent(int percent);
+
+ protected:
+ //! Used by MigrateManager.
+ KexiMigrate(QObject *parent, const char *name, const QStringList &args = QStringList());
+
+ //! Connect to source database (driver specific)
+ virtual bool drv_connect() = 0;
+ //! Disconnect from source database (driver specific)
+ virtual bool drv_disconnect() = 0;
+
+ //! Get table names in source database (driver specific)
+ virtual bool drv_tableNames(QStringList& tablenames) = 0;
+
+ //! Read schema for a given table (driver specific)
+ virtual bool drv_readTableSchema(
+ const QString& originalName, KexiDB::TableSchema& tableSchema) = 0;
+
+ /*! Fetches maximum number from table \a tableName, column \a columnName
+ into \a result. On success true is returned. If there is no records in the table,
+ \a result is set to 0 and true is returned.
+ - Note 1: implement only if the database can already contain kexidb__* tables
+ (so e.g. keximdb driver doea not need this).
+ - Note 2: default implementation uses drv_querySingleStringFromSQL()
+ with "SELECT MAX(columName) FROM tableName" statement, assuming SQL-compliant
+ backend.
+ */
+ virtual bool drv_queryMaxNumber(const QString& tableName,
+ const QString& columnName, int& result);
+
+ /*! Fetches single string at column \a columnNumber for each record from result obtained
+ by running \a sqlStatement. \a numRecords can be specified to limit number of records read.
+ If \a numRecords is -1, all records are loaded.
+ On success the result is stored in \a stringList and true is returned.
+ \return cancelled if there are no records available.
+ - Note: implement only if the database can already contain kexidb__* tables
+ (so e.g. keximdb driver does not need this). */
+//! @todo SQL-dependent!
+ virtual tristate drv_queryStringListFromSQL(
+ const QString& sqlStatement, uint columnNumber, QStringList& stringList,
+ int numRecords = -1)
+ { Q_UNUSED(sqlStatement); Q_UNUSED(columnNumber); Q_UNUSED(stringList);
+ Q_UNUSED(numRecords);
+ return cancelled; }
+
+ /*! Fetches single string at column \a columnNumber from result obtained
+ by running \a sqlStatement.
+ On success the result is stored in \a string and true is returned.
+ \return cancelled if there are no records available.
+ This implementation uses drv_queryStringListFromSQL() with numRecords == 1. */
+//! @todo SQL-dependent!
+ virtual tristate drv_querySingleStringFromSQL(const QString& sqlStatement,
+ uint columnNumber, QString& string);
+
+ /*! Fetches single record from result obtained
+ by running \a sqlStatement.
+ \a firstRecord should be first initialized to true, so the method can run
+ the query at first call and then set it will set \a firstRecord to false,
+ so subsequent calls will only fetch records.
+ On success the result is stored in \a data and true is returned,
+ \a data is resized to appropriate size. cancelled is returned on EOF. */
+//! @todo SQL-dependent!
+ virtual tristate drv_fetchRecordFromSQL(const QString& sqlStatement,
+ KexiDB::RowData& data, bool &firstRecord)
+ { Q_UNUSED(sqlStatement); Q_UNUSED(data); Q_UNUSED(firstRecord);
+ return cancelled; }
+
+ //! Copy a table from source DB to target DB (driver specific)
+ //! - create copies of KexiDB tables
+ //! - create copies of non-KexiDB tables
+ virtual bool drv_copyTable(const QString& srcTable, KexiDB::Connection *destConn,
+ KexiDB::TableSchema* dstTable) = 0;
+
+ virtual bool drv_progressSupported() { return false; }
+
+ /*! \return the size of a table to be imported, or 0 if not supported
+ Finds the size of the named table, in order to provide feedback on
+ migration progress.
+
+ The units of the return type are deliberately unspecified. Migration
+ drivers may return the number of records in the table, or the size in
+ bytes, etc. Units should be chosen in order that the driver can
+ return the size in the fastest way possible (e.g. migration from CSV
+ files should use file size to avoid counting the number of rows, and
+ migration from MDB files should return the number of rows as this is
+ stored within the file).
+
+ Obviously, the driver should use the same units when reporting
+ migration progress.
+
+ \return size of the specified table
+ */
+ virtual bool drv_getTableSize(const QString&, Q_ULLONG&)
+ { return false; }
+
+ void updateProgress(Q_ULLONG step = 1ULL);
+
+//! @todo user should be asked ONCE using a convenient wizard's page, not a popup dialog
+ //! Prompt user to select a field type for unrecognized fields
+ KexiDB::Field::Type userType(const QString& fname);
+
+ virtual QString drv_escapeIdentifier( const QString& str ) const {
+ return m_kexiDBDriver ? m_kexiDBDriver->escapeIdentifier(str) : str; }
+
+//! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations!
+ //! Migrate Options
+ KexiMigration::Data* m_migrateData;
+
+// // Temporary values used during import (set by driver specific methods)
+// KexiDB::Field* m_f;
+
+ /*! Driver properties dictionary (indexed by name),
+ useful for presenting properties to the user.
+ Set available properties here in driver implementation. */
+ QMap<QCString,QVariant> m_properties;
+
+ /*! i18n'd captions for properties. You do not need
+ to set predefined properties' caption in driver implementation
+ -it's done automatically. */
+ QMap<QCString,QString> m_propertyCaptions;
+
+ //! KexiDB driver. For instance, it is used for escaping identifiers
+ QGuardedPtr<KexiDB::Driver> m_kexiDBDriver;
+
+ private:
+ //! Get the list of tables
+ bool tableNames(QStringList& tablenames);
+
+ //! Table schemas from source DB
+ QPtrList<KexiDB::TableSchema> m_tableSchemas;
+
+ QPtrList<KexiDB::TableSchema> m_kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport;
+
+ /*! Estimate size of migration job
+ Calls drv_getTableSize for each table to be copied.
+ \return sum of the size of all tables to be copied.
+ */
+ bool progressInitialise();
+
+ KexiProject *m_destPrj;
+
+ //! Size of migration job
+ Q_ULLONG m_progressTotal;
+
+ //! Amount of migration job complete
+ Q_ULLONG m_progressDone;
+
+ //! Don't recalculate progress done until this value is reached.
+ Q_ULLONG m_progressNextReport;
+
+ friend class MigrateManager;
+};
+
+} //namespace KexiMigration
+
+//! Driver's static version information (implementation),
+//! with KLibFactory symbol declaration.
+#define KEXIMIGRATE_DRIVER_INFO( class_name, internal_name ) \
+ int class_name::versionMajor() const { return KEXI_MIGRATION_VERSION_MAJOR; } \
+ int class_name::versionMinor() const { return KEXI_MIGRATION_VERSION_MINOR; } \
+ K_EXPORT_COMPONENT_FACTORY(keximigrate_ ## internal_name, \
+ KGenericFactory<KexiMigration::class_name>( "keximigrate_" #internal_name ))
+
+/*! Driver's static version information, automatically implemented for KexiDB drivers.
+ Put this into migration driver class declaration just like Q_OBJECT macro. */
+#define KEXIMIGRATION_DRIVER \
+ public: \
+ virtual int versionMajor() const; \
+ virtual int versionMinor() const;
+
+#endif
+
diff --git a/kexi/migration/keximigratedata.cpp b/kexi/migration/keximigratedata.cpp
new file mode 100644
index 000000000..80df12f25
--- /dev/null
+++ b/kexi/migration/keximigratedata.cpp
@@ -0,0 +1,34 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <kde@martinellis.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "keximigratedata.h"
+
+using namespace KexiMigration;
+
+Data::Data()
+ : source(0)
+ , destination(0)
+{
+}
+
+Data::~Data()
+{
+}
diff --git a/kexi/migration/keximigratedata.h b/kexi/migration/keximigratedata.h
new file mode 100644
index 000000000..81e8c0f5b
--- /dev/null
+++ b/kexi/migration/keximigratedata.h
@@ -0,0 +1,59 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Martin Ellis <kde@martinellis.co.uk>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_MIGRATE_DATA_H
+#define KEXI_MIGRATE_DATA_H
+
+#include "kexidb/connection.h"
+
+class KexiProjectData;
+
+namespace KexiMigration
+{
+ //Use this class to store all possible options that could be used by keximigrate.
+ //The current members are not meant to be a definite set, for example, i envisage
+ //adding table/field lists if we allow only importing certain tables/fields
+ class KEXIMIGR_EXPORT Data
+ {
+ public:
+ Data();
+ ~Data();
+
+ //! Connection data for the source database
+ KexiDB::ConnectionData* source;
+
+ //! Name of the source database
+ QString sourceName;
+
+ //! Destination project data
+ KexiProjectData* destination;
+
+// //! Actual connection to the new database
+// KexiDB::Connection* dest;
+
+// //! New database name
+// QString destName;
+
+ //! Flag to determine structure copy, or structure + data
+ bool keepData;
+ };
+}//namespace KexiMigration
+#endif
diff --git a/kexi/migration/keximigratetest.cpp b/kexi/migration/keximigratetest.cpp
new file mode 100644
index 000000000..bb67d9856
--- /dev/null
+++ b/kexi/migration/keximigratetest.cpp
@@ -0,0 +1,49 @@
+/***************************************************************************
+ * Copyright (C) 2004 by Adam Pigg *
+ * adam@piggz.co.uk *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the 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. *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+//#include <config.h>
+#endif
+
+#include <migration/importwizard.h>
+#include <kapplication.h>
+
+/*
+This is in no way meant to compile let alone work
+This is very preliminary and is meant for example only
+
+This will be an example program to demonstrate how to import an existing db into
+a new kexi based db
+*/
+
+using namespace KexiMigration;
+
+int main(int argc, char *argv[])
+{
+ KApplication app(argc, argv, "Kexi Migrate Test");
+
+ ImportWizard* iw = new ImportWizard();
+ iw->setGeometry(300,300,300,250);
+ app.setMainWidget(iw);
+ iw->show();
+
+ return app.exec();
+}
diff --git a/kexi/migration/keximigration_driver.desktop b/kexi/migration/keximigration_driver.desktop
new file mode 100644
index 000000000..9ccb6ff21
--- /dev/null
+++ b/kexi/migration/keximigration_driver.desktop
@@ -0,0 +1,59 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=Kexi/MigrationDriver
+Comment=Kexi Data Migration Driver
+Comment[bg]=Драйвер на Kexi за мигриране на данни
+Comment[ca]=Controlador de migració de dades de Kexi
+Comment[cy]=Gyrrydd Mudo Data Kexi
+Comment[da]=Kexi datamigrationsdriver
+Comment[de]=Datenmigrationstreiber für Kexi
+Comment[el]=Οδηγός μεταφοράς δεδομένων του Kexi
+Comment[eo]=Kexi datum-migrada pelilo
+Comment[es]=Controlador de migración de datos de Kexi
+Comment[et]=Kexi andmete migreerumisdraiver
+Comment[eu]=Kexi-ren datuak migratzeko kontrolatzailea
+Comment[fa]=گردانندۀ جابه‌جایی دادۀ Kexi
+Comment[fi]=Kexi tietojen yhdistämisajuri
+Comment[fr]=Pilote de migration de données de Kexi
+Comment[fy]=Kexi Gegevensmigraasje stjoerprogramma
+Comment[gl]=Controlador de Migración de Datos de Kexi
+Comment[he]=מנהל התקן Data-Migration ל־Kexi
+Comment[hr]=Kexi upravljački program za migraciju podataka
+Comment[hu]=Kexi adatmigrálási meghajtó
+Comment[is]=Kexi gagnaflutningsrekill
+Comment[it]=Driver di migrazione dei dati per Kexi
+Comment[ja]=Kexi データ移行ドライバ
+Comment[km]=កម្មវិធី​បញ្ជា​សម្រាប់​ផ្លាស់ប្ដូរ​ទិន្នន័យ​សម្រាប់ Kexi
+Comment[lv]=Kexi datu migrācijas draiveris
+Comment[ms]=Pemacu Migrasi Data Kexi
+Comment[nb]=Kexi-driver for datamigrering
+Comment[nds]=Datenutlagerndriever för Kexi
+Comment[ne]=केक्सी डेटा माइग्रेसन ड्राइभर
+Comment[nl]=Kexi Datamigratie Stuurprogramma
+Comment[nn]=Kexi-programtillegg for migrering av data
+Comment[pl]=Wtyczka do migracji danych programu Kexi
+Comment[pt]=Controlador de Migração de Dados do Kexi
+Comment[pt_BR]=Driver de Migração de Dados do Kexi
+Comment[ru]=Модуль драйвера миграции Kexi
+Comment[sk]=Ovládač Kexi Data Migration
+Comment[sl]=Gonilnik za prenos podatkov za Kexi
+Comment[sr]=Драјвер Kexi-ја за миграцију података
+Comment[sr@Latn]=Drajver Kexi-ja za migraciju podataka
+Comment[sv]=Kexi dataövergångsdrivrutin
+Comment[uk]=Драйвер міграції даних для Kexi
+Comment[uz]=Kexi uchun maʼlumotlar migratsiyasi drayveri
+Comment[uz@cyrillic]=Kexi учун маълумотлар миграцияси драйвери
+Comment[zh_CN]=Kexi 数据升迁驱动程序
+Comment[zh_TW]=Kexi 資料轉移驅動程式
+
+[PropertyDef::X-Kexi-FileMigrationDriverMime]
+Type=QString
+
+[PropertyDef::X-Kexi-MigrationDriverName]
+Type=QString
+
+[PropertyDef::X-Kexi-MigrationDriverType]
+Type=QString
+
+[PropertyDef::X-Kexi-KexiMigrationVersion]
+Type=QString
diff --git a/kexi/migration/migratemanager.cpp b/kexi/migration/migratemanager.cpp
new file mode 100644
index 000000000..320ad718e
--- /dev/null
+++ b/kexi/migration/migratemanager.cpp
@@ -0,0 +1,384 @@
+/* This file is part of the KDE project
+ Daniel Molkentin <molkentin@kde.org>
+ Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "migratemanager.h"
+#include "migratemanager_p.h"
+#include "keximigrate.h"
+
+#include <klibloader.h>
+#include <kparts/componentfactory.h>
+#include <ktrader.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kservice.h>
+
+#include <assert.h>
+
+#include <qapplication.h>
+
+//remove debug
+#undef KexiDBDbg
+#define KexiDBDbg if (0) kdDebug()
+
+using namespace KexiMigration;
+
+MigrateManagerInternal* MigrateManagerInternal::s_self = 0L;
+
+/*! @todo
+ Temporary, needed because MigrateManagerInternal::m_drivers is autodeleted
+ drivers currently own KexiMigrate::Data members so these are destroyed when
+ last MigrateManager instance is deleted. Remove this hack when
+ KexiMigrate is splitted into Driver and Connection. */
+MigrateManager __manager;
+
+MigrateManagerInternal::MigrateManagerInternal() /* protected */
+ : QObject( 0, "KexiMigrate::MigrateManagerInternal" )
+ , Object()
+ , m_drivers(17, false)
+ , m_refCount(0)
+ , lookupDriversNeeded(true)
+{
+ m_drivers.setAutoDelete(true);
+ m_serverResultNum=0;
+
+}
+
+MigrateManagerInternal::~MigrateManagerInternal()
+{
+ KexiDBDbg << "MigrateManagerInternal::~MigrateManagerInternal()" << endl;
+ m_drivers.clear();
+ if ( s_self == this )
+ s_self = 0;
+ KexiDBDbg << "MigrateManagerInternal::~MigrateManagerInternal() ok" << endl;
+}
+
+void MigrateManagerInternal::slotAppQuits()
+{
+ if (qApp->mainWidget() && qApp->mainWidget()->isVisible())
+ return; //what a hack! - we give up when app is still there
+ KexiDBDbg << "MigrateManagerInternal::slotAppQuits(): let's clear drivers..." << endl;
+ m_drivers.clear();
+}
+
+MigrateManagerInternal *MigrateManagerInternal::self()
+{
+ if (!s_self)
+ s_self = new MigrateManagerInternal();
+
+ return s_self;
+}
+
+bool MigrateManagerInternal::lookupDrivers()
+{
+ if (!lookupDriversNeeded)
+ return true;
+
+ if (qApp) {
+ connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(slotAppQuits()));
+ }
+//TODO: for QT-only version check for KInstance wrapper
+// KexiDBWarn << "DriverManagerInternal::lookupDrivers(): cannot work without KInstance (KGlobal::instance()==0)!" << endl;
+// setError("Driver Manager cannot work without KInstance (KGlobal::instance()==0)!");
+
+ lookupDriversNeeded = false;
+ clearError();
+ KTrader::OfferList tlist = KTrader::self()->query("Kexi/MigrationDriver");
+ KTrader::OfferList::ConstIterator it(tlist.constBegin());
+ for(; it != tlist.constEnd(); ++it)
+ {
+ KService::Ptr ptr = (*it);
+ QString srv_name = ptr->property("X-Kexi-MigrationDriverName").toString();
+ if (srv_name.isEmpty()) {
+ KexiDBWarn << "MigrateManagerInternal::lookupDrivers(): "
+ "X-Kexi-MigrationDriverName must be set for migration driver \""
+ << ptr->property("Name").toString() << "\" service!\n -- skipped!" << endl;
+ continue;
+ }
+ if (m_services_lcase.contains(srv_name.lower())) {
+ continue;
+ }
+
+//! @todo could be merged. Copied from KexiDB::DriverManager.
+//<COPIED>
+ QString srv_ver_str = ptr->property("X-Kexi-KexiMigrationVersion").toString();
+ QStringList lst( QStringList::split(".", srv_ver_str) );
+ int minor_ver, major_ver;
+ bool ok = (lst.count() == 2);
+ if (ok)
+ major_ver = lst[0].toUInt(&ok);
+ if (ok)
+ minor_ver = lst[1].toUInt(&ok);
+ if (!ok) {
+ KexiDBWarn << "MigrateManagerInternal::lookupDrivers(): problem with detecting '"
+ << srv_name.lower() << "' driver's version -- skipping it!" << endl;
+ possibleProblems += QString("\"%1\" migration driver has unrecognized version; "
+ "required driver version is \"%2.%3\"")
+ .arg(srv_name.lower())
+ .arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor());
+ continue;
+ }
+ if (major_ver != KexiMigration::versionMajor() || minor_ver != KexiMigration::versionMinor()) {
+ KexiDBWarn << QString("MigrateManagerInternal::lookupDrivers(): '%1' driver"
+ " has version '%2' but required migration driver version is '%3.%4'\n"
+ " -- skipping this driver!").arg(srv_name.lower()).arg(srv_ver_str)
+ .arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor()) << endl;
+ possibleProblems += QString("\"%1\" migration driver has version \"%2\" "
+ "but required driver version is \"%3.%4\"")
+ .arg(srv_name.lower()).arg(srv_ver_str)
+ .arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor());
+ continue;
+ }
+//</COPIED>
+
+ QString mime = ptr->property("X-Kexi-FileDBDriverMime").toString().lower();
+ QString drvType = ptr->property("X-Kexi-MigrationDriverType").toString().lower();
+ if (drvType=="file") {
+ if (!mime.isEmpty()) {
+ if (!m_services_by_mimetype.contains(mime)) {
+ m_services_by_mimetype.insert(mime, ptr);
+ }
+ else {
+ KexiDBWarn << "MigrateManagerInternal::lookupDrivers(): more than one driver for '"
+ << mime << "' mime type!" << endl;
+ }
+ }
+ }
+ m_services.insert(srv_name, ptr);
+ m_services_lcase.insert(srv_name.lower(), ptr);
+ KexiDBDbg << "MigrateManager::lookupDrivers(): registered driver: " << ptr->name()
+ << "(" << ptr->library() << ")" << endl;
+ }
+
+ if (tlist.isEmpty())
+ {
+ setError(ERR_DRIVERMANAGER, i18n("Could not find any import/export database drivers.") );
+ return false;
+ }
+ return true;
+}
+
+KexiMigrate* MigrateManagerInternal::driver(const QString& name)
+{
+ if (!lookupDrivers())
+ return 0;
+
+ clearError();
+ KexiDBDbg << "MigrationrManagerInternal::migrationDriver(): loading " << name << endl;
+
+ KexiMigrate *drv = name.isEmpty() ? 0 : m_drivers.find(name.latin1());
+ if (drv)
+ return drv; //cached
+
+ if (!m_services_lcase.contains(name.lower())) {
+ setError(ERR_DRIVERMANAGER, i18n("Could not find import/export database driver \"%1\".").arg(name) );
+ return 0;
+ }
+
+ KService::Ptr ptr= *(m_services_lcase.find(name.lower()));
+ QString srv_name = ptr->property("X-Kexi-MigrationDriverName").toString();
+
+ KexiDBDbg << "MigrateManagerInternal::driver(): library: "<<ptr->library()<<endl;
+ drv = KParts::ComponentFactory::createInstanceFromService<KexiMigrate>(ptr,
+ this, srv_name.latin1(), QStringList(),&m_serverResultNum);
+
+ if (!drv) {
+ setError(ERR_DRIVERMANAGER, i18n("Could not load import/export database driver \"%1\".")
+ .arg(name) );
+ if (m_componentLoadingErrors.isEmpty()) {//fill errtable on demand
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrNoServiceFound]="ErrNoServiceFound";
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrServiceProvidesNoLibrary]="ErrServiceProvidesNoLibrary";
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrNoLibrary]="ErrNoLibrary";
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrNoFactory]="ErrNoFactory";
+ m_componentLoadingErrors[KParts::ComponentFactory::ErrNoComponent]="ErrNoComponent";
+ }
+ m_serverResultName=m_componentLoadingErrors[m_serverResultNum];
+ return 0;
+ }
+ KexiDBDbg << "MigrateManagerInternal::driver(): loading succeed: " << name <<endl;
+ KexiDBDbg << "drv="<<(long)drv <<endl;
+
+// drv->setName(srv_name.latin1());
+// drv->d->service = ptr; //store info
+// drv->d->fileDBDriverMimeType = ptr->property("X-Kexi-FileDBDriverMime").toString();
+// drv->d->initInternalProperties();
+
+ if (!drv->isValid()) {
+ setError(drv);
+ delete drv;
+ return 0;
+ }
+
+ m_drivers.insert(name.latin1(), drv); //cache it
+ return drv;
+}
+
+void MigrateManagerInternal::incRefCount()
+{
+ m_refCount++;
+ KexiDBDbg << "MigrateManagerInternal::incRefCount(): " << m_refCount << endl;
+}
+
+void MigrateManagerInternal::decRefCount()
+{
+ m_refCount--;
+ KexiDBDbg << "MigrateManagerInternal::decRefCount(): " << m_refCount << endl;
+// if (m_refCount<1) {
+// KexiDBDbg<<"KexiDB::DriverManagerInternal::decRefCount(): reached m_refCount<1 -->deletelater()"<<endl;
+// s_self=0;
+// deleteLater();
+// }
+}
+
+// ---------------------------
+// --- DriverManager impl. ---
+// ---------------------------
+
+MigrateManager::MigrateManager()
+ : QObject( 0, "KexiMigrate::MigrateManager" )
+ , Object()
+ , d_int( MigrateManagerInternal::self() )
+{
+ d_int->incRefCount();
+// if ( !s_self )
+// s_self = this;
+// lookupDrivers();
+}
+
+MigrateManager::~MigrateManager()
+{
+ KexiDBDbg << "MigrateManager::~MigrateManager()" << endl;
+/* Connection *conn;
+ for ( conn = m_connections.first(); conn ; conn = m_connections.next() ) {
+ conn->disconnect();
+ conn->m_driver = 0; //don't let the connection touch our driver now
+ m_connections.remove();
+ delete conn;
+ }*/
+
+ d_int->decRefCount();
+ if (d_int->m_refCount==0) {
+ //delete internal drv manager!
+ delete d_int;
+ }
+// if ( s_self == this )
+ //s_self = 0;
+ KexiDBDbg << "MigrateManager::~MigrateManager() ok" << endl;
+}
+
+
+const QStringList MigrateManager::driverNames()
+{
+ if (!d_int->lookupDrivers()) {
+ kdDebug() << "MigrateManager::driverNames() lookupDrivers failed" << endl;
+ return QStringList();
+ }
+
+ if (d_int->m_services.isEmpty()) {
+ kdDebug() << "MigrateManager::driverNames() MigrateManager::ServicesMap is empty" << endl;
+ return QStringList();
+ }
+
+ if (d_int->error()) {
+ kdDebug() << "MigrateManager::driverNames() Error: " << d_int->errorMsg() << endl;
+ return QStringList();
+ }
+
+ return d_int->m_services.keys();
+}
+
+QString MigrateManager::driverForMimeType(const QString &mimeType)
+{
+ if (!d_int->lookupDrivers()) {
+ kdDebug() << "MigrateManager::driverForMimeType() lookupDrivers() failed" << endl;
+ setError(d_int);
+ return 0;
+ }
+
+ KService::Ptr ptr = d_int->m_services_by_mimetype[mimeType.lower()];
+ if (!ptr) {
+ kdDebug() << QString("MigrateManager::driverForMimeType(%1) No such mimetype").arg(mimeType) << endl;
+ return QString::null;
+ }
+
+ return ptr->property("X-Kexi-MigrationDriverName").toString();
+}
+
+KexiMigrate* MigrateManager::driver(const QString& name)
+{
+ KexiMigrate *drv = d_int->driver(name);
+ if (d_int->error()) {
+ kdDebug() << QString("MigrateManager::driver(%1) Error: %2").arg(name).arg(d_int->errorMsg()) << endl;
+ setError(d_int);
+ }
+ return drv;
+}
+
+QString MigrateManager::serverErrorMsg()
+{
+ return d_int->m_serverErrMsg;
+}
+
+int MigrateManager::serverResult()
+{
+ return d_int->m_serverResultNum;
+}
+
+QString MigrateManager::serverResultName()
+{
+ return d_int->m_serverResultName;
+}
+
+void MigrateManager::drv_clearServerResult()
+{
+ d_int->m_serverErrMsg=QString::null;
+ d_int->m_serverResultNum=0;
+ d_int->m_serverResultName=QString::null;
+}
+
+QString MigrateManager::possibleProblemsInfoMsg() const
+{
+ if (d_int->possibleProblems.isEmpty())
+ return QString::null;
+ QString str;
+ str.reserve(1024);
+ str = "<ul>";
+ for (QStringList::ConstIterator it = d_int->possibleProblems.constBegin();
+ it!=d_int->possibleProblems.constEnd(); ++it)
+ {
+ str += (QString::fromLatin1("<li>") + *it + QString::fromLatin1("</li>"));
+ }
+ str += "</ul>";
+ return str;
+}
+
+//------------------------
+
+int KexiMigration::versionMajor()
+{
+ return KEXI_MIGRATION_VERSION_MAJOR;
+}
+
+int KexiMigration::versionMinor()
+{
+ return KEXI_MIGRATION_VERSION_MINOR;
+}
+
+#include "migratemanager_p.moc"
diff --git a/kexi/migration/migratemanager.h b/kexi/migration/migratemanager.h
new file mode 100644
index 000000000..c876ab91c
--- /dev/null
+++ b/kexi/migration/migratemanager.h
@@ -0,0 +1,82 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_MIGRATION_MNGR_H
+#define KEXI_MIGRATION_MNGR_H
+
+#include <qobject.h>
+#include <qcstring.h>
+#include <qmap.h>
+#include <qdict.h>
+
+#include <klibloader.h>
+#include <kservice.h>
+
+#include "keximigrate.h"
+
+namespace KexiMigration {
+
+class MigrateManagerInternal;
+
+//! @short Migration library management, for finding and loading mogration drivers.
+class KEXIMIGR_EXPORT MigrateManager : public QObject, public KexiDB::Object
+{
+ public:
+ typedef QMap<QString, KService::Ptr> ServicesMap;
+
+ MigrateManager();
+ virtual ~MigrateManager();
+
+ /*! Tries to load db driver with named name \a name.
+ The name is case insensitive.
+ \return db driver, or 0 if error (then error message is also set) */
+ KexiMigrate* driver(const QString& name);
+
+ /*! returns list of available drivers names.
+ That drivers can be loaded by first use of driver() method. */
+ const QStringList driverNames();
+
+ /*! Looks up a drivers list by MIME type of database file.
+ Only file-based database drivers are checked.
+ The lookup is case insensitive.
+ \return driver name or null string if no driver found.
+ */
+ QString driverForMimeType(const QString &mimeType);
+
+ //! server error is set if there is error at KService level (useful for debugging)
+ virtual QString serverErrorMsg();
+ virtual int serverResult();
+ virtual QString serverResultName();
+
+//! @todo copied from KexiDB::DriverManager, merge it.
+ /*! HTML information about possible problems encountered.
+ It's displayed in 'details' section, if an error encountered.
+ Currently it contains a list of incompatible migration drivers. */
+ QString possibleProblemsInfoMsg() const;
+
+ protected:
+ virtual void drv_clearServerResult();
+
+ private:
+ MigrateManagerInternal *d_int;
+};
+
+} //namespace KexiMigrate
+
+#endif
diff --git a/kexi/migration/migratemanager_p.h b/kexi/migration/migratemanager_p.h
new file mode 100644
index 000000000..0d90acfef
--- /dev/null
+++ b/kexi/migration/migratemanager_p.h
@@ -0,0 +1,85 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_MIGRATE_MNGR_P_H
+#define KEXI_MIGRATE_MNGR_P_H
+
+#include <qobject.h>
+#include <qasciidict.h>
+
+namespace KexiMigration {
+
+/*! Internal class of driver manager.
+*/
+class MigrateManagerInternal : public QObject, public KexiDB::Object
+{
+ Q_OBJECT
+ public:
+ ~MigrateManagerInternal();
+
+ /*! Tries to load db driver \a name.
+ \return db driver, or 0 if error (then error message is also set) */
+ KexiMigrate* driver(const QString& name);
+
+ static MigrateManagerInternal *self();
+
+ /*! increments the refcount for the manager */
+ void incRefCount();
+
+ /*! decrements the refcount for the manager
+ if the refcount reaches a value less than 1 the manager is freed */
+ void decRefCount();
+
+ protected slots:
+ /*! Used to destroy all drivers on QApplication quit, so even if there are
+ DriverManager's static instances that are destroyed on program
+ "static destruction", drivers are not kept after QApplication death.
+ */
+ void slotAppQuits();
+
+ protected:
+ /*! Used by self() */
+ MigrateManagerInternal();
+
+ bool lookupDrivers();
+
+ static MigrateManagerInternal* s_self;
+
+ MigrateManager::ServicesMap m_services; //! services map
+ MigrateManager::ServicesMap m_services_lcase; //! as above but service names in lowercase
+ MigrateManager::ServicesMap m_services_by_mimetype;
+
+ QAsciiDict<KexiMigrate> m_drivers;
+ ulong m_refCount;
+
+ QString m_serverErrMsg;
+ int m_serverResultNum;
+ QString m_serverResultName;
+ //! result names for KParts::ComponentFactory::ComponentLoadingError
+ QMap<int,QString> m_componentLoadingErrors;
+
+ bool lookupDriversNeeded : 1;
+
+ QStringList possibleProblems;
+
+ friend class MigrateManager;
+};
+}
+
+#endif
diff --git a/kexi/migration/mysql/Makefile.am b/kexi/migration/mysql/Makefile.am
new file mode 100644
index 000000000..635bbf1de
--- /dev/null
+++ b/kexi/migration/mysql/Makefile.am
@@ -0,0 +1,18 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = keximigrate_mysql.la
+
+INCLUDES = -I$(srcdir)/../../.. -I$(top_srcdir)/kexi $(all_includes) -I$(MYSQL_INC)
+
+keximigrate_mysql_la_METASOURCES = AUTO
+
+keximigrate_mysql_la_SOURCES = mysqlmigrate.cpp
+
+keximigrate_mysql_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) ../libkeximigrate.la $(MYSQL_LIBS) -lmysqlclient
+
+keximigrate_mysql_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) -no-undefined
+
+kde_services_DATA = keximigrate_mysql.desktop
+
+noinst_HEADERS = mysqlmigrate.h
+
diff --git a/kexi/migration/mysql/keximigrate_mysql.desktop b/kexi/migration/mysql/keximigrate_mysql.desktop
new file mode 100644
index 000000000..529ad459a
--- /dev/null
+++ b/kexi/migration/mysql/keximigrate_mysql.desktop
@@ -0,0 +1,54 @@
+[Desktop Entry]
+Name=MySQL
+Name[ne]=मेरो एसक्यूएल
+Name[sk]=mySQL
+Comment=MySQL Migration Driver for Kexi
+Comment[bg]=Драйвер за мигриране от MySQL към Kexi
+Comment[ca]=Controlador de migració de MySQL per a Kexi
+Comment[cy]=Gyrrydd Mudo MySQL ar gyfer Kexi
+Comment[da]=MySQL Migrationsdriver for Kexi
+Comment[de]=MySQL-Migrationstreiber für Kexi
+Comment[el]=Οδηγός μεταφοράς MySQL του Kexi
+Comment[eo]=MySQL-migrada pelilo por Kexi
+Comment[es]=Controlador de migración a MySQL para Kexi
+Comment[et]=Kexi MySQL migreerumisdraiver
+Comment[eu]=Kexi-ren MySQL migraziorako kontrolatzailea
+Comment[fa]=گردانندۀ جابه‌جایی MySQL برای Kexi
+Comment[fi]=MySQL yhdistäjäajuri Kexille
+Comment[fr]=Pilote de migration MySQL pour Kexi
+Comment[fy]=MySQL-Migraasjestjoerprogramma foar Kexi
+Comment[gl]=Controlador de Migración de MySQL de Kexi
+Comment[hr]=MySQL upravljački program migracije podataka za Kexi
+Comment[hu]=Kexi MySQL-migrálási meghajtó
+Comment[is]=MySQL gagnaflutningsrekill fyrir Kexi
+Comment[it]=Driver di migrazione MySQL per Kexi
+Comment[ja]=Kexi MySQL データ移行ドライバ
+Comment[km]=កម្មវិធី​បញ្ជា​សម្រាប់​ផ្លាស់ប្ដូរ MySQL សម្រាប់ Kexi
+Comment[lv]=MySQL datu migrācijas draiveris priekš Kexi
+Comment[ms]=Pemacu Migrasi MySQL bagi Kexi
+Comment[nb]=Kexi-programmodul for migrering av MySQL-drivere
+Comment[nds]=MySQL-Datenutlagerndriever för Kexi
+Comment[ne]=केक्सीका लागि MySQL माइग्रेसन ड्राइभर
+Comment[nl]=MySQL-migratiestuurprogramma voor Kexi
+Comment[nn]=Kexi-programmodul for migrering av MySQL-drivarar
+Comment[pl]=Wtyczka migracji danych z serwera MySQL dla Kexi
+Comment[pt]=Controlador de Migração de MySQL do Kexi
+Comment[pt_BR]=Driver de Migração do MySQL para o Kexi
+Comment[ru]=Драйвер миграции MySQL для Kexi
+Comment[sk]=Ovládač MySQL Migration Driver pre Kexi
+Comment[sl]=Gonilnik MySQL za prenos podatkov za Kexi
+Comment[sr]=Драјвер Kexi-ја за миграцију са MySQL-а
+Comment[sr@Latn]=Drajver Kexi-ja za migraciju sa MySQL-a
+Comment[sv]=MySQL-övergångsdrivrutin för Kexi
+Comment[uk]=MySQL драйвер міграції даних для Kexi
+Comment[uz]=Kexi uchun MySQL migratsiya drayveri
+Comment[uz@cyrillic]=Kexi учун MySQL миграция драйвери
+Comment[zh_CN]=Kexi MySQL 升迁驱动程序
+Comment[zh_TW]=Kexi 的 MySQL 轉移驅動程式
+X-KDE-Library=keximigrate_mysql
+ServiceTypes=Kexi/MigrationDriver
+Type=Service
+InitialPreference=8
+X-Kexi-MigrationDriverName=MySQL
+X-Kexi-MigrationDriverType=Network
+X-Kexi-KexiMigrationVersion=1.1
diff --git a/kexi/migration/mysql/mysqlmigrate.cpp b/kexi/migration/mysql/mysqlmigrate.cpp
new file mode 100644
index 000000000..a2c62dd5c
--- /dev/null
+++ b/kexi/migration/mysql/mysqlmigrate.cpp
@@ -0,0 +1,522 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Martin Ellis <m.a.ellis@ncl.ac.uk>
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "mysqlmigrate.h"
+
+#include <qstring.h>
+#include <qregexp.h>
+#include <qfile.h>
+#include <qvariant.h>
+#include <qvaluelist.h>
+#include <kdebug.h>
+
+#include <mysql_version.h>
+#include <mysql.h>
+
+#include "migration/keximigratedata.h"
+#include <kexidb/cursor.h>
+#include <kexidb/field.h>
+#include <kexidb/utils.h>
+#include <kexidb/drivers/mySQL/mysqlconnection_p.cpp>
+#include <kexidb/drivermanager.h>
+#include <kexiutils/identifier.h>
+
+using namespace KexiMigration;
+
+/* This is the implementation for the MySQL specific import routines. */
+
+KEXIMIGRATE_DRIVER_INFO( MySQLMigrate, mysql )
+
+/* ************************************************************************** */
+//! Constructor
+/*MySQLMigrate::MySQLMigrate() :
+ d(new MySqlConnectionInternal())
+{
+}*/
+
+//! Constructor (needed for trading interface)
+MySQLMigrate::MySQLMigrate(QObject *parent, const char *name,
+ const QStringList &args) :
+ KexiMigrate(parent, name, args)
+ ,d(new MySqlConnectionInternal(0))
+ ,m_mysqlres(0)
+{
+ KexiDB::DriverManager manager;
+ m_kexiDBDriver = manager.driver("mysql");
+}
+
+/* ************************************************************************** */
+//! Destructor
+MySQLMigrate::~MySQLMigrate() {
+ if (m_mysqlres)
+ mysql_free_result(m_mysqlres);
+ m_mysqlres = 0;
+}
+
+
+/* ************************************************************************** */
+/*! Connect to the db backend */
+bool MySQLMigrate::drv_connect() {
+ if(d->db_connect(*m_migrateData->source)) {
+ return d->useDatabase(m_migrateData->sourceName);
+ } else {
+ return false;
+ }
+}
+
+
+/*! Disconnect from the db backend */
+bool MySQLMigrate::drv_disconnect()
+{
+ return d->db_disconnect();
+}
+
+
+/* ************************************************************************** */
+/*! Get the types and properties for each column. */
+bool MySQLMigrate::drv_readTableSchema(
+ const QString& originalName, KexiDB::TableSchema& tableSchema)
+{
+// m_table = new KexiDB::TableSchema(table);
+
+// //TODO IDEA: ask for user input for captions
+// tableSchema.setCaption(table + " table");
+
+ //Perform a query on the table to get some data
+ QString query = QString("SELECT * FROM `") + drv_escapeIdentifier(originalName) + "` LIMIT 0";
+ if(d->executeSQL(query)) {
+ MYSQL_RES *res = mysql_store_result(d->mysql);
+ if (res != NULL) {
+
+ unsigned int numFlds = mysql_num_fields(res);
+ MYSQL_FIELD *fields = mysql_fetch_fields(res);
+
+ for(unsigned int i = 0; i < numFlds; i++) {
+ QString fldName(fields[i].name);
+ QString fldID( KexiUtils::string2Identifier(fldName) );
+
+ KexiDB::Field *fld =
+ new KexiDB::Field(fldID, type(originalName, &fields[i]));
+
+ if(fld->type() == KexiDB::Field::Enum) {
+ QStringList values = examineEnumField(originalName, &fields[i]);
+ }
+
+ fld->setCaption(fldName);
+ getConstraints(fields[i].flags, fld);
+ getOptions(fields[i].flags, fld);
+ tableSchema.addField(fld);
+ }
+ mysql_free_result(res);
+ } else {
+ kdDebug() << "MySQLMigrate::drv_tableNames: null result" << endl;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+/*! Get a list of tables and put into the supplied string list */
+bool MySQLMigrate::drv_tableNames(QStringList& tableNames)
+{
+ if(d->executeSQL("SHOW TABLES")) {
+ MYSQL_RES *res = mysql_store_result(d->mysql);
+ if (res != NULL) {
+ MYSQL_ROW row;
+ while ((row = mysql_fetch_row(res)) != NULL) {
+ tableNames << QString::fromUtf8(row[0]); //utf8.. ok?
+ }
+ mysql_free_result(res);
+ } else {
+ kdDebug() << "MySQLMigrate::drv_tableNames: null result" << endl;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/*! Fetches single string at column \a columnNumber for each record from result obtained
+ by running \a sqlStatement.
+ On success the result is stored in \a stringList and true is returned.
+ \return cancelled if there are no records available. */
+tristate MySQLMigrate::drv_queryStringListFromSQL(
+ const QString& sqlStatement, uint columnNumber, QStringList& stringList, int numRecords)
+{
+ stringList.clear();
+ if (d->executeSQL(sqlStatement)) {
+ MYSQL_RES *res = mysql_use_result(d->mysql);
+ if (res != NULL) {
+ for (int i=0; numRecords == -1 || i < numRecords; i++) {
+ MYSQL_ROW row = mysql_fetch_row(res);
+ if (!row) {
+ tristate r;
+ if (mysql_errno(d->mysql))
+ r = false;
+ else
+ r = (numRecords == -1) ? true : cancelled;
+ mysql_free_result(res);
+ return r;
+ }
+ uint numFields = mysql_num_fields(res);
+ if (columnNumber > (numFields-1)) {
+ kdWarning() << "MySQLMigrate::drv_querySingleStringFromSQL("<<sqlStatement
+ << "): columnNumber too large ("
+ << columnNumber << "), expected 0.." << numFields << endl;
+ mysql_free_result(res);
+ return false;
+ }
+ unsigned long *lengths = mysql_fetch_lengths(res);
+ if (!lengths) {
+ mysql_free_result(res);
+ return false;
+ }
+ stringList.append( QString::fromUtf8(row[columnNumber], lengths[columnNumber]) ); //ok? utf8?
+ }
+ mysql_free_result(res);
+ } else {
+ kdDebug() << "MySQLMigrate::drv_querySingleStringFromSQL(): null result" << endl;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/*! Fetches single record from result obtained
+ by running \a sqlStatement. */
+tristate MySQLMigrate::drv_fetchRecordFromSQL(const QString& sqlStatement,
+ KexiDB::RowData& data, bool &firstRecord)
+{
+ if (firstRecord || !m_mysqlres) {
+ if (m_mysqlres) {
+ mysql_free_result(m_mysqlres);
+ m_mysqlres = 0;
+ }
+ if (!d->executeSQL(sqlStatement) || !(m_mysqlres = mysql_use_result(d->mysql)))
+ return false;
+ firstRecord = false;
+ }
+
+ MYSQL_ROW row = mysql_fetch_row(m_mysqlres);
+ if (!row) {
+ tristate r = cancelled;
+ if (mysql_errno(d->mysql))
+ r = false;
+ mysql_free_result(m_mysqlres);
+ m_mysqlres = 0;
+ return r;
+ }
+ const int numFields = mysql_num_fields(m_mysqlres);
+ unsigned long *lengths = mysql_fetch_lengths(m_mysqlres);
+ if (!lengths) {
+ mysql_free_result(m_mysqlres);
+ m_mysqlres = 0;
+ return false;
+ }
+ data.resize(numFields);
+ for (int i=0; i < numFields; i++)
+ data[i] = QString::fromUtf8(row[i], lengths[i] ); //ok? utf8?
+ return true;
+}
+
+/*! Copy MySQL table to KexiDB database */
+bool MySQLMigrate::drv_copyTable(const QString& srcTable, KexiDB::Connection *destConn,
+ KexiDB::TableSchema* dstTable)
+{
+ if(d->executeSQL("SELECT * FROM `" + drv_escapeIdentifier(srcTable)) + "`") {
+ MYSQL_RES *res = mysql_use_result(d->mysql);
+ if (res != NULL) {
+ MYSQL_ROW row;
+ const KexiDB::QueryColumnInfo::Vector fieldsExpanded( dstTable->query()->fieldsExpanded() );
+ while ((row = mysql_fetch_row(res)) != NULL) {
+ const int numFields = QMIN((int)fieldsExpanded.count(), (int)mysql_num_fields(res));
+ QValueList<QVariant> vals;
+ unsigned long *lengths = mysql_fetch_lengths(res);
+ if (!lengths) {
+ mysql_free_result(res);
+ return false;
+ }
+ for(int i = 0; i < numFields; i++)
+ vals.append( KexiDB::cstringToVariant(row[i], fieldsExpanded.at(i)->field, (int)lengths[i]) );
+ if (!destConn->insertRecord(*dstTable, vals)) {
+ mysql_free_result(res);
+ return false;
+ }
+ updateProgress();
+ }
+ if (!row && mysql_errno(d->mysql)) {
+ mysql_free_result(res);
+ return false;
+ }
+ /*! @todo Check that wasn't an error, rather than end of result set */
+ mysql_free_result(res);
+ } else {
+ kdDebug() << "MySQLMigrate::drv_copyTable: null result" << endl;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+bool MySQLMigrate::drv_getTableSize(const QString& table, Q_ULLONG& size) {
+ if(d->executeSQL("SELECT COUNT(*) FROM `" + drv_escapeIdentifier(table)) + "`") {
+ MYSQL_RES *res = mysql_store_result(d->mysql);
+ if (res != NULL) {
+ MYSQL_ROW row;
+ while ((row = mysql_fetch_row(res)) != NULL) {
+ //! @todo check result valid
+ size = QString(row[0]).toULongLong();
+ }
+ mysql_free_result(res);
+ } else {
+ kdDebug() << "MySQLMigrate::drv_getTableSize: null result" << endl;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+//! Convert a MySQL type to a KexiDB type, prompting user if necessary.
+KexiDB::Field::Type MySQLMigrate::type(const QString& table,
+ const MYSQL_FIELD *fld)
+{
+ // Field type
+ KexiDB::Field::Type kexiType = KexiDB::Field::InvalidType;
+
+ switch(fld->type)
+ {
+ // These are in the same order as mysql_com.h.
+ // MySQL names given on the right
+ case FIELD_TYPE_DECIMAL: // DECIMAL or NUMERIC
+ break;
+ case FIELD_TYPE_TINY: // TINYINT (-2^7..2^7-1 or 2^8)
+ kexiType = KexiDB::Field::Byte;
+ break;
+ case FIELD_TYPE_SHORT: // SMALLINT (-2^15..2^15-1 or 2^16)
+ kexiType = KexiDB::Field::ShortInteger;
+ break;
+ case FIELD_TYPE_LONG: // INTEGER (-2^31..2^31-1 or 2^32)
+ kexiType = KexiDB::Field::Integer;
+ break;
+ case FIELD_TYPE_FLOAT: // FLOAT
+ kexiType = KexiDB::Field::Float;
+ break;
+ case FIELD_TYPE_DOUBLE: // DOUBLE or REAL (8 byte)
+ kexiType = KexiDB::Field::Double;
+ break;
+ case FIELD_TYPE_NULL: // WTF?
+ break;
+ case FIELD_TYPE_TIMESTAMP: // TIMESTAMP (promote?)
+ kexiType = KexiDB::Field::DateTime;
+ break;
+ case FIELD_TYPE_LONGLONG: // BIGINT (-2^63..2^63-1 or 2^64)
+ case FIELD_TYPE_INT24: // MEDIUMINT (-2^23..2^23-1 or 2^24) (promote)
+ kexiType = KexiDB::Field::BigInteger;
+ break;
+ case FIELD_TYPE_DATE: // DATE
+ kexiType = KexiDB::Field::Date;
+ break;
+ case FIELD_TYPE_TIME: // TIME
+ kexiType = KexiDB::Field::Time;
+ break;
+ case FIELD_TYPE_DATETIME: // DATETIME
+ kexiType = KexiDB::Field::DateTime;
+ break;
+ case FIELD_TYPE_YEAR: // YEAR (promote)
+ kexiType = KexiDB::Field::ShortInteger;
+ break;
+ case FIELD_TYPE_NEWDATE: // WTF?
+ case FIELD_TYPE_ENUM: // ENUM
+ // If MySQL did what it's documentation said it did, we would come here
+ // for enum fields ...
+ kexiType = KexiDB::Field::Enum;
+ break;
+ case FIELD_TYPE_SET: // SET
+ //! @todo: Support set column type
+ break;
+ case FIELD_TYPE_TINY_BLOB:
+ case FIELD_TYPE_MEDIUM_BLOB:
+ case FIELD_TYPE_LONG_BLOB:
+ case FIELD_TYPE_BLOB: // BLOB or TEXT
+ case FIELD_TYPE_VAR_STRING: // VARCHAR
+ case FIELD_TYPE_STRING: // CHAR
+
+ if (fld->flags & ENUM_FLAG) {
+ // ... instead we come here, using the ENUM_FLAG which is supposed to
+ // be deprecated! Duh.
+ kexiType = KexiDB::Field::Enum;
+ break;
+ }
+ kexiType = examineBlobField(table, fld);
+ break;
+ default:
+ kexiType = KexiDB::Field::InvalidType;
+ }
+
+ if (kexiType == KexiDB::Field::InvalidType) {
+ return userType(table);
+ }
+ return kexiType;
+}
+
+
+//! Distinguish between a BLOB and a TEXT field
+/*! MySQL uses the same field type to identify BLOB and TEXT fields.
+ This method queries the server to find out if a field is a binary
+ field or a text field. It also considers the length of CHAR and VARCHAR
+ fields to see whether Text or LongText is the appropriate Kexi field type.
+ Assumes fld is a CHAR, VARCHAR, one of the BLOBs or TEXTs.
+ \return KexiDB::Field::Text, KexiDB::Field::LongText or KexiDB::Field::BLOB
+*/
+KexiDB::Field::Type MySQLMigrate::examineBlobField(const QString& table,
+ const MYSQL_FIELD* fld) {
+ QString mysqlType;
+ KexiDB::Field::Type kexiType;
+ QString query = "SHOW COLUMNS FROM `" + drv_escapeIdentifier(table) +
+ "` LIKE '" + QString::fromLatin1(fld->name) + "'";
+
+ if(d->executeSQL(query)) {
+ MYSQL_RES *res = mysql_store_result(d->mysql);
+
+ if (res != NULL) {
+ MYSQL_ROW row;
+ while ((row = mysql_fetch_row(res)) != NULL) {
+ mysqlType = QString(row[1]);
+ }
+ mysql_free_result(res);
+ } else {
+ kdDebug() << "MySQLMigrate::examineBlobField: null result" << endl;
+ }
+ } else {
+ // Huh? MySQL wont tell us what kind of field it is! Lets guess.
+ return KexiDB::Field::LongText;
+ }
+
+ kdDebug() << "MySQLMigrate::examineBlobField: considering "
+ << mysqlType << endl;
+ if(mysqlType.contains("blob", false) != 0) {
+ // Doesn't matter how big it is, it's binary
+ kexiType = KexiDB::Field::BLOB;
+ } else if(mysqlType.contains("text", false) != 0) {
+ // All the TEXT types are too big for Kexi text.
+ kexiType = KexiDB::Field::BLOB;
+ } else if(fld->length < 200) {
+ kexiType = KexiDB::Field::Text;
+ } else {
+ kexiType = KexiDB::Field::LongText;
+ }
+ return kexiType;
+}
+
+
+//! Get the strings that identify values in an enum field
+/*! Parse the type of a MySQL enum field as returned by the server in a
+ 'DESCRIBE table' or 'SHOW COLUMNS FROM table' statement. The string
+ returned by the server is in the form 'enum('option1','option2').
+ In this example, the result should be a string list containing two
+ strings, "option1", "option2".
+ \return list of possible values the field can take
+ */
+QStringList MySQLMigrate::examineEnumField(const QString& table,
+ const MYSQL_FIELD* fld) {
+ QString vals;
+ QString query = "SHOW COLUMNS FROM `" + drv_escapeIdentifier(table) +
+ "` LIKE '" + QString::fromLatin1(fld->name) + "'";
+
+ if(d->executeSQL(query)) {
+ MYSQL_RES *res = mysql_store_result(d->mysql);
+
+ if (res != NULL) {
+ MYSQL_ROW row;
+ while ((row = mysql_fetch_row(res)) != NULL) {
+ vals = QString(row[1]);
+ }
+ mysql_free_result(res);
+ } else {
+ kdDebug() << "MySQLMigrate::examineEnumField: null result" << endl;
+ }
+ } else {
+ // Huh? MySQL wont tell us what values it can take.
+ return QStringList();
+ }
+
+ kdDebug() << "MySQLMigrate::examineEnumField: considering "
+ << vals << endl;
+
+ // Crash and burn if we get confused...
+ if(!vals.startsWith("enum(")) {
+ // Huh? We're supposed to be parsing an enum!
+ kdDebug() << "MySQLMigrate::examineEnumField:1 not an enum!" << endl;
+ return QStringList();
+ }
+ if(!vals.endsWith(")")) {
+ kdDebug() << "MySQLMigrate::examineEnumField:2 not an enum!" << endl;
+ return QStringList();
+ }
+
+ // It'd be nice to use QString.section or QStringList.split, but we need
+ // to be careful as enum values can have commas and quote marks in them
+ // e.g. CREATE TABLE t(f enum('option,''') gives one option: "option,'"
+ vals = vals.remove(0,5);
+ QRegExp rx = QRegExp("^'((?:[^,']|,|'')*)'");
+ QStringList values = QStringList();
+ int index = 0;
+
+ while ((index = rx.search(vals, index, QRegExp::CaretAtOffset)) != -1) {
+ int len = rx.matchedLength();
+ if (len != -1) {
+ kdDebug() << "MySQLMigrate::examineEnumField:3 " << rx.cap(1) << endl;
+ values << rx.cap(1);
+ } else {
+ kdDebug() << "MySQLMigrate::examineEnumField:4 lost" << endl;
+ }
+
+ QChar next = vals[index + len];
+ if (next != QChar(',') && next != QChar(')')) {
+ kdDebug() << "MySQLMigrate::examineEnumField:5 " << (char)next << endl;
+ }
+ index += len + 1;
+ }
+
+ return values;
+}
+
+
+void MySQLMigrate::getConstraints(int flags, KexiDB::Field* fld) {
+ fld->setPrimaryKey(flags & PRI_KEY_FLAG);
+ fld->setAutoIncrement(flags & AUTO_INCREMENT_FLAG);
+ fld->setNotNull(flags & NOT_NULL_FLAG);
+ fld->setUniqueKey(flags & UNIQUE_KEY_FLAG);
+ //! @todo: Keys and uniqueness
+}
+
+
+void MySQLMigrate::getOptions(int flags, KexiDB::Field* fld) {
+ fld->setUnsigned(flags & UNSIGNED_FLAG);
+}
+
+
+#include "mysqlmigrate.moc"
diff --git a/kexi/migration/mysql/mysqlmigrate.h b/kexi/migration/mysql/mysqlmigrate.h
new file mode 100644
index 000000000..9f32bd2f4
--- /dev/null
+++ b/kexi/migration/mysql/mysqlmigrate.h
@@ -0,0 +1,85 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Martin Ellis <m.a.ellis@ncl.ac.uk>
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef MYSQLMIGRATE_H
+#define MYSQLMIGRATE_H
+
+#include "migration/keximigrate.h"
+#include "kexidb/drivers/mySQL/mysqlconnection_p.h"
+
+namespace KexiMigration
+{
+
+ class MySQLMigrate : public KexiMigrate
+ {
+ Q_OBJECT
+ KEXIMIGRATION_DRIVER
+
+ private:
+ MySqlConnectionInternal *d;
+ MYSQL_RES *m_mysqlres;
+
+ protected:
+ //Driver specific function to return table names
+ virtual bool drv_tableNames(QStringList& tablenames);
+
+ //Driver specific implementation to read a table schema
+ virtual bool drv_readTableSchema(
+ const QString& originalName, KexiDB::TableSchema& tableSchema);
+ //Driver specific connection implementation
+ virtual bool drv_connect();
+ virtual bool drv_disconnect();
+
+ virtual tristate drv_queryStringListFromSQL(
+ const QString& sqlStatement, uint columnNumber,
+ QStringList& stringList, int numRecords = -1);
+
+ virtual tristate drv_fetchRecordFromSQL(const QString& sqlStatement,
+ KexiDB::RowData& data, bool &firstRecord);
+
+ virtual bool drv_copyTable(const QString& srcTable,
+ KexiDB::Connection *destConn, KexiDB::TableSchema* dstTable);
+
+ virtual bool drv_progressSupported() { return true; }
+ virtual bool drv_getTableSize(const QString& table, Q_ULLONG& size);
+
+//TODO: move this somewhere to low level class (MIGRATION?)
+// virtual bool drv_getTablesList( QStringList &list );
+//TODO: move this somewhere to low level class (MIGRATION?)
+// virtual bool drv_containsTable( const QString &tableName );
+
+ public:
+// MySQLMigrate();
+ MySQLMigrate(QObject *parent, const char *name, const QStringList& args = QStringList());
+ ~MySQLMigrate();
+
+ KexiDB::Field::Type type(const QString& table, const MYSQL_FIELD* t);
+
+ KexiDB::Field::Type examineBlobField(const QString& table,
+ const MYSQL_FIELD* fld);
+
+ QStringList examineEnumField(const QString& table,
+ const MYSQL_FIELD* fld);
+ void getConstraints(int mysqlConstraints, KexiDB::Field* fld);
+ void getOptions(int flags, KexiDB::Field* fld);
+ };
+}
+
+#endif
diff --git a/kexi/migration/pqxx/Makefile.am b/kexi/migration/pqxx/Makefile.am
new file mode 100644
index 000000000..d49027f18
--- /dev/null
+++ b/kexi/migration/pqxx/Makefile.am
@@ -0,0 +1,20 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = keximigrate_pqxx.la
+
+INCLUDES = -I$(srcdir)/../../.. -I$(top_srcdir)/kexi $(all_includes) -I$(PG_INCDIR) -I$(PQXX_INCDIR)
+
+keximigrate_pqxx_la_METASOURCES = AUTO
+
+keximigrate_pqxx_la_SOURCES = pqxxmigrate.cpp
+
+#TODO share -libs with pqxx kexidb drv!
+#keximigrate_pqxx_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) -lcom_err -lkrb5 -lssl -lcrypto -lcrypt -lpqxx ../libkeximigrate.la
+keximigrate_pqxx_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) -lpqxx ../libkeximigrate.la
+
+keximigrate_pqxx_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -L$(PQXX_LIBDIR) -L$(PG_LIBDIR) $(VER_INFO) -no-undefined
+
+kde_services_DATA = keximigrate_pqxx.desktop
+
+noinst_HEADERS = pqxxmigrate.h pg_type.h
+
diff --git a/kexi/migration/pqxx/keximigrate_pqxx.desktop b/kexi/migration/pqxx/keximigrate_pqxx.desktop
new file mode 100644
index 000000000..3f88cbefa
--- /dev/null
+++ b/kexi/migration/pqxx/keximigrate_pqxx.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Name=PostgreSQL
+Name[hi]=पोस्टग्रे-एसक्यूएल
+Name[ne]=पोस्ट ग्रे एसक्यूएल
+Comment=PostgreSQL Migration Driver for Kexi
+Comment[bg]=Драйвер за мигриране от PostgreSQL към Kexi
+Comment[ca]=Controlador de migració de PostgreSQL per a Kexi
+Comment[cy]=Gyrrydd Mudo PostgreSQL ar gyfer Kexi
+Comment[da]=PostgreSQL Migrationsdriver for Kexi
+Comment[de]=PostgreSQL-Migrationstreiber für Kexi
+Comment[el]=Οδηγός μεταφοράς PostgreSQL του Kexi
+Comment[eo]=PostgreSQL-migrada pelilo por Kexi
+Comment[es]=Controlador de migración a PostgreSQL para Kexi
+Comment[et]=Kexi PostgreSQL migreerumisdraiver
+Comment[eu]=Kexi-ren PostgreSQL migraziorako kontrolatzailea
+Comment[fa]=گردانندۀ جابه‌جایی PostgreSQL برای Kexi
+Comment[fi]=PostgreSQL yhdistämisajuri Kexille
+Comment[fr]=Pilote de migration PostgreSQL pour Kexi
+Comment[fy]=PostgreSQL-Migraasjestjoerprogramma foar Kexi
+Comment[gl]=Controlador de Migración de PostgreSQL de Kexi
+Comment[hr]=PostgreSQL upravljački program migracije podataka za Kexi
+Comment[hu]=Kexi PostgreSQL-migrálási meghajtó
+Comment[is]=PostgreSQL gagnaflutngingsrekill fyrir Kexi
+Comment[it]=Driver di migrazione PostgreSQL per Kexi
+Comment[ja]=Kexi PostgreSQL データ移行ドライバ
+Comment[km]=កម្មវិធី​បញ្ជា​សម្រាប់​ផ្លាស់ប្ដូរ PostgreSQL សម្រាប់ Kexi
+Comment[lv]=PostgreSQL datu migrācijas draiveris priekš Kexi
+Comment[nb]=Kexi-programmodul for migrering av MySQL-drivere
+Comment[nds]=PostgreSQL-Datenutlagerndriever för Kexi
+Comment[ne]=केक्सीका लागि पोस्टग्रेसSQL मापग्रेसन ड्राइभर
+Comment[nl]=PostgreSQL-migratiestuurprogramma voor Kexi
+Comment[nn]=Kexi-programmodul for migrering av MySQL-drivarar
+Comment[pl]=Wtyczka migracji danych z serwera PostgreSQL dla Kexi
+Comment[pt]=Controlador de Migração de PostgreSQL do Kexi
+Comment[pt_BR]=Driver de Migração do PostgreSQL para o Kexi
+Comment[ru]=Драйвер миграции PostrgeSQL для Kexi
+Comment[sk]=Ovládač PostgreSQL Migration pre Kexi
+Comment[sl]=Gonilnik PostgreSQL za prenos podatkov za Kexi
+Comment[sr]=Драјвер Kexi-ја за миграцију са PostgreSQL-а
+Comment[sr@Latn]=Drajver Kexi-ja za migraciju sa PostgreSQL-a
+Comment[sv]=PostgreSQL-övergångsdrivrutin för Kexi
+Comment[uk]=PostgreSQL драйвер міграції даних для Kexi
+Comment[uz]=Kexi uchun PostgreSQL migratsiya drayveri
+Comment[uz@cyrillic]=Kexi учун PostgreSQL миграция драйвери
+Comment[zh_CN]=Kexi PostgreSQL 升迁驱动程序
+Comment[zh_TW]=Kexi 的 PostgreSQL 轉移驅動程式
+X-KDE-Library=keximigrate_pqxx
+ServiceTypes=Kexi/MigrationDriver
+Type=Service
+InitialPreference=8
+X-Kexi-MigrationDriverName=PostgreSQL
+X-Kexi-MigrationDriverType=Network
+X-Kexi-KexiMigrationVersion=1.1
diff --git a/kexi/migration/pqxx/pg_type.h b/kexi/migration/pqxx/pg_type.h
new file mode 100644
index 000000000..e0ead91ad
--- /dev/null
+++ b/kexi/migration/pqxx/pg_type.h
@@ -0,0 +1,192 @@
+//
+//
+// C++ Interface: pg_type
+//
+// Description:
+//
+//
+// Author: Adam Pigg <adam@piggz.fsnet.co.uk>, (C) 2003
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+//boolean, 'true'/'false'
+#define BOOLOID 16
+
+//Unknown Type
+#define UNKNOWNOID 705
+
+//Numberic Types
+//==============
+
+//~18 digit integer, 8-byte storage
+#define INT8OID 20
+
+//-32 thousand to 32 thousand, 2-byte storage
+#define INT2OID 21
+
+//array of INDEX_MAX_KEYS int2 integers, used in system tables
+#define INT2VECTOROID 22
+
+//-2 billion to 2 billion integer, 4-byte storage
+#define INT4OID 23
+
+//single-precision floating point number, 4-byte storage
+#define FLOAT4OID 700
+
+//double-precision floating point number, 8-byte storage
+#define FLOAT8OID 701
+
+//monetary amounts, $d,ddd.cc
+#define CASHOID 790
+
+//numeric(precision, decimal), arbitrary precision number
+#define NUMERICOID 1700
+
+//==================================
+
+//Text Types
+//==========
+//variable-length string, binary values escaped
+#define BYTEAOID 17
+
+//single character
+#define CHAROID 18
+
+//variable-length string, no limit specified
+#define TEXTOID 25
+
+//char(length), blank-padded string, fixed storage length
+#define BPCHAROID 1042
+
+//varchar(length), non-blank-padded string, variable storage length
+#define VARCHAROID 1043
+
+//fixed-length bit string
+#define BITOID 1560
+
+//variable-length bit string
+#define VARBITOID 1562
+
+//==================================
+
+//Date Time Types
+//===============
+//absolute, limited-range date and time (Unix system time)
+#define ABSTIMEOID 702
+
+//relative, limited-range time interval (Unix delta time)
+#define RELTIMEOID 703
+
+//(abstime,abstime), time interval
+#define TINTERVALOID 704
+
+//ANSI SQL date
+#define DATEOID 1082
+
+//hh:mm:ss, ANSI SQL time
+#define TIMEOID 1083
+
+//date and time
+#define TIMESTAMPOID 1114
+
+//date and time with time zone
+#define TIMESTAMPTZOID 1184
+
+//@ <number> <units>, time interval
+#define INTERVALOID 1186
+
+//hh:mm:ss, ANSI SQL time
+#define TIMETZOID 1266
+
+
+//==================================
+
+//Internal OID Types
+//==================
+//object identifier(oid), maximum 4 billion
+#define OIDOID 26
+
+//(Block, offset), physical location of tuple
+#define TIDOID 27
+
+//transaction id
+#define XIDOID 28
+
+//command identifier type, sequence in transaction id
+#define CIDOID 29
+
+//array of INDEX_MAX_KEYS oids, used in system tables
+#define OIDVECTOROID 30
+
+
+//==================================
+
+//Geometric Types
+//===============
+//geometric point '(x, y)'
+#define POINTOID 600
+
+//geometric line segment '(pt1,pt2)'
+#define LSEGOID 601
+
+//geometric path '(pt1,...)'
+#define PATHOID 602
+
+//geometric box '(lower left,upper right)'
+#define BOXOID 603
+
+//geometric polygon '(pt1,...)'
+#define POLYGONOID 604
+
+//geometric line (not implemented)'
+#define LINEOID 628
+
+//geometric circle '(center,radius)'
+#define CIRCLEOID 718
+
+//==================================
+
+//Network Types
+//=============
+//XX:XX:XX:XX:XX:XX, MAC address
+#define MACADDROID 829
+
+//IP address/netmask, host address, netmask optional
+#define INETOID 869
+
+//network IP address/netmask, network address
+#define CIDROID 650
+
+//access control list
+#define ACLITEMOID 1033
+
+
+//==================================
+
+//Miscellaneous Types
+//===================
+//63-character type for storing system identifiers
+#define NAMEOID 19
+
+//registered procedure
+#define REGPROCOID 24
+
+//reference cursor (portal name)
+#define REFCURSOROID 1790
+
+//registered procedure (with args)
+#define REGPROCEDUREOID 2202
+
+//registered operator
+#define REGOPEROID 2203
+
+//registered operator (with args)
+#define REGOPERATOROID 2204
+
+//registered class
+#define REGCLASSOID 2205
+
+//registered type
+#define REGTYPEOID 2206
+
diff --git a/kexi/migration/pqxx/pqxxmigrate.cpp b/kexi/migration/pqxx/pqxxmigrate.cpp
new file mode 100644
index 000000000..157158973
--- /dev/null
+++ b/kexi/migration/pqxx/pqxxmigrate.cpp
@@ -0,0 +1,660 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "pqxxmigrate.h"
+#include "pg_type.h"
+
+#include <qstring.h>
+#include <kdebug.h>
+#include <qstringlist.h>
+
+//I maybe should not use stl?
+#include <string>
+#include <vector>
+
+#include <kexidb/cursor.h>
+#include <kexidb/utils.h>
+#include <kexidb/drivermanager.h>
+#include <kexiutils/identifier.h>
+#include <kexidb/drivers/pqxx/pqxxcursor.h> //for pgsqlCStrToVariant()
+
+using namespace KexiDB;
+using namespace KexiMigration;
+
+/*
+This is the implementation for the pqxx specific import routines
+Thi is currently pre alpha and in no way is it meant
+to compile, let alone work. This is meant as an example of
+what the system might be and is a work in progress
+*/
+
+KEXIMIGRATE_DRIVER_INFO( PqxxMigrate, pqxx )
+
+//==================================================================================
+//Constructor
+/*PqxxMigrate::PqxxMigrate()
+ : KexiMigrate(parent, name, args)
+{
+ m_res=0;
+ m_trans=0;
+ m_conn=0;
+}*/
+
+PqxxMigrate::PqxxMigrate(QObject *parent, const char *name, const QStringList &args)
+ : KexiMigrate(parent, name, args)
+{
+ m_res=0;
+ m_trans=0;
+ m_conn=0;
+ KexiDB::DriverManager manager;
+ m_kexiDBDriver = manager.driver("pqxx");
+}
+//==================================================================================
+//Destructor
+PqxxMigrate::~PqxxMigrate()
+{
+ clearResultInfo();
+}
+
+//==================================================================================
+//This is probably going to be quite complex...need to get the types for all columns
+//any any other attributes required by kexi
+//helped by reading the 'tables' test program
+bool PqxxMigrate::drv_readTableSchema(
+ const QString& originalName, KexiDB::TableSchema& tableSchema)
+{
+// m_table = new KexiDB::TableSchema(table);
+
+ //TODO IDEA: ask for user input for captions
+//moved m_table->setCaption(table + " table");
+
+ //Perform a query on the table to get some data
+ if (query("select * from \"" + originalName + "\" limit 1"))
+ {
+ //Loop round the fields
+ for (uint i = 0; i < (uint)m_res->columns(); i++)
+ {
+ QString fldName(m_res->column_name(i));
+ KexiDB::Field::Type fldType = type(m_res->column_type(i), fldName);
+ QString fldID( KexiUtils::string2Identifier(fldName) );
+ const pqxx::oid toid = tableOid(originalName);
+ if (toid==0)
+ return false;
+ KexiDB::Field *f = new KexiDB::Field(fldID, fldType);
+ f->setCaption(fldName);
+ f->setPrimaryKey(primaryKey(toid, i));
+ f->setUniqueKey(uniqueKey(toid, i));
+ f->setAutoIncrement(autoInc(toid, i));//This should be safe for all field types
+ tableSchema.addField(f);
+
+ // Do this for var/char types
+ //m_f->setLength(m_res->at(0)[i].size());
+
+ // Do this for numeric type
+ /*m_f->setScale(0);
+ m_f->setPrecision(0);*/
+
+ kdDebug() << "Added field [" << f->name() << "] type [" << f->typeName()
+ << "]" << endl;
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//==================================================================================
+//get a list of tables and put into the supplied string list
+bool PqxxMigrate::drv_tableNames(QStringList& tableNames)
+{
+ /*
+ //pg_ = standard postgresql tables, pga_ = tables added by pgaccess, sql_ = probably information schemas, kexi__ = existing kexi tables
+ if (query("SELECT relname FROM pg_class WHERE ((relkind = 'r') AND ((relname !~ '^pg_') AND (relname !~ '^pga_') AND (relname !~ '^sql_') AND (relname !~ '^kexi__')))"))
+ */
+ if (query("SELECT relname FROM pg_class WHERE ((relkind = 'r') AND ((relname !~ '^pg_') AND (relname !~ '^pga_') AND (relname !~ '^sql_')))"))
+ {
+ for (pqxx::result::const_iterator c = m_res->begin(); c != m_res->end(); ++c)
+ {
+ // Copy the result into the return list
+ tableNames << QString::fromLatin1 (c[0].c_str());
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//==================================================================================
+//Convert a postgresql type to a kexi type
+KexiDB::Field::Type PqxxMigrate::type(int t, const QString& fname)
+{
+ switch(t)
+ {
+ case UNKNOWNOID:
+ return KexiDB::Field::InvalidType;
+ case BOOLOID:
+ return KexiDB::Field::Boolean;
+ case INT2OID:
+ return KexiDB::Field::ShortInteger;
+ case INT4OID:
+ return KexiDB::Field::Integer;
+ case INT8OID:
+ return KexiDB::Field::BigInteger;
+ case FLOAT4OID:
+ return KexiDB::Field::Float;
+ case FLOAT8OID:
+ return KexiDB::Field::Double;
+ case NUMERICOID:
+ return KexiDB::Field::Double;
+ case DATEOID:
+ return KexiDB::Field::Date;
+ case TIMEOID:
+ return KexiDB::Field::Time;
+ case TIMESTAMPOID:
+ return KexiDB::Field::DateTime;
+ case BYTEAOID:
+ return KexiDB::Field::BLOB;
+ case BPCHAROID:
+ return KexiDB::Field::Text;
+ case VARCHAROID:
+ return KexiDB::Field::Text;
+ case TEXTOID:
+ return KexiDB::Field::LongText;
+ }
+
+ //Ask the user what to do with this field
+ return userType(fname);
+}
+
+//==================================================================================
+//Connect to the db backend
+bool PqxxMigrate::drv_connect()
+{
+ kdDebug() << "drv_connect: " << m_migrateData->sourceName << endl;
+
+ QString conninfo;
+ QString socket;
+
+ //Setup local/remote connection
+ if (m_migrateData->source->hostName.isEmpty())
+ {
+ if (m_migrateData->source->fileName().isEmpty())
+ {
+ socket="/tmp/.s.PGSQL.5432";
+ }
+ else
+ {
+ socket=m_migrateData->source->fileName();
+ }
+ }
+ else
+ {
+ conninfo = "host='" + m_migrateData->source->hostName + "'";
+ }
+
+ //Build up the connection string
+ if (m_migrateData->source->port == 0)
+ m_migrateData->source->port = 5432;
+
+ conninfo += QString::fromLatin1(" port='%1'").arg(m_migrateData->source->port);
+
+ conninfo += QString::fromLatin1(" dbname='%1'").arg(m_migrateData->sourceName);
+
+ if (!m_migrateData->source->userName.isNull())
+ conninfo += QString::fromLatin1(" user='%1'").arg(m_migrateData->source->userName);
+
+ if (!m_migrateData->source->password.isNull())
+ conninfo += QString::fromLatin1(" password='%1'").arg(m_migrateData->source->password);
+
+ try
+ {
+ m_conn = new pqxx::connection( conninfo.latin1() );
+ return true;
+ }
+ catch(const std::exception &e)
+ {
+ kdDebug() << "PqxxMigrate::drv_connect:exception - " << e.what() << endl;
+ }
+ catch(...)
+ {
+ kdDebug() << "PqxxMigrate::drv_connect:exception(...)??" << endl;
+ }
+ return false;
+}
+
+//==================================================================================
+//Connect to the db backend
+bool PqxxMigrate::drv_disconnect()
+{
+ if (m_conn)
+ {
+ m_conn->disconnect();
+ delete m_conn;
+ m_conn = 0;
+ }
+ return true;
+}
+//==================================================================================
+//Perform a query on the database and store result in m_res
+bool PqxxMigrate::query(const QString& statement)
+{
+ kdDebug() << "query: " << statement.latin1() << endl;
+
+ Q_ASSERT (m_conn);
+
+ // Clear the last result information...
+ clearResultInfo ();
+
+ try
+ {
+ //Create a transaction
+ m_trans = new pqxx::nontransaction(*m_conn);
+ //Create a result opject through the transaction
+ m_res = new pqxx::result(m_trans->exec(statement.latin1()));
+ //Commit the transaction
+ m_trans->commit();
+ //If all went well then return true, errors picked up by the catch block
+ return true;
+ }
+ catch (const std::exception &e)
+ {
+ //If an error ocurred then put the error description into _dbError
+ kdDebug() << "pqxxImport::query:exception - " << e.what() << endl;
+ return false;
+ }
+ catch(...)
+ {
+ kdDebug() << "PqxxMigrate::query:exception(...)??" << endl;
+ }
+ return true;
+}
+
+//=========================================================================
+//Clears the current result
+void PqxxMigrate::clearResultInfo()
+{
+ delete m_res;
+ m_res = 0;
+
+ delete m_trans;
+ m_trans = 0;
+}
+
+//=========================================================================
+//Return the OID for a table
+pqxx::oid PqxxMigrate::tableOid(const QString& table)
+{
+ QString statement;
+ static QString otable;
+ static pqxx::oid toid;
+
+ pqxx::nontransaction* tran = 0;
+ pqxx::result* tmpres = 0;
+
+ //Some simple result caching
+ if (table == otable)
+ {
+ kdDebug() << "Returning table OID from cache..." << endl;
+ return toid;
+ }
+ else
+ {
+ otable = table;
+ }
+
+ try
+ {
+ statement = "SELECT relfilenode FROM pg_class WHERE (relname = '";
+ statement += table;
+ statement += "')";
+
+ tran = new pqxx::nontransaction(*m_conn, "find_t_oid");
+ tmpres = new pqxx::result(tran->exec(statement.latin1()));
+
+ tran->commit();
+ if (tmpres->size() > 0)
+ {
+ //We have a key field for this table, lets check if its this column
+ tmpres->at(0).at(0).to(toid);
+ }
+ else
+ {
+ toid = 0;
+ }
+ }
+ catch(const std::exception &e)
+ {
+ kdDebug() << "pqxxSqlDB::tableOid:exception - " << e.what() << endl;
+ kdDebug() << "pqxxSqlDB::tableOid:failed statement - " << statement << endl;
+ toid = 0;
+ }
+ catch(...)
+ {
+ kdDebug() << "PqxxMigrate::tableOid:exception(...)??" << endl;
+ }
+ delete tmpres;
+ tmpres = 0;
+
+ delete tran;
+ tran = 0;
+
+ kdDebug() << "OID for table [" << table << "] is [" << toid << "]" << endl;
+ return toid;
+}
+
+//=========================================================================
+//Return whether or not the curent field is a primary key
+//TODO: Add result caching for speed
+bool PqxxMigrate::primaryKey(pqxx::oid table_uid, int col) const
+{
+ QString statement;
+ bool pkey;
+ int keyf;
+
+ pqxx::nontransaction* tran = 0;
+ pqxx::result* tmpres = 0;
+
+ try
+ {
+ statement = QString("SELECT indkey FROM pg_index WHERE ((indisprimary = true) AND (indrelid = %1))").arg(table_uid);
+
+ tran = new pqxx::nontransaction(*m_conn, "find_pkey");
+ tmpres = new pqxx::result(tran->exec(statement.latin1()));
+
+ tran->commit();
+ if (tmpres->size() > 0)
+ {
+ //We have a key field for this table, lets check if its this column
+ tmpres->at(0).at(0).to(keyf);
+ if (keyf-1 == col) //-1 because pg counts from 1 and we count from 0
+ {
+ pkey = true;
+ kdDebug() << "Field is pkey" << endl;
+ }
+ else
+ {
+ pkey = false;
+ kdDebug() << "Field is NOT pkey" << endl;
+ }
+ }
+ else
+ {
+ pkey = false;
+ kdDebug() << "Field is NOT pkey" << endl;
+ }
+ }
+ catch(const std::exception &e)
+ {
+ kdDebug() << "pqxxSqlDB::primaryKey:exception - " << e.what() << endl;
+ kdDebug() << "pqxxSqlDB::primaryKey:failed statement - " << statement << endl;
+ pkey = false;
+ }
+ delete tmpres;
+ tmpres = 0;
+
+ delete tran;
+ tran = 0;
+
+ return pkey;
+}
+
+//=========================================================================
+/*! Fetches single string at column \a columnNumber from result obtained
+ by running \a sqlStatement.
+ On success the result is stored in \a string and true is returned.
+ \return cancelled if there are no records available. */
+tristate PqxxMigrate::drv_queryStringListFromSQL(
+ const QString& sqlStatement, uint columnNumber, QStringList& stringList, int numRecords)
+{
+ std::string result;
+ int i = 0;
+ if (query(sqlStatement))
+ {
+ for (pqxx::result::const_iterator it = m_res->begin();
+ it != m_res->end() && (numRecords == -1 || i < numRecords); ++it, i++)
+ {
+ if (it.size() > 0 && it.size() > columnNumber) {
+ it.at(columnNumber).to(result);
+ stringList.append( QString::fromUtf8(result.c_str()) );
+ }
+ else {
+ clearResultInfo();
+ return cancelled;
+ }
+ }
+ }
+ else
+ return false;
+ clearResultInfo();
+/* delete tmpres;
+ tmpres = 0;
+
+ delete tran;
+ tran = 0;*/
+
+ if (i < numRecords)
+ return cancelled;
+
+ return true;
+ /*
+ if (d->executeSQL(sqlStatement)) {
+ MYSQL_RES *res = mysql_use_result(d->mysql);
+ if (res != NULL) {
+ MYSQL_ROW row = mysql_fetch_row(res);
+ if (!row) {
+ tristate r = mysql_errno(d->mysql) ? false : cancelled;
+ mysql_free_result(res);
+ return r;
+ }
+ uint numFields = mysql_num_fields(res);
+ if (columnNumber > (numFields-1)) {
+ kdWarning() << "PqxxMigrate::drv_querySingleStringFromSQL("<<sqlStatement
+ << "): columnNumber too large ("
+ << columnNumber << "), expected 0.." << numFields << endl;
+ mysql_free_result(res);
+ return false;
+ }
+ unsigned long *lengths = mysql_fetch_lengths(res);
+ if (!lengths) {
+ mysql_free_result(res);
+ return false;
+ }
+ string = QString::fromLatin1(row[columnNumber], lengths[columnNumber]);
+ mysql_free_result(res);
+ } else {
+ kdDebug() << "PqxxMigrate::drv_querySingleStringFromSQL(): null result" << endl;
+ }
+ return true;
+ } else {
+ return false;
+ }*/
+}
+
+tristate PqxxMigrate::drv_fetchRecordFromSQL(const QString& sqlStatement,
+ KexiDB::RowData& data, bool &firstRecord)
+{
+ if (firstRecord || !m_res) {
+ if (m_res)
+ clearResultInfo();
+ if (!query(sqlStatement))
+ return false;
+ m_fetchRecordFromSQL_iter = m_res->begin();
+ firstRecord = false;
+ }
+ else
+ ++m_fetchRecordFromSQL_iter;
+
+ if (m_fetchRecordFromSQL_iter == m_res->end()) {
+ clearResultInfo();
+ return cancelled;
+ }
+
+ std::string result;
+ const int numFields = m_fetchRecordFromSQL_iter.size();
+ data.resize(numFields);
+ for (int i=0; i < numFields; i++)
+ data[i] = KexiDB::pgsqlCStrToVariant(m_fetchRecordFromSQL_iter.at(i));
+ return true;
+}
+
+//=========================================================================
+/*! Copy PostgreSQL table to KexiDB database */
+bool PqxxMigrate::drv_copyTable(const QString& srcTable, KexiDB::Connection *destConn,
+ KexiDB::TableSchema* dstTable)
+{
+ std::vector<std::string> R;
+
+ pqxx::work T(*m_conn, "PqxxMigrate::drv_copyTable");
+
+ pqxx::tablereader stream(T, (srcTable.latin1()));
+
+ //Loop round each row, reading into a vector of strings
+ const KexiDB::QueryColumnInfo::Vector fieldsExpanded( dstTable->query()->fieldsExpanded() );
+ for (int n=0; (stream >> R); ++n)
+ {
+ QValueList<QVariant> vals;
+ std::vector<std::string>::const_iterator i, end( R.end() );
+ int index = 0;
+ for ( i = R.begin(); i != end; ++i, index++) {
+ if (fieldsExpanded.at(index)->field->type()==KexiDB::Field::BLOB || fieldsExpanded.at(index)->field->type()==KexiDB::Field::LongText)
+ vals.append( KexiDB::pgsqlByteaToByteArray((*i).c_str(), (*i).size()) );
+ else
+ vals.append( KexiDB::cstringToVariant((*i).c_str(),
+ fieldsExpanded.at(index)->field, (*i).size()) );
+ }
+ if (!destConn->insertRecord(*dstTable, vals))
+ return false;
+ updateProgress();
+ R.clear();
+ }
+
+ //This does not work in <libpqxx 2.2
+ //stream.complete();
+
+ return true;
+}
+
+//=========================================================================
+//Return whether or not the curent field is a primary key
+//TODO: Add result caching for speed
+bool PqxxMigrate::uniqueKey(pqxx::oid table_uid, int col) const
+{
+ QString statement;
+ bool ukey;
+ int keyf;
+
+ pqxx::nontransaction* tran = 0;
+ pqxx::result* tmpres = 0;
+
+ try
+ {
+ statement = QString("SELECT indkey FROM pg_index WHERE ((indisunique = true) AND (indrelid = %1))").arg(table_uid);
+
+ tran = new pqxx::nontransaction(*m_conn, "find_ukey");
+ tmpres = new pqxx::result(tran->exec(statement.latin1()));
+
+ tran->commit();
+ if (tmpres->size() > 0)
+ {
+ //We have a key field for this table, lets check if its this column
+ tmpres->at(0).at(0).to(keyf);
+ if (keyf-1 == col) //-1 because pg counts from 1 and we count from 0
+ {
+ ukey = true;
+ kdDebug() << "Field is unique" << endl;
+ }
+ else
+ {
+ ukey = false;
+ kdDebug() << "Field is NOT unique" << endl;
+ }
+ }
+ else
+ {
+ ukey = false;
+ kdDebug() << "Field is NOT unique" << endl;
+ }
+ }
+ catch(const std::exception &e)
+ {
+ kdDebug() << "uniqueKey:exception - " << e.what() << endl;
+ kdDebug() << "uniqueKey:failed statement - " << statement << endl;
+ ukey = false;
+ }
+
+ delete tmpres;
+ tmpres = 0;
+
+ delete tran;
+ tran = 0;
+
+ return ukey;
+}
+
+//==================================================================================
+//TODO::Implement
+bool PqxxMigrate::autoInc(pqxx::oid /*table_uid*/, int /*col*/) const
+{
+ return false;
+}
+
+//==================================================================================
+//TODO::Implement
+bool PqxxMigrate::notNull(pqxx::oid /*table_uid*/, int /*col*/) const
+{
+ return false;
+}
+
+//==================================================================================
+//TODO::Implement
+bool PqxxMigrate::notEmpty(pqxx::oid /*table_uid*/, int /*col*/) const
+{
+ return false;
+}
+
+//==================================================================================
+//Return a list of database names
+/*bool PqxxMigrate::drv_getDatabasesList( QStringList &list )
+{
+ KexiDBDrvDbg << "pqxxSqlConnection::drv_getDatabaseList" << endl;
+
+ if (executeSQL("SELECT datname FROM pg_database WHERE datallowconn = TRUE"))
+ {
+ std::string N;
+ for (pqxx::result::const_iterator c = m_res->begin(); c != m_res->end(); ++c)
+ {
+ // Read value of column 0 into a string N
+ c[0].to(N);
+ // Copy the result into the return list
+ list << QString::fromLatin1 (N.c_str());
+ KexiDBDrvDbg << N.c_str() << endl;
+ }
+ return true;
+ }
+
+ return false;
+}*/
+
+
+#include "pqxxmigrate.moc"
diff --git a/kexi/migration/pqxx/pqxxmigrate.h b/kexi/migration/pqxx/pqxxmigrate.h
new file mode 100644
index 000000000..c09c8a7a9
--- /dev/null
+++ b/kexi/migration/pqxx/pqxxmigrate.h
@@ -0,0 +1,120 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef PQXXIMPORT_H
+#define PQXXIMPORT_H
+
+#include <migration/keximigrate.h>
+
+//Kexi Includes
+#include <kexidb/field.h>
+#include <kexidb/connection.h>
+
+#include <pqxx/pqxx>
+
+namespace KexiMigration
+{
+ class PqxxMigrate : public KexiMigrate
+ {
+ Q_OBJECT
+ KEXIMIGRATION_DRIVER
+
+ public:
+ PqxxMigrate(QObject *parent, const char *name, const QStringList &args = QStringList());
+ virtual ~PqxxMigrate();
+
+ protected:
+ //Driver specific function to return table names
+ virtual bool drv_tableNames(QStringList& tablenames);
+
+ //Driver specific implementation to read a table schema
+ virtual bool drv_readTableSchema(
+ const QString& originalName, KexiDB::TableSchema& tableSchema);
+
+ //Driver specific connection implementation
+ virtual bool drv_connect();
+ virtual bool drv_disconnect();
+
+ virtual tristate drv_queryStringListFromSQL(
+ const QString& sqlStatement, uint columnNumber, QStringList& stringList,
+ int numRecords = -1);
+
+ /*! Fetches single record from result obtained
+ by running \a sqlStatement.
+ \a firstRecord should be first initialized to true, so the method can run
+ the query at first call and then set it will set \a firstRecord to false,
+ so subsequent calls will only fetch records.
+ On success the result is stored in \a data and true is returned,
+ \a data is resized to appropriate size. cancelled is returned on EOF. */
+//! @todo SQL-dependent!
+ virtual tristate drv_fetchRecordFromSQL(const QString& sqlStatement,
+ KexiDB::RowData& data, bool &firstRecord);
+
+ virtual bool drv_copyTable(const QString& srcTable,
+ KexiDB::Connection *destConn, KexiDB::TableSchema* dstTable);
+
+ private:
+ //lowlevel functions/objects
+ //database connection
+ pqxx::connection* m_conn;
+
+ //transaction
+ pqxx::nontransaction* m_trans;
+
+ //lowlevel result
+ pqxx::result* m_res;
+
+ //! Used in drv_fetchRecordFromSQL
+ pqxx::result::const_iterator m_fetchRecordFromSQL_iter;
+
+ //perform a query on the database
+ bool query(const QString& statement);
+
+ //Clear the result info
+ void clearResultInfo ();
+
+ pqxx::oid tableOid(const QString& tablename);
+
+ //Convert the pqxx type to a kexi type
+ KexiDB::Field::Type type(int t, const QString& fname);
+
+ //Find out the field constraints
+ //Return whether or not the field is a pkey
+ bool primaryKey(pqxx::oid table, int col) const;
+
+ //Return whether or not the field is unique
+ bool uniqueKey(pqxx::oid table, int col) const;
+
+ //Return whether or not the field is a foreign key
+ bool foreignKey(pqxx::oid table, int col) const;
+
+ //Return whether or not the field is not null
+ bool notNull(pqxx::oid table, int col) const;
+
+ //Return whether or not the field is not empty
+ bool notEmpty(pqxx::oid table, int col) const;
+
+ //Return whether or not the field is auto incrementing
+ bool autoInc(pqxx::oid table, int col) const;
+
+ };
+}
+
+#endif
diff --git a/kexi/migration/txt/Makefile.am b/kexi/migration/txt/Makefile.am
new file mode 100644
index 000000000..46964222e
--- /dev/null
+++ b/kexi/migration/txt/Makefile.am
@@ -0,0 +1,18 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = keximigrate_txtmigrate.la
+
+INCLUDES = -I$(srcdir)/../../.. -I$(top_srcdir)/kexi $(all_includes)
+
+keximigrate_txtmigrate_la_METASOURCES = AUTO
+
+keximigrate_txtmigrate_la_SOURCES = txtmigrate.cpp
+
+keximigrate_txtmigrate_la_LIBADD = $(LIB_KPARTS) $(LIB_QT) ../libkeximigrate.la
+
+keximigrate_txtmigrate_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(VER_INFO) -no-undefined
+
+kde_services_DATA = keximigrate_txtmigrate.desktop
+
+noinst_HEADERS = txtmigrate.h
+
diff --git a/kexi/migration/txt/txtmigrate.cpp b/kexi/migration/txt/txtmigrate.cpp
new file mode 100644
index 000000000..8109a5b1b
--- /dev/null
+++ b/kexi/migration/txt/txtmigrate.cpp
@@ -0,0 +1,27 @@
+//
+// C++ Implementation: txtmigrate
+//
+// Description:
+//
+//
+// Author: Adam Pigg <adam@piggz.co.uk>, (C) 2004
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include "txtmigrate.h"
+
+namespace KexiMigration {
+
+TxtMigrate::TxtMigrate()
+ : KexiMigrate()
+{
+}
+
+
+TxtMigrate::~TxtMigrate()
+{
+}
+
+
+};
diff --git a/kexi/migration/txt/txtmigrate.h b/kexi/migration/txt/txtmigrate.h
new file mode 100644
index 000000000..e4b830c08
--- /dev/null
+++ b/kexi/migration/txt/txtmigrate.h
@@ -0,0 +1,33 @@
+//
+// C++ Interface: txtmigrate
+//
+// Description:
+//
+//
+// Author: Adam Pigg <adam@piggz.co.uk>, (C) 2004
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef KEXIMIGRATIONTXTMIGRATE_H
+#define KEXIMIGRATIONTXTMIGRATE_H
+
+#include <keximigrate.h>
+
+namespace KexiMigration {
+
+/**
+@author Adam Pigg
+*/
+class TxtMigrate : public KexiMigrate
+{
+public:
+ TxtMigrate();
+
+ ~TxtMigrate();
+
+};
+
+};
+
+#endif
diff --git a/kexi/pics/Makefile.am b/kexi/pics/Makefile.am
new file mode 100644
index 000000000..191c9387d
--- /dev/null
+++ b/kexi/pics/Makefile.am
@@ -0,0 +1,34 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kexipics_DATA = cp-wiz.png imagebox.png
+
+kexipicsdir = $(kde_datadir)/kexi/pics
+
+
+icon_ICON = button test_it database database_import widgets form_edit \
+ lineedit autofield business_user urlrequest tabwidget frame \
+ relation state_data state_edit state_sql state_text state_view \
+ table table_newobj \
+ query query_newobj \
+ form form_newobj \
+ script script_newobj \
+ report report_newobj \
+ macro macro_newobj \
+ label calculation calculations key navigator_prev navigator_next navigator_first navigator_last \
+ navigator_new check combo groupbox line line_vertical line_horizontal listbox listview progress \
+ radio slider spin mouse_pointer widgetstack dateedit datetimeedit timeedit textedit \
+ pixmaplabel select_item spring spring_vertical signalslot \
+ grid aoleft aoright aotop aobottom \
+ aowidest aotallest aoshortest aonarrowest aogrid aofit aopos2grid \
+ new_sign raise lower alignobjs \
+ sort_az sort_za autonumber \
+ form_action \
+ clear_table_contents delete_table_row insert_table_row \
+ add_field multiple_obj
+
+icondir = $(kde_datadir)/kexi/icons
+
+EXTRA_DIST = $(icon_ICON)
+
+
+KDE_ICON = kexi kexiproject_sqlite kexiproject_sqlite2 kexiproject_shortcut
diff --git a/kexi/pics/blendkdeicons.sh b/kexi/pics/blendkdeicons.sh
new file mode 100755
index 000000000..47016de65
--- /dev/null
+++ b/kexi/pics/blendkdeicons.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Generates series of blended icons
+#
+# Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+#
+
+usage() {
+ echo "USAGE: $0 <modifier_icon> <suffix> <icon> <icon> ..
+example: $0 action-new_sign _newobj action_table action_query
+will result with:
+cr16-action-table.png blended using cr16-action-new_sign.png
+ and saved to cr16-action-table_newobj.png,
+cr22-action-table.png blended using cr22-action-new_sign.png
+ and saved to cr22-action-table_newobj.png,
+cr16-action-query.png blended using cr16-action-new_sign.png
+ and saved to cr16-action-query_newobj.png,
+etc..."
+}
+
+if [ $# -lt 3 ] ; then usage; exit 1; fi
+
+mod=$1
+shift
+suffix=$1
+shift
+
+if [ -z "$mod" -o -z "$suffix" ] ; then
+ usage
+ exit 1
+fi
+
+icon=$1
+while [ -n "$1" ] ; do
+ for size in 16 22 32 ; do
+ mod_file="cr"$size"-"$mod".png"
+ if [ -f "$mod_file" ] ; then
+ for i in `ls "cr"$size"-"$icon".png" 2> /dev/null` ; do
+ blendicons "$i" "$mod_file" `echo $i | sed "s/\.png/"$suffix".png/"`
+ done
+ fi
+ done
+ shift
+ icon=$1
+done
+
diff --git a/kexi/pics/cp-wiz.png b/kexi/pics/cp-wiz.png
new file mode 100644
index 000000000..24a058435
--- /dev/null
+++ b/kexi/pics/cp-wiz.png
Binary files differ
diff --git a/kexi/pics/cr128-action-form_action.png b/kexi/pics/cr128-action-form_action.png
new file mode 100644
index 000000000..81e20cd15
--- /dev/null
+++ b/kexi/pics/cr128-action-form_action.png
Binary files differ
diff --git a/kexi/pics/cr16-action-add_field.png b/kexi/pics/cr16-action-add_field.png
new file mode 100644
index 000000000..649986ccd
--- /dev/null
+++ b/kexi/pics/cr16-action-add_field.png
Binary files differ
diff --git a/kexi/pics/cr16-action-aofit.png b/kexi/pics/cr16-action-aofit.png
new file mode 100644
index 000000000..b61eb37cb
--- /dev/null
+++ b/kexi/pics/cr16-action-aofit.png
Binary files differ
diff --git a/kexi/pics/cr16-action-aogrid.png b/kexi/pics/cr16-action-aogrid.png
new file mode 100644
index 000000000..2e05aea92
--- /dev/null
+++ b/kexi/pics/cr16-action-aogrid.png
Binary files differ
diff --git a/kexi/pics/cr16-action-aopos2grid.png b/kexi/pics/cr16-action-aopos2grid.png
new file mode 100644
index 000000000..4045db719
--- /dev/null
+++ b/kexi/pics/cr16-action-aopos2grid.png
Binary files differ
diff --git a/kexi/pics/cr16-action-autofield.png b/kexi/pics/cr16-action-autofield.png
new file mode 100644
index 000000000..44ecbc366
--- /dev/null
+++ b/kexi/pics/cr16-action-autofield.png
Binary files differ
diff --git a/kexi/pics/cr16-action-autonumber.png b/kexi/pics/cr16-action-autonumber.png
new file mode 100644
index 000000000..0b0b2b132
--- /dev/null
+++ b/kexi/pics/cr16-action-autonumber.png
Binary files differ
diff --git a/kexi/pics/cr16-action-business_user.png b/kexi/pics/cr16-action-business_user.png
new file mode 100644
index 000000000..666020b83
--- /dev/null
+++ b/kexi/pics/cr16-action-business_user.png
Binary files differ
diff --git a/kexi/pics/cr16-action-button.png b/kexi/pics/cr16-action-button.png
new file mode 100644
index 000000000..77970a863
--- /dev/null
+++ b/kexi/pics/cr16-action-button.png
Binary files differ
diff --git a/kexi/pics/cr16-action-button_no.png b/kexi/pics/cr16-action-button_no.png
new file mode 100644
index 000000000..9f29ee21b
--- /dev/null
+++ b/kexi/pics/cr16-action-button_no.png
Binary files differ
diff --git a/kexi/pics/cr16-action-check.png b/kexi/pics/cr16-action-check.png
new file mode 100644
index 000000000..a894bd4a3
--- /dev/null
+++ b/kexi/pics/cr16-action-check.png
Binary files differ
diff --git a/kexi/pics/cr16-action-clear_table_contents.png b/kexi/pics/cr16-action-clear_table_contents.png
new file mode 100644
index 000000000..bc9ee6797
--- /dev/null
+++ b/kexi/pics/cr16-action-clear_table_contents.png
Binary files differ
diff --git a/kexi/pics/cr16-action-combo.png b/kexi/pics/cr16-action-combo.png
new file mode 100644
index 000000000..548662401
--- /dev/null
+++ b/kexi/pics/cr16-action-combo.png
Binary files differ
diff --git a/kexi/pics/cr16-action-database.png b/kexi/pics/cr16-action-database.png
new file mode 100644
index 000000000..9eb3ca562
--- /dev/null
+++ b/kexi/pics/cr16-action-database.png
Binary files differ
diff --git a/kexi/pics/cr16-action-database_import.png b/kexi/pics/cr16-action-database_import.png
new file mode 100644
index 000000000..2379fa7bf
--- /dev/null
+++ b/kexi/pics/cr16-action-database_import.png
Binary files differ
diff --git a/kexi/pics/cr16-action-delete_table_row.png b/kexi/pics/cr16-action-delete_table_row.png
new file mode 100644
index 000000000..7219a4775
--- /dev/null
+++ b/kexi/pics/cr16-action-delete_table_row.png
Binary files differ
diff --git a/kexi/pics/cr16-action-form.png b/kexi/pics/cr16-action-form.png
new file mode 100644
index 000000000..fee34b1ac
--- /dev/null
+++ b/kexi/pics/cr16-action-form.png
Binary files differ
diff --git a/kexi/pics/cr16-action-form_action.png b/kexi/pics/cr16-action-form_action.png
new file mode 100644
index 000000000..4e38a3247
--- /dev/null
+++ b/kexi/pics/cr16-action-form_action.png
Binary files differ
diff --git a/kexi/pics/cr16-action-form_newobj.png b/kexi/pics/cr16-action-form_newobj.png
new file mode 100644
index 000000000..107db309a
--- /dev/null
+++ b/kexi/pics/cr16-action-form_newobj.png
Binary files differ
diff --git a/kexi/pics/cr16-action-grid.png b/kexi/pics/cr16-action-grid.png
new file mode 100644
index 000000000..49205af58
--- /dev/null
+++ b/kexi/pics/cr16-action-grid.png
Binary files differ
diff --git a/kexi/pics/cr16-action-insert_table_row.png b/kexi/pics/cr16-action-insert_table_row.png
new file mode 100644
index 000000000..99d1a64fa
--- /dev/null
+++ b/kexi/pics/cr16-action-insert_table_row.png
Binary files differ
diff --git a/kexi/pics/cr16-action-key.png b/kexi/pics/cr16-action-key.png
new file mode 100644
index 000000000..5a58d06e1
--- /dev/null
+++ b/kexi/pics/cr16-action-key.png
Binary files differ
diff --git a/kexi/pics/cr16-action-line.png b/kexi/pics/cr16-action-line.png
new file mode 100644
index 000000000..95a95c1a7
--- /dev/null
+++ b/kexi/pics/cr16-action-line.png
Binary files differ
diff --git a/kexi/pics/cr16-action-line_horizontal.png b/kexi/pics/cr16-action-line_horizontal.png
new file mode 100644
index 000000000..db9ae438e
--- /dev/null
+++ b/kexi/pics/cr16-action-line_horizontal.png
Binary files differ
diff --git a/kexi/pics/cr16-action-line_vertical.png b/kexi/pics/cr16-action-line_vertical.png
new file mode 100644
index 000000000..30630770c
--- /dev/null
+++ b/kexi/pics/cr16-action-line_vertical.png
Binary files differ
diff --git a/kexi/pics/cr16-action-lineedit.png b/kexi/pics/cr16-action-lineedit.png
new file mode 100644
index 000000000..3be3fea8d
--- /dev/null
+++ b/kexi/pics/cr16-action-lineedit.png
Binary files differ
diff --git a/kexi/pics/cr16-action-macro.png b/kexi/pics/cr16-action-macro.png
new file mode 100644
index 000000000..7810f9081
--- /dev/null
+++ b/kexi/pics/cr16-action-macro.png
Binary files differ
diff --git a/kexi/pics/cr16-action-macro_newobj.png b/kexi/pics/cr16-action-macro_newobj.png
new file mode 100644
index 000000000..36a02fbbc
--- /dev/null
+++ b/kexi/pics/cr16-action-macro_newobj.png
Binary files differ
diff --git a/kexi/pics/cr16-action-mouse_pointer.png b/kexi/pics/cr16-action-mouse_pointer.png
new file mode 100644
index 000000000..089b2f031
--- /dev/null
+++ b/kexi/pics/cr16-action-mouse_pointer.png
Binary files differ
diff --git a/kexi/pics/cr16-action-multiple_obj.png b/kexi/pics/cr16-action-multiple_obj.png
new file mode 100644
index 000000000..8c557a685
--- /dev/null
+++ b/kexi/pics/cr16-action-multiple_obj.png
Binary files differ
diff --git a/kexi/pics/cr16-action-navigator_first.png b/kexi/pics/cr16-action-navigator_first.png
new file mode 100644
index 000000000..75d7d2031
--- /dev/null
+++ b/kexi/pics/cr16-action-navigator_first.png
Binary files differ
diff --git a/kexi/pics/cr16-action-navigator_last.png b/kexi/pics/cr16-action-navigator_last.png
new file mode 100644
index 000000000..6eacb116a
--- /dev/null
+++ b/kexi/pics/cr16-action-navigator_last.png
Binary files differ
diff --git a/kexi/pics/cr16-action-navigator_new.png b/kexi/pics/cr16-action-navigator_new.png
new file mode 100644
index 000000000..69f1d7166
--- /dev/null
+++ b/kexi/pics/cr16-action-navigator_new.png
Binary files differ
diff --git a/kexi/pics/cr16-action-navigator_next.png b/kexi/pics/cr16-action-navigator_next.png
new file mode 100644
index 000000000..e51e7f6b8
--- /dev/null
+++ b/kexi/pics/cr16-action-navigator_next.png
Binary files differ
diff --git a/kexi/pics/cr16-action-navigator_prev.png b/kexi/pics/cr16-action-navigator_prev.png
new file mode 100644
index 000000000..d2133b073
--- /dev/null
+++ b/kexi/pics/cr16-action-navigator_prev.png
Binary files differ
diff --git a/kexi/pics/cr16-action-new_sign.png b/kexi/pics/cr16-action-new_sign.png
new file mode 100644
index 000000000..d6cb56c81
--- /dev/null
+++ b/kexi/pics/cr16-action-new_sign.png
Binary files differ
diff --git a/kexi/pics/cr16-action-pixmaplabel.png b/kexi/pics/cr16-action-pixmaplabel.png
new file mode 100644
index 000000000..6998c63dd
--- /dev/null
+++ b/kexi/pics/cr16-action-pixmaplabel.png
Binary files differ
diff --git a/kexi/pics/cr16-action-query.png b/kexi/pics/cr16-action-query.png
new file mode 100644
index 000000000..fda7d8e25
--- /dev/null
+++ b/kexi/pics/cr16-action-query.png
Binary files differ
diff --git a/kexi/pics/cr16-action-query_newobj.png b/kexi/pics/cr16-action-query_newobj.png
new file mode 100644
index 000000000..b97c0a73f
--- /dev/null
+++ b/kexi/pics/cr16-action-query_newobj.png
Binary files differ
diff --git a/kexi/pics/cr16-action-radio.png b/kexi/pics/cr16-action-radio.png
new file mode 100644
index 000000000..bcfe5f073
--- /dev/null
+++ b/kexi/pics/cr16-action-radio.png
Binary files differ
diff --git a/kexi/pics/cr16-action-relation.png b/kexi/pics/cr16-action-relation.png
new file mode 100644
index 000000000..b1d22304c
--- /dev/null
+++ b/kexi/pics/cr16-action-relation.png
Binary files differ
diff --git a/kexi/pics/cr16-action-report.png b/kexi/pics/cr16-action-report.png
new file mode 100644
index 000000000..e37e98cf2
--- /dev/null
+++ b/kexi/pics/cr16-action-report.png
Binary files differ
diff --git a/kexi/pics/cr16-action-report_newobj.png b/kexi/pics/cr16-action-report_newobj.png
new file mode 100644
index 000000000..511af8d2b
--- /dev/null
+++ b/kexi/pics/cr16-action-report_newobj.png
Binary files differ
diff --git a/kexi/pics/cr16-action-script.png b/kexi/pics/cr16-action-script.png
new file mode 100644
index 000000000..94ce068ad
--- /dev/null
+++ b/kexi/pics/cr16-action-script.png
Binary files differ
diff --git a/kexi/pics/cr16-action-script_newobj.png b/kexi/pics/cr16-action-script_newobj.png
new file mode 100644
index 000000000..48e1806f8
--- /dev/null
+++ b/kexi/pics/cr16-action-script_newobj.png
Binary files differ
diff --git a/kexi/pics/cr16-action-select_item.png b/kexi/pics/cr16-action-select_item.png
new file mode 100644
index 000000000..2af52e206
--- /dev/null
+++ b/kexi/pics/cr16-action-select_item.png
Binary files differ
diff --git a/kexi/pics/cr16-action-sort_az.png b/kexi/pics/cr16-action-sort_az.png
new file mode 100644
index 000000000..d88b6549a
--- /dev/null
+++ b/kexi/pics/cr16-action-sort_az.png
Binary files differ
diff --git a/kexi/pics/cr16-action-sort_za.png b/kexi/pics/cr16-action-sort_za.png
new file mode 100644
index 000000000..5ef418fc7
--- /dev/null
+++ b/kexi/pics/cr16-action-sort_za.png
Binary files differ
diff --git a/kexi/pics/cr16-action-spring.png b/kexi/pics/cr16-action-spring.png
new file mode 100644
index 000000000..77100a90e
--- /dev/null
+++ b/kexi/pics/cr16-action-spring.png
Binary files differ
diff --git a/kexi/pics/cr16-action-spring_vertical.png b/kexi/pics/cr16-action-spring_vertical.png
new file mode 100644
index 000000000..0d79db116
--- /dev/null
+++ b/kexi/pics/cr16-action-spring_vertical.png
Binary files differ
diff --git a/kexi/pics/cr16-action-state_data.png b/kexi/pics/cr16-action-state_data.png
new file mode 100644
index 000000000..80d277ad3
--- /dev/null
+++ b/kexi/pics/cr16-action-state_data.png
Binary files differ
diff --git a/kexi/pics/cr16-action-state_edit.png b/kexi/pics/cr16-action-state_edit.png
new file mode 100644
index 000000000..a30c037b8
--- /dev/null
+++ b/kexi/pics/cr16-action-state_edit.png
Binary files differ
diff --git a/kexi/pics/cr16-action-state_sql.png b/kexi/pics/cr16-action-state_sql.png
new file mode 100644
index 000000000..59151da19
--- /dev/null
+++ b/kexi/pics/cr16-action-state_sql.png
Binary files differ
diff --git a/kexi/pics/cr16-action-state_text.png b/kexi/pics/cr16-action-state_text.png
new file mode 100644
index 000000000..c96bd65b3
--- /dev/null
+++ b/kexi/pics/cr16-action-state_text.png
Binary files differ
diff --git a/kexi/pics/cr16-action-subform.png b/kexi/pics/cr16-action-subform.png
new file mode 100644
index 000000000..aa9dd8372
--- /dev/null
+++ b/kexi/pics/cr16-action-subform.png
Binary files differ
diff --git a/kexi/pics/cr16-action-table.png b/kexi/pics/cr16-action-table.png
new file mode 100644
index 000000000..8c69b0651
--- /dev/null
+++ b/kexi/pics/cr16-action-table.png
Binary files differ
diff --git a/kexi/pics/cr16-action-table_newobj.png b/kexi/pics/cr16-action-table_newobj.png
new file mode 100644
index 000000000..5fb7ead38
--- /dev/null
+++ b/kexi/pics/cr16-action-table_newobj.png
Binary files differ
diff --git a/kexi/pics/cr16-action-tabwidget.png b/kexi/pics/cr16-action-tabwidget.png
new file mode 100644
index 000000000..7aeae2e11
--- /dev/null
+++ b/kexi/pics/cr16-action-tabwidget.png
Binary files differ
diff --git a/kexi/pics/cr16-action-test_it.png b/kexi/pics/cr16-action-test_it.png
new file mode 100644
index 000000000..b88d12c36
--- /dev/null
+++ b/kexi/pics/cr16-action-test_it.png
Binary files differ
diff --git a/kexi/pics/cr16-action-textedit.png b/kexi/pics/cr16-action-textedit.png
new file mode 100644
index 000000000..f9e2a79a7
--- /dev/null
+++ b/kexi/pics/cr16-action-textedit.png
Binary files differ
diff --git a/kexi/pics/cr16-action-unknown_widget.png b/kexi/pics/cr16-action-unknown_widget.png
new file mode 100644
index 000000000..6d66ae519
--- /dev/null
+++ b/kexi/pics/cr16-action-unknown_widget.png
Binary files differ
diff --git a/kexi/pics/cr16-action-widgets.png b/kexi/pics/cr16-action-widgets.png
new file mode 100644
index 000000000..a6e5cba12
--- /dev/null
+++ b/kexi/pics/cr16-action-widgets.png
Binary files differ
diff --git a/kexi/pics/cr16-mime-kexiproject_sqlite.png b/kexi/pics/cr16-mime-kexiproject_sqlite.png
new file mode 100644
index 000000000..9e8a3093e
--- /dev/null
+++ b/kexi/pics/cr16-mime-kexiproject_sqlite.png
Binary files differ
diff --git a/kexi/pics/cr16-mime-kexiproject_sqlite.xcf b/kexi/pics/cr16-mime-kexiproject_sqlite.xcf
new file mode 100644
index 000000000..2f37f22e8
--- /dev/null
+++ b/kexi/pics/cr16-mime-kexiproject_sqlite.xcf
Binary files differ
diff --git a/kexi/pics/cr16-mime-kexiproject_sqlite2.png b/kexi/pics/cr16-mime-kexiproject_sqlite2.png
new file mode 100644
index 000000000..f87b20063
--- /dev/null
+++ b/kexi/pics/cr16-mime-kexiproject_sqlite2.png
Binary files differ
diff --git a/kexi/pics/cr16-mime-kexiproject_sqlite2.xcf b/kexi/pics/cr16-mime-kexiproject_sqlite2.xcf
new file mode 100644
index 000000000..f4cdd829c
--- /dev/null
+++ b/kexi/pics/cr16-mime-kexiproject_sqlite2.xcf
Binary files differ
diff --git a/kexi/pics/cr22-action-alignobjs.png b/kexi/pics/cr22-action-alignobjs.png
new file mode 100644
index 000000000..a9cd76f90
--- /dev/null
+++ b/kexi/pics/cr22-action-alignobjs.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aobottom.png b/kexi/pics/cr22-action-aobottom.png
new file mode 100644
index 000000000..c55261637
--- /dev/null
+++ b/kexi/pics/cr22-action-aobottom.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aofit.png b/kexi/pics/cr22-action-aofit.png
new file mode 100644
index 000000000..06f8377cd
--- /dev/null
+++ b/kexi/pics/cr22-action-aofit.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aogrid.png b/kexi/pics/cr22-action-aogrid.png
new file mode 100644
index 000000000..f666562d0
--- /dev/null
+++ b/kexi/pics/cr22-action-aogrid.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aoleft.png b/kexi/pics/cr22-action-aoleft.png
new file mode 100644
index 000000000..18207b037
--- /dev/null
+++ b/kexi/pics/cr22-action-aoleft.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aonarrowest.png b/kexi/pics/cr22-action-aonarrowest.png
new file mode 100644
index 000000000..e03d700db
--- /dev/null
+++ b/kexi/pics/cr22-action-aonarrowest.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aopos2grid.png b/kexi/pics/cr22-action-aopos2grid.png
new file mode 100644
index 000000000..e6836b284
--- /dev/null
+++ b/kexi/pics/cr22-action-aopos2grid.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aoright.png b/kexi/pics/cr22-action-aoright.png
new file mode 100644
index 000000000..be541a36c
--- /dev/null
+++ b/kexi/pics/cr22-action-aoright.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aoshortest.png b/kexi/pics/cr22-action-aoshortest.png
new file mode 100644
index 000000000..11c9ead0d
--- /dev/null
+++ b/kexi/pics/cr22-action-aoshortest.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aotallest.png b/kexi/pics/cr22-action-aotallest.png
new file mode 100644
index 000000000..2867afa29
--- /dev/null
+++ b/kexi/pics/cr22-action-aotallest.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aotop.png b/kexi/pics/cr22-action-aotop.png
new file mode 100644
index 000000000..80f289797
--- /dev/null
+++ b/kexi/pics/cr22-action-aotop.png
Binary files differ
diff --git a/kexi/pics/cr22-action-aowidest.png b/kexi/pics/cr22-action-aowidest.png
new file mode 100644
index 000000000..22a69b6ed
--- /dev/null
+++ b/kexi/pics/cr22-action-aowidest.png
Binary files differ
diff --git a/kexi/pics/cr22-action-autofield.png b/kexi/pics/cr22-action-autofield.png
new file mode 100644
index 000000000..bd5f038de
--- /dev/null
+++ b/kexi/pics/cr22-action-autofield.png
Binary files differ
diff --git a/kexi/pics/cr22-action-business_user.png b/kexi/pics/cr22-action-business_user.png
new file mode 100644
index 000000000..7278cfda2
--- /dev/null
+++ b/kexi/pics/cr22-action-business_user.png
Binary files differ
diff --git a/kexi/pics/cr22-action-button.png b/kexi/pics/cr22-action-button.png
new file mode 100644
index 000000000..4e1d7d679
--- /dev/null
+++ b/kexi/pics/cr22-action-button.png
Binary files differ
diff --git a/kexi/pics/cr22-action-check.png b/kexi/pics/cr22-action-check.png
new file mode 100644
index 000000000..1b6a3e8c2
--- /dev/null
+++ b/kexi/pics/cr22-action-check.png
Binary files differ
diff --git a/kexi/pics/cr22-action-clear_table_contents.png b/kexi/pics/cr22-action-clear_table_contents.png
new file mode 100644
index 000000000..0d12a443f
--- /dev/null
+++ b/kexi/pics/cr22-action-clear_table_contents.png
Binary files differ
diff --git a/kexi/pics/cr22-action-combo.png b/kexi/pics/cr22-action-combo.png
new file mode 100644
index 000000000..274641c8b
--- /dev/null
+++ b/kexi/pics/cr22-action-combo.png
Binary files differ
diff --git a/kexi/pics/cr22-action-database.png b/kexi/pics/cr22-action-database.png
new file mode 100644
index 000000000..47bad145d
--- /dev/null
+++ b/kexi/pics/cr22-action-database.png
Binary files differ
diff --git a/kexi/pics/cr22-action-database_import.png b/kexi/pics/cr22-action-database_import.png
new file mode 100644
index 000000000..868ca76b8
--- /dev/null
+++ b/kexi/pics/cr22-action-database_import.png
Binary files differ
diff --git a/kexi/pics/cr22-action-dateedit.png b/kexi/pics/cr22-action-dateedit.png
new file mode 100644
index 000000000..38061fc3b
--- /dev/null
+++ b/kexi/pics/cr22-action-dateedit.png
Binary files differ
diff --git a/kexi/pics/cr22-action-datetimeedit.png b/kexi/pics/cr22-action-datetimeedit.png
new file mode 100644
index 000000000..4b09cef13
--- /dev/null
+++ b/kexi/pics/cr22-action-datetimeedit.png
Binary files differ
diff --git a/kexi/pics/cr22-action-delete_table_row.png b/kexi/pics/cr22-action-delete_table_row.png
new file mode 100644
index 000000000..e326894ca
--- /dev/null
+++ b/kexi/pics/cr22-action-delete_table_row.png
Binary files differ
diff --git a/kexi/pics/cr22-action-form.png b/kexi/pics/cr22-action-form.png
new file mode 100644
index 000000000..50f608cca
--- /dev/null
+++ b/kexi/pics/cr22-action-form.png
Binary files differ
diff --git a/kexi/pics/cr22-action-form_action.png b/kexi/pics/cr22-action-form_action.png
new file mode 100644
index 000000000..6df938857
--- /dev/null
+++ b/kexi/pics/cr22-action-form_action.png
Binary files differ
diff --git a/kexi/pics/cr22-action-form_edit.png b/kexi/pics/cr22-action-form_edit.png
new file mode 100644
index 000000000..3ba359b9b
--- /dev/null
+++ b/kexi/pics/cr22-action-form_edit.png
Binary files differ
diff --git a/kexi/pics/cr22-action-frame.png b/kexi/pics/cr22-action-frame.png
new file mode 100644
index 000000000..41d3c3e9d
--- /dev/null
+++ b/kexi/pics/cr22-action-frame.png
Binary files differ
diff --git a/kexi/pics/cr22-action-grid.png b/kexi/pics/cr22-action-grid.png
new file mode 100644
index 000000000..258ee4a4e
--- /dev/null
+++ b/kexi/pics/cr22-action-grid.png
Binary files differ
diff --git a/kexi/pics/cr22-action-groupbox.png b/kexi/pics/cr22-action-groupbox.png
new file mode 100644
index 000000000..0bb662de2
--- /dev/null
+++ b/kexi/pics/cr22-action-groupbox.png
Binary files differ
diff --git a/kexi/pics/cr22-action-insert_table_row.png b/kexi/pics/cr22-action-insert_table_row.png
new file mode 100644
index 000000000..47bebb2ae
--- /dev/null
+++ b/kexi/pics/cr22-action-insert_table_row.png
Binary files differ
diff --git a/kexi/pics/cr22-action-key.png b/kexi/pics/cr22-action-key.png
new file mode 100644
index 000000000..5fbab54ad
--- /dev/null
+++ b/kexi/pics/cr22-action-key.png
Binary files differ
diff --git a/kexi/pics/cr22-action-label.png b/kexi/pics/cr22-action-label.png
new file mode 100644
index 000000000..b0ad6b77b
--- /dev/null
+++ b/kexi/pics/cr22-action-label.png
Binary files differ
diff --git a/kexi/pics/cr22-action-line.png b/kexi/pics/cr22-action-line.png
new file mode 100644
index 000000000..d2b1aff8a
--- /dev/null
+++ b/kexi/pics/cr22-action-line.png
Binary files differ
diff --git a/kexi/pics/cr22-action-line_horizontal.png b/kexi/pics/cr22-action-line_horizontal.png
new file mode 100644
index 000000000..fc8df6bac
--- /dev/null
+++ b/kexi/pics/cr22-action-line_horizontal.png
Binary files differ
diff --git a/kexi/pics/cr22-action-line_vertical.png b/kexi/pics/cr22-action-line_vertical.png
new file mode 100644
index 000000000..4d5c06b8e
--- /dev/null
+++ b/kexi/pics/cr22-action-line_vertical.png
Binary files differ
diff --git a/kexi/pics/cr22-action-lineedit.png b/kexi/pics/cr22-action-lineedit.png
new file mode 100644
index 000000000..f7f034f5c
--- /dev/null
+++ b/kexi/pics/cr22-action-lineedit.png
Binary files differ
diff --git a/kexi/pics/cr22-action-listbox.png b/kexi/pics/cr22-action-listbox.png
new file mode 100644
index 000000000..15a300b68
--- /dev/null
+++ b/kexi/pics/cr22-action-listbox.png
Binary files differ
diff --git a/kexi/pics/cr22-action-listview.png b/kexi/pics/cr22-action-listview.png
new file mode 100644
index 000000000..ffdf33db5
--- /dev/null
+++ b/kexi/pics/cr22-action-listview.png
Binary files differ
diff --git a/kexi/pics/cr22-action-lower.png b/kexi/pics/cr22-action-lower.png
new file mode 100644
index 000000000..670ba7612
--- /dev/null
+++ b/kexi/pics/cr22-action-lower.png
Binary files differ
diff --git a/kexi/pics/cr22-action-macro.png b/kexi/pics/cr22-action-macro.png
new file mode 100644
index 000000000..bd073b34c
--- /dev/null
+++ b/kexi/pics/cr22-action-macro.png
Binary files differ
diff --git a/kexi/pics/cr22-action-macro_newobj.png b/kexi/pics/cr22-action-macro_newobj.png
new file mode 100644
index 000000000..f9aebd459
--- /dev/null
+++ b/kexi/pics/cr22-action-macro_newobj.png
Binary files differ
diff --git a/kexi/pics/cr22-action-mouse_pointer.png b/kexi/pics/cr22-action-mouse_pointer.png
new file mode 100644
index 000000000..adf448142
--- /dev/null
+++ b/kexi/pics/cr22-action-mouse_pointer.png
Binary files differ
diff --git a/kexi/pics/cr22-action-multiple_obj.png b/kexi/pics/cr22-action-multiple_obj.png
new file mode 100644
index 000000000..828e9bd51
--- /dev/null
+++ b/kexi/pics/cr22-action-multiple_obj.png
Binary files differ
diff --git a/kexi/pics/cr22-action-new_sign.png b/kexi/pics/cr22-action-new_sign.png
new file mode 100644
index 000000000..7e49e4e78
--- /dev/null
+++ b/kexi/pics/cr22-action-new_sign.png
Binary files differ
diff --git a/kexi/pics/cr22-action-pixmaplabel.png b/kexi/pics/cr22-action-pixmaplabel.png
new file mode 100644
index 000000000..e556c8db0
--- /dev/null
+++ b/kexi/pics/cr22-action-pixmaplabel.png
Binary files differ
diff --git a/kexi/pics/cr22-action-progress.png b/kexi/pics/cr22-action-progress.png
new file mode 100644
index 000000000..16fb9eff9
--- /dev/null
+++ b/kexi/pics/cr22-action-progress.png
Binary files differ
diff --git a/kexi/pics/cr22-action-radio.png b/kexi/pics/cr22-action-radio.png
new file mode 100644
index 000000000..a7f51ffe8
--- /dev/null
+++ b/kexi/pics/cr22-action-radio.png
Binary files differ
diff --git a/kexi/pics/cr22-action-raise.png b/kexi/pics/cr22-action-raise.png
new file mode 100644
index 000000000..596009cdb
--- /dev/null
+++ b/kexi/pics/cr22-action-raise.png
Binary files differ
diff --git a/kexi/pics/cr22-action-relation.png b/kexi/pics/cr22-action-relation.png
new file mode 100644
index 000000000..8fb0493c4
--- /dev/null
+++ b/kexi/pics/cr22-action-relation.png
Binary files differ
diff --git a/kexi/pics/cr22-action-signalslot.png b/kexi/pics/cr22-action-signalslot.png
new file mode 100644
index 000000000..3ef87277a
--- /dev/null
+++ b/kexi/pics/cr22-action-signalslot.png
Binary files differ
diff --git a/kexi/pics/cr22-action-slider.png b/kexi/pics/cr22-action-slider.png
new file mode 100644
index 000000000..cf7752311
--- /dev/null
+++ b/kexi/pics/cr22-action-slider.png
Binary files differ
diff --git a/kexi/pics/cr22-action-sort_az.png b/kexi/pics/cr22-action-sort_az.png
new file mode 100644
index 000000000..0b5a256e8
--- /dev/null
+++ b/kexi/pics/cr22-action-sort_az.png
Binary files differ
diff --git a/kexi/pics/cr22-action-sort_za.png b/kexi/pics/cr22-action-sort_za.png
new file mode 100644
index 000000000..f984b95d5
--- /dev/null
+++ b/kexi/pics/cr22-action-sort_za.png
Binary files differ
diff --git a/kexi/pics/cr22-action-spin.png b/kexi/pics/cr22-action-spin.png
new file mode 100644
index 000000000..43e3067c8
--- /dev/null
+++ b/kexi/pics/cr22-action-spin.png
Binary files differ
diff --git a/kexi/pics/cr22-action-spring.png b/kexi/pics/cr22-action-spring.png
new file mode 100644
index 000000000..967d70779
--- /dev/null
+++ b/kexi/pics/cr22-action-spring.png
Binary files differ
diff --git a/kexi/pics/cr22-action-spring_vertical.png b/kexi/pics/cr22-action-spring_vertical.png
new file mode 100644
index 000000000..71a1dc045
--- /dev/null
+++ b/kexi/pics/cr22-action-spring_vertical.png
Binary files differ
diff --git a/kexi/pics/cr22-action-state_data.png b/kexi/pics/cr22-action-state_data.png
new file mode 100644
index 000000000..49d3a6854
--- /dev/null
+++ b/kexi/pics/cr22-action-state_data.png
Binary files differ
diff --git a/kexi/pics/cr22-action-state_edit.png b/kexi/pics/cr22-action-state_edit.png
new file mode 100644
index 000000000..3ba359b9b
--- /dev/null
+++ b/kexi/pics/cr22-action-state_edit.png
Binary files differ
diff --git a/kexi/pics/cr22-action-state_sql.png b/kexi/pics/cr22-action-state_sql.png
new file mode 100644
index 000000000..46361b968
--- /dev/null
+++ b/kexi/pics/cr22-action-state_sql.png
Binary files differ
diff --git a/kexi/pics/cr22-action-state_text.png b/kexi/pics/cr22-action-state_text.png
new file mode 100644
index 000000000..114ea1d09
--- /dev/null
+++ b/kexi/pics/cr22-action-state_text.png
Binary files differ
diff --git a/kexi/pics/cr22-action-subform.png b/kexi/pics/cr22-action-subform.png
new file mode 100644
index 000000000..86ed50edd
--- /dev/null
+++ b/kexi/pics/cr22-action-subform.png
Binary files differ
diff --git a/kexi/pics/cr22-action-table.png b/kexi/pics/cr22-action-table.png
new file mode 100644
index 000000000..0cf6507f6
--- /dev/null
+++ b/kexi/pics/cr22-action-table.png
Binary files differ
diff --git a/kexi/pics/cr22-action-table_newobj.png b/kexi/pics/cr22-action-table_newobj.png
new file mode 100644
index 000000000..198f9e589
--- /dev/null
+++ b/kexi/pics/cr22-action-table_newobj.png
Binary files differ
diff --git a/kexi/pics/cr22-action-tabwidget.png b/kexi/pics/cr22-action-tabwidget.png
new file mode 100644
index 000000000..68fb136c4
--- /dev/null
+++ b/kexi/pics/cr22-action-tabwidget.png
Binary files differ
diff --git a/kexi/pics/cr22-action-test_it.png b/kexi/pics/cr22-action-test_it.png
new file mode 100644
index 000000000..cdcd3e555
--- /dev/null
+++ b/kexi/pics/cr22-action-test_it.png
Binary files differ
diff --git a/kexi/pics/cr22-action-textedit.png b/kexi/pics/cr22-action-textedit.png
new file mode 100644
index 000000000..1f9120ec8
--- /dev/null
+++ b/kexi/pics/cr22-action-textedit.png
Binary files differ
diff --git a/kexi/pics/cr22-action-timeedit.png b/kexi/pics/cr22-action-timeedit.png
new file mode 100644
index 000000000..c47af2041
--- /dev/null
+++ b/kexi/pics/cr22-action-timeedit.png
Binary files differ
diff --git a/kexi/pics/cr22-action-unknown_widget.png b/kexi/pics/cr22-action-unknown_widget.png
new file mode 100644
index 000000000..0272349a1
--- /dev/null
+++ b/kexi/pics/cr22-action-unknown_widget.png
Binary files differ
diff --git a/kexi/pics/cr22-action-urlrequest.png b/kexi/pics/cr22-action-urlrequest.png
new file mode 100644
index 000000000..b49f32794
--- /dev/null
+++ b/kexi/pics/cr22-action-urlrequest.png
Binary files differ
diff --git a/kexi/pics/cr22-action-widgets.png b/kexi/pics/cr22-action-widgets.png
new file mode 100644
index 000000000..7f2726330
--- /dev/null
+++ b/kexi/pics/cr22-action-widgets.png
Binary files differ
diff --git a/kexi/pics/cr22-action-widgetstack.png b/kexi/pics/cr22-action-widgetstack.png
new file mode 100644
index 000000000..7a1b7c40e
--- /dev/null
+++ b/kexi/pics/cr22-action-widgetstack.png
Binary files differ
diff --git a/kexi/pics/cr32-action-business_user.png b/kexi/pics/cr32-action-business_user.png
new file mode 100644
index 000000000..8f73aec8c
--- /dev/null
+++ b/kexi/pics/cr32-action-business_user.png
Binary files differ
diff --git a/kexi/pics/cr32-action-clear_table_contents.png b/kexi/pics/cr32-action-clear_table_contents.png
new file mode 100644
index 000000000..2c9bb01e8
--- /dev/null
+++ b/kexi/pics/cr32-action-clear_table_contents.png
Binary files differ
diff --git a/kexi/pics/cr32-action-database.png b/kexi/pics/cr32-action-database.png
new file mode 100644
index 000000000..3ba597280
--- /dev/null
+++ b/kexi/pics/cr32-action-database.png
Binary files differ
diff --git a/kexi/pics/cr32-action-database_import.png b/kexi/pics/cr32-action-database_import.png
new file mode 100644
index 000000000..676b859d0
--- /dev/null
+++ b/kexi/pics/cr32-action-database_import.png
Binary files differ
diff --git a/kexi/pics/cr32-action-delete_table_row.png b/kexi/pics/cr32-action-delete_table_row.png
new file mode 100644
index 000000000..8924aba23
--- /dev/null
+++ b/kexi/pics/cr32-action-delete_table_row.png
Binary files differ
diff --git a/kexi/pics/cr32-action-form.png b/kexi/pics/cr32-action-form.png
new file mode 100644
index 000000000..e98d13d9b
--- /dev/null
+++ b/kexi/pics/cr32-action-form.png
Binary files differ
diff --git a/kexi/pics/cr32-action-form_action.png b/kexi/pics/cr32-action-form_action.png
new file mode 100644
index 000000000..aff8b52c0
--- /dev/null
+++ b/kexi/pics/cr32-action-form_action.png
Binary files differ
diff --git a/kexi/pics/cr32-action-grid.png b/kexi/pics/cr32-action-grid.png
new file mode 100644
index 000000000..62bb563d9
--- /dev/null
+++ b/kexi/pics/cr32-action-grid.png
Binary files differ
diff --git a/kexi/pics/cr32-action-insert_table_row.png b/kexi/pics/cr32-action-insert_table_row.png
new file mode 100644
index 000000000..36a33aa21
--- /dev/null
+++ b/kexi/pics/cr32-action-insert_table_row.png
Binary files differ
diff --git a/kexi/pics/cr32-action-key.png b/kexi/pics/cr32-action-key.png
new file mode 100644
index 000000000..9540a960a
--- /dev/null
+++ b/kexi/pics/cr32-action-key.png
Binary files differ
diff --git a/kexi/pics/cr32-action-macro.png b/kexi/pics/cr32-action-macro.png
new file mode 100644
index 000000000..7082cdb06
--- /dev/null
+++ b/kexi/pics/cr32-action-macro.png
Binary files differ
diff --git a/kexi/pics/cr32-action-macro_newobj.png b/kexi/pics/cr32-action-macro_newobj.png
new file mode 100644
index 000000000..bb083a41d
--- /dev/null
+++ b/kexi/pics/cr32-action-macro_newobj.png
Binary files differ
diff --git a/kexi/pics/cr32-action-new_sign.png b/kexi/pics/cr32-action-new_sign.png
new file mode 100644
index 000000000..a9bb26882
--- /dev/null
+++ b/kexi/pics/cr32-action-new_sign.png
Binary files differ
diff --git a/kexi/pics/cr32-action-pixmaplabel.png b/kexi/pics/cr32-action-pixmaplabel.png
new file mode 100644
index 000000000..12280a7c4
--- /dev/null
+++ b/kexi/pics/cr32-action-pixmaplabel.png
Binary files differ
diff --git a/kexi/pics/cr32-action-query.png b/kexi/pics/cr32-action-query.png
new file mode 100644
index 000000000..ea69dd45f
--- /dev/null
+++ b/kexi/pics/cr32-action-query.png
Binary files differ
diff --git a/kexi/pics/cr32-action-spring.png b/kexi/pics/cr32-action-spring.png
new file mode 100644
index 000000000..af9ef7367
--- /dev/null
+++ b/kexi/pics/cr32-action-spring.png
Binary files differ
diff --git a/kexi/pics/cr32-action-state_data.png b/kexi/pics/cr32-action-state_data.png
new file mode 100644
index 000000000..b5f03e635
--- /dev/null
+++ b/kexi/pics/cr32-action-state_data.png
Binary files differ
diff --git a/kexi/pics/cr32-action-state_sql.png b/kexi/pics/cr32-action-state_sql.png
new file mode 100644
index 000000000..0053f9971
--- /dev/null
+++ b/kexi/pics/cr32-action-state_sql.png
Binary files differ
diff --git a/kexi/pics/cr32-action-state_text.png b/kexi/pics/cr32-action-state_text.png
new file mode 100644
index 000000000..8877ae6e9
--- /dev/null
+++ b/kexi/pics/cr32-action-state_text.png
Binary files differ
diff --git a/kexi/pics/cr32-action-table.png b/kexi/pics/cr32-action-table.png
new file mode 100644
index 000000000..094d75f84
--- /dev/null
+++ b/kexi/pics/cr32-action-table.png
Binary files differ
diff --git a/kexi/pics/cr32-action-table_newobj.png b/kexi/pics/cr32-action-table_newobj.png
new file mode 100644
index 000000000..13754f248
--- /dev/null
+++ b/kexi/pics/cr32-action-table_newobj.png
Binary files differ
diff --git a/kexi/pics/cr32-mime-kexiproject_shortcut.png b/kexi/pics/cr32-mime-kexiproject_shortcut.png
new file mode 100644
index 000000000..7889509fc
--- /dev/null
+++ b/kexi/pics/cr32-mime-kexiproject_shortcut.png
Binary files differ
diff --git a/kexi/pics/cr32-mime-kexiproject_shortcut.xcf b/kexi/pics/cr32-mime-kexiproject_shortcut.xcf
new file mode 100644
index 000000000..31f9f3fa5
--- /dev/null
+++ b/kexi/pics/cr32-mime-kexiproject_shortcut.xcf
Binary files differ
diff --git a/kexi/pics/cr32-mime-kexiproject_sqlite.png b/kexi/pics/cr32-mime-kexiproject_sqlite.png
new file mode 100644
index 000000000..3bae78ab3
--- /dev/null
+++ b/kexi/pics/cr32-mime-kexiproject_sqlite.png
Binary files differ
diff --git a/kexi/pics/cr32-mime-kexiproject_sqlite.xcf b/kexi/pics/cr32-mime-kexiproject_sqlite.xcf
new file mode 100644
index 000000000..eaaa91819
--- /dev/null
+++ b/kexi/pics/cr32-mime-kexiproject_sqlite.xcf
Binary files differ
diff --git a/kexi/pics/cr32-mime-kexiproject_sqlite2.png b/kexi/pics/cr32-mime-kexiproject_sqlite2.png
new file mode 100644
index 000000000..07469f431
--- /dev/null
+++ b/kexi/pics/cr32-mime-kexiproject_sqlite2.png
Binary files differ
diff --git a/kexi/pics/cr32-mime-kexiproject_sqlite2.xcf b/kexi/pics/cr32-mime-kexiproject_sqlite2.xcf
new file mode 100644
index 000000000..78cf2ae04
--- /dev/null
+++ b/kexi/pics/cr32-mime-kexiproject_sqlite2.xcf
Binary files differ
diff --git a/kexi/pics/cr48-action-database_import.png b/kexi/pics/cr48-action-database_import.png
new file mode 100644
index 000000000..8a86f8ef9
--- /dev/null
+++ b/kexi/pics/cr48-action-database_import.png
Binary files differ
diff --git a/kexi/pics/cr48-action-form_action.png b/kexi/pics/cr48-action-form_action.png
new file mode 100644
index 000000000..ee3418cdf
--- /dev/null
+++ b/kexi/pics/cr48-action-form_action.png
Binary files differ
diff --git a/kexi/pics/cr48-action-key.png b/kexi/pics/cr48-action-key.png
new file mode 100644
index 000000000..0706ab2e3
--- /dev/null
+++ b/kexi/pics/cr48-action-key.png
Binary files differ
diff --git a/kexi/pics/cr64-action-business_user.png b/kexi/pics/cr64-action-business_user.png
new file mode 100644
index 000000000..d43f4048f
--- /dev/null
+++ b/kexi/pics/cr64-action-business_user.png
Binary files differ
diff --git a/kexi/pics/cr64-action-database.png b/kexi/pics/cr64-action-database.png
new file mode 100644
index 000000000..0b9cc3fa1
--- /dev/null
+++ b/kexi/pics/cr64-action-database.png
Binary files differ
diff --git a/kexi/pics/cr64-action-form_action.png b/kexi/pics/cr64-action-form_action.png
new file mode 100644
index 000000000..698ffc2e8
--- /dev/null
+++ b/kexi/pics/cr64-action-form_action.png
Binary files differ
diff --git a/kexi/pics/database-80.png b/kexi/pics/database-80.png
new file mode 100644
index 000000000..a29001c92
--- /dev/null
+++ b/kexi/pics/database-80.png
Binary files differ
diff --git a/kexi/pics/generate_newobj_icons.sh b/kexi/pics/generate_newobj_icons.sh
new file mode 100755
index 000000000..a235211c6
--- /dev/null
+++ b/kexi/pics/generate_newobj_icons.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# Generates _newobj icons for Kexi parts
+# append more icons when needed
+
+./blendkdeicons.sh action-new_sign _newobj action-table action-query action-form action-report action-script
diff --git a/kexi/pics/hi16-app-kexi.png b/kexi/pics/hi16-app-kexi.png
new file mode 100644
index 000000000..b6a5f8837
--- /dev/null
+++ b/kexi/pics/hi16-app-kexi.png
Binary files differ
diff --git a/kexi/pics/hi22-app-kexi.png b/kexi/pics/hi22-app-kexi.png
new file mode 100644
index 000000000..a7c740398
--- /dev/null
+++ b/kexi/pics/hi22-app-kexi.png
Binary files differ
diff --git a/kexi/pics/hi32-app-kexi.png b/kexi/pics/hi32-app-kexi.png
new file mode 100644
index 000000000..2e5ee71ab
--- /dev/null
+++ b/kexi/pics/hi32-app-kexi.png
Binary files differ
diff --git a/kexi/pics/hi48-app-kexi.png b/kexi/pics/hi48-app-kexi.png
new file mode 100644
index 000000000..5350a802a
--- /dev/null
+++ b/kexi/pics/hi48-app-kexi.png
Binary files differ
diff --git a/kexi/pics/hisc-app-kexi.svgz b/kexi/pics/hisc-app-kexi.svgz
new file mode 100644
index 000000000..daf7af66d
--- /dev/null
+++ b/kexi/pics/hisc-app-kexi.svgz
Binary files differ
diff --git a/kexi/pics/imagebox.png b/kexi/pics/imagebox.png
new file mode 100644
index 000000000..9f1953369
--- /dev/null
+++ b/kexi/pics/imagebox.png
Binary files differ
diff --git a/kexi/pics/kexi_yellow.svg b/kexi/pics/kexi_yellow.svg
new file mode 100644
index 000000000..0a0ba714f
--- /dev/null
+++ b/kexi/pics/kexi_yellow.svg
@@ -0,0 +1,603 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
+[
+ <!ATTLIST svg
+ xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink">
+]>
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+<svg
+ xml:space="preserve"
+ width="144pt"
+ height="144pt"
+ viewBox="0 0 144 144"
+ id="svg293"
+ sodipodi:version="0.28"
+ sodipodi:docname="/home/kristof/Desktop/kexi_yellow.svg"
+ sodipodi:docbase="/home/kristof/Desktop/"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs
+ id="defs391">
+ <linearGradient
+ id="linearGradient166">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop168" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#fff332;stop-opacity:1;"
+ id="stop167" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient163">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop165" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop164" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient160">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop162" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop161" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient157">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop159" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop158" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient154">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop156" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop155" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient151">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop153" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop152" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient148">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop150" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop149" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient145">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop147" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop146" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient142">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop144" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop143" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient139">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop141" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop140" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient136">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop138" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop137" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient133">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop135" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop134" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient130">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop132" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop131" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient127">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffa800;stop-opacity:1;"
+ id="stop129" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ffcc00;stop-opacity:1;"
+ id="stop128" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient124">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffcd00;stop-opacity:1;"
+ id="stop126" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#fff332;stop-opacity:1;"
+ id="stop125" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient121">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffcd00;stop-opacity:1;"
+ id="stop123" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#fff332;stop-opacity:1;"
+ id="stop122" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient118">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffcd00;stop-opacity:1;"
+ id="stop120" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#fff332;stop-opacity:1;"
+ id="stop119" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient115">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffcd00;stop-opacity:1;"
+ id="stop117" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#fff332;stop-opacity:1;"
+ id="stop116" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient112">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffcd00;stop-opacity:1;"
+ id="stop114" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#fff332;stop-opacity:1;"
+ id="stop113" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient108">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ffcd00;stop-opacity:1;"
+ id="stop110" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#fff332;stop-opacity:1;"
+ id="stop109" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient626">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ff0000;stop-opacity:1;"
+ id="stop627" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#ff771d;stop-opacity:1;"
+ id="stop628" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient621">
+ <stop
+ offset="0.000000"
+ style="stop-color:#ff1a00;stop-opacity:1;"
+ id="stop623" />
+ <stop
+ offset="1.000000"
+ style="stop-color:#970000;stop-opacity:1;"
+ id="stop622" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient626"
+ id="linearGradient625"
+ x1="0.0434781"
+ y1="0.953042"
+ x2="0.927536"
+ y2="0.0686696"
+ gradientUnits="objectBoundingBox"
+ gradientTransform="translate(-2.82978e-08,-2.09903e-08)"
+ spreadMethod="pad" />
+ <linearGradient
+ x1="126.131"
+ y1="96.3356"
+ x2="126.131"
+ y2="18.2654"
+ xlink:href="#linearGradient166"
+ id="linearGradient111"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="scale(1,1)"
+ spreadMethod="pad" />
+ </defs>
+ <sodipodi:namedview
+ id="base" />
+ <path
+ sodipodi:type="arc"
+ style="fill-rule:evenodd;stroke:none;fill:gray;fill-opacity:1;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;"
+ id="path515"
+ d="M 102.183029 67.114212 A 18.239388 18.318691 0 1 0 65.704254,67.114212 A 18.2394 18.3187 0 1 0 102.183 67.1142 L 83.943642 67.114212 z"
+ sodipodi:cx="83.943642"
+ sodipodi:cy="67.114212"
+ sodipodi:rx="18.239388"
+ sodipodi:ry="18.318691"
+ transform="matrix(2.15652,0,0,2.15584,-109.137,-86.6932)" />
+ <g
+ id="Layer_x0020_1"
+ style="fill-rule:nonzero;clip-rule:nonzero;fill:#FFFFFF;stroke:#FFFFFF;stroke-width:0;stroke-miterlimit:4;">
+ <path
+ style="fill:none;stroke:none;"
+ d="M144,144H0V0h144v144z"
+ id="path295" />
+ <g
+ style="stroke:none;"
+ id="g296">
+ <path
+ style="opacity:0.15;fill:#000000;"
+ d="M141.946,86.339c0,0-9.583-7.144-11.612-8.657c0.012-0.294,0.014-0.346,0.024-0.589c0.99-0.692,2.33-1.556,3.575-2.354c3.808-2.444,5.569-3.628,6.449-4.708c0.442-0.542,0.675-1.214,0.675-1.896c0-0.292-0.042-0.584-0.129-0.871 c-1.427-4.704-3.459-9.409-6.042-13.986c-0.422-0.749-1.145-1.28-1.985-1.459c0,0-10.194-2.176-11.073-2.363c-1.526-2.38-3.254-4.684-5.132-6.914c1.465-1.876,3.508-4.494,3.508-4.494c0.426-0.546,0.635-1.197,0.635-1.845c0-0.786-0.307-1.567-0.909-2.152 c-3.535-3.437-7.345-6.604-11.322-9.414c-0.88-0.622-2.025-0.724-3.001-0.268c0,0-4.592,2.146-6.515,3.044c-2.399-1.474-4.885-2.807-7.436-4.001c0.046-2.244,0.129-6.273,0.129-6.294c0-1.214-0.732-2.311-1.859-2.774c-4.843-1.992-9.847-3.551-14.873-4.635 c-1.485-0.32-2.975,0.52-3.469,1.957c0,0-1.129,3.283-1.915,5.566C67.274,17,64.907,16.9,62.591,16.961c-1.405-1.775-6.133-7.749-6.133-7.749c-0.615-0.777-1.574-1.2-2.563-1.131C49.719,8.375,45.7,9.12,41.861,10.178c-0.463-0.142-0.933-0.269-1.431-0.169 c-1.269,0.252-2.233,1.29-2.392,2.574c-0.125,1.016-0.167,3.89-0.167,7.191c0,1.511,0.01,3.091,0.023,4.643c-2.848-1.388-11.075-5.397-11.075-5.397c-1.058-0.516-2.328-0.369-3.235,0.38c-3.252,2.688-6.055,5.795-8.331,9.235c-0.265,0.4-0.43,0.858-0.481,1.335 c-0.094,0.879-0.573,10.581-0.718,13.533c-0.002,0.049-0.003,0.098-0.003,0.147c0,0.995,0.494,1.929,1.324,2.488c0,0,1.369,0.922,2.086,1.405c-3.138,0.184-6.438,0.377-6.438,0.377c-1.421,0.083-2.587,1.153-2.793,2.562L5.84,66.887 c-0.021,0.144-0.031,0.288-0.031,0.433c0,0.126,0.008,0.253,0.024,0.379L7.59,81.493c0.179,1.402,1.327,2.491,2.735,2.611c0,0,11.201,0.986,11.52,1.015c0.921,2.001,1.969,3.97,2.943,5.646c-0.052,0.069-0.118,0.084-0.167,0.165l-0.946,1.534 c-0.845,2.258-2.65,6.625-5.367,12.979c-0.162,0.379-0.242,0.78-0.242,1.179c0,0.648,0.21,1.291,0.617,1.823c2.807,3.67,11.993,12.969,15.994,13.923c0.829,0.197,1.702,0.033,2.403-0.452c0,0,8.722-6.038,11.201-7.755c2.149,1.334,5.337,3.25,7.654,4.383 c0.248,2.202,1.742,15.5,1.742,15.5c0.127,1.126,0.876,2.084,1.938,2.478c8.056,2.987,13.286,4.29,17.489,4.358c1.051,0.017,2.039-0.523,2.596-1.415l6.801-10.874c0.493-0.784,0.767-1.229,0.965-1.575c1.632-0.091,3.833-0.273,5.775-0.614 c1.229,1.58,7.355,9.457,7.355,9.457c0.723,0.93,1.924,1.351,3.069,1.075c5.195-1.249,10.589-5.115,10.816-5.278c0.162-0.117,0.312-0.25,0.447-0.396c0.431-0.466,10.582-11.445,12.104-13.242c0.474-0.559,0.72-1.248,0.72-1.941c0-0.501-0.128-1.004-0.391-1.463 c0,0-0.517-0.903-0.71-1.242c0.073-0.052,0.16-0.074,0.229-0.133l11.991-10.245c0.339-0.29,0.609-0.651,0.791-1.059c1.78-3.991,2.948-8.313,3.47-12.846c0.013-0.115,0.02-0.23,0.02-0.344c0-0.94-0.442-1.835-1.207-2.405z M94.708,91.046 c-9.713,2.066-21.49-0.58-31.501-7.08c-12.604-8.182-19.973-20.655-19.889-31.644c8.731-10.46,27.288-11.688,42.298-1.143c12.992,9.125,19.138,24.524,17.307,36.612c-2.38,1.494-5.133,2.598-8.215,3.254z"
+ id="path297" />
+ <linearGradient
+ id="aigrd1"
+ gradientUnits="userSpaceOnUse"
+ x1="72.902802"
+ y1="-4.174800"
+ x2="72.902802"
+ y2="62.410500"
+ xlink:href="#linearGradient166" />
+ <path
+ style="fill:url(#aigrd1);"
+ d="M88.425,32.417c-18.025-11.702-39.597-10.471-48.18,2.75c-2.726,4.2-3.809,9.105-3.446,14.216c7.919-12.325,27.871-14.404,43.817-3.203c13.741,9.65,19.836,26.324,16.922,38.671c3.231-1.768,5.97-4.203,7.983-7.308 c8.585-13.22,0.929-33.426-17.097-45.127z"
+ id="path301" />
+ <linearGradient
+ id="aigrd2"
+ gradientUnits="userSpaceOnUse"
+ x1="106.852097"
+ y1="78.406303"
+ x2="106.852097"
+ y2="78.406303"
+ xlink:href="#linearGradient148" />
+ <path
+ style="fill:url(#aigrd2);"
+ d="M106.852,78.406z"
+ id="path305" />
+ <linearGradient
+ id="aigrd3"
+ gradientUnits="userSpaceOnUse"
+ x1="115.468803"
+ y1="96.335403"
+ x2="115.468803"
+ y2="18.266199"
+ xlink:href="#linearGradient124" />
+ <path
+ style="fill:url(#aigrd3);"
+ d="M112.471,103.529c0,0.254,0.061,0.51,0.186,0.745c0,0,3.749,7.03,4.626,8.675c-0.043,0.049-0.116,0.136-0.185,0.218l1.368-0.693l-5.413-10.152c-0.04,0.033-0.086,0.052-0.122,0.089c-0.303,0.305-0.46,0.708-0.46,1.118z"
+ id="path309" />
+ <linearGradient
+ id="aigrd4"
+ gradientUnits="userSpaceOnUse"
+ x1="126.131798"
+ y1="96.336403"
+ x2="126.131798"
+ y2="18.265600"
+ xlink:href="#linearGradient108" />
+ <path
+ style="fill:url(#linearGradient111);"
+ d="M132.09,81.941l-10.999-8.209l-0.212-3.823c-0.217,0.267-0.352,0.432-0.352,0.432c-0.23,0.283-0.355,0.638-0.355,1.001c0,0.029,0.001,0.059,0.001,0.088l0.142,2.544c0.026,0.468,0.26,0.902,0.635,1.182c0,0,9.119,6.805,10.421,7.778 c-0.177,1.307-1.086,7.998-1.415,10.416l0.491,0.246l1.642-11.654z"
+ id="path313" />
+ <linearGradient
+ id="aigrd5"
+ gradientUnits="userSpaceOnUse"
+ x1="136.383301"
+ y1="153.057602"
+ x2="-6.856700"
+ y2="9.817600"
+ xlink:href="#linearGradient154" />
+ <path
+ style="fill:url(#aigrd5);"
+ d="M26.306,33.055l-8.973-6.675c-0.009,0.011-4.57-1.099-4.578-1.09c-0.092,0.861-0.704,13.36-0.704,13.36l11.19,7.536l3.064-13.131z"
+ id="path317" />
+ <linearGradient
+ id="aigrd6"
+ gradientUnits="userSpaceOnUse"
+ x1="100.092300"
+ y1="161.630905"
+ x2="-27.368000"
+ y2="34.170601"
+ xlink:href="#linearGradient151" />
+ <path
+ style="fill:url(#aigrd6);"
+ d="M61.79,98.94l-13.742-7.615l-13.586,9.342L21.664,88.126c-0.183,0.17,0.195-0.056-5.598,13.497c3.207,4.193,11.716,12.209,14.307,12.827c12.827-8.88,12.827-8.88,12.827-8.88s8.345,5.304,10.524,5.92c1.936,17.219,0,0,1.936,17.219 c6.78,2.514,12.274,4.103,16.495,4.171c7.793-12.522,7.765-12.334,7.793-12.522l-17.974-4.199L61.79,98.94z"
+ id="path321" />
+ <linearGradient
+ id="aigrd7"
+ gradientUnits="userSpaceOnUse"
+ x1="122.380402"
+ y1="137.483902"
+ x2="-8.173200"
+ y2="6.930300"
+ xlink:href="#linearGradient163" />
+ <path
+ style="fill:url(#aigrd7);"
+ d="M95.446,102.969l-13.01,0.885l-3.091,14.319c0.002,0,0.888,0.936,0.89,0.936c0,0,6.252-0.139,9.213-1.043c8.518,10.951,0,0,8.518,10.951c4.68-1.125,9.762-4.794,9.762-4.794s10.52-11.377,12.018-13.145l-13.936,7.124l-10.363-15.233z"
+ id="path325" />
+ <linearGradient
+ id="aigrd8"
+ gradientUnits="userSpaceOnUse"
+ x1="175.345200"
+ y1="114.981400"
+ x2="2.082100"
+ y2="-58.281601"
+ xlink:href="#linearGradient160" />
+ <path
+ style="fill:url(#aigrd8);"
+ d="M121.866,71.06c1.231-1.511,9.873-6.309,11.19-7.925l-11.634-0.069l0.444,7.994z"
+ id="path329" />
+ <linearGradient
+ id="aigrd9"
+ gradientUnits="userSpaceOnUse"
+ x1="146.297394"
+ y1="124.143097"
+ x2="-10.254700"
+ y2="-32.409000"
+ xlink:href="#linearGradient157" />
+ <path
+ style="fill:url(#aigrd9);"
+ d="M107.04,98.942l12.893,7.015l11.991-10.245l-14.396-8.396L107.04,98.942z"
+ id="path333" />
+ <linearGradient
+ id="aigrd10"
+ gradientUnits="userSpaceOnUse"
+ x1="150.292496"
+ y1="129.427704"
+ x2="11.981900"
+ y2="-8.882800"
+ xlink:href="#linearGradient145" />
+ <path
+ style="fill:url(#aigrd10);"
+ d="M36.016,7.951c-0.25,2.024-0.094,14.307-0.094,14.307c-0.001,0.008,0.001-0.008,0,0l5.896-4.068c0,0-5.801-10.239-5.802-10.239z"
+ id="path337" />
+ <linearGradient
+ id="aigrd11"
+ gradientUnits="userSpaceOnUse"
+ x1="112.647499"
+ y1="166.654297"
+ x2="-25.752100"
+ y2="28.254700"
+ xlink:href="#linearGradient142" />
+ <path
+ style="fill:url(#aigrd11);"
+ d="M25.015,62.481l-16.2-1.549L6.2,45.916L3.809,62.32l1.757,13.794c0,0,0,0,13.295,1.171c2.138,5.221,5.92,10.854,5.92,10.854l6.687-11.122l-6.453-14.536z"
+ id="path341" />
+ <linearGradient
+ id="aigrd12"
+ gradientUnits="userSpaceOnUse"
+ x1="53.722198"
+ y1="108.853996"
+ x2="-13.569500"
+ y2="41.562401"
+ xlink:href="#linearGradient139" />
+ <path
+ style="fill:url(#aigrd12);"
+ d="M6.2,45.916L5.048,61.498l0.518,14.616l2.223-15.27L6.2,45.916z"
+ id="path345" />
+ <linearGradient
+ id="aigrd13"
+ gradientUnits="userSpaceOnUse"
+ x1="66.733902"
+ y1="116.981003"
+ x2="8.118600"
+ y2="58.365601"
+ xlink:href="#linearGradient136" />
+ <path
+ style="fill:url(#aigrd13);"
+ d="M25.015,62.481l-6.154,14.804l6.249,10.854L32.392,76l-7.376-13.52z"
+ id="path349" />
+ <linearGradient
+ id="aigrd14"
+ gradientUnits="userSpaceOnUse"
+ x1="42.560501"
+ y1="121.809097"
+ x2="42.560501"
+ y2="5.797200"
+ xlink:href="#linearGradient121" />
+ <path
+ style="fill:url(#aigrd14);"
+ d="M48.886,5.797l-12.87,2.153l0.255,0.45c4.046-1.231,8.351-2.012,12.834-2.327l-0.22-0.277z"
+ id="path353" />
+ <linearGradient
+ id="aigrd15"
+ gradientUnits="userSpaceOnUse"
+ x1="6.216800"
+ y1="115.264198"
+ x2="135.466797"
+ y2="115.264198"
+ xlink:href="#linearGradient118" />
+ <path
+ style="fill:url(#aigrd15);"
+ d="M114.648,114.415c-1.104,0.56-2.261,1.145-3.356,1.699c1.155-0.524,2.271-1.093,3.356-1.699z"
+ id="path357" />
+ <linearGradient
+ id="aigrd16"
+ gradientUnits="userSpaceOnUse"
+ x1="70.676300"
+ y1="121.831497"
+ x2="70.676300"
+ y2="5.799800"
+ xlink:href="#linearGradient115" />
+ <path
+ style="fill:url(#aigrd16);"
+ d="M122.296,74.16l-0.017-0.2c0.069-0.901,0.115-1.81,0.115-2.73c0-2.471-0.265-5.016-0.772-7.608l11.435-0.487c-1.375-4.536-3.325-9.025-5.783-13.382c-2.799-0.599-10.558-2.253-12.243-2.613c-2.032-3.323-4.443-6.528-7.181-9.545 l4.99-6.392c-3.365-3.271-7.026-6.334-10.962-9.115l-7.944,3.712c-3.365-2.175-6.823-4.009-10.316-5.528l0.168-8.152c-4.776-1.964-9.591-3.447-14.364-4.477l-2.69,7.82c-3.603-0.498-7.144-0.653-10.561-0.461l-7.065-8.927c-4.483,0.314-8.788,1.095-12.834,2.327 l5.546,9.789c-3.018,1.267-5.792,2.896-8.255,4.89c-2.102-1.021-10.205-4.961-13.066-6.367c-2.966,2.452-5.576,5.307-7.741,8.579l12.525,8.231c-1.717,3.58-2.692,7.442-2.985,11.451L6.2,45.916c-0.226,4.868,0.324,9.889,1.589,14.928l17.226,1.637 c1.719,4.618,4.208,9.182,7.376,13.52L21.859,88.07c3.633,4.472,7.847,8.714,12.603,12.598l13.586-9.342l0.054,0.03c0.781,0.552,1.576,1.094,2.388,1.621c3.626,2.354,7.424,4.346,11.3,5.964l0.184,17.218c5.966,2.102,12.019,3.521,17.974,4.199 c0.632-4.201,2.375-15.751,2.489-16.503c4.491,0.267,8.876-0.01,13.01-0.885l10.363,15.233c1.902-0.601,3.733-1.296,5.483-2.088c1.095-0.554,2.251-1.139,3.356-1.699c1.799-1.002,3.499-2.116,5.097-3.336c-1.521-2.718-7.084-12.348-8.771-15.266 c2.494-2.028,4.642-4.407,6.396-7.112c0.064-0.098,0.117-0.202,0.18-0.3l14.374,7.311c1.673-3.752,2.745-7.765,3.229-11.967l-12.856-9.584z M104.967,77.334c-8.448,13.017-29.687,14.23-47.434,2.711C39.784,68.523,32.247,48.63,40.698,35.612 c8.45-13.019,29.688-14.232,47.437-2.711c17.748,11.524,25.285,31.415,16.832,44.433z"
+ id="path361" />
+ <path
+ d="M122.296,74.16l-0.017-0.2c0.069-0.901,0.115-1.81,0.115-2.73c0-2.471-0.265-5.016-0.772-7.608l11.435-0.487c-1.375-4.536-3.325-9.025-5.783-13.382c-2.799-0.599-10.558-2.253-12.243-2.613c-2.032-3.323-4.443-6.528-7.181-9.545l4.99-6.392 c-3.365-3.271-7.026-6.334-10.962-9.115l-7.944,3.712c-3.365-2.175-6.823-4.009-10.316-5.528l0.168-8.152c-4.776-1.964-9.591-3.447-14.364-4.477l-2.69,7.82c-3.603-0.498-7.144-0.653-10.561-0.461l-7.065-8.927c-4.483,0.314-8.788,1.095-12.834,2.327l5.546,9.789 c-3.018,1.267-5.792,2.896-8.255,4.89c-2.102-1.021-10.205-4.961-13.066-6.367c-2.966,2.452-5.576,5.307-7.741,8.579l12.525,8.231c-1.717,3.58-2.692,7.442-2.985,11.451L6.2,45.916c-0.226,4.868,0.324,9.889,1.589,14.928l17.226,1.637 c1.719,4.618,4.208,9.182,7.376,13.52L21.859,88.07c3.633,4.472,7.847,8.714,12.603,12.598l13.586-9.342l0.054,0.03c0.781,0.552,1.576,1.094,2.388,1.621c3.626,2.354,7.424,4.346,11.3,5.964l0.184,17.218c5.966,2.102,12.019,3.521,17.974,4.199 c0.632-4.201,2.375-15.751,2.489-16.503c4.491,0.267,8.876-0.01,13.01-0.885l10.363,15.233c1.902-0.601,3.733-1.296,5.483-2.088c1.095-0.554,2.251-1.139,3.356-1.699c1.799-1.002,3.499-2.116,5.097-3.336c-1.521-2.718-7.084-12.348-8.771-15.266 c2.494-2.028,4.642-4.407,6.396-7.112c0.064-0.098,0.117-0.202,0.18-0.3l14.374,7.311c1.673-3.752,2.745-7.765,3.229-11.967l-12.856-9.584z M104.967,77.334c-8.448,13.017-29.687,14.23-47.434,2.711C39.784,68.523,32.247,48.63,40.698,35.612 c8.45-13.019,29.688-14.232,47.437-2.711c17.748,11.524,25.285,31.415,16.832,44.433z"
+ id="path362" />
+ <linearGradient
+ id="aigrd17"
+ gradientUnits="userSpaceOnUse"
+ x1="46.714401"
+ y1="110.391602"
+ x2="7.720500"
+ y2="71.397697"
+ xlink:href="#linearGradient133" />
+ <path
+ style="fill:url(#aigrd17);"
+ d="M34.461,100.668l-4.089,13.781l12.827-8.88l4.849-14.243l-13.586,9.342z"
+ id="path366" />
+ <path
+ style="opacity:0.4;"
+ d="M53.723,111.489L61.79,98.94l0.184,17.218l-6.315,12.55l-1.936-17.219z"
+ id="path367" />
+ <path
+ style="opacity:0.4;"
+ d="M95.368,103.226l-5.92,14.84l8.518,10.951l7.844-10.815l-10.441-14.977z"
+ id="path368" />
+ <linearGradient
+ id="aigrd18"
+ gradientUnits="userSpaceOnUse"
+ x1="6.598100"
+ y1="68.167999"
+ x2="47.220600"
+ y2="73.443298"
+ xlink:href="#linearGradient130" />
+ <path
+ style="opacity:0.5;fill:url(#aigrd18);"
+ d="M7.789,60.844l-2.223,15.27l13.295,1.171l6.154-14.804L7.789,60.844z"
+ id="path372" />
+ <path
+ style="fill:none;"
+ d="M78.891,48.635c-12.729-8.941-29.232-8.926-37.496-0.202c0.413,10.034,7.507,20.918,18.444,28.018h0c9.357,6.076,20.29,8.566,29.242,6.662c2.197-0.468,4.167-1.258,5.982-2.226c0.08-0.847,0.188-1.688,0.188-2.547 c0-10.912-6.335-22.664-16.361-29.705z"
+ id="path373" />
+ <linearGradient
+ id="aigrd19"
+ gradientUnits="userSpaceOnUse"
+ x1="95.310501"
+ y1="91.098602"
+ x2="-32.409100"
+ y2="-8.183100"
+ xlink:href="#linearGradient127" />
+ <path
+ style="fill:url(#aigrd19);"
+ d="M136.946,81.339c0,0-9.583-7.144-11.612-8.657c0.012-0.294,0.014-0.346,0.024-0.589c0.99-0.692,2.33-1.556,3.575-2.354c3.808-2.444,5.569-3.628,6.449-4.708c0.442-0.542,0.675-1.214,0.675-1.896c0-0.292-0.042-0.584-0.129-0.871 c-1.427-4.704-3.459-9.409-6.042-13.986c-0.422-0.749-1.145-1.28-1.985-1.459c0,0-10.194-2.176-11.073-2.363c-1.526-2.38-3.254-4.684-5.132-6.914c1.465-1.876,3.508-4.494,3.508-4.494c0.426-0.546,0.635-1.197,0.635-1.845c0-0.786-0.307-1.567-0.909-2.152 c-3.535-3.437-7.345-6.604-11.322-9.414c-0.88-0.622-2.025-0.724-3.001-0.268c0,0-4.592,2.146-6.515,3.044c-2.399-1.474-4.885-2.807-7.436-4.001c0.046-2.244,0.129-6.273,0.129-6.294c0-1.214-0.732-2.311-1.859-2.774c-4.843-1.992-9.847-3.551-14.873-4.635 c-1.485-0.32-2.975,0.52-3.469,1.957c0,0-1.129,3.283-1.915,5.566C62.274,12,59.907,11.9,57.591,11.961c-1.405-1.775-6.133-7.749-6.133-7.749c-0.615-0.777-1.574-1.2-2.563-1.131C44.719,3.375,40.7,4.12,36.861,5.178c-0.463-0.142-0.933-0.269-1.431-0.169 c-1.269,0.252-2.233,1.29-2.392,2.574c-0.125,1.016-0.167,3.89-0.167,7.191c0,1.511,0.01,3.091,0.023,4.643c-2.848-1.388-11.075-5.397-11.075-5.397c-1.058-0.516-2.328-0.369-3.235,0.38c-3.252,2.688-6.055,5.795-8.331,9.235c-0.265,0.4-0.43,0.858-0.481,1.335 C9.678,25.85,9.199,35.551,9.055,38.504c-0.002,0.049-0.003,0.098-0.003,0.147c0,0.995,0.494,1.929,1.324,2.488c0,0,1.369,0.922,2.086,1.405c-3.138,0.184-6.438,0.377-6.438,0.377c-1.421,0.083-2.587,1.153-2.793,2.562L0.84,61.887 c-0.021,0.144-0.031,0.288-0.031,0.433c0,0.126,0.008,0.253,0.024,0.379L2.59,76.493c0.179,1.402,1.327,2.491,2.735,2.611c0,0,11.201,0.986,11.52,1.015c0.921,2.001,1.969,3.97,2.943,5.646c-0.052,0.069-0.118,0.084-0.167,0.165l-0.946,1.534 c-0.845,2.258-2.65,6.625-5.367,12.979c-0.162,0.379-0.242,0.78-0.242,1.179c0,0.648,0.21,1.291,0.617,1.823c2.807,3.67,11.993,12.969,15.994,13.923c0.829,0.197,1.702,0.033,2.403-0.452c0,0,8.722-6.038,11.201-7.755c2.149,1.334,5.337,3.25,7.654,4.383 c0.248,2.202,1.742,15.5,1.742,15.5c0.127,1.126,0.876,2.084,1.938,2.478c8.056,2.987,13.286,4.29,17.489,4.358c1.051,0.017,2.039-0.523,2.596-1.415l6.801-10.874c0.493-0.784,0.767-1.229,0.965-1.575c1.632-0.091,3.833-0.273,5.775-0.614 c1.229,1.58,7.355,9.457,7.355,9.457c0.723,0.93,1.924,1.351,3.069,1.075c5.195-1.249,10.589-5.115,10.816-5.278c0.162-0.117,0.312-0.25,0.447-0.396c0.431-0.466,10.582-11.445,12.104-13.242c0.474-0.559,0.72-1.248,0.72-1.941c0-0.501-0.128-1.004-0.391-1.463 c0,0-0.517-0.903-0.71-1.242c0.073-0.052,0.16-0.074,0.229-0.133l11.991-10.245c0.339-0.29,0.609-0.651,0.791-1.059c1.78-3.991,2.948-8.313,3.47-12.846c0.013-0.115,0.02-0.23,0.02-0.344c0-0.94-0.442-1.835-1.207-2.405z M131.923,95.712l-11.991,10.245 l-4.525-2.462c1.797,3.121,3.576,6.221,4.338,7.584c-1.498,1.768-12.018,13.145-12.018,13.145s-5.082,3.669-9.762,4.794c-8.518-10.951,0,0-8.518-10.951c-2.961,0.904-9.213,1.043-9.213,1.043c0,0-0.037-0.039-0.086-0.09c-0.075,0.499-0.143,0.95-0.202,1.338 c-0.028,0.188,0,0-7.793,12.522c-4.221-0.068-9.715-1.658-16.495-4.171c-1.936-17.219,0,0-1.936-17.219c-2.179-0.617-10.524-5.92-10.524-5.92s0,0-12.827,8.88c-2.59-0.617-11.1-8.634-14.307-12.827c5.793-13.553,5.416-13.327,5.598-13.497l1.512,1.482 c-0.438-0.513-0.895-1.019-1.317-1.538l1.664-1.907c-1.295-2.101-3.308-5.57-4.662-8.878l-2.509-0.221c-0.795-0.07-1.528-0.134-2.21-0.194L12.9,76.76c-0.491-0.043-0.948-0.083-1.375-0.121l-1.367-0.121c-0.813-0.071-1.484-0.13-2.034-0.179L7.228,76.26 c-0.125-0.011-0.237-0.021-0.344-0.03l-0.497-0.044c-0.077-0.007-0.147-0.013-0.211-0.019l-0.277-0.024c-0.093-0.008-0.161-0.014-0.209-0.019l-0.108-0.009c-0.015-0.001-0.016-0.001-0.016-0.001L3.809,62.32L6.2,45.916l15.308-0.897l-9.456-6.368 c0,0,0.612-12.499,0.704-13.36c2.165-3.271,4.775-6.127,7.741-8.579c2.861,1.406,10.964,5.346,13.066,6.367c0.752-0.608,1.542-1.171,2.35-1.712c-0.03-2.854-0.105-11.723,0.104-13.417c0,0,0.093,0.165,0.255,0.45c4.046-1.231,8.351-2.012,12.834-2.327 l7.065,8.927c3.417-0.192,6.958-0.037,10.561,0.461l2.69-7.82c4.773,1.029,9.588,2.513,14.364,4.477l-0.168,8.152c3.493,1.519,6.951,3.354,10.316,5.528l7.944-3.712c3.936,2.78,7.597,5.844,10.962,9.115l-4.99,6.392c2.738,3.018,5.149,6.223,7.181,9.545 c1.686,0.36,9.444,2.014,12.243,2.613c2.458,4.357,4.408,8.847,5.783,13.382c-1.201,1.473-8.464,5.58-10.68,7.442c0.004,0.217,0.017,0.437,0.017,0.653c0,0.921-0.045,1.83-0.115,2.73l0.017,0.2l12.856,9.584c-0.484,4.203-1.556,8.215-3.229,11.967z"
+ id="path377" />
+ <linearGradient
+ id="aigrd20"
+ gradientUnits="userSpaceOnUse"
+ x1="19.146999"
+ y1="2.485800"
+ x2="97.641296"
+ y2="80.981003"
+ xlink:href="#linearGradient621" />
+ <linearGradient
+ id="aigrd21"
+ gradientUnits="userSpaceOnUse"
+ x1="105.202103"
+ y1="96.159203"
+ x2="39.035801"
+ y2="31.004801"
+ xlink:href="#linearGradient112" />
+ <path
+ style="fill:url(#aigrd21);"
+ d="M121.231,73.251c0.285-2.209,0.34-7.574-0.167-10.167l11.992,0.051c-1.375-4.536-3.325-9.025-5.783-13.382c-2.799-0.599-10.558-2.253-12.243-2.613c-2.032-3.323-4.443-6.528-7.181-9.545l4.99-6.392c-3.365-3.271-7.026-6.334-10.962-9.115 l-7.944,3.712c-3.365-2.175-6.823-4.009-10.316-5.528l0.168-8.152c-4.776-1.964-9.591-3.447-14.364-4.477l-2.69,7.82c-3.603-0.498-7.144-0.653-10.561-0.461l-7.065-8.927c-4.483,0.314-8.788,1.095-12.834,2.327l5.546,9.789c-3.018,1.267-5.792,2.896-8.255,4.89 c-2.102-1.021-10.205-4.961-13.066-6.367c-2.966,2.452-5.576,5.307-7.741,8.579l12.976,7.793c-1.717,3.58-2.207,7.825-2.5,11.833L6.2,45.916c-0.226,4.868,0.933,9.296,2.198,14.335l17.333,1c1.719,4.618,5.165,10.329,8.333,14.667l-10,12 c2.903,3.574,4.552,6.248,10.167,10.833c0,0-0.167,0,13.667-9.5c2.833,2.833,11.291,6.882,15.167,8.5l0.5,17.5c5.966,2.102,9.045,2.655,15,3.333c0.632-4.201,2.219-15.581,2.333-16.333c4.491,0.267,11.2-0.125,15.333-1l10.167,15c3-0.833,9.902-4.446,11.5-5.667 c-1.521-2.718-6.646-12.415-8.333-15.333c2.494-2.028,5.746-5.462,7.5-8.167c0.064-0.098,14.5,7.667,14.5,7.667c1.673-3.752,3.104-6.804,3.588-11.006l-13.922-10.494z M104.967,77.334c-8.448,13.017-29.687,14.23-47.434,2.711 C39.784,68.523,32.247,48.63,40.698,35.612c8.45-13.019,29.688-14.232,47.437-2.711c17.748,11.524,25.285,31.415,16.832,44.433z"
+ id="path385" />
+ <path
+ d="M62.231,114.667l-6.625,13.875l8-12l-1.375-1.875z"
+ id="path386" />
+ <path
+ d="M105.356,117.542l-7.25,11.25l9.125-11l-1.875-0.25z"
+ id="path387" />
+ <path
+ d="M33.856,100.167l-3.625,14.125l5.125-14.25l-1.5,0.125z"
+ id="path388" />
+ <path
+ d="M7.606,60.042l-2,15.875l2.75-15l-0.75-0.875z"
+ id="path389" />
+ <path
+ style="fill:url(#aigrd20);"
+ d="M 38.916 34.304 C 29.87 48.236 37.75 69.465 56.48 81.626 C 67.182 88.572 79.85 91.384 90.369 89.146 C 97.589 87.611 103.289 83.897 106.852 78.405 C 109.36 74.544 111.872 66.5365 112.049 61.5347 C 112.403 56.6743 112.177 53.5166 111.721 51.3766 C 109.484 40.8586 105.648 31.8478 93.5306 23.6614 C 74.7996 11.5014 49.9055 17.7184 38.916 34.303 L 38.916 34.304 z M 58.207 78.967 C 40.942 67.759 33.481 48.498 41.575 36.03 C 49.668 23.563 70.297 22.538 87.562 33.745 C 97.574 40.246 104.785 49.925 106.851 59.639 C 108.207 66.019 107.288 71.911 104.191 76.681 C 101.095 81.45 96.087 84.687 89.707 86.046 C 79.994 88.112 68.217 85.466 58.206 78.966 L 58.207 78.967 z "
+ id="path381"
+ sodipodi:nodetypes="ccccccccccccccccc" />
+ </g>
+ </g>
+ <path
+ style="fill:url(#aigrd20);"
+ d="M 58.207 78.967 C 40.942 67.759 33.481 48.498 41.575 36.03 C 49.668 23.563 73.1254 17.5882 90.3904 28.7952 C 100.402 35.2962 106.73 47.8037 108.796 57.5177 C 110.152 63.8977 107.288 71.911 104.191 76.681 C 101.095 81.45 96.087 84.687 89.707 86.046 C 79.994 88.112 68.217 85.466 58.206 78.966 L 58.207 78.967 z "
+ id="path516" />
+ <path
+ style="fill:url(#linearGradient625);fill-opacity:1;"
+ d="M 58.207 78.967 C 40.942 67.759 33.481 48.7223 41.575 36.2543 C 49.668 23.7873 71.2222 12.8301 92.135 26.2575 C 103.891 33.7102 108.316 47.8037 109.113 56.0903 C 109.359 62.6289 107.288 71.911 104.191 76.681 C 101.095 81.45 96.087 84.687 89.707 86.046 C 79.994 88.112 68.217 85.466 58.206 78.966 L 58.207 78.967 z "
+ id="path624"
+ sodipodi:nodetypes="cccccccc" />
+</svg>
diff --git a/kexi/pics/tableview_pen.png b/kexi/pics/tableview_pen.png
new file mode 100644
index 000000000..35aa11639
--- /dev/null
+++ b/kexi/pics/tableview_pen.png
Binary files differ
diff --git a/kexi/pics/tableview_plus.png b/kexi/pics/tableview_plus.png
new file mode 100644
index 000000000..a99de1873
--- /dev/null
+++ b/kexi/pics/tableview_plus.png
Binary files differ
diff --git a/kexi/pics/wiz-temlate.png b/kexi/pics/wiz-temlate.png
new file mode 100644
index 000000000..1c554e493
--- /dev/null
+++ b/kexi/pics/wiz-temlate.png
Binary files differ
diff --git a/kexi/pics/wiz-template.png b/kexi/pics/wiz-template.png
new file mode 100644
index 000000000..1c554e493
--- /dev/null
+++ b/kexi/pics/wiz-template.png
Binary files differ
diff --git a/kexi/plugins/Makefile.am b/kexi/plugins/Makefile.am
new file mode 100644
index 000000000..6e0a7432f
--- /dev/null
+++ b/kexi/plugins/Makefile.am
@@ -0,0 +1,15 @@
+#if compile_kexi_reports_plugin
+# REPORTS=reports
+#endif
+
+if compile_kexi_macros_plugin
+ MACRODIR=macros
+endif
+
+if compile_kross
+ SCRIPTINGDIR=scripting
+endif
+
+SUBDIRS = tables relations migration queries forms $(SCRIPTINGDIR) $(MACRODIR) importexport
+
+#$(REPORTS) importwizard relations
diff --git a/kexi/plugins/Makefile.common b/kexi/plugins/Makefile.common
new file mode 100644
index 000000000..d49a2a0f9
--- /dev/null
+++ b/kexi/plugins/Makefile.common
@@ -0,0 +1,2 @@
+INCLUDES += $(LIB_KEXI_KMDI_INCLUDES)
+
diff --git a/kexi/plugins/configure.in.in b/kexi/plugins/configure.in.in
new file mode 100644
index 000000000..6d2cffcb6
--- /dev/null
+++ b/kexi/plugins/configure.in.in
@@ -0,0 +1,20 @@
+# disabled
+#AC_ARG_ENABLE(kexi-reports,
+# AC_HELP_STRING([--enable-kexi-reports],
+# [build Kexi reports plugin (EXPERIMENTAL) [default=no]]),
+# compile_kexi_reports_plugin=$enableval, compile_kexi_reports_plugin=no)
+#AM_CONDITIONAL(compile_kexi_reports_plugin, test "x$compile_kexi_reports_plugin" != "xno")
+#
+#if test "$compile_kexi_reports_plugin" == "yes"; then
+# AC_DEFINE(KEXI_REPORTS_SUPPORT, 1, [build Kexi reports plugin])
+#fi
+
+AC_ARG_ENABLE(kexi-macros,
+ AC_HELP_STRING([--enable-kexi-macros],
+ [build Kexi macro plugin (EXPERIMENTAL) [default=yes]]),
+ compile_kexi_macros_plugin=$enableval, compile_kexi_macros_plugin=no)
+AM_CONDITIONAL(compile_kexi_macros_plugin, test "x$compile_kexi_macros_plugin" == "xyes")
+
+if test "$compile_kexi_macros_plugin" == "yes"; then
+ AC_DEFINE(KEXI_MACROS_SUPPORT, 1, [build Kexi macros plugin])
+fi
diff --git a/kexi/plugins/configure.in.mid b/kexi/plugins/configure.in.mid
new file mode 100644
index 000000000..c8ca24b17
--- /dev/null
+++ b/kexi/plugins/configure.in.mid
@@ -0,0 +1,26 @@
+if test -s $srcdir/inst-apps ; then
+ SUBDIRLIST=`cat $srcdir/inst-apps`
+else
+ SUBDIRLIST=`cat $srcdir/subdirs`
+fi
+
+# fallback (KDE_CREATE_SUBDIRLIST has this fallback, so I have put it here too.)
+if test -z "$SUBDIRLIST" ; then
+ SUBDIRLIST=`ls -1 $srcdir`
+fi
+
+# first check which main apllication we could compile
+for args in $SUBDIRLIST ; do
+ case $args in
+ kugar) COMPILE_PLUGIN_KUGAR="$args " ;;
+ esac
+done
+
+# now remove the applications the user has asked not to compile
+for args in $DO_NOT_COMPILE ; do
+ case $args in
+ kugar) COMPILE_PLUGIN_KUGAR= ;;
+ esac
+done
+
+AM_CONDITIONAL(compile_plugin_KUGAR, test -n "$COMPILE_PLUGIN_KUGAR")
diff --git a/kexi/plugins/forms/Makefile.am b/kexi/plugins/forms/Makefile.am
new file mode 100644
index 000000000..e01b4f6ca
--- /dev/null
+++ b/kexi/plugins/forms/Makefile.am
@@ -0,0 +1,56 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_form.la kformdesigner_kexidbwidgets.la
+
+kexihandler_form_la_SOURCES = kexiforms.cpp
+
+kexihandler_form_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined
+kexihandler_form_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/widget/utils/libkexiguiutils.la \
+ $(top_builddir)/kexi/widget/tableview/libkexidatatable.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/kexi/formeditor/libkformdesigner.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la \
+ ./libkexiformutils.la
+
+kformdesigner_kexidbwidgets_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined
+kformdesigner_kexidbwidgets_la_SOURCES = kexidbfactory.cpp
+kformdesigner_kexidbwidgets_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ ./libkexiformutils.la
+
+lib_LTLIBRARIES = libkexiformutils.la
+libkexiformutils_la_SOURCES = kexiformdataiteminterface.cpp kexidataawarewidgetinfo.cpp \
+ kexidataprovider.cpp kexiformscrollview.cpp kexiformeventhandler.cpp \
+ kexidbtextwidgetinterface.cpp kexiactionselectiondialog.cpp kexiformmanager.cpp \
+ kexidatasourcepage.cpp kexiformpart.cpp kexiformview.cpp
+libkexiformutils_la_LDFLAGS = $(all_libraries) $(VER_INFO) -no-undefined
+libkexiformutils_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/kexi/formeditor/libkformdesigner.la \
+ $(top_builddir)/kexi/plugins/forms/widgets/libkexiformutilswidgets.la
+
+kformdesignerservicesdir=$(kde_servicesdir)/kformdesigner
+kformdesignerservices_DATA=kformdesigner_kexidbfactory.desktop
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=kexiformhandler.desktop
+
+rcdir = $(kde_datadir)/kexi
+rc_DATA = kexiformpartui.rc kexiformpartinstui.rc
+
+SUBDIRS = widgets .
+
+INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget/utils \
+ -I$(top_srcdir)/kexi/widget \
+ -I$(top_srcdir)/kexi/formeditor \
+ -I$(top_srcdir)/lib -I$(top_srcdir)/lib/koproperty -I$(top_srcdir)/lib/kofficecore \
+ -I$(top_srcdir)/kexi/widget/tableview/private \
+ -I$(top_srcdir)/kexi/widget/tableview $(all_includes)
+
+METASOURCES = AUTO
+
+include ../Makefile.common
+noinst_HEADERS = kexidataprovider.h kexidbfactory.h \
+ kexiformpart.h kexiformscrollview.h kexiformview.h \ No newline at end of file
diff --git a/kexi/plugins/forms/kexiactionselectiondialog.cpp b/kexi/plugins/forms/kexiactionselectiondialog.cpp
new file mode 100644
index 000000000..26b4a9a68
--- /dev/null
+++ b/kexi/plugins/forms/kexiactionselectiondialog.cpp
@@ -0,0 +1,724 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiactionselectiondialog.h"
+#include "kexiactionselectiondialog_p.h"
+
+#include <keximainwindow.h>
+#include <kexipartitem.h>
+#include <kexiproject.h>
+#include <kexipartinfo.h>
+#include <kexipart.h>
+#include <kexiactioncategories.h>
+
+#include <klistview.h>
+#include <kaction.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <kstdguiitem.h>
+#include <kpushbutton.h>
+
+#include <qbitmap.h>
+#include <qlabel.h>
+#include <qheader.h>
+#include <qvbox.h>
+#include <qtooltip.h>
+#include <qwidgetstack.h>
+
+#include <widget/utils/klistviewitemtemplate.h>
+#include <widget/kexibrowser.h>
+#include <widget/kexibrowseritem.h>
+#include <kexiutils/utils.h>
+
+typedef KListViewItemTemplate<QString> ActionSelectorDialogListItemBase;
+
+class ActionSelectorDialogListItem : public ActionSelectorDialogListItemBase
+{
+public:
+ ActionSelectorDialogListItem(const QString& data, QListView *parent, QString label1)
+ : ActionSelectorDialogListItemBase(data, parent, label1)
+ , fifoSorting(true)
+ {
+ m_sortKey.sprintf("%2.2d", parent->childCount());
+ }
+
+ ActionSelectorDialogListItem(const QString& data, QListViewItem *parent, QString label1)
+ : ActionSelectorDialogListItemBase(data, parent, label1)
+ , fifoSorting(true)
+ {
+ m_sortKey.sprintf("%2.2d", parent->childCount());
+ }
+
+ virtual QString key( int column, bool ascending ) const
+ {
+ return fifoSorting ? m_sortKey : ActionSelectorDialogListItemBase::key(column, ascending);
+ }
+
+ bool fifoSorting : 1;
+
+protected:
+ QString m_sortKey;
+};
+
+//---------------------------------------
+
+ActionsListViewBase::ActionsListViewBase(QWidget* parent)
+ : KListView(parent)
+{
+ setResizeMode(QListView::AllColumns);
+ addColumn("");
+ header()->hide();
+ setColumnWidthMode(0, QListView::Maximum);
+ setAllColumnsShowFocus(true);
+ setTooltipColumn(0);
+}
+
+ActionsListViewBase::~ActionsListViewBase()
+{
+}
+
+QListViewItem *ActionsListViewBase::itemForAction(const QString& actionName)
+{
+ for (QListViewItemIterator it(this); it.current(); ++it) {
+ ActionSelectorDialogListItem* item = dynamic_cast<ActionSelectorDialogListItem*>(it.current());
+ if (item && item->data == actionName)
+ return item;
+ }
+ return 0;
+}
+
+void ActionsListViewBase::selectAction(const QString& actionName)
+{
+ QListViewItem *item = itemForAction(actionName);
+ if (item) {
+ setSelected(item, true);
+ ensureItemVisible(firstChild());
+ ensureItemVisible(selectedItem());
+ }
+}
+
+//---------------------------------------
+
+KActionsListViewBase::KActionsListViewBase(QWidget* parent, KexiMainWindow* mainWin)
+ : ActionsListViewBase(parent)
+ , m_mainWin(mainWin)
+{
+}
+
+KActionsListViewBase::~KActionsListViewBase() {}
+
+void KActionsListViewBase::init()
+{
+ setSorting(0);
+ const QPixmap noIcon( KexiUtils::emptyIcon(KIcon::Small) );
+ KActionPtrList sharedActions( m_mainWin->allActions() );
+ const Kexi::ActionCategories *acat = Kexi::actionCategories();
+ foreach (KActionPtrList::ConstIterator, it, sharedActions) {
+// kdDebug() << (*it)->name() << " " << (*it)->text() << endl;
+ //! @todo group actions
+ //! @todo: store KAction* here?
+ const int actionCategories = acat->actionCategories((*it)->name());
+ if (actionCategories==-1) {
+ kexipluginswarn << "KActionsListViewBase(): no category declared for action \""
+ << (*it)->name() << "\"! Fix this!" << endl;
+ continue;
+ }
+ if (!isActionVisible((*it)->name(), actionCategories))
+ continue;
+ ActionSelectorDialogListItem *pitem = new ActionSelectorDialogListItem((*it)->name(),
+ this, (*it)->toolTip().isEmpty() ? (*it)->text().replace("&", "") : (*it)->toolTip() );
+ pitem->fifoSorting = false; //alpha sort
+ pitem->setPixmap( 0, (*it)->iconSet( KIcon::Small, 16 ).pixmap( QIconSet::Small, QIconSet::Active ) );
+ if (!pitem->pixmap(0) || pitem->pixmap(0)->isNull())
+ pitem->setPixmap( 0, noIcon );
+ }
+}
+
+//---------------------------------------
+
+//! @internal Used to display KActions (in column 2)
+class KActionsListView : public KActionsListViewBase
+{
+public:
+ KActionsListView(QWidget* parent, KexiMainWindow* mainWin)
+ : KActionsListViewBase(parent, mainWin)
+ {
+ }
+ virtual ~KActionsListView() {}
+
+ virtual bool isActionVisible(const char* actionName, int actionCategories) const {
+ Q_UNUSED(actionName);
+ return actionCategories & Kexi::GlobalActionCategory;
+ }
+};
+
+//! @internal Used to display KActions (in column 2)
+class CurrentFormActionsListView : public KActionsListViewBase
+{
+public:
+ CurrentFormActionsListView(QWidget* parent, KexiMainWindow* mainWin)
+ : KActionsListViewBase(parent, mainWin)
+ {
+ }
+ virtual ~CurrentFormActionsListView() {}
+
+ virtual bool isActionVisible(const char* actionName, int actionCategories) const {
+ return actionCategories & Kexi::WindowActionCategory
+ && Kexi::actionCategories()->actionSupportsObjectType(actionName, KexiPart::FormObjectType);
+ }
+};
+
+//! @internal a list view displaying action categories user can select from (column 1)
+class ActionCategoriesListView : public ActionsListViewBase
+{
+public:
+ ActionCategoriesListView(QWidget* parent) //, KexiProject& project)
+ : ActionsListViewBase(parent)
+ {
+ QListViewItem *item = new ActionSelectorDialogListItem("noaction", this, i18n("No action") );
+ const QPixmap noIcon( KexiUtils::emptyIcon(KIcon::Small) );
+ item->setPixmap(0, noIcon);
+ item = new ActionSelectorDialogListItem("kaction", this, i18n("Application actions") );
+ item->setPixmap(0, SmallIcon("form_action"));
+
+ KexiPart::PartInfoList *pl = Kexi::partManager().partInfoList();
+ for (KexiPart::Info *info = pl->first(); info; info = pl->next()) {
+ KexiPart::Part *part = Kexi::partManager().part(info);
+ if (!info->isVisibleInNavigator() || !part)
+ continue;
+ item = new KexiBrowserItem(this, info);
+ item->setText(0, part->instanceCaption());
+ }
+ QListViewItem *formItem = itemForAction("form");
+ if (formItem) {
+ item = new ActionSelectorDialogListItem("currentForm", formItem,
+ i18n("Current form's actions", "Current"));
+ }
+ adjustColumn(0);
+ setMinimumWidth( columnWidth(0) + 6 );
+ }
+
+ ~ActionCategoriesListView()
+ {
+ }
+
+ //! \return item for action \a actionName, reimplemented to support KexiBrowserItem items
+ virtual QListViewItem *itemForAction(const QString& actionName)
+ {
+ for (QListViewItemIterator it(this); it.current(); ++it) {
+ //simple case
+ ActionSelectorDialogListItem* item = dynamic_cast<ActionSelectorDialogListItem*>(it.current());
+ if (item) {
+ if (item->data == actionName)
+ return it.current();
+ continue;
+ }
+ KexiBrowserItem* bitem = dynamic_cast<KexiBrowserItem*>(it.current());
+ if (bitem) {
+ if (bitem->info()->objectName() == actionName)
+ return it.current();
+ }
+ }
+ return 0;
+ }
+};
+
+//! @internal Used to display list of actions available to executing (column 3)
+class ActionToExecuteListView : public ActionsListViewBase
+{
+ public:
+ ActionToExecuteListView(QWidget* parent)
+ : ActionsListViewBase(parent)
+ {
+ }
+
+ ~ActionToExecuteListView()
+ {
+ }
+
+ //! Updates actions
+ void showActionsForMimeType(const QString& mimeType) {
+ if (m_currentMimeType == mimeType)
+ return;
+ m_currentMimeType = mimeType;
+ clear();
+ KexiPart::Part *part = Kexi::partManager().partForMimeType( m_currentMimeType );
+ if (!part)
+ return;
+ int supportedViewModes = part->supportedViewModes();
+ ActionSelectorDialogListItem *item;
+ const QPixmap noIcon( KexiUtils::emptyIcon(KIcon::Small) );
+ if (supportedViewModes & Kexi::DataViewMode) {
+ item = new ActionSelectorDialogListItem("open", this, i18n("Open in Data View"));
+ item->setPixmap(0, SmallIcon("fileopen"));
+ }
+ if (part->info()->isExecuteSupported()) {
+ item = new ActionSelectorDialogListItem("execute", this, i18n("Execute"));
+ item->setPixmap(0, SmallIcon("player_play"));
+ }
+ if (part->info()->isPrintingSupported()) {
+ ActionSelectorDialogListItem *printItem = new ActionSelectorDialogListItem(
+ "print", this, i18n("Print"));
+ printItem->setPixmap(0, SmallIcon("fileprint"));
+ KAction *a = KStdAction::printPreview(0, 0, 0);
+ item = new ActionSelectorDialogListItem("printPreview", printItem,
+ a->text().replace("&", "").replace("...", ""));
+ item->setPixmap(0, SmallIcon(a->icon()));
+ delete a;
+ item = new ActionSelectorDialogListItem("pageSetup", printItem, i18n("Show Page Setup"));
+ item->setPixmap(0, noIcon);
+ setOpen(printItem, true);
+ printItem->setExpandable(false);
+ }
+ if (part->info()->isDataExportSupported()) {
+ ActionSelectorDialogListItem *exportItem = new ActionSelectorDialogListItem(
+ "exportToCSV", this,
+ i18n("Note: use multiple rows if needed", "Export to File\nAs Data Table"));
+ exportItem->setMultiLinesEnabled(true);
+ exportItem->setPixmap(0, SmallIcon("table"));
+ item = new ActionSelectorDialogListItem("copyToClipboardAsCSV",
+ exportItem,
+ i18n("Note: use multiple rows if needed", "Copy to Clipboard\nAs Data Table"));
+ item->setPixmap(0, SmallIcon("table"));
+ item->setMultiLinesEnabled(true);
+ setOpen(exportItem, true);
+ exportItem->setExpandable(false);
+ }
+ item = new ActionSelectorDialogListItem("new", this, i18n("Create New Object"));
+ item->setPixmap(0, SmallIcon("filenew"));
+ if (supportedViewModes & Kexi::DesignViewMode) {
+ item = new ActionSelectorDialogListItem("design", this, i18n("Open in Design View"));
+ item->setPixmap(0, SmallIcon("edit"));
+ }
+ if (supportedViewModes & Kexi::TextViewMode) {
+ item = new ActionSelectorDialogListItem("editText", this, i18n("Open in Text View"));
+ item->setPixmap(0, noIcon);
+ }
+ item = new ActionSelectorDialogListItem("close", this, i18n("Close View"));
+ item->setPixmap(0, SmallIcon("fileclose"));
+ updateWidth();
+ }
+
+ void updateWidth()
+ {
+ adjustColumn(0);
+ setMinimumWidth( columnWidth(0) );
+ }
+
+ QString m_currentMimeType;
+};
+
+//-------------------------------------
+
+//! @internal
+class KexiActionSelectionDialog::KexiActionSelectionDialogPrivate
+{
+public:
+ KexiActionSelectionDialogPrivate()
+ : kactionPageWidget(0), kactionListView(0), objectsListView(0)
+ , currentFormActionsPageWidget(0)
+ , currentFormActionsListView(0)
+ , secondAnd3rdColumnMainWidget(0)
+ , hideActionToExecuteListView(false)
+ {
+ }
+
+ void raiseWidget(QWidget *w)
+ {
+ secondAnd3rdColumnStack->raiseWidget( w );
+ selectActionToBeExecutedLbl->setBuddy(w);
+ }
+
+ void updateSelectActionToBeExecutedMessage(const QString& actionType)
+ {
+ QString msg;
+ if (actionType=="noaction")
+ msg = QString::null;
+ // hardcoded, but it's not that bad
+ else if (actionType=="macro")
+ msg = i18n("&Select macro to be executed after clicking \"%1\" button:").arg(actionWidgetName);
+ else if (actionType=="script")
+ msg = i18n("&Select script to be executed after clicking \"%1\" button:").arg(actionWidgetName);
+ //default: table/query/form/report...
+ else
+ msg = i18n("&Select object to be opened after clicking \"%1\" button:").arg(actionWidgetName);
+ selectActionToBeExecutedLbl->setText(msg);
+ }
+
+ // changes 3rd column visibility
+ void setActionToExecuteSectionVisible(bool visible, bool force = false)
+ {
+ if (!force && hideActionToExecuteListView != visible)
+ return;
+ hideActionToExecuteListView = !visible;
+ actionToExecuteListView->hide();
+ actionToExecuteLbl->hide();
+ actionToExecuteListView->show();
+ actionToExecuteLbl->show();
+ }
+
+ KexiMainWindow* mainWin;
+ QString actionWidgetName;
+ ActionCategoriesListView* actionCategoriesListView; //!< for column #1
+ QWidget *kactionPageWidget;
+ KActionsListView* kactionListView; //!< for column #2
+ KexiBrowser* objectsListView; //!< for column #2
+ QWidget *currentFormActionsPageWidget; //!< for column #2
+ CurrentFormActionsListView* currentFormActionsListView; //!< for column #2
+ QWidget *emptyWidget;
+ QLabel* selectActionToBeExecutedLbl;
+ ActionToExecuteListView* actionToExecuteListView;
+ QLabel *actionToExecuteLbl;
+ QWidget *secondAnd3rdColumnMainWidget;
+ QGridLayout *glyr;
+ QGridLayout *secondAnd3rdColumnGrLyr;
+ QWidgetStack *secondAnd3rdColumnStack, *secondColumnStack;
+ bool hideActionToExecuteListView;
+};
+
+//-------------------------------------
+
+KexiActionSelectionDialog::KexiActionSelectionDialog(KexiMainWindow* mainWin, QWidget *parent,
+ const KexiFormEventAction::ActionData& action, const QCString& actionWidgetName)
+ : KDialogBase(parent, "actionSelectorDialog", true, i18n("Assigning Action to Command Button"),
+ KDialogBase::Ok | KDialogBase::Cancel )
+ , d( new KexiActionSelectionDialogPrivate() )
+{
+ d->mainWin = mainWin;
+ d->actionWidgetName = actionWidgetName;
+ setButtonOK( KGuiItem(i18n("Assign action", "&Assign"), "button_ok", i18n("Assign action")) );
+
+ QWidget *mainWidget = new QWidget( this );
+ mainWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setMainWidget(mainWidget);
+
+/* lbl 1
+ +------------+ +-------------------------------+
+ | | | [a] |
+ | 1st column | | +----------- + +------------+ |
+ | | | | 2nd column | | 3rd column | |
+ | | | + + + + |
+ | | | +------------+ +------------+ |
+ +------------+ +-------------------------------+
+ \______________________________________________/
+ glyr
+ [a]- QWidgetStack *secondAnd3rdColumnStack,
+ - for displaying KActions, the stack contains d->kactionPageWidget QWidget
+ - for displaying objects, the stack contains secondAnd3rdColumnMainWidget QWidget and QGridLayout *secondAnd3rdColumnGrLyr
+ - kactionPageWidget contains only a QVBoxLayout and label+kactionListView
+*/
+ d->glyr = new QGridLayout(mainWidget, 2, 2, KDialog::marginHint(), KDialog::spacingHint());
+ d->glyr->setRowStretch(1, 1);
+
+ // 1st column: action types
+ d->actionCategoriesListView = new ActionCategoriesListView(mainWidget);
+ d->actionCategoriesListView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
+ d->glyr->addWidget(d->actionCategoriesListView, 1, 0);
+ connect( d->actionCategoriesListView, SIGNAL(selectionChanged(QListViewItem*)),
+ this, SLOT(slotActionCategorySelected(QListViewItem*)));
+
+ QLabel *lbl = new QLabel(d->actionCategoriesListView, i18n("Action category:"), mainWidget);
+ lbl->setMinimumHeight(lbl->fontMetrics().height()*2);
+ lbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ lbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak);
+ d->glyr->addWidget(lbl, 0, 0, Qt::AlignTop|Qt::AlignLeft);
+
+ // widget stack for 2nd and 3rd column
+ d->secondAnd3rdColumnStack = new QWidgetStack(mainWidget);
+ d->secondAnd3rdColumnStack->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ d->glyr->addMultiCellWidget(d->secondAnd3rdColumnStack, 0, 1, 1, 1);//, Qt::AlignTop|Qt::AlignLeft);
+
+ d->secondAnd3rdColumnMainWidget = new QWidget(d->secondAnd3rdColumnStack);
+ d->secondAnd3rdColumnMainWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ d->secondAnd3rdColumnGrLyr = new QGridLayout(d->secondAnd3rdColumnMainWidget, 2, 2, 0, KDialog::spacingHint());
+ d->secondAnd3rdColumnGrLyr->setRowStretch(1, 2);
+ d->secondAnd3rdColumnStack->addWidget(d->secondAnd3rdColumnMainWidget);
+
+ // 2nd column: list of actions/objects
+ d->objectsListView = new KexiBrowser(d->secondAnd3rdColumnMainWidget, d->mainWin, 0/*features*/);
+ d->secondAnd3rdColumnGrLyr->addWidget(d->objectsListView, 1, 0);
+ connect(d->objectsListView, SIGNAL(selectionChanged(KexiPart::Item*)),
+ this, SLOT(slotItemForOpeningOrExecutingSelected(KexiPart::Item*)));
+
+ d->selectActionToBeExecutedLbl = new QLabel(d->secondAnd3rdColumnMainWidget);
+ d->selectActionToBeExecutedLbl->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ d->selectActionToBeExecutedLbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak);
+ d->selectActionToBeExecutedLbl->setMinimumHeight(d->selectActionToBeExecutedLbl->fontMetrics().height()*2);
+ d->secondAnd3rdColumnGrLyr->addWidget(d->selectActionToBeExecutedLbl, 0, 0, Qt::AlignTop|Qt::AlignLeft);
+
+ d->emptyWidget = new QWidget(d->secondAnd3rdColumnStack);
+ d->secondAnd3rdColumnStack->addWidget(d->emptyWidget);
+
+ // 3rd column: actions to execute
+ d->actionToExecuteListView = new ActionToExecuteListView(d->secondAnd3rdColumnMainWidget);
+ d->actionToExecuteListView->installEventFilter(this); //to be able to disable painting
+ d->actionToExecuteListView->viewport()->installEventFilter(this); //to be able to disable painting
+ d->actionToExecuteListView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
+ connect(d->actionToExecuteListView, SIGNAL(executed(QListViewItem*)),
+ this, SLOT(slotActionToExecuteItemExecuted(QListViewItem*)));
+ connect(d->actionToExecuteListView, SIGNAL(selectionChanged(QListViewItem*)),
+ this, SLOT(slotActionToExecuteItemSelected(QListViewItem*)));
+ d->secondAnd3rdColumnGrLyr->addWidget(d->actionToExecuteListView, 1, 1);
+
+ d->actionToExecuteLbl = new QLabel(d->actionToExecuteListView,
+ i18n("Action to execute:"), d->secondAnd3rdColumnMainWidget);
+ d->actionToExecuteLbl->installEventFilter(this); //to be able to disable painting
+ d->actionToExecuteLbl->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ d->actionToExecuteLbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak);
+ d->secondAnd3rdColumnGrLyr->addWidget(d->actionToExecuteLbl, 0, 1, Qt::AlignTop|Qt::AlignLeft);
+
+ // temporary show all sections to avoid resizing the dialog in the future
+ d->actionCategoriesListView->selectAction("table");
+ d->setActionToExecuteSectionVisible(true);
+ adjustSize();
+ resize(QMAX(700, width()), QMAX(450, height()));
+ d->actionToExecuteListView->updateWidth();
+
+ bool ok;
+ QString actionType, actionArg;
+ KexiPart::Info* partInfo = action.decodeString(actionType, actionArg, ok);
+ if (ok) {
+ d->actionCategoriesListView->selectAction(actionType);
+ if (actionType=="kaction") {
+ d->kactionListView->selectAction(actionArg);
+ d->kactionListView->setFocus();
+ }
+ else if (actionType=="currentForm") {
+ d->currentFormActionsListView->selectAction(actionArg);
+ d->currentFormActionsListView->setFocus();
+ }
+ else if (partInfo
+ && Kexi::partManager().part(partInfo)) // We use the Part Manager
+ // to determine whether the Kexi-plugin is installed and whether we like to show
+ // it in our list of actions.
+ {
+ KexiPart::Item *item = d->mainWin->project()->item(partInfo, actionArg);
+ if (d->objectsListView && item) {
+ d->objectsListView->selectItem(*item);
+ QString actionOption( action.option );
+ if (actionOption.isEmpty())
+ actionOption = "open"; // for backward compatibility
+ d->actionToExecuteListView->selectAction(actionOption);
+ d->objectsListView->setFocus();
+ }
+ }
+ }
+ else {//invalid assignment or 'noaction'
+ d->actionCategoriesListView->selectAction("noaction");
+ d->actionCategoriesListView->setFocus();
+ }
+}
+
+KexiActionSelectionDialog::~KexiActionSelectionDialog()
+{
+ delete d;
+}
+
+void KexiActionSelectionDialog::slotKActionItemExecuted(QListViewItem*)
+{
+ accept();
+}
+
+void KexiActionSelectionDialog::slotKActionItemSelected(QListViewItem*)
+{
+ d->setActionToExecuteSectionVisible(false);
+ updateOKButtonStatus();
+}
+
+void KexiActionSelectionDialog::slotCurrentFormActionItemExecuted(QListViewItem*)
+{
+ accept();
+}
+
+void KexiActionSelectionDialog::slotCurrentFormActionItemSelected(QListViewItem*)
+{
+ d->setActionToExecuteSectionVisible(false);
+ updateOKButtonStatus();
+}
+
+void KexiActionSelectionDialog::slotItemForOpeningOrExecutingSelected(KexiPart::Item* item)
+{
+ d->setActionToExecuteSectionVisible(item);
+}
+
+void KexiActionSelectionDialog::slotActionToExecuteItemExecuted(QListViewItem* item)
+{
+ if (!item)
+ return;
+ ActionSelectorDialogListItemBase *listItem = dynamic_cast<ActionSelectorDialogListItemBase*>(item);
+ if (listItem && !listItem->data.isEmpty())
+ accept();
+}
+
+void KexiActionSelectionDialog::slotActionToExecuteItemSelected(QListViewItem*)
+{
+ updateOKButtonStatus();
+}
+
+void KexiActionSelectionDialog::slotActionCategorySelected(QListViewItem* item)
+{
+ ActionSelectorDialogListItem *simpleItem = dynamic_cast<ActionSelectorDialogListItem*>(item);
+ // simple case: part-less item, e.g. kaction:
+ if (simpleItem) {
+ d->updateSelectActionToBeExecutedMessage(simpleItem->data);
+ QString selectActionToBeExecutedMsg(
+ i18n("&Select action to be executed after clicking \"%1\" button:")); // msg for a label
+ if (simpleItem->data == "kaction") {
+ if (!d->kactionPageWidget) {
+ //create lbl+list view with a vlayout
+ d->kactionPageWidget = new QWidget();
+ d->kactionPageWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ QVBoxLayout *vlyr = new QVBoxLayout(d->kactionPageWidget, 0, KDialog::spacingHint());
+ d->kactionListView = new KActionsListView(d->kactionPageWidget, d->mainWin);
+ d->kactionListView->init();
+ QLabel *lbl = new QLabel(d->kactionListView, selectActionToBeExecutedMsg.arg(d->actionWidgetName),
+ d->kactionPageWidget);
+ lbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak);
+ lbl->setMinimumHeight(lbl->fontMetrics().height()*2);
+ vlyr->addWidget(lbl);
+ vlyr->addWidget(d->kactionListView);
+ d->secondAnd3rdColumnStack->addWidget(d->kactionPageWidget);
+ connect(d->kactionListView, SIGNAL(executed(QListViewItem*)),
+ this, SLOT(slotKActionItemExecuted(QListViewItem*)));
+ connect( d->kactionListView, SIGNAL(selectionChanged(QListViewItem*)),
+ this, SLOT(slotKActionItemSelected(QListViewItem*)));
+ }
+ d->setActionToExecuteSectionVisible(false);
+ d->raiseWidget(d->kactionPageWidget);
+ slotKActionItemSelected(d->kactionListView->selectedItem()); //to refresh column #3
+ }
+ else if (simpleItem->data == "currentForm") {
+ if (!d->currentFormActionsPageWidget) {
+ //create lbl+list view with a vlayout
+ d->currentFormActionsPageWidget = new QWidget();
+ d->currentFormActionsPageWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ QVBoxLayout *vlyr = new QVBoxLayout(d->currentFormActionsPageWidget, 0, KDialog::spacingHint());
+ d->currentFormActionsListView = new CurrentFormActionsListView(
+ d->currentFormActionsPageWidget, d->mainWin);
+ d->currentFormActionsListView->init();
+ QLabel *lbl = new QLabel(d->currentFormActionsListView,
+ selectActionToBeExecutedMsg.arg(d->actionWidgetName), d->currentFormActionsPageWidget);
+ lbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak);
+ lbl->setMinimumHeight(lbl->fontMetrics().height()*2);
+ vlyr->addWidget(lbl);
+ vlyr->addWidget(d->currentFormActionsListView);
+ d->secondAnd3rdColumnStack->addWidget(d->currentFormActionsPageWidget);
+ connect(d->currentFormActionsListView, SIGNAL(executed(QListViewItem*)),
+ this, SLOT(slotCurrentFormActionItemExecuted(QListViewItem*)));
+ connect( d->currentFormActionsListView, SIGNAL(selectionChanged(QListViewItem*)),
+ this, SLOT(slotCurrentFormActionItemSelected(QListViewItem*)));
+ }
+ d->setActionToExecuteSectionVisible(false);
+ d->raiseWidget(d->currentFormActionsPageWidget);
+ slotCurrentFormActionItemSelected(d->currentFormActionsListView->selectedItem()); //to refresh column #3
+ }
+ else if (simpleItem->data == "noaction") {
+ d->raiseWidget(d->emptyWidget);
+ d->objectsListView->clearSelection();
+ //hide column #3
+ d->setActionToExecuteSectionVisible(false);
+ }
+ d->actionCategoriesListView->update();
+ updateOKButtonStatus();
+ return;
+ }
+ // other case
+ KexiBrowserItem* browserItem = dynamic_cast<KexiBrowserItem*>(item);
+ if (browserItem) {
+ d->updateSelectActionToBeExecutedMessage(browserItem->info()->objectName());
+ if (d->objectsListView->itemsMimeType().latin1()!=browserItem->info()->mimeType()) {
+ d->objectsListView->setProject(d->mainWin->project(), browserItem->info()->mimeType());
+ d->actionToExecuteListView->showActionsForMimeType( browserItem->info()->mimeType() );
+ d->setActionToExecuteSectionVisible(false);
+ }
+ if (d->secondAnd3rdColumnStack->visibleWidget()!=d->secondAnd3rdColumnMainWidget) {
+ d->raiseWidget( d->secondAnd3rdColumnMainWidget );
+ d->objectsListView->clearSelection();
+ d->setActionToExecuteSectionVisible(false, true);
+ }
+ else
+ d->raiseWidget( d->secondAnd3rdColumnMainWidget );
+ }
+ d->actionCategoriesListView->update();
+ updateOKButtonStatus();
+}
+
+KexiMainWindow* KexiActionSelectionDialog::mainWin() const
+{
+ return d->mainWin;
+}
+
+KexiFormEventAction::ActionData KexiActionSelectionDialog::currentAction() const
+{
+ KexiFormEventAction::ActionData data;
+ ActionSelectorDialogListItem *simpleItem = dynamic_cast<ActionSelectorDialogListItem*>(
+ d->actionCategoriesListView->selectedItem());
+ // simple case: part-less item, e.g. kaction:
+ if (simpleItem) {
+ if (simpleItem->data == "kaction") {
+ if (d->kactionListView->selectedItem()) {
+ data.string = QString("kaction:")
+ + dynamic_cast<ActionSelectorDialogListItem*>( d->kactionListView->selectedItem() )->data;
+ return data;
+ }
+ }
+ else if (simpleItem->data == "currentForm") {
+ if (d->currentFormActionsListView->selectedItem()) {
+ data.string = QString("currentForm:")
+ + dynamic_cast<ActionSelectorDialogListItem*>(
+ d->currentFormActionsListView->selectedItem() )->data;
+ return data;
+ }
+ }
+ }
+ KexiBrowserItem* browserItem = dynamic_cast<KexiBrowserItem*>( d->actionCategoriesListView->selectedItem() );
+ if (browserItem) {
+ ActionSelectorDialogListItem *actionToExecute = dynamic_cast<ActionSelectorDialogListItem*>(
+ d->actionToExecuteListView->selectedItem());
+ if (d->objectsListView && actionToExecute && !actionToExecute->data.isEmpty()) {
+ KexiPart::Item* partItem = d->objectsListView->selectedPartItem();
+ KexiPart::Info* partInfo = partItem ? Kexi::partManager().infoForMimeType( partItem->mimeType() ) : 0;
+ if (partInfo) {
+ // opening or executing: table:name, query:name, form:name, macro:name, script:name, etc.
+ data.string = QString("%1:%2").arg(partInfo->objectName()).arg(partItem->name());
+ data.option = actionToExecute->data;
+ return data;
+ }
+ }
+ }
+ return data; // No Action
+}
+
+void KexiActionSelectionDialog::updateOKButtonStatus()
+{
+ QPushButton *btn = actionButton(Ok);
+ ActionSelectorDialogListItem *simpleItem = dynamic_cast<ActionSelectorDialogListItem*>(
+ d->actionCategoriesListView->selectedItem());
+ btn->setEnabled( (simpleItem && simpleItem->data == "noaction") || !currentAction().isEmpty() );
+}
+
+bool KexiActionSelectionDialog::eventFilter(QObject *o, QEvent *e)
+{
+ if (d->hideActionToExecuteListView)
+ return true;
+ return KDialogBase::eventFilter(o, e);
+}
+
+#include "kexiactionselectiondialog.moc"
+#include "kexiactionselectiondialog_p.moc"
diff --git a/kexi/plugins/forms/kexiactionselectiondialog.h b/kexi/plugins/forms/kexiactionselectiondialog.h
new file mode 100644
index 000000000..6b6a896ba
--- /dev/null
+++ b/kexi/plugins/forms/kexiactionselectiondialog.h
@@ -0,0 +1,71 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIACTIONSELECTIONDIALOG_H
+#define KEXIACTIONSELECTIONDIALOG_H
+
+#include <kdialogbase.h>
+#include "kexiformeventhandler.h"
+
+class KexiMainWindow;
+class KListView;
+namespace KexiPart {
+ class Item;
+}
+
+//! @short A dialog for selecting an action to be executed for a form's command button
+/*! Available actions are:
+ - application's global actions like "edit->copy" (KAction-based)
+ - opening/printing/executing of selected object (table/query/form/script/macrto, etc.)
+*/
+class KEXIFORMUTILS_EXPORT KexiActionSelectionDialog : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ KexiActionSelectionDialog(KexiMainWindow* mainWin, QWidget *parent,
+ const KexiFormEventAction::ActionData& action, const QCString& actionWidgetName);
+ ~KexiActionSelectionDialog();
+
+ /*! \return selected action data or empty action if dialog has been rejected
+ or "No action" has been selected. */
+ KexiFormEventAction::ActionData currentAction() const;
+
+ //! \return the \a KexiMainWindow instance.
+ KexiMainWindow* mainWin() const;
+
+ virtual bool eventFilter(QObject *o, QEvent *e);
+
+ protected slots:
+ void slotActionCategorySelected(QListViewItem* item);
+ void slotKActionItemExecuted(QListViewItem*);
+ void slotKActionItemSelected(QListViewItem*);
+ void slotActionToExecuteItemExecuted(QListViewItem* item);
+ void slotActionToExecuteItemSelected(QListViewItem*);
+ void slotCurrentFormActionItemExecuted(QListViewItem*);
+ void slotCurrentFormActionItemSelected(QListViewItem*);
+ void slotItemForOpeningOrExecutingSelected(KexiPart::Item* item);
+
+ protected:
+ void updateOKButtonStatus();
+
+ class KexiActionSelectionDialogPrivate;
+ KexiActionSelectionDialogPrivate* d;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexiactionselectiondialog_p.h b/kexi/plugins/forms/kexiactionselectiondialog_p.h
new file mode 100644
index 000000000..51f5c3696
--- /dev/null
+++ b/kexi/plugins/forms/kexiactionselectiondialog_p.h
@@ -0,0 +1,51 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIACTIONSELECTIONDIALOG_P_H
+#define KEXIACTIONSELECTIONDIALOG_P_H
+
+#include <klistview.h>
+
+//! @internal
+class ActionsListViewBase : public KListView
+{
+ public:
+ ActionsListViewBase(QWidget* parent);
+ virtual ~ActionsListViewBase();
+
+ //! \return item for action \a actionName
+ virtual QListViewItem *itemForAction(const QString& actionName);
+ void selectAction(const QString& actionName);
+};
+
+//! @internal Used by KActionsListView and CurrentFormActionsListView (in column 2)
+class KActionsListViewBase : public ActionsListViewBase
+{
+ Q_OBJECT
+ public:
+ KActionsListViewBase(QWidget* parent, KexiMainWindow* mainWin);
+ virtual ~KActionsListViewBase();
+ void init();
+ virtual bool isActionVisible(const char* actionName, int actionCategories) const = 0;
+
+ protected:
+ KexiMainWindow* m_mainWin;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexidataawarewidgetinfo.cpp b/kexi/plugins/forms/kexidataawarewidgetinfo.cpp
new file mode 100644
index 000000000..a6033c706
--- /dev/null
+++ b/kexi/plugins/forms/kexidataawarewidgetinfo.cpp
@@ -0,0 +1,43 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidataawarewidgetinfo.h"
+
+KexiDataAwareWidgetInfo::KexiDataAwareWidgetInfo(KFormDesigner::WidgetFactory *f)
+ : KFormDesigner::WidgetInfo(f)
+{
+ init();
+}
+
+KexiDataAwareWidgetInfo::KexiDataAwareWidgetInfo(KFormDesigner::WidgetFactory *f,
+ const char* parentFactoryName, const char* inheritedClassName)
+ : KFormDesigner::WidgetInfo(f, parentFactoryName, inheritedClassName)
+{
+ init();
+}
+
+KexiDataAwareWidgetInfo::~KexiDataAwareWidgetInfo()
+{
+}
+
+void KexiDataAwareWidgetInfo::init()
+{
+ setAutoSyncForProperty( "dataSource", false );
+ setAutoSyncForProperty( "dataSourceMimeType", false );
+}
diff --git a/kexi/plugins/forms/kexidataawarewidgetinfo.h b/kexi/plugins/forms/kexidataawarewidgetinfo.h
new file mode 100644
index 000000000..41e67d85d
--- /dev/null
+++ b/kexi/plugins/forms/kexidataawarewidgetinfo.h
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDATAAWAREWIDGETINFO_H
+#define KEXIDATAAWAREWIDGETINFO_H
+
+#include <formeditor/widgetfactory.h>
+
+//! A widget info for data-aware widgets
+/*! Used within factories just like KFormDesigner::WidgetInfo,
+ but also predefines specific behaviour,
+ e.g. sets autoSync flag to false for "dataSource" property.
+*/
+class KEXIFORMUTILS_EXPORT KexiDataAwareWidgetInfo : public KFormDesigner::WidgetInfo
+{
+ public:
+ KexiDataAwareWidgetInfo(KFormDesigner::WidgetFactory *f);
+
+ KexiDataAwareWidgetInfo(KFormDesigner::WidgetFactory *f,
+ const char* parentFactoryName, const char* inheritedClassName = 0);
+
+ virtual ~KexiDataAwareWidgetInfo();
+
+ protected:
+ void init();
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexidataprovider.cpp b/kexi/plugins/forms/kexidataprovider.cpp
new file mode 100644
index 000000000..6706f838c
--- /dev/null
+++ b/kexi/plugins/forms/kexidataprovider.cpp
@@ -0,0 +1,315 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidataprovider.h"
+
+#include <qwidget.h>
+#include <qobjectlist.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <widget/tableview/kexitableitem.h>
+#include <widget/tableview/kexitableviewdata.h>
+#include <widget/tableview/kexicomboboxbase.h>
+#include <kexidb/queryschema.h>
+#include <kexiutils/utils.h>
+
+#include "widgets/kexidbform.h"
+
+KexiFormDataProvider::KexiFormDataProvider()
+ : KexiDataItemChangesListener()
+ , m_mainWidget(0)
+ , m_duplicatedItems(0)
+ , m_disableFillDuplicatedDataItems(false)
+{
+}
+
+KexiFormDataProvider::~KexiFormDataProvider()
+{
+ delete m_duplicatedItems;
+}
+
+void KexiFormDataProvider::setMainDataSourceWidget(QWidget* mainWidget)
+{
+ m_mainWidget = mainWidget;
+ m_dataItems.clear();
+ m_usedDataSources.clear();
+ m_fieldNumbersForDataItems.clear();
+ if (!m_mainWidget)
+ return;
+
+ //find widgets whose will work as data items
+ QObjectList *l = m_mainWidget->queryList( "QWidget" );
+ QObjectListIt it( *l );
+ QObject *obj;
+ QDict<char> tmpSources;
+ for ( ; (obj = it.current()) != 0; ++it ) {
+ KexiFormDataItemInterface* const formDataItem = dynamic_cast<KexiFormDataItemInterface*>(obj);
+ if (!formDataItem)
+ continue;
+ if (formDataItem->parentInterface()) //item with parent interface: collect parent instead...
+ continue;
+#if 0 //! @todo reenable when subform is moved to KexiDBForm
+ KexiDBForm *dbForm = KexiUtils::findParent<KexiDBForm>(obj, "KexiDBForm"); //form's surface...
+ if (dbForm!=m_mainWidget) //only set data for this form's data items
+ continue;
+#else
+ //tmp: reject widgets within subforms
+ if (KexiUtils::findParent<KexiDBForm>(obj, "KexiDBSubForm"))
+ continue;
+#endif
+ QString dataSource( formDataItem->dataSource().lower() );
+ if (dataSource.isEmpty())
+ continue;
+ kexipluginsdbg << obj->name() << endl;
+ m_dataItems.append( formDataItem );
+ formDataItem->installListener( this );
+ tmpSources.replace( dataSource, (char*)1 );
+ }
+ delete l;
+ //now we've got a set (unique list) of field names in tmpSources
+ //remember it in m_usedDataSources
+ for (QDictIterator<char> it(tmpSources); it.current(); ++it) {
+ m_usedDataSources += it.currentKey();
+ }
+}
+
+void KexiFormDataProvider::fillDataItems(KexiTableItem& row, bool cursorAtNewRow)
+{
+ kexipluginsdbg << "KexiFormDataProvider::fillDataItems() cnt=" << row.count() << endl;
+ for (KexiFormDataItemInterfaceToIntMap::ConstIterator it = m_fieldNumbersForDataItems.constBegin();
+ it!=m_fieldNumbersForDataItems.constEnd(); ++it)
+ {
+ KexiFormDataItemInterface *itemIface = it.key();
+ if (!itemIface->columnInfo()) {
+ kexipluginsdbg << "KexiFormDataProvider::fillDataItems(): itemIface->columnInfo() == 0" << endl;
+ continue;
+ }
+ //1. Is this a value with a combo box (lookup)?
+ int indexForVisibleLookupValue = itemIface->columnInfo()->indexForVisibleLookupValue();
+ if (indexForVisibleLookupValue<0 && indexForVisibleLookupValue>=(int)row.count()) //sanity
+ indexForVisibleLookupValue = -1; //no
+ const QVariant value(row.at(it.data()));
+ QVariant visibleLookupValue;
+ if (indexForVisibleLookupValue!=-1 && (int)row.size()>indexForVisibleLookupValue)
+ visibleLookupValue = row.at(indexForVisibleLookupValue);
+ kexipluginsdbg << "fill data of '" << itemIface->dataSource() << "' at idx=" << it.data()
+ << " data=" << value << (indexForVisibleLookupValue!=-1
+ ? QString(" SPECIAL: indexForVisibleLookupValue=%1 visibleValue=%2")
+ .arg(indexForVisibleLookupValue).arg(visibleLookupValue.toString())
+ : QString::null)
+ << endl;
+ const bool displayDefaultValue = cursorAtNewRow && (value.isNull() && visibleLookupValue.isNull())
+ && !itemIface->columnInfo()->field->defaultValue().isNull()
+ && !itemIface->columnInfo()->field->isAutoIncrement(); //no value to set but there is default value defined
+ itemIface->setValue(
+ displayDefaultValue ? itemIface->columnInfo()->field->defaultValue() : value,
+ QVariant(), /*add*/
+ /*!remove old*/false,
+ indexForVisibleLookupValue==-1 ? 0 : &visibleLookupValue //pass visible value if available
+ );
+ // now disable/enable "display default value" if needed (do it after setValue(), before setValue() turns it off)
+ if (itemIface->hasDisplayedDefaultValue() != displayDefaultValue)
+ itemIface->setDisplayDefaultValue( dynamic_cast<QWidget*>(itemIface), displayDefaultValue );
+ }
+}
+
+void KexiFormDataProvider::fillDuplicatedDataItems(
+ KexiFormDataItemInterface* item, const QVariant& value)
+{
+ if (m_disableFillDuplicatedDataItems)
+ return;
+ if (!m_duplicatedItems) {
+ //build (once) a set of duplicated data items (having the same fields assigned)
+ //so we can later check if an item is duplicated with a cost of o(1)
+ QMap<KexiDB::Field*,int> tmpDuplicatedItems;
+ QMapIterator<KexiDB::Field*,int> it_dup;
+ for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current(); ++it) {
+ if (!it.current()->columnInfo() || !it.current()->columnInfo()->field)
+ continue;
+ kdDebug() << " ** " << it.current()->columnInfo()->field->name() << endl;
+ it_dup = tmpDuplicatedItems.find( it.current()->columnInfo()->field );
+ uint count;
+ if (it_dup==tmpDuplicatedItems.end())
+ count = 0;
+ else
+ count = it_dup.data();
+ tmpDuplicatedItems.insert( it.current()->columnInfo()->field, ++count );
+ }
+ m_duplicatedItems = new QPtrDict<char>(101);
+ for (it_dup = tmpDuplicatedItems.begin(); it_dup!=tmpDuplicatedItems.end(); ++it_dup) {
+ if (it_dup.data() > 1) {
+ m_duplicatedItems->insert( it_dup.key(), (char*)1 );
+ kexipluginsdbg << "duplicated item: " << static_cast<KexiDB::Field*>(it_dup.key())->name()
+ << " (" << it_dup.data() << " times)" << endl;
+ }
+ }
+ }
+ if (item->columnInfo() && m_duplicatedItems->find( item->columnInfo()->field )) {
+ for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current(); ++it) {
+ if (it.current()!=item && item->columnInfo()->field == it.current()->columnInfo()->field) {
+ kexipluginsdbg << "- setting a copy of value for item '"
+ << dynamic_cast<QObject*>(it.current())->name() << "' == " << value << endl;
+ it.current()->setValue( value );
+ }
+ }
+ }
+}
+
+void KexiFormDataProvider::valueChanged(KexiDataItemInterface* item)
+{
+ Q_UNUSED( item );
+}
+
+bool KexiFormDataProvider::cursorAtNewRow() const
+{
+ return false;
+}
+
+void KexiFormDataProvider::invalidateDataSources( const QDict<char>& invalidSources,
+ KexiDB::QuerySchema* query)
+{
+ //fill m_fieldNumbersForDataItems mapping from data item to field number
+ //(needed for fillDataItems)
+ KexiDB::QueryColumnInfo::Vector fieldsExpanded;
+// uint dataFieldsCount; // == fieldsExpanded.count() if query is available or else == m_dataItems.count()
+
+ if (query) {
+ fieldsExpanded = query->fieldsExpanded( KexiDB::QuerySchema::WithInternalFields );
+// dataFieldsCount = fieldsExpanded.count();
+ QMap<KexiDB::QueryColumnInfo*,int> columnsOrder( query->columnsOrder() );
+ for (QMapConstIterator<KexiDB::QueryColumnInfo*,int> it = columnsOrder.constBegin(); it!=columnsOrder.constEnd(); ++it) {
+ kexipluginsdbg << "query->columnsOrder()[ " << it.key()->field->name() << " ] = " << it.data() << endl;
+ }
+ for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current(); ++it) {
+ KexiFormDataItemInterface *item = it.current();
+ KexiDB::QueryColumnInfo* ci = query->columnInfo( it.current()->dataSource() );
+ int index = ci ? columnsOrder[ ci ] : -1;
+ kexipluginsdbg << "query->columnsOrder()[ " << (ci ? ci->field->name() : "") << " ] = " << index
+ << " (dataSource: " << item->dataSource() << ", name=" << dynamic_cast<QObject*>(item)->name() << ")" << endl;
+ if (index!=-1 && !m_fieldNumbersForDataItems[ item ])
+ m_fieldNumbersForDataItems.insert( item, index );
+ //todo
+ //WRONG: not only used data sources can be fetched!
+ // m_fieldNumbersForDataItems.insert( it.current(),
+ // m_usedDataSources.findIndex(it.current()->dataSource().lower()) );
+ }
+ }
+ else {//!query
+// dataFieldsCount = m_dataItems.count();
+ }
+
+#if 0 //moved down
+ //in 'newIndices' let's collect new indices for every data source
+ foreach(QValueList<uint>::ConstIterator, it, invalidSources) {
+ //all previous indices have corresponding data source
+// for (; i < (*it); i++) {
+// newIndices[i] = number++;
+ //kexipluginsdbg << "invalidateDataSources(): " << i << " -> " << number-1 << endl;
+// }
+ //this index have no corresponding data source
+// newIndices[i]=-1;
+ KexiFormDataItemInterface *item = m_dataItems.at( *it );
+ if (item)
+ item->setInvalidState( QString::fromLatin1("#") + i18n("NAME") + QString::fromLatin1("?") );
+ m_dataItems.remove(*it);
+ kexipluginsdbg << "invalidateDataSources(): " << (*it) << " -> " << -1 << endl;
+// i++;
+ }
+#endif
+ //fill remaining part of the vector
+// for (; i < dataFieldsCount; i++) { //m_dataItems.count(); i++) {
+ //newIndices[i] = number++;
+ //kexipluginsdbg << "invalidateDataSources(): " << i << " -> " << number-1 << endl;
+ //}
+
+#if 0
+ //recreate m_fieldNumbersForDataItems and mark widgets with invalid data sources
+ KexiFormDataItemInterfaceToIntMap newFieldNumbersForDataItems;
+ foreach(KexiFormDataItemInterfaceToIntMap::ConstIterator, it, m_fieldNumbersForDataItems) {
+ bool ok;
+ const int newIndex = newIndices.at( it.data(), &ok );
+ if (ok && newIndex!=-1) {
+ kexipluginsdbg << "invalidateDataSources(): " << it.key()->dataSource() << ": " << it.data() << " -> " << newIndex << endl;
+ newFieldNumbersForDataItems.replace(it.key(), newIndex);
+ }
+ else {
+ kexipluginsdbg << "invalidateDataSources(): removing " << it.key()->dataSource() << endl;
+ m_dataItems.remove(it.key());
+ it.key()->setInvalidState( QString::fromLatin1("#") + i18n("NAME") + QString::fromLatin1("?") );
+ }
+ }
+#endif
+// m_fieldNumbersForDataItems = newFieldNumbersForDataItems;
+
+ //update data sources set (some of them may be removed)
+ QDict<char> tmpUsedDataSources(1013);
+
+ if (query)
+ query->debug();
+
+ //if (query && m_dataItems.count()!=query->fieldCount()) {
+ // kdWarning() << "KexiFormDataProvider::invalidateDataSources(): m_dataItems.count()!=query->fieldCount() ("
+ // << m_dataItems.count() << "," << query->fieldCount() << ")" << endl;
+ //}
+ //i = 0;
+ m_disableFillDuplicatedDataItems = true; // temporary disable fillDuplicatedDataItems()
+ // because setColumnInfo() can activate it
+ for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current();) {
+ KexiFormDataItemInterface * item = it.current();
+ if (invalidSources[ item->dataSource().lower() ]) {
+ item->setInvalidState( QString::fromLatin1("#") + i18n("NAME") + QString::fromLatin1("?") );
+ m_dataItems.remove(item);
+ continue;
+ }
+ uint fieldNumber = m_fieldNumbersForDataItems[ item ];
+ if (query) {
+ KexiDB::QueryColumnInfo *ci = fieldsExpanded[fieldNumber];
+ item->setColumnInfo(ci);
+ kexipluginsdbg << "- item=" << dynamic_cast<QObject*>(item)->name()
+ << " dataSource=" << item->dataSource()
+ << " field=" << ci->field->name() << endl;
+ const int indexForVisibleLookupValue = ci->indexForVisibleLookupValue();
+ if (-1 != indexForVisibleLookupValue && indexForVisibleLookupValue < (int)fieldsExpanded.count()) {
+ //there's lookup column defined: set visible column as well
+ KexiDB::QueryColumnInfo *visibleColumnInfo = fieldsExpanded[ indexForVisibleLookupValue ];
+ if (visibleColumnInfo) {
+ item->setVisibleColumnInfo( visibleColumnInfo );
+ if (dynamic_cast<KexiComboBoxBase*>(item) && m_mainWidget
+ && dynamic_cast<KexiComboBoxBase*>(item)->internalEditor())
+ {
+ // m_mainWidget (dbform) should filter the (just created using setVisibleColumnInfo())
+ // combo box' internal editor (actually, only if the combo is in 'editable' mode)
+ dynamic_cast<KexiComboBoxBase*>(item)->internalEditor()->installEventFilter(m_mainWidget);
+ }
+ kexipluginsdbg << " ALSO SET visibleColumn=" << visibleColumnInfo->debugString()
+ << "\n at position " << indexForVisibleLookupValue << endl;
+ }
+ }
+ }
+ tmpUsedDataSources.replace( item->dataSource().lower(), (char*)1 );
+ ++it;
+ }
+ m_disableFillDuplicatedDataItems = false;
+ m_usedDataSources.clear();
+ foreach_list(QDictIterator<char>, it, tmpUsedDataSources) {
+ m_usedDataSources += it.currentKey();
+ }
+}
diff --git a/kexi/plugins/forms/kexidataprovider.h b/kexi/plugins/forms/kexidataprovider.h
new file mode 100644
index 000000000..64019842a
--- /dev/null
+++ b/kexi/plugins/forms/kexidataprovider.h
@@ -0,0 +1,95 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFORMDATAPROVIDER_H
+#define KEXIFORMDATAPROVIDER_H
+
+#include "kexiformdataiteminterface.h"
+#include <qptrdict.h>
+#include <qdict.h>
+
+class KexiTableItem;
+namespace KexiDB {
+ class QuerySchema;
+}
+
+//! @short The KexiFormDataProvider class is a data provider for Kexi Forms
+/*! This provider collects data-aware widgets using setMainWidget().
+ Then, usedDataSources() unique list of required field names is available.
+ On every call of fillDataItems() method, the provider will fill data items
+ with appropriate data from a database cursor.
+
+ Field names are collected effectively, so eg. having widgets using data sources:
+ ("name", "surname", "surname", "name") - "name" and "surname" repeated - will only
+ return ("name", "surname") list, so the cursor's query can be simplified
+ and thus more effective.
+*/
+class KEXIFORMUTILS_EXPORT KexiFormDataProvider : public KexiDataItemChangesListener
+{
+ public:
+ KexiFormDataProvider();
+ virtual ~KexiFormDataProvider();
+
+ /*! sets \a mainWidget to be a main widget for this data provider.
+ Also find widgets whose will work as data items
+ (all of them must implement KexiFormDataItemInterface), so these could be
+ filled with data on demand. */
+ void setMainDataSourceWidget(QWidget* mainWidget);
+
+ QStringList usedDataSources() const { return m_usedDataSources; }
+
+ //unused QPtrList<KexiFormDataItemInterface>& dataItems() { return m_dataItems; }
+
+ /*! Fills data items with appropriate data fetched from \a cursor.
+ \a newRowEditing == true means that we are at new (not yet inserted) database row. */
+ void fillDataItems(KexiTableItem& row, bool cursorAtNewRow);
+
+ /*! Implementation for KexiDataItemChangesListener.
+ Reaction for change of \a item. Does nothing here. */
+ virtual void valueChanged(KexiDataItemInterface* item);
+
+ /*! Implementation for KexiDataItemChangesListener.
+ Implement this to return information whether we're currently at new row or now.
+ This can be used e.g. by data-aware widgets to determine if "(autonumber)"
+ label should be displayed. Returns false here. */
+ virtual bool cursorAtNewRow() const;
+
+ /*! Invalidates data sources collected by this provided.
+ \a invalidSources is the set of data sources that should
+ be omitted for fillDataItems().
+ Used by KexiFormView::initDataSource(). */
+ void invalidateDataSources( const QDict<char>& invalidSources,
+ KexiDB::QuerySchema* query = 0 );
+
+ /*! Fills the same data provided by \a value to every data item (other than \a item)
+ having the same data source as \a item. This method is called immediately when
+ \a value is changed, so duplicated data items are quickly updated. */
+ void fillDuplicatedDataItems(KexiFormDataItemInterface* item, const QVariant& value);
+
+ protected:
+ QWidget *m_mainWidget;
+ QPtrDict<char> *m_duplicatedItems;
+ typedef QMap<KexiFormDataItemInterface*,uint> KexiFormDataItemInterfaceToIntMap;
+ QPtrList<KexiFormDataItemInterface> m_dataItems;
+ QStringList m_usedDataSources;
+ KexiFormDataItemInterfaceToIntMap m_fieldNumbersForDataItems;
+ bool m_disableFillDuplicatedDataItems : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexidatasourcepage.cpp b/kexi/plugins/forms/kexidatasourcepage.cpp
new file mode 100644
index 000000000..6c0de8303
--- /dev/null
+++ b/kexi/plugins/forms/kexidatasourcepage.cpp
@@ -0,0 +1,471 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidatasourcepage.h"
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qheader.h>
+
+#include <kiconloader.h>
+#include <klocale.h>
+#include <ktoolbarbutton.h>
+#include <kdebug.h>
+#include <kpopupmenu.h>
+
+#include <widget/kexipropertyeditorview.h>
+#include <widget/kexidatasourcecombobox.h>
+#include <widget/kexifieldlistview.h>
+#include <widget/kexifieldcombobox.h>
+#include <widget/kexismalltoolbutton.h>
+#include <kexidb/connection.h>
+#include <kexiproject.h>
+
+#include <formeditor/commands.h>
+
+#include <koproperty/property.h>
+#include <koproperty/utils.h>
+
+KexiDataSourcePage::KexiDataSourcePage(QWidget *parent, const char *name)
+ : QWidget(parent, name)
+ , m_insideClearDataSourceSelection(false)
+{
+ QVBoxLayout *vlyr = new QVBoxLayout(this);
+ m_objectInfoLabel = new KexiObjectInfoLabel(this, "KexiObjectInfoLabel");
+ vlyr->addWidget(m_objectInfoLabel);
+
+ m_noDataSourceAvailableSingleText = i18n("No data source could be assigned for this widget.");
+ m_noDataSourceAvailableMultiText = i18n("No data source could be assigned for multiple widgets.");
+
+ vlyr->addSpacing(8);
+
+ //Section 1: Form's/Widget's Data Source
+ KoProperty::GroupContainer *container = new KoProperty::GroupContainer(i18n("Data Source"), this);
+ vlyr->addWidget(container);
+
+ QWidget *contents = new QWidget(container);
+ container->setContents(contents);
+ QVBoxLayout *contentsVlyr = new QVBoxLayout(contents);
+
+ m_noDataSourceAvailableLabel = new QLabel(m_noDataSourceAvailableSingleText, contents);
+ m_noDataSourceAvailableLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ m_noDataSourceAvailableLabel->setMargin(2);
+ m_noDataSourceAvailableLabel->setAlignment(Qt::WordBreak | Qt::AlignBottom | Qt::AlignLeft);
+ contentsVlyr->addWidget(m_noDataSourceAvailableLabel);
+
+ //-Widget's Data Source
+ QHBoxLayout *hlyr = new QHBoxLayout(contentsVlyr);
+#if 0
+//! @todo unhide this when expression work
+// m_widgetDSLabel = new QLabel(i18n("Table Field, Query Field or Expression", "Source field or expression:"), this);
+#else
+ m_widgetDSLabel = new QLabel(i18n("Table Field or Query Field", "Widget's data source:"), contents);
+#endif
+ m_widgetDSLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ m_widgetDSLabel->setMargin(2);
+ m_widgetDSLabel->setMinimumHeight(IconSize(KIcon::Small)+4);
+ m_widgetDSLabel->setAlignment(AlignLeft|AlignBottom);
+ hlyr->addWidget(m_widgetDSLabel);
+
+ m_clearWidgetDSButton = new KexiSmallToolButton(contents, QString::null, "clear_left", "clearWidgetDSButton");
+ m_clearWidgetDSButton->setMinimumHeight(m_widgetDSLabel->minimumHeight());
+ QToolTip::add(m_clearWidgetDSButton, i18n("Clear widget's data source"));
+ hlyr->addWidget(m_clearWidgetDSButton);
+ connect(m_clearWidgetDSButton, SIGNAL(clicked()), this, SLOT(clearWidgetDataSourceSelection()));
+
+ m_sourceFieldCombo = new KexiFieldComboBox(contents, "sourceFieldCombo");
+ m_widgetDSLabel->setBuddy(m_sourceFieldCombo);
+ contentsVlyr->addWidget(m_sourceFieldCombo);
+
+/* m_dataSourceSeparator = new QFrame(contents);
+ m_dataSourceSeparator->setFrameShape(QFrame::HLine);
+ m_dataSourceSeparator->setFrameShadow(QFrame::Sunken);
+ contentsVlyr->addWidget(m_dataSourceSeparator);*/
+
+ contentsVlyr->addSpacing(8);
+
+ //- Form's Data Source
+ hlyr = new QHBoxLayout(contentsVlyr);
+ m_dataSourceLabel = new QLabel(i18n("Form's data source:"), contents);
+ m_dataSourceLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ m_dataSourceLabel->setMargin(2);
+ m_dataSourceLabel->setMinimumHeight(IconSize(KIcon::Small)+4);
+ m_dataSourceLabel->setAlignment(AlignLeft|AlignBottom);
+ hlyr->addWidget(m_dataSourceLabel);
+
+ m_gotoButton = new KexiSmallToolButton(contents, QString::null, "goto", "gotoButton");
+ m_gotoButton->setMinimumHeight(m_dataSourceLabel->minimumHeight());
+ QToolTip::add(m_gotoButton, i18n("Go to selected form's data source"));
+ hlyr->addWidget(m_gotoButton);
+ connect(m_gotoButton, SIGNAL(clicked()), this, SLOT(slotGotoSelected()));
+
+ m_clearDSButton = new KexiSmallToolButton(contents, QString::null, "clear_left", "clearDSButton");
+ m_clearDSButton->setMinimumHeight(m_dataSourceLabel->minimumHeight());
+ QToolTip::add(m_clearDSButton, i18n("Clear form's data source"));
+ hlyr->addWidget(m_clearDSButton);
+ connect(m_clearDSButton, SIGNAL(clicked()), this, SLOT(clearDataSourceSelection()));
+
+ m_dataSourceCombo = new KexiDataSourceComboBox(contents, "dataSourceCombo");
+ m_dataSourceLabel->setBuddy(m_dataSourceCombo);
+ contentsVlyr->addWidget(m_dataSourceCombo);
+
+#ifdef KEXI_NO_AUTOFIELD_WIDGET
+ m_availableFieldsLabel = 0;
+ m_addField = 0;
+// m_fieldListView = 0;
+ vlyr->addStretch();
+#else
+ vlyr->addSpacing(fontMetrics().height());
+/* QFrame *separator = new QFrame(this);
+ separator->setFrameShape(QFrame::HLine);
+ separator->setFrameShadow(QFrame::Sunken);
+ vlyr->addWidget(separator);*/
+/*
+ KPopupTitle *title = new KPopupTitle(this);
+ title->setTitle(i18n("Inserting fields"));
+ vlyr->addWidget(title);
+ vlyr->addSpacing(4);*/
+
+
+ //2. Inserting fields
+ container = new KoProperty::GroupContainer(i18n("Inserting Fields"), this);
+ vlyr->addWidget(container, 1);
+
+ //helper info
+//! @todo allow to hide such helpers by adding global option
+ contents = new QWidget(container);
+ container->setContents(contents);
+ contentsVlyr = new QVBoxLayout(contents);
+ hlyr = new QHBoxLayout(contentsVlyr);
+ m_mousePointerLabel = new QLabel(contents);
+ hlyr->addWidget(m_mousePointerLabel);
+ m_mousePointerLabel->setPixmap( SmallIcon("mouse_pointer") );
+ m_mousePointerLabel->setFixedWidth( m_mousePointerLabel->pixmap() ? m_mousePointerLabel->pixmap()->width() : 0);
+ m_availableFieldsDescriptionLabel = new QLabel(
+ i18n("Select fields from the list below and drag them onto a form or click the \"Insert\" button"), contents);
+ m_availableFieldsDescriptionLabel->setAlignment( Qt::AlignAuto | Qt::WordBreak );
+ hlyr->addWidget(m_availableFieldsDescriptionLabel);
+
+ //Available Fields
+ contentsVlyr->addSpacing(4);
+ hlyr = new QHBoxLayout(contentsVlyr);
+ m_availableFieldsLabel = new QLabel(i18n("Available fields:"), contents);
+ m_availableFieldsLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ m_availableFieldsLabel->setMargin(2);
+ m_availableFieldsLabel->setMinimumHeight(IconSize(KIcon::Small));
+ hlyr->addWidget(m_availableFieldsLabel);
+
+ m_addField = new KexiSmallToolButton(contents, i18n("Insert selected field into form", "Insert"),
+ "add_field", "addFieldButton");
+ m_addField->setMinimumHeight(m_availableFieldsLabel->minimumHeight());
+// m_addField->setTextPosition(QToolButton::Right);
+ m_addField->setFocusPolicy(StrongFocus);
+ QToolTip::add(m_addField, i18n("Insert selected fields into form"));
+ hlyr->addWidget(m_addField);
+ connect(m_addField, SIGNAL(clicked()), this, SLOT(slotInsertSelectedFields()));
+
+ m_fieldListView = new KexiFieldListView(contents, "fieldListView",
+ KexiFieldListView::ShowDataTypes | KexiFieldListView::AllowMultiSelection );
+ m_fieldListView->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
+ m_availableFieldsLabel->setBuddy(m_fieldListView);
+ contentsVlyr->addWidget(m_fieldListView, 1);
+ connect(m_fieldListView, SIGNAL(selectionChanged()), this, SLOT(slotFieldListViewSelectionChanged()));
+ connect(m_fieldListView, SIGNAL(fieldDoubleClicked(const QString&, const QString&, const QString&)),
+ this, SLOT(slotFieldDoubleClicked(const QString&, const QString&, const QString&)));
+#endif
+
+ vlyr->addStretch(1);
+
+ connect(m_dataSourceCombo, SIGNAL(textChanged(const QString &)), this, SLOT(slotDataSourceTextChanged(const QString &)));
+ connect(m_dataSourceCombo, SIGNAL(dataSourceChanged()), this, SLOT(slotDataSourceChanged()));
+ connect(m_sourceFieldCombo, SIGNAL(selected()), this, SLOT(slotFieldSelected()));
+
+ clearDataSourceSelection();
+ slotFieldListViewSelectionChanged();
+}
+
+KexiDataSourcePage::~KexiDataSourcePage()
+{
+}
+
+void KexiDataSourcePage::setProject(KexiProject *prj)
+{
+ m_sourceFieldCombo->setProject(prj);
+ m_dataSourceCombo->setProject(prj);
+}
+
+void KexiDataSourcePage::clearDataSourceSelection(bool alsoClearComboBox)
+{
+ if (m_insideClearDataSourceSelection)
+ return;
+ m_insideClearDataSourceSelection = true;
+ if (alsoClearComboBox && !m_dataSourceCombo->selectedName().isEmpty())
+ m_dataSourceCombo->setDataSource("", "");
+// if (!m_dataSourceCombo->currentText().isEmpty()) {
+// m_dataSourceCombo->setCurrentText("");
+// emit m_dataSourceCombo->dataSourceSelected();
+// }
+ m_clearDSButton->setEnabled(false);
+ m_gotoButton->setEnabled(false);
+#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ m_addField->setEnabled(false);
+ m_fieldListView->clear();
+#endif
+ m_insideClearDataSourceSelection = false;
+}
+
+void KexiDataSourcePage::clearWidgetDataSourceSelection()
+{
+ if (!m_sourceFieldCombo->currentText().isEmpty()) {
+ m_sourceFieldCombo->setCurrentText("");
+ m_sourceFieldCombo->setFieldOrExpression(QString::null);
+ slotFieldSelected();
+ }
+ m_clearWidgetDSButton->setEnabled(false);
+}
+
+void KexiDataSourcePage::slotGotoSelected()
+{
+ QCString mime = m_dataSourceCombo->selectedMimeType().latin1();
+ if (mime=="kexi/table" || mime=="kexi/query") {
+ if (m_dataSourceCombo->isSelectionValid())
+ emit jumpToObjectRequested(mime, m_dataSourceCombo->selectedName().latin1());
+ }
+}
+
+void KexiDataSourcePage::slotInsertSelectedFields()
+{
+#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ QStringList selectedFieldNames(m_fieldListView->selectedFieldNames());
+ if (selectedFieldNames.isEmpty())
+ return;
+
+ emit insertAutoFields(m_fieldListView->schema()->table() ? "kexi/table" : "kexi/query",
+ m_fieldListView->schema()->name(), selectedFieldNames);
+#endif
+}
+
+void KexiDataSourcePage::slotFieldDoubleClicked(const QString& sourceMimeType, const QString& sourceName,
+ const QString& fieldName)
+{
+#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ QStringList selectedFields;
+ selectedFields.append(fieldName);
+ emit insertAutoFields(sourceMimeType, sourceName, selectedFields);
+#endif
+}
+
+void KexiDataSourcePage::slotDataSourceTextChanged(const QString & string)
+{
+ Q_UNUSED(string);
+ const bool enable = m_dataSourceCombo->isSelectionValid(); //!string.isEmpty() && m_dataSourceCombo->selectedName() == string.latin1();
+ if (!enable) {
+ clearDataSourceSelection( m_dataSourceCombo->selectedName().isEmpty()/*alsoClearComboBox*/ );
+ }
+ updateSourceFieldWidgetsAvailability();
+/*#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ m_fieldListView->setEnabled(enable);
+// m_addField->setEnabled(enable);
+ m_availableFieldsLabel->setEnabled(enable);
+#endif*/
+}
+
+void KexiDataSourcePage::slotDataSourceChanged()
+{
+ if (!m_dataSourceCombo->project())
+ return;
+ QCString mime = m_dataSourceCombo->selectedMimeType().latin1();
+ bool dataSourceFound = false;
+ QCString name = m_dataSourceCombo->selectedName().latin1();
+ if ((mime=="kexi/table" || mime=="kexi/query") && m_dataSourceCombo->isSelectionValid()) {
+ KexiDB::TableOrQuerySchema *tableOrQuery = new KexiDB::TableOrQuerySchema(
+ m_dataSourceCombo->project()->dbConnection(), name, mime=="kexi/table");
+ if (tableOrQuery->table() || tableOrQuery->query()) {
+#ifdef KEXI_NO_AUTOFIELD_WIDGET
+ m_tableOrQuerySchema = tableOrQuery;
+#else
+ m_fieldListView->setSchema( tableOrQuery );
+#endif
+ dataSourceFound = true;
+ m_sourceFieldCombo->setTableOrQuery(name, mime=="kexi/table");
+ }
+ else {
+ delete tableOrQuery;
+ }
+ }
+ if (!dataSourceFound) {
+ m_sourceFieldCombo->setTableOrQuery("", true);
+ }
+ //if (m_sourceFieldCombo->hasFocus())
+// m_dataSourceCombo->setFocus();
+ m_clearDSButton->setEnabled(dataSourceFound);
+ m_gotoButton->setEnabled(dataSourceFound);
+ if (dataSourceFound) {
+ slotFieldListViewSelectionChanged();
+ } else {
+#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ m_addField->setEnabled(false);
+#endif
+ }
+ updateSourceFieldWidgetsAvailability();
+ emit formDataSourceChanged(mime, name);
+}
+
+void KexiDataSourcePage::slotFieldSelected()
+{
+ KexiDB::Field::Type dataType = KexiDB::Field::InvalidType;
+#ifdef KEXI_NO_AUTOFIELD_WIDGET
+ KexiDB::Field *field = m_tableOrQuerySchema->field( m_sourceFieldCombo->fieldOrExpression() ); //temp
+#else
+//! @todo this should also work for expressions
+ KexiDB::Field *field = m_fieldListView->schema()->field( m_sourceFieldCombo->fieldOrExpression() );
+#endif
+ if (field)
+ dataType = field->type();
+
+ m_clearWidgetDSButton->setEnabled( !m_sourceFieldCombo->fieldOrExpression().isEmpty() );
+
+ emit dataSourceFieldOrExpressionChanged(
+ m_sourceFieldCombo->fieldOrExpression(),
+ m_sourceFieldCombo->fieldOrExpressionCaption(),
+ dataType
+ );
+}
+
+void KexiDataSourcePage::setDataSource(const QCString& mimeType, const QCString& name)
+{
+ m_dataSourceCombo->setDataSource(mimeType, name);
+}
+
+void KexiDataSourcePage::assignPropertySet(KoProperty::Set* propertySet)
+{
+ QCString objectName;
+ if (propertySet && propertySet->contains("name"))
+ objectName = (*propertySet)["name"].value().toCString();
+ if (!objectName.isEmpty() && objectName == m_currentObjectName)
+ return; //the same object
+ m_currentObjectName = objectName;
+
+ QCString objectClassName;
+ if (propertySet && propertySet->contains("this:className"))
+ objectClassName = (*propertySet)["this:className"].value().toCString();
+/*moved if (propertySet) {
+ QCString iconName;
+ QString objectClassString;
+ if (propertySet->contains("this:iconName"))
+ iconName = (*propertySet)["this:iconName"].value().toCString();
+ if (propertySet->contains("this:classString"))
+ objectClassString = (*propertySet)["this:classString"].value().toString();
+ m_objectInfoLabel->setObjectName(objectName);
+ m_objectInfoLabel->setObjectClassIcon(iconName);
+ m_objectInfoLabel->setObjectClassName(objectClassString);
+ if (propertySet->contains("this:className"))
+ objectClassName = (*propertySet)["this:className"].value().toCString();
+ }*/
+ KexiPropertyEditorView::updateInfoLabelForPropertySet(
+ m_objectInfoLabel, propertySet);
+
+ const bool isForm = objectClassName=="KexiDBForm";
+// kdDebug() << "objectClassName=" << objectClassName << endl;
+// {
+/* //this is top level form's surface: data source means table or query
+ QCString dataSourceMimeType, dataSource;
+ if (buffer->hasProperty("dataSourceMimeType"))
+ dataSourceMimeType = (*buffer)["dataSourceMimeType"].value().toCString();
+ if (buffer->hasProperty("dataSource"))
+ dataSource = (*buffer)["dataSource"].value().toCString();
+ m_dataSourceCombo->setDataSource(dataSourceMimeType, dataSource);*/
+// }
+// else {
+
+ const bool multipleSelection = objectClassName=="special:multiple";
+ const bool hasDataSourceProperty = propertySet && propertySet->contains("dataSource") && !multipleSelection;
+
+ if (!isForm) {
+ //this is a widget
+ QCString dataSource;
+ if (hasDataSourceProperty) {
+ if (propertySet)
+ dataSource = (*propertySet)["dataSource"].value().toCString();
+ m_noDataSourceAvailableLabel->hide();
+ m_sourceFieldCombo->setFieldOrExpression(dataSource);
+ m_sourceFieldCombo->setEnabled(true);
+ m_clearWidgetDSButton->setEnabled(!m_sourceFieldCombo->currentText().isEmpty());
+ m_widgetDSLabel->show();
+ m_clearWidgetDSButton->show();
+ m_sourceFieldCombo->show();
+// m_dataSourceSeparator->hide();
+ updateSourceFieldWidgetsAvailability();
+ }
+ }
+
+ if (isForm) {
+ m_noDataSourceAvailableLabel->hide();
+// m_dataSourceSeparator->hide();
+ }
+ else if (!hasDataSourceProperty) {
+ if (multipleSelection)
+ m_noDataSourceAvailableLabel->setText(m_noDataSourceAvailableMultiText);
+ else
+ m_noDataSourceAvailableLabel->setText(m_noDataSourceAvailableSingleText);
+ m_noDataSourceAvailableLabel->show();
+// m_dataSourceSeparator->show();
+ //make 'No data source could be assigned' label's height the same as the 'source field' combo+label
+ m_noDataSourceAvailableLabel->setMinimumHeight(m_widgetDSLabel->height()
+ + m_sourceFieldCombo->height()/*-m_dataSourceSeparator->height()*/);
+ m_sourceFieldCombo->setCurrentText("");
+ }
+
+ if (isForm || !hasDataSourceProperty) {
+ //no source field can be set
+ m_widgetDSLabel->hide();
+ m_clearWidgetDSButton->hide();
+ m_sourceFieldCombo->hide();
+ }
+}
+
+void KexiDataSourcePage::slotFieldListViewSelectionChanged()
+{
+#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ //update "add field" button's state
+ for (QListViewItemIterator it(m_fieldListView); it.current(); ++it) {
+ if (it.current()->isSelected()) {
+ m_addField->setEnabled(true);
+ return;
+ }
+ }
+ m_addField->setEnabled(false);
+#endif
+}
+
+void KexiDataSourcePage::updateSourceFieldWidgetsAvailability()
+{
+ const bool hasDataSource = m_dataSourceCombo->isSelectionValid(); //!m_dataSourceCombo->selectedName().isEmpty();
+ m_sourceFieldCombo->setEnabled( hasDataSource );
+ m_widgetDSLabel->setEnabled( hasDataSource );
+#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ m_fieldListView->setEnabled( hasDataSource );
+ m_availableFieldsLabel->setEnabled( hasDataSource );
+ m_mousePointerLabel->setEnabled( hasDataSource );
+ m_availableFieldsDescriptionLabel->setEnabled( hasDataSource );
+#endif
+}
+
+#include "kexidatasourcepage.moc"
diff --git a/kexi/plugins/forms/kexidatasourcepage.h b/kexi/plugins/forms/kexidatasourcepage.h
new file mode 100644
index 000000000..0f113aa7e
--- /dev/null
+++ b/kexi/plugins/forms/kexidatasourcepage.h
@@ -0,0 +1,112 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef KEXIDATASOURCEPAGE_H
+#define KEXIDATASOURCEPAGE_H
+
+#include <qwidget.h>
+#include <kexidb/field.h>
+#include <kexidb/utils.h>
+#include <koproperty/set.h>
+
+class KCommand;
+class KexiObjectInfoLabel;
+class KexiDataSourceComboBox;
+class KexiFieldComboBox;
+class KexiFieldListView;
+class KexiProject;
+class QToolButton;
+class QLabel;
+class QFrame;
+
+//! A page within form designer's property tabbed pane, providing data source editor
+class KEXIFORMUTILS_EXPORT KexiDataSourcePage : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiDataSourcePage(QWidget *parent, const char *name = 0);
+ virtual ~KexiDataSourcePage();
+
+ KexiDataSourceComboBox* dataSourceCombo() const { return m_dataSourceCombo; }
+ KexiObjectInfoLabel* objectInfoLabel() const { return m_objectInfoLabel; }
+
+ public slots:
+ void setProject(KexiProject *prj);
+ void clearDataSourceSelection(bool alsoClearComboBox = true);
+ void clearWidgetDataSourceSelection();
+
+ //! Sets data source of a currently selected form.
+ //! This is performed on form initialization and on activating.
+ void setDataSource(const QCString& mimeType, const QCString& name);
+
+ //! Receives a pointer to a new property \a set (from KexiFormView::managerPropertyChanged())
+ void assignPropertySet(KoProperty::Set* propertySet);
+
+ signals:
+ //! Signal emitted when helper button 'go to selected data source' is clicked.
+ void jumpToObjectRequested(const QCString& mime, const QCString& name);
+
+ //! Signal emitted when form's data source has been changed. It's connected to the Form Manager.
+ void formDataSourceChanged(const QCString& mime, const QCString& name);
+
+ /*! Signal emitted when current widget's data source (field/expression)
+ has been changed. It's connected to the Form Manager.
+ \a caption for this field is also provided (e.g. AutoField form widget use it) */
+ void dataSourceFieldOrExpressionChanged(const QString& string, const QString& caption,
+ KexiDB::Field::Type type);
+
+ /*! Signal emitted when 'insert fields' button has been clicked */
+ void insertAutoFields(const QString& sourceMimeType, const QString& sourceName,
+ const QStringList& fields);
+
+ protected slots:
+ void slotDataSourceTextChanged(const QString & string);
+ void slotDataSourceChanged();
+ void slotFieldSelected();
+ void slotGotoSelected();
+ void slotInsertSelectedFields();
+ void slotFieldListViewSelectionChanged();
+ void slotFieldDoubleClicked(const QString& sourceMimeType, const QString& sourceName,
+ const QString& fieldName);
+
+ protected:
+ void updateSourceFieldWidgetsAvailability();
+
+ KexiFieldComboBox *m_sourceFieldCombo;
+ KexiObjectInfoLabel *m_objectInfoLabel;
+ KexiDataSourceComboBox* m_dataSourceCombo;
+ QLabel *m_dataSourceLabel, *m_noDataSourceAvailableLabel,
+ *m_widgetDSLabel, *m_availableFieldsLabel,
+ *m_mousePointerLabel, *m_availableFieldsDescriptionLabel;
+ QToolButton *m_clearWidgetDSButton, *m_clearDSButton, *m_gotoButton, *m_addField;
+ QFrame *m_dataSourceSeparator;
+ QString m_noDataSourceAvailableSingleText, m_noDataSourceAvailableMultiText;
+ bool m_insideClearDataSourceSelection : 1;
+#ifdef KEXI_NO_AUTOFIELD_WIDGET
+ KexiDB::TableOrQuerySchema *m_tableOrQuerySchema; //!< temp.
+#else
+ KexiFieldListView* m_fieldListView;
+#endif
+
+ //! Used only in assignPropertySet() to check whether we already have the set assigned
+ QCString m_currentObjectName;
+ //QGuardedPtr<KoProperty::Set> m_propertySet;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexidbfactory.cpp b/kexi/plugins/forms/kexidbfactory.cpp
new file mode 100644
index 000000000..4ab05d76c
--- /dev/null
+++ b/kexi/plugins/forms/kexidbfactory.cpp
@@ -0,0 +1,713 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qpopupmenu.h>
+#include <qscrollview.h>
+#include <qcursor.h>
+#include <qpainter.h>
+#include <qstyle.h>
+
+#include <kgenericfactory.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <kactioncollection.h>
+#include <kstdaction.h>
+
+#include <formeditor/container.h>
+#include <formeditor/form.h>
+#include <formeditor/formIO.h>
+#include <formeditor/formmanager.h>
+#include <formeditor/objecttree.h>
+#include <formeditor/utils.h>
+#include <kexidb/utils.h>
+#include <kexidb/connection.h>
+#include <kexipart.h>
+#include <formeditor/widgetlibrary.h>
+#include <kexigradientwidget.h>
+#include <keximainwindow.h>
+#include <kexiutils/utils.h>
+#include <widget/kexicustompropertyfactory.h>
+#include <widget/utils/kexicontextmenuutils.h>
+
+#include "kexiformview.h"
+#include "widgets/kexidbautofield.h"
+#include "widgets/kexidbcheckbox.h"
+#include "widgets/kexidbimagebox.h"
+//#include "widgets/kexidbdoublespinbox.h"
+//#include "widgets/kexidbintspinbox.h"
+#include "widgets/kexiframe.h"
+#include "widgets/kexidblabel.h"
+#include "widgets/kexidblineedit.h"
+#include "widgets/kexidbtextedit.h"
+#include "widgets/kexidbcombobox.h"
+#include "widgets/kexipushbutton.h"
+#include "widgets/kexidbform.h"
+#include "widgets/kexidbsubform.h"
+#include "kexidataawarewidgetinfo.h"
+
+#include "kexidbfactory.h"
+#include <core/kexi.h>
+
+
+//////////////////////////////////////////
+
+KexiDBFactory::KexiDBFactory(QObject *parent, const char *name, const QStringList &)
+ : KFormDesigner::WidgetFactory(parent, name)
+{
+ KFormDesigner::WidgetInfo *wi;
+ wi = new KexiDataAwareWidgetInfo(this);
+ wi->setPixmap("form");
+ wi->setClassName("KexiDBForm");
+ wi->setName(i18n("Form"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "form"));
+ wi->setDescription(i18n("A data-aware form widget"));
+ addClass(wi);
+
+#ifndef KEXI_NO_SUBFORM
+ wi = new KexiDataAwareWidgetInfo(this);
+ wi->setPixmap("subform");
+ wi->setClassName("KexiDBSubForm");
+ wi->addAlternateClassName("KexiSubForm", true/*override*/); //older
+ wi->setName(i18n("Sub Form"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "subForm"));
+ wi->setDescription(i18n("A form widget included in another Form"));
+ wi->setAutoSyncForProperty( "formName", false );
+ addClass(wi);
+#endif
+
+ // inherited
+ wi = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KLineEdit");
+ wi->setPixmap("lineedit");
+ wi->setClassName("KexiDBLineEdit");
+ wi->addAlternateClassName("QLineEdit", true/*override*/);
+ wi->addAlternateClassName("KLineEdit", true/*override*/);
+ wi->setIncludeFileName("klineedit.h");
+ wi->setName(i18n("Text Box"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "textBox"));
+ wi->setDescription(i18n("A widget for entering and displaying text"));
+ addClass(wi);
+
+ // inherited
+ wi = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KTextEdit");
+ wi->setPixmap("textedit");
+ wi->setClassName("KexiDBTextEdit");
+ wi->addAlternateClassName("QTextEdit", true/*override*/);
+ wi->addAlternateClassName("KTextEdit", true/*override*/);
+ wi->setIncludeFileName("ktextedit.h");
+ wi->setName(i18n("Text Editor"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "textEditor"));
+ wi->setDescription(i18n("A multiline text editor"));
+ addClass(wi);
+
+ wi = new KFormDesigner::WidgetInfo(
+ this, "containers", "QFrame" /*we're inheriting to get i18n'd strings already translated there*/);
+ wi->setPixmap("frame");
+ wi->setClassName("KexiFrame");
+ wi->addAlternateClassName("QFrame", true/*override*/);
+ wi->setName(i18n("Frame"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "frame"));
+ wi->setDescription(i18n("A simple frame widget"));
+ addClass(wi);
+
+ wi = new KexiDataAwareWidgetInfo(
+ this, "stdwidgets", "QLabel" /*we're inheriting to get i18n'd strings already translated there*/);
+ wi->setPixmap("label");
+ wi->setClassName("KexiDBLabel");
+ wi->addAlternateClassName("QLabel", true/*override*/);
+ wi->addAlternateClassName("KexiLabel", true/*override*/); //older
+ wi->setName(i18n("Text Label", "Label"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "label"));
+ wi->setDescription(i18n("A widget for displaying text"));
+ addClass(wi);
+
+#ifndef KEXI_NO_IMAGEBOX_WIDGET
+ wi = new KexiDataAwareWidgetInfo(
+ this, "stdwidgets", "KexiPictureLabel" /*we're inheriting to get i18n'd strings already translated there*/);
+ wi->setPixmap("pixmaplabel");
+ wi->setClassName("KexiDBImageBox");
+ wi->addAlternateClassName("KexiPictureLabel", true/*override*/);
+ wi->addAlternateClassName("KexiImageBox", true/*override*/); //older
+ wi->setName(i18n("Image Box"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "image"));
+ wi->setDescription(i18n("A widget for displaying images"));
+// wi->setCustomTypeForProperty("pixmapData", KexiCustomPropertyFactory::PixmapData);
+ wi->setCustomTypeForProperty("pixmapId", KexiCustomPropertyFactory::PixmapId);
+ addClass(wi);
+
+ setInternalProperty("KexiDBImageBox", "dontStartEditingOnInserting", "1");
+// setInternalProperty("KexiDBImageBox", "forceShowAdvancedProperty:pixmap", "1");
+#endif
+
+#ifdef KEXI_DB_COMBOBOX_WIDGET
+ wi = new KexiDataAwareWidgetInfo(
+ this, "stdwidgets", "KComboBox" /*we're inheriting to get i18n'd strings already translated there*/);
+ wi->setPixmap("combo");
+ wi->setClassName("KexiDBComboBox");
+ wi->addAlternateClassName("KComboBox", true/*override*/);
+ wi->setName(i18n("Combo Box"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "comboBox"));
+ wi->setDescription(i18n("A combo box widget"));
+ addClass(wi);
+#endif
+
+ wi = new KexiDataAwareWidgetInfo(this, "stdwidgets", "QCheckBox");
+ wi->setPixmap("check");
+ wi->setClassName("KexiDBCheckBox");
+ wi->addAlternateClassName("QCheckBox", true/*override*/);
+ wi->setName(i18n("Check Box"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "checkBox"));
+ wi->setDescription(i18n("A check box with text label"));
+ addClass(wi);
+
+#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ wi = new KexiDataAwareWidgetInfo(this);
+ wi->setPixmap("autofield");
+ wi->setClassName("KexiDBAutoField");
+ wi->addAlternateClassName("KexiDBFieldEdit", true/*override*/); //older
+ wi->setName(i18n("Auto Field"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters", "autoField"));
+ wi->setDescription(i18n("A widget containing an automatically selected editor "
+ "and a label to edit the value of a database field of any type."));
+ addClass(wi);
+#endif
+
+/*
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9)
+ KexiDataAwareWidgetInfo *wDate = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KDateWidget");
+#else
+ KexiDataAwareWidgetInfo *wDate = new KexiDataAwareWidgetInfo(this, "stdwidgets", "QDateEdit");
+#endif
+ wDate->setPixmap("dateedit");
+ wDate->setClassName("KexiDBDateEdit");
+ wDate->addAlternateClassName("QDateEdit", true);//override
+ wDate->addAlternateClassName("KDateWidget", true);//override
+ wDate->setName(i18n("Date Widget"));
+ wDate->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "dateWidget"));
+ wDate->setDescription(i18n("A widget to input and display a date"));
+ addClass(wDate);
+
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9)
+ KexiDataAwareWidgetInfo *wTime = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KTimeWidget");
+#else
+ KexiDataAwareWidgetInfo *wTime = new KexiDataAwareWidgetInfo(this, "stdwidgets", "QTimeEdit");
+#endif
+ wTime->setPixmap("timeedit");
+ wTime->setClassName("KexiDBTimeEdit");
+ wTime->addAlternateClassName("QTimeEdit", true);//override
+ wTime->addAlternateClassName("KTimeWidget", true);//override
+ wTime->setName(i18n("Time Widget"));
+ wTime->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "timeWidget"));
+ wTime->setDescription(i18n("A widget to input and display a time"));
+ addClass(wTime);
+
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9)
+ KexiDataAwareWidgetInfo *wDateTime = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KDateTimeWidget");
+#else
+ KexiDataAwareWidgetInfo *wDateTime = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KDateTimeWidget");
+#endif
+ wDateTime->setPixmap("datetimeedit");
+ wDateTime->setClassName("KexiDBDateTimeEdit");
+ wDateTime->addAlternateClassName("QDateTimeEdit", true);//override
+ wDateTime->addAlternateClassName("KDateTimeWidget", true);//override
+ wDateTime->setName(i18n("Date/Time Widget"));
+ wDateTime->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "dateTimeWidget"));
+ wDateTime->setDescription(i18n("A widget to input and display a date and time"));
+ addClass(wDateTime);
+*/
+
+/* KexiDataAwareWidgetInfo *wIntSpinBox = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KIntSpinBox");
+ wIntSpinBox->setPixmap("spin");
+ wIntSpinBox->setClassName("KexiDBIntSpinBox");
+ wIntSpinBox->addAlternateClassName("QSpinBox", true);
+ wIntSpinBox->addAlternateClassName("KIntSpinBox", true);
+ wIntSpinBox->setName(i18n("Integer Number Spin Box"));
+ wIntSpinBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "intSpinBox"));
+ wIntSpinBox->setDescription(i18n("A spin box widget to input and display integer numbers"));
+ addClass(wIntSpinBox);
+
+ KexiDataAwareWidgetInfo *wDoubleSpinBox = new KexiDataAwareWidgetInfo(this, "stdwidgets");
+ wDoubleSpinBox->setPixmap("spin");
+ wDoubleSpinBox->setClassName("KexiDBDoubleSpinBox");
+ wDoubleSpinBox->addAlternateClassName("KDoubleSpinBox", true);
+ wDoubleSpinBox->setName(i18n("Floating-point Number Spin Box"));
+ wDoubleSpinBox->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "dblSpinBox"));
+ wDoubleSpinBox->setDescription(i18n("A spin box widget to input and display floating-point numbers"));
+ addClass(wDoubleSpinBox);*/
+
+ // inherited
+ wi = new KFormDesigner::WidgetInfo(
+ this, "stdwidgets", "KPushButton");
+ wi->addAlternateClassName("KexiPushButton");
+ wi->setName(i18n("Command Button"));
+ wi->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. "
+ "It must _not_ contain white spaces and non latin1 characters.", "button"));
+ wi->setDescription(i18n("A command button to execute actions"));
+ addClass(wi);
+
+ m_propDesc["dataSource"] = i18n("Data Source");
+ m_propDesc["formName"] = i18n("Form Name");
+ m_propDesc["onClickAction"] = i18n("On Click");
+ m_propDesc["onClickActionOption"] = i18n("On Click Option");
+ m_propDesc["autoTabStops"] = i18n("Auto Tab Order");
+ m_propDesc["shadowEnabled"] = i18n("Shadow Enabled");
+ m_propDesc["on"] = i18n("On: button", "On");
+
+ m_propDesc["widgetType"] = i18n("Editor Type");
+ //for autofield's type: inherit i18n from KexiDB
+ m_propValDesc["Auto"] = i18n("AutoField editor's type", "Auto");
+ m_propValDesc["Text"] = KexiDB::Field::typeName(KexiDB::Field::Text);
+ m_propValDesc["Integer"] = KexiDB::Field::typeName(KexiDB::Field::Integer);
+ m_propValDesc["Double"] = KexiDB::Field::typeName(KexiDB::Field::Double);
+ m_propValDesc["Boolean"] = KexiDB::Field::typeName(KexiDB::Field::Boolean);
+ m_propValDesc["Date"] = KexiDB::Field::typeName(KexiDB::Field::Date);
+ m_propValDesc["Time"] = KexiDB::Field::typeName(KexiDB::Field::Time);
+ m_propValDesc["DateTime"] = KexiDB::Field::typeName(KexiDB::Field::DateTime);
+ m_propValDesc["MultiLineText"] = i18n("AutoField editor's type", "Multiline Text");
+ m_propValDesc["ComboBox"] = i18n("AutoField editor's type", "Drop-Down List");
+ m_propValDesc["Image"] = i18n("AutoField editor's type", "Image");
+
+// m_propDesc["labelCaption"] = i18n("Label Text");
+ m_propDesc["autoCaption"] = i18n("Auto Label");
+ m_propDesc["foregroundLabelColor"] = i18n("Label Text Color");
+ m_propDesc["backgroundLabelColor"] = i18n("(a property name, keep the text narrow!)",
+ "Label Background\nColor");
+
+ m_propDesc["labelPosition"] = i18n("Label Position");
+ m_propValDesc["Left"] = i18n("Label Position", "Left");
+ m_propValDesc["Top"] = i18n("Label Position", "Top");
+ m_propValDesc["NoLabel"] = i18n("Label Position", "No Label");
+
+ m_propDesc["sizeInternal"] = i18n("Size");
+// m_propDesc["pixmap"] = i18n("Image");
+ m_propDesc["pixmapId"] = i18n("Image");
+ m_propDesc["scaledContents"] = i18n("Scaled Contents");
+ m_propDesc["keepAspectRatio"] = i18n("Keep Aspect Ratio (short)", "Keep Ratio");
+
+ //hide classes that are replaced by db-aware versions
+ hideClass("KexiPictureLabel");
+ hideClass("KComboBox");
+
+ //used in labels, frames...
+ m_propDesc["frameColor"] = i18n("Frame Color");
+ m_propDesc["dropDownButtonVisible"] =
+ i18n("Drop-Down Button for Image Box Visible (a property name, keep the text narrow!)",
+ "Drop-Down\nButton Visible");
+
+ //for checkbox
+ m_propValDesc["TristateDefault"] = i18n("Tristate checkbox, default", "Default");
+ m_propValDesc["TristateOn"] = i18n("Tristate checkbox, yes", "Yes");
+ m_propValDesc["TristateOff"] = i18n("Tristate checkbox, no", "No");
+
+ //for combobox
+ m_propDesc["editable"] = i18n("Editable combobox", "Editable");
+}
+
+KexiDBFactory::~KexiDBFactory()
+{
+}
+
+QWidget*
+KexiDBFactory::createWidget(const QCString &c, QWidget *p, const char *n,
+ KFormDesigner::Container *container, int options)
+{
+ kexipluginsdbg << "KexiDBFactory::createWidget() " << this << endl;
+
+ QWidget *w=0;
+ QString text( container->form()->library()->textForWidgetName(n, c) );
+ const bool designMode = options & KFormDesigner::WidgetFactory::DesignViewMode;
+
+ if(c == "KexiDBSubForm")
+ w = new KexiDBSubForm(container->form(), p, n);
+ else if(c == "KexiDBLineEdit")
+ {
+ w = new KexiDBLineEdit(p, n);
+ if (designMode)
+ w->setCursor(QCursor(Qt::ArrowCursor));
+ }
+ else if(c == "KexiDBTextEdit")
+ {
+ w = new KexiDBTextEdit(p, n);
+ if (designMode)
+ w->setCursor(QCursor(Qt::ArrowCursor));
+ }
+ else if(c == "QFrame" || c == "KexiFrame")
+ {
+ w = new KexiFrame(p, n);
+ new KFormDesigner::Container(container, w, container);
+ }
+ else if(c == "KexiDBLabel")
+ w = new KexiDBLabel(text, p, n);
+#ifndef KEXI_NO_IMAGEBOX_WIDGET
+ else if(c == "KexiDBImageBox") {
+ w = new KexiDBImageBox(designMode, p, n);
+ connect(w, SIGNAL(idChanged(long)), this, SLOT(slotImageBoxIdChanged(long)));
+ }
+#endif
+#ifndef KEXI_NO_AUTOFIELD_WIDGET
+ else if(c == "KexiDBAutoField")
+ w = new KexiDBAutoField(p, n, designMode);
+#endif
+ else if(c == "KexiDBCheckBox")
+ w = new KexiDBCheckBox(text, p, n);
+ else if(c == "KexiDBComboBox")
+ w = new KexiDBComboBox(p, n, designMode);
+/* else if(c == "KexiDBTimeEdit")
+ w = new KexiDBTimeEdit(QTime::currentTime(), p, n);
+ else if(c == "KexiDBDateEdit")
+ w = new KexiDBDateEdit(QDate::currentDate(), p, n);
+ else if(c == "KexiDBDateTimeEdit")
+ w = new KexiDBDateTimeEdit(QDateTime::currentDateTime(), p, n);*/
+// else if(c == "KexiDBIntSpinBox")
+// w = new KexiDBIntSpinBox(p, n);
+// else if(c == "KexiDBDoubleSpinBox")
+// w = new KexiDBDoubleSpinBox(p, n);
+ else if(c == "KPushButton" || c == "KexiPushButton")
+ w = new KexiPushButton(text, p, n);
+
+ return w;
+}
+
+bool
+KexiDBFactory::createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *)
+{
+ if(classname == "QPushButton" || classname == "KPushButton" || classname == "KexiPushButton")
+ {
+/*! @todo also call createMenuActions() for inherited factory! */
+ m_assignAction->plug( menu );
+ return true;
+ }
+ else if(classname == "KexiDBImageBox")
+ {
+ KexiDBImageBox *imageBox = static_cast<KexiDBImageBox*>(w);
+ imageBox->contextMenu()->updateActionsAvailability();
+ KActionCollection *ac = imageBox->contextMenu()->actionCollection();
+ KPopupMenu *subMenu = new KPopupMenu();
+//! @todo make these actions undoable/redoable
+ menu->insertItem(i18n("&Image"), subMenu);
+ ac->action("insert")->plug(subMenu);
+ ac->action("file_save_as")->plug(subMenu);
+ subMenu->insertSeparator();
+ ac->action("edit_cut")->plug(subMenu);
+ ac->action("edit_copy")->plug(subMenu);
+ ac->action("edit_paste")->plug(subMenu);
+ ac->action("delete")->plug(subMenu);
+ if (ac->action("properties")) {
+ subMenu->insertSeparator();
+ ac->action("properties")->plug(subMenu);
+ }
+ }
+ return false;
+}
+
+void
+KexiDBFactory::createCustomActions(KActionCollection* col)
+{
+ //this will create shared instance action for design mode (special collection is provided)
+ m_assignAction = new KAction( i18n("&Assign Action..."), SmallIconSet("form_action"),
+ 0, 0, 0, col, "widget_assign_action");
+}
+
+bool
+KexiDBFactory::startEditing(const QCString &classname, QWidget *w, KFormDesigner::Container *container)
+{
+ m_container = container;
+ if(classname == "KexiDBLineEdit")
+ {
+//! @todo this code should not be copied here but
+//! just inherited StdWidgetFactory::clearWidgetContent() should be called
+ KLineEdit *lineedit = static_cast<KLineEdit*>(w);
+ createEditor(classname, lineedit->text(), lineedit, container,
+ lineedit->geometry(), lineedit->alignment(), true);
+ return true;
+ }
+ if(classname == "KexiDBTextEdit")
+ {
+//! @todo this code should not be copied here but
+//! just inherited StdWidgetFactory::clearWidgetContent() should be called
+ KTextEdit *textedit = static_cast<KTextEdit*>(w);
+ createEditor(classname, textedit->text(), textedit, container,
+ textedit->geometry(), textedit->alignment(), true, true);
+ //copy a few properties
+ KTextEdit *ed = dynamic_cast<KTextEdit *>( editor(w) );
+ ed->setWrapPolicy(textedit->wrapPolicy());
+ ed->setWordWrap(textedit->wordWrap());
+ ed->setTabStopWidth(textedit->tabStopWidth());
+ ed->setWrapColumnOrWidth(textedit->wrapColumnOrWidth());
+ ed->setLinkUnderline(textedit->linkUnderline());
+ ed->setTextFormat(textedit->textFormat());
+ ed->setHScrollBarMode(textedit->hScrollBarMode());
+ ed->setVScrollBarMode(textedit->vScrollBarMode());
+ return true;
+ }
+ else if ( classname == "KexiDBLabel" ) {
+ KexiDBLabel *label = static_cast<KexiDBLabel*>(w);
+ m_widget = w;
+ if(label->textFormat() == RichText)
+ {
+ QString text = label->text();
+ if ( editRichText( label, text ) )
+ {
+ changeProperty( "textFormat", "RichText", container->form() );
+ changeProperty( "text", text, container->form() );
+ }
+
+ if ( classname == "KexiDBLabel" )
+ w->resize(w->sizeHint());
+ }
+ else
+ {
+ createEditor(classname, label->text(), label, container,
+ label->geometry(), label->alignment(),
+ false, label->alignment() & Qt::WordBreak /*multiline*/);
+ }
+ return true;
+ }
+ else if (classname == "KexiDBSubForm") {
+ // open the form in design mode
+ KexiMainWindow *mainWin = KexiUtils::findParent<KexiMainWindow>(w, "KexiMainWindow");
+ KexiDBSubForm *subform = static_cast<KexiDBSubForm*>(w);
+ if(mainWin) {
+ bool openingCancelled;
+ mainWin->openObject("kexi/form", subform->formName(), Kexi::DesignViewMode,
+ openingCancelled);
+ }
+ return true;
+ }
+#if 0
+ else if( (classname == "KexiDBDateEdit") || (classname == "KexiDBDateTimeEdit") || (classname == "KexiDBTimeEdit")
+ /*|| (classname == "KexiDBIntSpinBox") || (classname == "KexiDBDoubleSpinBox")*/ ) {
+ disableFilter(w, container);
+ return true;
+ }
+#endif
+ else if(classname == "KexiDBAutoField") {
+ if(static_cast<KexiDBAutoField*>(w)->hasAutoCaption())
+ return false; // caption is auto, abort editing
+ QLabel *label = static_cast<KexiDBAutoField*>(w)->label();
+ createEditor(classname, label->text(), label, container, label->geometry(), label->alignment());
+ return true;
+ }
+ else if (classname == "KexiDBCheckBox") {
+ KexiDBCheckBox *cb = static_cast<KexiDBCheckBox*>(w);
+ QRect r( cb->geometry() );
+ r.setLeft( r.left() + 2 + cb->style().subRect( QStyle::SR_CheckBoxIndicator, cb ).width() );
+ createEditor(classname, cb->text(), cb, container, r, Qt::AlignAuto);
+ return true;
+ }
+ else if(classname == "KexiDBImageBox") {
+ KexiDBImageBox *image = static_cast<KexiDBImageBox*>(w);
+ image->insertFromFile();
+ return true;
+ }
+ return false;
+}
+
+bool
+KexiDBFactory::previewWidget(const QCString &, QWidget *, KFormDesigner::Container *)
+{
+ return false;
+}
+
+bool
+KexiDBFactory::clearWidgetContent(const QCString & /*classname*/, QWidget *w)
+{
+//! @todo this code should not be copied here but
+//! just inherited StdWidgetFactory::clearWidgetContent() should be called
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>(w);
+ if(iface)
+ iface->clear();
+ return true;
+}
+
+QValueList<QCString>
+KexiDBFactory::autoSaveProperties(const QCString & /*classname*/)
+{
+ QValueList<QCString> lst;
+// if(classname == "KexiDBSubForm")
+ //lst << "formName";
+// if(classname == "KexiDBLineEdit")
+// lst += "dataSource";
+// if(classname == "KexiDBAutoField")
+// lst << "labelCaption";
+ return lst;
+}
+
+bool
+KexiDBFactory::isPropertyVisibleInternal(const QCString& classname, QWidget *w,
+ const QCString& property, bool isTopLevel)
+{
+ //general
+ if (property=="dataSource" || property=="dataSourceMimeType") {
+ return false; //force
+ }
+
+ bool ok = true;
+
+ if(classname == "KexiPushButton") {
+ ok = property!="isDragEnabled"
+#ifdef KEXI_NO_UNFINISHED
+ && property!="onClickAction" /*! @todo reenable */
+ && property!="onClickActionOption" /*! @todo reenable */
+ && property!="iconSet" /*! @todo reenable */
+ && property!="stdItem" /*! @todo reenable stdItem */
+#endif
+ ;
+ }
+ else if(classname == "KexiDBLineEdit")
+ ok = property!="urlDropsEnabled"
+ && property!="vAlign"
+#ifdef KEXI_NO_UNFINISHED
+ && property!="inputMask"
+ && property!="maxLength" //!< we may want to integrate this with db schema
+#endif
+ ;
+ else if(classname == "KexiDBComboBox")
+ ok = property!="autoCaption"
+ && property!="labelPosition"
+ && property!="widgetType"
+ && property!="fieldTypeInternal"
+ && property!="fieldCaptionInternal"; //hide properties that come with KexiDBAutoField
+ else if(classname == "KexiDBTextEdit")
+ ok = property!="undoDepth"
+ && property!="undoRedoEnabled" //always true!
+ && property!="dragAutoScroll" //always true!
+ && property!="overwriteMode" //always false!
+ && property!="resizePolicy"
+ && property!="autoFormatting" //too complex
+#ifdef KEXI_NO_UNFINISHED
+ && property!="paper"
+#endif
+ ;
+ else if(classname == "KexiDBSubForm")
+ ok = property!="dragAutoScroll"
+ && property!="resizePolicy"
+ && property!="focusPolicy";
+ else if(classname == "KexiDBForm")
+ ok = property!="iconText"
+ && property!="geometry" /*nonsense for toplevel widget; for size, "size" property is used*/;
+ else if(classname == "KexiDBLabel")
+ ok = property!="focusPolicy";
+ else if(classname == "KexiDBAutoField") {
+ if (!isTopLevel && property=="caption")
+ return true; //force
+ if (property=="fieldTypeInternal" || property=="fieldCaptionInternal"
+//! @todo unhide in 2.0
+ || property=="widgetType")
+ return false;
+ ok = property!="text"; /* "text" is not needed as "caption" is used instead */
+ }
+ else if (classname == "KexiDBImageBox") {
+ ok = property!="font" && property!="wordbreak";
+ }
+ else if(classname == "KexiDBCheckBox") {
+ //hide text property if the widget is a child of an autofield beause there's already "caption" for this purpose
+ if (property=="text" && w && dynamic_cast<KFormDesigner::WidgetWithSubpropertiesInterface*>(w->parentWidget()))
+ return false;
+ ok = property!="autoRepeat";
+ }
+
+ return ok && WidgetFactory::isPropertyVisibleInternal(classname, w, property, isTopLevel);
+}
+
+bool
+KexiDBFactory::propertySetShouldBeReloadedAfterPropertyChange(const QCString& classname,
+ QWidget *w, const QCString& property)
+{
+ Q_UNUSED(classname);
+ Q_UNUSED(w);
+ if (property=="fieldTypeInternal" || property=="widgetType")
+ return true;
+ return false;
+}
+
+bool
+KexiDBFactory::changeText(const QString &text)
+{
+ KFormDesigner::Form *form = m_container ? m_container->form() : 0;
+ if (!form)
+ return false;
+ if (!form->selectedWidget())
+ return false;
+ QCString n( form->selectedWidget()->className() );
+// QWidget *w = WidgetFactory::widget();
+ if(n == "KexiDBAutoField") {
+ changeProperty("caption", text, form);
+ return true;
+ }
+ //! \todo check field's geometry
+ return false;
+}
+
+void
+KexiDBFactory::resizeEditor(QWidget *editor, QWidget *w, const QCString &classname)
+{
+ //QSize s = widget->size();
+ //QPoint p = widget->pos();
+
+ if(classname == "KexiDBAutoField")
+ editor->setGeometry( static_cast<KexiDBAutoField*>(w)->label()->geometry() );
+}
+
+void
+KexiDBFactory::slotImageBoxIdChanged(KexiBLOBBuffer::Id_t id)
+{
+//old KexiFormView *formView = KexiUtils::findParent<KexiFormView>((QWidget*)m_widget, "KexiFormView");
+
+ // (js) heh, porting to KFormDesigner::FormManager::self() singleton took me entire day of work...
+ KFormDesigner::Form *form = KFormDesigner::FormManager::self()->activeForm();
+ KexiFormView *formView = form ? KexiUtils::findParent<KexiFormView>((QWidget*)form->widget(), "KexiFormView") : 0;
+ if (formView) {
+ changeProperty("pixmapId", (uint)/*! @todo unsafe */id, form);
+//old formView->setUnsavedLocalBLOB(m_widget, id);
+ formView->setUnsavedLocalBLOB(form->selectedWidget(), id);
+ }
+}
+
+KFORMDESIGNER_WIDGET_FACTORY(KexiDBFactory, kexidbwidgets)
+
+#include "kexidbfactory.moc"
diff --git a/kexi/plugins/forms/kexidbfactory.h b/kexi/plugins/forms/kexidbfactory.h
new file mode 100644
index 000000000..6064a0011
--- /dev/null
+++ b/kexi/plugins/forms/kexidbfactory.h
@@ -0,0 +1,74 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDBFACTORY_H
+#define KEXIDBFACTORY_H
+
+#include <formeditor/widgetfactory.h>
+
+class KAction;
+
+namespace KFormDesigner {
+ class Form;
+ class FormManager;
+}
+
+//! Kexi Factory (DB widgets + subform)
+class KexiDBFactory : public KFormDesigner::WidgetFactory
+{
+ Q_OBJECT
+
+ public:
+ KexiDBFactory(QObject *parent, const char *name, const QStringList &args);
+ virtual ~KexiDBFactory();
+
+ virtual QWidget *createWidget(const QCString &classname, QWidget *parent, const char *name,
+ KFormDesigner::Container *container, int options = DefaultOptions );
+
+ virtual void createCustomActions(KActionCollection* col);
+ virtual bool createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *container);
+ virtual bool startEditing(const QCString &classname, QWidget *w, KFormDesigner::Container *container);
+ virtual bool previewWidget(const QCString &, QWidget *, KFormDesigner::Container *);
+ virtual bool clearWidgetContent(const QCString &classname, QWidget *w);
+
+ //virtual void saveSpecialProperty(const QString &classname, const QString &name, const QVariant &value, QWidget *w,
+ //QDomElement &parentNode, QDomDocument &parent) {}
+ //virtual void readSpecialProperty(const QCString &classname, QDomElement &node, QWidget *w, KFormDesigner::ObjectTreeItem *item) {}
+ virtual QValueList<QCString> autoSaveProperties(const QCString &classname);
+
+ protected slots:
+ void slotImageBoxIdChanged(long id); /*KexiBLOBBuffer::Id_t*/
+
+ protected:
+ virtual bool changeText(const QString &newText);
+ virtual void resizeEditor(QWidget *editor, QWidget *widget, const QCString &classname);
+
+ virtual bool isPropertyVisibleInternal(const QCString& classname, QWidget *w,
+ const QCString& property, bool isTopLevel);
+
+ //! Sometimes property sets should be reloaded when a given property value changed.
+ virtual bool propertySetShouldBeReloadedAfterPropertyChange(const QCString& classname, QWidget *w,
+ const QCString& property);
+
+ KAction* m_assignAction;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexidbtextwidgetinterface.cpp b/kexi/plugins/forms/kexidbtextwidgetinterface.cpp
new file mode 100644
index 000000000..47eabe9dd
--- /dev/null
+++ b/kexi/plugins/forms/kexidbtextwidgetinterface.cpp
@@ -0,0 +1,71 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbtextwidgetinterface.h"
+#include "kexiformdataiteminterface.h"
+#include <kexidb/queryschema.h>
+#include <kexiutils/utils.h>
+#include <qframe.h>
+#include <qpainter.h>
+
+KexiDBTextWidgetInterface::KexiDBTextWidgetInterface()
+ : m_autonumberDisplayParameters(0)
+{
+}
+
+KexiDBTextWidgetInterface::~KexiDBTextWidgetInterface()
+{
+ delete m_autonumberDisplayParameters;
+}
+
+void KexiDBTextWidgetInterface::setColumnInfo(KexiDB::QueryColumnInfo* cinfo, QWidget *w)
+{
+ if (cinfo->field->isAutoIncrement()) {
+ if (!m_autonumberDisplayParameters)
+ m_autonumberDisplayParameters = new KexiDisplayUtils::DisplayParameters();
+ KexiDisplayUtils::initDisplayForAutonumberSign(*m_autonumberDisplayParameters, w);
+ }
+}
+
+void KexiDBTextWidgetInterface::paint( QFrame *w, QPainter* p, bool textIsEmpty, int alignment, bool hasFocus )
+{
+ KexiFormDataItemInterface *dataItemIface = dynamic_cast<KexiFormDataItemInterface*>(w);
+ KexiDB::QueryColumnInfo *columnInfo = dataItemIface ? dataItemIface->columnInfo() : 0;
+ if (columnInfo && columnInfo->field && dataItemIface->cursorAtNewRow() && textIsEmpty) {
+ const int margin = w->lineWidth() + w->midLineWidth();
+ if (columnInfo->field->isAutoIncrement() && m_autonumberDisplayParameters) {
+ if (w->hasFocus()) {
+ p->setPen(
+ KexiUtils::blendedColors(
+ m_autonumberDisplayParameters->textColor, w->palette().active().base(), 1, 3));
+ }
+ KexiDisplayUtils::paintAutonumberSign(*m_autonumberDisplayParameters, p,
+ 2 + margin + w->margin(), margin, w->width() - margin*2 -2-2,
+ w->height() - margin*2 -2, alignment, hasFocus);
+ }
+ }
+}
+
+void KexiDBTextWidgetInterface::event( QEvent * e, QWidget *w, bool textIsEmpty )
+{
+ if (e->type()==QEvent::FocusIn || e->type()==QEvent::FocusOut) {
+ if (m_autonumberDisplayParameters && textIsEmpty)
+ w->repaint();
+ }
+}
diff --git a/kexi/plugins/forms/kexidbtextwidgetinterface.h b/kexi/plugins/forms/kexidbtextwidgetinterface.h
new file mode 100644
index 000000000..ca10fc4e0
--- /dev/null
+++ b/kexi/plugins/forms/kexidbtextwidgetinterface.h
@@ -0,0 +1,53 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBTextWidgetInterface_H
+#define KexiDBTextWidgetInterface_H
+
+#include <widget/utils/kexidisplayutils.h>
+
+namespace KexiDB {
+ class QueryColumnInfo;
+}
+class QFrame;
+
+//! @short An interface providing common text editor's functionality
+/*! Widgets (e.g. KexiDBLineEdit, KexiDBTextEdit) implementing KexiFormDataItemInterface
+ use this interface to customize painting and data handling. */
+class KEXIFORMUTILS_EXPORT KexiDBTextWidgetInterface
+{
+ public:
+ KexiDBTextWidgetInterface();
+ ~KexiDBTextWidgetInterface();
+
+ //! Called from KexiFormDataItemInterface::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) implementation.
+ void setColumnInfo(KexiDB::QueryColumnInfo* cinfo, QWidget *w);
+
+ //! Called from paintEvent( QPaintEvent *pe ) method of the data aware widget.
+ void paint( QFrame *w, QPainter *p, bool textIsEmpty, int alignment, bool hasFocus );
+
+ //! Called from event( QEvent * e ) method of the data aware widget.
+ void event( QEvent * e, QWidget *w, bool textIsEmpty );
+
+ protected:
+ //! parameters for displaying autonumber sign
+ KexiDisplayUtils::DisplayParameters *m_autonumberDisplayParameters;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexiformdataiteminterface.cpp b/kexi/plugins/forms/kexiformdataiteminterface.cpp
new file mode 100644
index 000000000..c87a2dabf
--- /dev/null
+++ b/kexi/plugins/forms/kexiformdataiteminterface.cpp
@@ -0,0 +1,68 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexiformdataiteminterface.h"
+#include "kexiformscrollview.h"
+#include <kexidb/queryschema.h>
+#include <kexiutils/utils.h>
+
+KexiFormDataItemInterface::KexiFormDataItemInterface()
+ : KexiDataItemInterface()
+ , m_columnInfo(0)
+ , m_displayParametersForEnteredValue(0)
+ , m_displayParametersForDefaultValue(0)
+ , m_displayDefaultValue(false)
+{
+}
+
+KexiFormDataItemInterface::~KexiFormDataItemInterface()
+{
+ delete m_displayParametersForEnteredValue;
+ delete m_displayParametersForDefaultValue;
+}
+
+void KexiFormDataItemInterface::undoChanges()
+{
+// m_disable_signalValueChanged = true;
+ setValueInternal(QString::null, false);
+// m_disable_signalValueChanged = false;
+}
+
+KexiDB::Field* KexiFormDataItemInterface::field() const
+{
+ return m_columnInfo ? m_columnInfo->field : 0;
+}
+
+void KexiFormDataItemInterface::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue)
+{
+ m_displayDefaultValue = displayDefaultValue;
+ if (!m_displayParametersForDefaultValue) {
+ m_displayParametersForEnteredValue = new KexiDisplayUtils::DisplayParameters(widget);
+ m_displayParametersForDefaultValue = new KexiDisplayUtils::DisplayParameters();
+ KexiDisplayUtils::initDisplayForDefaultValue(*m_displayParametersForDefaultValue, widget);
+ }
+}
+
+void KexiFormDataItemInterface::cancelEditor()
+{
+ QWidget *parentWidget = dynamic_cast<QWidget*>(this)->parentWidget();
+ KexiFormScrollView* view = KexiUtils::findParent<KexiFormScrollView>(parentWidget, "KexiFormScrollView");
+ if (view)
+ view->cancelEditor();
+}
diff --git a/kexi/plugins/forms/kexiformdataiteminterface.h b/kexi/plugins/forms/kexiformdataiteminterface.h
new file mode 100644
index 000000000..99d20db4e
--- /dev/null
+++ b/kexi/plugins/forms/kexiformdataiteminterface.h
@@ -0,0 +1,145 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIFORMDATAITEMINTERFACE_H
+#define KEXIFORMDATAITEMINTERFACE_H
+
+#include <widget/utils/kexidisplayutils.h>
+#include <kexidataiteminterface.h>
+#include <qwidget.h>
+
+namespace KexiDB {
+ class Field;
+}
+
+//! An interface for declaring form widgets to be data-aware.
+class KEXIFORMUTILS_EXPORT KexiFormDataItemInterface : public KexiDataItemInterface
+{
+ public:
+ KexiFormDataItemInterface();
+ virtual ~KexiFormDataItemInterface();
+
+ //! \return the name of the data source for this widget.
+ //! Data source usually means here a table or query, a field name or an expression.
+ inline QString dataSource() const { return m_dataSource; }
+
+ //! Sets the name of the data source for this widget.
+ //! Data source usually means here a table or query or field name name.
+ inline void setDataSource(const QString &ds) { m_dataSource = ds; }
+
+ /*! \return the mime type of the data source for this widget.
+ Data source mime type means here types like "kexi/table" or "kexi/query"
+ in.the data source is set to object (as within form or subform) or is empty
+ if the data source is set to table field or query column. */
+ inline QCString dataSourceMimeType() const { return m_dataSourceMimeType; }
+
+ /*! Sets the mime type of the data source for this widget.
+ Data source usually means here a "kexi/table" or "kexi/query".
+ @see dataSourceMimeType() */
+ inline void setDataSourceMimeType(const QCString &ds) { m_dataSourceMimeType = ds; }
+
+ /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue()
+ is displayed in a special way. Used by KexiFormDataProvider::fillDataItems().
+ \a widget is equal to 'this'.
+ You can reimplement this in the widget. Always call the superclass' implementation.
+ setDisplayDefaultValue(.., false) is called in KexiFormScrollView::valueChanged()
+ as a response on data change performed by user. */
+ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue);
+
+ /*! \return true if default value is displayed for this item. */
+ virtual bool hasDisplayedDefaultValue() const { return m_displayDefaultValue; }
+
+ /*! Convenience function: casts this item to a QWidget.
+ Can return 0 if the item is not a QWidget-derived object. */
+ virtual QWidget* widget() { return dynamic_cast<QWidget*>(this); }
+
+ /*! Sets 'invalid' state, e.g. a text editor widget should display
+ text \a displayText and become read only to prevent entering data,
+ because updating at the database backend is not available.
+ \a displayText is usually set to something i18n'd like "#NAME?".
+ Note: that even widgets that usualy do not display texts (e.g. pixmaps)
+ should display \a displayText too.
+ */
+ virtual void setInvalidState( const QString& displayText ) = 0;
+
+ /*! Changes 'read only' flag, for this widget.
+ Typically this flag can be passed to a widget itself,
+ e.g. KLineEdit::setReadOnly(bool). */
+ virtual void setReadOnly( bool readOnly ) = 0;
+
+ //! \return database column information for this item
+ virtual KexiDB::Field* field() const;
+
+ //! \return database column information for this item
+ virtual KexiDB::QueryColumnInfo* columnInfo() const { return m_columnInfo; }
+
+ /*! Used internally to set database column information.
+ Reimplement if you need to do additional actions,
+ e.g. set data validator based on field type. Don't forget about
+ calling superclass implementation. */
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo) { m_columnInfo = cinfo; }
+
+ /*! Used internally to set visible database column information.
+ Reimplemented in KexiDBComboBox: except for combo box, this does nothing. */
+ virtual void setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo) { Q_UNUSED(cinfo); }
+
+ /*! \return visible database column information for this item.
+ Except for combo box, this is exactly the same as columnInfo(). */
+ virtual KexiDB::QueryColumnInfo* visibleColumnInfo() const { return columnInfo(); }
+
+ /*! Does nothing, because within forms, widgets are always visible. */
+ virtual void hideWidget() { }
+
+ /*! Does nothing, because within forms, widgets are always visible. */
+ virtual void showWidget() { }
+
+ /*! Undoes changes made to this item - just resets the widget to original value.
+ Note: This is internal method called by KexiFormScrollView::cancelEditor().
+ To cancel editing of the widget's data from the widget's code,
+ use KexiFormDataItemInterface::cancelEditor().
+ Reimplemented in KexiDBComboBox to also revert the visible value (i.e. text) to the original state.
+ */
+ virtual void undoChanges();
+
+ /* Cancels editing of the widget's data. This method just looks for
+ the (grand)parent KexiFormScrollView object and calls
+ KexiFormScrollView::cancelEditor(). */
+ void cancelEditor();
+
+ /*! @internal
+ Called by top-level form on key press event.
+ Default implementation does nothing.
+ Implement this if you want to handle key presses from within the editor widget item.
+ \return true if \a ke should be accepted by the widget item.
+ This method is used e.g. in KexiDBImageBox for Key_Escape to if the popup is visible,
+ so the key press won't be consumed to perform "cancel editing". */
+ virtual bool keyPressed(QKeyEvent *ke) { Q_UNUSED(ke); return false; };
+
+ protected:
+ QString m_dataSource;
+ QCString m_dataSourceMimeType;
+ KexiDB::QueryColumnInfo* m_columnInfo;
+ KexiDisplayUtils::DisplayParameters *m_displayParametersForEnteredValue; //!< used in setDisplayDefaultValue()
+ KexiDisplayUtils::DisplayParameters *m_displayParametersForDefaultValue; //!< used in setDisplayDefaultValue()
+ bool m_displayDefaultValue : 1; //!< used by setDisplayDefaultValue()
+
+ friend class KexiDBAutoField;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexiformeventhandler.cpp b/kexi/plugins/forms/kexiformeventhandler.cpp
new file mode 100644
index 000000000..01bca2018
--- /dev/null
+++ b/kexi/plugins/forms/kexiformeventhandler.cpp
@@ -0,0 +1,188 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiformeventhandler.h"
+
+#include <qwidget.h>
+#include <qobjectlist.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <tableview/kexitableitem.h>
+#include <tableview/kexitableviewdata.h>
+#include <kexidb/queryschema.h>
+#include <keximainwindow.h>
+#include <kexidialogbase.h>
+#include <kexipart.h>
+#include <kexipartinfo.h>
+#include <kexipartitem.h>
+
+KexiFormEventAction::ActionData::ActionData()
+{
+}
+
+bool KexiFormEventAction::ActionData::isEmpty() const
+{
+ return string.isEmpty();
+}
+
+KexiPart::Info* KexiFormEventAction::ActionData::decodeString(
+ QString& actionType, QString& actionArg, bool& ok) const
+{
+ const int idx = string.find(':');
+ ok = false;
+ if (idx==-1)
+ return 0;
+ const QString _actionType = string.left(idx);
+ const QString _actionArg = string.mid(idx+1);
+ if (_actionType.isEmpty() || _actionArg.isEmpty())
+ return 0;
+ KexiPart::Info *info = 0;
+ if (_actionType!="kaction" && _actionType!="currentForm") {
+ info = Kexi::partManager().infoForMimeType( QString("kexi/%1").arg(_actionType) );
+ if (!info)
+ return 0;
+ }
+ actionType = _actionType;
+ actionArg = _actionArg;
+ ok = true;
+ return info;
+}
+
+//-------------------------------------
+
+KexiFormEventAction::KexiFormEventAction(KexiMainWindow *mainWin, QObject* parent,
+ const QString& actionName, const QString& objectName, const QString& actionOption)
+ : KAction(parent), m_mainWin(mainWin), m_actionName(actionName), m_objectName(objectName)
+ , m_actionOption(actionOption)
+{
+}
+
+KexiFormEventAction::~KexiFormEventAction()
+{
+}
+
+void KexiFormEventAction::activate()
+{
+ KexiProject* project = m_mainWin->project();
+ if (!project)
+ return;
+ KexiPart::Part* part = Kexi::partManager().partForMimeType(
+ QString("kexi/%1").arg(m_actionName) );
+ if (!part)
+ return;
+ KexiPart::Item* item = project->item( part->info(), m_objectName );
+ if (!item)
+ return;
+ bool actionCancelled = false;
+ if (m_actionOption.isEmpty()) { // backward compatibility (good defaults)
+ if (part->info()->isExecuteSupported())
+ part->execute(item, parent());
+ else
+ m_mainWin->openObject(item, Kexi::DataViewMode, actionCancelled);
+ }
+ else {
+//! @todo react on failure...
+ if (m_actionOption == "open")
+ m_mainWin->openObject(item, Kexi::DataViewMode, actionCancelled);
+ else if (m_actionOption == "execute")
+ part->execute(item, parent());
+ else if (m_actionOption == "print") {
+ if (part->info()->isPrintingSupported())
+ m_mainWin->printItem(item);
+ }
+ else if (m_actionOption == "printPreview") {
+ if (part->info()->isPrintingSupported())
+ m_mainWin->printPreviewForItem(item);
+ }
+ else if (m_actionOption == "pageSetup") {
+ if (part->info()->isPrintingSupported())
+ m_mainWin->showPageSetupForItem(item);
+ }
+ else if (m_actionOption == "exportToCSV"
+ || m_actionOption == "copyToClipboardAsCSV")
+ {
+ if (part->info()->isDataExportSupported())
+ m_mainWin->executeCustomActionForObject(item, m_actionOption);
+ }
+ else if (m_actionOption == "new")
+ m_mainWin->newObject( part->info(), actionCancelled );
+ else if (m_actionOption == "design")
+ m_mainWin->openObject(item, Kexi::DesignViewMode, actionCancelled);
+ else if (m_actionOption == "editText")
+ m_mainWin->openObject(item, Kexi::TextViewMode, actionCancelled);
+ else if (m_actionOption == "close") {
+ tristate res = m_mainWin->closeObject(item);
+ if (~res)
+ actionCancelled = true;
+ }
+ }
+}
+
+//------------------------------------------
+
+KexiFormEventHandler::KexiFormEventHandler()
+ : m_mainWidget(0)
+{
+}
+
+KexiFormEventHandler::~KexiFormEventHandler()
+{
+}
+
+void KexiFormEventHandler::setMainWidgetForEventHandling(KexiMainWindow *mainWin, QWidget* mainWidget)
+{
+ m_mainWidget = mainWidget;
+ if (!m_mainWidget)
+ return;
+
+ //find widgets whose will work as data items
+//! @todo look for other widgets too
+ QObjectList *l = m_mainWidget->queryList( "KexiPushButton" );
+ QObjectListIt it( *l );
+ QObject *obj;
+ for ( ; (obj = it.current()) != 0; ++it ) {
+ bool ok;
+ KexiFormEventAction::ActionData data;
+ data.string = obj->property("onClickAction").toString();
+ data.option = obj->property("onClickActionOption").toString();
+ if (data.isEmpty())
+ continue;
+
+ QString actionType, actionArg;
+ KexiPart::Info* partInfo = data.decodeString(actionType, actionArg, ok);
+ if (!ok)
+ continue;
+ if (actionType=="kaction" || actionType=="currentForm") {
+ KAction *action = mainWin->actionCollection()->action( actionArg.latin1() );
+ if (!action)
+ continue;
+ QObject::disconnect( obj, SIGNAL(clicked()), action, SLOT(activate()) ); //safety
+ QObject::connect( obj, SIGNAL(clicked()), action, SLOT(activate()) );
+ }
+ else if (partInfo) { //'open or execute' action
+ KexiFormEventAction* action = new KexiFormEventAction(mainWin, obj, actionType, actionArg,
+ data.option);
+ QObject::disconnect( obj, SIGNAL(clicked()), action, SLOT(activate()) );
+ QObject::connect( obj, SIGNAL(clicked()), action, SLOT(activate()) );
+ }
+ }
+ delete l;
+}
diff --git a/kexi/plugins/forms/kexiformeventhandler.h b/kexi/plugins/forms/kexiformeventhandler.h
new file mode 100644
index 000000000..e92e9ff9c
--- /dev/null
+++ b/kexi/plugins/forms/kexiformeventhandler.h
@@ -0,0 +1,101 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFORMEVENTHANDLER_H
+#define KEXIFORMEVENTHANDLER_H
+
+#include <qwidget.h>
+#include <kaction.h>
+
+class KexiMainWindow;
+namespace KexiPart {
+ class Info;
+}
+
+//! The KexiFormEventHandler class handles events defined within Kexi Forms
+/*! For now only "onClickAction" property of Push Button widget is handled:
+ It's possible to connect this event to predefined global action.
+
+ Note: This interface will be extended in the future!
+
+ @see KexiFormPart::slotAssignAction()
+ */
+class KEXIFORMUTILS_EXPORT KexiFormEventHandler
+{
+ public:
+ KexiFormEventHandler();
+ virtual ~KexiFormEventHandler();
+
+ /*! Sets \a mainWidget to be a main widget for this handler.
+ Also find widgets having action assigned and connects them
+ to appropriate actions.
+ For now, all of them must be KexiPushButton).
+ \a mainWin is used to get action list. */
+ void setMainWidgetForEventHandling(KexiMainWindow *mainWin, QWidget* mainWidget);
+
+ protected:
+ QWidget *m_mainWidget;
+};
+
+//! @internal form-level action for handling "on click" actions
+class KEXIFORMUTILS_EXPORT KexiFormEventAction : public KAction
+{
+ public:
+ //! A structure used in currentActionName()
+ class KEXIFORMUTILS_EXPORT ActionData
+ {
+ public:
+ ActionData();
+
+ /*! Decodes action string into action type/action argument parts.
+ Action string has to be in a form of "actiontype:actionarg"
+ - Action type is passed to \a actionType on success. Action type can be "kaction"
+ or any of the part names (see KexiPart::Info::objectName()), e.g. "table", "query", etc.
+ - Action argument can be an action name in case of "kaction" type or object name
+ in case of action of type "table", "query", etc.
+ \a ok is set to true on success and to false on failure. On failure no other
+ values are passed.
+ \return part info if action type is "table", "query", etc., or 0 for "kaction" type. */
+ KexiPart::Info* decodeString(QString& actionType, QString& actionArg, bool& ok) const;
+
+ //! \return true if the action is empty
+ bool isEmpty() const;
+
+ QString string; //!< action string with prefix, like "kaction:edit_copy" or "table:<tableName>"
+
+ QString option; //!< option used when name is "table/query/etc.:\<objectName\>" is set;
+ //!< can be set to "open", "design", "editText", etc.
+ //!< @see ActionToExecuteListView::showActionsForMimeType()
+ };
+
+ KexiFormEventAction(KexiMainWindow *mainWin, QObject* parent, const QString& actionName,
+ const QString& objectName, const QString& actionOption);
+ virtual ~KexiFormEventAction();
+
+ public slots:
+ //! Activates the action. If the object supports executing (macro, script),
+ //! it is executed; otherwise (table, query, form,...) it is opened in its data view.
+ virtual void activate();
+
+ private:
+ KexiMainWindow *m_mainWin;
+ QString m_actionName, m_objectName, m_actionOption;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexiformhandler.desktop b/kexi/plugins/forms/kexiformhandler.desktop
new file mode 100644
index 000000000..3b476daaf
--- /dev/null
+++ b/kexi/plugins/forms/kexiformhandler.desktop
@@ -0,0 +1,115 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+GenericName=Forms
+GenericName[bg]=Формуляри
+GenericName[br]=Paperennoù-reol
+GenericName[ca]=Formularis
+GenericName[cs]=Formuláře
+GenericName[cy]=Ffurflenni
+GenericName[da]=Formularer
+GenericName[de]=Formulare
+GenericName[el]=Φόρμες
+GenericName[eo]=Formularoj
+GenericName[es]=Formularios
+GenericName[et]=Vormid
+GenericName[eu]=Formularioak
+GenericName[fa]=برگه‌ها
+GenericName[fi]=Lomakkeet
+GenericName[fr]=Formulaires
+GenericName[fy]=Formulieren
+GenericName[ga]=Foirmeacha
+GenericName[gl]=Formularios
+GenericName[he]=טפסים
+GenericName[hi]=फॉर्म्स
+GenericName[hr]=Obrasci
+GenericName[hu]=Űrlapok
+GenericName[is]=Form
+GenericName[it]=Moduli
+GenericName[ja]=フォーム
+GenericName[km]=សំណុំបែបបទ
+GenericName[lt]=Formos
+GenericName[lv]=Formas
+GenericName[ms]=Borang
+GenericName[nb]=Skjema
+GenericName[nds]=Kiekwarken
+GenericName[ne]=फारमहरू
+GenericName[nn]=Skjema
+GenericName[pl]=Formularze
+GenericName[pt]=Formulários
+GenericName[pt_BR]=Formulários
+GenericName[ru]=Формы
+GenericName[se]=Skovit
+GenericName[sk]=Formuláre
+GenericName[sl]=Obrazci
+GenericName[sr]=Форме
+GenericName[sr@Latn]=Forme
+GenericName[sv]=Formulär
+GenericName[ta]=படிவங்கள்
+GenericName[tr]=Formlar
+GenericName[uk]=Форми
+GenericName[uz]=Shakllar
+GenericName[uz@cyrillic]=Шакллар
+GenericName[zh_CN]=表单
+GenericName[zh_TW]=表單
+Name=Forms
+Name[bg]=Формуляри
+Name[br]=Paperennoù-reol
+Name[ca]=Formularis
+Name[cs]=Formuláře
+Name[cy]=Ffurflenni
+Name[da]=Formularer
+Name[de]=Formulare
+Name[el]=Φόρμες
+Name[eo]=Formularoj
+Name[es]=Formularios
+Name[et]=Vormid
+Name[eu]=Formularioak
+Name[fa]=برگه‌ها
+Name[fi]=Lomakkeet
+Name[fr]=Formulaires
+Name[fy]=Formulieren
+Name[ga]=Foirmeacha
+Name[gl]=Formularios
+Name[he]=טפסים
+Name[hi]=फ़ॉर्म
+Name[hr]=Obrasci
+Name[hu]=Űrlapok
+Name[is]=Form
+Name[it]=Moduli
+Name[ja]=フォーム
+Name[km]=សំណុំបែបបទ
+Name[lt]=Formos
+Name[lv]=Formas
+Name[ms]=Borang
+Name[nb]=Skjema
+Name[nds]=Kiekwarken
+Name[ne]=फारमहरू
+Name[nn]=Skjema
+Name[pl]=Formularze
+Name[pt]=Formulários
+Name[pt_BR]=Formulários
+Name[ru]=Формы
+Name[se]=Skovit
+Name[sk]=Formuláre
+Name[sl]=Obrazci
+Name[sr]=Форме
+Name[sr@Latn]=Forme
+Name[sv]=Formulär
+Name[ta]=Kவிதி
+Name[tg]=Шаклҳо
+Name[tr]=Formlar
+Name[uk]=Форми
+Name[uz]=Shakllar
+Name[uz@cyrillic]=Шакллар
+Name[zh_CN]=表单
+Name[zh_TW]=表單
+X-KDE-Library=kexihandler_form
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=form
+X-Kexi-TypeMime=kexi/form
+X-Kexi-ItemIcon=form
+X-Kexi-SupportsDataExport=false
+X-Kexi-SupportsPrinting=false
diff --git a/kexi/plugins/forms/kexiformmanager.cpp b/kexi/plugins/forms/kexiformmanager.cpp
new file mode 100644
index 000000000..6134cfc8f
--- /dev/null
+++ b/kexi/plugins/forms/kexiformmanager.cpp
@@ -0,0 +1,235 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiformmanager.h"
+#include "widgets/kexidbform.h"
+#include "widgets/kexidbautofield.h"
+#include "kexiformscrollview.h"
+#include "kexiformview.h"
+#include "kexidatasourcepage.h"
+
+#include <formeditor/formmanager.h>
+#include <formeditor/widgetpropertyset.h>
+#include <formeditor/form.h>
+#include <formeditor/widgetlibrary.h>
+#include <formeditor/commands.h>
+#include <formeditor/objecttree.h>
+
+#include <koproperty/set.h>
+#include <koproperty/property.h>
+#include <widget/kexicustompropertyfactory.h>
+
+KexiFormManager::KexiFormManager(KexiPart::Part *parent, const char* name)
+ : KFormDesigner::FormManager(parent,
+ KFormDesigner::FormManager::HideEventsInPopupMenu |
+ KFormDesigner::FormManager::SkipFileActions |
+ KFormDesigner::FormManager::HideSignalSlotConnections
+ , name)
+ , m_part(parent)
+{
+ m_emitSelectionSignalsUpdatesPropertySet = true;
+ KexiCustomPropertyFactory::init();
+}
+
+KexiFormManager::~KexiFormManager()
+{
+}
+
+KAction* KexiFormManager::action( const char* name )
+{
+ KActionCollection *col = m_part->actionCollectionForMode(Kexi::DesignViewMode);
+ if (!col)
+ return 0;
+ QCString n( translateName( name ).latin1() );
+ KAction *a = col->action(n);
+ if (a)
+ return a;
+ KexiDBForm *dbform;
+ if (!activeForm() || !activeForm()->designMode()
+ || !(dbform = dynamic_cast<KexiDBForm*>(activeForm()->formWidget())))
+ return 0;
+ KexiFormScrollView *scrollViewWidget = dynamic_cast<KexiFormScrollView*>(dbform->dataAwareObject());
+ if (!scrollViewWidget)
+ return 0;
+ KexiFormView* formViewWidget = dynamic_cast<KexiFormView*>(scrollViewWidget->parent());
+ if (!formViewWidget)
+ return 0;
+ return formViewWidget->parentDialog()->mainWin()->actionCollection()->action(n);
+}
+
+KexiFormView* KexiFormManager::activeFormViewWidget() const
+{
+ KexiDBForm *dbform;
+ if (!activeForm() || !activeForm()->designMode()
+ || !(dbform = dynamic_cast<KexiDBForm*>(activeForm()->formWidget())))
+ return 0;
+ KexiFormScrollView *scrollViewWidget = dynamic_cast<KexiFormScrollView*>(dbform->dataAwareObject());
+ if (!scrollViewWidget)
+ return 0;
+ return dynamic_cast<KexiFormView*>(scrollViewWidget->parent());
+}
+
+void KexiFormManager::enableAction( const char* name, bool enable )
+{
+ KexiFormView* formViewWidget = activeFormViewWidget();
+ if (!formViewWidget)
+ return;
+// if (QString(name)=="layout_menu")
+// kdDebug() << "!!!!!!!!!!! " << enable << endl;
+ formViewWidget->setAvailable(translateName( name ).latin1(), enable);
+}
+
+void KexiFormManager::setFormDataSource(const QCString& mime, const QCString& name)
+{
+ if (!activeForm())
+ return;
+ KexiDBForm* formWidget = dynamic_cast<KexiDBForm*>(activeForm()->widget());
+ if (!formWidget)
+ return;
+
+// setPropertyValueInDesignMode(formWidget, "dataSource", name);
+
+ QCString oldDataSourceMimeType( formWidget->dataSourceMimeType() );
+ QCString oldDataSource( formWidget->dataSource().latin1() );
+ if (mime!=oldDataSourceMimeType || name!=oldDataSource) {
+ QMap<QCString, QVariant> propValues;
+ propValues.insert("dataSource", name);
+ propValues.insert("dataSourceMimeType", mime);
+ KFormDesigner::CommandGroup *group
+ = new KFormDesigner::CommandGroup(i18n("Set Form's Data Source to \"%1\"").arg(name), propertySet());
+ propertySet()->createPropertyCommandsInDesignMode(formWidget, propValues, group, true /*addToActiveForm*/);
+ }
+
+/*
+ if (activeForm()->selectedWidget() == formWidget) {
+ //active form is selected: just use properties system
+ KFormDesigner::WidgetPropertySet *set = propertySet();
+ if (!set || !set->contains("dataSource"))
+ return;
+ (*set)["dataSource"].setValue(name);
+ if (set->contains("dataSourceMimeType"))
+ (*set)["dataSourceMimeType"].setValue(mime);
+ return;
+ }
+
+ //active form isn't selected: change it's data source and mime type by hand
+ QCString oldDataSourceMimeType( formWidget->dataSourceMimeType() );
+ QCString oldDataSource( formWidget->dataSource().latin1() );
+
+ if (mime!=oldDataSourceMimeType || name!=oldDataSource) {
+ formWidget->setDataSourceMimeType(mime);
+ formWidget->setDataSource(name);
+ emit dirty(activeForm(), true);
+
+ activeForm()->addCommand(
+ new KFormDesigner::PropertyCommand(propertySet(), QString(formWidget->name()),
+ oldDataSource, name, "dataSource"),
+ false );
+
+ // If the property is changed, we add it in ObjectTreeItem modifProp
+ KFormDesigner::ObjectTreeItem *fromTreeItem = activeForm()->objectTree()->lookup(formWidget->name());
+ fromTreeItem->addModifiedProperty("dataSourceMimeType", mime);
+ fromTreeItem->addModifiedProperty("dataSource", name);
+ }*/
+}
+
+void KexiFormManager::setDataSourceFieldOrExpression(const QString& string, const QString& caption,
+ KexiDB::Field::Type type)
+{
+ if (!activeForm())
+ return;
+// KexiFormDataItemInterface* dataWidget = dynamic_cast<KexiFormDataItemInterface*>(activeForm()->selectedWidget());
+// if (!dataWidget)
+// return;
+
+ KFormDesigner::WidgetPropertySet *set = propertySet();
+ if (!set || !set->contains("dataSource"))
+ return;
+
+ (*set)["dataSource"].setValue(string);
+
+ if (set->contains("autoCaption") && (*set)["autoCaption"].value().toBool()) {
+ if (set->contains("fieldCaptionInternal"))
+ (*set)["fieldCaptionInternal"].setValue(caption);
+ }
+ if (//type!=KexiDB::Field::InvalidType &&
+ set->contains("widgetType") && (*set)["widgetType"].value().toString()=="Auto")
+ {
+ if (set->contains("fieldTypeInternal"))
+ (*set)["fieldTypeInternal"].setValue(type);
+ }
+
+/* QString oldDataSource( dataWidget->dataSource() );
+ if (string!=oldDataSource) {
+ dataWidget->setDataSource(string);
+ emit dirty(activeForm(), true);
+
+ buffer
+ }*/
+}
+
+void KexiFormManager::insertAutoFields(const QString& sourceMimeType, const QString& sourceName,
+ const QStringList& fields)
+{
+ KexiFormView* formViewWidget = activeFormViewWidget();
+ if (!formViewWidget || !formViewWidget->form() || !formViewWidget->form()->activeContainer())
+ return;
+ formViewWidget->insertAutoFields(sourceMimeType, sourceName, fields,
+ formViewWidget->form()->activeContainer());
+}
+
+void KexiFormManager::slotHistoryCommandExecuted()
+{
+ const KFormDesigner::CommandGroup *group = dynamic_cast<const KFormDesigner::CommandGroup*>(sender());
+ if (group) {
+ if (group->commands().count()==2) {
+ KexiDBForm* formWidget = dynamic_cast<KexiDBForm*>(activeForm()->widget());
+ if (!formWidget)
+ return;
+ QPtrListIterator<KCommand> it(group->commands());
+ const KFormDesigner::PropertyCommand* pc1 = dynamic_cast<const KFormDesigner::PropertyCommand*>(it.current());
+ ++it;
+ const KFormDesigner::PropertyCommand* pc2 = dynamic_cast<const KFormDesigner::PropertyCommand*>(it.current());
+ if (pc1 && pc2 && pc1->property()=="dataSource" && pc2->property()=="dataSourceMimeType") {
+ const QMap<QCString, QVariant>::const_iterator it1( pc1->oldValues().constBegin() );
+ const QMap<QCString, QVariant>::const_iterator it2( pc2->oldValues().constBegin() );
+ if (it1.key()==formWidget->name() && it2.key()==formWidget->name())
+ static_cast<KexiFormPart*>(m_part)->dataSourcePage()->setDataSource(
+ formWidget->dataSourceMimeType(), formWidget->dataSource().latin1());
+ }
+ }
+ }
+}
+
+/*
+bool KexiFormManager::loadFormFromDomInternal(Form *form, QWidget *container, QDomDocument &inBuf)
+{
+ QMap<QCString,QString> customProperties;
+ FormIO::loadFormFromDom(myform, container, domDoc, &customProperties);
+}
+
+bool KexiFormManager::saveFormToStringInternal(Form *form, QString &dest, int indent)
+{
+ QMap<QCString,QString> customProperties;
+ return KFormDesigner::FormIO::saveFormToString(form, dest, indent, &customProperties);
+}
+
+*/
+
+#include "kexiformmanager.moc"
diff --git a/kexi/plugins/forms/kexiformmanager.h b/kexi/plugins/forms/kexiformmanager.h
new file mode 100644
index 000000000..1cc5f0c64
--- /dev/null
+++ b/kexi/plugins/forms/kexiformmanager.h
@@ -0,0 +1,87 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFORMMANAGER_H
+#define KEXIFORMMANAGER_H
+
+#include <formmanager.h>
+#include <kexipart.h>
+
+class KCommand;
+class KexiFormView;
+
+//! @internal
+//! Used to customize KFormDesigner::FormManager behaviour.
+class KEXIFORMUTILS_EXPORT KexiFormManager : public KFormDesigner::FormManager
+{
+ Q_OBJECT
+
+ public:
+ KexiFormManager(KexiPart::Part *parent, const char* name = 0);
+ virtual ~KexiFormManager();
+
+ virtual KAction* action( const char* name );
+ virtual void enableAction( const char* name, bool enable );
+
+ public slots:
+ //! Receives signal from KexiDataSourcePage about changed form's data source
+ void setFormDataSource(const QCString& mime, const QCString& name);
+
+ /*! Receives signal from KexiDataSourcePage about changed widget's data source.
+ This is because we couldn't pass objects like KexiDB::QueryColumnInfo.
+
+ Also sets following things in KexiDBAutoField:
+ - caption related to the data source
+ - data type related to the data source */
+ void setDataSourceFieldOrExpression(const QString& string, const QString& caption,
+ KexiDB::Field::Type type);
+
+ /*! Receives signal from KexiDataSourcePage and inserts autofields onto the current form. */
+ void insertAutoFields(const QString& sourceMimeType, const QString& sourceName,
+ const QStringList& fields);
+
+ protected slots:
+ void slotHistoryCommandExecuted();
+
+ protected:
+ inline QString translateName( const char* name ) const;
+
+ private:
+ //! Helper: return active form's view widget or 0 if there's no active form having such widget
+ KexiFormView* activeFormViewWidget() const;
+
+// virtual bool loadFormFromDomInternal(Form *form, QWidget *container, QDomDocument &inBuf);
+// virtual bool saveFormToStringInternal(Form *form, QString &dest, int indent = 0);
+
+ KexiPart::Part* m_part;
+};
+
+QString KexiFormManager::translateName( const char* name ) const
+{
+ QString n( name );
+ //translate to our name space:
+ if (n.startsWith("align_") || n.startsWith("adjust_") || n.startsWith("layout_")
+ || n=="format_raise" || n=="format_raise" || n=="taborder" | n=="break_layout")
+ {
+ n.prepend("formpart_");
+ }
+ return n;
+}
+
+#endif
diff --git a/kexi/plugins/forms/kexiformpart.cpp b/kexi/plugins/forms/kexiformpart.cpp
new file mode 100644
index 000000000..8693cb5bc
--- /dev/null
+++ b/kexi/plugins/forms/kexiformpart.cpp
@@ -0,0 +1,550 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+#include <kgenericfactory.h>
+#include <kdialogbase.h>
+#include <klistview.h>
+#include <ktabwidget.h>
+#include <kiconloader.h>
+#include <kcombobox.h>
+#include <kapplication.h>
+#include <kconfig.h>
+
+#include <kexiviewbase.h>
+#include <keximainwindow.h>
+#include <kexiproject.h>
+#include <kexipartitem.h>
+#include <kexidialogbase.h>
+#include <kexidatasourcecombobox.h>
+#include <kexidb/connection.h>
+#include <kexidb/fieldlist.h>
+#include <kexidb/field.h>
+#include <kexiutils/utils.h>
+
+#include <form.h>
+#include <formIO.h>
+#include <widgetpropertyset.h>
+#include <widgetlibrary.h>
+#include <objecttreeview.h>
+#include <koproperty/property.h>
+
+#include "kexiformview.h"
+#include "widgets/kexidbform.h"
+#include "kexiformscrollview.h"
+#include "kexiactionselectiondialog.h"
+#include "kexiformmanager.h"
+#include "kexiformpart.h"
+#include "kexidatasourcepage.h"
+
+//! @todo #define KEXI_SHOW_SPLITTER_WIDGET
+
+KFormDesigner::WidgetLibrary* KexiFormPart::static_formsLibrary = 0L;
+
+//! @internal
+class KexiFormPart::Private
+{
+ public:
+ Private()
+ {
+ }
+ ~Private()
+ {
+ delete static_cast<KFormDesigner::ObjectTreeView*>(objectTreeView);
+ delete static_cast<KexiDataSourcePage*>(dataSourcePage);
+ }
+// QGuardedPtr<KFormDesigner::FormManager> manager;
+ QGuardedPtr<KFormDesigner::ObjectTreeView> objectTreeView;
+ QGuardedPtr<KexiDataSourcePage> dataSourcePage;
+ KexiDataSourceComboBox *dataSourceCombo;
+};
+
+KexiFormPart::KexiFormPart(QObject *parent, const char *name, const QStringList &l)
+ : KexiPart::Part(parent, name, l)
+ , d(new Private())
+{
+ // REGISTERED ID:
+ m_registeredPartID = (int)KexiPart::FormObjectType;
+
+ kexipluginsdbg << "KexiFormPart::KexiFormPart()" << endl;
+ m_names["instanceName"]
+ = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). "
+ "Use '_' character instead of spaces. First character should be a..z character. "
+ "If you cannot use latin characters in your language, use english word.",
+ "form");
+ m_names["instanceCaption"] = i18n("Form");
+ m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode;
+ m_newObjectsAreDirty = true;
+
+ // Only create form manager if it's not yet created.
+ // KexiReportPart could have created it already.
+ KFormDesigner::FormManager *formManager = KFormDesigner::FormManager::self();
+ if (!formManager)
+ formManager = new KexiFormManager(this, "kexi_form_and_report_manager");
+
+ // Create and store a handle to forms' library. Reports will have their own library too.
+/* @todo add configuration for supported factory groups */
+ QStringList supportedFactoryGroups;
+ supportedFactoryGroups += "kexi";
+ static_formsLibrary = KFormDesigner::FormManager::createWidgetLibrary(
+ formManager, supportedFactoryGroups);
+ static_formsLibrary->setAdvancedPropertiesVisible(false);
+ connect(static_formsLibrary, SIGNAL(widgetCreated(QWidget*)),
+ this, SLOT(slotWidgetCreatedByFormsLibrary(QWidget*)));
+
+ connect(KFormDesigner::FormManager::self()->propertySet(), SIGNAL(widgetPropertyChanged(QWidget *, const QCString &, const QVariant&)),
+ this, SLOT(slotPropertyChanged(QWidget *, const QCString &, const QVariant&)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(autoTabStopsSet(KFormDesigner::Form*,bool)),
+ this, SLOT(slotAutoTabStopsSet(KFormDesigner::Form*,bool)));
+}
+
+KexiFormPart::~KexiFormPart()
+{
+ delete d;
+}
+
+KFormDesigner::WidgetLibrary* KexiFormPart::library()
+{
+ return static_formsLibrary;
+}
+
+#if 0
+void KexiFormPart::initPartActions(KActionCollection *collection)
+{
+//this is automatic? -no
+//create child guicilent: guiClient()->setXMLFile("kexidatatableui.rc");
+
+ kexipluginsdbg<<"FormPart INIT ACTIONS***********************************************************************"<<endl;
+ //TODO
+
+ //guiClient()->setXMLFile("kexiformui.rc");
+//js m_manager->createActions(collection, 0);
+}
+
+void KexiFormPart::initInstanceActions( int mode, KActionCollection *col )
+{
+ if (mode==Kexi::DesignViewMode) {
+ KFormDesigner::FormManager::self()->createActions(col, 0);
+ new KAction(i18n("Edit Tab Order..."), "tab_order", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(editTabOrder()), col, "taborder");
+ new KAction(i18n("Adjust Size"), "viewmagfit", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(ajustWidgetSize()), col, "adjust");
+ }
+ //TODO
+}
+#endif
+
+void KexiFormPart::initPartActions()
+{
+// new KAction(i18n("Show Form UI Code"), "show_form_ui", CTRL+Key_U, m_manager, SLOT(showFormUICode()),
+// guiClient()->actionCollection(), "show_form_ui");
+}
+
+void KexiFormPart::initInstanceActions()
+{
+#ifdef KEXI_DEBUG_GUI
+ kapp->config()->setGroup("General");
+ if (kapp->config()->readBoolEntry("showInternalDebugger", false)) {
+ new KAction(i18n("Show Form UI Code"), "compfile",
+ CTRL+Key_U, KFormDesigner::FormManager::self(), SLOT(showFormUICode()),
+ actionCollectionForMode(Kexi::DesignViewMode), "show_form_ui");
+ }
+#endif
+
+ KActionCollection *col = actionCollectionForMode(Kexi::DesignViewMode);
+ KFormDesigner::FormManager::self()->createActions( library(), col, (KXMLGUIClient*)col->parentGUIClient() ); //guiClient() );
+
+ //connect actions provided by widget factories
+ connect( col->action("widget_assign_action"), SIGNAL(activated()), this, SLOT(slotAssignAction()));
+
+ createSharedAction(Kexi::DesignViewMode, i18n("Clear Widget Contents"), "editclear", 0, "formpart_clear_contents");
+ createSharedAction(Kexi::DesignViewMode, i18n("Edit Tab Order..."), "tab_order", 0, "formpart_taborder");
+//TODO createSharedAction(Kexi::DesignViewMode, i18n("Edit Pixmap Collection"), "icons", 0, "formpart_pixmap_collection");
+//TODO createSharedAction(Kexi::DesignViewMode, i18n("Edit Form Connections"), "connections", 0, "formpart_connections");
+
+// KFormDesigner::CreateLayoutCommand
+
+ KAction *action = createSharedAction(Kexi::DesignViewMode, i18n("Layout Widgets"), "", 0, "formpart_layout_menu", "KActionMenu");
+ KActionMenu *menu = static_cast<KActionMenu*>(action);
+
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("&Horizontally"),
+ QString::null, 0, "formpart_layout_hbox"));
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("&Vertically"),
+ QString::null, 0, "formpart_layout_vbox"));
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("In &Grid"),
+ QString::null, 0, "formpart_layout_grid"));
+#ifdef KEXI_SHOW_SPLITTER_WIDGET
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("Horizontally in &Splitter"),
+ QString::null, 0, "formpart_layout_hsplitter"));
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("Verti&cally in Splitter"),
+ QString::null, 0, "formpart_layout_vsplitter"));
+#endif
+
+ createSharedAction(Kexi::DesignViewMode, i18n("&Break Layout"), QString::null, 0, "formpart_break_layout");
+/*
+ createSharedAction(Kexi::DesignViewMode, i18n("Lay Out Widgets &Horizontally"), QString::null, 0, "formpart_layout_hbox");
+ createSharedAction(Kexi::DesignViewMode, i18n("Lay Out Widgets &Vertically"), QString::null, 0, "formpart_layout_vbox");
+ createSharedAction(Kexi::DesignViewMode, i18n("Lay Out Widgets in &Grid"), QString::null, 0, "formpart_layout_grid");
+*/
+ createSharedAction(Kexi::DesignViewMode, i18n("Bring Widget to Front"), "raise", 0, "formpart_format_raise");
+ createSharedAction(Kexi::DesignViewMode, i18n("Send Widget to Back"), "lower", 0, "formpart_format_lower");
+
+#ifndef KEXI_NO_UNFINISHED
+ action = createSharedAction(Kexi::DesignViewMode, i18n("Other Widgets"), "", 0, "other_widgets_menu", "KActionMenu");
+#endif
+
+ action = createSharedAction(Kexi::DesignViewMode, i18n("Align Widgets Position"), "aoleft", 0, "formpart_align_menu", "KActionMenu");
+ menu = static_cast<KActionMenu*>(action);
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Left"), "aoleft", 0, "formpart_align_to_left") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Right"), "aoright", 0, "formpart_align_to_right") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Top"), "aotop", 0, "formpart_align_to_top") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Bottom"), "aobottom", 0, "formpart_align_to_bottom") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Grid"), "aopos2grid", 0, "formpart_align_to_grid") );
+
+ action = createSharedAction(Kexi::DesignViewMode, i18n("Adjust Widgets Size"), "aogrid", 0, "formpart_adjust_size_menu", "KActionMenu");
+ menu = static_cast<KActionMenu*>(action);
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Fit"), "aofit", 0, "formpart_adjust_to_fit") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Grid"), "aogrid", 0, "formpart_adjust_size_grid") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Shortest"), "aoshortest", 0, "formpart_adjust_height_small") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Tallest"), "aotallest", 0, "formpart_adjust_height_big") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Narrowest"), "aonarrowest", 0, "formpart_adjust_width_small") );
+ menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Widest"), "aowidest", 0, "formpart_adjust_width_big") );
+}
+
+KexiDialogTempData*
+KexiFormPart::createTempData(KexiDialogBase* dialog)
+{
+ return new KexiFormPart::TempData(dialog);
+}
+
+KexiViewBase* KexiFormPart::createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode, QMap<QString,QString>*)
+{
+ Q_UNUSED( viewMode );
+
+ kexipluginsdbg << "KexiFormPart::createView()" << endl;
+ KexiMainWindow *win = dialog->mainWin();
+ if (!win || !win->project() || !win->project()->dbConnection())
+ return 0;
+
+ KexiFormView *view = new KexiFormView(win, parent, item.name().latin1(),
+ win->project()->dbConnection() );
+
+ return view;
+}
+
+void
+KexiFormPart::generateForm(KexiDB::FieldList *list, QDomDocument &domDoc)
+{
+ //this form generates a .ui from FieldList list
+ //basically that is a Label and a LineEdit for each field
+ domDoc = QDomDocument("UI");
+ QDomElement uiElement = domDoc.createElement("UI");
+ domDoc.appendChild(uiElement);
+ uiElement.setAttribute("version", "3.1");
+ uiElement.setAttribute("stdsetdef", 1);
+
+ QDomElement baseClass = domDoc.createElement("class");
+ uiElement.appendChild(baseClass);
+ QDomText baseClassV = domDoc.createTextNode("QWidget");
+ baseClass.appendChild(baseClassV);
+ QDomElement baseWidget = domDoc.createElement("widget");
+ baseWidget.setAttribute("class", "QWidget");
+
+ int y=0;
+
+ for(unsigned int i=0; i < list->fieldCount(); i++)
+ {
+ QDomElement lclass = domDoc.createElement("widget");
+ baseWidget.appendChild(lclass);
+ lclass.setAttribute("class", "QLabel");
+ QDomElement lNameProperty = domDoc.createElement("property");
+ lNameProperty.setAttribute("name", "name");
+ QDomElement lType = domDoc.createElement("cstring");
+ QDomText lClassN = domDoc.createTextNode(QString("l%1").arg(list->field(i)->name()));
+ lType.appendChild(lClassN);
+ lNameProperty.appendChild(lType);
+ lclass.appendChild(lNameProperty);
+
+ QDomElement gNameProperty = domDoc.createElement("property");
+ gNameProperty.setAttribute("name", "geometry");
+ QDomElement lGType = domDoc.createElement("rect");
+
+ QDomElement lx = domDoc.createElement("x");
+ QDomText lxV = domDoc.createTextNode("10");
+ lx.appendChild(lxV);
+ QDomElement ly = domDoc.createElement("y");
+ QDomText lyV = domDoc.createTextNode(QString::number(y + 10));
+ ly.appendChild(lyV);
+ QDomElement lWidth = domDoc.createElement("width");
+ QDomText lWidthV = domDoc.createTextNode("100");
+ lWidth.appendChild(lWidthV);
+ QDomElement lHeight = domDoc.createElement("height");
+ QDomText lHeightV = domDoc.createTextNode("20");
+ lHeight.appendChild(lHeightV);
+
+ lGType.appendChild(lx);
+ lGType.appendChild(ly);
+ lGType.appendChild(lWidth);
+ lGType.appendChild(lHeight);
+
+ gNameProperty.appendChild(lGType);
+ lclass.appendChild(gNameProperty);
+
+ QDomElement tNameProperty = domDoc.createElement("property");
+ tNameProperty.setAttribute("name", "text");
+ QDomElement lTType = domDoc.createElement("string");
+ QDomText lTextV = domDoc.createTextNode(list->field(i)->name());
+ lTType.appendChild(lTextV);
+ tNameProperty.appendChild(lTType);
+ lclass.appendChild(tNameProperty);
+
+
+ ///line edit!
+
+
+ QDomElement vclass = domDoc.createElement("widget");
+ baseWidget.appendChild(vclass);
+ vclass.setAttribute("class", "KLineEdit");
+ QDomElement vNameProperty = domDoc.createElement("property");
+ vNameProperty.setAttribute("name", "name");
+ QDomElement vType = domDoc.createElement("cstring");
+ QDomText vClassN = domDoc.createTextNode(list->field(i)->name());
+ vType.appendChild(vClassN);
+ vNameProperty.appendChild(vType);
+ vclass.appendChild(vNameProperty);
+
+ QDomElement vgNameProperty = domDoc.createElement("property");
+ vgNameProperty.setAttribute("name", "geometry");
+ QDomElement vGType = domDoc.createElement("rect");
+
+ QDomElement vx = domDoc.createElement("x");
+ QDomText vxV = domDoc.createTextNode("110");
+ vx.appendChild(vxV);
+ QDomElement vy = domDoc.createElement("y");
+ QDomText vyV = domDoc.createTextNode(QString::number(y + 10));
+ vy.appendChild(vyV);
+ QDomElement vWidth = domDoc.createElement("width");
+ QDomText vWidthV = domDoc.createTextNode("200");
+ vWidth.appendChild(vWidthV);
+ QDomElement vHeight = domDoc.createElement("height");
+ QDomText vHeightV = domDoc.createTextNode("20");
+ vHeight.appendChild(vHeightV);
+
+ vGType.appendChild(vx);
+ vGType.appendChild(vy);
+ vGType.appendChild(vWidth);
+ vGType.appendChild(vHeight);
+
+ vgNameProperty.appendChild(vGType);
+ vclass.appendChild(vgNameProperty);
+
+ y += 20;
+ }
+
+ QDomElement lNameProperty = domDoc.createElement("property");
+ lNameProperty.setAttribute("name", "name");
+ QDomElement lType = domDoc.createElement("cstring");
+ QDomText lClassN = domDoc.createTextNode("DBForm");
+ lType.appendChild(lClassN);
+ lNameProperty.appendChild(lType);
+ baseWidget.appendChild(lNameProperty);
+
+ QDomElement wNameProperty = domDoc.createElement("property");
+ wNameProperty.setAttribute("name", "geometry");
+ QDomElement wGType = domDoc.createElement("rect");
+
+ QDomElement wx = domDoc.createElement("x");
+ QDomText wxV = domDoc.createTextNode("0");
+ wx.appendChild(wxV);
+ QDomElement wy = domDoc.createElement("y");
+ QDomText wyV = domDoc.createTextNode("0");
+ wy.appendChild(wyV);
+ QDomElement wWidth = domDoc.createElement("width");
+ QDomText wWidthV = domDoc.createTextNode("340");
+ wWidth.appendChild(wWidthV);
+ QDomElement wHeight = domDoc.createElement("height");
+ QDomText wHeightV = domDoc.createTextNode(QString::number(y + 30));
+ wHeight.appendChild(wHeightV);
+
+ wGType.appendChild(wx);
+ wGType.appendChild(wy);
+ wGType.appendChild(wWidth);
+ wGType.appendChild(wHeight);
+
+ wNameProperty.appendChild(wGType);
+ baseWidget.appendChild(wNameProperty);
+
+ uiElement.appendChild(baseWidget);
+}
+
+void KexiFormPart::slotAutoTabStopsSet(KFormDesigner::Form *form, bool set)
+{
+ Q_UNUSED( form );
+
+ KoProperty::Property &p = (*KFormDesigner::FormManager::self()->propertySet())["autoTabStops"];
+ if (!p.isNull())
+ p.setValue(QVariant(set, 4));
+}
+
+void KexiFormPart::slotAssignAction()
+{
+ KexiDBForm *dbform;
+ if (!KFormDesigner::FormManager::self()->activeForm() || !KFormDesigner::FormManager::self()->activeForm()->designMode()
+ || !(dbform = dynamic_cast<KexiDBForm*>(KFormDesigner::FormManager::self()->activeForm()->formWidget())))
+ return;
+
+ KFormDesigner::WidgetPropertySet * propSet = KFormDesigner::FormManager::self()->propertySet();
+
+ KoProperty::Property &onClickActionProp = propSet->property("onClickAction");
+ if (onClickActionProp.isNull())
+ return;
+ KoProperty::Property &onClickActionOptionProp = propSet->property("onClickActionOption");
+ KexiFormEventAction::ActionData data;
+ data.string = onClickActionProp.value().toString();
+ if (!onClickActionOptionProp.isNull())
+ data.option = onClickActionOptionProp.value().toString();
+
+ KexiFormScrollView *scrollViewWidget = dynamic_cast<KexiFormScrollView*>(dbform->dataAwareObject());
+ if (!scrollViewWidget)
+ return;
+ KexiFormView* formViewWidget = dynamic_cast<KexiFormView*>(scrollViewWidget->parent());
+ if (!formViewWidget)
+ return;
+
+ KexiMainWindow * mainWin = formViewWidget->parentDialog()->mainWin();
+ KexiActionSelectionDialog dlg(mainWin, dbform, data,
+ propSet->property("name").value().toCString());
+
+ if(dlg.exec() == QDialog::Accepted) {
+ data = dlg.currentAction();
+ //update property value
+ propSet->property("onClickAction").setValue(data.string);
+ propSet->property("onClickActionOption").setValue(data.option);
+ }
+}
+
+QString
+KexiFormPart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const
+{
+ Q_UNUSED(dlg);
+ if (englishMessage=="Design of object \"%1\" has been modified.")
+ return i18n("Design of form \"%1\" has been modified.");
+ if (englishMessage=="Object \"%1\" already exists.")
+ return i18n("Form \"%1\" already exists.");
+
+ return englishMessage;
+}
+
+void
+KexiFormPart::slotPropertyChanged(QWidget *w, const QCString &name, const QVariant &value)
+{
+ Q_UNUSED( w );
+
+ if (!KFormDesigner::FormManager::self()->activeForm())
+ return;
+ if (name == "autoTabStops") {
+ //QWidget *w = KFormDesigner::FormManager::self()->activeForm()->selectedWidget();
+ //update autoTabStops setting at KFD::Form level
+ KFormDesigner::FormManager::self()->activeForm()->setAutoTabStops( value.toBool() );
+ }
+ if (KFormDesigner::FormManager::self()->activeForm()->widget() && name == "geometry") {
+ //fall back to sizeInternal property....
+ if (KFormDesigner::FormManager::self()->propertySet()->contains("sizeInternal"))
+ KFormDesigner::FormManager::self()->propertySet()->property("sizeInternal").setValue(value.toRect().size());
+ }
+}
+
+/*KFormDesigner::FormManager*
+KexiFormPart::manager() const
+{
+ return d->manager;
+}*/
+
+KexiDataSourcePage* KexiFormPart::dataSourcePage() const
+{
+ return d->dataSourcePage;
+}
+
+void KexiFormPart::setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin)
+{
+ if (!d->objectTreeView) {
+ d->objectTreeView = new KFormDesigner::ObjectTreeView(0, "KexiFormPart:ObjectTreeView");
+ KFormDesigner::FormManager::self()->setObjectTreeView(d->objectTreeView); //important: assign to manager
+ d->dataSourcePage = new KexiDataSourcePage(0, "dataSourcePage");
+ connect(d->dataSourcePage, SIGNAL(jumpToObjectRequested(const QCString&, const QCString&)),
+ mainWin, SLOT(highlightObject(const QCString&, const QCString&)));
+ connect(d->dataSourcePage, SIGNAL(formDataSourceChanged(const QCString&, const QCString&)),
+ KFormDesigner::FormManager::self(), SLOT(setFormDataSource(const QCString&, const QCString&)));
+ connect(d->dataSourcePage, SIGNAL(dataSourceFieldOrExpressionChanged(const QString&, const QString&, KexiDB::Field::Type)),
+ KFormDesigner::FormManager::self(), SLOT(setDataSourceFieldOrExpression(const QString&, const QString&, KexiDB::Field::Type)));
+ connect(d->dataSourcePage, SIGNAL(insertAutoFields(const QString&, const QString&, const QStringList&)),
+ KFormDesigner::FormManager::self(), SLOT(insertAutoFields(const QString&, const QString&, const QStringList&)));
+ }
+
+ KexiProject *prj = mainWin->project();
+ d->dataSourcePage->setProject(prj);
+
+ tab->addTab( d->dataSourcePage, SmallIconSet("database"), "");
+ tab->setTabToolTip( d->dataSourcePage, i18n("Data Source"));
+
+ tab->addTab( d->objectTreeView, SmallIconSet("widgets"), "");
+ tab->setTabToolTip( d->objectTreeView, i18n("Widgets"));
+}
+
+void KexiFormPart::slotWidgetCreatedByFormsLibrary(QWidget* widget)
+{
+ QStrList signalNames(widget->metaObject()->signalNames());
+ if (!signalNames.isEmpty()) {
+ const char *handleDragMoveEventSignal = "handleDragMoveEvent(QDragMoveEvent*)";
+ const char *handleDropEventSignal = "handleDropEvent(QDropEvent*)";
+
+ for (QStrListIterator it(signalNames); it.current(); ++it) {
+ if (0==qstrcmp(it.current(), handleDragMoveEventSignal)) {
+ kdDebug() << it.current() << endl;
+ KexiFormView *formView = KexiUtils::findParent<KexiFormView>(widget, "KexiFormView");
+ if (formView) {
+ connect(widget, SIGNAL(handleDragMoveEvent(QDragMoveEvent*)),
+ formView, SLOT(slotHandleDragMoveEvent(QDragMoveEvent*)));
+ }
+ }
+ else if (0==qstrcmp(it.current(), handleDropEventSignal)) {
+ kdDebug() << it.current() << endl;
+ KexiFormView *formView = KexiUtils::findParent<KexiFormView>(widget, "KexiFormView");
+ if (formView) {
+ connect(widget, SIGNAL(handleDropEvent(QDropEvent*)),
+ formView, SLOT(slotHandleDropEvent(QDropEvent*)));
+ }
+ }
+ }
+ }
+}
+
+//----------------
+
+KexiFormPart::TempData::TempData(QObject* parent)
+ : KexiDialogTempData(parent)
+{
+}
+
+KexiFormPart::TempData::~TempData()
+{
+}
+
+#include "kexiformpart.moc"
diff --git a/kexi/plugins/forms/kexiformpart.h b/kexi/plugins/forms/kexiformpart.h
new file mode 100644
index 000000000..1ddbab533
--- /dev/null
+++ b/kexi/plugins/forms/kexiformpart.h
@@ -0,0 +1,108 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFORMPART_H
+#define KEXIFORMPART_H
+
+#include <qdom.h>
+#include <qcstring.h>
+
+#include <kexi.h>
+#include <kexipart.h>
+#include <kexidialogbase.h>
+#include <kexiblobbuffer.h>
+
+namespace KFormDesigner
+{
+ class WidgetLibrary;
+ class FormManager;
+ class Form;
+}
+
+namespace KexiDB
+{
+ class FieldList;
+}
+
+class KexiDataSourcePage;
+
+//! Kexi Form Plugin
+/*! It just creates a \ref KexiFormView. See there for most of code. */
+class KEXIFORMUTILS_EXPORT KexiFormPart : public KexiPart::Part
+{
+ Q_OBJECT
+
+ public:
+ KexiFormPart(QObject *parent, const char *name, const QStringList &);
+ virtual ~KexiFormPart();
+
+ //! \return a pointer to Forms Widget Library.
+ static KFormDesigner::WidgetLibrary* library();
+
+ KexiDataSourcePage* dataSourcePage() const;
+
+ void generateForm(KexiDB::FieldList *list, QDomDocument &domDoc);
+
+ class TempData : public KexiDialogTempData
+ {
+ public:
+ TempData(QObject* parent);
+ ~TempData();
+ QGuardedPtr<KFormDesigner::Form> form;
+ QGuardedPtr<KFormDesigner::Form> previewForm;
+ QString tempForm;
+ QPoint scrollViewContentsPos; //!< to preserve contents pos after switching to other view
+ int resizeMode; //!< form's window's resize mode -one of KexiFormView::ResizeMode items
+ //! Used in KexiFormView::setUnsavedLocalBLOBs()
+ QMap<QWidget*, KexiBLOBBuffer::Id_t> unsavedLocalBLOBs;
+ //! Used when loading a form from (temporary) XML in Data View
+ //! to get unsaved blobs collected at design mode.
+ QMap<QCString, KexiBLOBBuffer::Id_t> unsavedLocalBLOBsByName;
+ };
+
+ virtual QString i18nMessage(const QCString& englishMessage,
+ KexiDialogBase* dlg) const;
+
+ protected:
+ virtual KexiDialogTempData* createTempData(KexiDialogBase* dialog);
+
+ virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode = Kexi::DataViewMode, QMap<QString,QString>* staticObjectArgs = 0);
+
+ virtual void initPartActions();
+ virtual void initInstanceActions();
+ virtual void setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin);
+
+ static KFormDesigner::WidgetLibrary* static_formsLibrary;
+
+ protected slots:
+ void slotAutoTabStopsSet(KFormDesigner::Form *form, bool set);
+ void slotAssignAction();
+ void slotPropertyChanged(QWidget *widget, const QCString &name, const QVariant &value);
+ void slotWidgetCreatedByFormsLibrary(QWidget* widget);
+
+ private:
+ class Private;
+ Private* d;
+};
+
+#endif
+
diff --git a/kexi/plugins/forms/kexiformpartinstui.rc b/kexi/plugins/forms/kexiformpartinstui.rc
new file mode 100644
index 000000000..75f233f24
--- /dev/null
+++ b/kexi/plugins/forms/kexiformpartinstui.rc
@@ -0,0 +1,77 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexiformpartinst" version="13">
+
+<MenuBar>
+ <Menu name="edit">
+ <Action name="fompart_clear_contents"/>
+ <Separator />
+ <Action name="formpart_taborder"/>
+ <Action name="formpart_adjust_size"/>
+ <Action name="formpart_pixmap_collection"/>
+ <Action name="formpart_connections"/>
+ <!-- Action name="change_style"/ -->
+ </Menu>
+ <Menu name="format" noMerge="0">
+ <text>&amp;Format</text>
+ <Action name="snap_to_grid"/>
+ <Separator/>
+ <Action name="formpart_layout_menu"/>
+ <Action name="formpart_break_layout"/>
+ <Separator/>
+ <Action name="formpart_align_menu"/>
+ <Action name="formpart_adjust_size_menu"/>
+ <Separator/>
+ <Action name="formpart_format_raise"/>
+ <Action name="formpart_format_lower"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="widgets" fullWidth="false">
+ <text>Widgets</text>
+ <Action name="pointer"/>
+ <!-- Action name="drag_connection"/ -->
+ <Separator/>
+ <Action name="library_widget_KexiDBAutoField"/>
+ <Action name="library_widget_KexiDBLabel"/>
+ <Action name="library_widget_KexiPictureLabel"/>
+ <Action name="library_widget_KexiDBImageBox"/>
+ <Action name="library_widget_KexiDBLineEdit"/>
+ <Action name="library_widget_KexiDBTextEdit"/>
+ <Action name="library_widget_KPushButton"/>
+ <Action name="library_widget_KexiDBComboBox"/>
+ <!-- Action name="library_widget_QRadioButton"/ -->
+ <Action name="library_widget_KexiDBCheckBox"/>
+ <Action name="library_widget_Spacer"/>
+ <Action name="library_widget_Line"/>
+ <Separator/>
+ <Action name="library_widget_KexiFrame"/>
+ <Action name="library_widget_QGroupBox"/>
+ <Action name="library_widget_KFDTabWidget"/>
+ <!-- TODO Action name="library_widget_KexiDBSubForm"/ -->
+ <Separator/>
+ <Action name="library_widget_Spring"/>
+ <Separator/>
+ <Action name="other_widgets_menu"/>
+ <ActionList name="library_widgets" />
+ <Merge/>
+</ToolBar>
+<ToolBar name="format" fullWidth="false" noMerge="1">
+<text>Format</text>
+ <!-- Action name="formpart_layout_menu"/ -->
+ <Action name="formpart_align_menu"/>
+ <Action name="formpart_adjust_size_menu"/>
+ <Action name="show_form_ui" />
+</ToolBar>
+<!-- ToolBar name="tools" fullWidth="false">
+ <Action name="change_style"/>
+</ToolBar -->
+
+<Menu name="other_widgets_menu">
+ <Action name="library_widget_KexiDBIntSpinBox"/>
+ <Action name="library_widget_KexiDBDoubleSpinBox"/>
+ <Action name="library_widget_KexiDBDateEdit"/>
+ <Action name="library_widget_KexiDBTimeEdit"/>
+ <Action name="library_widget_KexiDBDateTimeEdit"/>
+</Menu>
+
+</kpartgui>
diff --git a/kexi/plugins/forms/kexiformpartui.rc b/kexi/plugins/forms/kexiformpartui.rc
new file mode 100644
index 000000000..20fd49d86
--- /dev/null
+++ b/kexi/plugins/forms/kexiformpartui.rc
@@ -0,0 +1,10 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexiformpart" version="6">
+
+<!-- ToolBar name="design" fullWidth="false" noMerge="0">
+ <text>Design</text>
+ <Action name="show_form_ui"/>
+</ToolBar -->
+
+</kpartgui>
+
diff --git a/kexi/plugins/forms/kexiforms.cpp b/kexi/plugins/forms/kexiforms.cpp
new file mode 100644
index 000000000..07c4726f7
--- /dev/null
+++ b/kexi/plugins/forms/kexiforms.cpp
@@ -0,0 +1,25 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kgenericfactory.h>
+
+#include "kexiformpart.h"
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_form, KGenericFactory<KexiFormPart>("kexihandler_form") )
+
diff --git a/kexi/plugins/forms/kexiformscrollview.cpp b/kexi/plugins/forms/kexiformscrollview.cpp
new file mode 100644
index 000000000..351a1e3ea
--- /dev/null
+++ b/kexi/plugins/forms/kexiformscrollview.cpp
@@ -0,0 +1,587 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiformscrollview.h"
+//#include "kexiformview.h"
+
+#include <formeditor/form.h>
+#include <formeditor/formmanager.h>
+#include <formeditor/objecttree.h>
+#include <formeditor/commands.h>
+#include <widget/utils/kexirecordmarker.h>
+
+#include <kpopupmenu.h>
+#include <kdebug.h>
+
+KexiFormScrollView::KexiFormScrollView(QWidget *parent, bool preview)
+ : KexiScrollView(parent, preview)
+ , KexiRecordNavigatorHandler()
+ , KexiSharedActionClient()
+ , KexiDataAwareObjectInterface()
+ , KexiFormDataProvider()
+ , KexiFormEventHandler()
+{
+ m_currentLocalSortColumn = -1; /* no column */
+ m_localSortingOrder = -1; /* no sorting */
+ m_previousItem = 0;
+ m_navPanel = m_scrollViewNavPanel; //copy this pointer from KexiScrollView
+ if (preview) {
+ setRecordNavigatorVisible(true);
+//tmp
+// recordNavigator()->setEditingIndicatorEnabled(true);
+// recordNavigator()->showEditingIndicator(true);
+ }
+
+ connect(this, SIGNAL(resizingStarted()), this, SLOT(slotResizingStarted()));
+
+ m_popupMenu = new KPopupMenu(this, "contextMenu");
+
+// setFocusPolicy(NoFocus);
+}
+
+KexiFormScrollView::~KexiFormScrollView()
+{
+ if (m_owner)
+ delete m_data;
+ m_data = 0;
+}
+
+void
+KexiFormScrollView::show()
+{
+ KexiScrollView::show();
+
+#if 0 //moved to KexiFormView, OK?
+ //now get resize mode settings for entire form
+ if (m_preview) {
+ KexiFormView* fv = dynamic_cast<KexiFormView*>(parent());
+ int resizeMode = fv ? fv->resizeMode() : KexiFormView::ResizeAuto;
+ if (resizeMode == KexiFormView::ResizeAuto)
+ setResizePolicy(AutoOneFit);
+ }
+#endif
+}
+
+void
+KexiFormScrollView::slotResizingStarted()
+{
+ if(m_form && KFormDesigner::FormManager::self())
+ setSnapToGrid(KFormDesigner::FormManager::self()->snapWidgetsToGrid(), m_form->gridSize());
+ else
+ setSnapToGrid(false);
+}
+
+int KexiFormScrollView::rowsPerPage() const
+{
+ //! @todo
+ return 10;
+}
+
+void KexiFormScrollView::selectCellInternal()
+{
+ //m_currentItem is already set by KexiDataAwareObjectInterface::setCursorPosition()
+ if (m_currentItem) {
+ if (m_currentItem!=m_previousItem) {
+ fillDataItems(*m_currentItem, cursorAtNewRow());
+ m_previousItem = m_currentItem;
+ }
+ }
+ else {
+ m_previousItem = 0;
+ }
+}
+
+void KexiFormScrollView::ensureCellVisible(int row, int col/*=-1*/)
+{
+ Q_UNUSED( row );
+ Q_UNUSED( col );
+ //! @todo
+// if (m_currentItem)
+ //fillDataItems(*m_currentItem);
+
+// if (m_form->tabStops()->first() && m_form->tabStops()->first()->widget())
+// m_form->tabStops()->first()->widget()->setFocus();
+}
+
+void KexiFormScrollView::moveToRecordRequested(uint r)
+{
+ //! @todo
+ selectRow(r);
+}
+
+void KexiFormScrollView::moveToLastRecordRequested()
+{
+ //! @todo
+ selectLastRow();
+}
+
+void KexiFormScrollView::moveToPreviousRecordRequested()
+{
+ //! @todo
+ selectPrevRow();
+}
+
+void KexiFormScrollView::moveToNextRecordRequested()
+{
+ //! @todo
+ selectNextRow();
+}
+
+void KexiFormScrollView::moveToFirstRecordRequested()
+{
+ //! @todo
+ selectFirstRow();
+}
+
+void KexiFormScrollView::clearColumnsInternal(bool repaint)
+{
+ Q_UNUSED( repaint );
+ //! @todo
+}
+
+void KexiFormScrollView::addHeaderColumn(const QString& caption, const QString& description,
+ const QIconSet& icon, int width)
+{
+ Q_UNUSED( caption );
+ Q_UNUSED( description );
+ Q_UNUSED( icon );
+ Q_UNUSED( width );
+
+ //! @todo
+}
+
+int KexiFormScrollView::currentLocalSortingOrder() const
+{
+ //! @todo
+ return m_localSortingOrder;
+}
+
+int KexiFormScrollView::currentLocalSortColumn() const
+{
+ return m_currentLocalSortColumn;
+}
+
+void KexiFormScrollView::setLocalSortingOrder(int col, int order)
+{
+ //! @todo
+ m_currentLocalSortColumn = col;
+ m_localSortingOrder = order;
+}
+
+void KexiFormScrollView::sortColumnInternal(int col, int order)
+{
+ Q_UNUSED( col );
+ Q_UNUSED( order );
+ //! @todo
+}
+
+void KexiFormScrollView::updateGUIAfterSorting()
+{
+ //! @todo
+}
+
+void KexiFormScrollView::createEditor(int row, int col, const QString& addText,
+ bool removeOld)
+{
+ Q_UNUSED( row );
+ Q_UNUSED( addText );
+ Q_UNUSED( removeOld );
+
+ if (isReadOnly()) {
+ kexipluginsdbg << "KexiFormScrollView::createEditor(): DATA IS READ ONLY!"<<endl;
+ return;
+ }
+ if (column( col )->isReadOnly()) {
+ kexipluginsdbg << "KexiFormScrollView::createEditor(): COL IS READ ONLY!"<<endl;
+ return;
+ }
+
+ //! @todo
+ const bool startRowEdit = !m_rowEditing; //remember if we're starting row edit
+
+ if (!m_rowEditing) {
+ //we're starting row editing session
+ m_data->clearRowEditBuffer();
+
+ m_rowEditing = true;
+ //indicate on the vheader that we are editing:
+ if (m_verticalHeader)
+ m_verticalHeader->setEditRow(m_curRow);
+ if (isInsertingEnabled() && m_currentItem==m_insertItem) {
+ //we should know that we are in state "new row editing"
+ m_newRowEditing = true;
+ //'insert' row editing: show another row after that:
+ m_data->append( m_insertItem );
+ //new empty insert item
+ m_insertItem = m_data->createItem(); //new KexiTableItem(dataColumns());
+// updateContents();
+ if (m_verticalHeader)
+ m_verticalHeader->addLabel();
+// m_verticalHeaderAlreadyAdded = true;
+ updateWidgetContentsSize();
+ //refr. current and next row
+// updateContents(columnPos(0), rowPos(row), viewport()->width(), d->rowHeight*2);
+//js: warning this breaks behaviour (cursor is skipping, etc.): qApp->processEvents(500);
+// ensureVisible(columnPos(m_curCol), rowPos(row+1)+d->rowHeight-1, columnWidth(m_curCol), d->rowHeight);
+
+// m_verticalHeader->setOffset(contentsY());
+ }
+ }
+
+ m_editor = editor(col); //m_dataItems.at(col);
+ if (!m_editor)
+ return;
+
+ if (startRowEdit) {
+ recordNavigator()->showEditingIndicator(true);
+// recordNavigator()->updateButtons(); //refresh 'next btn'
+
+ emit rowEditStarted(m_curRow);
+ }
+}
+
+KexiDataItemInterface *KexiFormScrollView::editor( int col, bool ignoreMissingEditor )
+{
+ Q_UNUSED( ignoreMissingEditor );
+
+ if (!m_data || col<0 || col>=columns())
+ return 0;
+
+ return dynamic_cast<KexiFormDataItemInterface*>(dbFormWidget()->orderedDataAwareWidgets()->at( col ));
+// KexiFormDataItemInterface *item = m_dataItems.at(col);
+ //return item;
+
+/*
+ KexiTableViewColumn *tvcol = m_data->column(col);
+// int t = tvcol->field->type();
+
+ //find the editor for this column
+ KexiDataItemInterface *editor = d->editors[ tvcol ];
+ if (editor)
+ return editor;
+
+ //not found: create
+// editor = KexiCellEditorFactory::createEditor(*m_data->column(col)->field, this);
+ editor = KexiCellEditorFactory::createEditor(*m_data->column(col), this);
+ if (!editor) {//create error!
+ if (!ignoreMissingEditor) {
+ //js TODO: show error???
+ cancelRowEdit();
+ }
+ return 0;
+ }
+ editor->hide();
+ connect(editor,SIGNAL(editRequested()),this,SLOT(slotEditRequested()));
+ connect(editor,SIGNAL(cancelRequested()),this,SLOT(cancelEditor()));
+ connect(editor,SIGNAL(acceptRequested()),this,SLOT(acceptEditor()));
+
+ editor->resize(columnWidth(col)-1, rowHeight()-1);
+ editor->installEventFilter(this);
+ if (editor->widget())
+ editor->widget()->installEventFilter(this);
+ //store
+ d->editors.insert( tvcol, editor );
+ return editor;*/
+}
+
+void KexiFormScrollView::editorShowFocus( int row, int col )
+{
+ Q_UNUSED( row );
+ Q_UNUSED( col );
+ //! @todo
+// if (m_currentItem)
+// m_provider->fillDataItems(*m_currentItem);
+}
+
+void KexiFormScrollView::updateCell(int row, int col)
+{
+ Q_UNUSED( row );
+ Q_UNUSED( col );
+ //! @todo
+}
+
+void KexiFormScrollView::updateCurrentCell()
+{
+}
+
+void KexiFormScrollView::updateRow(int row)
+{
+ Q_UNUSED(row)
+ //! @todo
+}
+
+void KexiFormScrollView::updateWidgetContents()
+{
+ //! @todo
+}
+
+void KexiFormScrollView::updateWidgetContentsSize()
+{
+ //! @todo
+}
+
+void KexiFormScrollView::updateWidgetScrollBars()
+{
+ //! @todo
+}
+
+void KexiFormScrollView::slotRowRepaintRequested(KexiTableItem& item)
+{
+ Q_UNUSED( item );
+ //! @todo
+}
+
+/*void KexiFormScrollView::slotAboutToDeleteRow(KexiTableItem& item,
+ KexiDB::ResultInfo* result, bool repaint)
+{
+ //! @todo
+}*/
+
+/*void KexiFormScrollView::slotRowDeleted()
+{
+ //! @todo
+}*/
+
+void KexiFormScrollView::slotRowInserted(KexiTableItem *item, bool repaint)
+{
+ Q_UNUSED( item );
+ Q_UNUSED( repaint );
+ //! @todo
+}
+
+void KexiFormScrollView::slotRowInserted(KexiTableItem *item, uint row, bool repaint)
+{
+ Q_UNUSED( item );
+ Q_UNUSED( row );
+ Q_UNUSED( repaint );
+ //! @todo
+}
+
+void KexiFormScrollView::slotRowsDeleted( const QValueList<int> & )
+{
+ //! @todo
+}
+
+KexiDBForm* KexiFormScrollView::dbFormWidget() const
+{
+ return dynamic_cast<KexiDBForm*>(m_widget);
+}
+
+int KexiFormScrollView::columns() const
+{
+ return dbFormWidget()->orderedDataAwareWidgets()->count(); //m_dataItems.count();
+}
+
+/*uint KexiFormScrollView::fieldNumberForColumn(int col)
+{
+ KexiFormDataItemInterface *item = dynamic_cast<KexiFormDataItemInterface*>(dbFormWidget()->orderedDataAwareWidgets()->at( col ));
+ if (!item)
+ return -1;
+ KexiFormDataItemInterfaceToIntMap::ConstIterator it(m_fieldNumbersForDataItems.find( item ));
+ return it!=m_fieldNumbersForDataItems.constEnd() ? it.data() : -1;
+}*/
+
+bool KexiFormScrollView::columnEditable(int col)
+{
+ kexipluginsdbg << "KexiFormScrollView::columnEditable(" << col << ")" << endl;
+ foreach_list (QPtrListIterator<KexiFormDataItemInterface>, it, m_dataItems) {
+ kexipluginsdbg << (dynamic_cast<QWidget*>(it.current()) ? dynamic_cast<QWidget*>(it.current())->name() : "" )
+ << " " << it.current()->dataSource() << endl;
+ }
+ kexipluginsdbg << "-- focus widgets --" << endl;
+ foreach_list (QPtrListIterator<QWidget>, it, *dbFormWidget()->orderedFocusWidgets()) {
+ kexipluginsdbg << it.current()->name() << endl;
+ }
+ kexipluginsdbg << "-- data-aware widgets --" << endl;
+ foreach_list (QPtrListIterator<QWidget>, it, *dbFormWidget()->orderedDataAwareWidgets()) {
+ kexipluginsdbg << it.current()->name() << endl;
+ }
+
+ //int index = dbFormWidget()->indexForDataItem( item );
+// KexiFormDataItemInterface *item1 = dynamic_cast<KexiFormDataItemInterface*>(dbFormWidget()->orderedFocusWidgets()->at( col ));
+ KexiFormDataItemInterface *item = dynamic_cast<KexiFormDataItemInterface*>(dbFormWidget()->orderedDataAwareWidgets()->at( col ));
+
+ if (!item || item->isReadOnly())
+ return false;
+
+// KexiFormDataItemInterfaceToIntMap::ConstIterator it(m_fieldNumbersForDataItems.find( item ));
+// return KexiDataAwareObjectInterface::columnEditable( it!=m_fieldNumbersForDataItems.constEnd() ? it.data() : -1 );
+ return KexiDataAwareObjectInterface::columnEditable( col );
+}
+
+void KexiFormScrollView::valueChanged(KexiDataItemInterface* item)
+{
+ if (!item)
+ return;
+ //only signal start editing when no row editing was started already
+ kexipluginsdbg << "** KexiFormScrollView::valueChanged(): editedItem="
+ << (dbFormWidget()->editedItem ? dbFormWidget()->editedItem->value().toString() : QString::null)
+ << ", "
+ << (item ? item->value().toString() : QString::null)
+ << endl;
+ if (dbFormWidget()->editedItem!=item) {
+ kexipluginsdbg << "**>>> dbFormWidget()->editedItem = dynamic_cast<KexiFormDataItemInterface*>(item)" << endl;
+ dbFormWidget()->editedItem = dynamic_cast<KexiFormDataItemInterface*>(item);
+ startEditCurrentCell();
+ }
+ fillDuplicatedDataItems(dynamic_cast<KexiFormDataItemInterface*>(item), item->value());
+
+ //value changed: clear 'default value' mode (e.g. a blue italic text)
+ dynamic_cast<KexiFormDataItemInterface*>(item)->setDisplayDefaultValue(dynamic_cast<QWidget*>(item), false);
+}
+
+bool KexiFormScrollView::cursorAtNewRow() const
+{
+ return isInsertingEnabled() && ( m_currentItem==m_insertItem || m_newRowEditing );
+}
+
+void KexiFormScrollView::initDataContents()
+{
+ KexiDataAwareObjectInterface::initDataContents();
+
+ if (m_preview) {
+//! @todo here we can react if user wanted to show the navigator
+ setRecordNavigatorVisible(m_data);
+ recordNavigator()->setEnabled(m_data);
+ if (m_data) {
+ recordNavigator()->setEditingIndicatorEnabled( !isReadOnly() );
+ recordNavigator()->showEditingIndicator(false);
+ }
+
+ dbFormWidget()->updateReadOnlyFlags();
+ }
+}
+
+KexiTableViewColumn* KexiFormScrollView::column(int col)
+{
+ const int id = fieldNumberForColumn(col);
+ return (id >= 0) ? m_data->column( id ) : 0;
+}
+
+bool KexiFormScrollView::shouldDisplayDefaultValueForItem(KexiFormDataItemInterface* itemIface) const
+{
+ return cursorAtNewRow()
+ && !itemIface->columnInfo()->field->defaultValue().isNull()
+//?? && (m_editor ? m_editor->value()==itemIface->columnInfo()->field->defaultValue() : true)
+ && !itemIface->columnInfo()->field->isAutoIncrement(); // default value defined
+}
+
+bool KexiFormScrollView::cancelEditor()
+{
+ if (!dynamic_cast<KexiFormDataItemInterface*>(m_editor))
+ return false;
+
+ if (m_errorMessagePopup)
+ m_errorMessagePopup->close();
+
+ KexiFormDataItemInterface *itemIface = dynamic_cast<KexiFormDataItemInterface*>(m_editor);
+ itemIface->undoChanges();
+
+ const bool displayDefaultValue = shouldDisplayDefaultValueForItem(itemIface);
+ // now disable/enable "display default value" if needed (do it after setValue(), before setValue() turns it off)
+ if (itemIface->hasDisplayedDefaultValue() != displayDefaultValue)
+ itemIface->setDisplayDefaultValue( dynamic_cast<QWidget*>(itemIface), displayDefaultValue );
+
+ fillDuplicatedDataItems(itemIface, m_editor->value());
+
+ // this will clear editor pointer and close message popup (if present)
+ return KexiDataAwareObjectInterface::cancelEditor();
+}
+
+void KexiFormScrollView::updateAfterCancelRowEdit()
+{
+ for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current(); ++it) {
+ if (dynamic_cast<QWidget*>(it.current())) {
+ kexipluginsdbg << "KexiFormScrollView::updateAfterCancelRowEdit(): "
+ << dynamic_cast<QWidget*>(it.current())->className() << " "
+ << dynamic_cast<QWidget*>(it.current())->name() << endl;
+ }
+ KexiFormDataItemInterface *itemIface = it.current();
+ const bool displayDefaultValue = shouldDisplayDefaultValueForItem(itemIface);
+ itemIface->undoChanges();
+ if (itemIface->hasDisplayedDefaultValue() != displayDefaultValue)
+ itemIface->setDisplayDefaultValue( dynamic_cast<QWidget*>(itemIface), displayDefaultValue );
+ }
+ recordNavigator()->showEditingIndicator(false);
+ dbFormWidget()->editedItem = 0;
+}
+
+void KexiFormScrollView::updateAfterAcceptRowEdit()
+{
+ if (!m_currentItem)
+ return;
+ recordNavigator()->showEditingIndicator(false);
+ dbFormWidget()->editedItem = 0;
+ //update visible data because there could be auto-filled (eg. autonumber) fields
+ fillDataItems(*m_currentItem, cursorAtNewRow());
+ m_previousItem = m_currentItem;
+}
+
+void KexiFormScrollView::beforeSwitchView()
+{
+ m_editor = 0;
+}
+
+void KexiFormScrollView::refreshContentsSize()
+{
+ KexiScrollView::refreshContentsSize();
+ //only clear cmd history when KexiScrollView::refreshContentsSizeLater() has been called
+ if (!m_preview && sender()==&m_delayedResize) {
+ if (m_form)
+ m_form->clearCommandHistory();
+ }
+}
+
+void KexiFormScrollView::handleDataWidgetAction(const QString& actionName)
+{
+ QWidget *w = focusWidget();
+ KexiFormDataItemInterface *item = 0;
+ while (w) {
+ item = dynamic_cast<KexiFormDataItemInterface*>(w);
+ if (item)
+ break;
+ w = w->parentWidget();
+ }
+ if (item)
+ item->handleAction(actionName);
+}
+
+void KexiFormScrollView::copySelection()
+{
+ handleDataWidgetAction("edit_copy");
+}
+
+void KexiFormScrollView::cutSelection()
+{
+ handleDataWidgetAction("edit_cut");
+}
+
+void KexiFormScrollView::paste()
+{
+ handleDataWidgetAction("edit_paste");
+}
+
+int KexiFormScrollView::lastVisibleRow() const
+{
+//! @todo unimplemented for now, this will be used for continuous forms
+ return -1;
+}
+
+#include "kexiformscrollview.moc"
diff --git a/kexi/plugins/forms/kexiformscrollview.h b/kexi/plugins/forms/kexiformscrollview.h
new file mode 100644
index 000000000..123157613
--- /dev/null
+++ b/kexi/plugins/forms/kexiformscrollview.h
@@ -0,0 +1,297 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFORMSCROLLVIEW_H
+#define KEXIFORMSCROLLVIEW_H
+
+#include "kexidataprovider.h"
+#include "kexiformeventhandler.h"
+#include "widgets/kexidbform.h"
+#include <widget/kexiscrollview.h>
+#include <widget/utils/kexirecordnavigator.h>
+#include <widget/utils/kexisharedactionclient.h>
+#include <widget/tableview/kexidataawareobjectiface.h>
+
+//! @short KexiFormScrollView class provides a widget for displaying data in a form view
+/*! This class also implements:
+ - record navigation handling (KexiRecordNavigatorHandler)
+ - shared actions handling (KexiSharedActionClient)
+ - data-aware behaviour (KexiDataAwareObjectInterface)
+ - data provider bound to data-aware widgets (KexiFormDataProvider)
+
+ @see KexiTableView
+*/
+class KEXIFORMUTILS_EXPORT KexiFormScrollView :
+ public KexiScrollView,
+ public KexiRecordNavigatorHandler,
+ public KexiSharedActionClient,
+ public KexiDataAwareObjectInterface,
+ public KexiFormDataProvider,
+ public KexiFormEventHandler
+{
+ Q_OBJECT
+ KEXI_DATAAWAREOBJECTINTERFACE
+
+ public:
+ KexiFormScrollView(QWidget *parent, bool preview);
+ virtual ~KexiFormScrollView();
+
+ void setForm(KFormDesigner::Form *form) { m_form = form; }
+
+ /*! Reimplemented from KexiDataAwareObjectInterface
+ for checking 'readOnly' flag from a widget
+ ('readOnly' flag from data member is still checked though). */
+ virtual bool columnEditable(int col);
+
+ /*! \return number of visible columns in this view.
+ There can be a number of duplicated columns defined,
+ so columns() can return greater or smaller number than dataColumns(). */
+ virtual int columns() const;
+
+ /*! \return column information for column number \a col.
+ Reimplemented for KexiDataAwareObjectInterface:
+ column data corresponding to widget number is used here
+ (see fieldNumberForColumn()). */
+ virtual KexiTableViewColumn* column(int col);
+
+ /*! \return field number within data model connected to a data-aware
+ widget at column \a col. */
+ virtual int fieldNumberForColumn(int col) {
+ KexiFormDataItemInterface *item = dynamic_cast<KexiFormDataItemInterface*>(
+ dbFormWidget()->orderedDataAwareWidgets()->at( col ));
+ if (!item)
+ return -1;
+ KexiFormDataItemInterfaceToIntMap::ConstIterator it(m_fieldNumbersForDataItems.find( item ));
+ return it!=m_fieldNumbersForDataItems.constEnd() ? (int)it.data() : -1;
+ }
+
+ /*! @internal Used by KexiFormView in view switching. */
+ void beforeSwitchView();
+
+ /*! \return last row visible on the screen (counting from 0).
+ The returned value is guaranteed to be smaller or equal to currentRow() or -1
+ if there are no rows.
+ Implemented for KexiDataAwareObjectInterface. */
+//! @todo unimplemented for now, this will be used for continuous forms
+ virtual int lastVisibleRow() const;
+
+ /*! \return vertical scrollbar. Implemented for KexiDataAwareObjectInterface. */
+ virtual QScrollBar* verticalScrollBar() const { return KexiScrollView::verticalScrollBar(); }
+
+ public slots:
+ /*! Reimplemented to update resize policy. */
+ virtual void show();
+
+ //virtual void setFocus();
+
+ //! Implementation for KexiDataAwareObjectInterface
+ //! \return arbitraty value of 10.
+ virtual int rowsPerPage() const;
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual void ensureCellVisible(int row, int col/*=-1*/);
+
+ virtual void moveToRecordRequested(uint r);
+ virtual void moveToLastRecordRequested();
+ virtual void moveToPreviousRecordRequested();
+ virtual void moveToNextRecordRequested();
+ virtual void moveToFirstRecordRequested();
+ virtual void addNewRecordRequested() { KexiDataAwareObjectInterface::addNewRecordRequested(); }
+
+ /*! Cancels changes made to the currently active editor.
+ Reverts the editor's value to old one.
+ \return true on success or false on failure (e.g. when editor does not exist) */
+ virtual bool cancelEditor();
+
+ public slots:
+ /*! Reimplemented to also clear command history right after final resize. */
+ virtual void refreshContentsSize();
+
+ /*! Handles verticalScrollBar()'s valueChanged(int) signal.
+ Called when vscrollbar's value has been changed. */
+//! @todo unused for now, will be used for continuous forms
+ virtual void vScrollBarValueChanged(int v) { KexiDataAwareObjectInterface::vScrollBarValueChanged(v); }
+
+ /*! Handles sliderReleased() signal of the verticalScrollBar(). Used to hide the "row number" tooltip. */
+//! @todo unused for now, will be used for continuous forms
+ virtual void vScrollBarSliderReleased() { KexiDataAwareObjectInterface::vScrollBarSliderReleased(); }
+
+ /*! Handles timeout() signal of the m_scrollBarTipTimer. If the tooltip is visible,
+ m_scrollBarTipTimerCnt is set to 0 and m_scrollBarTipTimerCnt is restarted;
+ else the m_scrollBarTipTimerCnt is just set to 0.*/
+//! @todo unused for now, will be used for continuous forms
+ virtual void scrollBarTipTimeout() { KexiDataAwareObjectInterface::scrollBarTipTimeout(); }
+
+ signals:
+ virtual void itemChanged(KexiTableItem *, int row, int col);
+ virtual void itemChanged(KexiTableItem *, int row, int col, QVariant oldValue);
+ virtual void itemDeleteRequest(KexiTableItem *, int row, int col);
+ virtual void currentItemDeleteRequest();
+ virtual void newItemAppendedForAfterDeletingInSpreadSheetMode(); //!< does nothing
+ virtual void dataRefreshed();
+ virtual void dataSet( KexiTableViewData *data );
+ virtual void itemSelected(KexiTableItem *);
+ virtual void cellSelected(int col, int row);
+ virtual void sortedColumnChanged(int col);
+ virtual void rowEditStarted(int row);
+ virtual void rowEditTerminated(int row);
+ virtual void reloadActions();
+
+ protected slots:
+ void slotResizingStarted();
+
+ //! Handles KexiTableViewData::rowRepaintRequested() signal
+ virtual void slotRowRepaintRequested(KexiTableItem& item);
+
+ //! Handles KexiTableViewData::aboutToDeleteRow() signal. Prepares info for slotRowDeleted().
+ virtual void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint)
+ { KexiDataAwareObjectInterface::slotAboutToDeleteRow(item, result, repaint); }
+
+ //! Handles KexiTableViewData::rowDeleted() signal to repaint when needed.
+ virtual void slotRowDeleted() { KexiDataAwareObjectInterface::slotRowDeleted(); }
+
+ //! Handles KexiTableViewData::rowInserted() signal to repaint when needed.
+ virtual void slotRowInserted(KexiTableItem *item, bool repaint);
+
+ //! Like above, not db-aware version
+ virtual void slotRowInserted(KexiTableItem *item, uint row, bool repaint);
+
+ virtual void slotRowsDeleted( const QValueList<int> & );
+
+ virtual void slotDataDestroying() { KexiDataAwareObjectInterface::slotDataDestroying(); }
+
+ /*! Reloads data for this widget.
+ Handles KexiTableViewData::reloadRequested() signal. */
+ virtual void reloadData() { KexiDataAwareObjectInterface::reloadData(); }
+
+ //! Copy current selection to a clipboard (e.g. cell)
+ virtual void copySelection();
+
+ //! Cut current selection to a clipboard (e.g. cell)
+ virtual void cutSelection();
+
+ //! Paste current clipboard contents (e.g. to a cell)
+ virtual void paste();
+
+ protected:
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual void clearColumnsInternal(bool repaint);
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual void addHeaderColumn(const QString& caption, const QString& description,
+ const QIconSet& icon, int width);
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual int currentLocalSortingOrder() const;
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual int currentLocalSortColumn() const;
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual void setLocalSortingOrder(int col, int order);
+
+ //! Implementation for KexiDataAwareObjectInterface
+ void sortColumnInternal(int col, int order = 0);
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual void updateGUIAfterSorting();
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual void createEditor(int row, int col, const QString& addText = QString::null,
+ bool removeOld = false);
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual KexiDataItemInterface *editor( int col, bool ignoreMissingEditor = false );
+
+ //! Implementation for KexiDataAwareObjectInterface
+ virtual void editorShowFocus( int row, int col );
+
+ /*! Implementation for KexiDataAwareObjectInterface
+ Redraws specified cell. */
+ virtual void updateCell(int row, int col);
+
+ /*! Redraws the current cell. Implemented after KexiDataAwareObjectInterface. */
+ virtual void updateCurrentCell();
+
+ /*! Implementation for KexiDataAwareObjectInterface
+ Redraws all cells of specified row. */
+ virtual void updateRow(int row);
+
+ /*! Implementation for KexiDataAwareObjectInterface
+ Updates contents of the widget. Just call update() here on your widget. */
+ virtual void updateWidgetContents();
+
+ /*! Implementation for KexiDataAwareObjectInterface
+ Implementation for KexiDataAwareObjectInterface
+ Updates widget's contents size e.g. using QScrollView::resizeContents(). */
+ virtual void updateWidgetContentsSize();
+
+ /*! Implementation for KexiDataAwareObjectInterface
+ Updates scrollbars of the widget.
+ QScrollView::updateScrollbars() will be usually called here. */
+ virtual void updateWidgetScrollBars();
+
+ KexiDBForm* dbFormWidget() const;
+
+ //! Reimplemented from KexiFormDataProvider. Reaction for change of \a item.
+ virtual void valueChanged(KexiDataItemInterface* item);
+
+ /*! Reimplemented from KexiFormDataProvider.
+ \return information whether we're currently at new row or now.
+ This can be used e.g. by data-aware widgets to determine if "(autonumber)"
+ label should be displayed. */
+ virtual bool cursorAtNewRow() const;
+
+ //! Implementation for KexiDataAwareObjectInterface
+ //! Called by KexiDataAwareObjectInterface::setCursorPosition()
+ //! if cursor's position is really changed.
+ inline virtual void selectCellInternal();
+
+ /*! Reimplementation: used to refresh "editing indicator" visibility. */
+ virtual void initDataContents();
+
+ /*! @internal
+ Updates row appearance after canceling row edit.
+ Reimplemented from KexiDataAwareObjectInterface: just undoes changes for every data item.
+ Used by cancelRowEdit(). */
+ virtual void updateAfterCancelRowEdit();
+
+ /*! @internal
+ Updates row appearance after accepting row edit.
+ Reimplemented from KexiDataAwareObjectInterface: just clears 'edit' indicator.
+ Used by cancelRowEdit(). */
+ virtual void updateAfterAcceptRowEdit();
+
+ /*! @internal
+ Used to invoke copy/paste/cut etc. actions at the focused widget's level. */
+ void handleDataWidgetAction(const QString& actionName);
+
+ /*! @internal */
+ bool shouldDisplayDefaultValueForItem(KexiFormDataItemInterface* itemIface) const;
+
+ //virtual bool focusNextPrevChild( bool next );
+
+ KFormDesigner::Form *m_form;
+ int m_currentLocalSortColumn, m_localSortingOrder;
+ //! Used in selectCellInternal() to avoid fetching the same record twice
+ KexiTableItem *m_previousItem;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kexiformview.cpp b/kexi/plugins/forms/kexiformview.cpp
new file mode 100644
index 000000000..7e52e5b6e
--- /dev/null
+++ b/kexi/plugins/forms/kexiformview.cpp
@@ -0,0 +1,1278 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiformview.h"
+
+#include <qobjectlist.h>
+#include <qfileinfo.h>
+
+#include <formeditor/form.h>
+#include <formeditor/formIO.h>
+#include <formeditor/formmanager.h>
+#include <formeditor/objecttree.h>
+#include <formeditor/container.h>
+#include <formeditor/widgetpropertyset.h>
+#include <formeditor/commands.h>
+#include <formeditor/widgetwithsubpropertiesinterface.h>
+#include <formeditor/objecttree.h>
+
+#include <kexi.h>
+#include <kexidialogbase.h>
+#include <kexidragobjects.h>
+#include <kexidb/field.h>
+#include <kexidb/fieldlist.h>
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+#include <kexidb/utils.h>
+#include <kexidb/preparedstatement.h>
+#include <tableview/kexitableitem.h>
+#include <tableview/kexitableviewdata.h>
+#include <widget/kexipropertyeditorview.h>
+#include <widget/kexiqueryparameters.h>
+#include <kexiutils/utils.h>
+
+#include <koproperty/set.h>
+#include <koproperty/property.h>
+
+#include "widgets/kexidbform.h"
+#include "kexiformscrollview.h"
+#include "kexidatasourcepage.h"
+#include "widgets/kexidbautofield.h"
+
+#define NO_DSWIZARD
+
+//! @todo #define KEXI_SHOW_SPLITTER_WIDGET
+
+KexiFormView::KexiFormView(KexiMainWindow *mainWin, QWidget *parent,
+ const char *name, bool /*dbAware*/)
+ : KexiDataAwareView( mainWin, parent, name )
+ , m_propertySet(0)
+ , m_resizeMode(KexiFormView::ResizeDefault)
+ , m_query(0)
+ , m_queryIsOwned(false)
+ , m_cursor(0)
+// , m_firstFocusWidget(0)
+{
+ m_delayedFormContentsResizeOnShow = 0;
+
+ QHBoxLayout *l = new QHBoxLayout(this);
+ l->setAutoAdd(true);
+
+ m_scrollView = new KexiFormScrollView(this, viewMode()==Kexi::DataViewMode);
+
+//moved setViewWidget(m_scrollView);
+// m_scrollView->show();
+
+ m_dbform = new KexiDBForm(m_scrollView->viewport(), m_scrollView, name/*, conn*/);
+// m_dbform->resize( m_scrollView->viewport()->size() - QSize(20, 20) );
+// m_dbform->resize(QSize(400, 300));
+ m_scrollView->setWidget(m_dbform);
+ m_scrollView->setResizingEnabled(viewMode()!=Kexi::DataViewMode);
+
+// initForm();
+
+ if (viewMode()==Kexi::DataViewMode) {
+ m_scrollView->recordNavigator()->setRecordHandler( m_scrollView );
+ m_scrollView->viewport()->setPaletteBackgroundColor(m_dbform->palette().active().background());
+//moved to formmanager connect(formPart()->manager(), SIGNAL(noFormSelected()), SLOT(slotNoFormSelected()));
+ }
+ else
+ {
+ connect(KFormDesigner::FormManager::self(), SIGNAL(propertySetSwitched(KoProperty::Set*, bool, const QCString&)),
+ this, SLOT(slotPropertySetSwitched(KoProperty::Set*, bool, const QCString&)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(dirty(KFormDesigner::Form *, bool)),
+ this, SLOT(slotDirty(KFormDesigner::Form *, bool)));
+
+ connect(m_dbform, SIGNAL(handleDragMoveEvent(QDragMoveEvent*)),
+ this, SLOT(slotHandleDragMoveEvent(QDragMoveEvent*)));
+ connect(m_dbform, SIGNAL(handleDropEvent(QDropEvent*)),
+ this, SLOT(slotHandleDropEvent(QDropEvent*)));
+
+ // action stuff
+ plugSharedAction("formpart_taborder", KFormDesigner::FormManager::self(), SLOT(editTabOrder()));
+ plugSharedAction("formpart_adjust_size", KFormDesigner::FormManager::self(), SLOT(adjustWidgetSize()));
+//TODO plugSharedAction("formpart_pixmap_collection", formPart()->manager(), SLOT(editFormPixmapCollection()));
+//TODO plugSharedAction("formpart_connections", formPart()->manager(), SLOT(editConnections()));
+
+ plugSharedAction("edit_copy", KFormDesigner::FormManager::self(), SLOT(copyWidget()));
+ plugSharedAction("edit_cut", KFormDesigner::FormManager::self(), SLOT(cutWidget()));
+ plugSharedAction("edit_paste", KFormDesigner::FormManager::self(), SLOT(pasteWidget()));
+ plugSharedAction("edit_delete", KFormDesigner::FormManager::self(), SLOT(deleteWidget()));
+ plugSharedAction("edit_select_all", KFormDesigner::FormManager::self(), SLOT(selectAll()));
+ plugSharedAction("formpart_clear_contents", KFormDesigner::FormManager::self(), SLOT(clearWidgetContent()));
+ plugSharedAction("edit_undo", KFormDesigner::FormManager::self(), SLOT(undo()));
+ plugSharedAction("edit_redo", KFormDesigner::FormManager::self(), SLOT(redo()));
+
+ plugSharedAction("formpart_layout_menu", KFormDesigner::FormManager::self(), 0 );
+ plugSharedAction("formpart_layout_hbox", KFormDesigner::FormManager::self(), SLOT(layoutHBox()) );
+ plugSharedAction("formpart_layout_vbox", KFormDesigner::FormManager::self(), SLOT(layoutVBox()) );
+ plugSharedAction("formpart_layout_grid", KFormDesigner::FormManager::self(), SLOT(layoutGrid()) );
+#ifdef KEXI_SHOW_SPLITTER_WIDGET
+ plugSharedAction("formpart_layout_hsplitter", KFormDesigner::FormManager::self(), SLOT(layoutHSplitter()) );
+ plugSharedAction("formpart_layout_vsplitter", KFormDesigner::FormManager::self(), SLOT(layoutVSplitter()) );
+#endif
+ plugSharedAction("formpart_break_layout", KFormDesigner::FormManager::self(), SLOT(breakLayout()) );
+
+ plugSharedAction("formpart_format_raise", KFormDesigner::FormManager::self(), SLOT(bringWidgetToFront()) );
+ plugSharedAction("formpart_format_lower", KFormDesigner::FormManager::self(), SLOT(sendWidgetToBack()) );
+
+ plugSharedAction("other_widgets_menu", KFormDesigner::FormManager::self(), 0 );
+ setAvailable("other_widgets_menu", true);
+
+ plugSharedAction("formpart_align_menu", KFormDesigner::FormManager::self(), 0 );
+ plugSharedAction("formpart_align_to_left", KFormDesigner::FormManager::self(),SLOT(alignWidgetsToLeft()) );
+ plugSharedAction("formpart_align_to_right", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToRight()) );
+ plugSharedAction("formpart_align_to_top", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToTop()) );
+ plugSharedAction("formpart_align_to_bottom", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToBottom()) );
+ plugSharedAction("formpart_align_to_grid", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToGrid()) );
+
+ plugSharedAction("formpart_adjust_size_menu", KFormDesigner::FormManager::self(), 0 );
+ plugSharedAction("formpart_adjust_to_fit", KFormDesigner::FormManager::self(), SLOT(adjustWidgetSize()) );
+ plugSharedAction("formpart_adjust_size_grid", KFormDesigner::FormManager::self(), SLOT(adjustSizeToGrid()) );
+ plugSharedAction("formpart_adjust_height_small", KFormDesigner::FormManager::self(), SLOT(adjustHeightToSmall()) );
+ plugSharedAction("formpart_adjust_height_big", KFormDesigner::FormManager::self(), SLOT(adjustHeightToBig()) );
+ plugSharedAction("formpart_adjust_width_small", KFormDesigner::FormManager::self(), SLOT(adjustWidthToSmall()) );
+ plugSharedAction("formpart_adjust_width_big", KFormDesigner::FormManager::self(), SLOT(adjustWidthToBig()) );
+
+ plugSharedAction("format_font", KFormDesigner::FormManager::self(), SLOT(changeFont()) );
+ }
+
+ initForm();
+
+ KexiDataAwareView::init( m_scrollView, m_scrollView, m_scrollView,
+ /* skip data-awarness if design mode */ viewMode()==Kexi::DesignViewMode );
+
+ connect(this, SIGNAL(focus(bool)), this, SLOT(slotFocus(bool)));
+ /// @todo skip this if ther're no borders
+// m_dbform->resize( m_dbform->size()+QSize(m_scrollView->verticalScrollBar()->width(), m_scrollView->horizontalScrollBar()->height()) );
+}
+
+KexiFormView::~KexiFormView()
+{
+ if (m_cursor) {
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ conn->deleteCursor(m_cursor);
+ m_cursor = 0;
+ }
+ deleteQuery();
+
+ // Important: form window is closed.
+ // Set property set to 0 because there is *only one* instance of a property set class
+ // in Kexi, so the main window wouldn't know the set in fact has been changed.
+ m_propertySet = 0;
+ propertySetSwitched();
+}
+
+void
+KexiFormView::deleteQuery()
+{
+ if (m_cursor) {
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ conn->deleteCursor(m_cursor);
+ m_cursor = 0;
+ }
+
+ if (m_queryIsOwned) {
+ delete m_query;
+ } else {
+//! @todo remove this shared query from listened queries list
+ }
+ m_query = 0;
+}
+
+KFormDesigner::Form*
+KexiFormView::form() const
+{
+ if(viewMode()==Kexi::DataViewMode)
+ return tempData()->previewForm;
+ else
+ return tempData()->form;
+}
+
+void
+KexiFormView::setForm(KFormDesigner::Form *f)
+{
+ if(viewMode()==Kexi::DataViewMode)
+ tempData()->previewForm = f;
+ else
+ tempData()->form = f;
+}
+
+void
+KexiFormView::initForm()
+{
+ setForm( new KFormDesigner::Form(KexiFormPart::library(), 0, viewMode()==Kexi::DesignViewMode) );
+// if (viewMode()==Kexi::DataViewMode)
+ //form()->setDesignMode(false);
+ form()->createToplevel(m_dbform, m_dbform);
+
+ if (viewMode()==Kexi::DesignViewMode) {
+ //we want to be informed about executed commands
+ connect(form()->commandHistory(), SIGNAL(commandExecuted()),
+ KFormDesigner::FormManager::self(), SLOT(slotHistoryCommandExecuted()));
+ }
+
+ const bool newForm = parentDialog()->id() < 0;
+
+ KexiDB::FieldList *fields = 0;
+ if (newForm) {
+ // Show the form wizard if this is a new Form
+#ifndef NO_DSWIZARD
+ KexiDataSourceWizard *w = new KexiDataSourceWizard(mainWin(), (QWidget*)mainWin(), "datasource_wizard");
+ if(!w->exec())
+ fields = 0;
+ else
+ fields = w->fields();
+ delete w;
+#endif
+ }
+
+ if(fields)
+ {
+ QDomDocument dom;
+ formPart()->generateForm(fields, dom);
+ KFormDesigner::FormIO::loadFormFromDom(form(), m_dbform, dom);
+ //! @todo handle errors
+ }
+ else
+ loadForm();
+
+ if(form()->autoTabStops())
+ form()->autoAssignTabStops();
+
+ //collect tab order information
+ m_dbform->updateTabStopsOrder(form());
+
+// if (m_dbform->orderedFocusWidgets()->first())
+ // m_scrollView->setFocusProxy( m_dbform->orderedFocusWidgets()->first() );
+
+ KFormDesigner::FormManager::self()->importForm(form(), viewMode()==Kexi::DataViewMode);
+ m_scrollView->setForm(form());
+
+// m_dbform->updateTabStopsOrder(form());
+// QSize s = m_dbform->size();
+// QApplication::sendPostedEvents();
+// m_scrollView->resize( s );
+// m_dbform->resize(s);
+ m_scrollView->refreshContentsSize();
+// m_scrollView->refreshContentsSizeLater(true,true);
+
+ if (newForm && !fields) {
+ /* Our form's area will be resized more than once.
+ Let's resize form widget itself later. */
+ m_delayedFormContentsResizeOnShow = 3;
+ }
+
+ updateDataSourcePage();
+
+ if (!newForm && viewMode()==Kexi::DesignViewMode) {
+ form()->clearCommandHistory();
+ }
+}
+
+void KexiFormView::updateAutoFieldsDataSource()
+{
+//! @todo call this when form's data source is changed
+ //update autofields:
+ //-inherit captions
+ //-inherit data types
+ //(this data has not been stored in the form)
+ QString dataSourceString( m_dbform->dataSource() );
+ QCString dataSourceMimeTypeString( m_dbform->dataSourceMimeType() );
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ KexiDB::TableOrQuerySchema tableOrQuery(
+ conn, dataSourceString.latin1(), dataSourceMimeTypeString=="kexi/table");
+ if (!tableOrQuery.table() && !tableOrQuery.query())
+ return;
+ for (KFormDesigner::ObjectTreeDictIterator it(*form()->objectTree()->dict());
+ it.current(); ++it)
+ {
+ KexiDBAutoField *afWidget = dynamic_cast<KexiDBAutoField*>( it.current()->widget() );
+ if (afWidget) {
+ KexiDB::QueryColumnInfo *colInfo = tableOrQuery.columnInfo( afWidget->dataSource() );
+ if (colInfo) {
+ afWidget->setColumnInfo(colInfo);
+ //setFieldTypeInternal((int)colInfo->field->type());
+ //afWidget->setFieldCaptionInternal(colInfo->captionOrAliasOrName());
+ }
+ }
+ }
+}
+
+void KexiFormView::updateValuesForSubproperties()
+{
+//! @todo call this when form's data source is changed
+ //update autofields:
+ //-inherit captions
+ //-inherit data types
+ //(this data has not been stored in the form)
+ QString dataSourceString( m_dbform->dataSource() );
+ QCString dataSourceMimeTypeString( m_dbform->dataSourceMimeType() );
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ KexiDB::TableOrQuerySchema tableOrQuery(
+ conn, dataSourceString.latin1(), dataSourceMimeTypeString=="kexi/table");
+ if (!tableOrQuery.table() && !tableOrQuery.query())
+ return;
+
+ for (KFormDesigner::ObjectTreeDictIterator it(*form()->objectTree()->dict());
+ it.current(); ++it)
+ {
+ // (delayed) set values for subproperties
+//! @todo this could be at the KFD level, but KFD is going to be merged anyway with kexiforms, right?
+ KFormDesigner::WidgetWithSubpropertiesInterface* subpropIface
+ = dynamic_cast<KFormDesigner::WidgetWithSubpropertiesInterface*>( it.current()->widget() );
+ if (subpropIface && subpropIface->subwidget() && it.current()->subproperties() ) {
+ QWidget *subwidget = subpropIface->subwidget();
+ QMap<QString, QVariant>* subprops = it.current()->subproperties();
+ for (QMapConstIterator<QString, QVariant> subpropIt = subprops->constBegin(); subpropIt!=subprops->constEnd(); ++subpropIt) {
+ kexipluginsdbg << "KexiFormView::loadForm(): delayed setting of the subproperty: widget="
+ << it.current()->widget()->name() << " prop=" << subpropIt.key() << " val=" << subpropIt.data() << endl;
+
+ const int count = subwidget->metaObject()->findProperty(subpropIt.key().latin1(), true);
+ const QMetaProperty *meta = count!=-1 ? subwidget->metaObject()->property(count, true) : 0;
+ if (meta) {
+ // Special case: the property value of type enum (set) but is saved as a string list,
+ // not as int, so we need to translate it to int. It's been created as such
+ // by FormIO::readPropertyValue(). Example: "alignment" property.
+ if (meta->isSetType() && subpropIt.data().type()==QVariant::StringList) {
+ QStrList keys;
+ const QStringList list( subpropIt.data().toStringList() );
+ for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it)
+ keys.append((*it).latin1());
+ subwidget->setProperty( subpropIt.key().latin1(), meta->keysToValue(keys) );
+ }
+ else {
+ subwidget->setProperty( subpropIt.key().latin1(), subpropIt.data() );
+ }
+ }
+ }//for
+ }
+ }
+}
+
+//! Used in KexiFormView::loadForm()
+static void setUnsavedBLOBIdsForDataViewMode(
+ QWidget* widget, const QMap<QCString, KexiBLOBBuffer::Id_t>& unsavedLocalBLOBsByName)
+{
+ if (-1 != widget->metaObject()->findProperty("pixmapId")) {
+ const KexiBLOBBuffer::Id_t blobID = unsavedLocalBLOBsByName[ widget->name() ];
+ if (blobID > 0)
+ widget->setProperty("pixmapId", (uint /* KexiBLOBBuffer::Id_t is unsafe and unsupported by QVariant - will be fixed in Qt4*/)blobID);
+ }
+ const QObjectList *list = widget->children();
+ if (!list)
+ return;
+ for (QObjectListIterator it(*list); it.current(); ++it) {
+ if (dynamic_cast<QWidget*>(it.current()))
+ setUnsavedBLOBIdsForDataViewMode(dynamic_cast<QWidget*>(it.current()), unsavedLocalBLOBsByName);
+ }
+}
+
+void
+KexiFormView::loadForm()
+{
+//@todo also load m_resizeMode !
+
+ kexipluginsdbg << "KexiFormView::loadForm() Loading the form with id : " << parentDialog()->id() << endl;
+ // If we are previewing the Form, use the tempData instead of the form stored in the db
+ if(viewMode()==Kexi::DataViewMode && !tempData()->tempForm.isNull() )
+ {
+ KFormDesigner::FormIO::loadFormFromString(form(), m_dbform, tempData()->tempForm);
+ setUnsavedBLOBIdsForDataViewMode( m_dbform, tempData()->unsavedLocalBLOBsByName );
+ updateAutoFieldsDataSource();
+ updateValuesForSubproperties();
+ return;
+ }
+
+ // normal load
+ QString data;
+ loadDataBlock(data);
+ KFormDesigner::FormIO::loadFormFromString(form(), m_dbform, data);
+
+ //"autoTabStops" property is loaded -set it within the form tree as well
+ form()->setAutoTabStops( m_dbform->autoTabStops() );
+
+ updateAutoFieldsDataSource();
+ updateValuesForSubproperties();
+}
+
+void
+KexiFormView::slotPropertySetSwitched(KoProperty::Set *set, bool forceReload, const QCString& propertyToSelect)
+{
+// if (set && parentDialog()!=parentDialog()->mainWin()->currentDialog())
+ if (form() != KFormDesigner::FormManager::self()->activeForm())
+ return; //this is not the current form view
+ m_propertySet = set;
+ if (forceReload)
+ propertySetReloaded(true/*preservePrevSelection*/, propertyToSelect);
+ else
+ propertySetSwitched();
+
+ formPart()->dataSourcePage()->assignPropertySet(m_propertySet);
+}
+
+tristate
+KexiFormView::beforeSwitchTo(int mode, bool &dontStore)
+{
+ if (mode!=viewMode()) {
+ if (viewMode()==Kexi::DataViewMode) {
+ if (!m_scrollView->acceptRowEdit())
+ return cancelled;
+
+ m_scrollView->beforeSwitchView();
+ }
+ else {
+ //remember our pos
+ tempData()->scrollViewContentsPos
+ = QPoint(m_scrollView->contentsX(), m_scrollView->contentsY());
+ }
+ }
+
+ // we don't store on db, but in our TempData
+ dontStore = true;
+ if(dirty() && (mode == Kexi::DataViewMode) && form()->objectTree()) {
+ KexiFormPart::TempData* temp = tempData();
+ if (!KFormDesigner::FormIO::saveFormToString(form(), temp->tempForm))
+ return false;
+
+ //collect blobs from design mode by name for use in data view mode
+ temp->unsavedLocalBLOBsByName.clear();
+ for (QMapConstIterator<QWidget*, KexiBLOBBuffer::Id_t> it = temp->unsavedLocalBLOBs.constBegin();
+ it!=temp->unsavedLocalBLOBs.constEnd(); ++it)
+ {
+ if (!it.key())
+ continue;
+ temp->unsavedLocalBLOBsByName.insert( it.key()->name(), it.data() );
+ }
+ }
+
+ return true;
+}
+
+tristate
+KexiFormView::afterSwitchFrom(int mode)
+{
+ if (mode == 0 || mode == Kexi::DesignViewMode) {
+ if (parentDialog()->neverSaved()) {
+ m_dbform->resize(QSize(400, 300));
+ m_scrollView->refreshContentsSizeLater(true,true);
+ //m_delayedFormContentsResizeOnShow = false;
+ }
+ }
+
+ if (mode != 0 && mode != Kexi::DesignViewMode) {
+ //preserve contents pos after switching to other view
+ m_scrollView->setContentsPos(tempData()->scrollViewContentsPos.x(),
+ tempData()->scrollViewContentsPos.y());
+ }
+// if (mode == Kexi::DesignViewMode) {
+ //m_scrollView->move(0,0);
+ //m_scrollView->setContentsPos(0,0);
+ //m_scrollView->moveChild(m_dbform, 0, 0);
+// }
+
+ if((mode == Kexi::DesignViewMode) && viewMode()==Kexi::DataViewMode) {
+ // The form may have been modified, so we must recreate the preview
+ delete m_dbform; // also deletes form()
+ m_dbform = new KexiDBForm(m_scrollView->viewport(), m_scrollView, "KexiDBForm");
+ m_scrollView->setWidget(m_dbform);
+
+ initForm();
+//moved to formmanager slotNoFormSelected();
+
+ //reset position
+ m_scrollView->setContentsPos(0,0);
+ m_dbform->move(0,0);
+
+ }
+
+ //update tab stops if needed
+ if (viewMode()==Kexi::DataViewMode) {
+// //propagate current "autoTabStops" property value to the form tree
+// form()->setAutoTabStops( m_dbform->autoTabStops() );
+
+// if(form()->autoTabStops())
+// form()->autoAssignTabStops();
+ }
+ else {
+ //set "autoTabStops" property
+ m_dbform->setAutoTabStops( form()->autoTabStops() );
+ }
+
+ if (viewMode() == Kexi::DataViewMode) {
+//TMP!!
+ initDataSource();
+
+ //handle events for this form
+ m_scrollView->setMainWidgetForEventHandling(parentDialog()->mainWin(), m_dbform);
+
+ //set focus on 1st focusable widget which has valid dataSource property set
+ if (!m_dbform->orderedFocusWidgets()->isEmpty()) {
+// QWidget *www = focusWidget();
+ //if (Kexi::hasParent(this, qApp->focusWidget())) {
+ KexiUtils::unsetFocusWithReason(qApp->focusWidget(), QFocusEvent::Tab);
+ //}
+
+ QPtrListIterator<QWidget> it(*m_dbform->orderedFocusWidgets());
+ for (;it.current(); ++it) {
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>(it.current());
+ if (iface)
+ kexipluginsdbg << iface->dataSource() << endl;
+ if (iface && iface->columnInfo() && !iface->isReadOnly()
+/*! @todo add option for skipping autoincremented fields */
+ /* also skip autoincremented fields:*/
+ && !iface->columnInfo()->field->isAutoIncrement()) //!iface->dataSource().isEmpty()
+ break;
+ }
+ if (!it.current()) //eventually, focus first available widget if nothing other is available
+ it.toFirst();
+
+ it.current()->setFocus();
+ KexiUtils::setFocusWithReason(it.current(), QFocusEvent::Tab);
+ m_setFocusInternalOnce = it.current();
+ }
+
+ if (m_query)
+ m_scrollView->selectFirstRow();
+ }
+
+ //dirty only if it's a new object
+ if (mode == 0)
+ setDirty( parentDialog()->partItem()->neverSaved() );
+
+ if (mode==Kexi::DataViewMode && viewMode()==Kexi::DesignViewMode) {
+// slotPropertySetSwitched
+// emit KFormDesigner::FormManager::self()->propertySetSwitched( KFormDesigner::FormManager::self()->propertySet()->set(), true );
+ }
+
+ return true;
+}
+
+void KexiFormView::initDataSource()
+{
+ deleteQuery();
+ QString dataSourceString( m_dbform->dataSource() );
+ QCString dataSourceMimeTypeString( m_dbform->dataSourceMimeType() );
+//! @todo also handle anonymous (not stored) queries provided as statements here
+ bool ok = !dataSourceString.isEmpty();
+
+/* if (m_previousDataSourceString.lower()==dataSourceString.lower() && !m_cursor) {
+ //data source changed: delete previous cursor
+ m_conn->deleteCursor(m_cursor);
+ m_cursor = 0;
+ }*/
+
+ KexiDB::TableSchema *tableSchema = 0;
+ KexiDB::Connection *conn = 0;
+ QStringList sources;
+ bool forceReadOnlyDataSource = false;
+
+ if (ok) {
+// m_previousDataSourceString = dataSourceString;
+
+ //collect all data-aware widgets and create query schema
+ m_scrollView->setMainDataSourceWidget(m_dbform);
+ sources = m_scrollView->usedDataSources();
+ conn = parentDialog()->mainWin()->project()->dbConnection();
+ if (dataSourceMimeTypeString.isEmpty() /*table type is the default*/
+ || dataSourceMimeTypeString=="kexi/table")
+ {
+ tableSchema = conn->tableSchema( dataSourceString );
+ if (tableSchema) {
+ /* We will build a _minimum_ query schema from selected table fields. */
+ m_query = new KexiDB::QuerySchema();
+ m_queryIsOwned = true;
+
+ if (dataSourceMimeTypeString.isEmpty())
+ m_dbform->setDataSourceMimeType("kexi/table"); //update for compatibility
+ }
+ }
+
+ if (!tableSchema) {
+ if (dataSourceMimeTypeString.isEmpty() /*also try to find a query (for compatibility with Kexi<=0.9)*/
+ || dataSourceMimeTypeString=="kexi/query")
+ {
+ //try to find predefined query schema.
+ //Note: In general, we could not skip unused fields within this query because
+ // it can have GROUP BY clause.
+ //! @todo check if the query could have skipped unused fields (no GROUP BY, no joins, etc.)
+ m_query = conn->querySchema( dataSourceString );
+ m_queryIsOwned = false;
+ ok = m_query != 0;
+ if (ok && dataSourceMimeTypeString.isEmpty())
+ m_dbform->setDataSourceMimeType("kexi/query"); //update for compatibility
+ // query results are read-only
+//! @todo There can be read-write queries, e.g. simple "SELECT * FROM...". Add a checking function to KexiDB.
+ forceReadOnlyDataSource = true;
+ }
+ else //no other mime types supported
+ ok = false;
+ }
+ }
+
+ QDict<char> invalidSources(997);
+ if (ok) {
+ KexiDB::IndexSchema *pkey = tableSchema ? tableSchema->primaryKey() : 0;
+ if (pkey) {
+ //always add all fields from table's primary key
+ // (don't worry about duplicates, unique list will be computed later)
+ sources += pkey->names();
+ kexipluginsdbg << "KexiFormView::initDataSource(): pkey added to data sources: " << pkey->names() << endl;
+ }
+ kexipluginsdbg << "KexiFormView::initDataSource(): sources=" << sources << endl;
+
+ uint index = 0;
+ for (QStringList::ConstIterator it = sources.constBegin();
+ it!=sources.constEnd(); ++it, index++) {
+/*! @todo add expression support */
+ QString fieldName( (*it).lower() );
+ //remove "tablename." if it was prepended
+ if (tableSchema && fieldName.startsWith( tableSchema->name().lower()+"." ))
+ fieldName = fieldName.mid(tableSchema->name().length()+1);
+ //remove "queryname." if it was prepended
+ if (!tableSchema && fieldName.startsWith( m_query->name().lower()+"." ))
+ fieldName = fieldName.mid(m_query->name().length()+1);
+ KexiDB::Field *f = tableSchema ? tableSchema->field(fieldName) : m_query->field(fieldName);
+ if (!f) {
+/*! @todo show error */
+ //remove this widget from the set of data widgets in the provider
+/*! @todo fieldName is ok, but what about expressions? */
+ invalidSources.insert( fieldName, (const char*)1 ); // += index;
+ kexipluginsdbg << "KexiFormView::initDataSource(): invalidSources+=" << index << " ("
+ << (*it) << ")" << endl;
+ continue;
+ }
+ if (tableSchema) {
+ if (!m_query->hasField( f )) {
+ //we're building a new query: add this field
+ m_query->addField( f );
+ }
+ }
+ }
+ if (invalidSources.count()==sources.count()) {
+ //all data sources are invalid! don't execute the query
+ deleteQuery();
+ }
+ else {
+ KexiDB::debug( m_query->parameters() );
+ // like in KexiQueryView::executeQuery()
+ QValueList<QVariant> params;
+ {
+ KexiUtils::WaitCursorRemover remover;
+ params = KexiQueryParameters::getParameters(this, *conn->driver(), *m_query, ok);
+ }
+ if (ok) //input cancelled
+ m_cursor = conn->executeQuery( *m_query, params );
+ }
+ m_scrollView->invalidateDataSources( invalidSources, m_query );
+ ok = m_cursor!=0;
+ }
+
+ if (!invalidSources.isEmpty())
+ m_dbform->updateTabStopsOrder();
+
+ if (ok) {
+//! @todo PRIMITIVE!! data setting:
+//! @todo KexiTableViewData is not great name for data class here... rename/move?
+ KexiTableViewData* data = new KexiTableViewData(m_cursor);
+ if (forceReadOnlyDataSource)
+ data->setReadOnly(true);
+ data->preloadAllRows();
+
+///*! @todo few backends return result count for free! - no need to reopen() */
+// int resultCount = -1;
+// if (ok) {
+// resultCount = m_conn->resultCount(m_conn->selectStatement(*m_query));
+// ok = m_cursor->reopen();
+// }
+// if (ok)
+// ok = ! (!m_cursor->moveFirst() && m_cursor->error());
+
+ m_scrollView->setData( data, true /*owner*/ );
+ }
+ else
+ m_scrollView->setData( 0, false );
+}
+
+void
+KexiFormView::slotDirty(KFormDesigner::Form *dirtyForm, bool isDirty)
+{
+ if(dirtyForm == form())
+ KexiViewBase::setDirty(isDirty);
+}
+
+KexiDB::SchemaData*
+KexiFormView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ KexiDB::SchemaData *s = KexiViewBase::storeNewData(sdata, cancel);
+ kexipluginsdbg << "KexiDBForm::storeNewData(): new id:" << s->id() << endl;
+
+ if (!s || cancel) {
+ delete s;
+ return 0;
+ }
+ if (!storeData()) {
+ //failure: remove object's schema data to avoid garbage
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ conn->removeObject( s->id() );
+ delete s;
+ return 0;
+ }
+ return s;
+}
+
+tristate
+KexiFormView::storeData(bool dontAsk)
+{
+ Q_UNUSED(dontAsk);
+ kexipluginsdbg << "KexiDBForm::storeData(): " << parentDialog()->partItem()->name()
+ << " [" << parentDialog()->id() << "]" << endl;
+
+ //-- first, store local BLOBs, so identifiers can be updated
+//! @todo remove unused data stored previously
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ KexiDB::TableSchema *blobsTable = conn->tableSchema("kexi__blobs");
+ if (!blobsTable) { //compatibility check for older Kexi project versions
+//! @todo show message about missing kexi__blobs?
+ return false;
+ }
+ // Not all engines accept passing NULL to PKEY o_id, so we're omitting it.
+ QStringList blobsFieldNamesWithoutID(blobsTable->names());
+ blobsFieldNamesWithoutID.pop_front();
+ KexiDB::FieldList *blobsFieldsWithoutID = blobsTable->subList(blobsFieldNamesWithoutID);
+
+ KexiDB::PreparedStatement::Ptr st = conn->prepareStatement(
+ KexiDB::PreparedStatement::InsertStatement, *blobsFieldsWithoutID);
+ if (!st) {
+ delete blobsFieldsWithoutID;
+ //! @todo show message
+ return false;
+ }
+ KexiBLOBBuffer *blobBuf = KexiBLOBBuffer::self();
+ KexiFormView *designFormView
+ = dynamic_cast<KexiFormView*>( parentDialog()->viewForMode(Kexi::DesignViewMode) );
+ if (designFormView) {
+ for (QMapConstIterator<QWidget*, KexiBLOBBuffer::Id_t> it = tempData()->unsavedLocalBLOBs.constBegin();
+ it!=tempData()->unsavedLocalBLOBs.constEnd(); ++it)
+ {
+ if (!it.key()) {
+ kexipluginswarn << "KexiFormView::storeData(): it.key()==0 !" << endl;
+ continue;
+ }
+ kexipluginsdbg << "name=" << it.key()->name() << " dataID=" << it.data() << endl;
+ KexiBLOBBuffer::Handle h( blobBuf->objectForId(it.data(), /*!stored*/false) );
+ if (!h)
+ continue; //no BLOB assigned
+
+ QString originalFileName(h.originalFileName());
+ QFileInfo fi(originalFileName);
+ QString caption(fi.baseName().replace('_', " ").simplifyWhiteSpace());
+
+ if (st) {
+ *st /* << NO, (pgsql doesn't support this):QVariant()*/ /*id*/
+ << h.data() << originalFileName << caption
+ << h.mimeType() << (uint)/*! @todo unsafe */h.folderId();
+ if (!st->execute()) {
+ delete blobsFieldsWithoutID;
+ kexipluginsdbg << " execute error" << endl;
+ return false;
+ }
+ }
+ delete blobsFieldsWithoutID;
+ blobsFieldsWithoutID=0;
+ const Q_ULLONG storedBLOBID = conn->lastInsertedAutoIncValue("o_id", "kexi__blobs");
+ if ((Q_ULLONG)-1 == storedBLOBID) {
+ //! @todo show message?
+ return false;
+ }
+ kexipluginsdbg << " storedDataID=" << storedBLOBID << endl;
+ h.setStoredWidthID((KexiBLOBBuffer::Id_t /*unsafe - will be fixed in Qt4*/)storedBLOBID);
+ //set widget's internal property so it can be saved...
+ const QVariant oldStoredPixmapId( it.key()->property("storedPixmapId") );
+ it.key()->setProperty("storedPixmapId",
+ QVariant((uint /* KexiBLOBBuffer::Id_t is unsafe and unsupported by QVariant - will be fixed in Qt4*/)storedBLOBID));
+ KFormDesigner::ObjectTreeItem *widgetItem = designFormView->form()->objectTree()->lookup(it.key()->name()); //form()->objectTree()->lookup(it.key()->name());
+ if (widgetItem)
+ widgetItem->addModifiedProperty( "storedPixmapId", oldStoredPixmapId );
+ else
+ kexipluginswarn << "KexiFormView::storeData(): no '" << widgetItem->name() << "' widget found within a form" << endl;
+ }
+ }
+
+ //-- now, save form's XML
+ QString data;
+ if (!KFormDesigner::FormIO::saveFormToString(tempData()->form, data))
+ return false;
+ if (!storeDataBlock(data))
+ return false;
+
+ //all blobs are now saved
+ tempData()->unsavedLocalBLOBs.clear();
+
+ tempData()->tempForm = QString::null;
+ return true;
+}
+
+#if 0
+/// Action stuff /////////////////
+void
+KexiFormView::slotWidgetSelected(KFormDesigner::Form *f, bool multiple)
+{
+ if(f != form())
+ return;
+
+ enableFormActions();
+ // Enable edit actions
+ setAvailable("edit_copy", true);
+ setAvailable("edit_cut", true);
+ setAvailable("edit_clear", true);
+
+ // 'Align Widgets' menu
+ setAvailable("formpart_align_menu", multiple);
+ setAvailable("formpart_align_to_left", multiple);
+ setAvailable("formpart_align_to_right", multiple);
+ setAvailable("formpart_align_to_top", multiple);
+ setAvailable("formpart_align_to_bottom", multiple);
+
+ setAvailable("formpart_adjust_size_menu", true);
+ setAvailable("formpart_adjust_width_small", multiple);
+ setAvailable("formpart_adjust_width_big", multiple);
+ setAvailable("formpart_adjust_height_small", multiple);
+ setAvailable("formpart_adjust_height_big", multiple);
+
+ setAvailable("formpart_format_raise", true);
+ setAvailable("formpart_format_lower", true);
+
+ // If the widgets selected is a container, we enable layout actions
+ if(!multiple)
+ {
+ KFormDesigner::ObjectTreeItem *item = f->objectTree()->lookup( f->selectedWidgets()->first()->name() );
+ if(item && item->container())
+ multiple = true;
+ }
+ // Layout actions
+ setAvailable("formpart_layout_hbox", multiple);
+ setAvailable("formpart_layout_vbox", multiple);
+ setAvailable("formpart_layout_grid", multiple);
+
+ KFormDesigner::Container *container = f->activeContainer();
+ setAvailable("formpart_break_layout", container ?
+ (container->layoutType() != KFormDesigner::Container::NoLayout) : false );
+}
+
+void
+KexiFormView::slotFormWidgetSelected(KFormDesigner::Form *f)
+{
+ if(f != form())
+ return;
+
+ disableWidgetActions();
+ enableFormActions();
+
+ // Layout actions
+ setAvailable("formpart_layout_hbox", true);
+ setAvailable("formpart_layout_vbox", true);
+ setAvailable("formpart_layout_grid", true);
+ setAvailable("formpart_break_layout", (f->toplevelContainer()->layoutType() != KFormDesigner::Container::NoLayout));
+}
+
+void
+KexiFormView::slotNoFormSelected() // == form in preview mode
+{
+ disableWidgetActions();
+
+ // Disable paste action
+ setAvailable("edit_paste", false);
+ setAvailable("edit_undo", false);
+ setAvailable("edit_redo", false);
+
+ // Disable 'Tools' actions
+ setAvailable("formpart_pixmap_collection", false);
+ setAvailable("formpart_connections", false);
+ setAvailable("formpart_taborder", false);
+ setAvailable("formpart_change_style", false);
+}
+
+void
+KexiFormView::enableFormActions()
+{
+ // Enable 'Tools' actions
+ setAvailable("formpart_pixmap_collection", true);
+ setAvailable("formpart_connections", true);
+ setAvailable("formpart_taborder", true);
+
+ setAvailable("edit_paste", KFormDesigner::FormManager::self()->isPasteEnabled());
+}
+
+void
+KexiFormView::disableWidgetActions()
+{
+ // Disable edit actions
+ setAvailable("edit_copy", false);
+ setAvailable("edit_cut", false);
+ setAvailable("edit_clear", false);
+
+ // Disable format functions
+ setAvailable("formpart_align_menu", false);
+ setAvailable("formpart_align_to_left", false);
+ setAvailable("formpart_align_to_right", false);
+ setAvailable("formpart_align_to_top", false);
+ setAvailable("formpart_align_to_bottom", false);
+
+ setAvailable("formpart_adjust_size_menu", false);
+ setAvailable("formpart_adjust_width_small", false);
+ setAvailable("formpart_adjust_width_big", false);
+ setAvailable("formpart_adjust_height_small", false);
+ setAvailable("formpart_adjust_height_big", false);
+
+ setAvailable("formpart_format_raise", false);
+ setAvailable("formpart_format_lower", false);
+
+ setAvailable("formpart_layout_hbox", false);
+ setAvailable("formpart_layout_vbox", false);
+ setAvailable("formpart_layout_grid", false);
+ setAvailable("formpart_break_layout", false);
+}
+
+void
+KexiFormView::setUndoEnabled(bool enabled)
+{
+ setAvailable("edit_undo", enabled);
+}
+
+void
+KexiFormView::setRedoEnabled(bool enabled)
+{
+ setAvailable("edit_redo", enabled);
+}
+#endif //0
+
+QSize
+KexiFormView::preferredSizeHint(const QSize& otherSize)
+{
+ if (parentDialog()->neverSaved()) {
+ //ignore otherSize if possible
+// return KexiViewBase::preferredSizeHint( (parentDialog() && parentDialog()->mdiParent()) ? QSize(10000,10000) : otherSize);
+ }
+
+ return (m_dbform->size()
+ +QSize(m_scrollView->verticalScrollBar()->isVisible() ? m_scrollView->verticalScrollBar()->width()*3/2 : 10,
+ m_scrollView->horizontalScrollBar()->isVisible() ? m_scrollView->horizontalScrollBar()->height()*3/2 : 10))
+ .expandedTo( KexiViewBase::preferredSizeHint(otherSize) );
+}
+
+void
+KexiFormView::resizeEvent( QResizeEvent *e )
+{
+ if (viewMode()==Kexi::DataViewMode) {
+ m_scrollView->refreshContentsSizeLater(
+ e->size().width()!=e->oldSize().width(),
+ e->size().height()!=e->oldSize().height()
+ );
+ }
+ KexiViewBase::resizeEvent(e);
+ m_scrollView->updateNavPanelGeometry();
+ if (m_delayedFormContentsResizeOnShow>0) { // && isVisible()) {
+ m_delayedFormContentsResizeOnShow--;
+ m_dbform->resize( e->size() - QSize(30, 30) );
+ }
+}
+
+void
+KexiFormView::setFocusInternal()
+{
+ if (viewMode() == Kexi::DataViewMode) {
+ if (m_dbform->focusWidget()) {
+ //better-looking focus
+ if (m_setFocusInternalOnce) {
+ KexiUtils::setFocusWithReason(m_setFocusInternalOnce, QFocusEvent::Other);//Tab);
+ m_setFocusInternalOnce = 0;
+ }
+ else {
+ //ok? SET_FOCUS_USING_REASON(m_dbform->focusWidget(), QFocusEvent::Other);//Tab);
+ }
+ return;
+ }
+ }
+ QWidget::setFocus();
+}
+
+void
+KexiFormView::show()
+{
+ KexiDataAwareView::show();
+
+//moved from KexiFormScrollView::show():
+
+ //now get resize mode settings for entire form
+ // if (resizeMode() == KexiFormView::ResizeAuto)
+ if (viewMode()==Kexi::DataViewMode) {
+ if (resizeMode() == KexiFormView::ResizeAuto)
+ m_scrollView->setResizePolicy(QScrollView::AutoOneFit);
+ }
+}
+
+void
+KexiFormView::slotFocus(bool in)
+{
+ if(in && form() && KFormDesigner::FormManager::self() && KFormDesigner::FormManager::self()->activeForm() != form()) {
+ KFormDesigner::FormManager::self()->windowChanged(m_dbform);
+ updateDataSourcePage();
+ }
+}
+
+void
+KexiFormView::updateDataSourcePage()
+{
+ if (viewMode()==Kexi::DesignViewMode) {
+ QCString dataSourceMimeType, dataSource;
+ KFormDesigner::WidgetPropertySet *set = KFormDesigner::FormManager::self()->propertySet();
+ if (set->contains("dataSourceMimeType"))
+ dataSourceMimeType = (*set)["dataSourceMimeType"].value().toCString();
+ if (set->contains("dataSource"))
+ dataSource = (*set)["dataSource"].value().toCString();
+
+ formPart()->dataSourcePage()->setDataSource(dataSourceMimeType, dataSource);
+ }
+}
+
+void
+KexiFormView::slotHandleDragMoveEvent(QDragMoveEvent* e)
+{
+ if (KexiFieldDrag::canDecodeMultiple( e )) {
+ e->accept(true);
+ //dirty: drawRect(QRect( e->pos(), QSize(50, 20)), 2);
+ }
+}
+
+void
+KexiFormView::slotHandleDropEvent(QDropEvent* e)
+{
+ const QWidget *targetContainerWidget = dynamic_cast<const QWidget*>(sender());
+ KFormDesigner::ObjectTreeItem *targetContainerWidgetItem = targetContainerWidget
+ ? form()->objectTree()->lookup( targetContainerWidget->name() ) : 0;
+ if (targetContainerWidgetItem && targetContainerWidgetItem->container()
+ && KexiFieldDrag::canDecodeMultiple( e ))
+ {
+ QString sourceMimeType, sourceName;
+ QStringList fields;
+ if (!KexiFieldDrag::decodeMultiple( e, sourceMimeType, sourceName, fields ))
+ return;
+ insertAutoFields(sourceMimeType, sourceName, fields,
+ targetContainerWidgetItem->container(), e->pos());
+ }
+}
+
+void
+KexiFormView::insertAutoFields(const QString& sourceMimeType, const QString& sourceName,
+ const QStringList& fields, KFormDesigner::Container* targetContainer, const QPoint& _pos)
+{
+ if (fields.isEmpty())
+ return;
+
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ KexiDB::TableOrQuerySchema tableOrQuery(conn, sourceName.latin1(), sourceMimeType=="kexi/table");
+ if (!tableOrQuery.table() && !tableOrQuery.query()) {
+ kexipluginswarn << "KexiFormView::insertAutoFields(): no such table/query \""
+ << sourceName << "\"" << endl;
+ return;
+ }
+
+ QPoint pos(_pos);
+ //if pos is not specified, compute a new position:
+ if (pos==QPoint(-1,-1)) {
+ if (m_widgetGeometryForRecentInsertAutoFields.isValid()) {
+ pos = m_widgetGeometryForRecentInsertAutoFields.bottomLeft()
+ + QPoint(0,form()->gridSize());
+ }
+ else {
+ pos = QPoint(40, 40); //start here
+ }
+ }
+
+ // there will be many actions performed, do not update property pane until all that's finished
+ KFormDesigner::FormManager::self()->blockPropertyEditorUpdating(this);
+
+//! todo unnamed query colums are not supported
+
+// KFormDesigner::WidgetList* prevSelection = form()->selectedWidgets();
+ KFormDesigner::WidgetList widgetsToSelect;
+ KFormDesigner::CommandGroup *group = new KFormDesigner::CommandGroup(
+ fields.count()==1 ? i18n("Insert AutoField widget") : i18n("Insert %1 AutoField widgets").arg(fields.count()),
+ KFormDesigner::FormManager::self()->propertySet()
+ );
+
+ foreach( QStringList::ConstIterator, it, fields ) {
+ KexiDB::QueryColumnInfo* column = tableOrQuery.columnInfo(*it);
+ if (!column) {
+ kexipluginswarn << "KexiFormView::insertAutoFields(): no such field \""
+ << *it << "\" in table/query \"" << sourceName << "\"" << endl;
+ continue;
+ }
+//! todo add autolabel using field's caption or name
+ //KFormDesigner::Container *targetContainer;
+/* QWidget* targetContainerWidget = QApplication::widgetAt(pos, true);
+ while (targetContainerWidget
+ && !dynamic_cast<KFormDesigner::Container*>(targetContainerWidget))
+ {
+ targetContainerWidget = targetContainerWidget->parentWidget();
+ }
+ if (dynamic_cast<KFormDesigner::Container*>(targetContainerWidget))
+ targetContainer = dynamic_cast<KFormDesigner::Container*>(targetContainerWidget);
+ else
+ targetContainer = form()->toplevelContainer();*/
+ KFormDesigner::InsertWidgetCommand *insertCmd
+ = new KFormDesigner::InsertWidgetCommand(targetContainer,
+ //! todo this is hardcoded!
+ "KexiDBAutoField",
+ //! todo this name can be invalid for expressions: if so, fall back to a default class' prefix!
+ pos, column->aliasOrName()
+ );
+ insertCmd->execute();
+ group->addCommand(insertCmd, false/*don't exec twice*/);
+
+ KFormDesigner::ObjectTreeItem *newWidgetItem
+ = form()->objectTree()->dict()->find(insertCmd->widgetName());
+ KexiDBAutoField* newWidget
+ = newWidgetItem ? dynamic_cast<KexiDBAutoField*>(newWidgetItem->widget()) : 0;
+ widgetsToSelect.append(newWidget);
+//#if 0
+ KFormDesigner::CommandGroup *subGroup
+ = new KFormDesigner::CommandGroup("", KFormDesigner::FormManager::self()->propertySet());
+ QMap<QCString, QVariant> propValues;
+ propValues.insert("dataSource", column->aliasOrName());
+ propValues.insert("fieldTypeInternal", (int)column->field->type());
+ propValues.insert("fieldCaptionInternal", column->captionOrAliasOrName());
+ KFormDesigner::FormManager::self()->propertySet()->createPropertyCommandsInDesignMode(
+ newWidget, propValues, subGroup, false/*!addToActiveForm*/,
+ true /*!execFlagForSubCommands*/);
+ subGroup->execute();
+ group->addCommand( subGroup, false/*will not be executed on CommandGroup::execute()*/ );
+
+//#endif
+ //set data source and caption
+ //-we don't need to use PropertyCommand here beacause we don't need UNDO
+ // for these single commands
+// newWidget->setDataSource(column->aliasOrName());
+// newWidget->setFieldTypeInternal((int)column->field->type());
+// newWidget->setFieldCaptionInternal(column->captionOrAliasOrName());
+ //resize again because autofield's type changed what can lead to changed sizeHint()
+// newWidget->resize(newWidget->sizeHint());
+ KFormDesigner::WidgetList list;
+ list.append(newWidget);
+ KFormDesigner::AdjustSizeCommand *adjustCommand
+ = new KFormDesigner::AdjustSizeCommand(KFormDesigner::AdjustSizeCommand::SizeToFit,
+ list, form());
+ adjustCommand->execute();
+ group->addCommand( adjustCommand,
+ false/*will not be executed on CommandGroup::execute()*/
+ );
+
+ if (newWidget) {//move position down for next widget
+ pos.setY( pos.y() + newWidget->height() + form()->gridSize());
+ }
+ }
+ if (widgetsToSelect.last()) {
+ //resize form if needed
+ QRect oldFormRect( m_dbform->geometry() );
+ QRect newFormRect( oldFormRect );
+ newFormRect.setWidth(QMAX(m_dbform->width(), widgetsToSelect.last()->geometry().right()+1));
+ newFormRect.setHeight(QMAX(m_dbform->height(), widgetsToSelect.last()->geometry().bottom()+1));
+ if (newFormRect != oldFormRect) {
+ //1. resize by hand
+ m_dbform->setGeometry( newFormRect );
+ //2. store information about resize
+ KFormDesigner::PropertyCommand *resizeFormCommand = new KFormDesigner::PropertyCommand(
+ KFormDesigner::FormManager::self()->propertySet(), m_dbform->name(),
+ oldFormRect, newFormRect, "geometry");
+ group->addCommand(resizeFormCommand, true/*will be executed on CommandGroup::execute()*/);
+ }
+
+ //remember geometry of the last inserted widget
+ m_widgetGeometryForRecentInsertAutoFields = widgetsToSelect.last()->geometry();
+ }
+
+ //eventually, add entire command group to active form
+ form()->addCommand( group, true/*exec*/ );
+
+// group->debug();
+
+ //enable proper REDO usage
+ group->resetAllowExecuteFlags();
+
+ m_scrollView->repaint();
+ m_scrollView->viewport()->repaint();
+ m_scrollView->repaintContents();
+ m_scrollView->updateContents();
+ m_scrollView->clipper()->repaint();
+ m_scrollView->refreshContentsSize();
+
+ //select all inserted widgets, if multiple
+ if (widgetsToSelect.count()>1) {
+ form()->setSelectedWidget(0);
+ foreach_list (KFormDesigner::WidgetListIterator, it, widgetsToSelect)
+ form()->setSelectedWidget(it.current(), true/*add*/, true/*dontRaise*/);
+ }
+
+ // eventually, update property pane
+ KFormDesigner::FormManager::self()->unblockPropertyEditorUpdating(this, KFormDesigner::FormManager::self()->propertySet());
+}
+
+void
+KexiFormView::setUnsavedLocalBLOB(QWidget *widget, KexiBLOBBuffer::Id_t id)
+{
+//! @todo if there already was data assigned, remember it should be dereferenced
+ if (id==0)
+ tempData()->unsavedLocalBLOBs.remove(widget);
+ else
+ tempData()->unsavedLocalBLOBs.insert(widget, id);
+}
+
+/*
+todo
+void KexiFormView::updateActions(bool activated)
+{
+ if (viewMode()==Kexi::DesignViewMode) {
+ if (form()->selectedWidget()) {
+ if (form()->widget() == form()->selectedWidget())
+ KFormDesigner::FormManager::self()->emitFormWidgetSelected( form() );
+ else
+ KFormDesigner::FormManager::self()->emitWidgetSelected( form(), false );
+ }
+ else if (form()->selectedWidgets()) {
+ KFormDesigner::FormManager::self()->emitWidgetSelected( form(), true );
+ }
+ }
+ KexiDataAwareView::updateActions(activated);
+}*/
+
+/*
+void KexiFormView::parentDialogDetached()
+{
+ m_dbform->updateTabStopsOrder(form());
+}
+
+void KexiFormView::parentDialogAttached(KMdiChildFrm *)
+{
+ m_dbform->updateTabStopsOrder(form());
+}*/
+
+#include "kexiformview.moc"
+
diff --git a/kexi/plugins/forms/kexiformview.h b/kexi/plugins/forms/kexiformview.h
new file mode 100644
index 000000000..0a7745564
--- /dev/null
+++ b/kexi/plugins/forms/kexiformview.h
@@ -0,0 +1,231 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFORMVIEW_H
+#define KEXIFORMVIEW_H
+
+#include <qtimer.h>
+
+#include <kexiviewbase.h>
+#include <widget/kexidataawareview.h>
+
+#include "kexiformpart.h"
+#include <core/kexiblobbuffer.h>
+
+class KexiFormPart;
+class KexiMainWindow;
+class KexiDBForm;
+class KexiTableItem;
+class KexiTableViewData;
+class KexiFormScrollView;
+namespace KexiDB { class Cursor; }
+namespace KFormDesigner
+{
+ class Container;
+}
+
+//! The KexiFormView lass provides a data-driven (record-based) form view .
+/*! The KexiFormView can display data provided "by hand"
+ or from KexiDB-compatible database source.
+
+ This class provides a single view used inside KexiDialogBase.
+ It takes care of saving/loading form, of enabling actions when needed.
+ One KexiFormView object is instantiated for data view mode
+ and a second KexiFormView object is instantiated for design view mode.
+
+ @see KexiDataTable
+*/
+class KEXIFORMUTILS_EXPORT KexiFormView : public KexiDataAwareView
+{
+ Q_OBJECT
+
+ public:
+ enum ResizeMode {
+ ResizeAuto = 0,
+ ResizeDefault = ResizeAuto,
+ ResizeFixed = 1,
+ NoResize = 2 /*! @todo */
+ };
+
+// KexiFormView(KexiMainWindow *win, QWidget *parent, const char *name, KexiDB::Connection *conn);
+ KexiFormView(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0,
+ bool dbAware = true);
+ virtual ~KexiFormView();
+
+// KexiDB::Connection* connection() { return m_conn; }
+
+ virtual QSize preferredSizeHint(const QSize& otherSize);
+
+ int resizeMode() const { return m_resizeMode; }
+
+ KFormDesigner::Form* form() const;
+
+ /*! Assigns \a id local (static) BLOB's identifier for \a widget widget.
+ Previously assigned BLOB will be usassigned.
+ If \a id is 0, BLOB is unassigned and no new is assigned.
+
+ This method is called when a widget supporting BLOB data
+ (currently, images from KexiDBImageBox, within KexiDBFactory) has BLOB assigned by identifier \a id.
+ BLOB identifiers are defined by KexiBLOBBuffer (KexiBLOBBuffer::self() instance).
+
+ The data collected by this method is used on form's design saving (in design mode).
+ Local BLOBs are retrieved KexiBLOBBuffer::self() and stored in "kexi__blobs" 'system' table.
+ Note that db-aware BLOBs (non local) are not handled this way.
+ */
+ void setUnsavedLocalBLOB(QWidget *widget, KexiBLOBBuffer::Id_t id);
+
+ public slots:
+ /*! Reimplemented to update resize policy. */
+ virtual void show();
+
+ /*! Inserts autofields onto the form at \a pos position.
+ \a sourceMimeType can be "kexi/table" or "kexi/query",
+ \a sourceName is a name of a table or query, \a fields is a list of fields to insert (one or more)
+ Fields are inserted using standard KFormDesigner::InsertWidgetCommand framework,
+ so undo/redo is available for this operation.
+
+ If multiple fields are provided, they will be aligned vertically.
+ If \a pos is QPoint(-1,-1) (the default), position is computed automatically
+ based on a position last inserted field using this method.
+ If this method has not been called yet, position of QPoint(40, 40) will be set.
+
+ Called by:
+ - slotHandleDropEvent() when field(s) are dropped from the data source pane onto the form
+ - KexiFormManager is a used clicked "Insert fields" button on the data source pane. */
+ void insertAutoFields(const QString& sourceMimeType, const QString& sourceName,
+ const QStringList& fields, KFormDesigner::Container* targetContainerWidget,
+ const QPoint& pos = QPoint(-1,-1));
+
+ protected slots:
+ void slotPropertySetSwitched(KoProperty::Set *b, bool forceReload = false,
+ const QCString& propertyToSelect = QCString());
+ void slotDirty(KFormDesigner::Form *f, bool isDirty);
+ void slotFocus(bool in);
+ void slotHandleDragMoveEvent(QDragMoveEvent* e);
+
+ //! Handles field(s) dropping from the data source pane onto the form
+ //! @see insertAutoFields()
+ void slotHandleDropEvent(QDropEvent* e);
+
+//moved to formmanager void slotWidgetSelected(KFormDesigner::Form *form, bool multiple);
+//moved to formmanager void slotFormWidgetSelected(KFormDesigner::Form *form);
+//moved to formmanager void slotNoFormSelected();
+
+//moved to formmanager void setUndoEnabled(bool enabled);
+//moved to formmanager void setRedoEnabled(bool enabled);
+
+ protected:
+ virtual tristate beforeSwitchTo(int mode, bool &dontStore);
+ virtual tristate afterSwitchFrom(int mode);
+ virtual KoProperty::Set* propertySet() { return m_propertySet; }
+
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+ virtual tristate storeData(bool dontAsk = false);
+
+ KexiFormPart::TempData* tempData() const {
+ return dynamic_cast<KexiFormPart::TempData*>(parentDialog()->tempData()); }
+ KexiFormPart* formPart() const { return dynamic_cast<KexiFormPart*>(part()); }
+
+//moved to formmanager void disableWidgetActions();
+//moved to formmanager void enableFormActions();
+
+ void setForm(KFormDesigner::Form *f);
+
+ void initForm();
+
+ void loadForm();
+
+ //! Used in loadForm()
+ void updateAutoFieldsDataSource();
+
+ //! Used in loadForm()
+ void updateValuesForSubproperties();
+
+ virtual void resizeEvent ( QResizeEvent * );
+
+ void initDataSource();
+
+ virtual void setFocusInternal();
+
+/* // for navigator
+ virtual void moveToRecordRequested(uint r);
+ virtual void moveToLastRecordRequested();
+ virtual void moveToPreviousRecordRequested();
+ virtual void moveToNextRecordRequested();
+ virtual void moveToFirstRecordRequested();
+ virtual void addNewRecordRequested();*/
+
+ /*! Called after loading the form contents (before showing it).
+ Also called when the form window (KexiDialogBase) is detached
+ (in KMDI's Child Frame mode), because otherwise tabstop ordering can get broken. */
+ void updateTabStopsOrder();
+
+ /*! @internal */
+ void deleteQuery();
+
+ /*! @internal */
+ void updateDataSourcePage();
+
+ /*! Reimplemented after KexiViewBase.
+ Updates actions (e.g. availability). */
+// todo virtual void updateActions(bool activated);
+
+ KexiDBForm *m_dbform;
+ KexiFormScrollView *m_scrollView;
+ KoProperty::Set *m_propertySet;
+
+ /*! Database cursor used for data retrieving.
+ It is shared between subsequent Data view sessions (just reopened on switch),
+ but deleted and recreated from scratch when form's "dataSource" property changed
+ since last form viewing (m_previousDataSourceString is used for that). */
+ QString m_previousDataSourceString;
+
+ int m_resizeMode;
+
+ KexiDB::QuerySchema* m_query;
+
+ /*! True, if m_query is created as temporary object within this form.
+ If user selected an existing, predefined (stored) query, m_queryIsOwned will be false,
+ so the query object will not be destroyed. */
+ bool m_queryIsOwned;
+
+ KexiDB::Cursor *m_cursor;
+
+ /*! For new (empty) forms only:
+ Our form's area will be resized more than once.
+ We will resize form widget itself later (in resizeEvent()). */
+ int m_delayedFormContentsResizeOnShow;
+
+ //! Used in setFocusInternal()
+ QGuardedPtr<QWidget> m_setFocusInternalOnce;
+
+
+ /*! Stores geometry of widget recently inserted using insertAutoFields() method.
+ having this information, we'r eable to compute position for a newly
+ inserted widget in insertAutoFields() is such position has not been specified.
+ (the position is specified when a widget is inserted with mouse drag & dropping
+ but not with clicking of 'Insert fields' button from Data Source pane) */
+ QRect m_widgetGeometryForRecentInsertAutoFields;
+
+ //! Used in setUnsavedLocalBLOBs()
+// QMap<QWidget*, KexiBLOBBuffer::Id_t> m_unsavedLocalBLOBs;
+};
+
+#endif
diff --git a/kexi/plugins/forms/kformdesigner_kexidbfactory.desktop b/kexi/plugins/forms/kformdesigner_kexidbfactory.desktop
new file mode 100644
index 000000000..4e5bb719c
--- /dev/null
+++ b/kexi/plugins/forms/kformdesigner_kexidbfactory.desktop
@@ -0,0 +1,55 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=KFormDesigner/WidgetFactory
+
+Name=Kexi DB Widgets
+Name[bg]=Графични обекти на Kexi за бази данни
+Name[ca]=Estris DB de Kexi
+Name[cy]=Celfigion Cronfa Ddata Kexi
+Name[da]=Kexi DB-kontroller
+Name[de]=Kexi Datenbank-Elemente
+Name[el]=Γραφικά συστατικά Kexi DB
+Name[eo]=Kexi DB-fenestraĵo
+Name[es]=Wigdet de BD de Kexi
+Name[et]=Kexi andmebaasividinad
+Name[eu]=Kexi-ren datu-baseko trepetak
+Name[fa]=عناصر Kexi DB
+Name[fi]=Kexi tietokantaelementit
+Name[fr]=Éléments graphiques de base de données Kexi
+Name[fy]=Kexi DB-widgets
+Name[gl]=Elementos de Base de Datos Kexi
+Name[he]=פריטי מסד נתונים של Kexi
+Name[hr]=Kexi DB widgeti
+Name[hu]=Kexi adatbázis-kezelési grafikus elemek
+Name[is]=Kexi gagnagrunns hlutir
+Name[it]=Oggetti per banche dati per Kexi
+Name[ja]=Kexi DB ウィジェット
+Name[km]=ធាតុ​ក្រាហ្វិក DB សម្រាប់ Kexi
+Name[lv]=Kexi DB logdaļas
+Name[ms]=Widget DB Kexi
+Name[nb]=DB-element for Kexi
+Name[nds]=Datenbank-Stüerelementen för Kexi
+Name[ne]=केक्सी DB विजेटहरू
+Name[nl]=Kexi DB-widgets
+Name[nn]=DB-element for Kexi
+Name[pl]=Kontrolki baz danych dla Kexi
+Name[pt]=Elementos de Base de Dados Kexi
+Name[pt_BR]=Widgets de BD do Kexi
+Name[ru]=Элементы управления для работы с базами данных Kexi
+Name[se]=Kexi-DV-áđat
+Name[sk]=Komponenty Kexi DB
+Name[sl]=Gradniki za zbirko podatkov za Kexi
+Name[sr]=Kexi-јеве DB контроле
+Name[sr@Latn]=Kexi-jeve DB kontrole
+Name[sv]=Kexi-databaskomponenter
+Name[ta]=கெக்சி டிபி சாளர உருக்கள்
+Name[tr]=Kexi DB Parçacıkları
+Name[uk]=Віджети Kexi DB
+Name[uz]=Kexi maʼlumot baza vidjetlari
+Name[uz@cyrillic]=Kexi маълумот база виджетлари
+Name[zh_CN]=Kexi 数据库部件
+Name[zh_TW]=Kexi DB 視窗元件
+
+X-KDE-Library=kformdesigner_kexidbwidgets
+X-KFormDesigner-FactoryGroup=kexi
+X-KFormDesigner-WidgetFactoryVersion=2
diff --git a/kexi/plugins/forms/widgets/Makefile.am b/kexi/plugins/forms/widgets/Makefile.am
new file mode 100644
index 000000000..5ca5cbd86
--- /dev/null
+++ b/kexi/plugins/forms/widgets/Makefile.am
@@ -0,0 +1,28 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_LTLIBRARIES = libkexiformutilswidgets.la
+
+libkexiformutilswidgets_la_SOURCES = \
+ kexidbutils.cpp \
+ kexidbautofield.cpp \
+ kexidbform.cpp \
+ kexidbsubform.cpp \
+ kexidblabel.cpp \
+ kexidbimagebox.cpp \
+ kexipushbutton.cpp \
+ kexiframe.cpp \
+ kexidblineedit.cpp \
+ kexidbcheckbox.cpp \
+ kexidbtextedit.cpp \
+ kexidbcombobox.cpp
+
+libkexiformutilswidgets_la_LDFLAGS = $(all_libraries) -Wnounresolved
+libkexiformutilswidgets_la_LIBADD =
+
+libkexiformutilswidgets_la_METASOURCES = AUTO
+
+SUBDIRS = .
+
+# set the include path for X, qt and KDE
+INCLUDES= -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/plugins/forms -I$(top_srcdir)/kexi/core $(all_includes)
+
diff --git a/kexi/plugins/forms/widgets/kexidbautofield.cpp b/kexi/plugins/forms/widgets/kexidbautofield.cpp
new file mode 100644
index 000000000..36fbdb1a8
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbautofield.cpp
@@ -0,0 +1,846 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de>
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbautofield.h"
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qmetaobject.h>
+#include <qapplication.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include "kexidbcheckbox.h"
+#include "kexidbimagebox.h"
+#include "kexidblabel.h"
+#include "kexidblineedit.h"
+#include "kexidbtextedit.h"
+#include "kexidbcombobox.h"
+#include "kexipushbutton.h"
+#include "kexidbform.h"
+
+#include <kexidb/queryschema.h>
+#include <formeditor/utils.h>
+#include <kexiutils/utils.h>
+
+#define KexiDBAutoField_SPACING 10 //10 pixel for spacing between a label and an editor widget
+
+//! @internal
+class KexiDBAutoField::Private
+{
+ public:
+ Private()
+ {
+ }
+
+ WidgetType widgetType; //!< internal: equal to m_widgetType_property or equal to result
+ //!< of widgetTypeForFieldType() if widgetTypeForFieldType is Auto
+ WidgetType widgetType_property; //!< provides widget type or Auto
+ LabelPosition lblPosition;
+ QBoxLayout *layout;
+ QLabel *label;
+ QString caption;
+ KexiDB::Field::Type fieldTypeInternal;
+ QString fieldCaptionInternal;
+ QColor baseColor; //!< needed because for unbound mode editor==0
+ QColor textColor; //!< needed because for unbound mode editor==0
+ bool autoCaption : 1;
+ bool focusPolicyChanged : 1;
+ bool designMode : 1;
+};
+
+//-------------------------------------
+
+KexiDBAutoField::KexiDBAutoField(const QString &text, WidgetType type, LabelPosition pos,
+ QWidget *parent, const char *name, bool designMode)
+ : QWidget(parent, name)
+ , KexiFormDataItemInterface()
+ , KFormDesigner::DesignTimeDynamicChildWidgetHandler()
+ , d( new Private() )
+{
+ d->designMode = designMode;
+ init(text, type, pos);
+}
+
+KexiDBAutoField::KexiDBAutoField(QWidget *parent, const char *name, bool designMode, LabelPosition pos)
+ : QWidget(parent, name)
+ , KexiFormDataItemInterface()
+ , KFormDesigner::DesignTimeDynamicChildWidgetHandler()
+ , d( new Private() )
+{
+ d->designMode = designMode;
+ init(QString::null/*i18n("Auto Field")*/, Auto, pos);
+}
+
+KexiDBAutoField::~KexiDBAutoField()
+{
+ setUpdatesEnabled(false);
+ if (m_subwidget)
+ m_subwidget->setUpdatesEnabled(false);
+ delete d;
+}
+
+void
+KexiDBAutoField::init(const QString &text, WidgetType type, LabelPosition pos)
+{
+ d->fieldTypeInternal = KexiDB::Field::InvalidType;
+ d->layout = 0;
+ m_subwidget = 0;
+ d->label = new QLabel(text, this);
+ d->label->installEventFilter( this );
+ //QFontMetrics fm( font() );
+ //d->label->setFixedWidth( fm.width("This is a test string length") );
+ d->autoCaption = true;
+ d->focusPolicyChanged = false;
+ d->widgetType = Auto;
+ d->widgetType_property = (type==Auto ? Text : type); //to force "differ" to be true in setWidgetType()
+ setLabelPosition(pos);
+ setWidgetType(type);
+ d->baseColor = palette().active().base();
+ d->textColor = palette().active().text();
+}
+
+void
+KexiDBAutoField::setWidgetType(WidgetType type)
+{
+ const bool differ = (type != d->widgetType_property);
+ d->widgetType_property = type;
+ if(differ) {
+ if(type == Auto) {// try to guess type from data source type
+ if (visibleColumnInfo())
+ d->widgetType = KexiDBAutoField::widgetTypeForFieldType(visibleColumnInfo()->field->type());
+ else
+ d->widgetType = Auto;
+ }
+ else
+ d->widgetType = d->widgetType_property;
+ createEditor();
+ }
+}
+
+void
+KexiDBAutoField::createEditor()
+{
+ if(m_subwidget) {
+ delete (QWidget *)m_subwidget;
+ }
+
+ QWidget *newSubwidget;
+ switch( d->widgetType ) {
+ case Text:
+ case Double: //! @todo setup validator
+ case Integer: //! @todo setup validator
+ case Date:
+ case Time:
+ case DateTime:
+ newSubwidget = new KexiDBLineEdit( this, QCString("KexiDBAutoField_KexiDBLineEdit:")+name() );
+ break;
+ case MultiLineText:
+ newSubwidget = new KexiDBTextEdit( this, QCString("KexiDBAutoField_KexiDBTextEdit:")+name() );
+ break;
+ case Boolean:
+ newSubwidget = new KexiDBCheckBox(dataSource(), this, QCString("KexiDBAutoField_KexiDBCheckBox:")+name());
+ break;
+ case Image:
+ newSubwidget = new KexiDBImageBox(d->designMode, this, QCString("KexiDBAutoField_KexiDBImageBox:")+name());
+ break;
+ case ComboBox:
+ newSubwidget = new KexiDBComboBox(this, QCString("KexiDBAutoField_KexiDBComboBox:")+name(), d->designMode);
+ break;
+ default:
+ newSubwidget = 0;
+ changeText(d->caption);
+ //d->label->setText( d->dataSource.isEmpty() ? "<datasource>" : d->dataSource );
+ break;
+ }
+
+ setSubwidget( newSubwidget ); //this will also allow to declare subproperties, see KFormDesigner::WidgetWithSubpropertiesInterface
+ if(newSubwidget) {
+ newSubwidget->setName( QCString("KexiDBAutoField_") + newSubwidget->className() );
+ dynamic_cast<KexiDataItemInterface*>(newSubwidget)->setParentDataItemInterface(this);
+ dynamic_cast<KexiFormDataItemInterface*>(newSubwidget)
+ ->setColumnInfo(columnInfo()); //needed at least by KexiDBImageBox
+ dynamic_cast<KexiFormDataItemInterface*>(newSubwidget)
+ ->setVisibleColumnInfo(visibleColumnInfo()); //needed at least by KexiDBComboBox
+ newSubwidget->setProperty("dataSource", dataSource()); //needed at least by KexiDBImageBox
+ KFormDesigner::DesignTimeDynamicChildWidgetHandler::childWidgetAdded(this);
+ newSubwidget->show();
+ d->label->setBuddy(newSubwidget);
+ if (d->focusPolicyChanged) {//if focusPolicy is changed at top level, editor inherits it
+ newSubwidget->setFocusPolicy(focusPolicy());
+ }
+ else {//if focusPolicy is not changed at top level, inherit it from editor
+ QWidget::setFocusPolicy(newSubwidget->focusPolicy());
+ }
+ setFocusProxy(newSubwidget); //ok?
+ if (parentWidget())
+ newSubwidget->setPalette( qApp->palette() );
+ copyPropertiesToEditor();
+// KFormDesigner::installRecursiveEventFilter(newSubwidget, this);
+ }
+
+ setLabelPosition(labelPosition());
+}
+
+void KexiDBAutoField::copyPropertiesToEditor()
+{
+ if (m_subwidget) {
+// kdDebug() << "KexiDBAutoField::copyPropertiesToEditor(): base col: " << d->baseColor.name() <<
+// "; text col: " << d->textColor.name() << endl;
+ QPalette p( m_subwidget->palette() );
+ p.setColor( QPalette::Active, QColorGroup::Base, d->baseColor );
+ if(d->widgetType == Boolean)
+ p.setColor( QPalette::Active, QColorGroup::Foreground, d->textColor );
+ else
+ p.setColor( QPalette::Active, QColorGroup::Text, d->textColor );
+ m_subwidget->setPalette(p);
+ //m_subwidget->setPaletteBackgroundColor( d->baseColor );
+ }
+}
+
+void
+KexiDBAutoField::setLabelPosition(LabelPosition position)
+{
+ d->lblPosition = position;
+ if(d->layout) {
+ QBoxLayout *lyr = d->layout;
+ d->layout = 0;
+ delete lyr;
+ }
+
+ if(m_subwidget)
+ m_subwidget->show();
+ //! \todo support right-to-left layout where positions are inverted
+ if (position==Top || position==Left) {
+ int align = d->label->alignment();
+ if(position == Top) {
+ d->layout = (QBoxLayout*) new QVBoxLayout(this);
+ align |= AlignVertical_Mask;
+ align ^= AlignVertical_Mask;
+ align |= AlignTop;
+ }
+ else {
+ d->layout = (QBoxLayout*) new QHBoxLayout(this);
+ align |= AlignVertical_Mask;
+ align ^= AlignVertical_Mask;
+ align |= AlignVCenter;
+ }
+ d->label->setAlignment(align);
+ if(d->widgetType == Boolean
+ || (d->widgetType == Auto && fieldTypeInternal() == KexiDB::Field::InvalidType && !d->designMode))
+ {
+ d->label->hide();
+ }
+ else {
+ d->label->show();
+ }
+ d->layout->addWidget(d->label, 0, position == Top ? int(Qt::AlignLeft) : 0);
+ if(position == Left && d->widgetType != Boolean)
+ d->layout->addSpacing(KexiDBAutoField_SPACING);
+ d->layout->addWidget(m_subwidget, 1);
+ KexiSubwidgetInterface *subwidgetInterface = dynamic_cast<KexiSubwidgetInterface*>((QWidget*)m_subwidget);
+ if (subwidgetInterface) {
+ if (subwidgetInterface->appendStretchRequired(this))
+ d->layout->addStretch(0);
+ if (subwidgetInterface->subwidgetStretchRequired(this)) {
+ QSizePolicy sizePolicy( m_subwidget->sizePolicy() );
+ if(position == Left) {
+ sizePolicy.setHorData( QSizePolicy::Minimum );
+ d->label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ }
+ else {
+ sizePolicy.setVerData( QSizePolicy::Minimum );
+ d->label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ }
+ m_subwidget->setSizePolicy(sizePolicy);
+ }
+ }
+// if(m_subwidget)
+ // m_subwidget->setSizePolicy(...);
+ }
+ else {
+ d->layout = (QBoxLayout*) new QHBoxLayout(this);
+ d->label->hide();
+ d->layout->addWidget(m_subwidget);
+ }
+ //a hack to force layout to be refreshed (any better idea for this?)
+ resize(size()+QSize(1,0));
+ resize(size()-QSize(1,0));
+ if (dynamic_cast<KexiDBAutoField*>((QWidget*)m_subwidget)) {
+ //needed for KexiDBComboBox
+ dynamic_cast<KexiDBAutoField*>((QWidget*)m_subwidget)->setLabelPosition(position);
+ }
+}
+
+void
+KexiDBAutoField::setInvalidState( const QString &text )
+{
+ // Widget with an invalid dataSource is just a QLabel
+ if (d->designMode)
+ return;
+ d->widgetType = Auto;
+ createEditor();
+ setFocusPolicy(QWidget::NoFocus);
+ if (m_subwidget)
+ m_subwidget->setFocusPolicy(QWidget::NoFocus);
+//! @todo or set this to editor's text?
+ d->label->setText( text );
+}
+
+bool
+KexiDBAutoField::isReadOnly() const
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ return iface->isReadOnly();
+ else
+ return false;
+}
+
+void
+KexiDBAutoField::setReadOnly( bool readOnly )
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ iface->setReadOnly(readOnly);
+}
+
+void
+KexiDBAutoField::setValueInternal(const QVariant& add, bool removeOld)
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ iface->setValue(m_origValue, add, removeOld);
+// iface->setValueInternal(add, removeOld);
+}
+
+QVariant
+KexiDBAutoField::value()
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ return iface->value();
+ return QVariant();
+}
+
+bool
+KexiDBAutoField::valueIsNull()
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ return iface->valueIsNull();
+ return true;
+}
+
+bool
+KexiDBAutoField::valueIsEmpty()
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ return iface->valueIsEmpty();
+ return true;
+}
+
+bool
+KexiDBAutoField::valueIsValid()
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ return iface->valueIsValid();
+ return true;
+}
+
+bool
+KexiDBAutoField::valueChanged()
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ kexipluginsdbg << m_origValue << endl;
+ if(iface)
+ return iface->valueChanged();
+ return false;
+}
+
+void
+KexiDBAutoField::installListener(KexiDataItemChangesListener* listener)
+{
+ KexiFormDataItemInterface::installListener(listener);
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ iface->installListener(listener);
+}
+
+KexiDBAutoField::WidgetType KexiDBAutoField::widgetType() const
+{
+ return d->widgetType_property;
+}
+
+KexiDBAutoField::LabelPosition KexiDBAutoField::labelPosition() const
+{
+ return d->lblPosition;
+}
+
+QString KexiDBAutoField::caption() const
+{
+ return d->caption;
+}
+
+bool KexiDBAutoField::hasAutoCaption() const
+{
+ return d->autoCaption;
+}
+
+QWidget* KexiDBAutoField::editor() const
+{
+ return m_subwidget;
+}
+
+QLabel* KexiDBAutoField::label() const
+{
+ return d->label;
+}
+
+int KexiDBAutoField::fieldTypeInternal() const
+{
+ return d->fieldTypeInternal;
+}
+
+QString KexiDBAutoField::fieldCaptionInternal() const
+{
+ return d->fieldCaptionInternal;
+}
+
+bool
+KexiDBAutoField::cursorAtStart()
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ return iface->cursorAtStart();
+ return false;
+}
+
+bool
+KexiDBAutoField::cursorAtEnd()
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ return iface->cursorAtEnd();
+ return false;
+}
+
+void
+KexiDBAutoField::clear()
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ iface->clear();
+}
+
+void
+KexiDBAutoField::setFieldTypeInternal(int kexiDBFieldType)
+{
+ d->fieldTypeInternal = (KexiDB::Field::Type)kexiDBFieldType;
+ KexiDB::Field::Type fieldType;
+ //find real fied type to use
+ if (d->fieldTypeInternal==KexiDB::Field::InvalidType) {
+ if (visibleColumnInfo())
+ fieldType = KexiDB::Field::Text;
+ else
+ fieldType = KexiDB::Field::InvalidType;
+ }
+ else
+ fieldType = d->fieldTypeInternal;
+
+ const WidgetType newWidgetType = KexiDBAutoField::widgetTypeForFieldType( fieldType );
+
+ if(d->widgetType != newWidgetType) {
+ d->widgetType = newWidgetType;
+ createEditor();
+ }
+ setFieldCaptionInternal(d->fieldCaptionInternal);
+}
+
+void
+KexiDBAutoField::setFieldCaptionInternal(const QString& text)
+{
+ d->fieldCaptionInternal = text;
+ //change text only if autocaption is set and no columnInfo is available
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if((!iface || !iface->columnInfo()) && d->autoCaption) {
+ changeText(d->fieldCaptionInternal);
+ }
+}
+
+void
+KexiDBAutoField::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
+{
+ KexiFormDataItemInterface::setColumnInfo(cinfo);
+ setColumnInfoInternal(cinfo, cinfo);
+}
+
+void
+KexiDBAutoField::setColumnInfoInternal(KexiDB::QueryColumnInfo* cinfo, KexiDB::QueryColumnInfo* visibleColumnInfo)
+{
+ // change widget type depending on field type
+ if(d->widgetType_property == Auto) {
+ WidgetType newWidgetType = Auto;
+ KexiDB::Field::Type fieldType;
+ if (cinfo)
+ fieldType = visibleColumnInfo->field->type();
+ else if (dataSource().isEmpty())
+ fieldType = KexiDB::Field::InvalidType;
+ else
+ fieldType = KexiDB::Field::Text;
+
+ if (fieldType != KexiDB::Field::InvalidType) {
+ newWidgetType = KexiDBAutoField::widgetTypeForFieldType( fieldType );
+ }
+ if(d->widgetType != newWidgetType || newWidgetType==Auto) {
+ d->widgetType = newWidgetType;
+ createEditor();
+ }
+ }
+ // update label's text
+ changeText((cinfo && d->autoCaption) ? cinfo->captionOrAliasOrName() : d->caption);
+
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ iface->setColumnInfo(visibleColumnInfo);
+}
+
+//static
+KexiDBAutoField::WidgetType
+KexiDBAutoField::widgetTypeForFieldType(KexiDB::Field::Type type)
+{
+ switch(type) {
+ case KexiDB::Field::Integer:
+ case KexiDB::Field::ShortInteger:
+ case KexiDB::Field::BigInteger:
+ return Integer;
+ case KexiDB::Field::Boolean:
+ return Boolean;
+ case KexiDB::Field::Float:
+ case KexiDB::Field::Double:
+ return Double;
+ case KexiDB::Field::Date:
+ return Date;
+ case KexiDB::Field::DateTime:
+ return DateTime;
+ case KexiDB::Field::Time:
+ return Time;
+ case KexiDB::Field::Text:
+ return Text;
+ case KexiDB::Field::LongText:
+ return MultiLineText;
+ case KexiDB::Field::Enum:
+ return ComboBox;
+ case KexiDB::Field::InvalidType:
+ return Auto;
+ case KexiDB::Field::BLOB:
+ return Image;
+ default:
+ break;
+ }
+ return Text;
+}
+
+void
+KexiDBAutoField::changeText(const QString &text, bool beautify)
+{
+ QString realText;
+ bool unbound = false;
+ if (d->autoCaption && (d->widgetType==Auto || dataSource().isEmpty())) {
+ if (d->designMode)
+ realText = QString::fromLatin1(name())+" "+i18n("Unbound Auto Field", "(unbound)");
+ else
+ realText = QString::null;
+ unbound = true;
+ }
+ else {
+ if (beautify) {
+ /*! @todo look at appendColonToAutoLabels setting [bool]
+ @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool]
+ (see doc/dev/settings.txt) */
+ if (!text.isEmpty()) {
+ realText = text[0].upper() + text.mid(1);
+ if (d->widgetType!=Boolean) {
+//! @todo ":" suffix looks weird for checkbox; remove this condition when [x] is displayed _after_ label
+//! @todo support right-to-left layout where position of ":" is inverted
+ realText += ": ";
+ }
+ }
+ }
+ else
+ realText = text;
+ }
+
+ if (unbound)
+ d->label->setAlignment( Qt::AlignCenter | Qt::WordBreak );
+ else
+ d->label->setAlignment( Qt::AlignCenter );
+// QWidget* widgetToAlterForegroundColor;
+ if(d->widgetType == Boolean) {
+ static_cast<QCheckBox*>((QWidget*)m_subwidget)->setText(realText);
+// widgetToAlterForegroundColor = m_subwidget;
+ }
+ else {
+ d->label->setText(realText);
+// widgetToAlterForegroundColor = d->label;
+ }
+/*
+ if (unbound)
+ widgetToAlterForegroundColor->setPaletteForegroundColor(
+ KexiUtils::blendedColors(
+ widgetToAlterForegroundColor->paletteForegroundColor(),
+ widgetToAlterForegroundColor->paletteBackgroundColor(), 2, 1));
+ else
+ widgetToAlterForegroundColor->setPaletteForegroundColor( paletteForegroundColor() );*/
+}
+
+void
+KexiDBAutoField::setCaption(const QString &caption)
+{
+ d->caption = caption;
+ if(!d->autoCaption && !caption.isEmpty())
+ changeText(d->caption);
+}
+
+void
+KexiDBAutoField::setAutoCaption(bool autoCaption)
+{
+ d->autoCaption = autoCaption;
+ if(d->autoCaption) {
+ //d->caption = QString::null;
+ if(columnInfo()) {
+ changeText(columnInfo()->captionOrAliasOrName());
+ }
+ else {
+ changeText(d->fieldCaptionInternal);
+ }
+ }
+ else
+ changeText(d->caption);
+}
+
+void
+KexiDBAutoField::setDataSource( const QString &ds ) {
+ KexiFormDataItemInterface::setDataSource(ds);
+ if (ds.isEmpty()) {
+ setColumnInfo(0);
+ }
+}
+
+QSize
+KexiDBAutoField::sizeHint() const
+{
+ if (d->lblPosition == NoLabel)
+ return m_subwidget ? m_subwidget->sizeHint() : QWidget::sizeHint();
+
+ QSize s1(0,0);
+ if (m_subwidget)
+ s1 = m_subwidget->sizeHint();
+ QSize s2(d->label->sizeHint());
+ if (d->lblPosition == Top)
+ return QSize(QMAX(s1.width(), s2.width()), s1.height()+KexiDBAutoField_SPACING+s2.height());
+
+ //left
+ return QSize(s1.width()+KexiDBAutoField_SPACING+s2.width(), QMAX(s1.height(), s2.height()));
+}
+
+void
+KexiDBAutoField::setFocusPolicy( FocusPolicy policy )
+{
+ d->focusPolicyChanged = true;
+ QWidget::setFocusPolicy(policy);
+ d->label->setFocusPolicy(policy);
+ if (m_subwidget)
+ m_subwidget->setFocusPolicy(policy);
+}
+
+void
+KexiDBAutoField::updateInformationAboutUnboundField()
+{
+ if ( (d->autoCaption && (dataSource().isEmpty() || dataSourceMimeType().isEmpty()))
+ || (!d->autoCaption && d->caption.isEmpty()) )
+ {
+ d->label->setText( QString::fromLatin1(name())+" "+i18n("Unbound Auto Field", " (unbound)") );
+ }
+// else
+// d->label->setText( QString::fromLatin1(name())+" "+i18n(" (unbound)") );
+}
+
+/*void
+KexiDBAutoField::paintEvent( QPaintEvent* pe )
+{
+ QWidget::paintEvent( pe );
+
+ if ( (d->autoCaption && (dataSource().isEmpty() || dataSourceMimeType().isEmpty()))
+ || (!d->autoCaption && d->caption.isEmpty()) )
+ {
+ QPainter p(this);
+ p.setPen( d->label->paletteForegroundColor() );
+ p.setClipRect(pe->rect());
+ p.setFont(d->label->font());
+ p.drawText(rect(), Qt::AlignLeft | Qt::WordBreak,
+ QString::fromLatin1(name())+" "+i18n(" (unbound)"));
+ }
+}*/
+
+void
+KexiDBAutoField::paletteChange( const QPalette& oldPal )
+{
+ Q_UNUSED(oldPal);
+ d->label->setPalette( palette() );
+}
+
+void KexiDBAutoField::unsetPalette()
+{
+ QWidget::unsetPalette();
+
+}
+
+// ===== methods below are just proxies for the internal editor or label =====
+
+const QColor & KexiDBAutoField::paletteForegroundColor() const
+{
+ return d->textColor;
+}
+
+void KexiDBAutoField::setPaletteForegroundColor( const QColor & color )
+{
+ d->textColor = color;
+ copyPropertiesToEditor();
+}
+
+const QColor & KexiDBAutoField::paletteBackgroundColor() const
+{
+ return d->baseColor;
+}
+
+void KexiDBAutoField::setPaletteBackgroundColor( const QColor & color )
+{
+ d->baseColor = color;
+ copyPropertiesToEditor();
+}
+
+const QColor & KexiDBAutoField::foregroundLabelColor() const
+{
+ if(d->widgetType == Boolean)
+ return paletteForegroundColor();
+
+ return d->label->paletteForegroundColor();
+}
+
+void KexiDBAutoField::setForegroundLabelColor( const QColor & color )
+{
+ if(d->widgetType == Boolean)
+ setPaletteForegroundColor(color);
+ else {
+ d->label->setPaletteForegroundColor(color);
+ QWidget::setPaletteForegroundColor(color);
+ }
+}
+
+const QColor & KexiDBAutoField::backgroundLabelColor() const
+{
+ if(d->widgetType == Boolean)
+ return paletteBackgroundColor();
+
+ return d->label->paletteBackgroundColor();
+}
+
+void KexiDBAutoField::setBackgroundLabelColor( const QColor & color )
+{
+ if(d->widgetType == Boolean)
+ setPaletteBackgroundColor(color);
+ else {
+ d->label->setPaletteBackgroundColor(color);
+ QWidget::setPaletteBackgroundColor(color);
+ }
+
+// if (m_subwidget)
+// m_subwidget->setPalette( qApp->palette() );
+}
+
+QVariant KexiDBAutoField::property( const char * name ) const
+{
+ bool ok;
+ QVariant val = KFormDesigner::WidgetWithSubpropertiesInterface::subproperty(name, ok);
+ if (ok)
+ return val;
+ return QWidget::property(name);
+}
+
+bool KexiDBAutoField::setProperty( const char * name, const QVariant & value )
+{
+ bool ok = KFormDesigner::WidgetWithSubpropertiesInterface::setSubproperty(name, value);
+ if (ok)
+ return true;
+ return QWidget::setProperty(name, value);
+}
+
+bool KexiDBAutoField::eventFilter( QObject *o, QEvent *e )
+{
+ if (o==d->label && d->label->buddy() && e->type()==QEvent::MouseButtonRelease) {
+ //focus label's buddy when user clicked the label
+ d->label->buddy()->setFocus();
+ }
+ return QWidget::eventFilter(o, e);
+}
+
+void KexiDBAutoField::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue)
+{
+ KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue);
+ if (dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget))
+ dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget)->setDisplayDefaultValue(m_subwidget, displayDefaultValue);
+}
+
+void KexiDBAutoField::moveCursorToEnd()
+{
+ KexiDataItemInterface *iface = dynamic_cast<KexiDataItemInterface*>((QWidget*)m_subwidget);
+ if (iface)
+ iface->moveCursorToEnd();
+}
+
+void KexiDBAutoField::moveCursorToStart()
+{
+ KexiDataItemInterface *iface = dynamic_cast<KexiDataItemInterface*>((QWidget*)m_subwidget);
+ if (iface)
+ iface->moveCursorToStart();
+}
+
+void KexiDBAutoField::selectAll()
+{
+ KexiDataItemInterface *iface = dynamic_cast<KexiDataItemInterface*>((QWidget*)m_subwidget);
+ if (iface)
+ iface->selectAll();
+}
+
+bool KexiDBAutoField::keyPressed(QKeyEvent *ke)
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if (iface && iface->keyPressed(ke))
+ return true;
+ return false;
+}
+
+#include "kexidbautofield.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbautofield.h b/kexi/plugins/forms/widgets/kexidbautofield.h
new file mode 100644
index 000000000..981a05194
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbautofield.h
@@ -0,0 +1,210 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de>
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDBAUTOFIELD_H
+#define KEXIDBAUTOFIELD_H
+
+#include <qwidget.h>
+#include <kexidb/field.h>
+#include <formeditor/container.h>
+#include <formeditor/widgetwithsubpropertiesinterface.h>
+#include "kexiformdataiteminterface.h"
+
+class QBoxLayout;
+class QLabel;
+
+//! Universal "Auto Field" widget for Kexi forms
+/*! It acts as a container for most data-aware widgets. */
+class KEXIFORMUTILS_EXPORT KexiDBAutoField :
+ public QWidget,
+ public KexiFormDataItemInterface,
+ public KFormDesigner::DesignTimeDynamicChildWidgetHandler,
+ public KFormDesigner::WidgetWithSubpropertiesInterface
+{
+ Q_OBJECT
+//'caption' is uncovered now Q_PROPERTY(QString labelCaption READ caption WRITE setCaption DESIGNABLE true)
+ Q_OVERRIDE(QString caption READ caption WRITE setCaption DESIGNABLE true)
+ Q_OVERRIDE(QColor paletteForegroundColor READ paletteForegroundColor WRITE setPaletteForegroundColor DESIGNABLE true RESET unsetPalette)
+ Q_OVERRIDE(QColor paletteBackgroundColor READ paletteBackgroundColor WRITE setPaletteBackgroundColor DESIGNABLE true RESET unsetPalette)
+ Q_PROPERTY(QColor foregroundLabelColor READ foregroundLabelColor WRITE setForegroundLabelColor DESIGNABLE true RESET unsetPalette)
+ Q_PROPERTY(QColor backgroundLabelColor READ backgroundLabelColor WRITE setBackgroundLabelColor DESIGNABLE true RESET unsetPalette)
+ Q_PROPERTY(bool autoCaption READ hasAutoCaption WRITE setAutoCaption DESIGNABLE true)
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly )
+ Q_PROPERTY(LabelPosition labelPosition READ labelPosition WRITE setLabelPosition DESIGNABLE true)
+ Q_PROPERTY(WidgetType widgetType READ widgetType WRITE setWidgetType DESIGNABLE true)
+ /*internal, for design time only*/
+ Q_PROPERTY(int fieldTypeInternal READ fieldTypeInternal WRITE setFieldTypeInternal DESIGNABLE true STORED false)
+ Q_PROPERTY(QString fieldCaptionInternal READ fieldCaptionInternal WRITE setFieldCaptionInternal DESIGNABLE true STORED false)
+ Q_ENUMS( WidgetType LabelPosition )
+
+ public:
+ enum WidgetType { Auto = 100, Text, Integer, Double, Boolean, Date, Time, DateTime,
+ MultiLineText, ComboBox, Image };
+ enum LabelPosition { Left = 300, Top, NoLabel };
+
+ KexiDBAutoField(const QString &text, WidgetType type, LabelPosition pos,
+ QWidget *parent = 0, const char *name = 0, bool designMode = true);
+ KexiDBAutoField(QWidget *parent = 0, const char *name = 0, bool designMode = true,
+ LabelPosition pos = Left);
+
+ virtual ~KexiDBAutoField();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual void setDataSource( const QString &ds );
+ virtual void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo);
+
+ virtual void setInvalidState(const QString& text);
+ virtual bool isReadOnly() const;
+ virtual void setReadOnly( bool readOnly );
+
+ virtual QVariant value();
+ virtual bool valueIsNull();
+ virtual bool valueIsEmpty();
+ virtual bool valueIsValid();
+ virtual bool valueChanged();
+ virtual void clear();
+
+ //! Reimpelmented to also install \a listenter for internal editor
+ virtual void installListener(KexiDataItemChangesListener* listener);
+
+ WidgetType widgetType() const;
+ void setWidgetType(WidgetType type);
+
+ LabelPosition labelPosition() const;
+ virtual void setLabelPosition(LabelPosition position);
+
+ QString caption() const;
+ void setCaption(const QString &caption);
+
+ bool hasAutoCaption() const;
+ void setAutoCaption(bool autoCaption);
+
+ /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue()
+ is displayed in a special way. Used by KexiFormDataProvider::fillDataItems().
+ \a widget is equal to 'this'.
+ Reimplemented after KexiFormDataItemInterface. */
+ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue);
+
+ QWidget* editor() const;
+ QLabel* label() const;
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+
+ static WidgetType widgetTypeForFieldType(KexiDB::Field::Type type);
+
+ /*! On design time it is not possible to pass a reference to KexiDB::Field object
+ so we're just providing field type. Only used when widget type is Auto.
+ @internal */
+ void setFieldTypeInternal(int kexiDBFieldType);
+
+ /*! On design time it is not possible to pass a reference to KexiDB::Field object
+ so we're just providing field caption. Only used when widget type is Auto.
+ @internal */
+ void setFieldCaptionInternal(const QString& text);
+
+ /*! @internal */
+ int fieldTypeInternal() const;
+
+ /*! @internal */
+ QString fieldCaptionInternal() const;
+
+ virtual QSize sizeHint() const;
+ virtual void setFocusPolicy ( FocusPolicy policy );
+
+ //! Reimplemented to return internal editor's color.
+ const QColor & paletteForegroundColor() const;
+
+ //! Reimplemented to set internal editor's color.
+ void setPaletteForegroundColor( const QColor & color );
+
+ //! Reimplemented to return internal editor's color.
+ const QColor & paletteBackgroundColor() const;
+
+ //! Reimplemented to set internal editor's color.
+ virtual void setPaletteBackgroundColor( const QColor & color );
+
+ //! \return label's foreground color
+ const QColor & foregroundLabelColor() const;
+
+ //! Sets label's foreground color
+ virtual void setForegroundLabelColor( const QColor & color );
+
+ //! \return label's background color
+ const QColor & backgroundLabelColor() const;
+
+ //! Sets label's background color
+ virtual void setBackgroundLabelColor( const QColor & color );
+
+ //! Reimplemented to accept subproperties. @see KFormDesigner::WidgetWithSubpropertiesInterface
+ virtual QVariant property( const char * name ) const;
+
+ //! Reimplemented to accept subproperties. @see KFormDesigner::WidgetWithSubpropertiesInterface
+ virtual bool setProperty( const char * name, const QVariant & value );
+
+ /*! Called by the top-level form on key press event to consume widget-specific shortcuts. */
+ virtual bool keyPressed(QKeyEvent *ke);
+
+ public slots:
+ virtual void unsetPalette();
+
+ protected slots:
+// void slotValueChanged();
+ virtual void paletteChange( const QPalette& oldPal );
+
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToEnd();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToStart();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void selectAll();
+
+ protected:
+ virtual void setValueInternal(const QVariant&add, bool removeOld);
+ void init(const QString &text, WidgetType type, LabelPosition pos);
+ virtual void createEditor();
+ void changeText(const QString &text, bool beautify = true);
+// virtual void paintEvent( QPaintEvent* pe );
+ void updateInformationAboutUnboundField();
+
+ //! internal editor can be created too late, so certain properties should be copied
+ void copyPropertiesToEditor();
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ //! Used by @ref setLabelPositionInternal(LabelPosition)
+ void setLabelPositionInternal(LabelPosition position, bool noLabel);
+
+ //! Used by KexiDBAutoField::setColumnInfo() and KexiDBComboBox::setColumnInfo()
+ void setColumnInfoInternal(KexiDB::QueryColumnInfo* cinfo, KexiDB::QueryColumnInfo* visibleColumnInfo);
+
+ private:
+ class Private;
+ Private *d;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbcheckbox.cpp b/kexi/plugins/forms/widgets/kexidbcheckbox.cpp
new file mode 100644
index 000000000..6b63851af
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbcheckbox.cpp
@@ -0,0 +1,175 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbcheckbox.h"
+
+#include <kexiutils/utils.h>
+#include <kexidb/queryschema.h>
+
+KexiDBCheckBox::KexiDBCheckBox(const QString &text, QWidget *parent, const char *name)
+ : QCheckBox(text, parent, name), KexiFormDataItemInterface()
+ , m_invalidState(false)
+ , m_tristateChanged(false)
+ , m_tristate(TristateDefault)
+{
+ setFocusPolicy(QWidget::StrongFocus);
+ updateTristate();
+ connect(this, SIGNAL(stateChanged(int)), this, SLOT(slotStateChanged(int)));
+}
+
+KexiDBCheckBox::~KexiDBCheckBox()
+{
+}
+
+void KexiDBCheckBox::setInvalidState( const QString& displayText )
+{
+ setEnabled(false);
+ setState(NoChange);
+ m_invalidState = true;
+//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ?
+ if (focusPolicy() & TabFocus)
+ setFocusPolicy(QWidget::ClickFocus);
+ setText(displayText);
+}
+
+void
+KexiDBCheckBox::setEnabled(bool enabled)
+{
+ if(enabled && m_invalidState)
+ return;
+ QCheckBox::setEnabled(enabled);
+}
+
+void
+KexiDBCheckBox::setReadOnly(bool readOnly)
+{
+ setEnabled(!readOnly);
+}
+
+void KexiDBCheckBox::setValueInternal(const QVariant &add, bool removeOld)
+{
+ Q_UNUSED(add);
+ Q_UNUSED(removeOld);
+ if (isTristateInternal())
+ setState( m_origValue.isNull() ? NoChange : (m_origValue.toBool() ? On : Off) );
+ else
+ setState( m_origValue.toBool() ? On : Off );
+}
+
+QVariant
+KexiDBCheckBox::value()
+{
+ if (state()==NoChange)
+ return QVariant();
+ return QVariant(state()==On, 1);
+}
+
+void KexiDBCheckBox::slotStateChanged(int )
+{
+ signalValueChanged();
+}
+
+bool KexiDBCheckBox::valueIsNull()
+{
+ return state() == NoChange;
+}
+
+bool KexiDBCheckBox::valueIsEmpty()
+{
+ return false;
+}
+
+bool KexiDBCheckBox::isReadOnly() const
+{
+ return !isEnabled();
+}
+
+QWidget*
+KexiDBCheckBox::widget()
+{
+ return this;
+}
+
+bool KexiDBCheckBox::cursorAtStart()
+{
+ return false; //! \todo ?
+}
+
+bool KexiDBCheckBox::cursorAtEnd()
+{
+ return false; //! \todo ?
+}
+
+void KexiDBCheckBox::clear()
+{
+ setState(NoChange);
+}
+
+void KexiDBCheckBox::setTristate(KexiDBCheckBox::Tristate tristate)
+{
+ m_tristateChanged = true;
+ m_tristate = tristate;
+ updateTristate();
+}
+
+KexiDBCheckBox::Tristate KexiDBCheckBox::isTristate() const
+{
+ return m_tristate;
+}
+
+bool KexiDBCheckBox::isTristateInternal() const
+{
+ if (m_tristate == TristateDefault)
+ return !dataSource().isEmpty();
+
+ return m_tristate == TristateOn;
+}
+
+void KexiDBCheckBox::updateTristate()
+{
+ if (m_tristate == TristateDefault) {
+//! @todo the data source may be defined as NOT NULL... thus disallowing NULL state
+ QCheckBox::setTristate( !dataSource().isEmpty() );
+ }
+ else {
+ QCheckBox::setTristate( m_tristate == TristateOn );
+ }
+}
+
+void KexiDBCheckBox::setDataSource(const QString &ds)
+{
+ KexiFormDataItemInterface::setDataSource(ds);
+ updateTristate();
+}
+
+void KexiDBCheckBox::setDisplayDefaultValue(QWidget *widget, bool displayDefaultValue)
+{
+ KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue);
+ // initialize display parameters for default / entered value
+ KexiDisplayUtils::DisplayParameters * const params
+ = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue;
+// setFont(params->font);
+ QPalette pal(palette());
+// pal.setColor(QPalette::Active, QColorGroup::Text, params->textColor);
+ pal.setColor(QPalette::Active, QColorGroup::Foreground, params->textColor);
+ setPalette(pal);
+}
+
+#include "kexidbcheckbox.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbcheckbox.h b/kexi/plugins/forms/widgets/kexidbcheckbox.h
new file mode 100644
index 000000000..d4a68bf39
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbcheckbox.h
@@ -0,0 +1,99 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBCheckBox_H
+#define KexiDBCheckBox_H
+
+#include "kexiformdataiteminterface.h"
+#include <qcheckbox.h>
+
+//! @short A db-aware check box
+class KEXIFORMUTILS_EXPORT KexiDBCheckBox : public QCheckBox, public KexiFormDataItemInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ Q_OVERRIDE( Tristate tristate READ isTristate WRITE setTristate )
+ Q_ENUMS( Tristate )
+
+ public:
+ KexiDBCheckBox(const QString &text, QWidget *parent, const char *name=0);
+ virtual ~KexiDBCheckBox();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual QVariant value();
+ virtual void setInvalidState( const QString& displayText );
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsEmpty();
+
+ /*! \return 'readOnly' flag for this widget. */
+ virtual bool isReadOnly() const;
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ virtual void setEnabled(bool enabled);
+
+ enum Tristate { TristateDefault, TristateOn, TristateOff };
+
+ void setTristate(Tristate tristate);
+ Tristate isTristate() const;
+
+ /*! Reimplemented after KexiFormDataItemInterface. */
+ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue);
+
+ public slots:
+ void setDataSource(const QString &ds);
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ void slotStateChanged(int state);
+
+ //! This implementation just disables read only widget
+ virtual void setReadOnly( bool readOnly );
+
+ protected:
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ //! \return true in isTristate() == TristateDefault and the widget has bound data source
+ //! or if isTristate() == TristateOn, else false is returned.
+ bool isTristateInternal() const;
+
+ //! Updates tristate in QCheckBox itself according to m_tristate.
+ void updateTristate();
+
+ private:
+ bool m_invalidState : 1;
+ bool m_tristateChanged : 1; //!< used in setTristate()
+ Tristate m_tristate; //!< used in isTristate() and setTristate()
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbcombobox.cpp b/kexi/plugins/forms/widgets/kexidbcombobox.cpp
new file mode 100644
index 000000000..19366a155
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbcombobox.cpp
@@ -0,0 +1,550 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbcombobox.h"
+#include "kexidblineedit.h"
+#include "../kexiformscrollview.h"
+
+#include <kcombobox.h>
+#include <kdebug.h>
+#include <kapplication.h>
+
+#include <qmetaobject.h>
+#include <qpainter.h>
+#include <qstyle.h>
+#include <qdrawutil.h>
+#include <qptrdict.h>
+#include <qcursor.h>
+
+#include <kexidb/queryschema.h>
+#include <widget/tableview/kexicomboboxpopup.h>
+#include <widget/tableview/kexicelleditorfactory.h>
+#include <kexiutils/utils.h>
+
+//! @internal
+class KexiDBComboBox::Private
+{
+ public:
+ Private()
+ : popup(0)
+ , visibleColumnInfo(0)
+ , subWidgetsWithDisabledEvents(0)
+ , isEditable(false)
+ , buttonPressed(false)
+ , mouseOver(false)
+ , dataEnteredByHand(true)
+ {
+ }
+ ~Private()
+ {
+ delete subWidgetsWithDisabledEvents;
+ subWidgetsWithDisabledEvents = 0;
+ }
+
+ KexiComboBoxPopup *popup;
+ KComboBox *paintedCombo; //!< fake combo used only to pass it as 'this' for QStyle (because styles use <static_cast>)
+ QSize sizeHint; //!< A cache for KexiDBComboBox::sizeHint(),
+ //!< rebuilt by KexiDBComboBox::fontChange() and KexiDBComboBox::styleChange()
+ KexiDB::QueryColumnInfo* visibleColumnInfo;
+ QPtrDict<char> *subWidgetsWithDisabledEvents; //! used to collect subwidget and its children (if isEditable is false)
+ bool isEditable : 1; //!< true is the combo box is editable
+ bool buttonPressed : 1;
+ bool mouseOver : 1;
+ bool dataEnteredByHand : 1;
+ bool designMode : 1;
+};
+
+//-------------------------------------
+
+KexiDBComboBox::KexiDBComboBox(QWidget *parent, const char *name, bool designMode)
+ : KexiDBAutoField(parent, name, designMode, NoLabel)
+ , KexiComboBoxBase()
+ , d(new Private())
+{
+ setMouseTracking(true);
+ setFocusPolicy(WheelFocus);
+ installEventFilter(this);
+ d->designMode = designMode;
+ d->paintedCombo = new KComboBox(this);
+ d->paintedCombo->hide();
+ d->paintedCombo->move(0,0);
+}
+
+KexiDBComboBox::~KexiDBComboBox()
+{
+ delete d;
+}
+
+KexiComboBoxPopup *KexiDBComboBox::popup() const
+{
+ return d->popup;
+}
+
+void KexiDBComboBox::setPopup(KexiComboBoxPopup *popup)
+{
+ d->popup = popup;
+}
+
+void KexiDBComboBox::setEditable(bool set)
+{
+ if (d->isEditable == set)
+ return;
+ d->isEditable = set;
+ d->paintedCombo->setEditable(set);
+ if (set)
+ createEditor();
+ else {
+ delete m_subwidget;
+ m_subwidget = 0;
+ }
+ update();
+}
+
+bool KexiDBComboBox::isEditable() const
+{
+ return d->isEditable;
+}
+
+void KexiDBComboBox::paintEvent( QPaintEvent * )
+{
+ QPainter p( this );
+ QColorGroup cg( palette().active() );
+// if ( hasFocus() )
+// cg.setColor(QColorGroup::Base, cg.highlight());
+// else
+ cg.setColor(QColorGroup::Base, paletteBackgroundColor()); //update base color using (reimplemented) bg color
+ p.setPen(cg.text());
+
+ QStyle::SFlags flags = QStyle::Style_Default;
+ if (isEnabled())
+ flags |= QStyle::Style_Enabled;
+ if (hasFocus())
+ flags |= QStyle::Style_HasFocus;
+ if (d->mouseOver)
+ flags |= QStyle::Style_MouseOver;
+
+ if ( width() < 5 || height() < 5 ) {
+ qDrawShadePanel( &p, rect(), cg, FALSE, 2, &cg.brush( QColorGroup::Button ) );
+ return;
+ }
+
+//! @todo support reverse layout
+//bool reverse = QApplication::reverseLayout();
+ style().drawComplexControl( QStyle::CC_ComboBox, &p, d->paintedCombo /*this*/, rect(), cg,
+ flags, (uint)QStyle::SC_All,
+ (d->buttonPressed ? QStyle::SC_ComboBoxArrow : QStyle::SC_None )
+ );
+
+ if (d->isEditable) {
+ //if editable, editor paints itself, nothing to do
+ }
+ else { //not editable: we need to paint the current item
+ QRect editorGeometry( this->editorGeometry() );
+ if ( hasFocus() ) {
+ if (0==qstrcmp(style().name(), "windows")) //a hack
+ p.fillRect( editorGeometry, cg.brush( QColorGroup::Highlight ) );
+ QRect r( QStyle::visualRect( style().subRect( QStyle::SR_ComboBoxFocusRect, d->paintedCombo ), this ) );
+ r = QRect(r.left()-1, r.top()-1, r.width()+2, r.height()+2); //enlare by 1 pixel each side to avoid covering by the subwidget
+ style().drawPrimitive( QStyle::PE_FocusRect, &p,
+ r, cg, flags | QStyle::Style_FocusAtBorder, QStyleOption(cg.highlight()));
+ }
+ //todo
+ }
+}
+
+QRect KexiDBComboBox::editorGeometry() const
+{
+ QRect r( QStyle::visualRect(
+ style().querySubControlMetrics(QStyle::CC_ComboBox, d->paintedCombo,
+ QStyle::SC_ComboBoxEditField), d->paintedCombo ) );
+
+ //if ((height()-r.bottom())<6)
+ // r.setBottom(height()-6);
+ return r;
+}
+
+void KexiDBComboBox::createEditor()
+{
+ KexiDBAutoField::createEditor();
+ if (m_subwidget) {
+ m_subwidget->setGeometry( editorGeometry() );
+ if (!d->isEditable) {
+ m_subwidget->setCursor(QCursor(Qt::ArrowCursor)); // widgets like listedit have IbeamCursor, we don't want that
+//! @todo Qt4: set transparent background, for now we're setting button color
+ QPalette subwidgetPalette( m_subwidget->palette() );
+ subwidgetPalette.setColor(QPalette::Active, QColorGroup::Base,
+ subwidgetPalette.color(QPalette::Active, QColorGroup::Button));
+ m_subwidget->setPalette( subwidgetPalette );
+ if (d->subWidgetsWithDisabledEvents)
+ d->subWidgetsWithDisabledEvents->clear();
+ else
+ d->subWidgetsWithDisabledEvents = new QPtrDict<char>();
+ d->subWidgetsWithDisabledEvents->insert(m_subwidget, (char*)1);
+ m_subwidget->installEventFilter(this);
+ QObjectList *l = m_subwidget->queryList( "QWidget" );
+ for ( QObjectListIt it( *l ); it.current(); ++it ) {
+ d->subWidgetsWithDisabledEvents->insert(it.current(), (char*)1);
+ it.current()->installEventFilter(this);
+ }
+ delete l;
+ }
+ }
+ updateGeometry();
+}
+
+void KexiDBComboBox::setLabelPosition(LabelPosition position)
+{
+ if(m_subwidget) {
+ if (-1 != m_subwidget->metaObject()->findProperty("frameShape", true))
+ m_subwidget->setProperty("frameShape", QVariant((int)QFrame::NoFrame));
+ m_subwidget->setGeometry( editorGeometry() );
+ }
+// KexiSubwidgetInterface *subwidgetInterface = dynamic_cast<KexiSubwidgetInterface*>((QWidget*)m_subwidget);
+ // update size policy
+// if (subwidgetInterface && subwidgetInterface->subwidgetStretchRequired(this)) {
+ QSizePolicy sizePolicy( this->sizePolicy() );
+ if(position == Left)
+ sizePolicy.setHorData( QSizePolicy::Minimum );
+ else
+ sizePolicy.setVerData( QSizePolicy::Minimum );
+ //m_subwidget->setSizePolicy(sizePolicy);
+ setSizePolicy(sizePolicy);
+ //}
+// }
+}
+
+QRect KexiDBComboBox::buttonGeometry() const
+{
+ QRect arrowRect(
+ style().querySubControlMetrics( QStyle::CC_ComboBox, d->paintedCombo, QStyle::SC_ComboBoxArrow) );
+ arrowRect = QStyle::visualRect(arrowRect, d->paintedCombo);
+ arrowRect.setHeight( QMAX( height() - (2 * arrowRect.y()), arrowRect.height() ) ); // a fix for Motif style
+ return arrowRect;
+}
+
+bool KexiDBComboBox::handleMousePressEvent(QMouseEvent *e)
+{
+ if ( e->button() != Qt::LeftButton || d->designMode )
+ return true;
+/*todo if ( m_discardNextMousePress ) {
+ d->discardNextMousePress = FALSE;
+ return;
+ }*/
+
+ if ( /*count() &&*/ ( !isEditable() || buttonGeometry().contains( e->pos() ) ) ) {
+ d->buttonPressed = false;
+
+/* if ( d->usingListBox() ) {
+ listBox()->blockSignals( TRUE );
+ qApp->sendEvent( listBox(), e ); // trigger the listbox's autoscroll
+ listBox()->setCurrentItem(d->current);
+ listBox()->blockSignals( FALSE );
+ popup();
+ if ( arrowRect.contains( e->pos() ) ) {
+ d->arrowPressed = TRUE;
+ d->arrowDown = TRUE;
+ repaint( FALSE );
+ }
+ } else {*/
+ showPopup();
+ return true;
+ }
+ return false;
+}
+
+bool KexiDBComboBox::handleKeyPressEvent(QKeyEvent *ke)
+{
+ const int k = ke->key();
+ const bool dropDown = (ke->state() == Qt::NoButton && ((k==Qt::Key_F2 && !d->isEditable) || k==Qt::Key_F4))
+ || (ke->state() == Qt::AltButton && k==Qt::Key_Down);
+ const bool escPressed = ke->state() == Qt::NoButton && k==Qt::Key_Escape;
+ const bool popupVisible = popup() && popup()->isVisible();
+ if ((dropDown || escPressed) && popupVisible) {
+ popup()->hide();
+ return true;
+ }
+ else if (dropDown && !popupVisible) {
+ d->buttonPressed = false;
+ showPopup();
+ return true;
+ }
+ else if (popupVisible) {
+ const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return;
+ if (enterPressed/* && m_internalEditorValueChanged*/) {
+ acceptPopupSelection();
+ return true;
+ }
+ return handleKeyPressForPopup( ke );
+ }
+
+ return false;
+}
+
+bool KexiDBComboBox::keyPressed(QKeyEvent *ke)
+{
+ if (KexiDBAutoField::keyPressed(ke))
+ return true;
+
+ const int k = ke->key();
+ const bool popupVisible = popup() && popup()->isVisible();
+ const bool escPressed = ke->state() == Qt::NoButton && k==Qt::Key_Escape;
+ if (escPressed && popupVisible) {
+ popup()->hide();
+ return true;
+ }
+ if (ke->state() == Qt::NoButton && (k==Qt::Key_PageDown || k==Qt::Key_PageUp) && popupVisible)
+ return true;
+ return false;
+}
+
+void KexiDBComboBox::mousePressEvent( QMouseEvent *e )
+{
+ if (handleMousePressEvent(e))
+ return;
+
+// QTimer::singleShot( 200, this, SLOT(internalClickTimeout()));
+// d->shortClick = TRUE;
+// }
+ KexiDBAutoField::mousePressEvent( e );
+}
+
+void KexiDBComboBox::mouseDoubleClickEvent( QMouseEvent *e )
+{
+ mousePressEvent( e );
+}
+
+bool KexiDBComboBox::eventFilter( QObject *o, QEvent *e )
+{
+ if (o==this) {
+ if (e->type()==QEvent::Resize) {
+ d->paintedCombo->resize(size());
+ if (m_subwidget)
+ m_subwidget->setGeometry( editorGeometry() );
+ }
+ else if (e->type()==QEvent::Enter) {
+ if (!d->isEditable
+ || /*over button if editable combo*/buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() ))
+ {
+ d->mouseOver = true;
+ update();
+ }
+ }
+ else if (e->type()==QEvent::MouseMove) {
+ if (d->isEditable) {
+ const bool overButton = buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() );
+ if (overButton != d->mouseOver) {
+ d->mouseOver = overButton;
+ update();
+ }
+ }
+ }
+ else if (e->type()==QEvent::Leave) {
+ d->mouseOver = false;
+ update();
+ }
+ else if (e->type()==QEvent::KeyPress) {
+ // handle F2/F4
+ if (handleKeyPressEvent(static_cast<QKeyEvent*>(e)))
+ return true;
+ }
+ else if (e->type()==QEvent::FocusOut) {
+ if (popup() && popup()->isVisible()) {
+ popup()->hide();
+ undoChanges();
+ }
+ }
+ }
+ else if (!d->isEditable && d->subWidgetsWithDisabledEvents && d->subWidgetsWithDisabledEvents->find(o)) {
+ if (e->type()==QEvent::MouseButtonPress) {
+ // clicking the subwidget should mean the same as clicking the combo box (i.e. show the popup)
+ if (handleMousePressEvent(static_cast<QMouseEvent*>(e)))
+ return true;
+ }
+ else if (e->type()==QEvent::KeyPress) {
+ if (handleKeyPressEvent(static_cast<QKeyEvent*>(e)))
+ return true;
+ }
+ return e->type()!=QEvent::Paint;
+ }
+ return KexiDBAutoField::eventFilter( o, e );
+}
+
+bool KexiDBComboBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const
+{
+ Q_UNUSED(autoField);
+ return true;
+}
+
+void KexiDBComboBox::setPaletteBackgroundColor( const QColor & color )
+{
+ KexiDBAutoField::setPaletteBackgroundColor(color);
+ QPalette pal(palette());
+ QColorGroup cg(pal.active());
+ pal.setColor(QColorGroup::Base, red);
+ pal.setColor(QColorGroup::Background, red);
+ pal.setActive(cg);
+ QWidget::setPalette(pal);
+ update();
+}
+
+bool KexiDBComboBox::valueChanged()
+{
+ kdDebug() << "KexiDataItemInterface::valueChanged(): " << m_origValue.toString() << " ? " << value().toString() << endl;
+ return m_origValue != value();
+}
+
+void
+KexiDBComboBox::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
+{
+ KexiFormDataItemInterface::setColumnInfo(cinfo);
+}
+
+void KexiDBComboBox::setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo)
+{
+ d->visibleColumnInfo = cinfo;
+ // we're assuming we already have columnInfo()
+ setColumnInfoInternal(columnInfo(), d->visibleColumnInfo);
+}
+
+KexiDB::QueryColumnInfo* KexiDBComboBox::visibleColumnInfo() const
+{
+ return d->visibleColumnInfo;
+}
+
+void KexiDBComboBox::moveCursorToEndInInternalEditor()
+{
+ if (d->isEditable && m_moveCursorToEndInInternalEditor_enabled)
+ moveCursorToEnd();
+}
+
+void KexiDBComboBox::selectAllInInternalEditor()
+{
+ if (d->isEditable && m_selectAllInInternalEditor_enabled)
+ selectAll();
+}
+
+void KexiDBComboBox::setValueInternal(const QVariant& add, bool removeOld)
+{
+ //// use KexiDBAutoField instead of KexiComboBoxBase::setValueInternal
+ //// expects existing popup(), but we want to have delayed creation
+ if (popup())
+ popup()->hide();
+ KexiComboBoxBase::setValueInternal(add, removeOld);
+}
+
+void KexiDBComboBox::setVisibleValueInternal(const QVariant& value)
+{
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ iface->setValue(value, QVariant(), false /*!removeOld*/);
+}
+
+QVariant KexiDBComboBox::visibleValue()
+{
+ return KexiComboBoxBase::visibleValue();
+}
+
+void KexiDBComboBox::setValueInInternalEditor(const QVariant& value)
+{
+ if (!m_setValueInInternalEditor_enabled)
+ return;
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if(iface)
+ iface->setValue(value, QVariant(), false/*!removeOld*/);
+}
+
+QVariant KexiDBComboBox::valueFromInternalEditor()
+{
+ return KexiDBAutoField::value();
+}
+
+QPoint KexiDBComboBox::mapFromParentToGlobal(const QPoint& pos) const
+{
+// const KexiFormScrollView* view = KexiUtils::findParentConst<const KexiFormScrollView>(this, "KexiFormScrollView");
+ if (!parentWidget())
+ return QPoint(-1,-1);
+ return parentWidget()->mapToGlobal(pos);
+// return view->viewport()->mapToGlobal(pos);
+}
+
+int KexiDBComboBox::popupWidthHint() const
+{
+ return width(); //popup() ? popup()->width() : 0;
+}
+
+void KexiDBComboBox::fontChange( const QFont & oldFont )
+{
+ d->sizeHint = QSize(); //force rebuild the cache
+ KexiDBAutoField::fontChange(oldFont);
+}
+
+void KexiDBComboBox::styleChange( QStyle& oldStyle )
+{
+ KexiDBAutoField::styleChange( oldStyle );
+ d->sizeHint = QSize(); //force rebuild the cache
+ if (m_subwidget)
+ m_subwidget->setGeometry( editorGeometry() );
+}
+
+QSize KexiDBComboBox::sizeHint() const
+{
+ if ( isVisible() && d->sizeHint.isValid() )
+ return d->sizeHint;
+
+ const int maxWidth = 7 * fontMetrics().width(QChar('x')) + 18;
+ const int maxHeight = QMAX( fontMetrics().lineSpacing(), 14 ) + 2;
+ d->sizeHint = (style().sizeFromContents(QStyle::CT_ComboBox, d->paintedCombo,
+ QSize(maxWidth, maxHeight)).expandedTo(QApplication::globalStrut()));
+
+ return d->sizeHint;
+}
+
+void KexiDBComboBox::editRequested()
+{
+}
+
+void KexiDBComboBox::acceptRequested()
+{
+ signalValueChanged();
+}
+
+void KexiDBComboBox::slotRowAccepted(KexiTableItem *item, int row)
+{
+ d->dataEnteredByHand = false;
+ KexiComboBoxBase::slotRowAccepted(item, row);
+ d->dataEnteredByHand = true;
+}
+
+void KexiDBComboBox::beforeSignalValueChanged()
+{
+ if (d->dataEnteredByHand) {
+ KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
+ if (iface) {
+ slotInternalEditorValueChanged( iface->value() );
+ }
+ }
+}
+
+void KexiDBComboBox::undoChanges()
+{
+ KexiDBAutoField::undoChanges();
+ KexiComboBoxBase::undoChanges();
+}
+
+#include "kexidbcombobox.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbcombobox.h b/kexi/plugins/forms/widgets/kexidbcombobox.h
new file mode 100644
index 000000000..5208d37d3
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbcombobox.h
@@ -0,0 +1,181 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBComboBox_H
+#define KexiDBComboBox_H
+
+#include "kexidbutils.h"
+#include "kexidbautofield.h"
+#include <widget/tableview/kexicomboboxbase.h>
+
+//! @short Combo box widget for Kexi forms
+/*! This widget is implemented on top of KexiDBAutoField,
+ so as it uses KexiDBAutoField's ability of embedding subwidgets,
+ it can display not only a line edit but also text edit or image box
+ (more can be added in the future).
+ A drop-down button is added to mimic native combo box widget's functionality.
+*/
+class KEXIFORMUTILS_EXPORT KexiDBComboBox :
+ public KexiDBAutoField, public KexiComboBoxBase
+{
+ Q_OBJECT
+ Q_PROPERTY( bool editable READ isEditable WRITE setEditable )
+ //properties from KexiDBAutoField that should not be visible:
+ Q_OVERRIDE(QColor paletteBackgroundColor READ paletteBackgroundColor WRITE setPaletteBackgroundColor DESIGNABLE true RESET unsetPalette)
+ Q_OVERRIDE(QColor foregroundLabelColor DESIGNABLE false)
+ Q_OVERRIDE(QColor backgroundLabelColor DESIGNABLE false)
+ Q_OVERRIDE(bool autoCaption DESIGNABLE false)
+
+ public:
+ KexiDBComboBox(QWidget *parent, const char *name=0, bool designMode = true);
+ virtual ~KexiDBComboBox();
+
+ //! Implemented for KexiComboBoxBase: form has no 'related data' model (only the full database model)
+ virtual KexiTableViewColumn *column() const { return 0; }
+
+ //! Implemented for KexiComboBoxBase
+ virtual KexiDB::Field *field() const { return KexiDBAutoField::field(); }
+
+ //! Implemented for KexiComboBoxBase
+ virtual QVariant origValue() const { return m_origValue; }
+
+ void setEditable(bool set);
+ bool isEditable() const;
+
+ virtual void setLabelPosition(LabelPosition position);
+
+ virtual QVariant value() { return KexiComboBoxBase::value(); }
+
+ virtual QVariant visibleValue();
+
+ //! Reimpemented because to avoid taking value from the internal editor (index is taken from the popup instead)
+ virtual bool valueChanged();
+
+ virtual QSize sizeHint() const;
+
+ //! Reimplemented after KexiDBAutoField: jsut sets \a cinfo without initializing a subwidget.
+ //! Initialization is performed by \ref setVisibleColumnInfo().
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo);
+
+ /*! Used internally to set visible database column information.
+ Reimplemented: performs initialization of the subwidget. */
+ virtual void setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo);
+
+ /*! \return visible database column information for this item.
+ Reimplemented. */
+ virtual KexiDB::QueryColumnInfo* visibleColumnInfo() const;
+
+ const QColor & paletteBackgroundColor() const { return KexiDBAutoField::paletteBackgroundColor(); }
+
+ //! Reimplemented to also set 'this' widget's background color, not only subwidget's.
+ virtual void setPaletteBackgroundColor( const QColor & color );
+
+ /*! Undoes changes made to this item - just resets the widget to original value.
+ Reimplemented after KexiFormDataItemInterface to also revert the visible value
+ (i.e. text) to the original state. */
+ virtual void undoChanges();
+
+ public slots:
+ void slotRowAccepted(KexiTableItem *item, int row);
+ void slotItemSelected(KexiTableItem* item) { KexiComboBoxBase::slotItemSelected(item); }
+
+ protected slots:
+ void slotInternalEditorValueChanged(const QVariant& v)
+ { KexiComboBoxBase::slotInternalEditorValueChanged(v); }
+
+ protected:
+ QRect buttonGeometry() const;
+
+ virtual void paintEvent( QPaintEvent * );
+
+ virtual void mousePressEvent( QMouseEvent *e );
+
+ void mouseDoubleClickEvent( QMouseEvent *e );
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ //! \return internal editor's geometry
+ QRect editorGeometry() const;
+
+ //! Creates editor. Reimplemented, because if the combo box is not editable,
+ //! editor should not be created.
+ virtual void createEditor();
+
+ /*! Reimplemented */
+ virtual void styleChange( QStyle& oldStyle );
+
+ /*! Reimplemented */
+ virtual void fontChange( const QFont & oldFont );
+
+ virtual bool subwidgetStretchRequired(KexiDBAutoField* autoField) const;
+
+ //! Implemented for KexiComboBoxBase
+ virtual QWidget *internalEditor() const { return /*WidgetWithSubpropertiesInterface*/m_subwidget; }
+
+ //! Implemented for KexiComboBoxBase. Does nothing if the widget is not editable.
+ virtual void moveCursorToEndInInternalEditor();
+
+ //! Implemented for KexiComboBoxBase. Does nothing if the widget is not editable.
+ virtual void selectAllInInternalEditor();
+
+ //! Implemented for KexiComboBoxBase
+ virtual void setValueInInternalEditor(const QVariant& value);
+
+ //! Implemented for KexiComboBoxBase
+ virtual QVariant valueFromInternalEditor();
+
+ //! Implemented for KexiComboBoxBase
+ virtual void editRequested();
+
+ //! Implemented for KexiComboBoxBase
+ virtual void acceptRequested();
+
+ //! Implement this to return a position \a pos mapped from parent (e.g. viewport)
+ //! to global coordinates. QPoint(-1, -1) should be returned if this cannot be computed.
+ virtual QPoint mapFromParentToGlobal(const QPoint& pos) const;
+
+ //! Implement this to return a hint for popup width.
+ virtual int popupWidthHint() const;
+
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ //! Implemented to handle visible value instead of index
+ virtual void setVisibleValueInternal(const QVariant& value);
+
+ bool handleMousePressEvent(QMouseEvent *e);
+
+ bool handleKeyPressEvent(QKeyEvent *ke);
+
+ //! Implemented for KexiDataItemInterface
+ virtual void beforeSignalValueChanged();
+
+ virtual KexiComboBoxPopup *popup() const;
+ virtual void setPopup(KexiComboBoxPopup *popup);
+
+ /*! Called by top-level form on key press event.
+ Used for Key_Escape to if the popup is visible,
+ so the key press won't be consumed to perform "cancel editing".
+ Also used for grabbing page down/up keys. */
+ virtual bool keyPressed(QKeyEvent *ke);
+
+ class Private;
+ Private * const d;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbdateedit.cpp b/kexi/plugins/forms/widgets/kexidbdateedit.cpp
new file mode 100644
index 000000000..32584fceb
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbdateedit.cpp
@@ -0,0 +1,230 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbdateedit.h"
+#include <qlayout.h>
+#include <qtoolbutton.h>
+#include <kpopupmenu.h>
+#include <kdatepicker.h>
+#include <kdatetbl.h>
+
+#include <kexiutils/utils.h>
+#include <kexidb/queryschema.h>
+
+KexiDBDateEdit::KexiDBDateEdit(const QDate &date, QWidget *parent, const char *name)
+ : QWidget(parent, name), KexiFormDataItemInterface()
+{
+ m_invalidState = false;
+ m_cleared = false;
+ m_readOnly = false;
+
+ m_edit = new QDateEdit(date, this);
+ m_edit->setAutoAdvance(true);
+ m_edit->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
+ connect( m_edit, SIGNAL(valueChanged(const QDate&)), this, SLOT(slotValueChanged(const QDate&)) );
+ connect( m_edit, SIGNAL(valueChanged(const QDate&)), this, SIGNAL(dateChanged(const QDate&)) );
+
+ QToolButton* btn = new QToolButton(this);
+ btn->setText("...");
+ btn->setFixedWidth( QFontMetrics(btn->font()).width(" ... ") );
+ btn->setPopupDelay(1); //1 ms
+
+#ifdef QDateTimeEditor_HACK
+ m_dte_date = KexiUtils::findFirstChild<QDateTimeEditor>(m_edit, "QDateTimeEditor");
+#else
+ m_dte_date = 0;
+#endif
+
+ m_datePickerPopupMenu = new KPopupMenu(0, "date_popup");
+ connect(m_datePickerPopupMenu, SIGNAL(aboutToShow()), this, SLOT(slotShowDatePicker()));
+ m_datePicker = new KDatePicker(m_datePickerPopupMenu, QDate::currentDate(), 0);
+
+ KDateTable *dt = KexiUtils::findFirstChild<KDateTable>(m_datePicker, "KDateTable");
+ if (dt)
+ connect(dt, SIGNAL(tableClicked()), this, SLOT(acceptDate()));
+ m_datePicker->setCloseButton(true);
+ m_datePicker->installEventFilter(this);
+ m_datePickerPopupMenu->insertItem(m_datePicker);
+ btn->setPopup(m_datePickerPopupMenu);
+
+ QHBoxLayout* layout = new QHBoxLayout(this);
+ layout->addWidget(m_edit, 1);
+ layout->addWidget(btn, 0);
+
+ setFocusProxy(m_edit);
+}
+
+KexiDBDateEdit::~KexiDBDateEdit()
+{
+}
+
+void KexiDBDateEdit::setInvalidState( const QString& )
+{
+ setEnabled(false);
+ setReadOnly(true);
+ m_invalidState = true;
+//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ?
+ if (focusPolicy() & TabFocus)
+ setFocusPolicy(QWidget::ClickFocus);
+}
+
+void
+KexiDBDateEdit::setEnabled(bool enabled)
+{
+ // prevent the user from reenabling the widget when it is in invalid state
+ if(enabled && m_invalidState)
+ return;
+ QWidget::setEnabled(enabled);
+}
+
+void KexiDBDateEdit::setValueInternal(const QVariant &add, bool removeOld)
+{
+ int setNumberOnFocus = -1;
+ QDate d;
+ QString addString(add.toString());
+ if (removeOld) {
+ if (!addString.isEmpty() && addString[0].latin1()>='0' && addString[0].latin1() <='9') {
+ setNumberOnFocus = addString[0].latin1()-'0';
+ d = QDate(setNumberOnFocus*1000, 1, 1);
+ }
+ }
+ else
+ d = m_origValue.toDate();
+
+ m_edit->setDate(d);
+}
+
+QVariant
+KexiDBDateEdit::value()
+{
+ return QVariant(m_edit->date());
+}
+
+bool KexiDBDateEdit::valueIsNull()
+{
+ return !m_edit->date().isValid() || m_edit->date().isNull();
+}
+
+bool KexiDBDateEdit::valueIsEmpty()
+{
+ return m_cleared;
+}
+
+bool KexiDBDateEdit::isReadOnly() const
+{
+ //! @todo: data/time edit API has no readonly flag,
+ //! so use event filter to avoid changes made by keyboard or mouse when m_readOnly==true
+ return m_readOnly; //!isEnabled();
+}
+
+void KexiDBDateEdit::setReadOnly(bool set)
+{
+ m_readOnly = set;
+}
+
+QWidget*
+KexiDBDateEdit::widget()
+{
+ return this;
+}
+
+bool KexiDBDateEdit::cursorAtStart()
+{
+#ifdef QDateTimeEditor_HACK
+ return m_dte_date && m_edit->hasFocus() && m_dte_date->focusSection()==0;
+#else
+ return false;
+#endif
+}
+
+bool KexiDBDateEdit::cursorAtEnd()
+{
+#ifdef QDateTimeEditor_HACK
+ return m_dte_date && m_edit->hasFocus()
+ && m_dte_date->focusSection()==int(m_dte_date->sectionCount()-1);
+#else
+ return false;
+#endif
+}
+
+void KexiDBDateEdit::clear()
+{
+ m_edit->setDate(QDate());
+ m_cleared = true;
+}
+
+void
+KexiDBDateEdit::slotValueChanged(const QDate&)
+{
+ m_cleared = false;
+}
+
+void
+KexiDBDateEdit::slotShowDatePicker()
+{
+ QDate date = m_edit->date();
+
+ m_datePicker->setDate(date);
+ m_datePicker->setFocus();
+ m_datePicker->show();
+ m_datePicker->setFocus();
+}
+
+void
+KexiDBDateEdit::acceptDate()
+{
+ m_edit->setDate(m_datePicker->date());
+ m_datePickerPopupMenu->hide();
+}
+
+bool
+KexiDBDateEdit::eventFilter(QObject *o, QEvent *e)
+{
+ if (o != m_datePicker)
+ return false;
+
+ switch (e->type()) {
+ case QEvent::Hide:
+ m_datePickerPopupMenu->hide();
+ break;
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease: {
+ QKeyEvent *ke = (QKeyEvent *)e;
+ if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) {
+ //accepting picker
+ acceptDate();
+ return true;
+ }
+ else if (ke->key()==Qt::Key_Escape) {
+ //canceling picker
+ m_datePickerPopupMenu->hide();
+ return true;
+ }
+ else
+ m_datePickerPopupMenu->setFocus();
+ break;
+ }
+ default:
+ break;
+ }
+ return false;
+}
+
+#include "kexidbdateedit.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbdateedit.h b/kexi/plugins/forms/widgets/kexidbdateedit.h
new file mode 100644
index 000000000..2ad693a80
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbdateedit.h
@@ -0,0 +1,118 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBDateEdit_H
+#define KexiDBDateEdit_H
+
+#include "kexiformdataiteminterface.h"
+#include <qdatetimeedit.h>
+
+class KPopupMenu;
+class KDatePicker;
+class QDateTimeEditor;
+
+//! @short A db-aware date editor
+class KEXIFORMUTILS_EXPORT KexiDBDateEdit : public QWidget, public KexiFormDataItemInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ // properties copied from QDateEdit
+ Q_ENUMS( Order )
+ Q_PROPERTY( Order order READ order WRITE setOrder DESIGNABLE true)
+ Q_PROPERTY( QDate date READ date WRITE setDate DESIGNABLE true)
+ Q_PROPERTY( bool autoAdvance READ autoAdvance WRITE setAutoAdvance DESIGNABLE true)
+ Q_PROPERTY( QDate maxValue READ maxValue WRITE setMaxValue DESIGNABLE true)
+ Q_PROPERTY( QDate minValue READ minValue WRITE setMinValue DESIGNABLE true)
+ Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true )
+
+ public:
+ enum Order { DMY = QDateEdit::DMY, MDY = QDateEdit::MDY, YMD = QDateEdit::YMD, YDM = QDateEdit::YDM };
+
+ KexiDBDateEdit(const QDate &date, QWidget *parent, const char *name=0);
+ virtual ~KexiDBDateEdit();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual QVariant value();
+ virtual void setInvalidState( const QString& displayText );
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsEmpty();
+
+ /*! \return 'readOnly' flag for this widget. */
+ virtual bool isReadOnly() const;
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ virtual void setEnabled(bool enabled);
+
+ // property functions
+ inline QDate date() const { return m_edit->date(); }
+ inline void setOrder(Order order) { m_edit->setOrder( (QDateEdit::Order) order); }
+ inline Order order() const { return (Order)m_edit->order(); }
+ inline void setAutoAdvance( bool advance ) { m_edit->setAutoAdvance(advance); }
+ inline bool autoAdvance() const { return m_edit->autoAdvance(); }
+ inline void setMinValue(const QDate& d) { m_edit->setMinValue(d); }
+ inline QDate minValue() const { return m_edit->minValue(); }
+ inline void setMaxValue(const QDate& d) { m_edit->setMaxValue(d); }
+ inline QDate maxValue() const { return m_edit->maxValue(); }
+
+ signals:
+ void dateChanged(const QDate &date);
+
+ public slots:
+ inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); }
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ inline void setDate(const QDate& date) { m_edit->setDate(date); }
+ virtual void setReadOnly(bool set);
+
+ protected slots:
+ void slotValueChanged(const QDate&);
+ void slotShowDatePicker();
+ void acceptDate();
+
+ protected:
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+ virtual bool eventFilter(QObject *o, QEvent *e);
+
+ private:
+ KDatePicker *m_datePicker;
+ QDateEdit *m_edit;
+ KPopupMenu *m_datePickerPopupMenu;
+ QDateTimeEditor *m_dte_date;
+ bool m_invalidState : 1;
+ bool m_cleared : 1;
+ bool m_readOnly : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbdatetimeedit.cpp b/kexi/plugins/forms/widgets/kexidbdatetimeedit.cpp
new file mode 100644
index 000000000..faaeca662
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbdatetimeedit.cpp
@@ -0,0 +1,243 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbdatetimeedit.h"
+
+#include <qtoolbutton.h>
+#include <qlayout.h>
+#include <kpopupmenu.h>
+#include <kdatepicker.h>
+#include <kdatetbl.h>
+#include <kexiutils/utils.h>
+
+KexiDBDateTimeEdit::KexiDBDateTimeEdit(const QDateTime &datetime, QWidget *parent, const char *name)
+ : QWidget(parent, name), KexiFormDataItemInterface()
+{
+ m_invalidState = false;
+ m_cleared = false;
+ m_readOnly = false;
+
+ m_dateEdit = new QDateEdit(datetime.date(), this);
+ m_dateEdit->setAutoAdvance(true);
+ m_dateEdit->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
+// m_dateEdit->setFixedWidth( QFontMetrics(m_dateEdit->font()).width("8888-88-88___") );
+ connect(m_dateEdit, SIGNAL(valueChanged(const QDate&)), this, SLOT(slotValueChanged()));
+ connect(m_dateEdit, SIGNAL(valueChanged(const QDate&)), this, SIGNAL(dateTimeChanged()));
+
+ QToolButton* btn = new QToolButton(this);
+ btn->setText("...");
+ btn->setFixedWidth( QFontMetrics(btn->font()).width(" ... ") );
+ btn->setPopupDelay(1); //1 ms
+
+ m_timeEdit = new QTimeEdit(datetime.time(), this);;
+ m_timeEdit->setAutoAdvance(true);
+ m_timeEdit->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
+ connect(m_timeEdit, SIGNAL(valueChanged(const QTime&)), this, SLOT(slotValueChanged()));
+ connect(m_timeEdit, SIGNAL(valueChanged(const QTime&)), this, SIGNAL(dateTimeChanged()));
+
+#ifdef QDateTimeEditor_HACK
+ m_dte_date = KexiUtils::findFirstChild<QDateTimeEditor>(m_dateEdit, "QDateTimeEditor");
+ m_dte_time = KexiUtils::findFirstChild<QDateTimeEditor>(m_timeEdit, "QDateTimeEditor");
+#else
+ m_dte_date = 0;
+#endif
+
+ m_datePickerPopupMenu = new KPopupMenu(0, "date_popup");
+ connect(m_datePickerPopupMenu, SIGNAL(aboutToShow()), this, SLOT(slotShowDatePicker()));
+ m_datePicker = new KDatePicker(m_datePickerPopupMenu, QDate::currentDate(), 0);
+
+ KDateTable *dt = KexiUtils::findFirstChild<KDateTable>(m_datePicker, "KDateTable");
+ if (dt)
+ connect(dt, SIGNAL(tableClicked()), this, SLOT(acceptDate()));
+ m_datePicker->setCloseButton(true);
+ m_datePicker->installEventFilter(this);
+ m_datePickerPopupMenu->insertItem(m_datePicker);
+ btn->setPopup(m_datePickerPopupMenu);
+
+ QHBoxLayout* layout = new QHBoxLayout(this);
+ layout->addWidget(m_dateEdit, 0);
+ layout->addWidget(btn, 0);
+ layout->addWidget(m_timeEdit, 0);
+ //layout->addStretch(1);
+
+ setFocusProxy(m_dateEdit);
+}
+
+KexiDBDateTimeEdit::~KexiDBDateTimeEdit()
+{
+}
+
+void KexiDBDateTimeEdit::setInvalidState(const QString & /*! @todo paint this text: text*/)
+{
+ setEnabled(false);
+ setReadOnly(true);
+ m_invalidState = true;
+//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ?
+ if (focusPolicy() & TabFocus)
+ setFocusPolicy(QWidget::ClickFocus);
+}
+
+void
+KexiDBDateTimeEdit::setEnabled(bool enabled)
+{
+ // prevent the user from reenabling the widget when it is in invalid state
+ if(enabled && m_invalidState)
+ return;
+ QWidget::setEnabled(enabled);
+}
+
+void KexiDBDateTimeEdit::setValueInternal(const QVariant &, bool )
+{
+ m_dateEdit->setDate(m_origValue.toDate());
+ m_timeEdit->setTime(m_origValue.toTime());
+}
+
+QVariant
+KexiDBDateTimeEdit::value()
+{
+ return QDateTime(m_dateEdit->date(), m_timeEdit->time());
+}
+
+bool KexiDBDateTimeEdit::valueIsNull()
+{
+ return !m_dateEdit->date().isValid() || m_dateEdit->date().isNull()
+ || !m_timeEdit->time().isValid() || m_timeEdit->time().isNull();
+}
+
+bool KexiDBDateTimeEdit::valueIsEmpty()
+{
+ return m_cleared;
+}
+
+bool KexiDBDateTimeEdit::isReadOnly() const
+{
+ //! @todo: data/time edit API has no readonly flag,
+ //! so use event filter to avoid changes made by keyboard or mouse when m_readOnly==true
+ return m_readOnly; //!isEnabled();
+}
+
+void KexiDBDateTimeEdit::setReadOnly(bool set)
+{
+ m_readOnly = set;
+}
+
+QWidget*
+KexiDBDateTimeEdit::widget()
+{
+ return m_dateEdit;
+}
+
+bool KexiDBDateTimeEdit::cursorAtStart()
+{
+#ifdef QDateTimeEditor_HACK
+ return m_dte_date && m_dateEdit->hasFocus() && m_dte_date->focusSection()==0;
+#else
+ return false;
+#endif
+}
+
+bool KexiDBDateTimeEdit::cursorAtEnd()
+{
+#ifdef QDateTimeEditor_HACK
+ return m_dte_time && m_timeEdit->hasFocus()
+ && m_dte_time->focusSection()==int(m_dte_time->sectionCount()-1);
+#else
+ return false;
+#endif
+}
+
+void KexiDBDateTimeEdit::clear()
+{
+ m_dateEdit->setDate(QDate());
+ m_timeEdit->setTime(QTime());
+ m_cleared = true;
+}
+
+void
+KexiDBDateTimeEdit::slotValueChanged()
+{
+ m_cleared = false;
+}
+
+void
+KexiDBDateTimeEdit::slotShowDatePicker()
+{
+ QDate date = m_dateEdit->date();
+
+ m_datePicker->setDate(date);
+ m_datePicker->setFocus();
+ m_datePicker->show();
+ m_datePicker->setFocus();
+}
+
+void
+KexiDBDateTimeEdit::acceptDate()
+{
+ m_dateEdit->setDate(m_datePicker->date());
+ m_datePickerPopupMenu->hide();
+}
+
+bool
+KexiDBDateTimeEdit::eventFilter(QObject *o, QEvent *e)
+{
+ if (o != m_datePicker)
+ return false;
+
+ switch (e->type()) {
+ case QEvent::Hide:
+ m_datePickerPopupMenu->hide();
+ break;
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease: {
+ QKeyEvent *ke = (QKeyEvent *)e;
+ if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) {
+ //accepting picker
+ acceptDate();
+ return true;
+ }
+ else if (ke->key()==Qt::Key_Escape) {
+ //canceling picker
+ m_datePickerPopupMenu->hide();
+ return true;
+ }
+ else
+ m_datePickerPopupMenu->setFocus();
+ break;
+ }
+ default:
+ break;
+ }
+ return false;
+}
+
+QDateTime
+KexiDBDateTimeEdit::dateTime() const
+{
+ return QDateTime(m_dateEdit->date(), m_timeEdit->time());
+}
+
+void
+KexiDBDateTimeEdit::setDateTime(const QDateTime &dt)
+{
+ m_dateEdit->setDate(dt.date());
+ m_timeEdit->setTime(dt.time());
+}
+
+#include "kexidbdatetimeedit.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbdatetimeedit.h b/kexi/plugins/forms/widgets/kexidbdatetimeedit.h
new file mode 100644
index 000000000..1f185b164
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbdatetimeedit.h
@@ -0,0 +1,106 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBDateTimeEdit_H
+#define KexiDBDateTimeEdit_H
+
+#include "kexiformdataiteminterface.h"
+#include <qdatetimeedit.h>
+
+class KDatePicker;
+class QDateTimeEditor;
+class KPopupMenu;
+
+//! @short A db-aware datetime editor
+class KEXIFORMUTILS_EXPORT KexiDBDateTimeEdit : public QWidget, public KexiFormDataItemInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ // properties copied from QDateTimeEdit
+ Q_PROPERTY( QDateTime dateTime READ dateTime WRITE setDateTime )
+ Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true )
+
+ public:
+ enum Order { DMY, MDY, YMD, YDM };
+
+ KexiDBDateTimeEdit(const QDateTime &datetime, QWidget *parent, const char *name=0);
+ virtual ~KexiDBDateTimeEdit();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual QVariant value();
+ virtual void setInvalidState( const QString& displayText );
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsEmpty();
+
+ /*! \return 'readOnly' flag for this widget. */
+ virtual bool isReadOnly() const;
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ virtual void setEnabled(bool enabled);
+
+ // property functions
+ QDateTime dateTime() const;
+
+ signals:
+ void dateTimeChanged();
+
+ public slots:
+ inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); }
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ void setDateTime(const QDateTime &dt);
+ virtual void setReadOnly(bool set);
+
+ protected:
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+ virtual bool eventFilter(QObject *o, QEvent *e);
+
+ protected slots:
+ void slotValueChanged();
+ void slotShowDatePicker();
+ void acceptDate();
+
+ private:
+ KDatePicker *m_datePicker;
+ QDateEdit* m_dateEdit;
+ QTimeEdit* m_timeEdit;
+ QDateTimeEditor *m_dte_date, *m_dte_time;
+ KPopupMenu *m_datePickerPopupMenu;
+ bool m_invalidState : 1;
+ bool m_cleared : 1;
+ bool m_readOnly : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbdoublespinbox.cpp b/kexi/plugins/forms/widgets/kexidbdoublespinbox.cpp
new file mode 100644
index 000000000..67a2c1a6d
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbdoublespinbox.cpp
@@ -0,0 +1,113 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbdoublespinbox.h"
+
+#include <qlineedit.h>
+
+KexiDBDoubleSpinBox::KexiDBDoubleSpinBox(QWidget *parent, const char *name)
+ : KDoubleSpinBox(parent, name) , KexiFormDataItemInterface()
+{
+ connect(this, SIGNAL(valueChanged(double)), this, SLOT(slotValueChanged()));
+}
+
+KexiDBDoubleSpinBox::~KexiDBDoubleSpinBox()
+{
+}
+
+void KexiDBDoubleSpinBox::setInvalidState( const QString& displayText )
+{
+ m_invalidState = true;
+ setEnabled(false);
+ setReadOnly(true);
+//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ?
+ if (focusPolicy() & TabFocus)
+ setFocusPolicy(QWidget::ClickFocus);
+ setSpecialValueText(displayText);
+ KDoubleSpinBox::setValue(minValue());
+}
+
+void
+KexiDBDoubleSpinBox::setEnabled(bool enabled)
+{
+ // prevent the user from reenabling the widget when it is in invalid state
+ if(enabled && m_invalidState)
+ return;
+ KDoubleSpinBox::setEnabled(enabled);
+}
+
+void KexiDBDoubleSpinBox::setValueInternal(const QVariant&, bool )
+{
+ KDoubleSpinBox::setValue(m_origValue.toDouble());
+}
+
+QVariant
+KexiDBDoubleSpinBox::value()
+{
+ return KDoubleSpinBox::value();
+}
+
+void KexiDBDoubleSpinBox::slotValueChanged()
+{
+ signalValueChanged();
+}
+
+bool KexiDBDoubleSpinBox::valueIsNull()
+{
+ return cleanText().isEmpty();
+}
+
+bool KexiDBDoubleSpinBox::valueIsEmpty()
+{
+ return false;
+}
+
+bool KexiDBDoubleSpinBox::isReadOnly() const
+{
+ return editor()->isReadOnly();
+}
+
+void KexiDBDoubleSpinBox::setReadOnly(bool set)
+{
+ editor()->setReadOnly(set);
+}
+
+QWidget*
+KexiDBDoubleSpinBox::widget()
+{
+ return this;
+}
+
+bool KexiDBDoubleSpinBox::cursorAtStart()
+{
+ return false; //! \todo ?
+}
+
+bool KexiDBDoubleSpinBox::cursorAtEnd()
+{
+ return false; //! \todo ?
+}
+
+void KexiDBDoubleSpinBox::clear()
+{
+ KDoubleSpinBox::setValue(minValue());
+}
+
+#include "kexidbdoublespinbox.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbdoublespinbox.h b/kexi/plugins/forms/widgets/kexidbdoublespinbox.h
new file mode 100644
index 000000000..c6bc627d2
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbdoublespinbox.h
@@ -0,0 +1,79 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBDoubleSpinBox_H
+#define KexiDBDoubleSpinBox_H
+
+#include "kexiformdataiteminterface.h"
+#include <qwidget.h>
+#include <knuminput.h>
+
+//! @short A db-aware int spin box
+class KEXIFORMUTILS_EXPORT KexiDBDoubleSpinBox : public KDoubleSpinBox, public KexiFormDataItemInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true )
+
+ public:
+ KexiDBDoubleSpinBox(QWidget *parent, const char *name=0);
+ virtual ~KexiDBDoubleSpinBox();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual QVariant value();
+ virtual void setInvalidState( const QString& displayText );
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsEmpty();
+
+ /*! \return 'readOnly' flag for this widget. */
+ virtual bool isReadOnly() const;
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ public slots:
+ virtual void setEnabled(bool enabled);
+ inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); }
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ void slotValueChanged();
+ virtual void setReadOnly(bool set);
+
+ protected:
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ private:
+ bool m_invalidState : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbform.cpp b/kexi/plugins/forms/widgets/kexidbform.cpp
new file mode 100644
index 000000000..cff12c7cf
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbform.cpp
@@ -0,0 +1,714 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qobjectlist.h>
+#include <qpainter.h>
+#include <qcursor.h>
+#include <qapplication.h>
+#include <qfocusdata.h>
+
+#include <kdebug.h>
+
+#include "kexidbform.h"
+#include "kexiformpart.h"
+#include "kexiformscrollview.h"
+
+#include <formeditor/objecttree.h>
+#include <formeditor/formmanager.h>
+#include <formeditor/widgetlibrary.h>
+#include <widget/tableview/kexidataawareobjectiface.h>
+#include <widget/kexiscrollview.h>
+#include <kexiutils/utils.h>
+
+//! @internal
+class KexiDBForm::Private
+{
+ public:
+ Private()
+ : dataAwareObject(0)
+ , orderedFocusWidgetsIterator(orderedFocusWidgets)
+ , autoTabStops(false)
+ , popupFocused(false)
+ {
+ }
+
+ ~Private()
+ {
+ }
+
+ //! \return index of data-aware widget \a widget
+ int indexOfDataAwareWidget(QWidget *widget) const
+ {
+ if (!dynamic_cast<KexiDataItemInterface*>(widget))
+ return -1;
+ return indexOfDataItem( dynamic_cast<KexiDataItemInterface*>(widget) );
+ }
+
+ //! \return index of data item \a item, or -1 if not found
+ int indexOfDataItem( KexiDataItemInterface* item ) const
+ {
+ QMapConstIterator<KexiDataItemInterface*, uint> indicesForDataAwareWidgetsIt(
+ indicesForDataAwareWidgets.find(item));
+ if (indicesForDataAwareWidgetsIt == indicesForDataAwareWidgets.constEnd())
+ return -1;
+ kexipluginsdbg << "KexiDBForm: column # for item: "
+ << indicesForDataAwareWidgetsIt.data() << endl;
+ return indicesForDataAwareWidgetsIt.data();
+ }
+
+ //! Sets orderedFocusWidgetsIterator member to a position pointing to \a widget
+ void setOrderedFocusWidgetsIteratorTo( QWidget *widget )
+ {
+ if (orderedFocusWidgetsIterator.current() == widget)
+ return;
+ orderedFocusWidgetsIterator.toFirst();
+ while (orderedFocusWidgetsIterator.current() && orderedFocusWidgetsIterator.current()!=widget)
+ ++orderedFocusWidgetsIterator;
+ }
+
+ KexiDataAwareObjectInterface* dataAwareObject;
+ //! ordered list of focusable widgets (can be both data-widgets or buttons, etc.)
+ QPtrList<QWidget> orderedFocusWidgets;
+ //! ordered list of data-aware widgets
+ QPtrList<QWidget> orderedDataAwareWidgets;
+ QMap<KexiDataItemInterface*, uint> indicesForDataAwareWidgets; //!< a subset of orderedFocusWidgets mapped to indices
+ QPtrListIterator<QWidget> orderedFocusWidgetsIterator;
+ QPixmap buffer; //!< stores grabbed entire form's area for redraw
+ QRect prev_rect; //!< previously selected rectangle
+// QGuardedPtr<QWidget> widgetFocusedBeforePopup;
+ bool autoTabStops : 1;
+ bool popupFocused : 1; //!< used in KexiDBForm::eventFilter()
+};
+
+//========================
+
+KexiDBForm::KexiDBForm(QWidget *parent, KexiDataAwareObjectInterface* dataAwareObject,
+ const char *name/*, KexiDB::Connection *conn*/)
+ : KexiDBFormBase(parent, name)
+ , KexiFormDataItemInterface()
+ , d(new Private())
+{
+ installEventFilter(this);
+//test setDisplayMode( KexiGradientWidget::SimpleGradient );
+ editedItem = 0;
+ d->dataAwareObject = dataAwareObject;
+ m_hasFocusableWidget = false;
+
+ kexipluginsdbg << "KexiDBForm::KexiDBForm(): " << endl;
+ setCursor(QCursor(Qt::ArrowCursor)); //to avoid keeping Size cursor when moving from form's boundaries
+ setAcceptDrops( true );
+}
+
+KexiDBForm::~KexiDBForm()
+{
+ kexipluginsdbg << "KexiDBForm::~KexiDBForm(): close" << endl;
+ delete d;
+}
+
+KexiDataAwareObjectInterface* KexiDBForm::dataAwareObject() const { return d->dataAwareObject; }
+
+//repaint all children widgets
+static void repaintAll(QWidget *w)
+{
+ QObjectList *list = w->queryList("QWidget");
+ QObjectListIt it(*list);
+ for (QObject *obj; (obj=it.current()); ++it ) {
+ static_cast<QWidget*>(obj)->repaint();
+ }
+ delete list;
+}
+
+void
+KexiDBForm::drawRect(const QRect& r, int type)
+{
+ QValueList<QRect> l;
+ l.append(r);
+ drawRects(l, type);
+}
+
+void
+KexiDBForm::drawRects(const QValueList<QRect> &list, int type)
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ if (d->prev_rect.isValid()) {
+ //redraw prev. selection's rectangle
+ p.drawPixmap( QPoint(d->prev_rect.x()-2, d->prev_rect.y()-2), d->buffer,
+ QRect(d->prev_rect.x()-2, d->prev_rect.y()-2, d->prev_rect.width()+4, d->prev_rect.height()+4));
+ }
+ p.setBrush(QBrush::NoBrush);
+ if(type == 1) // selection rect
+ p.setPen(QPen(white, 1, Qt::DotLine));
+ else if(type == 2) // insert rect
+ p.setPen(QPen(white, 2));
+ p.setRasterOp(XorROP);
+
+ d->prev_rect = QRect();
+ QValueList<QRect>::ConstIterator endIt = list.constEnd();
+ for(QValueList<QRect>::ConstIterator it = list.constBegin(); it != endIt; ++it) {
+ p.drawRect(*it);
+ if (d->prev_rect.isValid())
+ d->prev_rect = d->prev_rect.unite(*it);
+ else
+ d->prev_rect = *it;
+ }
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+}
+
+void
+KexiDBForm::initBuffer()
+{
+ repaintAll(this);
+ d->buffer.resize( width(), height() );
+ d->buffer = QPixmap::grabWindow( winId() );
+ d->prev_rect = QRect();
+}
+
+void
+KexiDBForm::clearForm()
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ //redraw entire form surface
+ p.drawPixmap( QPoint(0,0), d->buffer, QRect(0,0,d->buffer.width(), d->buffer.height()) );
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+
+ repaintAll(this);
+}
+
+void
+KexiDBForm::highlightWidgets(QWidget *from, QWidget *to)//, const QPoint &point)
+{
+ QPoint fromPoint, toPoint;
+ if(from && from->parentWidget() && (from != this))
+ fromPoint = from->parentWidget()->mapTo(this, from->pos());
+ if(to && to->parentWidget() && (to != this))
+ toPoint = to->parentWidget()->mapTo(this, to->pos());
+
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ if (d->prev_rect.isValid()) {
+ //redraw prev. selection's rectangle
+ p.drawPixmap( QPoint(d->prev_rect.x(), d->prev_rect.y()), d->buffer,
+ QRect(d->prev_rect.x(), d->prev_rect.y(), d->prev_rect.width(), d->prev_rect.height()));
+ }
+
+ p.setPen( QPen(Qt::red, 2) );
+
+ if(to)
+ {
+ QPixmap pix1 = QPixmap::grabWidget(from);
+ QPixmap pix2 = QPixmap::grabWidget(to);
+
+ if((from != this) && (to != this))
+ p.drawLine( from->parentWidget()->mapTo(this, from->geometry().center()), to->parentWidget()->mapTo(this, to->geometry().center()) );
+
+ p.drawPixmap(fromPoint.x(), fromPoint.y(), pix1);
+ p.drawPixmap(toPoint.x(), toPoint.y(), pix2);
+
+ if(to == this)
+ p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4);
+ else
+ p.drawRoundRect(toPoint.x(), toPoint.y(), to->width(), to->height(), 5, 5);
+ }
+
+ if(from == this)
+ p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4);
+ else
+ p.drawRoundRect(fromPoint.x(), fromPoint.y(), from->width(), from->height(), 5, 5);
+
+ if((to == this) || (from == this))
+ d->prev_rect = QRect(0, 0, d->buffer.width(), d->buffer.height());
+ else if(to)
+ {
+ d->prev_rect.setX( (fromPoint.x() < toPoint.x()) ? (fromPoint.x() - 5) : (toPoint.x() - 5) );
+ d->prev_rect.setY( (fromPoint.y() < toPoint.y()) ? (fromPoint.y() - 5) : (toPoint.y() - 5) );
+ d->prev_rect.setRight( (fromPoint.x() < toPoint.x()) ? (toPoint.x() + to->width() + 10) : (fromPoint.x() + from->width() + 10) );
+ d->prev_rect.setBottom( (fromPoint.y() < toPoint.y()) ? (toPoint.y() + to->height() + 10) : (fromPoint.y() + from->height() + 10) ) ;
+ }
+ else
+ d->prev_rect = QRect(fromPoint.x()- 5, fromPoint.y() -5, from->width() + 10, from->height() + 10);
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+}
+
+QSize
+KexiDBForm::sizeHint() const
+{
+ //todo: find better size (user configured?)
+ return QSize(400,300);
+}
+
+void KexiDBForm::setInvalidState( const QString& displayText )
+{
+ Q_UNUSED( displayText );
+
+ //! @todo draw "invalid data source" text on the surface?
+}
+
+bool KexiDBForm::autoTabStops() const
+{
+ return d->autoTabStops;
+}
+
+void KexiDBForm::setAutoTabStops(bool set)
+{
+ d->autoTabStops = set;
+}
+
+QPtrList<QWidget>* KexiDBForm::orderedFocusWidgets() const
+{
+ return &d->orderedFocusWidgets;
+}
+
+QPtrList<QWidget>* KexiDBForm::orderedDataAwareWidgets() const
+{
+ return &d->orderedDataAwareWidgets;
+}
+
+void KexiDBForm::updateTabStopsOrder(KFormDesigner::Form* form)
+{
+ QWidget *fromWidget = 0;
+ //QWidget *topLevelWidget = form->widget()->topLevelWidget();
+//js form->updateTabStopsOrder(); //certain widgets can have now updated focusPolicy properties, fix this
+ uint numberOfDataAwareWidgets = 0;
+// if (d->orderedFocusWidgets.isEmpty()) {
+ //generate a new list
+ for (KFormDesigner::ObjectTreeListIterator it(form->tabStopsIterator()); it.current(); ++it) {
+ if (it.current()->widget()->focusPolicy() & QWidget::TabFocus) {
+ //this widget has tab focus:
+ it.current()->widget()->installEventFilter(this);
+ //also filter events for data-aware children of this widget (i.e. KexiDBAutoField's editors)
+ QObjectList *children = it.current()->widget()->queryList("QWidget");
+ for (QObjectListIt childrenIt(*children); childrenIt.current(); ++childrenIt) {
+ // if (dynamic_cast<KexiFormDataItemInterface*>(childrenIt.current())) {
+ kexipluginsdbg << "KexiDBForm::updateTabStopsOrder(): also adding '"
+ << childrenIt.current()->className() << " " << childrenIt.current()->name()
+ << "' child to filtered widgets" << endl;
+ //it.current()->widget()->installEventFilter(static_cast<QWidget*>(childrenIt.current()));
+ childrenIt.current()->installEventFilter(this);
+ // }
+ }
+ delete children;
+ if (fromWidget) {
+ kexipluginsdbg << "KexiDBForm::updateTabStopsOrder() tab order: " << fromWidget->name()
+ << " -> " << it.current()->widget()->name() << endl;
+ // setTabOrder( fromWidget, it.current()->widget() );
+ }
+ fromWidget = it.current()->widget();
+ d->orderedFocusWidgets.append( it.current()->widget() );
+ }
+
+ KexiFormDataItemInterface* dataItem = dynamic_cast<KexiFormDataItemInterface*>( it.current()->widget() );
+ if (dataItem && !dataItem->dataSource().isEmpty()) {
+ kexipluginsdbg << "#" << numberOfDataAwareWidgets << ": "
+ << dataItem->dataSource() << " (" << it.current()->widget()->name() << ")" << endl;
+
+// /*! @todo d->indicesForDataAwareWidgets SHOULDNT BE UPDATED HERE BECAUSE
+// THERE CAN BE ALSO NON-TABSTOP DATA WIDGETS!
+// */
+ d->indicesForDataAwareWidgets.replace(
+ dataItem,
+ numberOfDataAwareWidgets );
+ numberOfDataAwareWidgets++;
+
+ d->orderedDataAwareWidgets.append( it.current()->widget() );
+ }
+ }//for
+// }
+/* else {
+ //restore ordering
+ for (QPtrListIterator<QWidget> it(d->orderedFocusWidgets); it.current(); ++it) {
+ if (fromWidget) {
+ kdDebug() << "KexiDBForm::updateTabStopsOrder() tab order: " << fromWidget->name()
+ << " -> " << it.current()->name() << endl;
+ setTabOrder( fromWidget, it.current() );
+ }
+ fromWidget = it.current();
+ }
+// SET_FOCUS_USING_REASON(focusWidget(), QFocusEvent::Tab);
+ }*/
+}
+
+void KexiDBForm::updateTabStopsOrder()
+{
+ for (QPtrListIterator<QWidget> it( d->orderedFocusWidgets ); it.current();) {
+ if (! (it.current()->focusPolicy() & QWidget::TabFocus))
+ d->orderedFocusWidgets.remove( it.current() );
+ else
+ ++it;
+ }
+}
+
+void KexiDBForm::updateReadOnlyFlags()
+{
+ for (QPtrListIterator<QWidget> it(d->orderedDataAwareWidgets); it.current(); ++it) {
+ KexiFormDataItemInterface* dataItem = dynamic_cast<KexiFormDataItemInterface*>( it.current() );
+ if (dataItem && !dataItem->dataSource().isEmpty()) {
+ if (dataAwareObject()->isReadOnly()) {
+ dataItem->setReadOnly( true );
+ }
+ }
+ }
+}
+
+bool KexiDBForm::eventFilter( QObject * watched, QEvent * e )
+{
+ //kexipluginsdbg << e->type() << endl;
+ if (e->type()==QEvent::Resize && watched == this)
+ kexipluginsdbg << "RESIZE" << endl;
+ if (e->type()==QEvent::KeyPress) {
+ if (preview()) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ const int key = ke->key();
+ bool tab = ke->state() == Qt::NoButton && key == Qt::Key_Tab;
+ bool backtab = ((ke->state() == Qt::NoButton || ke->state() == Qt::ShiftButton) && key == Qt::Key_Backtab)
+ || (ke->state() == Qt::ShiftButton && key == Qt::Key_Tab);
+ QObject *o = watched; //focusWidget();
+ QWidget* realWidget = dynamic_cast<QWidget*>(o); //will beused below (for tab/backtab handling)
+
+ if (!tab && !backtab) {
+ //for buttons, left/up and right/down keys act like tab/backtab (see qbutton.cpp)
+ if (realWidget->inherits("QButton")) {
+ if (ke->state() == Qt::NoButton && (key == Qt::Key_Right || key == Qt::Key_Down))
+ tab = true;
+ else if (ke->state() == Qt::NoButton && (key == Qt::Key_Left || key == Qt::Key_Up))
+ backtab = true;
+ }
+ }
+
+ if (!tab && !backtab) {
+ // allow the editor widget to grab the key press event
+ while (true) {
+ if (!o || o == dynamic_cast<QObject*>(d->dataAwareObject))
+ break;
+ if (dynamic_cast<KexiFormDataItemInterface*>(o)) {
+ realWidget = dynamic_cast<QWidget*>(o); //will be used below
+ if (realWidget == this) //we have encountered 'this' form surface, give up
+ return false;
+ KexiFormDataItemInterface* dataItemIface = dynamic_cast<KexiFormDataItemInterface*>(o);
+ while (dataItemIface) {
+ if (dataItemIface->keyPressed(ke))
+ return false;
+ dataItemIface = dynamic_cast<KexiFormDataItemInterface*>(dataItemIface->parentInterface()); //try in parent, e.g. in combobox
+ }
+ break;
+ }
+ o = o->parent();
+ }
+ // try to handle global shortcuts at the KexiDataAwareObjectInterface
+ // level (e.g. for "next record" action)
+ int curRow = d->dataAwareObject->currentRow();
+ int curCol = d->dataAwareObject->currentColumn();
+ bool moveToFirstField; //if true, we'll move focus to the first field (in tab order)
+ bool moveToLastField; //if true, we'll move focus to the first field (in tab order)
+ if (! (ke->state() == Qt::NoButton && (key == Qt::Key_Home
+ || key == Qt::Key_End || key == Qt::Key_Down || key == Qt::Key_Up))
+ /* ^^ home/end/down/up are already handled by widgets */
+ && d->dataAwareObject->handleKeyPress(
+ ke, curRow, curCol, false/*!fullRowSelection*/, &moveToFirstField, &moveToLastField))
+ {
+ if (ke->isAccepted())
+ return true;
+ QWidget* widgetToFocus;
+ if (moveToFirstField) {
+ widgetToFocus = d->orderedFocusWidgets.first(); //?
+ curCol = d->indexOfDataAwareWidget( widgetToFocus );
+ }
+ else if (moveToLastField) {
+ widgetToFocus = d->orderedFocusWidgets.last(); //?
+ curCol = d->indexOfDataAwareWidget( widgetToFocus );
+ }
+ else
+ widgetToFocus = d->orderedDataAwareWidgets.at( curCol ); //?
+
+ d->dataAwareObject->setCursorPosition( curRow, curCol );
+
+ if (widgetToFocus)
+ widgetToFocus->setFocus();
+ else
+ kexipluginswarn << "KexiDBForm::eventFilter(): widgetToFocus not found!" << endl;
+
+ ke->accept();
+ return true;
+ }
+ if (key == Qt::Key_Delete && ke->state()==Qt::ControlButton) {
+//! @todo remove hardcoded shortcuts: can be reconfigured...
+ d->dataAwareObject->deleteCurrentRow();
+ return true;
+ }
+ }
+ // handle Esc key
+ if (ke->state() == Qt::NoButton && key == Qt::Key_Escape) {
+ //cancel field editing/row editing if possible
+ if (d->dataAwareObject->cancelEditor())
+ return true;
+ else if (d->dataAwareObject->cancelRowEdit())
+ return true;
+ return false; // canceling not needed - pass the event to the active widget
+ }
+ // jstaniek: Fix for Qt bug (handling e.g. Alt+2, Ctrl+2 keys on every platform)
+ // It's important because we're using alt+2 short cut by default
+ // Damn! I've reported this to Trolltech in November 2004 - still not fixed.
+ if (ke->isAccepted() && (ke->state() & Qt::AltButton) && ke->text()>="0" && ke->text()<="9")
+ return true;
+
+ if (tab || backtab) {
+ //the watched widget can be a subwidget of a real widget, e.g. a drop down button of image box: find it
+ while (!KexiFormPart::library()->widgetInfoForClassName(realWidget->className()))
+ realWidget = realWidget->parentWidget();
+ if (!realWidget)
+ return true; //ignore
+ //the watched widget can be a subwidget of a real widget, e.g. autofield: find it
+ //QWidget* realWidget = static_cast<QWidget*>(watched);
+ while (dynamic_cast<KexiDataItemInterface*>(realWidget) && dynamic_cast<KexiDataItemInterface*>(realWidget)->parentInterface())
+ realWidget = dynamic_cast<QWidget*>( dynamic_cast<KexiDataItemInterface*>(realWidget)->parentInterface() );
+
+ d->setOrderedFocusWidgetsIteratorTo( realWidget );
+ kexipluginsdbg << realWidget->name() << endl;
+
+ // find next/prev widget to focus
+ QWidget *widgetToUnfocus = realWidget;
+ QWidget *widgetToFocus = 0;
+ bool wasAtFirstWidget = false; //used to protect against infinite loop
+ while (true) {
+ if (tab) {
+ if (d->orderedFocusWidgets.first() && realWidget == d->orderedFocusWidgets.last()) {
+ if (wasAtFirstWidget)
+ break;
+ d->orderedFocusWidgetsIterator.toFirst();
+ wasAtFirstWidget = true;
+ }
+ else if (realWidget == d->orderedFocusWidgetsIterator.current()) {
+ ++d->orderedFocusWidgetsIterator; //next
+ }
+ else
+ return true; //ignore
+ }
+ else {//backtab
+ if (d->orderedFocusWidgets.last() && realWidget == d->orderedFocusWidgets.first()) {
+ d->orderedFocusWidgetsIterator.toLast();
+ }
+ else if (realWidget == d->orderedFocusWidgetsIterator.current()) {
+ --d->orderedFocusWidgetsIterator; //prev
+ }
+ else
+ return true; //ignore
+ }
+
+ widgetToFocus = d->orderedFocusWidgetsIterator.current();
+
+ QObject *pageFor_widgetToFocus = 0;
+ KFormDesigner::TabWidget *tabWidgetFor_widgetToFocus
+ = KFormDesigner::findParent<KFormDesigner::TabWidget>(
+ widgetToFocus, "KFormDesigner::TabWidget", pageFor_widgetToFocus);
+ if (tabWidgetFor_widgetToFocus && tabWidgetFor_widgetToFocus->currentPage()!=pageFor_widgetToFocus) {
+ realWidget = widgetToFocus;
+ continue; //the new widget to focus is placed on invisible tab page: move to next widget
+ }
+ break;
+ }//while
+
+ //set focus, but don't use just setFocus() because certain widgets
+ //behaves differently (e.g. QLineEdit calls selectAll()) when
+ //focus event's reason is QFocusEvent::Tab
+ if (widgetToFocus->focusProxy())
+ widgetToFocus = widgetToFocus->focusProxy();
+ if (widgetToFocus && d->dataAwareObject->acceptEditor()) {
+ if (tab) {
+ //try to accept this will validate the current input (if any)
+ KexiUtils::unsetFocusWithReason(widgetToUnfocus, QFocusEvent::Tab);
+ KexiUtils::setFocusWithReason(widgetToFocus, QFocusEvent::Tab);
+ kexipluginsdbg << "focusing " << widgetToFocus->name() << endl;
+ }
+ else {//backtab
+ KexiUtils::unsetFocusWithReason(widgetToUnfocus, QFocusEvent::Backtab);
+ //set focus, see above note
+ KexiUtils::setFocusWithReason(d->orderedFocusWidgetsIterator.current(), QFocusEvent::Backtab);
+ kexipluginsdbg << "focusing " << d->orderedFocusWidgetsIterator.current()->name() << endl;
+ }
+ }
+ return true;
+ }
+ }
+ }
+ else if (e->type()==QEvent::FocusIn) {
+ bool focusDataWidget = preview();
+ if (static_cast<QFocusEvent*>(e)->reason()==QFocusEvent::Popup) {
+ kdDebug() << "->>> focus IN, popup" <<endl;
+ focusDataWidget = !d->popupFocused;
+ d->popupFocused = false;
+// if (d->widgetFocusedBeforePopup) {
+// watched = d->widgetFocusedBeforePopup;
+// d->widgetFocusedBeforePopup = 0;
+// }
+ }
+
+ if (focusDataWidget) {
+ kexipluginsdbg << "KexiDBForm: FocusIn: " << watched->className() << " " << watched->name() << endl;
+ if (d->dataAwareObject) {
+ QWidget *dataItem = dynamic_cast<QWidget*>(watched);
+ while (dataItem) {
+ while (dataItem && !dynamic_cast<KexiDataItemInterface*>(dataItem))
+ dataItem = dataItem->parentWidget();
+ if (!dataItem)
+ break;
+ kexipluginsdbg << "KexiDBForm: FocusIn: FOUND " << dataItem->className() << " " << dataItem->name() << endl;
+
+ const int index = d->indexOfDataAwareWidget(dataItem);
+ if (index>=0) {
+ kexipluginsdbg << "KexiDBForm: moving cursor to column #" << index << endl;
+ editedItem = 0;
+ if ((int)index!=d->dataAwareObject->currentColumn()) {
+ d->dataAwareObject->setCursorPosition( d->dataAwareObject->currentRow(), index /*column*/ );
+ }
+ break;
+ }
+ else
+ dataItem = dataItem->parentWidget();
+
+ dataItem->update();
+ }
+ }
+ }
+ }
+ else if (e->type()==QEvent::FocusOut) {
+ if (static_cast<QFocusEvent*>(e)->reason()==QFocusEvent::Popup) {
+ //d->widgetFocusedBeforePopup = (QWidget*)watched;
+ d->popupFocused = true;
+ }
+ else
+ d->popupFocused = false;
+// d->widgetFocusedBeforePopup = 0;
+// kdDebug() << "e->type()==QEvent::FocusOut " << watched->className() << " " <<watched->name() << endl;
+// UNSET_FOCUS_USING_REASON(watched, static_cast<QFocusEvent*>(e)->reason());
+ }
+ return KexiDBFormBase::eventFilter(watched, e);
+}
+
+bool KexiDBForm::valueIsNull()
+{
+ return true;
+}
+
+bool KexiDBForm::valueIsEmpty()
+{
+ return true;
+}
+
+bool KexiDBForm::isReadOnly() const
+{
+ if (d->dataAwareObject)
+ return d->dataAwareObject->isReadOnly();
+//! @todo ?
+ return false;
+}
+
+void KexiDBForm::setReadOnly( bool readOnly )
+{
+ if (d->dataAwareObject)
+ d->dataAwareObject->setReadOnly( readOnly ); //???
+}
+
+QWidget* KexiDBForm::widget()
+{
+ return this;
+}
+
+bool KexiDBForm::cursorAtStart()
+{
+ return false;
+}
+
+bool KexiDBForm::cursorAtEnd()
+{
+ return false;
+}
+
+void KexiDBForm::clear()
+{
+ //! @todo clear all fields?
+}
+
+bool KexiDBForm::preview() const {
+ return dynamic_cast<KexiScrollView*>(d->dataAwareObject)
+ ? dynamic_cast<KexiScrollView*>(d->dataAwareObject)->preview() : false;
+}
+
+void KexiDBForm::dragMoveEvent( QDragMoveEvent *e )
+{
+ KexiDBFormBase::dragMoveEvent( e );
+ emit handleDragMoveEvent(e);
+}
+
+void KexiDBForm::dropEvent( QDropEvent *e )
+{
+ KexiDBFormBase::dropEvent( e );
+ emit handleDropEvent(e);
+}
+
+void KexiDBForm::setCursor( const QCursor & cursor )
+{
+ //js: empty, to avoid fscking problems with random cursors!
+ //! @todo?
+
+ if (KFormDesigner::FormManager::self()->isInserting()) //exception
+ KexiDBFormBase::setCursor(cursor);
+}
+
+//! @todo: Qt4? XORed resize rectangles instead of black widgets
+/*
+void KexiDBForm::paintEvent( QPaintEvent *e )
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ p.setPen(white);
+ p.setRasterOp(XorROP);
+ p.drawLine(e->rect().topLeft(), e->rect().bottomRight());
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+ KexiDBFormBase::paintEvent(e);
+}
+*/
+
+#include "kexidbform.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbform.h b/kexi/plugins/forms/widgets/kexidbform.h
new file mode 100644
index 000000000..81a71bba4
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbform.h
@@ -0,0 +1,139 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef KEXIDBFORM_H
+#define KEXIDBFORM_H
+
+#include <qpixmap.h>
+
+#include <formeditor/form.h>
+#include "../kexiformdataiteminterface.h"
+
+#ifdef KEXI_USE_GRADIENT_WIDGET
+#include <kexigradientwidget.h>
+# define KexiDBFormBase KexiGradientWidget
+#else
+# define KexiDBFormBase QWidget
+#endif
+
+class KexiDataAwareObjectInterface;
+class KexiFormScrollView;
+
+//! @short A DB-aware form widget, acting as form's toplevel widget
+class KEXIFORMUTILS_EXPORT KexiDBForm :
+ public KexiDBFormBase,
+ public KFormDesigner::FormWidget,
+ public KexiFormDataItemInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ Q_PROPERTY(bool autoTabStops READ autoTabStops WRITE setAutoTabStops DESIGNABLE true)
+ //original "size" property is not designable, so here's a custom (not storable) replacement
+ Q_PROPERTY( QSize sizeInternal READ sizeInternal WRITE resizeInternal DESIGNABLE true STORED false )
+ public:
+ KexiDBForm(QWidget *parent, KexiDataAwareObjectInterface* dataAwareObject, const char *name="kexi_dbform");
+ virtual ~KexiDBForm();
+
+ KexiDataAwareObjectInterface* dataAwareObject() const;
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+
+ //! no effect
+ QVariant value() { return QVariant(); }
+
+ virtual void setInvalidState( const QString& displayText );
+
+ virtual void drawRect(const QRect& r, int type);
+ virtual void drawRects(const QValueList<QRect> &list, int type);
+ virtual void initBuffer();
+ virtual void clearForm();
+ virtual void highlightWidgets(QWidget *from, QWidget *to/*, const QPoint &p*/);
+
+ virtual QSize sizeHint() const;
+
+ bool autoTabStops() const;
+
+ QPtrList<QWidget>* orderedFocusWidgets() const;
+
+ QPtrList<QWidget>* orderedDataAwareWidgets() const;
+
+ void updateTabStopsOrder(KFormDesigner::Form* form);
+
+ void updateTabStopsOrder();
+
+ virtual bool eventFilter ( QObject * watched, QEvent * e );
+
+ virtual bool valueIsNull();
+ virtual bool valueIsEmpty();
+ virtual bool isReadOnly() const;
+ virtual QWidget* widget();
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ bool preview() const;
+
+ virtual void setCursor( const QCursor & cursor );
+
+ public slots:
+ void setAutoTabStops(bool set);
+ inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); }
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+
+ //! This implementation just disables read only widget
+ virtual void setReadOnly( bool readOnly );
+
+ //! @internal for sizeInternal property
+ QSize sizeInternal() const { return KexiDBFormBase::size(); }
+
+ //! @internal for sizeInternal property
+ void resizeInternal(const QSize& s) { KexiDBFormBase::resize(s); }
+
+ signals:
+ void handleDragMoveEvent(QDragMoveEvent *e);
+ void handleDropEvent(QDropEvent *e);
+
+ protected:
+ //! no effect
+ virtual void setValueInternal(const QVariant&, bool) {}
+
+ //! Used to emit handleDragMoveEvent() signal needed to control dragging over the container's surface
+ virtual void dragMoveEvent( QDragMoveEvent *e );
+
+ //! Used to emit handleDropEvent() signal needed to control dropping on the container's surface
+ virtual void dropEvent( QDropEvent *e );
+
+ //! called from KexiFormScrollView::initDataContents()
+ void updateReadOnlyFlags();
+// virtual void paintEvent( QPaintEvent * );
+
+ //! Points to a currently edited data item.
+ //! It is cleared when the focus is moved to other
+ KexiFormDataItemInterface *editedItem;
+
+ class Private;
+ Private *d;
+
+ friend class KexiFormScrollView;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbimagebox.cpp b/kexi/plugins/forms/widgets/kexidbimagebox.cpp
new file mode 100644
index 000000000..82e700869
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbimagebox.cpp
@@ -0,0 +1,870 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbimagebox.h"
+
+#include <qapplication.h>
+#include <qpixmap.h>
+#include <qstyle.h>
+#include <qclipboard.h>
+#include <qtooltip.h>
+#include <qimage.h>
+#include <qbuffer.h>
+#include <qfiledialog.h>
+#include <qpainter.h>
+
+#include <kdebug.h>
+#include <kpopupmenu.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kfiledialog.h>
+#include <kimageio.h>
+#include <kstandarddirs.h>
+#include <kstaticdeleter.h>
+#include <kimageeffect.h>
+#include <kstdaccel.h>
+#include <kmessagebox.h>
+#include <kguiitem.h>
+
+#include <widget/utils/kexidropdownbutton.h>
+#include <widget/utils/kexicontextmenuutils.h>
+#include <kexiutils/utils.h>
+#include <kexidb/field.h>
+#include <kexidb/utils.h>
+#include <kexidb/queryschema.h>
+#include <formeditor/widgetlibrary.h>
+
+#ifdef Q_WS_WIN
+#include <win32_utils.h>
+#include <krecentdirs.h>
+#endif
+
+#include "kexidbutils.h"
+#include "../kexiformpart.h"
+
+static KStaticDeleter<QPixmap> KexiDBImageBox_pmDeleter;
+static QPixmap* KexiDBImageBox_pm = 0;
+static KStaticDeleter<QPixmap> KexiDBImageBox_pmSmallDeleter;
+static QPixmap* KexiDBImageBox_pmSmall = 0;
+
+KexiDBImageBox::KexiDBImageBox( bool designMode, QWidget *parent, const char *name )
+ : KexiFrame( parent, name, Qt::WNoAutoErase )
+ , KexiFormDataItemInterface()
+ , m_alignment(Qt::AlignAuto|Qt::AlignTop)
+ , m_designMode(designMode)
+ , m_readOnly(false)
+ , m_scaledContents(false)
+ , m_keepAspectRatio(true)
+ , m_insideSetData(false)
+ , m_setFocusOnButtonAfterClosingPopup(false)
+ , m_lineWidthChanged(false)
+ , m_paintEventEnabled(true)
+ , m_dropDownButtonVisible(true)
+ , m_insideSetPalette(false)
+{
+ installEventFilter(this);
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ //setup popup menu
+ m_popupMenu = new KexiImageContextMenu(this);
+ m_popupMenu->installEventFilter(this);
+
+ if (m_designMode) {
+ m_chooser = 0;
+ }
+ else {
+ m_chooser = new KexiDropDownButton(this);
+ m_chooser->setFocusPolicy(StrongFocus);
+ m_chooser->setPopup(m_popupMenu);
+ setFocusProxy(m_chooser);
+ m_chooser->installEventFilter(this);
+// m_chooser->setPalette(qApp->palette());
+// hlyr->addWidget(m_chooser);
+ }
+
+ setBackgroundMode(Qt::NoBackground);
+ setFrameShape(QFrame::Box);
+ setFrameShadow(QFrame::Plain);
+ setFrameColor(Qt::black);
+
+ m_paletteBackgroundColorChanged = false; //set this here, not before
+
+ connect(m_popupMenu, SIGNAL(updateActionsAvailabilityRequested(bool&, bool&)),
+ this, SLOT(slotUpdateActionsAvailabilityRequested(bool&, bool&)));
+ connect(m_popupMenu, SIGNAL(insertFromFileRequested(const KURL&)),
+ this, SLOT(handleInsertFromFileAction(const KURL&)));
+ connect(m_popupMenu, SIGNAL(saveAsRequested(const QString&)),
+ this, SLOT(handleSaveAsAction(const QString&)));
+ connect(m_popupMenu, SIGNAL(cutRequested()),
+ this, SLOT(handleCutAction()));
+ connect(m_popupMenu, SIGNAL(copyRequested()),
+ this, SLOT(handleCopyAction()));
+ connect(m_popupMenu, SIGNAL(pasteRequested()),
+ this, SLOT(handlePasteAction()));
+ connect(m_popupMenu, SIGNAL(clearRequested()),
+ this, SLOT(clear()));
+ connect(m_popupMenu, SIGNAL(showPropertiesRequested()),
+ this, SLOT(handleShowPropertiesAction()));
+
+// connect(m_popupMenu, SIGNAL(aboutToHide()), this, SLOT(slotAboutToHidePopupMenu()));
+// if (m_chooser) {
+ //we couldn't use m_chooser->setPopup() because of drawing problems
+// connect(m_chooser, SIGNAL(pressed()), this, SLOT(slotChooserPressed()));
+// connect(m_chooser, SIGNAL(released()), this, SLOT(slotChooserReleased()));
+// connect(m_chooser, SIGNAL(toggled(bool)), this, SLOT(slotToggled(bool)));
+// }
+
+ setDataSource( QString::null ); //to initialize popup menu and actions availability
+}
+
+KexiDBImageBox::~KexiDBImageBox()
+{
+}
+
+KexiImageContextMenu* KexiDBImageBox::contextMenu() const
+{
+ return m_popupMenu;
+}
+
+QVariant KexiDBImageBox::value()
+{
+ if (dataSource().isEmpty()) {
+ //not db-aware
+ return QVariant();
+ }
+ //db-aware mode
+ return m_value; //todo
+ //return QVariant(); //todo
+}
+
+void KexiDBImageBox::setValueInternal( const QVariant& add, bool removeOld, bool loadPixmap )
+{
+ if (isReadOnly())
+ return;
+ m_popupMenu->hide();
+ if (removeOld)
+ m_value = add.toByteArray();
+ else //do not add "m_origValue" to "add" as this is QByteArray
+ m_value = m_origValue.toByteArray();
+ bool ok = !m_value.isEmpty();
+ if (ok) {
+ ///unused (m_valueMimeType is not available unless the px is inserted) QString type( KImageIO::typeForMime(m_valueMimeType) );
+ ///ok = KImageIO::canRead( type );
+ ok = loadPixmap ? m_pixmap.loadFromData(m_value) : true; //, type.latin1());
+ if (!ok) {
+ //! @todo inform about error?
+ }
+ }
+ if (!ok) {
+ m_valueMimeType = QString::null;
+ m_pixmap = QPixmap();
+ }
+ repaint();
+}
+
+void KexiDBImageBox::setInvalidState( const QString& displayText )
+{
+ Q_UNUSED( displayText );
+
+// m_pixmapLabel->setPixmap(QPixmap());
+ if (!dataSource().isEmpty()) {
+ m_value = QByteArray();
+ }
+// m_pixmap = QPixmap();
+// m_originalFileName = QString::null;
+
+//! @todo m_pixmapLabel->setText( displayText );
+
+ if (m_chooser)
+ m_chooser->hide();
+ setReadOnly(true);
+}
+
+bool KexiDBImageBox::valueIsNull()
+{
+ return m_value.isEmpty();
+// return !m_pixmapLabel->pixmap() || m_pixmapLabel->pixmap()->isNull();
+}
+
+bool KexiDBImageBox::valueIsEmpty()
+{
+ return false;
+}
+
+bool KexiDBImageBox::isReadOnly() const
+{
+ return m_readOnly;
+}
+
+void KexiDBImageBox::setReadOnly(bool set)
+{
+ m_readOnly = set;
+}
+
+QPixmap KexiDBImageBox::pixmap() const
+{
+ if (dataSource().isEmpty()) {
+ //not db-aware
+ return m_data.pixmap();
+ }
+ //db-aware mode
+ return m_pixmap;
+}
+
+uint KexiDBImageBox::pixmapId() const
+{
+ if (dataSource().isEmpty()) {// && !m_data.stored()) {
+ //not db-aware
+ return m_data.id();
+ }
+ return 0;
+}
+
+void KexiDBImageBox::setPixmapId(uint id)
+{
+ if (m_insideSetData) //avoid recursion
+ return;
+ setData(KexiBLOBBuffer::self()->objectForId( id, /*unstored*/false ));
+ repaint();
+}
+
+uint KexiDBImageBox::storedPixmapId() const
+{
+ if (dataSource().isEmpty() && m_data.stored()) {
+ //not db-aware
+ return m_data.id();
+ }
+ return 0;
+}
+
+void KexiDBImageBox::setStoredPixmapId(uint id)
+{
+ setData(KexiBLOBBuffer::self()->objectForId( id, /*stored*/true ));
+ repaint();
+}
+
+bool KexiDBImageBox::hasScaledContents() const
+{
+ return m_scaledContents;
+// return m_pixmapLabel->hasScaledContents();
+}
+
+/*void KexiDBImageBox::setPixmap(const QByteArray& pixmap)
+{
+ setValueInternal(pixmap, true);
+// setBackgroundMode(pixmap.isNull() ? Qt::NoBackground : Qt::PaletteBackground);
+}*/
+
+void KexiDBImageBox::setScaledContents(bool set)
+{
+//todo m_pixmapLabel->setScaledContents(set);
+ m_scaledContents = set;
+ repaint();
+}
+
+void KexiDBImageBox::setKeepAspectRatio(bool set)
+{
+ m_keepAspectRatio = set;
+ if (m_scaledContents)
+ repaint();
+}
+
+QWidget* KexiDBImageBox::widget()
+{
+ //! @todo
+// return m_pixmapLabel;
+ return this;
+}
+
+bool KexiDBImageBox::cursorAtStart()
+{
+ return true;
+}
+
+bool KexiDBImageBox::cursorAtEnd()
+{
+ return true;
+}
+
+QByteArray KexiDBImageBox::data() const
+{
+ if (dataSource().isEmpty()) {
+ //static mode
+ return m_data.data();
+ }
+ else {
+ //db-aware mode
+ return m_value;
+ }
+}
+
+void KexiDBImageBox::insertFromFile()
+{
+ m_popupMenu->insertFromFile();
+}
+
+void KexiDBImageBox::handleInsertFromFileAction(const KURL& url)
+{
+ if (!dataSource().isEmpty() && isReadOnly())
+ return;
+
+ if (dataSource().isEmpty()) {
+ //static mode
+ KexiBLOBBuffer::Handle h = KexiBLOBBuffer::self()->insertPixmap( url );
+ if (!h)
+ return;
+ setData(h);
+ repaint();
+ }
+ else {
+ //db-aware
+ QString fileName( url.isLocalFile() ? url.path() : url.prettyURL() );
+
+ //! @todo download the file if remote, then set fileName properly
+ QFile f(fileName);
+ if (!f.open(IO_ReadOnly)) {
+ //! @todo err msg
+ return;
+ }
+ QByteArray ba = f.readAll();
+ if (f.status()!=IO_Ok) {
+ //! @todo err msg
+ f.close();
+ return;
+ }
+ m_valueMimeType = KImageIO::mimeType( fileName );
+ setValueInternal( ba, true );
+ }
+
+//! @todo emit signal for setting "dirty" flag within the design
+ if (!dataSource().isEmpty()) {
+ signalValueChanged();
+ }
+}
+
+void KexiDBImageBox::handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty)
+{
+ if (data().isEmpty()) {
+ kdWarning() << "KexiDBImageBox::handleAboutToSaveAs(): no pixmap!" << endl;
+ dataIsEmpty = false;
+ return;
+ }
+ if (dataSource().isEmpty()) { //for static images filename and mimetype can be available
+ origFilename = m_data.originalFileName();
+ if (!origFilename.isEmpty())
+ origFilename = QString("/") + origFilename;
+ if (!m_data.mimeType().isEmpty())
+ fileExtension = KImageIO::typeForMime(m_data.mimeType()).lower();
+ }
+}
+
+void KexiDBImageBox::handleSaveAsAction(const QString& fileName)
+{
+ QFile f(fileName);
+ if (!f.open(IO_WriteOnly)) {
+ //! @todo err msg
+ return;
+ }
+ f.writeBlock( data() );
+ if (f.status()!=IO_Ok) {
+ //! @todo err msg
+ f.close();
+ return;
+ }
+ f.close();
+}
+
+void KexiDBImageBox::handleCutAction()
+{
+ if (!dataSource().isEmpty() && isReadOnly())
+ return;
+ handleCopyAction();
+ clear();
+}
+
+void KexiDBImageBox::handleCopyAction()
+{
+ qApp->clipboard()->setPixmap(pixmap(), QClipboard::Clipboard);
+}
+
+void KexiDBImageBox::handlePasteAction()
+{
+ if (isReadOnly() || (!m_designMode && !hasFocus()))
+ return;
+ QPixmap pm( qApp->clipboard()->pixmap(QClipboard::Clipboard) );
+// if (!pm.isNull())
+// setValueInternal(pm, true);
+ if (dataSource().isEmpty()) {
+ //static mode
+ setData(KexiBLOBBuffer::self()->insertPixmap( pm ));
+ }
+ else {
+ //db-aware mode
+ m_pixmap = pm;
+ QByteArray ba;
+ QBuffer buffer( ba );
+ buffer.open( IO_WriteOnly );
+ if (m_pixmap.save( &buffer, "PNG" )) {// write pixmap into ba in PNG format
+ setValueInternal( ba, true, false/* !loadPixmap */ );
+ }
+ else {
+ setValueInternal( QByteArray(), true );
+ }
+ }
+
+ repaint();
+ if (!dataSource().isEmpty()) {
+// emit pixmapChanged();
+ signalValueChanged();
+ }
+}
+
+void KexiDBImageBox::clear()
+{
+ if (dataSource().isEmpty()) {
+ //static mode
+ setData(KexiBLOBBuffer::Handle());
+ }
+ else {
+ if (isReadOnly())
+ return;
+ //db-aware mode
+ setValueInternal(QByteArray(), true);
+ //m_pixmap = QPixmap();
+ }
+
+// m_originalFileName = QString::null;
+
+ //! @todo emit signal for setting "dirty" flag within the design
+
+// m_pixmap = QPixmap(); //will be loaded on demand
+ repaint();
+ if (!dataSource().isEmpty()) {
+// emit pixmapChanged();//valueChanged(data());
+ signalValueChanged();
+ }
+}
+
+void KexiDBImageBox::handleShowPropertiesAction()
+{
+ //! @todo
+}
+
+void KexiDBImageBox::slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly)
+{
+ valueIsNull = !(
+ (dataSource().isEmpty() && !pixmap().isNull()) /*static pixmap available*/
+ || (!dataSource().isEmpty() && !this->valueIsNull()) /*db-aware pixmap available*/
+ );
+ // read-only if static pixmap or db-aware pixmap for read-only widget:
+ valueIsReadOnly = !m_designMode && dataSource().isEmpty() || !dataSource().isEmpty() && isReadOnly()
+ || m_designMode && !dataSource().isEmpty();
+}
+
+/*
+void KexiDBImageBox::slotAboutToHidePopupMenu()
+{
+// kexipluginsdbg << "##### slotAboutToHidePopupMenu() " << endl;
+ m_clickTimer.start(50, true);
+ if (m_chooser && m_chooser->isOn()) {
+ m_chooser->toggle();
+ if (m_setFocusOnButtonAfterClosingPopup) {
+ m_setFocusOnButtonAfterClosingPopup = false;
+ m_chooser->setFocus();
+ }
+ }
+}*/
+
+void KexiDBImageBox::contextMenuEvent( QContextMenuEvent * e )
+{
+ if (popupMenuAvailable())
+ m_popupMenu->exec( e->globalPos(), -1 );
+}
+
+/*void KexiDBImageBox::slotChooserPressed()
+{
+// if (!m_clickTimer.isActive())
+// return;
+// m_chooser->setDown( false );
+}
+
+void KexiDBImageBox::slotChooserReleased()
+{
+}
+
+void KexiDBImageBox::slotToggled(bool on)
+{
+ return;
+
+// kexipluginsdbg << "##### slotToggled() " << on << endl;
+ if (m_clickTimer.isActive() || !on) {
+ m_chooser->disableMousePress = true;
+ return;
+ }
+ m_chooser->disableMousePress = false;
+ QRect screen = qApp->desktop()->availableGeometry( m_chooser );
+ QPoint p;
+ if ( QApplication::reverseLayout() ) {
+ if ( (mapToGlobal( m_chooser->rect().bottomLeft() ).y() + m_popupMenu->sizeHint().height()) <= screen.height() )
+ p = m_chooser->mapToGlobal( m_chooser->rect().bottomRight() );
+ else
+ p = m_chooser->mapToGlobal( m_chooser->rect().topRight() - QPoint( 0, m_popupMenu->sizeHint().height() ) );
+ p.rx() -= m_popupMenu->sizeHint().width();
+ }
+ else {
+ if ( (m_chooser->mapToGlobal( m_chooser->rect().bottomLeft() ).y() + m_popupMenu->sizeHint().height()) <= screen.height() )
+ p = m_chooser->mapToGlobal( m_chooser->rect().bottomLeft() );
+ else
+ p = m_chooser->mapToGlobal( m_chooser->rect().topLeft() - QPoint( 0, m_popupMenu->sizeHint().height() ) );
+ }
+ if (!m_popupMenu->isVisible() && on) {
+ m_popupMenu->exec( p, -1 );
+ m_popupMenu->setFocus();
+ }
+ //m_chooser->setDown( false );
+}*/
+
+void KexiDBImageBox::updateActionStrings()
+{
+ if (!m_popupMenu)
+ return;
+ if (m_designMode) {
+/* QString titleString( i18n("Image Box") );
+ if (!dataSource().isEmpty())
+ titleString.prepend(dataSource() + " : ");
+ m_popupMenu->changeTitle(m_popupMenu->idAt(0), m_popupMenu->titlePixmap(m_popupMenu->idAt(0)), titleString);*/
+ }
+ else {
+ //update title in data view mode, based on the data source
+ if (columnInfo()) {
+ KexiImageContextMenu::updateTitle( m_popupMenu, columnInfo()->captionOrAliasOrName(),
+ KexiFormPart::library()->iconName(className()) );
+ }
+ }
+
+ if (m_chooser) {
+ if (popupMenuAvailable() && dataSource().isEmpty()) { //this may work in the future (see @todo below)
+ QToolTip::add(m_chooser, i18n("Click to show actions for this image box"));
+ } else {
+ QString beautifiedImageBoxName;
+ if (m_designMode) {
+ beautifiedImageBoxName = dataSource();
+ }
+ else {
+ beautifiedImageBoxName = columnInfo() ? columnInfo()->captionOrAliasOrName() : QString::null;
+ /*! @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool]
+ (see doc/dev/settings.txt) */
+ beautifiedImageBoxName = beautifiedImageBoxName[0].upper() + beautifiedImageBoxName.mid(1);
+ }
+ QToolTip::add(m_chooser, i18n("Click to show actions for \"%1\" image box").arg(beautifiedImageBoxName));
+ }
+ }
+}
+
+bool KexiDBImageBox::popupMenuAvailable()
+{
+/*! @todo add kexi-global setting which anyway, allows to show this button
+ (read-only actions like copy/save as/print can be available) */
+ //chooser button can be only visible when data source is specified
+ return !dataSource().isEmpty();
+}
+
+void KexiDBImageBox::setDataSource( const QString &ds )
+{
+ KexiFormDataItemInterface::setDataSource( ds );
+ setData(KexiBLOBBuffer::Handle());
+ updateActionStrings();
+ KexiFrame::setFocusPolicy( focusPolicy() ); //set modified policy
+
+ if (m_chooser) {
+ m_chooser->setEnabled(popupMenuAvailable());
+ if (m_dropDownButtonVisible && popupMenuAvailable()) {
+ m_chooser->show();
+ }
+ else {
+ m_chooser->hide();
+ }
+ }
+
+ // update some properties s not changed by user
+//! @todo get default line width from global style settings
+ if (!m_lineWidthChanged) {
+ KexiFrame::setLineWidth( ds.isEmpty() ? 0 : 1 );
+ }
+ if (!m_paletteBackgroundColorChanged && parentWidget()) {
+ KexiFrame::setPaletteBackgroundColor(
+ dataSource().isEmpty() ? parentWidget()->paletteBackgroundColor() : palette().active().base() );
+ }
+}
+
+QSize KexiDBImageBox::sizeHint() const
+{
+ if (pixmap().isNull())
+ return QSize(80, 80);
+ return pixmap().size();
+}
+
+int KexiDBImageBox::realLineWidth() const
+{
+ if (frameShape()==QFrame::Box && (frameShadow()==QFrame::Sunken || frameShadow()==QFrame::Raised))
+ return 2 * lineWidth();
+ else
+ return lineWidth();
+}
+
+void KexiDBImageBox::paintEvent( QPaintEvent *pe )
+{
+ if (!m_paintEventEnabled)
+ return;
+ QPainter p(this);
+ p.setClipRect(pe->rect());
+ const int m = realLineWidth() + margin();
+ QColor bg(eraseColor());
+ if (m_designMode && pixmap().isNull()) {
+ QPixmap pm(size()-QSize(m, m));
+ QPainter p2;
+ p2.begin(&pm, this);
+ p2.fillRect(0,0,width(),height(), bg);
+
+ updatePixmap();
+ QPixmap *imagBoxPm;
+ const bool tooLarge = (height()-m-m) <= KexiDBImageBox_pm->height();
+ if (tooLarge || (width()-m-m) <= KexiDBImageBox_pm->width())
+ imagBoxPm = KexiDBImageBox_pmSmall;
+ else
+ imagBoxPm = KexiDBImageBox_pm;
+ QImage img(imagBoxPm->convertToImage());
+ img = KImageEffect::flatten(img, bg.dark(150),
+ qGray( bg.rgb() ) <= 20 ? QColor(Qt::gray).dark(150) : bg.light(105));
+
+ QPixmap converted;
+ converted.convertFromImage(img);
+// if (tooLarge)
+// p2.drawPixmap(2, 2, converted);
+// else
+ p2.drawPixmap(2, height()-m-m-imagBoxPm->height()-2, converted);
+ QFont f(qApp->font());
+ p2.setFont(f);
+ p2.setPen( KexiUtils::contrastColor( bg ) );
+ p2.drawText(pm.rect(), Qt::AlignCenter,
+ dataSource().isEmpty()
+ ? QString::fromLatin1(name())+"\n"+i18n("Unbound Image Box", "(unbound)") //i18n("No Image")
+ : dataSource());
+ p2.end();
+ bitBlt(this, m, m, &pm);
+ }
+ else {
+ QSize internalSize(size());
+ if (m_chooser && m_dropDownButtonVisible && !dataSource().isEmpty())
+ internalSize.setWidth( internalSize.width() - m_chooser->width() );
+
+ //clearing needed here because we may need to draw a pixmap with transparency
+ p.fillRect(0,0,width(),height(), bg);
+
+ KexiUtils::drawPixmap( p, m, QRect(QPoint(0,0), internalSize), pixmap(), m_alignment,
+ m_scaledContents, m_keepAspectRatio );
+ }
+ KexiFrame::drawFrame( &p );
+
+ // if the widget is focused, draw focus indicator rect _if_ there is no chooser button
+ if (!m_designMode && !dataSource().isEmpty() && hasFocus() && (!m_chooser || !m_chooser->isVisible())) {
+ style().drawPrimitive(
+ QStyle::PE_FocusRect, &p, style().subRect(QStyle::SR_PushButtonContents, this),
+ palette().active() );
+ }
+}
+
+/* virtual void KexiDBImageBox::paletteChange ( const QPalette & oldPalette )
+{
+ QFrame::paletteChange(oldPalette);
+ if (oldPalette.active().background()!=palette().active().background()) {
+ delete KexiDBImageBox_pm;
+ KexiDBImageBox_pm = 0;
+ repaint();
+ }
+}*/
+
+void KexiDBImageBox::updatePixmap()
+{
+ if (! (m_designMode && pixmap().isNull()) )
+ return;
+
+ if (!KexiDBImageBox_pm) {
+ QString fname( locate("data", QString("kexi/pics/imagebox.png")) );
+ KexiDBImageBox_pmDeleter.setObject( KexiDBImageBox_pm, new QPixmap(fname, "PNG") );
+ QImage img(KexiDBImageBox_pm->convertToImage());
+ KexiDBImageBox_pmSmallDeleter.setObject( KexiDBImageBox_pmSmall,
+ new QPixmap( img.smoothScale(img.width()/2, img.height()/2, QImage::ScaleMin) ) );
+ }
+}
+
+void KexiDBImageBox::setAlignment(int alignment)
+{
+ m_alignment = alignment;
+ if (!m_scaledContents || m_keepAspectRatio)
+ repaint();
+}
+
+void KexiDBImageBox::setData(const KexiBLOBBuffer::Handle& handle)
+{
+ if (m_insideSetData) //avoid recursion
+ return;
+ m_insideSetData = true;
+ m_data = handle;
+ emit idChanged(handle.id());
+ m_insideSetData = false;
+ update();
+}
+
+void KexiDBImageBox::resizeEvent( QResizeEvent * e )
+{
+ KexiFrame::resizeEvent(e);
+ if (m_chooser) {
+ QSize s( m_chooser->sizeHint() );
+ QSize margin( realLineWidth(), realLineWidth() );
+ s.setHeight( height() - 2*margin.height() );
+ s = s.boundedTo( size()-2*margin );
+ m_chooser->resize( s );
+ m_chooser->move( QRect(QPoint(0,0), e->size() - m_chooser->size() - margin + QSize(1,1)).bottomRight() );
+ }
+}
+
+/*
+bool KexiDBImageBox::setProperty( const char * name, const QVariant & value )
+{
+ const bool ret = QLabel::setProperty(name, value);
+ if (p_shadowEnabled) {
+ if (0==qstrcmp("indent", name) || 0==qstrcmp("font", name) || 0==qstrcmp("margin", name)
+ || 0==qstrcmp("frameShadow", name) || 0==qstrcmp("frameShape", name)
+ || 0==qstrcmp("frameStyle", name) || 0==qstrcmp("midLineWidth", name)
+ || 0==qstrcmp("lineWidth", name)) {
+ p_privateLabel->setProperty(name, value);
+ updatePixmap();
+ }
+ }
+ return ret;
+}
+*/
+
+void KexiDBImageBox::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
+{
+ KexiFormDataItemInterface::setColumnInfo(cinfo);
+ //updating strings and title is needed
+ updateActionStrings();
+}
+
+bool KexiDBImageBox::keyPressed(QKeyEvent *ke)
+{
+ // Esc key should close the popup
+ if (ke->state() == Qt::NoButton && ke->key() == Qt::Key_Escape) {
+ if (m_popupMenu->isVisible()) {
+ m_setFocusOnButtonAfterClosingPopup = true;
+ return true;
+ }
+ }
+// else if (ke->state() == Qt::ControlButton && KStdAccel::shortcut(KStdAccel::Copy).keyCodeQt() == (ke->key()|Qt::CTRL)) {
+// }
+ return false;
+}
+
+void KexiDBImageBox::setLineWidth( int width )
+{
+ m_lineWidthChanged = true;
+ KexiFrame::setLineWidth(width);
+}
+
+void KexiDBImageBox::setPalette( const QPalette &pal )
+{
+ KexiFrame::setPalette(pal);
+ if (m_insideSetPalette)
+ return;
+ m_insideSetPalette = true;
+ setPaletteBackgroundColor(pal.active().base());
+ setPaletteForegroundColor(pal.active().foreground());
+ m_insideSetPalette = false;
+}
+
+void KexiDBImageBox::setPaletteBackgroundColor( const QColor & color )
+{
+ kexipluginsdbg << "KexiDBImageBox::setPaletteBackgroundColor(): " << color.name() << endl;
+ m_paletteBackgroundColorChanged = true;
+ KexiFrame::setPaletteBackgroundColor(color);
+ if (m_chooser)
+ m_chooser->setPalette( qApp->palette() );
+}
+
+bool KexiDBImageBox::dropDownButtonVisible() const
+{
+ return m_dropDownButtonVisible;
+}
+
+void KexiDBImageBox::setDropDownButtonVisible( bool set )
+{
+//! @todo use global default setting for this property
+ if (m_dropDownButtonVisible == set)
+ return;
+ m_dropDownButtonVisible = set;
+ if (m_chooser) {
+ if (m_dropDownButtonVisible)
+ m_chooser->show();
+ else
+ m_chooser->hide();
+ }
+}
+
+bool KexiDBImageBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const
+{
+ Q_UNUSED(autoField);
+ return true;
+}
+
+bool KexiDBImageBox::eventFilter( QObject * watched, QEvent * e )
+{
+ if (watched==this || watched==m_chooser) { //we're watching chooser as well because it's a focus proxy even if invisible
+ if (e->type()==QEvent::FocusIn || e->type()==QEvent::FocusOut || e->type()==QEvent::MouseButtonPress) {
+ update(); //to repaint focus rect
+ }
+ }
+ // hide popup menu as soon as it loses focus
+ if (watched==m_popupMenu && e->type()==QEvent::FocusOut) {
+ m_popupMenu->hide();
+ }
+ return KexiFrame::eventFilter(watched, e);
+}
+
+QWidget::FocusPolicy KexiDBImageBox::focusPolicy() const
+{
+ if (dataSource().isEmpty())
+ return NoFocus;
+ return m_focusPolicyInternal;
+}
+
+QWidget::FocusPolicy KexiDBImageBox::focusPolicyInternal() const
+{
+ return m_focusPolicyInternal;
+}
+
+void KexiDBImageBox::setFocusPolicy( FocusPolicy policy )
+{
+ m_focusPolicyInternal = policy;
+ KexiFrame::setFocusPolicy( focusPolicy() ); //set modified policy
+}
+
+#include "kexidbimagebox.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbimagebox.h b/kexi/plugins/forms/widgets/kexidbimagebox.h
new file mode 100644
index 000000000..3ad2f710b
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbimagebox.h
@@ -0,0 +1,275 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBImageBox_H
+#define KexiDBImageBox_H
+
+#include "kexiformdataiteminterface.h"
+#include "kexiframe.h"
+#include "kexidbutils.h"
+#include <kexiblobbuffer.h>
+
+class KexiDropDownButton;
+class KexiImageContextMenu;
+
+//! @short A data-aware, editable image box.
+/*! Can also act as a normal static image box.
+*/
+class KEXIFORMUTILS_EXPORT KexiDBImageBox :
+ public KexiFrame,
+ public KexiFormDataItemInterface,
+ public KexiSubwidgetInterface
+{
+ Q_OBJECT
+ Q_PROPERTY( QString dataSource READ dataSource WRITE setDataSource )
+ Q_PROPERTY( QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType )
+ Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly )
+// Q_PROPERTY( QPixmap pixmap READ pixmap WRITE setPixmap )
+// Q_PROPERTY( QByteArray pixmapData READ pixmapData WRITE setPixmapData )
+ Q_PROPERTY( uint pixmapId READ pixmapId WRITE setPixmapId DESIGNABLE true STORED false )
+ Q_PROPERTY( uint storedPixmapId READ storedPixmapId WRITE setStoredPixmapId DESIGNABLE false STORED true )
+ Q_PROPERTY( bool scaledContents READ hasScaledContents WRITE setScaledContents )
+ Q_PROPERTY( bool keepAspectRatio READ keepAspectRatio WRITE setKeepAspectRatio )
+ Q_PROPERTY( Alignment alignment READ alignment WRITE setAlignment )
+// Q_PROPERTY( QString originalFileName READ originalFileName WRITE setOriginalFileName DESIGNABLE false )
+// Q_OVERRIDE( FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy )
+ Q_PROPERTY( bool dropDownButtonVisible READ dropDownButtonVisible WRITE setDropDownButtonVisible )
+ Q_OVERRIDE( int lineWidth READ lineWidth WRITE setLineWidth )
+ Q_OVERRIDE( FocusPolicy focusPolicy READ focusPolicyInternal WRITE setFocusPolicy )
+
+ public:
+ KexiDBImageBox( bool designMode, QWidget *parent, const char *name = 0 );
+ virtual ~KexiDBImageBox();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+
+ virtual QVariant value(); // { return m_value.data(); }
+
+// QByteArray pixmapData() const { return m_value.data(); }
+
+ QPixmap pixmap() const;
+
+ uint pixmapId() const;
+
+ uint storedPixmapId() const;
+//
+ virtual void setInvalidState( const QString& displayText );
+
+ virtual bool valueIsNull();
+
+ virtual bool valueIsEmpty();
+
+ virtual QWidget* widget();
+
+ //! always true
+ virtual bool cursorAtStart();
+
+ //! always true
+ virtual bool cursorAtEnd();
+
+// //! used to catch setIndent(), etc.
+// virtual bool setProperty ( const char * name, const QVariant & value );
+
+ virtual bool isReadOnly() const;
+
+ bool hasScaledContents() const;
+
+// bool designMode() const { return m_designMode; }
+
+ int alignment() const { return m_alignment; }
+
+ bool keepAspectRatio() const { return m_keepAspectRatio; }
+
+ virtual QSize sizeHint() const;
+
+ KexiImageContextMenu *contextMenu() const;
+
+ /*! \return original file name of image loaded from a file.
+ This can be later reused for displaying the image within a collection (to be implemented)
+ or on saving the image data back to file. */
+//todo QString originalFileName() const { return m_value.originalFileName(); }
+
+ //! Reimplemented to override behaviour of "lineWidth" property.
+ virtual void setLineWidth( int width );
+
+ //! Reimplemented to override behaviour of "paletteBackgroundColor"
+ //! and "paletteForegroundColor" properties.
+ virtual void setPalette( const QPalette &pal );
+
+ //! Reimplemented to override behaviour of "paletteBackgroundColor" property.
+ virtual void setPaletteBackgroundColor( const QColor & color );
+
+ //! \return true id drop down button should be visible (the default).
+ bool dropDownButtonVisible() const;
+
+ //! For overridden property
+ int lineWidth() const { return KexiFrame::lineWidth(); }
+
+ /*! Overriden to change the policy behaviour a bit:
+ NoFocus is returned regardless the real focus flag
+ if the data source is empty (see dataSource()). */
+ FocusPolicy focusPolicy() const;
+
+ //! \return the internal focus policy value, i.e. the one unrelated to data source presence.
+ FocusPolicy focusPolicyInternal() const;
+
+ /*! Sets the internal focus policy value.
+ "Internal" means that if there is no data source set, real policy becomes NoFocus. */
+ virtual void setFocusPolicy( FocusPolicy policy );
+
+ public slots:
+ void setPixmapId(uint id);
+
+ void setStoredPixmapId(uint id);
+
+ //! Sets the datasource to \a ds
+ virtual void setDataSource( const QString &ds );
+
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+
+ virtual void setReadOnly(bool set);
+
+ //! Sets \a pixmapData data for this widget. If the widget has data source set,
+ //! the pixmap will be also placed inside of the buffer and saved later.
+//todo void setPixmapData(const QByteArray& pixmapData) { m_value.setData(pixmapData); }
+
+ /*! Sets original file name of image loaded from a file.
+ @see originalFileName() */
+//todo void setOriginalFileName(const QString& name) { m_value.setOriginalFileName(name); }
+
+ void setScaledContents(bool set);
+
+ void setAlignment(int alignment);
+
+ void setKeepAspectRatio(bool set);
+
+// void updateActionsAvailability();
+
+ //! @internal
+// void slotToggled( bool on );
+
+ //! \return sets dropDownButtonVisible property. @see dropDownButtonVisible()
+ void setDropDownButtonVisible( bool set );
+
+ //! Forces execution of "insert from file" action
+ void insertFromFile();
+
+ signals:
+ //! Used for db-aware mode. Emitted when value has been changed.
+ //! Actual value can be obtained using value().
+// virtual void pixmapChanged();
+// virtual void valueChanged(const QByteArray& data);
+
+ void idChanged(long id);
+
+ protected slots:
+ void slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly);
+
+ void handleInsertFromFileAction(const KURL& url);
+ void handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty);
+ void handleSaveAsAction(const QString& fileName);
+ void handleCutAction();
+ void handleCopyAction();
+ void handlePasteAction();
+ virtual void clear();
+ void handleShowPropertiesAction();
+
+ protected:
+ //! \return data depending on the current mode (db-aware or static)
+ QByteArray data() const;
+
+ virtual void contextMenuEvent ( QContextMenuEvent * e );
+// virtual void mousePressEvent( QMouseEvent *e );
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo);
+ virtual void paintEvent( QPaintEvent* );
+ virtual void resizeEvent( QResizeEvent* e );
+ virtual bool eventFilter( QObject * watched, QEvent * e );
+
+ //! Sets value \a value for a widget.
+ virtual void setValueInternal( const QVariant& add, bool removeOld ) {
+ setValueInternal( add, removeOld, true /*loadPixmap*/ );
+ }
+
+ //! @internal, added \a loadPixmap option used by paste().
+ void setValueInternal( const QVariant& add, bool removeOld, bool loadPixmap );
+
+ //! Updates i18n'd action strings after datasource change
+ void updateActionStrings();
+ void updatePixmap();
+
+ //! @internal
+ void setData(const KexiBLOBBuffer::Handle& handle);
+
+ bool popupMenuAvailable();
+
+ /*! Called by top-level form on key press event.
+ Used for Key_Escape to if the popup is visible,
+ so the key press won't be consumed to perform "cancel editing". */
+ virtual bool keyPressed(QKeyEvent *ke);
+
+ //! \return real line width, i.e. for Boxed sunken or Boxed raised
+ //! frames returns doubled width value.
+ int realLineWidth() const;
+
+ //! Implemented for KexiSubwidgetInterface
+ virtual bool subwidgetStretchRequired(KexiDBAutoField* autoField) const;
+
+// virtual void drawContents ( QPainter *p );
+
+// virtual void fontChange( const QFont& font );
+// virtual void styleChange( QStyle& style );
+// virtual void enabledChange( bool enabled );
+
+// virtual void paletteChange( const QPalette& pal );
+// virtual void frameChanged();
+// virtual void showEvent( QShowEvent* e );
+
+// void updatePixmapLater();
+// class ImageLabel;
+// ImageLabel *m_pixmapLabel;
+ QPixmap m_pixmap;
+ QByteArray m_value; //!< for db-aware mode
+ QString m_valueMimeType; //!< for db-aware mode
+// PixmapData m_value;
+ KexiBLOBBuffer::Handle m_data;
+// QString m_originalFileName;
+ KexiDropDownButton *m_chooser;
+ KexiImageContextMenu *m_popupMenu;
+//moved KActionCollection m_actionCollection;
+//moved KAction *m_insertFromFileAction, *m_saveAsAction, *m_cutAction, *m_copyAction, *m_pasteAction,
+// *m_deleteAction, *m_propertiesAction;
+// QTimer m_clickTimer;
+ int m_alignment;
+ FocusPolicy m_focusPolicyInternal; //!< Used for focusPolicyInternal()
+ bool m_designMode : 1;
+ bool m_readOnly : 1;
+ bool m_scaledContents : 1;
+ bool m_keepAspectRatio : 1;
+ bool m_insideSetData : 1;
+ bool m_setFocusOnButtonAfterClosingPopup : 1;
+ bool m_lineWidthChanged : 1;
+ bool m_paletteBackgroundColorChanged : 1;
+ bool m_paintEventEnabled : 1; //!< used to disable paintEvent()
+ bool m_dropDownButtonVisible : 1;
+ bool m_insideSetPalette : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbintspinbox.cpp b/kexi/plugins/forms/widgets/kexidbintspinbox.cpp
new file mode 100644
index 000000000..ac9233474
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbintspinbox.cpp
@@ -0,0 +1,114 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbintspinbox.h"
+
+#include <qlineedit.h>
+#include <knumvalidator.h>
+
+KexiDBIntSpinBox::KexiDBIntSpinBox(QWidget *parent, const char *name)
+ : KIntSpinBox(parent, name) , KexiFormDataItemInterface()
+{
+ connect(this, SIGNAL(valueChanged(int)), this, SLOT(slotValueChanged()));
+}
+
+KexiDBIntSpinBox::~KexiDBIntSpinBox()
+{
+}
+
+void KexiDBIntSpinBox::setInvalidState( const QString& displayText )
+{
+ m_invalidState = true;
+ setEnabled(false);
+ setReadOnly(true);
+//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ?
+ if (focusPolicy() & TabFocus)
+ setFocusPolicy(QWidget::ClickFocus);
+ setSpecialValueText(displayText);
+ KIntSpinBox::setValue(minValue());
+}
+
+void
+KexiDBIntSpinBox::setEnabled(bool enabled)
+{
+ // prevent the user from reenabling the widget when it is in invalid state
+ if(enabled && m_invalidState)
+ return;
+ KIntSpinBox::setEnabled(enabled);
+}
+
+void KexiDBIntSpinBox::setValueInternal(const QVariant&, bool)
+{
+ KIntSpinBox::setValue(m_origValue.toInt());
+}
+
+QVariant
+KexiDBIntSpinBox::value()
+{
+ return KIntSpinBox::value();
+}
+
+void KexiDBIntSpinBox::slotValueChanged()
+{
+ signalValueChanged();
+}
+
+bool KexiDBIntSpinBox::valueIsNull()
+{
+ return cleanText().isEmpty();
+}
+
+bool KexiDBIntSpinBox::valueIsEmpty()
+{
+ return false;
+}
+
+bool KexiDBIntSpinBox::isReadOnly() const
+{
+ return editor()->isReadOnly();
+}
+
+void KexiDBIntSpinBox::setReadOnly(bool set)
+{
+ editor()->setReadOnly(set);
+}
+
+QWidget*
+KexiDBIntSpinBox::widget()
+{
+ return this;
+}
+
+bool KexiDBIntSpinBox::cursorAtStart()
+{
+ return false; //! \todo ?
+}
+
+bool KexiDBIntSpinBox::cursorAtEnd()
+{
+ return false; //! \todo ?
+}
+
+void KexiDBIntSpinBox::clear()
+{
+ KIntSpinBox::setValue(minValue()); //! \todo ?
+}
+
+#include "kexidbintspinbox.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbintspinbox.h b/kexi/plugins/forms/widgets/kexidbintspinbox.h
new file mode 100644
index 000000000..cddc614e2
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbintspinbox.h
@@ -0,0 +1,80 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBIntSpinBox_H
+#define KexiDBIntSpinBox_H
+
+#include "kexiformdataiteminterface.h"
+#include <qwidget.h>
+#include <knuminput.h>
+
+//! @short A db-aware int spin box
+class KEXIFORMUTILS_EXPORT KexiDBIntSpinBox : public KIntSpinBox, public KexiFormDataItemInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true )
+
+ public:
+ KexiDBIntSpinBox(QWidget *parent, const char *name=0);
+ virtual ~KexiDBIntSpinBox();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual QVariant value();
+ virtual void setInvalidState( const QString& displayText );
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsEmpty();
+
+ /*! \return 'readOnly' flag for this widget. */
+ virtual bool isReadOnly() const;
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ virtual void setEnabled(bool enabled);
+
+ public slots:
+ inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); }
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ void slotValueChanged();
+ virtual void setReadOnly(bool set);
+
+ protected:
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ private:
+ bool m_invalidState : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidblabel.cpp b/kexi/plugins/forms/widgets/kexidblabel.cpp
new file mode 100644
index 000000000..e30cc19e5
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidblabel.cpp
@@ -0,0 +1,650 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidblabel.h"
+
+#include <qbitmap.h>
+#include <qpainter.h>
+#include <qdrawutil.h>
+#include <qapplication.h>
+#include <qtimer.h>
+
+#include <kdebug.h>
+#include <kimageeffect.h>
+
+#include <kexidb/field.h>
+#include <kexiutils/utils.h>
+
+#define SHADOW_OFFSET_X 3
+#define SHADOW_OFFSET_Y 3
+#define SHADOW_FACTOR 16.0
+#define SHADOW_OPACITY 50.0
+#define SHADOW_AXIS_FACTOR 2.0
+#define SHADOW_DIAGONAL_FACTOR 1.0
+#define SHADOW_THICKNESS 1
+
+//! @internal
+class KexiDBInternalLabel : public QLabel {
+ friend class KexiDBLabel;
+ public:
+ KexiDBInternalLabel( KexiDBLabel* );
+ virtual ~KexiDBInternalLabel();
+
+ protected:
+ void updateFrame();
+
+ QImage makeShadow( const QImage& textImage, const QColor &bgColor, const QRect& boundingRect );
+ QRect getBounding( const QImage &image, const QRect& startRect );
+// double defaultDecay( QImage& source, int i, int j );
+ KPixmap getShadowPixmap();
+
+ QRect m_shadowRect;
+ KexiDBLabel *m_parentLabel;
+};
+
+KexiDBInternalLabel::KexiDBInternalLabel( KexiDBLabel* parent )
+ : QLabel( parent )
+ , m_parentLabel(parent)
+{
+ int a = alignment() | Qt::WordBreak;
+ a &= (0xffffff ^ Qt::AlignVertical_Mask);
+ a |= Qt::AlignTop;
+ setAlignment( a );
+ updateFrame();
+}
+
+void KexiDBInternalLabel::updateFrame()
+{
+ setIndent(m_parentLabel->indent());
+ setMargin(m_parentLabel->margin());
+ setFont(m_parentLabel->font());
+
+ setFrameShadow(m_parentLabel->frameShadow());
+ setFrameShape(m_parentLabel->frameShape());
+ setFrameStyle(m_parentLabel->frameStyle());
+ setMidLineWidth(m_parentLabel->midLineWidth());
+ setLineWidth(m_parentLabel->lineWidth());
+}
+
+KexiDBInternalLabel::~KexiDBInternalLabel()
+{
+}
+
+/*!
+* This method is copied from kdebase/kdesktop/kshadowengine.cpp
+* Some modifactions were made.
+* --
+* Christian Nitschkowski
+*/
+QImage KexiDBInternalLabel::makeShadow( const QImage& textImage,
+ const QColor &bgColor, const QRect& boundingRect )
+{
+ QImage result;
+ QString origText( text() );
+
+ // create a new image for for the shaddow
+ const int w = textImage.width();
+ const int h = textImage.height();
+
+ // avoid calling these methods for every pixel
+ const int bgRed = bgColor.red();
+ const int bgGreen = bgColor.green();
+ const int bgBlue = bgColor.blue();
+
+ const int startX = boundingRect.x() + SHADOW_THICKNESS;
+ const int startY = boundingRect.y() + SHADOW_THICKNESS;
+ const int effectWidth = boundingRect.bottomRight().x() - SHADOW_THICKNESS;
+ const int effectHeight = boundingRect.bottomRight().y() - SHADOW_THICKNESS;
+// const int period = (effectWidth - startX) / 10;
+
+ double alphaShadow;
+
+ /*
+ * This is the source pixmap
+ */
+ QImage img = textImage.convertDepth( 32 );
+
+ /*
+ * Resize the image if necessary
+ */
+ if ( ( result.width() != w ) || ( result.height() != h ) ) {
+ result.create( w, h, 32 );
+ }
+
+// result.fill( 0 ); // all black
+ double realOpacity = SHADOW_OPACITY + QMIN(50.0/double(256.0-qGray(bgColor.rgb())), 50.0);
+ //int _h, _s, _v;
+ //.getHsv( &_h, &_s, &_v );
+ if (colorGroup().background()==Qt::red)//_s>=250 && _v>=250) //for colors like cyan or red, make the result more white
+ realOpacity += 50.0;
+ result.fill( (int)realOpacity );
+ result.setAlphaBuffer( true );
+
+ for ( int i = startX; i < effectWidth; i++ ) {
+ for ( int j = startY; j < effectHeight; j++ ) {
+ /*!
+ * This method is copied from kdebase/kdesktop/kshadowengine.cpp
+ * Some modifactions were made.
+ * --
+ * Christian Nitschkowski
+ */
+ if ( ( i < 1 ) || ( j < 1 ) || ( i > img.width() - 2 ) || ( j > img.height() - 2 ) )
+ continue;
+ else
+ alphaShadow = ( qGray( img.pixel( i - 1, j - 1 ) ) * SHADOW_DIAGONAL_FACTOR +
+ qGray( img.pixel( i - 1, j ) ) * SHADOW_AXIS_FACTOR +
+ qGray( img.pixel( i - 1, j + 1 ) ) * SHADOW_DIAGONAL_FACTOR +
+ qGray( img.pixel( i , j - 1 ) ) * SHADOW_AXIS_FACTOR +
+ 0 +
+ qGray( img.pixel( i , j + 1 ) ) * SHADOW_AXIS_FACTOR +
+ qGray( img.pixel( i + 1, j - 1 ) ) * SHADOW_DIAGONAL_FACTOR +
+ qGray( img.pixel( i + 1, j ) ) * SHADOW_AXIS_FACTOR +
+ qGray( img.pixel( i + 1, j + 1 ) ) * SHADOW_DIAGONAL_FACTOR ) / SHADOW_FACTOR;
+
+ // update the shadow's i,j pixel.
+ if (alphaShadow > 0)
+ result.setPixel( i, j, qRgba( bgRed, bgGreen , bgBlue,
+ ( int ) (( alphaShadow > realOpacity ) ? realOpacity : alphaShadow)
+ ) );
+ }
+/*caused too much redraw problems if (period && i % period) {
+ qApp->processEvents();
+ if (text() != origText) //text has been changed in the meantime: abort
+ return QImage();
+ }*/
+ }
+ return result;
+}
+
+KPixmap KexiDBInternalLabel::getShadowPixmap() {
+ /*!
+ * Backup the default color used to draw text.
+ */
+ const QColor textColor = colorGroup().foreground();
+
+ /*!
+ * Temporary storage for the generated shadow
+ */
+ KPixmap finalPixmap, tempPixmap;
+ QImage shadowImage, tempImage;
+ QPainter painter;
+
+ m_shadowRect = QRect();
+
+ tempPixmap.resize( size() );
+ tempPixmap.fill( Qt::black );
+ tempPixmap.setMask( tempPixmap.createHeuristicMask( true ) );
+
+ /*!
+ * The textcolor has to be white for creating shadows!
+ */
+ setPaletteForegroundColor( Qt::white );
+
+ /*!
+ Draw the label "as usual" in a pixmap
+ */
+ painter.begin( &tempPixmap );
+ painter.setFont( font() );
+ drawContents( &painter );
+ painter.end();
+ setPaletteForegroundColor( textColor );
+
+ /*!
+ * Calculate the first bounding rect.
+ * This will fit around the unmodified text.
+ */
+ shadowImage = tempPixmap;
+ tempPixmap.setMask( QBitmap() );
+
+ /*!
+ Get the first bounding rect.
+ This may speed up makeShadow later.
+ */
+ m_shadowRect = getBounding( shadowImage, m_shadowRect );
+
+ /*!
+ * Enlarge the bounding rect to make sure the shadow
+ * will fit in.
+ * The new rect has to fit in the pixmap.
+ * I have to admit this isn't really nice code...
+ */
+ m_shadowRect.setX( QMAX( m_shadowRect.x() - ( m_shadowRect.width() / 4 ), 0 ) );
+ m_shadowRect.setY( QMAX( m_shadowRect.y() - ( m_shadowRect.height() / 4 ), 0 ) );
+ m_shadowRect.setBottomRight( QPoint(
+ QMIN( m_shadowRect.x() + ( m_shadowRect.width() * 3 / 2 ), shadowImage.width() ),
+ QMIN( m_shadowRect.y() + ( m_shadowRect.height() * 3 / 2 ), shadowImage.height() ) ) );
+
+ shadowImage = makeShadow( shadowImage,
+ qGray( colorGroup().background().rgb() ) < 127 ? Qt::white : Qt::black,
+ m_shadowRect );
+ if (shadowImage.isNull())
+ return KPixmap();
+
+ /*!
+ Now get the final bounding rect.
+ */
+ m_shadowRect = getBounding( shadowImage, m_shadowRect );
+
+ /*!
+ Paint the labels background in a new pixmap.
+ */
+ finalPixmap.resize( size() );
+ painter.begin( &finalPixmap );
+ painter.fillRect( 0, 0, finalPixmap.width(), finalPixmap.height(),
+ palette().brush(
+ isEnabled() ? QPalette::Active : QPalette::Disabled,
+ QColorGroup::Background ) );
+ painter.end();
+
+ /*!
+ Copy the part of the background the shadow will be on
+ to another pixmap.
+ */
+ tempPixmap.resize( m_shadowRect.size() );
+ if (!finalPixmap.isNull()) {
+ bitBlt( &tempPixmap, 0, 0, &finalPixmap,
+ m_shadowRect.x() + SHADOW_OFFSET_X,
+ m_shadowRect.y() + SHADOW_OFFSET_Y,
+ m_shadowRect.width(),
+ m_shadowRect.height() );
+ }
+ /*!
+ Replace the big background pixmap with the
+ part we could out just before.
+ */
+ finalPixmap = tempPixmap;
+
+ /*!
+ Copy the "interesting" part of the shadow image
+ to a new image.
+ I tried to copy this to a pixmap directly,
+ but it didn't work correctly.
+ Maybe a Qt bug?
+ */
+ tempImage = shadowImage.copy( m_shadowRect );
+ tempPixmap.convertFromImage( tempImage );
+ /*!
+ Anyways, merge the shadow with the background.
+ */
+ if (!tempPixmap.isNull()) {
+ bitBlt( &finalPixmap, 0, 0, &tempPixmap );
+ }
+
+ /**
+ Now move the rect.
+ Don't do this before the shadow is copied from shadowImage!
+ */
+ m_shadowRect.moveBy( SHADOW_OFFSET_X, SHADOW_OFFSET_Y );
+
+ return finalPixmap;
+}
+
+QRect KexiDBInternalLabel::getBounding( const QImage &image, const QRect& startRect ) {
+ QPoint topLeft;
+ QPoint bottomRight;
+
+ const int startX = startRect.x();
+ const int startY = startRect.y();
+ /*!
+ * Ugly beast to get the correct width and height
+ */
+ const int width = QMIN( ( startRect.bottomRight().x() > 0
+ ? startRect.bottomRight().x() : QCOORD_MAX ),
+ image.width() );
+ const int height = QMIN( ( startRect.bottomRight().y() > 0
+ ? startRect.bottomRight().y() : QCOORD_MAX ),
+ image.height() );
+
+ /*!
+ Assume the first pixel has the color of the
+ background that has to be cut away.
+ Qt uses the four corner pixels to guess the
+ correct color, but in this case the topleft
+ pixel should be enough.
+ */
+ QRgb trans = image.pixel( 0, 0 );
+
+ for ( int y = startY; y < height; y++ ) {
+ for ( int x = startX; x < width; x++ ) {
+ if ( image.pixel( x, y ) != trans ) {
+ topLeft.setY( y );
+ y = height;
+ break;
+ }
+ }
+ }
+
+ for ( int x = startX; x < width; x++ ) {
+ for ( int y = startY; y < height; y++ ) {
+ if ( image.pixel( x, y ) != trans ) {
+ topLeft.setX( x );
+ x = width;
+ break;
+ }
+ }
+ }
+
+ for ( int y = height - 1; y > topLeft.y(); y-- ) {
+ for ( int x = width - 1; x > topLeft.x(); x-- ) {
+ if ( image.pixel( x, y ) != trans ) {
+ bottomRight.setY( y + 1 );
+ y = 0;
+ break;
+ }
+ }
+ }
+
+ for ( int x = width - 1; x > topLeft.x(); x-- ) {
+ for ( int y = height - 1; y > topLeft.y(); y-- ) {
+ if ( image.pixel( x, y ) != trans ) {
+ bottomRight.setX( x + 1 );
+ x = 0;
+ break;
+ }
+ }
+ }
+
+ return QRect(
+ topLeft.x(),
+ topLeft.y(),
+ bottomRight.x() - topLeft.x(),
+ bottomRight.y() - topLeft.y() );
+}
+
+//=========================================================
+
+//! @internal
+class KexiDBLabel::Private
+{
+ public:
+ Private()
+ : timer(0)
+// , autonumberDisplayParameters(0)
+ , pixmapDirty( true )
+ , shadowEnabled( false )
+ , resizeEvent( false )
+ {
+ }
+ ~Private() {}
+ KPixmap shadowPixmap;
+ QPoint shadowPosition;
+ KexiDBInternalLabel* internalLabel;
+ QTimer* timer;
+ QColor frameColor;
+ bool pixmapDirty : 1;
+ bool shadowEnabled : 1;
+ bool resizeEvent : 1;
+};
+
+//=========================================================
+
+KexiDBLabel::KexiDBLabel( QWidget *parent, const char *name, WFlags f )
+ : QLabel( parent, name, f )
+ , KexiDBTextWidgetInterface()
+ , KexiFormDataItemInterface()
+ , d( new Private() )
+{
+ init();
+}
+
+KexiDBLabel::KexiDBLabel( const QString& text, QWidget *parent, const char *name, WFlags f )
+ : QLabel( parent, name, f )
+ , KexiDBTextWidgetInterface()
+ , KexiFormDataItemInterface()
+ , d( new Private() )
+{
+ init();
+ setText( text );
+}
+
+KexiDBLabel::~KexiDBLabel()
+{
+ delete d;
+}
+
+void KexiDBLabel::init()
+{
+ m_hasFocusableWidget = false;
+ d->internalLabel = new KexiDBInternalLabel( this );
+ d->internalLabel->hide();
+ d->frameColor = palette().active().foreground();
+
+ setAlignment( d->internalLabel->alignment() );
+}
+
+void KexiDBLabel::updatePixmapLater() {
+ if (d->resizeEvent) {
+ if (!d->timer) {
+ d->timer = new QTimer(this, "KexiDBLabelTimer");
+ connect(d->timer, SIGNAL(timeout()), this, SLOT(updatePixmap()));
+ }
+ d->timer->start(100, true);
+ d->resizeEvent = false;
+ return;
+ }
+ if (d->timer && d->timer->isActive())
+ return;
+ updatePixmap();
+}
+
+void KexiDBLabel::updatePixmap() {
+ /*!
+ Whatever has changed in KexiDBLabel,
+ every parameter is set to our private-label.
+ Just in case...
+ */
+ d->internalLabel->setText( text() );
+ d->internalLabel->setFixedSize( size() );
+ d->internalLabel->setPalette( palette() );
+ d->internalLabel->setAlignment( alignment() );
+// d->shadowPixmap = KPixmap(); //parallel repaints won't hurt us cause incomplete pixmap
+ KPixmap shadowPixmap = d->internalLabel->getShadowPixmap();
+ if (shadowPixmap.isNull())
+ return;
+ d->shadowPixmap = shadowPixmap;
+ d->shadowPosition = d->internalLabel->m_shadowRect.topLeft();
+ d->pixmapDirty = false;
+ repaint();
+}
+
+void KexiDBLabel::paintEvent( QPaintEvent* e )
+{
+ QPainter p( this );
+ if ( d->shadowEnabled ) {
+ /*!
+ If required, update the pixmap-cache.
+ */
+ if ( d->pixmapDirty ) {
+ updatePixmapLater();
+ }
+
+ /*!
+ If the part that should be redrawn intersects with our shadow,
+ redraw the shadow where it intersects with e->rect().
+ Have to move the clipping rect around a bit because
+ the shadow has to be drawn using an offset relative to
+ the widgets border.
+ */
+ if ( !d->pixmapDirty && e->rect().contains( d->shadowPosition ) && !d->shadowPixmap.isNull()) {
+ QRect clipRect = QRect(
+ QMAX( e->rect().x() - d->shadowPosition.x(), 0 ),
+ QMAX( e->rect().y() - d->shadowPosition.y(), 0 ),
+ QMIN( e->rect().width() + d->shadowPosition.x(), d->shadowPixmap.width() ),
+ QMIN( e->rect().height() + d->shadowPosition.y(), d->shadowPixmap.height() ) );
+ p.drawPixmap( d->internalLabel->m_shadowRect.topLeft(), d->shadowPixmap, clipRect );
+ }
+ }
+ KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), false );
+ QLabel::paintEvent( e );
+}
+
+void KexiDBLabel::setValueInternal( const QVariant& add, bool removeOld ) {
+ if (removeOld)
+ setText(add.toString());
+ else
+ setText( m_origValue.toString() + add.toString() );
+}
+
+QVariant KexiDBLabel::value() {
+ return text();
+}
+
+void KexiDBLabel::setInvalidState( const QString& displayText )
+{
+ setText( displayText );
+}
+
+bool KexiDBLabel::valueIsNull()
+{
+ return text().isNull();
+}
+
+bool KexiDBLabel::valueIsEmpty()
+{
+ return text().isEmpty();
+}
+
+bool KexiDBLabel::isReadOnly() const
+{
+ return true;
+}
+
+void KexiDBLabel::setReadOnly( bool readOnly )
+{
+ Q_UNUSED(readOnly);
+}
+
+QWidget* KexiDBLabel::widget()
+{
+ return this;
+}
+
+bool KexiDBLabel::cursorAtStart()
+{
+ return false;
+}
+
+bool KexiDBLabel::cursorAtEnd()
+{
+ return false;
+}
+
+void KexiDBLabel::clear()
+{
+ setText(QString::null);
+}
+
+bool KexiDBLabel::setProperty( const char * name, const QVariant & value )
+{
+ const bool ret = QLabel::setProperty(name, value);
+ if (d->shadowEnabled) {
+ if (0==qstrcmp("indent", name) || 0==qstrcmp("font", name) || 0==qstrcmp("margin", name)
+ || 0==qstrcmp("frameShadow", name) || 0==qstrcmp("frameShape", name)
+ || 0==qstrcmp("frameStyle", name) || 0==qstrcmp("midLineWidth", name)
+ || 0==qstrcmp("lineWidth", name)) {
+ d->internalLabel->setProperty(name, value);
+ updatePixmap();
+ }
+ }
+ return ret;
+}
+
+void KexiDBLabel::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
+{
+ KexiFormDataItemInterface::setColumnInfo(cinfo);
+ KexiDBTextWidgetInterface::setColumnInfo(cinfo, this);
+}
+
+void KexiDBLabel::setShadowEnabled( bool state ) {
+ d->shadowEnabled = state;
+ d->pixmapDirty = true;
+ if (state)
+ d->internalLabel->updateFrame();
+ repaint();
+}
+
+void KexiDBLabel::resizeEvent( QResizeEvent* e ) {
+ if (isVisible())
+ d->resizeEvent = true;
+ d->pixmapDirty = true;
+ QLabel::resizeEvent( e );
+}
+
+void KexiDBLabel::fontChange( const QFont& font ) {
+ d->pixmapDirty = true;
+ d->internalLabel->setFont( font );
+ QLabel::fontChange( font );
+}
+
+void KexiDBLabel::styleChange( QStyle& style ) {
+ d->pixmapDirty = true;
+ QLabel::styleChange( style );
+}
+
+void KexiDBLabel::enabledChange( bool enabled ) {
+ d->pixmapDirty = true;
+ d->internalLabel->setEnabled( enabled );
+ QLabel::enabledChange( enabled );
+}
+
+void KexiDBLabel::paletteChange( const QPalette& oldPal ) {
+ Q_UNUSED(oldPal);
+ d->pixmapDirty = true;
+ d->internalLabel->setPalette( palette() );
+}
+
+/*const QColor & KexiDBLabel::paletteForegroundColor () const
+{
+ return d->foregroundColor;
+}
+
+void KexiDBLabel::setPaletteForegroundColor ( const QColor& color )
+{
+ d->foregroundColor = color;
+}*/
+
+void KexiDBLabel::frameChanged() {
+ d->pixmapDirty = true;
+ d->internalLabel->updateFrame();
+ QFrame::frameChanged();
+}
+
+void KexiDBLabel::showEvent( QShowEvent* e ) {
+ d->pixmapDirty = true;
+ QLabel::showEvent( e );
+}
+
+void KexiDBLabel::setText( const QString& text ) {
+ d->pixmapDirty = true;
+ QLabel::setText( text );
+ //This is necessary for KexiFormDataItemInterface
+ valueChanged();
+ repaint();
+}
+
+bool KexiDBLabel::shadowEnabled() const
+{
+ return d->shadowEnabled;
+}
+
+#define ClassName KexiDBLabel
+#define SuperClassName QLabel
+#include "kexiframeutils_p.cpp"
+#include "kexidblabel.moc"
diff --git a/kexi/plugins/forms/widgets/kexidblabel.h b/kexi/plugins/forms/widgets/kexidblabel.h
new file mode 100644
index 000000000..ec4e626a5
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidblabel.h
@@ -0,0 +1,140 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de>
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDBLABEL_H
+#define KEXIDBLABEL_H
+
+#include <qimage.h>
+#include <qlabel.h>
+
+#include <kpixmap.h>
+
+#include "../kexiformdataiteminterface.h"
+#include "../kexidbtextwidgetinterface.h"
+#include <widget/utils/kexidisplayutils.h>
+
+class QPainter;
+class QTimer;
+class KexiDBInternalLabel;
+
+//! @short An extended, data-aware, read-only text label.
+/*! It's text may have a drop-shadow.
+
+ @author Christian Nitschkowski, Jaroslaw Staniek
+*/
+class KEXIFORMUTILS_EXPORT KexiDBLabel : public QLabel, protected KexiDBTextWidgetInterface, public KexiFormDataItemInterface {
+ Q_OBJECT
+ Q_PROPERTY( QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true )
+ Q_PROPERTY( QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true )
+ Q_PROPERTY( bool shadowEnabled READ shadowEnabled WRITE setShadowEnabled DESIGNABLE true )
+ Q_OVERRIDE( QPixmap pixmap DESIGNABLE false )
+ Q_OVERRIDE( bool scaledContents DESIGNABLE false )
+// Q_OVERRIDE( QColor paletteForegroundColor READ paletteForegroundColor WRITE setPaletteForegroundColor DESIGNABLE true )
+ Q_PROPERTY( QColor frameColor READ frameColor WRITE setFrameColor DESIGNABLE true )
+
+ public:
+ KexiDBLabel( QWidget *parent, const char *name = 0, WFlags f = 0 );
+ KexiDBLabel( const QString& text, QWidget *parent, const char *name = 0, WFlags f = 0 );
+ virtual ~KexiDBLabel();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+
+ virtual QVariant value();
+
+ bool shadowEnabled() const;
+
+ virtual void setInvalidState( const QString& displayText );
+
+ virtual bool valueIsNull();
+
+ virtual bool valueIsEmpty();
+
+ //! always true
+ virtual bool isReadOnly() const;
+
+ virtual QWidget* widget();
+
+ //! always false
+ virtual bool cursorAtStart();
+
+ //! always false
+ virtual bool cursorAtEnd();
+
+ virtual void clear();
+
+ //! used to catch setIndent(), etc.
+ virtual bool setProperty ( const char * name, const QVariant & value );
+
+ virtual const QColor& frameColor() const;
+
+// const QColor & paletteForegroundColor() const;
+
+ public slots:
+ //! Sets the datasource to \a ds
+ inline void setDataSource( const QString &ds ) { KexiFormDataItemInterface::setDataSource( ds ); }
+
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+
+ virtual void setText( const QString& text );
+
+ /*! Enable/Disable the shadow effect.
+ KexiDBLabel acts just like a normal QLabel when shadow is disabled. */
+ void setShadowEnabled( bool state );
+
+ virtual void setPalette( const QPalette &pal );
+
+ virtual void setFrameColor(const QColor& color);
+
+// void setPaletteForegroundColor( const QColor& color );
+
+ protected slots:
+ //! empty
+ virtual void setReadOnly( bool readOnly );
+ void updatePixmap();
+
+ protected:
+ void init();
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo);
+ virtual void paintEvent( QPaintEvent* );
+ virtual void resizeEvent( QResizeEvent* e );
+
+ //! Sets value \a value for a widget.
+ virtual void setValueInternal( const QVariant& add, bool removeOld );
+
+ virtual void fontChange( const QFont& font );
+ virtual void styleChange( QStyle& style );
+ virtual void enabledChange( bool enabled );
+
+ virtual void paletteChange( const QPalette& oldPal );
+ virtual void frameChanged();
+ virtual void showEvent( QShowEvent* e );
+
+ //! Reimplemented to paint using real frame color instead of froeground.
+ //! Also allows to paint more types of frame.
+ virtual void drawFrame( QPainter * );
+
+ void updatePixmapLater();
+
+ class Private;
+ Private *d;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidblineedit.cpp b/kexi/plugins/forms/widgets/kexidblineedit.cpp
new file mode 100644
index 000000000..3897a8cbd
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidblineedit.cpp
@@ -0,0 +1,417 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidblineedit.h"
+#include "kexidbautofield.h"
+
+#include <kdebug.h>
+#include <knumvalidator.h>
+#include <kdatetbl.h>
+
+#include <qpopupmenu.h>
+#include <qpainter.h>
+
+#include <kexiutils/utils.h>
+#include <kexidb/queryschema.h>
+#include <kexidb/fieldvalidator.h>
+#include <kexiutils/utils.h>
+
+//! @todo reenable as an app aption
+//#define USE_KLineEdit_setReadOnly
+
+//! @internal A validator used for read only flag to disable editing
+class KexiDBLineEdit_ReadOnlyValidator : public QValidator
+{
+ public:
+ KexiDBLineEdit_ReadOnlyValidator( QObject * parent )
+ : QValidator(parent)
+ {
+ }
+ ~KexiDBLineEdit_ReadOnlyValidator() {}
+ virtual State validate( QString &, int & ) const { return Invalid; }
+};
+
+//-----
+
+KexiDBLineEdit::KexiDBLineEdit(QWidget *parent, const char *name)
+ : KLineEdit(parent, name)
+ , KexiDBTextWidgetInterface()
+ , KexiFormDataItemInterface()
+//moved , m_dateFormatter(0)
+//moved , m_timeFormatter(0)
+ , m_menuExtender(this, this)
+ , m_internalReadOnly(false)
+ , m_slotTextChanged_enabled(true)
+{
+#ifdef USE_KLineEdit_setReadOnly
+//! @todo reenable as an app aption
+ QPalette p(widget->palette());
+ p.setColor( lighterGrayBackgroundColor(palette()) );
+ widget->setPalette(p);
+#endif
+
+ connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(slotTextChanged(const QString&)));
+}
+
+KexiDBLineEdit::~KexiDBLineEdit()
+{
+//moved delete m_dateFormatter;
+//moved delete m_timeFormatter;
+}
+
+void KexiDBLineEdit::setInvalidState( const QString& displayText )
+{
+ KLineEdit::setReadOnly(true);
+//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ?
+ if (focusPolicy() & TabFocus)
+ setFocusPolicy(QWidget::ClickFocus);
+ setText(displayText);
+}
+
+void KexiDBLineEdit::setValueInternal(const QVariant& add, bool removeOld)
+{
+#if 0 //moved to KexiTextFormatter
+ QVariant value;
+ if (removeOld)
+ value = add;
+ else {
+ if (add.toString().isEmpty())
+ value = m_origValue;
+ else
+ value = m_origValue.toString() + add.toString();
+ }
+
+ if (m_columnInfo) {
+ const KexiDB::Field::Type t = m_columnInfo->field->type();
+ if (t == KexiDB::Field::Boolean) {
+ //! @todo temporary solution for booleans!
+ setText( value.toBool() ? "1" : "0" );
+ return;
+ }
+ else if (t == KexiDB::Field::Date) {
+ setText( dateFormatter()->dateToString( value.toString().isEmpty() ? QDate() : value.toDate() ) );
+ setCursorPosition(0); //ok?
+ return;
+ }
+ else if (t == KexiDB::Field::Time) {
+ setText(
+ timeFormatter()->timeToString(
+ //hack to avoid converting null variant to valid QTime(0,0,0)
+ value.toString().isEmpty() ? value.toTime() : QTime(99,0,0)
+ )
+ );
+ setCursorPosition(0); //ok?
+ return;
+ }
+ else if (t == KexiDB::Field::DateTime) {
+ if (value.toString().isEmpty() ) {
+ setText( QString::null );
+ }
+ else {
+ setText(
+ dateFormatter()->dateToString( value.toDateTime().date() ) + " " +
+ timeFormatter()->timeToString( value.toDateTime().time() )
+ );
+ }
+ setCursorPosition(0); //ok?
+ return;
+ }
+ }
+#endif
+ m_slotTextChanged_enabled = false;
+ setText( m_textFormatter.valueToText(removeOld ? QVariant() : m_origValue, add.toString()) );
+// setText( value.toString() );
+ setCursorPosition(0); //ok?
+ m_slotTextChanged_enabled = true;
+}
+
+QVariant KexiDBLineEdit::value()
+{
+ return m_textFormatter.textToValue( text() );
+#if 0 // moved to KexiTextFormatter
+ if (! m_columnInfo)
+ return QVariant();
+ const KexiDB::Field::Type t = m_columnInfo->field->type();
+ switch (t) {
+ case KexiDB::Field::Text:
+ case KexiDB::Field::LongText:
+ return text();
+ case KexiDB::Field::Byte:
+ case KexiDB::Field::ShortInteger:
+ return text().toShort();
+//! @todo uint, etc?
+ case KexiDB::Field::Integer:
+ return text().toInt();
+ case KexiDB::Field::BigInteger:
+ return text().toLongLong();
+ case KexiDB::Field::Boolean:
+ //! @todo temporary solution for booleans!
+ return text() == "1" ? QVariant(true,1) : QVariant(false,0);
+ case KexiDB::Field::Date:
+ return dateFormatter()->stringToVariant( text() );
+ case KexiDB::Field::Time:
+ return timeFormatter()->stringToVariant( text() );
+ case KexiDB::Field::DateTime:
+ return stringToDateTime(*dateFormatter(), *timeFormatter(), text());
+ case KexiDB::Field::Float:
+ return text().toFloat();
+ case KexiDB::Field::Double:
+ return text().toDouble();
+ default:
+ return QVariant();
+ }
+//! @todo more data types!
+ return text();
+#endif
+}
+
+void KexiDBLineEdit::slotTextChanged(const QString&)
+{
+ if (!m_slotTextChanged_enabled)
+ return;
+ signalValueChanged();
+}
+
+bool KexiDBLineEdit::valueIsNull()
+{
+ return valueIsEmpty(); //ok??? text().isNull();
+}
+
+bool KexiDBLineEdit::valueIsEmpty()
+{
+ return m_textFormatter.valueIsEmpty( text() );
+#if 0 // moved to KexiTextFormatter
+ if (text().isEmpty())
+ return true;
+
+ if (m_columnInfo) {
+ const KexiDB::Field::Type t = m_columnInfo->field->type();
+ if (t == KexiDB::Field::Date || )
+ return dateFormatter()->isEmpty( text() );
+ else if (t == KexiDB::Field::Time)
+ return timeFormatter()->isEmpty( text() );
+ else if (t == KexiDB::Field::Time)
+ return dateTimeIsEmpty( *dateFormatter(), *timeFormatter(), text() );
+ }
+
+//! @todo
+ return text().isEmpty();
+#endif
+}
+
+bool KexiDBLineEdit::valueIsValid()
+{
+ return m_textFormatter.valueIsValid( text() );
+#if 0 // moved to KexiTextFormatter
+ if (!m_columnInfo)
+ return true;
+//! @todo fix for fields with "required" property = true
+ if (valueIsEmpty()/*ok?*/)
+ return true;
+
+ const KexiDB::Field::Type t = m_columnInfo->field->type();
+ if (t == KexiDB::Field::Date)
+ return dateFormatter()->stringToVariant( text() ).isValid();
+ else if (t == KexiDB::Field::Time)
+ return timeFormatter()->stringToVariant( text() ).isValid();
+ else if (t == KexiDB::Field::DateTime)
+ return dateTimeIsValid( *dateFormatter(), *timeFormatter(), text() );
+
+//! @todo
+ return true;
+#endif
+}
+
+bool KexiDBLineEdit::isReadOnly() const
+{
+ return m_internalReadOnly;
+}
+
+void KexiDBLineEdit::setReadOnly( bool readOnly )
+{
+#ifdef USE_KLineEdit_setReadOnly
+//! @todo reenable as an app aption
+ return KLineEdit::setReadOnly( readOnly );
+#else
+ m_internalReadOnly = readOnly;
+ if (m_internalReadOnly) {
+ m_readWriteValidator = validator();
+ if (!m_readOnlyValidator)
+ m_readOnlyValidator = new KexiDBLineEdit_ReadOnlyValidator(this);
+ setValidator( m_readOnlyValidator );
+ }
+ else {
+ //revert to r/w validator
+ setValidator( m_readWriteValidator );
+ }
+ m_menuExtender.updatePopupMenuActions();
+#endif
+}
+
+QPopupMenu * KexiDBLineEdit::createPopupMenu()
+{
+ QPopupMenu *contextMenu = KLineEdit::createPopupMenu();
+ m_menuExtender.createTitle(contextMenu);
+ return contextMenu;
+}
+
+
+QWidget* KexiDBLineEdit::widget()
+{
+ return this;
+}
+
+bool KexiDBLineEdit::cursorAtStart()
+{
+ return cursorPosition()==0;
+}
+
+bool KexiDBLineEdit::cursorAtEnd()
+{
+ return cursorPosition()==(int)text().length();
+}
+
+void KexiDBLineEdit::clear()
+{
+ if (!m_internalReadOnly)
+ KLineEdit::clear();
+}
+
+
+void KexiDBLineEdit::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
+{
+ KexiFormDataItemInterface::setColumnInfo(cinfo);
+ m_textFormatter.setField( cinfo ? cinfo->field : 0 );
+
+ if (!cinfo)
+ return;
+
+//! @todo handle input mask (via QLineEdit::setInputMask()) using a special KexiDB::FieldInputMask class
+ setValidator( new KexiDB::FieldValidator(*cinfo->field, this) );
+
+#if 0 // moved to KexiTextFormatter
+ if (t==KexiDB::Field::Date) {
+//! @todo use KDateWidget?
+ setInputMask( dateFormatter()->inputMask() );
+ }
+ else if (t==KexiDB::Field::Time) {
+//! @todo use KTimeWidget
+// setInputMask("00:00:00");
+ setInputMask( timeFormatter()->inputMask() );
+ }
+ else if (t==KexiDB::Field::DateTime) {
+ setInputMask(
+ dateTimeInputMask( *dateFormatter(), *timeFormatter() ) );
+ }
+#endif
+ const QString inputMask( m_textFormatter.inputMask() );
+ if (!inputMask.isEmpty())
+ setInputMask( inputMask );
+
+ KexiDBTextWidgetInterface::setColumnInfo(cinfo, this);
+}
+
+/*todo
+void KexiDBLineEdit::paint( QPainter *p )
+{
+ KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), hasFocus() );
+}*/
+
+void KexiDBLineEdit::paintEvent ( QPaintEvent *pe )
+{
+ KLineEdit::paintEvent( pe );
+ QPainter p(this);
+ KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), hasFocus() );
+}
+
+bool KexiDBLineEdit::event( QEvent * e )
+{
+ const bool ret = KLineEdit::event( e );
+ KexiDBTextWidgetInterface::event(e, this, text().isEmpty());
+ if (e->type()==QEvent::FocusOut) {
+ QFocusEvent *fe = static_cast<QFocusEvent *>(e);
+// if (fe->reason()!=QFocusEvent::ActiveWindow && fe->reason()!=QFocusEvent::Popup) {
+ if (fe->reason()==QFocusEvent::Tab || fe->reason()==QFocusEvent::Backtab) {
+ //display aligned to left after loosing the focus (only if this is tab/backtab event)
+//! @todo add option to set cursor at the beginning
+ setCursorPosition(0); //ok?
+ }
+ }
+ return ret;
+}
+
+bool KexiDBLineEdit::appendStretchRequired(KexiDBAutoField* autoField) const
+{
+ return KexiDBAutoField::Top == autoField->labelPosition();
+}
+
+void KexiDBLineEdit::handleAction(const QString& actionName)
+{
+ if (actionName=="edit_copy") {
+ copy();
+ }
+ else if (actionName=="edit_paste") {
+ paste();
+ }
+ else if (actionName=="edit_cut") {
+ cut();
+ }
+ //! @todo ?
+}
+
+void KexiDBLineEdit::setDisplayDefaultValue(QWidget *widget, bool displayDefaultValue)
+{
+ KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue);
+ // initialize display parameters for default / entered value
+ KexiDisplayUtils::DisplayParameters * const params
+ = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue;
+ setFont(params->font);
+ QPalette pal(palette());
+ pal.setColor(QPalette::Active, QColorGroup::Text, params->textColor);
+ setPalette(pal);
+}
+
+void KexiDBLineEdit::undo()
+{
+ cancelEditor();
+}
+
+void KexiDBLineEdit::moveCursorToEnd()
+{
+ KLineEdit::end(false/*!mark*/);
+}
+
+void KexiDBLineEdit::moveCursorToStart()
+{
+ KLineEdit::home(false/*!mark*/);
+}
+
+void KexiDBLineEdit::selectAll()
+{
+ KLineEdit::selectAll();
+}
+
+bool KexiDBLineEdit::keyPressed(QKeyEvent *ke)
+{
+ Q_UNUSED(ke);
+ return false;
+}
+
+#include "kexidblineedit.moc"
diff --git a/kexi/plugins/forms/widgets/kexidblineedit.h b/kexi/plugins/forms/widgets/kexidblineedit.h
new file mode 100644
index 000000000..5f0262b2d
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidblineedit.h
@@ -0,0 +1,170 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBLineEdit_H
+#define KexiDBLineEdit_H
+
+#include <klineedit.h>
+#include <qvalidator.h>
+
+#include "kexiformdataiteminterface.h"
+#include "kexidbtextwidgetinterface.h"
+#include "kexidbutils.h"
+#include <widget/tableview/kexitextformatter.h>
+#include <widget/utils/kexidatetimeformatter.h>
+
+class KexiDBWidgetContextMenuExtender;
+
+/*! @internal Utility: alter background color to be a blended color
+ of the background and base (usually lighter gray). Used for read-only mode. */
+void setLighterGrayBackgroundColor(QWidget* widget);
+
+//! @short Line edit widget for Kexi forms
+/*! Handles many data types. User input is validated by using validators
+ and/or input masks.
+*/
+class KEXIFORMUTILS_EXPORT KexiDBLineEdit :
+ public KLineEdit,
+ protected KexiDBTextWidgetInterface,
+ public KexiFormDataItemInterface,
+ public KexiSubwidgetInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ Q_OVERRIDE(bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true)
+
+ public:
+ KexiDBLineEdit(QWidget *parent, const char *name=0);
+ virtual ~KexiDBLineEdit();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual QVariant value();
+ virtual void setInvalidState( const QString& displayText );
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsEmpty();
+
+ /*! \return true if the value is valid */
+ virtual bool valueIsValid();
+
+ /*! \return 'readOnly' flag for this widget. */
+ virtual bool isReadOnly() const;
+
+ /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue()
+ is displayed in a special way. Used by KexiFormDataProvider::fillDataItems().
+ \a widget is equal to 'this'.
+ Reimplemented after KexiFormDataItemInterface. */
+ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue);
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo);
+
+ /*! Handles action having standard name \a actionName.
+ Action could be: "edit_copy", "edit_paste", etc.
+ Reimplemented after KexiDataItemChangesListener. */
+ virtual void handleAction(const QString& actionName);
+
+ /*! Called by top-level form on key press event to consume widget-specific shortcuts. */
+ virtual bool keyPressed(QKeyEvent *ke);
+
+ public slots:
+ inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); }
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ virtual void setReadOnly( bool readOnly );
+
+ //! Reimplemented, so "undo" means the same as "cancelEditor" action
+ virtual void undo();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToEnd();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToStart();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void selectAll();
+
+ protected slots:
+ void slotTextChanged(const QString&);
+
+ protected:
+ virtual void paintEvent ( QPaintEvent * );
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+ virtual bool event ( QEvent * );
+
+#if 0
+//moved to KexiTextFormatter
+ inline KexiDateFormatter* dateFormatter() {
+ return m_dateFormatter ? m_dateFormatter : m_dateFormatter = new KexiDateFormatter();
+ }
+
+ inline KexiTimeFormatter* timeFormatter() {
+ return m_timeFormatter ? m_timeFormatter : m_timeFormatter = new KexiTimeFormatter();
+ }
+#endif
+
+ virtual QPopupMenu * createPopupMenu();
+
+ //! Implemented for KexiSubwidgetInterface
+ virtual bool appendStretchRequired(KexiDBAutoField* autoField) const;
+
+#if 0
+//moved to KexiTextFormatter
+ //! Used for date and date/time types
+ KexiDateFormatter* m_dateFormatter;
+ //! Used for time and date/time types
+ KexiTimeFormatter* m_timeFormatter;
+#endif
+ //! Used to format text
+ KexiTextFormatter m_textFormatter;
+
+ //! Used for read only flag to disable editing
+ QGuardedPtr<const QValidator> m_readOnlyValidator;
+
+ //! Used to remember the previous validator used forf r/w mode, after setting the read only flag
+ QGuardedPtr<const QValidator> m_readWriteValidator;
+
+ //! Used for extending context menu
+ KexiDBWidgetContextMenuExtender m_menuExtender;
+
+ //! Used in isReadOnly, as sometimes we want to have the flag set tot true when KLineEdit::isReadOnly
+ //! is still false.
+ bool m_internalReadOnly : 1;
+
+ //! Used in slotTextChanged()
+ bool m_slotTextChanged_enabled : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbsubform.cpp b/kexi/plugins/forms/widgets/kexidbsubform.cpp
new file mode 100644
index 000000000..8d1971a97
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbsubform.cpp
@@ -0,0 +1,131 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbsubform.h"
+
+#include "kexidbform.h"
+#include "../kexiformview.h"
+#include <kexidb/utils.h>
+#include <formeditor/formIO.h>
+#include <formeditor/objecttree.h>
+#include <formeditor/utils.h>
+#include <formeditor/container.h>
+#include <formeditor/formmanager.h>
+
+KexiDBSubForm::KexiDBSubForm(KFormDesigner::Form *parentForm, QWidget *parent, const char *name)
+: QScrollView(parent, name), m_parentForm(parentForm), m_form(0), m_widget(0)
+{
+ setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
+ viewport()->setPaletteBackgroundColor(colorGroup().mid());
+}
+/*
+void
+KexiDBSubForm::paintEvent(QPaintEvent *ev)
+{
+ QScrollView::paintEvent(ev);
+ QPainter p;
+
+ setWFlags(WPaintUnclipped);
+
+ QString txt("Subform");
+ QFont f = font();
+ f.setPointSize(f.pointSize() * 3);
+ QFontMetrics fm(f);
+ const int txtw = fm.width(txt), txth = fm.height();
+
+ p.begin(this, true);
+ p.setPen(black);
+ p.setFont(f);
+ p.drawText(width()/2, height()/2, txt, Qt::AlignCenter|Qt::AlignVCenter);
+ p.end();
+
+ clearWFlags( WPaintUnclipped );
+}
+*/
+void
+KexiDBSubForm::setFormName(const QString &name)
+{
+ if(m_formName==name)
+ return;
+
+ m_formName = name; //assign, even if the name points to nowhere
+
+ if(name.isEmpty()) {
+ delete m_widget;
+ m_widget = 0;
+ updateScrollBars();
+ return;
+ }
+
+ QWidget *pw = parentWidget();
+ KexiFormView *view = 0;
+ QStringList list;
+ while(pw) {
+ if(pw->isA("KexiDBSubForm")) {
+ if(list.contains(pw->name())) {
+//! @todo error message
+ return; // Be sure to don't run into a endless-loop cause of recursive subforms.
+ }
+ list.append(pw->name());
+ }
+ else if(! view && pw->isA("KexiFormView"))
+ view = static_cast<KexiFormView*>(pw); // we need a KexiFormView*
+ pw = pw->parentWidget();
+ }
+
+ if (!view || !view->parentDialog() || !view->parentDialog()->mainWin()
+ || !view->parentDialog()->mainWin()->project()->dbConnection())
+ return;
+
+ KexiDB::Connection *conn = view->parentDialog()->mainWin()->project()->dbConnection();
+
+ // we check if there is a form with this name
+ int id = KexiDB::idForObjectName(*conn, name, KexiPart::FormObjectType);
+ if((id == 0) || (id == view->parentDialog()->id())) // == our form
+ return; // because of recursion when loading
+
+ // we create the container widget
+ delete m_widget;
+ m_widget = new KexiDBFormBase(viewport(), "KexiDBSubForm_widget");
+ m_widget->show();
+ addChild(m_widget);
+ m_form = new KFormDesigner::Form(KexiFormPart::library(), this->name());
+ m_form->createToplevel(m_widget);
+
+ // and load the sub form
+ QString data;
+ tristate res = conn->loadDataBlock(id, data, QString::null);
+ if (res == true)
+ res = KFormDesigner::FormIO::loadFormFromString(m_form, m_widget, data);
+ if(res != true) {
+ delete m_widget;
+ m_widget = 0;
+ updateScrollBars();
+ m_formName = QString::null;
+ return;
+ }
+ m_form->setDesignMode(false);
+
+ // Install event filters on the whole newly created form
+ KFormDesigner::ObjectTreeItem *tree = m_parentForm->objectTree()->lookup(QObject::name());
+ KFormDesigner::installRecursiveEventFilter(this, tree->eventEater());
+}
+
+#include "kexidbsubform.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbsubform.h b/kexi/plugins/forms/widgets/kexidbsubform.h
new file mode 100644
index 000000000..5b73f8602
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbsubform.h
@@ -0,0 +1,52 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBSubForm_H
+#define KexiDBSubForm_H
+
+#include <qscrollview.h>
+#include <formeditor/form.h>
+
+//! @short A form embedded as a widget inside other form
+class KEXIFORMUTILS_EXPORT KexiDBSubForm : public QScrollView
+{
+ Q_OBJECT
+ Q_PROPERTY(QString formName READ formName WRITE setFormName DESIGNABLE true)
+
+ public:
+ KexiDBSubForm(KFormDesigner::Form *parentForm, QWidget *parent, const char *name);
+ ~KexiDBSubForm() {}
+
+ //! \return the name of the subform to display inside this widget
+ QString formName() const { return m_formName; }
+
+ //! Sets the name of the subform to display inside this widget
+ void setFormName(const QString &name);
+
+ //void paintEvent(QPaintEvent *ev);
+
+ private:
+ KFormDesigner::Form *m_parentForm;
+ KFormDesigner::Form *m_form;
+ QWidget *m_widget;
+ QString m_formName;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbtextedit.cpp b/kexi/plugins/forms/widgets/kexidbtextedit.cpp
new file mode 100644
index 000000000..8541fc01e
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbtextedit.cpp
@@ -0,0 +1,209 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbtextedit.h"
+#include "kexidblineedit.h"
+#include <kexidb/queryschema.h>
+
+#include <kapplication.h>
+#include <kstdaccel.h>
+#include <kdebug.h>
+
+#include <qpainter.h>
+
+KexiDBTextEdit::KexiDBTextEdit(QWidget *parent, const char *name)
+ : KTextEdit(parent, name)
+ , KexiDBTextWidgetInterface()
+ , KexiFormDataItemInterface()
+ , m_menuExtender(this, this)
+ , m_slotTextChanged_enabled(true)
+{
+ connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
+ installEventFilter(this);
+}
+
+KexiDBTextEdit::~KexiDBTextEdit()
+{
+}
+
+void KexiDBTextEdit::setInvalidState( const QString& displayText )
+{
+ setReadOnly(true);
+//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ?
+ if (focusPolicy() & TabFocus)
+ setFocusPolicy(QWidget::ClickFocus);
+ KTextEdit::setText(displayText);
+}
+
+void KexiDBTextEdit::setValueInternal(const QVariant& add, bool removeOld)
+{
+ if (m_columnInfo && m_columnInfo->field->type()==KexiDB::Field::Boolean) {
+//! @todo temporary solution for booleans!
+ KTextEdit::setText( add.toBool() ? "1" : "0" );
+ }
+ else {
+ if (removeOld)
+ KTextEdit::setText( add.toString() );
+ else
+ KTextEdit::setText( m_origValue.toString() + add.toString() );
+ }
+}
+
+QVariant KexiDBTextEdit::value()
+{
+ return text();
+}
+
+void KexiDBTextEdit::slotTextChanged()
+{
+ if (!m_slotTextChanged_enabled)
+ return;
+ signalValueChanged();
+}
+
+bool KexiDBTextEdit::valueIsNull()
+{
+ return text().isNull();
+}
+
+bool KexiDBTextEdit::valueIsEmpty()
+{
+ return text().isEmpty();
+}
+
+bool KexiDBTextEdit::isReadOnly() const
+{
+ return KTextEdit::isReadOnly();
+}
+
+void KexiDBTextEdit::setReadOnly( bool readOnly )
+{
+ KTextEdit::setReadOnly( readOnly );
+ QPalette p = palette();
+ QColor c(readOnly ? lighterGrayBackgroundColor(kapp->palette()) : p.color(QPalette::Normal, QColorGroup::Base));
+ setPaper( c );
+ p.setColor(QColorGroup::Base, c);
+ p.setColor(QColorGroup::Background, c);
+ setPalette( p );
+}
+
+void KexiDBTextEdit::setText( const QString & text, const QString & context )
+{
+ KTextEdit::setText(text, context);
+}
+
+QWidget* KexiDBTextEdit::widget()
+{
+ return this;
+}
+
+bool KexiDBTextEdit::cursorAtStart()
+{
+ int para, index;
+ getCursorPosition ( &para, &index );
+ return para==0 && index==0;
+}
+
+bool KexiDBTextEdit::cursorAtEnd()
+{
+ int para, index;
+ getCursorPosition ( &para, &index );
+ return (paragraphs()-1)==para && (paragraphLength(paragraphs()-1)-1)==index;
+}
+
+void KexiDBTextEdit::clear()
+{
+ setText(QString::null, QString::null);
+}
+
+void KexiDBTextEdit::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
+{
+ KexiFormDataItemInterface::setColumnInfo(cinfo);
+ if (!cinfo)
+ return;
+ KexiDBTextWidgetInterface::setColumnInfo(m_columnInfo, this);
+}
+
+void KexiDBTextEdit::paintEvent ( QPaintEvent *pe )
+{
+ KTextEdit::paintEvent( pe );
+ QPainter p(this);
+ KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), hasFocus() );
+}
+
+QPopupMenu * KexiDBTextEdit::createPopupMenu(const QPoint & pos)
+{
+ QPopupMenu *contextMenu = KTextEdit::createPopupMenu(pos);
+ m_menuExtender.createTitle(contextMenu);
+ return contextMenu;
+}
+
+void KexiDBTextEdit::undo()
+{
+ cancelEditor();
+}
+
+void KexiDBTextEdit::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue)
+{
+ KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue);
+ // initialize display parameters for default / entered value
+ KexiDisplayUtils::DisplayParameters * const params
+ = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue;
+ QPalette pal(palette());
+ pal.setColor(QPalette::Active, QColorGroup::Text, params->textColor);
+ setPalette(pal);
+ setFont(params->font);
+//! @todo support rich text...
+/* m_slotTextChanged_enabled = false;
+ //for rich text...
+ const QString origText( text() );
+ KTextEdit::setText(QString::null);
+ setCurrentFont(params->font);
+ setColor(params->textColor);
+ KTextEdit::setText(origText);
+ m_slotTextChanged_enabled = true;*/
+}
+
+void KexiDBTextEdit::moveCursorToEnd()
+{
+ KTextEdit::setCursorPosition(paragraphs()-1, paragraphLength( paragraphs()-1 ));
+}
+
+void KexiDBTextEdit::moveCursorToStart()
+{
+ KTextEdit::setCursorPosition(0 /*para*/, 0 /*index*/);
+}
+
+void KexiDBTextEdit::selectAll()
+{
+ KTextEdit::selectAll();
+}
+
+void KexiDBTextEdit::keyPressEvent( QKeyEvent *ke )
+{
+ // for instance, Windows uses Ctrl+Tab for moving between tabs, so do not steal this shortcut
+ if (KStdAccel::tabNext().contains( KKey(ke) ) || KStdAccel::tabPrev().contains( KKey(ke) )) {
+ ke->ignore();
+ return;
+ }
+ KTextEdit::keyPressEvent(ke);
+}
+
+#include "kexidbtextedit.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbtextedit.h b/kexi/plugins/forms/widgets/kexidbtextedit.h
new file mode 100644
index 000000000..a380b070a
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbtextedit.h
@@ -0,0 +1,113 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBTextEdit_H
+#define KexiDBTextEdit_H
+
+#include "kexiformdataiteminterface.h"
+#include "kexidbtextwidgetinterface.h"
+#include "kexidbutils.h"
+#include <ktextedit.h>
+
+//! @short Multiline edit widget for Kexi forms
+class KEXIFORMUTILS_EXPORT KexiDBTextEdit :
+ public KTextEdit,
+ protected KexiDBTextWidgetInterface,
+ public KexiFormDataItemInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+
+ public:
+ KexiDBTextEdit(QWidget *parent, const char *name=0);
+ virtual ~KexiDBTextEdit();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual QVariant value();
+ virtual void setInvalidState( const QString& displayText );
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsEmpty();
+
+ /*! \return 'readOnly' flag for this widget. */
+ virtual bool isReadOnly() const;
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo);
+
+ /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue()
+ is displayed in a special way. Used by KexiFormDataProvider::fillDataItems().
+ \a widget is equal to 'this'.
+ Reimplemented after KexiFormDataItemInterface. */
+ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue);
+
+ //! Windows uses Ctrl+Tab for moving between tabs, so do not steal this shortcut
+ virtual void keyPressEvent( QKeyEvent *ke );
+
+ public slots:
+ inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); }
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ virtual void setReadOnly( bool readOnly );
+ virtual void setText( const QString & text, const QString & context );
+
+ //! Reimplemented, so "undo" means the same as "cancelEditor" action
+//! @todo enable "real" undo internally so user can use ctrl+z while editing
+ virtual void undo();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToEnd();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToStart();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void selectAll();
+
+ protected slots:
+ void slotTextChanged();
+
+ protected:
+ virtual void paintEvent ( QPaintEvent * );
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+ QPopupMenu * createPopupMenu(const QPoint & pos);
+
+ //! Used for extending context menu
+ KexiDBWidgetContextMenuExtender m_menuExtender;
+
+ //! Used to disable slotTextChanged()
+ bool m_slotTextChanged_enabled : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbtimeedit.cpp b/kexi/plugins/forms/widgets/kexidbtimeedit.cpp
new file mode 100644
index 000000000..82e61b83b
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbtimeedit.cpp
@@ -0,0 +1,156 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbtimeedit.h"
+
+#include <qtoolbutton.h>
+#include <qlayout.h>
+#include <qpainter.h>
+
+#include <kpopupmenu.h>
+#include <kdatepicker.h>
+#include <kdatetbl.h>
+#include <kexiutils/utils.h>
+
+KexiDBTimeEdit::KexiDBTimeEdit(const QTime &time, QWidget *parent, const char *name)
+ : QTimeEdit(time, parent, name), KexiFormDataItemInterface()
+{
+ m_invalidState = false;
+ setAutoAdvance(true);
+ m_cleared = false;
+
+#ifdef QDateTimeEditor_HACK
+ m_dte_time = KexiUtils::findFirstChild<QDateTimeEditor>(this, "QDateTimeEditor");
+#else
+ m_dte_time = 0;
+#endif
+
+ connect(this, SIGNAL(valueChanged(const QTime&)), this, SLOT(slotValueChanged(const QTime&)));
+}
+
+KexiDBTimeEdit::~KexiDBTimeEdit()
+{
+}
+
+void KexiDBTimeEdit::setInvalidState( const QString&)
+{
+ setEnabled(false);
+ setReadOnly(true);
+ m_invalidState = true;
+//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ?
+ if (focusPolicy() & TabFocus)
+ setFocusPolicy(QWidget::ClickFocus);
+}
+
+void
+KexiDBTimeEdit::setEnabled(bool enabled)
+{
+ // prevent the user from reenabling the widget when it is in invalid state
+ if(enabled && m_invalidState)
+ return;
+ QTimeEdit::setEnabled(enabled);
+}
+
+void KexiDBTimeEdit::setValueInternal(const QVariant &add, bool removeOld)
+{
+ m_cleared = !m_origValue.isValid();
+
+ int setNumberOnFocus = -1;
+ QTime t;
+ QString addString(add.toString());
+ if (removeOld) {
+ if (!addString.isEmpty() && addString[0].latin1()>='0' && addString[0].latin1() <='9') {
+ setNumberOnFocus = addString[0].latin1()-'0';
+ t = QTime(setNumberOnFocus, 0, 0);
+ }
+ }
+ else
+ t = m_origValue.toTime();
+
+ setTime(t);
+}
+
+QVariant
+KexiDBTimeEdit::value()
+{
+ //QDateTime - a hack needed because QVariant(QTime) has broken isNull()
+ return QVariant(QDateTime( m_cleared ? QDate() : QDate(0,1,2)/*nevermind*/, time()));
+}
+
+bool KexiDBTimeEdit::valueIsNull()
+{
+ return !time().isValid() || time().isNull();
+}
+
+bool KexiDBTimeEdit::valueIsEmpty()
+{
+ return m_cleared;
+}
+
+bool KexiDBTimeEdit::isReadOnly() const
+{
+ //! @todo: data/time edit API has no readonly flag,
+ //! so use event filter to avoid changes made by keyboard or mouse when m_readOnly==true
+ return m_readOnly; //!isEnabled();
+}
+
+void KexiDBTimeEdit::setReadOnly(bool set)
+{
+ m_readOnly = set;
+}
+
+QWidget*
+KexiDBTimeEdit::widget()
+{
+ return this;
+}
+
+bool KexiDBTimeEdit::cursorAtStart()
+{
+#ifdef QDateTimeEditor_HACK
+ return m_dte_time && hasFocus() && m_dte_time->focusSection()==0;
+#else
+ return false;
+#endif
+}
+
+bool KexiDBTimeEdit::cursorAtEnd()
+{
+#ifdef QDateTimeEditor_HACK
+ return m_dte_time && hasFocus()
+ && m_dte_time->focusSection()==int(m_dte_time->sectionCount()-1);
+#else
+ return false;
+#endif
+}
+
+void KexiDBTimeEdit::clear()
+{
+ setTime(QTime());
+ m_cleared = true;
+}
+
+void
+KexiDBTimeEdit::slotValueChanged(const QTime&)
+{
+ m_cleared = false;
+}
+
+#include "kexidbtimeedit.moc"
diff --git a/kexi/plugins/forms/widgets/kexidbtimeedit.h b/kexi/plugins/forms/widgets/kexidbtimeedit.h
new file mode 100644
index 000000000..9665b1f9e
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbtimeedit.h
@@ -0,0 +1,87 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDBTimeEdit_H
+#define KexiDBTimeEdit_H
+
+#include "kexiformdataiteminterface.h"
+#include "kexidbtextwidgetinterface.h"
+#include <qdatetimeedit.h>
+
+class QDateTimeEditor;
+
+//! @short A db-aware time editor
+class KEXIFORMUTILS_EXPORT KexiDBTimeEdit : public QTimeEdit, public KexiFormDataItemInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true)
+ Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true)
+ Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true )
+
+ public:
+ KexiDBTimeEdit(const QTime &time, QWidget *parent, const char *name=0);
+ virtual ~KexiDBTimeEdit();
+
+ inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); }
+ inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); }
+ virtual QVariant value();
+ virtual void setInvalidState( const QString& displayText );
+
+ //! \return true if editor's value is null (not empty)
+ //! Used for checking if a given constraint within table of form is met.
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not necessary null).
+ //! Only few data types can accept "EMPTY" property
+ //! (use KexiDB::Field::hasEmptyProperty() to check this).
+ //! Used for checking if a given constraint within table or form is met.
+ virtual bool valueIsEmpty();
+
+ /*! \return 'readOnly' flag for this widget. */
+ virtual bool isReadOnly() const;
+
+ /*! \return the view widget of this item, e.g. line edit widget. */
+ virtual QWidget* widget();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+ virtual void clear();
+
+ virtual void setEnabled(bool enabled);
+
+ public slots:
+ inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); }
+ inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); }
+ virtual void setReadOnly(bool set);
+
+ protected slots:
+ void slotValueChanged(const QTime&);
+
+ protected:
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ private:
+ QDateTimeEditor* m_dte_time;
+ bool m_invalidState : 1;
+ bool m_cleared : 1;
+ bool m_readOnly : 1;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexidbutils.cpp b/kexi/plugins/forms/widgets/kexidbutils.cpp
new file mode 100644
index 000000000..0c08d64c8
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbutils.cpp
@@ -0,0 +1,99 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbutils.h"
+
+#include <kpopupmenu.h>
+#include <kiconloader.h>
+
+#include <kexidb/queryschema.h>
+#include <kexidb/utils.h>
+#include <formeditor/widgetlibrary.h>
+#include <kexiutils/utils.h>
+#include "../kexiformpart.h"
+#include <widget/utils/kexicontextmenuutils.h>
+
+
+QColor lighterGrayBackgroundColor(const QPalette& palette)
+{
+ return KexiUtils::blendedColors(palette.active().background(), palette.active().base(), 1, 2);
+}
+
+//-------
+
+KexiDBWidgetContextMenuExtender::KexiDBWidgetContextMenuExtender( QObject* parent, KexiDataItemInterface* iface )
+ : QObject(parent)
+ , m_iface(iface)
+ , m_contextMenuHasTitle(false)
+{
+}
+
+KexiDBWidgetContextMenuExtender::~KexiDBWidgetContextMenuExtender()
+{
+}
+
+void KexiDBWidgetContextMenuExtender::createTitle(QPopupMenu *menu)
+{
+ if (!menu)
+ return;
+ m_contextMenu = menu;
+ KPopupTitle *titleItem = new KPopupTitle();
+ const int id = m_contextMenu->insertItem(titleItem, -1, 0);
+ m_contextMenu->setItemEnabled(id, false);
+ QString icon;
+ if (dynamic_cast<QWidget*>(m_iface))
+ icon = KexiFormPart::library()->iconName(dynamic_cast<QWidget*>(m_iface)->className());
+
+ m_contextMenuHasTitle = m_iface->columnInfo() ?
+ KexiContextMenuUtils::updateTitle(m_contextMenu,
+ m_iface->columnInfo()->captionOrAliasOrName(),
+ KexiDB::simplifiedTypeName(*m_iface->columnInfo()->field), icon)
+ : false;
+
+ if (!m_contextMenuHasTitle)
+ m_contextMenu->removeItem(id);
+ updatePopupMenuActions();
+}
+
+void KexiDBWidgetContextMenuExtender::updatePopupMenuActions()
+{
+ if (m_contextMenu) {
+ enum { IdUndo, IdRedo, IdSep1, IdCut, IdCopy, IdPaste, IdClear, IdSep2, IdSelectAll }; //from qlineedit.h
+ const bool readOnly = m_iface->isReadOnly();
+ const int id = m_contextMenu->idAt(m_contextMenuHasTitle ? 1 : 0);
+
+//! @todo maybe redo will be enabled one day?
+ m_contextMenu->removeItem(id-(int)IdRedo);
+
+ // update cut/copy/paste
+ m_contextMenu->setItemEnabled(id-(int)IdCut, !readOnly);
+ m_contextMenu->setItemEnabled(id-(int)IdPaste, !readOnly);
+ m_contextMenu->setItemEnabled(id-(int)IdClear, !readOnly);
+ }
+}
+
+//------------------
+
+KexiSubwidgetInterface::KexiSubwidgetInterface()
+{
+}
+
+KexiSubwidgetInterface::~KexiSubwidgetInterface()
+{
+}
diff --git a/kexi/plugins/forms/widgets/kexidbutils.h b/kexi/plugins/forms/widgets/kexidbutils.h
new file mode 100644
index 000000000..386f1ee5b
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexidbutils.h
@@ -0,0 +1,71 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KDBWIDGETS_UTILS_H
+#define KDBWIDGETS_UTILS_H
+
+#include <qpopupmenu.h>
+#include <kexidataiteminterface.h>
+
+QColor lighterGrayBackgroundColor(const QPalette& palette);
+
+//! @short Used for extending editor widgets' context menu.
+/*! @internal This is performed by adding a title and disabling editing
+ actions when "read only" flag is true. */
+class KexiDBWidgetContextMenuExtender : public QObject
+{
+ public:
+ KexiDBWidgetContextMenuExtender( QObject* parent, KexiDataItemInterface* iface );
+ ~KexiDBWidgetContextMenuExtender();
+
+ //! Creates title for context menu \a menu
+ void createTitle(QPopupMenu *menu);
+
+ //! Enables or disables context menu actions that can modify the value.
+ //! The menu has to be previously provided by createTitle().
+ void updatePopupMenuActions();
+
+ /*! Updates title for context menu based on data item \a iface caption or name
+ Used in createTitle(QPopupMenu *menu) and KexiDBImageBox.
+ \return true is the title has been added. */
+ static bool updateContextMenuTitleForDataItem(QPopupMenu *menu, KexiDataItemInterface* iface,
+ const QString& icon = QString::null);
+
+ protected:
+ KexiDataItemInterface* m_iface;
+ QGuardedPtr<QPopupMenu> m_contextMenu;
+ bool m_contextMenuHasTitle; //!< true if KPopupTitle has been added to the context menu.
+};
+
+class KexiDBAutoField;
+
+//! An interface allowing to define custom behaviour for subwidget of the KexiDBAutoField
+class KexiSubwidgetInterface
+{
+ public:
+ KexiSubwidgetInterface();
+ virtual ~KexiSubwidgetInterface();
+
+ virtual bool appendStretchRequired(KexiDBAutoField* autoField) const
+ { Q_UNUSED(autoField); return false; }
+ virtual bool subwidgetStretchRequired(KexiDBAutoField* autoField) const
+ { Q_UNUSED(autoField); return false; }
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexiframe.cpp b/kexi/plugins/forms/widgets/kexiframe.cpp
new file mode 100644
index 000000000..b49386dab
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexiframe.cpp
@@ -0,0 +1,77 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiframe.h"
+
+#include <qpainter.h>
+#include <qdrawutil.h>
+#include <kexiutils/utils.h>
+
+//! @internal
+class KexiFrame::Private
+{
+ public:
+ Private()
+ {
+ }
+ ~Private()
+ {
+ }
+ QColor frameColor;
+#if 0
+//todo
+ KexiFrame::Shape frameShape;
+ KexiFrame::Shadow frameShadow;
+#endif
+};
+
+//=========================================================
+
+KexiFrame::KexiFrame( QWidget * parent, const char * name, WFlags f )
+ : QFrame(parent, name, f)
+ , d( new Private() )
+{
+ //defaults
+ d->frameColor = palette().active().foreground();
+//! @todo obtain these defaults from current template's style...
+ setLineWidth(2);
+ setFrameStyle(QFrame::StyledPanel|QFrame::Raised);
+}
+
+KexiFrame::~KexiFrame()
+{
+ delete d;
+}
+
+void KexiFrame::dragMoveEvent( QDragMoveEvent *e )
+{
+ QFrame::dragMoveEvent(e);
+ emit handleDragMoveEvent(e);
+}
+
+void KexiFrame::dropEvent( QDropEvent *e )
+{
+ QFrame::dropEvent(e);
+ emit handleDropEvent(e);
+}
+
+#define ClassName KexiFrame
+#define SuperClassName QFrame
+#include "kexiframeutils_p.cpp"
+#include "kexiframe.moc"
diff --git a/kexi/plugins/forms/widgets/kexiframe.h b/kexi/plugins/forms/widgets/kexiframe.h
new file mode 100644
index 000000000..8d60d5979
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexiframe.h
@@ -0,0 +1,84 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiFrame_H
+#define KexiFrame_H
+
+#include <qframe.h>
+
+//! @short Frame widget for Kexi forms
+class KEXIFORMUTILS_EXPORT KexiFrame : public QFrame
+{
+ Q_OBJECT
+//todo Q_ENUMS( Shape Shadow )
+ Q_PROPERTY( QColor frameColor READ frameColor WRITE setFrameColor DESIGNABLE true )
+//todo Q_OVERRIDE( Shape frameShape READ frameShape WRITE setFrameShape )
+//todo Q_OVERRIDE( Shadow frameShadow READ frameShadow WRITE setFrameShadow )
+
+ public:
+ KexiFrame( QWidget * parent, const char * name = 0, WFlags f = 0 );
+ virtual ~KexiFrame();
+
+ virtual const QColor& frameColor() const;
+
+#if 0
+//! @todo more options
+ enum Shadow {
+ NoShadow = QFrame::Plain,
+ Raised = QFrame::Raised,
+ Sunken = QFrame::Sunken
+ };
+//! @todo more options
+ enum Shape { NoFrame = QFrame::NoFrame, //!< no frame
+ Box = QFrame::Box, //!< rectangular box
+ Panel = QFrame::Panel, //!< rectangular panel
+ StyledPanel = QFrame::StyledPanel, //!< rectangular panel depending on the GUI style
+ GroupBoxPanel = QFrame::GroupBoxPanel //!< rectangular group-box-like panel depending on the GUI style
+ };
+ Shape frameShape() const;
+ void setFrameShape( KexiFrame::Shape shape );
+ Shadow frameShadow() const;
+ void setFrameShadow( KexiFrame::Shadow shadow );
+#endif
+
+ //! Used to emit handleDragMoveEvent() signal needed to control dragging over the container's surface
+ virtual void dragMoveEvent( QDragMoveEvent *e );
+
+ //! Used to emit handleDropEvent() signal needed to control dropping on the container's surface
+ virtual void dropEvent( QDropEvent *e );
+
+ public slots:
+ virtual void setPalette( const QPalette &pal );
+ virtual void setFrameColor(const QColor& color);
+
+ signals:
+ //! Needed to control dragging over the container's surface
+ void handleDragMoveEvent(QDragMoveEvent *e);
+
+ //! Needed to control dropping on the container's surface
+ void handleDropEvent(QDropEvent *e);
+
+ protected:
+ virtual void drawFrame( QPainter * );
+
+ class Private;
+ Private *d;
+};
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexiframeutils_p.cpp b/kexi/plugins/forms/widgets/kexiframeutils_p.cpp
new file mode 100644
index 000000000..11b8650a4
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexiframeutils_p.cpp
@@ -0,0 +1,232 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+/* This file is included by KexiDBLabel and KexiFrame */
+
+//! @todo add more frame types
+void ClassName::drawFrame( QPainter *p )
+{
+ if (frameShape() == QFrame::Box) {
+ if ( frameShadow() == Plain )
+ qDrawPlainRect( p, frameRect(), d->frameColor, lineWidth() );
+ else
+ qDrawShadeRect( p, frameRect(), colorGroup(), frameShadow() == QFrame::Sunken,
+ lineWidth(), midLineWidth() );
+ }
+ else {
+ SuperClassName::drawFrame(p);
+ }
+}
+
+void ClassName::setPalette( const QPalette &pal )
+{
+ QPalette pal2(pal);
+ QColorGroup cg( pal2.active() );
+ cg.setColor(QColorGroup::Light, KexiUtils::bleachedColor( d->frameColor, 150 ));
+ cg.setColor(QColorGroup::Mid, d->frameColor);
+ cg.setColor(QColorGroup::Dark, d->frameColor.dark(150));
+ pal2.setActive(cg);
+ QColorGroup cg2( pal2.inactive() );
+ cg2.setColor(QColorGroup::Light, cg.light() );
+ cg2.setColor(QColorGroup::Mid, cg.mid());
+ cg2.setColor(QColorGroup::Dark, cg.dark());
+ pal2.setInactive(cg2);
+ SuperClassName::setPalette(pal2);
+}
+
+const QColor& ClassName::frameColor() const
+{
+ return d->frameColor;
+}
+
+void ClassName::setFrameColor(const QColor& color)
+{
+ d->frameColor = color;
+ //update light and dark colors
+ setPalette( palette() );
+}
+
+#if 0
+//todo
+ClassName::Shape ClassName::frameShape() const
+{
+ return d->frameShape;
+}
+
+void ClassName::setFrameShape( ClassName::Shape shape )
+{
+ d->frameShape = shape;
+ update();
+}
+
+ClassName::Shadow ClassName::frameShadow() const
+{
+ return d->frameShadow;
+}
+
+void ClassName::setFrameShadow( ClassName::Shadow shadow )
+{
+ d->frameShadow = shadow;
+ update();
+}
+#endif
+
+#if 0
+void QFrame::drawFrame( QPainter *p )
+{
+ QPoint p1, p2;
+ QRect r = frameRect();
+ int type = fstyle & MShape;
+ int cstyle = fstyle & MShadow;
+#ifdef QT_NO_DRAWUTIL
+ p->setPen( black ); // ####
+ p->drawRect( r ); //### a bit too simple
+#else
+ const QColorGroup & g = colorGroup();
+
+#ifndef QT_NO_STYLE
+ QStyleOption opt(lineWidth(),midLineWidth());
+
+ QStyle::SFlags flags = QStyle::Style_Default;
+ if (isEnabled())
+ flags |= QStyle::Style_Enabled;
+ if (cstyle == Sunken)
+ flags |= QStyle::Style_Sunken;
+ else if (cstyle == Raised)
+ flags |= QStyle::Style_Raised;
+ if (hasFocus())
+ flags |= QStyle::Style_HasFocus;
+ if (hasMouse())
+ flags |= QStyle::Style_MouseOver;
+#endif // QT_NO_STYLE
+
+ switch ( type ) {
+
+ case Box:
+ if ( cstyle == Plain )
+ qDrawPlainRect( p, r, g.foreground(), lwidth );
+ else
+ qDrawShadeRect( p, r, g, cstyle == Sunken, lwidth,
+ midLineWidth() );
+ break;
+
+ case LineEditPanel:
+ style().drawPrimitive( QStyle::PE_PanelLineEdit, p, r, g, flags, opt );
+ break;
+
+ case GroupBoxPanel:
+ style().drawPrimitive( QStyle::PE_PanelGroupBox, p, r, g, flags, opt );
+ break;
+
+ case TabWidgetPanel:
+ style().drawPrimitive( QStyle::PE_PanelTabWidget, p, r, g, flags, opt );
+ break;
+
+ case MenuBarPanel:
+#ifndef QT_NO_STYLE
+ style().drawPrimitive(QStyle::PE_PanelMenuBar, p, r, g, flags, opt);
+ break;
+#endif // fall through to Panel if QT_NO_STYLE
+
+ case ToolBarPanel:
+#ifndef QT_NO_STYLE
+ style().drawPrimitive( QStyle::PE_PanelDockWindow, p, rect(), g, flags, opt);
+ break;
+#endif // fall through to Panel if QT_NO_STYLE
+
+ case StyledPanel:
+#ifndef QT_NO_STYLE
+ if ( cstyle == Plain )
+ qDrawPlainRect( p, r, g.foreground(), lwidth );
+ else
+ style().drawPrimitive(QStyle::PE_Panel, p, r, g, flags, opt);
+ break;
+#endif // fall through to Panel if QT_NO_STYLE
+
+ case PopupPanel:
+#ifndef QT_NO_STYLE
+ {
+ int vextra = style().pixelMetric(QStyle::PM_PopupMenuFrameVerticalExtra, this),
+ hextra = style().pixelMetric(QStyle::PM_PopupMenuFrameHorizontalExtra, this);
+ if(vextra > 0 || hextra > 0) {
+ QRect fr = frameRect();
+ int fw = frameWidth();
+ if(vextra > 0) {
+ style().drawControl(QStyle::CE_PopupMenuVerticalExtra, p, this,
+ QRect(fr.x() + fw, fr.y() + fw, fr.width() - (fw*2), vextra),
+ g, flags, opt);
+ style().drawControl(QStyle::CE_PopupMenuVerticalExtra, p, this,
+ QRect(fr.x() + fw, fr.bottom() - fw - vextra, fr.width() - (fw*2), vextra),
+ g, flags, opt);
+ }
+ if(hextra > 0) {
+ style().drawControl(QStyle::CE_PopupMenuHorizontalExtra, p, this,
+ QRect(fr.x() + fw, fr.y() + fw + vextra, hextra, fr.height() - (fw*2) - vextra),
+ g, flags, opt);
+ style().drawControl(QStyle::CE_PopupMenuHorizontalExtra, p, this,
+ QRect(fr.right() - fw - hextra, fr.y() + fw + vextra, hextra, fr.height() - (fw*2) - vextra),
+ g, flags, opt);
+ }
+ }
+
+ if ( cstyle == Plain )
+ qDrawPlainRect( p, r, g.foreground(), lwidth );
+ else
+ style().drawPrimitive(QStyle::PE_PanelPopup, p, r, g, flags, opt);
+ break;
+ }
+#endif // fall through to Panel if QT_NO_STYLE
+
+ case Panel:
+ if ( cstyle == Plain )
+ qDrawPlainRect( p, r, g.foreground(), lwidth );
+ else
+ qDrawShadePanel( p, r, g, cstyle == Sunken, lwidth );
+ break;
+
+ case WinPanel:
+ if ( cstyle == Plain )
+ qDrawPlainRect( p, r, g.foreground(), wpwidth );
+ else
+ qDrawWinPanel( p, r, g, cstyle == Sunken );
+ break;
+ case HLine:
+ case VLine:
+ if ( type == HLine ) {
+ p1 = QPoint( r.x(), r.height()/2 );
+ p2 = QPoint( r.x()+r.width(), p1.y() );
+ }
+ else {
+ p1 = QPoint( r.x()+r.width()/2, 0 );
+ p2 = QPoint( p1.x(), r.height() );
+ }
+ if ( cstyle == Plain ) {
+ QPen oldPen = p->pen();
+ p->setPen( QPen(g.foreground(),lwidth) );
+ p->drawLine( p1, p2 );
+ p->setPen( oldPen );
+ }
+ else
+ qDrawShadeLine( p, p1, p2, g, cstyle == Sunken,
+ lwidth, midLineWidth() );
+ break;
+ }
+#endif // QT_NO_DRAWUTIL
+
+#endif
diff --git a/kexi/plugins/forms/widgets/kexipushbutton.cpp b/kexi/plugins/forms/widgets/kexipushbutton.cpp
new file mode 100644
index 000000000..acfda0a4e
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexipushbutton.cpp
@@ -0,0 +1,32 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexipushbutton.h"
+
+KexiPushButton::KexiPushButton( const QString & text, QWidget * parent, const char * name )
+: KPushButton(text, parent, name)
+{
+}
+
+KexiPushButton::~KexiPushButton()
+{
+}
+
+#include "kexipushbutton.moc"
diff --git a/kexi/plugins/forms/widgets/kexipushbutton.h b/kexi/plugins/forms/widgets/kexipushbutton.h
new file mode 100644
index 000000000..12c016316
--- /dev/null
+++ b/kexi/plugins/forms/widgets/kexipushbutton.h
@@ -0,0 +1,55 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiPushButton_H
+#define KexiPushButton_H
+
+#include <kpushbutton.h>
+#include "../kexiformeventhandler.h"
+
+//! @short Push Button widget for Kexi forms
+class KEXIFORMUTILS_EXPORT KexiPushButton : public KPushButton
+{
+ Q_OBJECT
+ Q_PROPERTY(QString onClickAction READ onClickAction WRITE setOnClickAction DESIGNABLE true)
+ Q_PROPERTY(QString onClickActionOption READ onClickActionOption WRITE setOnClickActionOption DESIGNABLE true)
+
+ public:
+ KexiPushButton( const QString & text, QWidget * parent, const char * name = 0 );
+ ~KexiPushButton();
+
+ public slots:
+ //! action string for "on click" event
+ //! @see KexiFormPart::slotAssignAction()
+ //! @see KexiFormEventAction::ActionData
+ QString onClickAction() const { return m_onClickActionData.string; }
+ void setOnClickAction(const QString& actionString) { m_onClickActionData.string = actionString; }
+
+ //! action option allowing to select whether the object should be opened in data view mode or printed, etc.
+ //! @see KexiFormPart::slotAssignAction()
+ //! @see KexiFormEventAction::ActionData
+ QString onClickActionOption() const { return m_onClickActionData.option; }
+ void setOnClickActionOption(const QString& option) { m_onClickActionData.option = option; }
+
+ protected:
+ KexiFormEventAction::ActionData m_onClickActionData;
+};
+
+#endif
diff --git a/kexi/plugins/importexport/Makefile.am b/kexi/plugins/importexport/Makefile.am
new file mode 100644
index 000000000..02d8b733f
--- /dev/null
+++ b/kexi/plugins/importexport/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = csv
diff --git a/kexi/plugins/importexport/csv/Makefile.am b/kexi/plugins/importexport/csv/Makefile.am
new file mode 100644
index 000000000..7ad164954
--- /dev/null
+++ b/kexi/plugins/importexport/csv/Makefile.am
@@ -0,0 +1,21 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_csv_importexport.la
+
+kexihandler_csv_importexport_la_SOURCES = kexicsv_importexportpart.cpp kexicsvimportdialog.cpp \
+ kexicsvimportoptionsdlg.cpp kexicsvwidgets.cpp kexicsvexport.cpp kexicsvexportwizard.cpp
+
+kexihandler_csv_importexport_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module
+kexihandler_csv_importexport_la_LIBADD = ../../../core/libkexicore.la \
+ ../../../migration/libkeximigrate.la
+
+INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/migration \
+ -I$(top_srcdir)/kexi/kexiDB $(all_includes)
+
+METASOURCES = AUTO
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=kexicsv_importexporthandler.desktop
+
+include ../../Makefile.common
diff --git a/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop b/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop
new file mode 100644
index 000000000..1c2a383a0
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop
@@ -0,0 +1,51 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+Name=Kexi CSV Data Import/Export Plugin
+Name[bg]=Приставка за импортиране/експортиране от CSV в Kexi
+Name[ca]=Connector d'importació/Exportació de dades CVS per a Kexi
+Name[da]=Kexi CSV data-import/eksport plugin
+Name[de]=Kexi CSV-Daten Import-/Export-Modul
+Name[el]=Πρόσθετο εισαγωγής/εξαγωγής CSV δεδομένων του Kexi
+Name[eo]=Kexi CSV-datuma import-eksport-kromaĵo
+Name[es]=Complemento de Kexi para importar y exportar datos CSV
+Name[et]=Kexi CSV-andmete impordi/ekspordifilter
+Name[fa]=وصلۀ واردات/صادرات دادۀ Kexi CSV
+Name[fr]=Module d'importation / exportation de données CSV de Kexi
+Name[fy]=Kexi ymport/eksport Plugin foar CSV-gegevens
+Name[gl]=Importación de Datos CSV de Kexi
+Name[he]=תוסף של Kexi ליבוא/יצוא של מידע מסוג CSV
+Name[hu]=Kexi CSV adatimportáló és -exportáló modul
+Name[is]=Kexi CSV gagna inn/útflutnings íforrit
+Name[it]=Importazione ed esportazione di dati CSV di Kexi
+Name[ja]=Kexi CSV データ インポート/エクスポートプラグイン
+Name[km]=កម្មវិធី​ជំនួយ​ក្នុង​ការ​នាំចេញ និង​នាំចូល​ទិន្នន័យ CSV សម្រាប់ Kexi
+Name[lv]=Kexi CSV datu importa/eksporta spraudnis
+Name[nb]=CSV-data import/eksportfilter for Kexi
+Name[nds]=CSV-Datenimport-/exportmoduul för Kexi
+Name[ne]=केक्सी CSV डेटा आयात/निर्यात प्लगइन
+Name[nl]=Kexi import/exportplugin voor CSV-gegevens
+Name[pl]=Wtyczka importu/eksportu danych CSV dla Kexi
+Name[pt]=Importação de Dados CSV do Kexi
+Name[pt_BR]=Plugin de Importação/Exportação de Dados CSV do Kexi
+Name[ru]=Модуль импорта/экспорта CSV (значения через запятую) для Kexi
+Name[se]=Kexi CSV-dáhta sisa-/olggosfievrridan lassemoduvla
+Name[sk]=Modul Kexi pre import a export CSV dát
+Name[sl]=Vstavek za uvoz/izvoz podatkov CVS za Kexi
+Name[sr]=Kexi-јев прикључак за увоз и извоз из CSV-а
+Name[sr@Latn]=Kexi-jev priključak za uvoz i izvoz iz CSV-a
+Name[sv]=Kexi insticksprogram för import/export av CSV-data
+Name[uk]=Втулок імпорту/експорту CSV-даних для Kexi
+Name[uz]=Kexi CSV maʼlumot import/eksport plagini
+Name[uz@cyrillic]=Kexi CSV маълумот импорт/экспорт плагини
+Name[zh_CN]=Kexi CSV 数据导入/导出插件
+Name[zh_TW]=Kexi CSV 資料匯入/匯出外掛程式
+
+X-KDE-Library=kexihandler_csv_importexport
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=csv_importexport
+X-Kexi-GroupIcon=csv_importexport
+X-Kexi-ItemIcon=csv_importexport
+X-Kexi-NoObject=true
diff --git a/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp b/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp
new file mode 100644
index 000000000..caa8640d5
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp
@@ -0,0 +1,87 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicsv_importexportpart.h"
+#include "kexicsvimportdialog.h"
+#include "kexicsvexportwizard.h"
+#include <core/keximainwindow.h>
+#include <core/kexiproject.h>
+#include <kexiutils/utils.h>
+
+#include <kgenericfactory.h>
+
+KexiCSVImportExportPart::KexiCSVImportExportPart(QObject *parent, const char *name, const QStringList &args)
+ : KexiInternalPart(parent, name, args)
+{
+}
+
+KexiCSVImportExportPart::~KexiCSVImportExportPart()
+{
+}
+
+QWidget *KexiCSVImportExportPart::createWidget(const char* widgetClass, KexiMainWindow* mainWin,
+ QWidget *parent, const char *objName, QMap<QString,QString>* args )
+{
+ if (0==qstrcmp(widgetClass, "KexiCSVImportDialog")) {
+ KexiCSVImportDialog::Mode mode = (args && (*args)["sourceType"]=="file")
+ ? KexiCSVImportDialog::File : KexiCSVImportDialog::Clipboard;
+ KexiCSVImportDialog *dlg = new KexiCSVImportDialog( mode, mainWin, parent, objName );
+ m_cancelled = dlg->cancelled();
+ if (m_cancelled) {
+ delete dlg;
+ return 0;
+ }
+ return dlg;
+ }
+ else if (0==qstrcmp(widgetClass, "KexiCSVExportWizard")) {
+ if (!args)
+ return 0;
+ KexiCSVExport::Options options;
+ if (!options.assign( *args ))
+ return 0;
+ KexiCSVExportWizard *dlg = new KexiCSVExportWizard( options, mainWin, parent, objName);
+ m_cancelled = dlg->cancelled();
+ if (m_cancelled) {
+ delete dlg;
+ return 0;
+ }
+ return dlg;
+ }
+ return 0;
+}
+
+bool KexiCSVImportExportPart::executeCommand(KexiMainWindow* mainWin, const char* commandName,
+ QMap<QString,QString>* args)
+{
+ if (0==qstrcmp(commandName, "KexiCSVExport")) {
+ KexiCSVExport::Options options;
+ if (!options.assign( *args ))
+ return false;
+ KexiDB::TableOrQuerySchema tableOrQuery(
+ mainWin->project()->dbConnection(), options.itemId);
+ QTextStream *stream = 0;
+ if (args->contains("textStream"))
+ stream = KexiUtils::stringToPtr<QTextStream>( (*args)["textStream"] );
+ return KexiCSVExport::exportData(tableOrQuery, options, -1, stream);
+ }
+ return false;
+}
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_csv_importexport,
+ KGenericFactory<KexiCSVImportExportPart>("kexihandler_csv_importexport") )
diff --git a/kexi/plugins/importexport/csv/kexicsv_importexportpart.h b/kexi/plugins/importexport/csv/kexicsv_importexportpart.h
new file mode 100644
index 000000000..8ee8e8cd0
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsv_importexportpart.h
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_MIGRATION_PART_H
+#define KEXI_MIGRATION_PART_H
+
+#include <core/kexiinternalpart.h>
+#include "kexicsvexportwizard.h"
+
+/*! Internal part for CSV data import/export dialogs. */
+class KexiCSVImportExportPart : public KexiInternalPart
+{
+ public:
+ KexiCSVImportExportPart(QObject *parent, const char *name, const QStringList &args);
+ virtual ~KexiCSVImportExportPart();
+
+ /*! Reimplemented to return wizard object. */
+ virtual QWidget *createWidget(const char* widgetClass, KexiMainWindow* mainWin,
+ QWidget *parent, const char *objName = 0, QMap<QString,QString>* args = 0);
+
+ /*! Reimplemented to execute a command \a commandName (nonvisual). The result are put into the \a args. */
+ virtual bool executeCommand(KexiMainWindow* mainWin, const char* commandName,
+ QMap<QString,QString>* args = 0);
+
+ protected:
+};
+
+#endif
diff --git a/kexi/plugins/importexport/csv/kexicsvexport.cpp b/kexi/plugins/importexport/csv/kexicsvexport.cpp
new file mode 100644
index 000000000..b83f85a7f
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvexport.cpp
@@ -0,0 +1,271 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicsvexport.h"
+#include "kexicsvwidgets.h"
+#include <main/startup/KexiStartupFileDialog.h>
+#include <kexidb/cursor.h>
+#include <kexidb/utils.h>
+#include <core/keximainwindow.h>
+#include <core/kexiproject.h>
+#include <core/kexipartinfo.h>
+#include <core/kexipartmanager.h>
+#include <core/kexiguimsghandler.h>
+#include <kexiutils/utils.h>
+#include <widget/kexicharencodingcombobox.h>
+
+#include <qcheckbox.h>
+#include <qgroupbox.h>
+#include <qclipboard.h>
+#include <kapplication.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kactivelabel.h>
+#include <kpushbutton.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <ksavefile.h>
+
+
+using namespace KexiCSVExport;
+
+Options::Options()
+ : mode(File), itemId(0), addColumnNames(true)
+{
+}
+
+bool Options::assign( QMap<QString,QString>& args )
+{
+ mode = (args["destinationType"]=="file")
+ ? KexiCSVExport::File : KexiCSVExport::Clipboard;
+
+ if (args.contains("delimiter"))
+ delimiter = args["delimiter"];
+ else
+ delimiter = (mode==File) ? KEXICSV_DEFAULT_FILE_DELIMITER : KEXICSV_DEFAULT_CLIPBOARD_DELIMITER;
+
+ if (args.contains("textQuote"))
+ textQuote = args["textQuote"];
+ else
+ textQuote = (mode==File) ? KEXICSV_DEFAULT_FILE_TEXT_QUOTE : KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE;
+
+ bool ok;
+ itemId = args["itemId"].toInt(&ok);
+ if (!ok || itemId<=0)
+ return false;
+ if (args.contains("forceDelimiter"))
+ forceDelimiter = args["forceDelimiter"];
+ if (args.contains("addColumnNames"))
+ addColumnNames = (args["addColumnNames"]=="1");
+ return true;
+}
+
+//------------------------------------
+
+bool KexiCSVExport::exportData(KexiDB::TableOrQuerySchema& tableOrQuery,
+ const Options& options, int rowCount, QTextStream *predefinedTextStream)
+{
+ KexiDB::Connection* conn = tableOrQuery.connection();
+ if (!conn)
+ return false;
+
+ if (rowCount == -1)
+ rowCount = KexiDB::rowCount(tableOrQuery);
+ if (rowCount == -1)
+ return false;
+
+//! @todo move this to non-GUI location so it can be also used via command line
+//! @todo add a "finish" page with a progressbar.
+//! @todo look at rowCount whether the data is really large;
+//! if so: avoid copying to clipboard (or ask user) because of system memory
+
+//! @todo OPTIMIZATION: use fieldsExpanded(true /*UNIQUE*/)
+//! @todo OPTIMIZATION? (avoid multiple data retrieving) look for already fetched data within KexiProject..
+
+ KexiDB::QuerySchema* query = tableOrQuery.query();
+ if (!query)
+ query = tableOrQuery.table()->query();
+
+ KexiDB::QueryColumnInfo::Vector fields( query->fieldsExpanded( KexiDB::QuerySchema::WithInternalFields ) );
+ QString buffer;
+
+ KSaveFile *kSaveFile = 0;
+ QTextStream *stream = 0;
+
+ const bool copyToClipboard = options.mode==Clipboard;
+ if (copyToClipboard) {
+//! @todo (during exporting): enlarge bufSize by factor of 2 when it became too small
+ uint bufSize = QMIN((rowCount<0 ? 10 : rowCount) * fields.count() * 20, 128000);
+ buffer.reserve( bufSize );
+ if (buffer.capacity() < bufSize) {
+ kdWarning() << "KexiCSVExportWizard::exportData() cannot allocate memory for " << bufSize
+ << " characters" << endl;
+ return false;
+ }
+ }
+ else {
+ if (predefinedTextStream) {
+ stream = predefinedTextStream;
+ }
+ else {
+ if (options.fileName.isEmpty()) {//sanity
+ kdWarning() << "KexiCSVExportWizard::exportData(): fname is empty" << endl;
+ return false;
+ }
+ kSaveFile = new KSaveFile(options.fileName);
+ if (0 == kSaveFile->status())
+ stream = kSaveFile->textStream();
+ if (0 != kSaveFile->status() || !stream) {//sanity
+ kdWarning() << "KexiCSVExportWizard::exportData(): status != 0 or stream == 0" << endl;
+ delete kSaveFile;
+ return false;
+ }
+ }
+ }
+
+//! @todo escape strings
+
+#define _ERR \
+ delete [] isText; \
+ if (kSaveFile) { kSaveFile->abort(); delete kSaveFile; } \
+ return false
+
+#define APPEND(what) \
+ if (copyToClipboard) buffer.append(what); else (*stream) << (what)
+
+// line endings should be as in RFC 4180
+#define CSV_EOLN "\r\n"
+
+ // 0. Cache information
+ const uint fieldsCount = query->fieldsExpanded().count(); //real fields count without internals
+ const QCString delimiter( options.delimiter.left(1).latin1() );
+ const bool hasTextQuote = !options.textQuote.isEmpty();
+ const QString textQuote( options.textQuote.left(1) );
+ const QCString escapedTextQuote( (textQuote + textQuote).latin1() ); //ok?
+ //cache for faster checks
+ bool *isText = new bool[fieldsCount];
+ bool *isDateTime = new bool[fieldsCount];
+ bool *isTime = new bool[fieldsCount];
+ bool *isBLOB = new bool[fieldsCount];
+ uint *visibleFieldIndex = new uint[fieldsCount];
+// bool isInteger[fieldsCount]; //cache for faster checks
+// bool isFloatingPoint[fieldsCount]; //cache for faster checks
+ for (uint i=0; i<fieldsCount; i++) {
+ KexiDB::QueryColumnInfo* ci;
+ const int indexForVisibleLookupValue = fields[i]->indexForVisibleLookupValue();
+ if (-1 != indexForVisibleLookupValue) {
+ ci = query->expandedOrInternalField( indexForVisibleLookupValue );
+ visibleFieldIndex[i] = indexForVisibleLookupValue;
+ }
+ else {
+ ci = fields[i];
+ visibleFieldIndex[i] = i;
+ }
+
+ isText[i] = ci->field->isTextType();
+ isDateTime[i] = ci->field->type()==KexiDB::Field::DateTime;
+ isTime[i] = ci->field->type()==KexiDB::Field::Time;
+ isBLOB[i] = ci->field->type()==KexiDB::Field::BLOB;
+// isInteger[i] = fields[i]->field->isIntegerType()
+// || fields[i]->field->type()==KexiDB::Field::Boolean;
+// isFloatingPoint[i] = fields[i]->field->isFPNumericType();
+ }
+
+ // 1. Output column names
+ if (options.addColumnNames) {
+ for (uint i=0; i<fieldsCount; i++) {
+ if (i>0)
+ APPEND( delimiter );
+ if (hasTextQuote){
+ APPEND( textQuote + fields[i]->captionOrAliasOrName().replace(textQuote, escapedTextQuote) + textQuote );
+ }
+ else {
+ APPEND( fields[i]->captionOrAliasOrName() );
+ }
+ }
+ APPEND(CSV_EOLN);
+ }
+
+ KexiGUIMessageHandler handler;
+ KexiDB::Cursor *cursor = conn->executeQuery(*query);
+ if (!cursor) {
+ handler.showErrorMessage(conn);
+ _ERR;
+ }
+ for (cursor->moveFirst(); !cursor->eof() && !cursor->error(); cursor->moveNext()) {
+ const uint realFieldCount = QMIN(cursor->fieldCount(), fieldsCount);
+ for (uint i=0; i<realFieldCount; i++) {
+ const uint real_i = visibleFieldIndex[i];
+ if (i>0)
+ APPEND( delimiter );
+ if (cursor->value(real_i).isNull())
+ continue;
+ if (isText[real_i]) {
+ if (hasTextQuote)
+ APPEND( textQuote + QString(cursor->value(real_i).toString()).replace(textQuote, escapedTextQuote) + textQuote );
+ else
+ APPEND( cursor->value(real_i).toString() );
+ }
+ else if (isDateTime[real_i]) { //avoid "T" in ISO DateTime
+ APPEND( cursor->value(real_i).toDateTime().date().toString(Qt::ISODate)+" "
+ + cursor->value(real_i).toDateTime().time().toString(Qt::ISODate) );
+ }
+ else if (isTime[real_i]) { //time is temporarily stored as null date + time...
+ APPEND( cursor->value(real_i).toTime().toString(Qt::ISODate) );
+ }
+ else if (isBLOB[real_i]) { //BLOB is escaped in a special way
+ if (hasTextQuote)
+//! @todo add options to suppport other types from KexiDB::BLOBEscapingType enum...
+ APPEND( textQuote + KexiDB::escapeBLOB(cursor->value(real_i).toByteArray(), KexiDB::BLOBEscapeHex) + textQuote );
+ else
+ APPEND( KexiDB::escapeBLOB(cursor->value(real_i).toByteArray(), KexiDB::BLOBEscapeHex) );
+ }
+ else {//other types
+ APPEND( cursor->value(real_i).toString() );
+ }
+ }
+ APPEND(CSV_EOLN);
+ }
+
+ if (copyToClipboard)
+ buffer.squeeze();
+
+ if (!conn->deleteCursor(cursor)) {
+ handler.showErrorMessage(conn);
+ _ERR;
+ }
+
+ if (copyToClipboard)
+ kapp->clipboard()->setText(buffer, QClipboard::Clipboard);
+
+ delete [] isText;
+ delete [] isDateTime;
+ delete [] isTime;
+ delete [] isBLOB;
+ delete [] visibleFieldIndex;
+
+ if (kSaveFile) {
+ if (!kSaveFile->close()) {
+ kdWarning() << "KexiCSVExportWizard::exportData(): error close(); status == "
+ << kSaveFile->status() << endl;
+ }
+ delete kSaveFile;
+ }
+ return true;
+}
diff --git a/kexi/plugins/importexport/csv/kexicsvexport.h b/kexi/plugins/importexport/csv/kexicsvexport.h
new file mode 100644
index 000000000..7c8138feb
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvexport.h
@@ -0,0 +1,58 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_CSVEXPORT_H
+#define KEXI_CSVEXPORT_H
+
+#include <kexidb/utils.h>
+
+namespace KexiCSVExport
+{
+
+//! Exporting mode: a file or clipboard
+enum Mode { Clipboard, File };
+
+//! Options used in KexiCSVExportWizard contructor.
+class Options {
+ public:
+ Options();
+
+ //! Assigns \a args. \return false on failure.
+ bool assign( QMap<QString,QString>& args );
+
+ Mode mode;
+ int itemId; //!< Table or query ID
+ QString fileName;
+ QString delimiter;
+ QString forceDelimiter; //!< Used for "clipboard" mode
+ QString textQuote;
+ bool addColumnNames : 1;
+};
+
+/*! Exports data. \return false on failure.
+ @param options options for the export
+ @param rowCount row count of the input data or -1 if the row cound has not yet been computed
+ @param predefinedTextStream text stream that should be used instead of writing to a file
+*/
+bool exportData(KexiDB::TableOrQuerySchema& tableOrQuery, const Options& options, int rowCount = -1,
+ QTextStream *predefinedTextStream = 0);
+
+}
+
+#endif
diff --git a/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp b/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp
new file mode 100644
index 000000000..11c0cff01
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp
@@ -0,0 +1,431 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicsvexportwizard.h"
+#include "kexicsvwidgets.h"
+#include <main/startup/KexiStartupFileDialog.h>
+#include <kexidb/cursor.h>
+#include <kexidb/utils.h>
+#include <core/keximainwindow.h>
+#include <core/kexiproject.h>
+#include <core/kexipartinfo.h>
+#include <core/kexipartmanager.h>
+#include <core/kexiguimsghandler.h>
+#include <kexiutils/utils.h>
+#include <widget/kexicharencodingcombobox.h>
+
+#include <qcheckbox.h>
+#include <qgroupbox.h>
+#include <qclipboard.h>
+#include <kapplication.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kactivelabel.h>
+#include <kpushbutton.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <ksavefile.h>
+
+
+KexiCSVExportWizard::KexiCSVExportWizard( const KexiCSVExport::Options& options,
+ KexiMainWindow* mainWin, QWidget * parent, const char * name )
+ : KWizard(parent, name)
+ , m_options(options)
+// , m_mode(mode)
+// , m_itemId(itemId)
+ , m_mainWin(mainWin)
+ , m_fileSavePage(0)
+ , m_defaultsBtn(0)
+ , m_rowCount(-1)
+ , m_rowCountDetermined(false)
+ , m_cancelled(false)
+{
+ if (m_options.mode==KexiCSVExport::Clipboard) {
+ finishButton()->setText(i18n("Copy"));
+ backButton()->hide();
+ }
+ else {
+ finishButton()->setText(i18n("Export"));
+ }
+ helpButton()->hide();
+
+ QString infoLblFromText;
+ KexiGUIMessageHandler msgh(this);
+ m_tableOrQuery = new KexiDB::TableOrQuerySchema(
+ m_mainWin->project()->dbConnection(), m_options.itemId);
+ if (m_tableOrQuery->table()) {
+ if (m_options.mode==KexiCSVExport::Clipboard) {
+ setCaption(i18n("Copy Data From Table to Clipboard"));
+ infoLblFromText = i18n("Copying data from table:");
+ }
+ else {
+ setCaption(i18n("Export Data From Table to CSV File"));
+ infoLblFromText = i18n("Exporting data from table:");
+ }
+ }
+ else if (m_tableOrQuery->query()) {
+ if (m_options.mode==KexiCSVExport::Clipboard) {
+ setCaption(i18n("Copy Data From Query to Clipboard"));
+ infoLblFromText = i18n("Copying data from table:");
+ }
+ else {
+ setCaption(i18n("Export Data From Query to CSV File"));
+ infoLblFromText = i18n("Exporting data from query:");
+ }
+ }
+ else {
+ msgh.showErrorMessage(m_mainWin->project()->dbConnection(),
+ i18n("Could not open data for exporting."));
+ m_cancelled = true;
+ return;
+ }
+ // OK, source data found.
+
+ // Setup pages
+
+ // 1. File Save Page
+ if (m_options.mode==KexiCSVExport::File) {
+ m_fileSavePage = new KexiStartupFileDialog(
+ ":CSVImportExport", //startDir
+ KexiStartupFileDialog::Custom | KexiStartupFileDialog::SavingFileBasedDB,
+ this, "m_fileSavePage");
+ m_fileSavePage->setMinimumHeight(kapp->desktop()->height()/2);
+ m_fileSavePage->setAdditionalFilters( csvMimeTypes() );
+ m_fileSavePage->setDefaultExtension("csv");
+ m_fileSavePage->setLocationText( KexiUtils::stringToFileName(m_tableOrQuery->captionOrName()) );
+ connect(m_fileSavePage, SIGNAL(rejected()), this, SLOT(reject()));
+ addPage(m_fileSavePage, i18n("Enter Name of File You Want to Save Data To"));
+ }
+
+ // 2. Export options
+ m_exportOptionsPage = new QWidget(this, "m_exportOptionsPage");
+ QGridLayout *exportOptionsLyr = new QGridLayout( m_exportOptionsPage, 6, 3,
+ KDialogBase::marginHint(), KDialogBase::spacingHint(), "exportOptionsLyr");
+ m_infoLblFrom = new KexiCSVInfoLabel( infoLblFromText, m_exportOptionsPage );
+ KexiPart::Info *partInfo = Kexi::partManager().infoForMimeType(
+ m_tableOrQuery->table() ? "kexi/table" : "kexi/query");
+ if (partInfo)
+ m_infoLblFrom->setIcon(partInfo->itemIcon());
+ m_infoLblFrom->separator()->hide();
+ exportOptionsLyr->addMultiCellWidget(m_infoLblFrom, 0, 0, 0, 2);
+
+ m_infoLblTo = new KexiCSVInfoLabel(
+ (m_options.mode==KexiCSVExport::File) ? i18n("To CSV file:") : i18n("To clipboard:"),
+ m_exportOptionsPage
+ );
+ if (m_options.mode==KexiCSVExport::Clipboard)
+ m_infoLblTo->setIcon("editpaste");
+ exportOptionsLyr->addMultiCellWidget(m_infoLblTo, 1, 1, 0, 2);
+
+ m_showOptionsButton = new KPushButton(KGuiItem(i18n("Show Options >>"), "configure"),
+ m_exportOptionsPage);
+ connect(m_showOptionsButton, SIGNAL(clicked()), this, SLOT(slotShowOptionsButtonClicked()));
+ exportOptionsLyr->addMultiCellWidget(m_showOptionsButton, 2, 2, 0, 0);
+ m_showOptionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ // -<options section>
+ m_exportOptionsSection = new QGroupBox(1, Vertical, i18n("Options"), m_exportOptionsPage,
+ "m_exportOptionsSection");
+ m_exportOptionsSection->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ exportOptionsLyr->addMultiCellWidget(m_exportOptionsSection, 3, 3, 0, 1);
+ QWidget *exportOptionsSectionWidget
+ = new QWidget(m_exportOptionsSection, "exportOptionsSectionWidget");
+ QGridLayout *exportOptionsSectionLyr = new QGridLayout( exportOptionsSectionWidget, 5, 2,
+ 0, KDialogBase::spacingHint(), "exportOptionsLyr");
+
+ // -delimiter
+ m_delimiterWidget = new KexiCSVDelimiterWidget(false, //!lineEditOnBottom
+ exportOptionsSectionWidget);
+ m_delimiterWidget->setDelimiter(defaultDelimiter());
+ exportOptionsSectionLyr->addWidget( m_delimiterWidget, 0, 1 );
+ QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), exportOptionsSectionWidget);
+ exportOptionsSectionLyr->addWidget( delimiterLabel, 0, 0 );
+
+ // -text quote
+ QWidget *textQuoteWidget = new QWidget(exportOptionsSectionWidget);
+ QHBoxLayout *textQuoteLyr = new QHBoxLayout(textQuoteWidget);
+ exportOptionsSectionLyr->addWidget(textQuoteWidget, 1, 1);
+ m_textQuote = new KexiCSVTextQuoteComboBox( textQuoteWidget );
+ m_textQuote->setTextQuote(defaultTextQuote());
+ textQuoteLyr->addWidget( m_textQuote );
+ textQuoteLyr->addStretch(0);
+ QLabel *textQuoteLabel = new QLabel(m_textQuote, i18n("Text quote:"), exportOptionsSectionWidget);
+ exportOptionsSectionLyr->addWidget( textQuoteLabel, 1, 0 );
+
+ // - character encoding
+ m_characterEncodingCombo = new KexiCharacterEncodingComboBox( exportOptionsSectionWidget );
+ m_characterEncodingCombo->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ exportOptionsSectionLyr->addWidget( m_characterEncodingCombo, 2, 1 );
+ QLabel *characterEncodingLabel = new QLabel(m_characterEncodingCombo, i18n("Text encoding:"),
+ exportOptionsSectionWidget);
+ exportOptionsSectionLyr->addWidget( characterEncodingLabel, 2, 0 );
+
+ // - checkboxes
+ m_addColumnNamesCheckBox = new QCheckBox(i18n("Add column names as the first row"),
+ exportOptionsSectionWidget);
+ m_addColumnNamesCheckBox->setChecked(true);
+ exportOptionsSectionLyr->addWidget( m_addColumnNamesCheckBox, 3, 1 );
+//! @todo 1.1: for copying use "Always use above options for copying" string
+ m_alwaysUseCheckBox = new QCheckBox(i18n("Always use above options for exporting"),
+ m_exportOptionsPage);
+ exportOptionsLyr->addMultiCellWidget(m_alwaysUseCheckBox, 4, 4, 0, 1);
+// exportOptionsSectionLyr->addWidget( m_alwaysUseCheckBox, 4, 1 );
+ m_exportOptionsSection->hide();
+ m_alwaysUseCheckBox->hide();
+ // -</options section>
+
+// exportOptionsLyr->setColStretch(3, 1);
+ exportOptionsLyr->addMultiCell(
+ new QSpacerItem( 0, 0, QSizePolicy::Preferred, QSizePolicy::MinimumExpanding), 5, 5, 0, 1 );
+
+// addPage(m_exportOptionsPage, i18n("Set Export Options"));
+ addPage(m_exportOptionsPage, m_options.mode==KexiCSVExport::Clipboard ? i18n("Copying") : i18n("Exporting"));
+ setFinishEnabled(m_exportOptionsPage, true);
+
+ // load settings
+ kapp->config()->setGroup("ImportExport");
+ if (m_options.mode!=KexiCSVExport::Clipboard && readBoolEntry("ShowOptionsInCSVExportDialog", false)) {
+ show();
+ slotShowOptionsButtonClicked();
+ }
+ if (readBoolEntry("StoreOptionsForCSVExportDialog", false)) {
+ // load defaults:
+ m_alwaysUseCheckBox->setChecked(true);
+ QString s = readEntry("DefaultDelimiterForExportingCSVFiles", defaultDelimiter());
+ if (!s.isEmpty())
+ m_delimiterWidget->setDelimiter(s);
+ s = readEntry("DefaultTextQuoteForExportingCSVFiles", defaultTextQuote());
+ m_textQuote->setTextQuote(s); //will be invaliudated here, so not a problem
+ s = readEntry("DefaultEncodingForExportingCSVFiles");
+ if (!s.isEmpty())
+ m_characterEncodingCombo->setSelectedEncoding(s);
+ m_addColumnNamesCheckBox->setChecked(
+ readBoolEntry("AddColumnNamesForExportingCSVFiles", true) );
+ }
+
+ updateGeometry();
+
+ // -keep widths equal on page #2:
+ int width = QMAX( m_infoLblFrom->leftLabel()->sizeHint().width(),
+ m_infoLblTo->leftLabel()->sizeHint().width());
+ m_infoLblFrom->leftLabel()->setFixedWidth(width);
+ m_infoLblTo->leftLabel()->setFixedWidth(width);
+}
+
+KexiCSVExportWizard::~KexiCSVExportWizard()
+{
+ delete m_tableOrQuery;
+}
+
+bool KexiCSVExportWizard::cancelled() const
+{
+ return m_cancelled;
+}
+
+void KexiCSVExportWizard::showPage ( QWidget * page )
+{
+ if (page == m_fileSavePage) {
+ m_fileSavePage->setFocus();
+ }
+ else if (page==m_exportOptionsPage) {
+ if (m_options.mode==KexiCSVExport::File)
+ m_infoLblTo->setFileName( m_fileSavePage->currentFileName() );
+ QString text = m_tableOrQuery->captionOrName();
+ if (!m_rowCountDetermined) {
+ //do this costly operation only once
+ m_rowCount = KexiDB::rowCount(*m_tableOrQuery);
+ m_rowCountDetermined = true;
+ }
+ int columns = KexiDB::fieldCount(*m_tableOrQuery);
+ text += "\n";
+ if (m_rowCount>0)
+ text += i18n("(rows: %1, columns: %2)").arg(m_rowCount).arg(columns);
+ else
+ text += i18n("(columns: %1)").arg(columns);
+ m_infoLblFrom->setLabelText(text);
+ QFontMetrics fm(m_infoLblFrom->fileNameLabel()->font());
+ m_infoLblFrom->fileNameLabel()->setFixedHeight( fm.height() * 2 + fm.lineSpacing() );
+ if (m_defaultsBtn)
+ m_defaultsBtn->show();
+ }
+
+ if (page!=m_exportOptionsPage) {
+ if (m_defaultsBtn)
+ m_defaultsBtn->hide();
+ }
+
+ KWizard::showPage(page);
+}
+
+void KexiCSVExportWizard::next()
+{
+ if (currentPage() == m_fileSavePage) {
+ if (!m_fileSavePage->checkFileName())
+ return;
+ KWizard::next();
+ finishButton()->setFocus();
+ return;
+ }
+ KWizard::next();
+}
+
+void KexiCSVExportWizard::done(int result)
+{
+ if (QDialog::Accepted == result) {
+ if (m_fileSavePage)
+ m_options.fileName = m_fileSavePage->currentFileName();
+ m_options.delimiter = m_delimiterWidget->delimiter();
+ m_options.textQuote = m_textQuote->textQuote();
+ m_options.addColumnNames = m_addColumnNamesCheckBox->isChecked();
+ if (!KexiCSVExport::exportData(*m_tableOrQuery, m_options))
+ return;
+ }
+ else if (QDialog::Rejected == result) {
+ //nothing to do
+ }
+
+ //store options
+ kapp->config()->setGroup("ImportExport");
+ if (m_options.mode!=KexiCSVExport::Clipboard)
+ writeEntry("ShowOptionsInCSVExportDialog", m_exportOptionsSection->isVisible());
+ const bool store = m_alwaysUseCheckBox->isChecked();
+ writeEntry("StoreOptionsForCSVExportDialog", store);
+ // only save if an option differs from default
+
+ if (store && m_delimiterWidget->delimiter()!=defaultDelimiter())
+ writeEntry("DefaultDelimiterForExportingCSVFiles", m_delimiterWidget->delimiter());
+ else
+ deleteEntry("DefaultDelimiterForExportingCSVFiles");
+ if (store && m_textQuote->textQuote()!=defaultTextQuote())
+ writeEntry("DefaultTextQuoteForExportingCSVFiles", m_textQuote->textQuote());
+ else
+ deleteEntry("DefaultTextQuoteForExportingCSVFiles");
+ if (store && !m_characterEncodingCombo->defaultEncodingSelected())
+ writeEntry("DefaultEncodingForExportingCSVFiles", m_characterEncodingCombo->selectedEncoding());
+ else
+ deleteEntry("DefaultEncodingForExportingCSVFiles");
+ if (store && !m_addColumnNamesCheckBox->isChecked())
+ writeEntry("AddColumnNamesForExportingCSVFiles", m_addColumnNamesCheckBox->isChecked());
+ else
+ deleteEntry("AddColumnNamesForExportingCSVFiles");
+
+ KWizard::done(result);
+}
+
+void KexiCSVExportWizard::slotShowOptionsButtonClicked()
+{
+ if (m_exportOptionsSection->isVisible()) {
+ m_showOptionsButton->setText(i18n("Show Options >>"));
+ m_exportOptionsSection->hide();
+ m_alwaysUseCheckBox->hide();
+ if (m_defaultsBtn)
+ m_defaultsBtn->hide();
+ }
+ else {
+ m_showOptionsButton->setText(i18n("Hide Options <<"));
+ m_exportOptionsSection->show();
+ m_alwaysUseCheckBox->show();
+ if (m_defaultsBtn)
+ m_defaultsBtn->show();
+ }
+}
+
+void KexiCSVExportWizard::layOutButtonRow( QHBoxLayout * layout )
+{
+ QWizard::layOutButtonRow( layout );
+
+ //find the last sublayout
+ QLayout *l = 0;
+ for (QLayoutIterator lit( layout->iterator() ); lit.current(); ++lit)
+ l = lit.current()->layout();
+ if (dynamic_cast<QBoxLayout*>(l)) {
+ if (!m_defaultsBtn) {
+ m_defaultsBtn = new KPushButton(i18n("Defaults"), this);
+ QWidget::setTabOrder(backButton(), m_defaultsBtn);
+ connect(m_defaultsBtn, SIGNAL(clicked()), this, SLOT(slotDefaultsButtonClicked()));
+ }
+ if (!m_exportOptionsSection->isVisible())
+ m_defaultsBtn->hide();
+ dynamic_cast<QBoxLayout*>(l)->insertWidget(0, m_defaultsBtn);
+ }
+}
+
+void KexiCSVExportWizard::slotDefaultsButtonClicked()
+{
+ m_delimiterWidget->setDelimiter(defaultDelimiter());
+ m_textQuote->setTextQuote(defaultTextQuote());
+ m_addColumnNamesCheckBox->setChecked(true);
+ m_characterEncodingCombo->selectDefaultEncoding();
+}
+
+static QString convertKey(const char *key, KexiCSVExport::Mode mode)
+{
+ QString _key(QString::fromLatin1(key));
+ if (mode == KexiCSVExport::Clipboard) {
+ _key.replace("Exporting", "Copying");
+ _key.replace("Export", "Copy");
+ _key.replace("CSVFiles", "CSVToClipboard");
+ }
+ return _key;
+}
+
+bool KexiCSVExportWizard::readBoolEntry(const char *key, bool defaultValue)
+{
+ return kapp->config()->readBoolEntry(convertKey(key, m_options.mode), defaultValue);
+}
+
+QString KexiCSVExportWizard::readEntry(const char *key, const QString& defaultValue)
+{
+ return kapp->config()->readEntry(convertKey(key, m_options.mode), defaultValue);
+}
+
+void KexiCSVExportWizard::writeEntry(const char *key, const QString& value)
+{
+ kapp->config()->writeEntry(convertKey(key, m_options.mode), value);
+}
+
+void KexiCSVExportWizard::writeEntry(const char *key, bool value)
+{
+ kapp->config()->writeEntry(convertKey(key, m_options.mode), value);
+}
+
+void KexiCSVExportWizard::deleteEntry(const char *key)
+{
+ kapp->config()->deleteEntry(convertKey(key, m_options.mode));
+}
+
+QString KexiCSVExportWizard::defaultDelimiter() const
+{
+ if (m_options.mode==KexiCSVExport::Clipboard) {
+ if (!m_options.forceDelimiter.isEmpty())
+ return m_options.forceDelimiter;
+ else
+ return KEXICSV_DEFAULT_CLIPBOARD_DELIMITER;
+ }
+ return KEXICSV_DEFAULT_FILE_DELIMITER;
+}
+
+QString KexiCSVExportWizard::defaultTextQuote() const
+{
+ if (m_options.mode==KexiCSVExport::Clipboard)
+ return KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE;
+ return KEXICSV_DEFAULT_FILE_TEXT_QUOTE;
+}
+
+#include "kexicsvexportwizard.moc"
diff --git a/kexi/plugins/importexport/csv/kexicsvexportwizard.h b/kexi/plugins/importexport/csv/kexicsvexportwizard.h
new file mode 100644
index 000000000..8fdaecd0c
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvexportwizard.h
@@ -0,0 +1,113 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_CSVEXPORTWIZARD_H
+#define KEXI_CSVEXPORTWIZARD_H
+
+#include <kwizard.h>
+
+#include "kexicsvexport.h"
+
+class QCheckBox;
+class QGroupBox;
+class KPushButton;
+class KexiMainWindow;
+class KexiStartupFileDialog;
+class KexiCSVDelimiterWidget;
+class KexiCSVTextQuoteComboBox;
+class KexiCSVInfoLabel;
+class KexiCharacterEncodingComboBox;
+namespace KexiDB {
+ class TableOrQuerySchema;
+}
+
+/*! @short Kexi CSV export wizard
+ Supports exporting to a file and to a clipboard. */
+class KexiCSVExportWizard : public KWizard
+{
+ Q_OBJECT
+
+ public:
+ KexiCSVExportWizard( const KexiCSVExport::Options& options, KexiMainWindow* mainWin,
+ QWidget * parent = 0, const char * name = 0 );
+
+ virtual ~KexiCSVExportWizard();
+
+ bool cancelled() const;
+
+ virtual void showPage ( QWidget * page );
+
+ protected slots:
+ virtual void next();
+ virtual void done(int result);
+ void slotShowOptionsButtonClicked();
+ void slotDefaultsButtonClicked();
+
+ protected:
+ //! reimplemented to add "Defaults" button on the left hand
+ virtual void layOutButtonRow( QHBoxLayout * layout );
+
+ //! \return default delimiter depending on mode.
+ QString defaultDelimiter() const;
+
+ //! \return default text quote depending on mode.
+ QString defaultTextQuote() const;
+
+ //! Helper, works like kapp->config()->readBoolEntry(const char*, bool) but if mode is Clipboard,
+ //! "Exporting" is replaced with "Copying" and "Export" is replaced with "Copy"
+ //! and "CSVFiles" is replaced with "CSVToClipboard"
+ //! in \a key, to keep the setting separate.
+ bool readBoolEntry(const char *key, bool defaultValue);
+
+ //! Helper like \ref readBoolEntry(const char *, bool), but for QString values.
+ QString readEntry(const char *key, const QString& defaultValue = QString::null);
+
+ //! Helper, works like kapp->config()->writeEntry(const char*,bool) but if mode is Clipboard,
+ //! "Exporting" is replaced with "Copying" and "Export" is replaced with "Copy"
+ //! and "CSVFiles" is replaced with "CSVToClipboard"
+ //! in \a key, to keep the setting separate.
+ void writeEntry(const char *key, bool value);
+
+ //! Helper like \ref writeEntry(const char *, bool), but for QString values.
+ void writeEntry(const char *key, const QString& value);
+
+ //! Helper like \ref writeEntry(const char *, bool), but for deleting config entry.
+ void deleteEntry(const char *key);
+
+ KexiCSVExport::Options m_options;
+// Mode m_mode;
+// int m_itemId;
+ KexiMainWindow* m_mainWin;
+ KexiStartupFileDialog* m_fileSavePage;
+ QWidget* m_exportOptionsPage;
+ KPushButton *m_showOptionsButton;
+ KPushButton *m_defaultsBtn;
+ QGroupBox* m_exportOptionsSection;
+ KexiCSVInfoLabel *m_infoLblFrom, *m_infoLblTo;
+ KexiCSVDelimiterWidget* m_delimiterWidget;
+ KexiCSVTextQuoteComboBox* m_textQuote;
+ KexiCharacterEncodingComboBox *m_characterEncodingCombo;
+ QCheckBox* m_addColumnNamesCheckBox, *m_alwaysUseCheckBox;
+ KexiDB::TableOrQuerySchema* m_tableOrQuery;
+ int m_rowCount; //!< Cached row count for a table/query.
+ bool m_rowCountDetermined : 1;
+ bool m_cancelled : 1;
+};
+
+#endif
diff --git a/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp b/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp
new file mode 100644
index 000000000..16a9d416b
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp
@@ -0,0 +1,1662 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This work is based on kspread/dialogs/kspread_dlg_csv.cc
+ and will be merged back with KOffice libraries.
+
+ Copyright (C) 2002-2003 Norbert Andres <nandres@web.de>
+ Copyright (C) 2002-2003 Ariya Hidayat <ariya@kde.org>
+ Copyright (C) 2002 Laurent Montel <montel@kde.org>
+ Copyright (C) 1999 David Faure <faure@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qclipboard.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qmime.h>
+#include <qpushbutton.h>
+#include <qradiobutton.h>
+#include <qtable.h>
+#include <qlayout.h>
+#include <qfiledialog.h>
+#include <qpainter.h>
+#include <qtextcodec.h>
+#include <qtimer.h>
+#include <qfontmetrics.h>
+#include <qtooltip.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kdialogbase.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <kcharsets.h>
+#include <knuminput.h>
+#include <kprogress.h>
+#include <kactivelabel.h>
+
+#include <kexiutils/identifier.h>
+#include <kexiutils/utils.h>
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipart.h>
+#include <core/kexipartinfo.h>
+#include <core/keximainwindow.h>
+#include <core/kexiguimsghandler.h>
+#include <kexidb/connection.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/transaction.h>
+#include <widget/kexicharencodingcombobox.h>
+
+#include "kexicsvimportdialog.h"
+#include "kexicsvwidgets.h"
+
+#ifdef Q_WS_WIN
+#include <krecentdirs.h>
+#include <windows.h>
+#endif
+
+#if 0
+#include <kspread_cell.h>
+#include <kspread_doc.h>
+#include <kspread_sheet.h>
+#include <kspread_undo.h>
+#include <kspread_view.h>
+#endif
+
+#define _IMPORT_ICON "table" /*todo: change to "file_import" or so*/
+#define _TEXT_TYPE 0
+#define _NUMBER_TYPE 1
+#define _DATE_TYPE 2
+#define _TIME_TYPE 3
+#define _DATETIME_TYPE 4
+#define _PK_FLAG 5
+
+//extra:
+#define _NO_TYPE_YET -1 //allows to accept a number of empty cells, before something non-empty
+#define _FP_NUMBER_TYPE 255 //_NUMBER_TYPE variant
+#define MAX_ROWS_TO_PREVIEW 100 //max 100 rows is reasonable
+#define MAX_BYTES_TO_PREVIEW 10240 //max 10KB is reasonable
+#define MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER 4096
+
+class KexiCSVImportDialogTable : public QTable
+{
+public:
+ KexiCSVImportDialogTable( QWidget * parent = 0, const char * name = 0 )
+ : QTable(parent, name) {
+ f = font();
+ f.setBold(true);
+ }
+ virtual void paintCell( QPainter * p, int row, int col, const QRect & cr, bool selected, const QColorGroup & cg ) {
+ if (row==0)
+ p->setFont(f);
+ else
+ p->setFont(font());
+ QTable::paintCell(p, row, col, cr, selected, cg);
+ }
+ virtual void setColumnWidth( int col, int w ) {
+ //make columns a bit wider
+ QTable::setColumnWidth( col, w + 16 );
+ }
+ QFont f;
+};
+
+//! Helper used to temporary disable keyboard and mouse events
+void installRecursiveEventFilter(QObject *filter, QObject *object)
+{
+ object->installEventFilter(filter);
+
+ if (!object->children())
+ return;
+
+ QObjectList list = *object->children();
+ for(QObject *obj = list.first(); obj; obj = list.next())
+ installRecursiveEventFilter(filter, obj);
+}
+
+KexiCSVImportDialog::KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin,
+ QWidget * parent, const char * name
+)
+ : KDialogBase(
+ KDialogBase::Plain,
+ i18n( "Import CSV Data File" )
+//! @todo use "Paste CSV Data From Clipboard" caption for mode==Clipboard
+ ,
+ (mode==File ? User1 : (ButtonCode)0) |Ok|Cancel,
+ Ok,
+ parent,
+ name ? name : "KexiCSVImportDialog",
+ true,
+ false,
+ KGuiItem( i18n("&Options"))
+ ),
+ m_mainWin(mainWin),
+ m_cancelled( false ),
+ m_adjustRows( true ),
+ m_startline( 0 ),
+ m_textquote( QString(KEXICSV_DEFAULT_FILE_TEXT_QUOTE)[0] ),
+ m_mode(mode),
+ m_prevSelectedCol(-1),
+ m_columnsAdjusted(false),
+ m_1stRowForFieldNamesDetected(false),
+ m_firstFillTableCall(true),
+ m_blockUserEvents(false),
+ m_primaryKeyColumn(-1),
+ m_dialogCancelled(false),
+ m_conn(0),
+ m_destinationTableSchema(0),
+ m_allRowsLoadedInPreview(false),
+ m_stoppedAt_MAX_BYTES_TO_PREVIEW(false)
+{
+ setWFlags(getWFlags() | Qt::WStyle_Maximize | Qt::WStyle_SysMenu);
+ hide();
+ setButtonOK(KGuiItem( i18n("&Import..."), _IMPORT_ICON));
+
+ m_typeNames.resize(5);
+ m_typeNames[0] = i18n("text");
+ m_typeNames[1] = i18n("number");
+ m_typeNames[2] = i18n("date");
+ m_typeNames[3] = i18n("time");
+ m_typeNames[4] = i18n("date/time");
+
+ kapp->config()->setGroup("ImportExport");
+ m_maximumRowsForPreview = kapp->config()->readNumEntry("MaximumRowsForPreviewInImportDialog", MAX_ROWS_TO_PREVIEW);
+ m_maximumBytesForPreview = kapp->config()->readNumEntry("MaximumBytesForPreviewInImportDialog", MAX_BYTES_TO_PREVIEW);
+
+ m_pkIcon = SmallIcon("key");
+
+ m_uniquenessTest.setAutoDelete(true);
+
+ setIcon(DesktopIcon(_IMPORT_ICON));
+ setSizeGripEnabled( TRUE );
+
+// m_encoding = QString::fromLatin1(KGlobal::locale()->encoding());
+// m_stripWhiteSpaceInTextValuesChecked = true;
+ m_file = 0;
+ m_inputStream = 0;
+
+ QVBoxLayout *lyr = new QVBoxLayout(plainPage(), 0, KDialogBase::spacingHint(), "lyr");
+
+ m_infoLbl = new KexiCSVInfoLabel(
+ m_mode==File ? i18n("Preview of data from file:")
+ : i18n("Preview of data from clipboard:"),
+ plainPage()
+ );
+ lyr->addWidget( m_infoLbl );
+
+ QWidget* page = new QFrame( plainPage(), "page" );
+ QGridLayout *glyr= new QGridLayout( page, 4, 5, 0, KDialogBase::spacingHint(), "glyr");
+ lyr->addWidget( page );
+
+ // Delimiter: comma, semicolon, tab, space, other
+ m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page);
+ m_detectDelimiter = true;
+ glyr->addMultiCellWidget( m_delimiterWidget, 1, 2, 0, 0 );
+
+ QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), page);
+ delimiterLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
+ glyr->addMultiCellWidget( delimiterLabel, 0, 0, 0, 0 );
+
+ // Format: number, text, currency,
+ m_formatComboText = i18n( "Format for column %1:" );
+ m_formatCombo = new KComboBox(page, "m_formatCombo");
+ m_formatCombo->insertItem(i18n("Text"));
+ m_formatCombo->insertItem(i18n("Number"));
+ m_formatCombo->insertItem(i18n("Date"));
+ m_formatCombo->insertItem(i18n("Time"));
+ m_formatCombo->insertItem(i18n("Date/Time"));
+ glyr->addMultiCellWidget( m_formatCombo, 1, 1, 1, 1 );
+
+ m_formatLabel = new QLabel(m_formatCombo, "", page);
+ m_formatLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
+ glyr->addWidget( m_formatLabel, 0, 1 );
+
+ m_primaryKeyField = new QCheckBox( i18n( "Primary key" ), page, "m_primaryKeyField" );
+ glyr->addWidget( m_primaryKeyField, 2, 1 );
+ connect(m_primaryKeyField, SIGNAL(toggled(bool)), this, SLOT(slotPrimaryKeyFieldToggled(bool)));
+
+ m_comboQuote = new KexiCSVTextQuoteComboBox( page );
+ glyr->addWidget( m_comboQuote, 1, 2 );
+
+ TextLabel2 = new QLabel( m_comboQuote, i18n( "Text quote:" ), page, "TextLabel2" );
+ TextLabel2->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
+ TextLabel2->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
+ glyr->addWidget( TextLabel2, 0, 2 );
+
+ m_startAtLineSpinBox = new KIntSpinBox( page, "m_startAtLineSpinBox" );
+ m_startAtLineSpinBox->setMinValue(1);
+ m_startAtLineSpinBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ m_startAtLineSpinBox->setMinimumWidth(QFontMetrics(m_startAtLineSpinBox->font()).width("8888888"));
+ glyr->addWidget( m_startAtLineSpinBox, 1, 3 );
+
+ m_startAtLineLabel = new QLabel( m_startAtLineSpinBox, "",
+ page, "TextLabel3" );
+ m_startAtLineLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
+ m_startAtLineLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom);
+ glyr->addWidget( m_startAtLineLabel, 0, 3 );
+
+ QSpacerItem* spacer_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ glyr->addItem( spacer_2, 0, 4 );
+
+ m_ignoreDuplicates = new QCheckBox( page, "m_ignoreDuplicates" );
+ m_ignoreDuplicates->setText( i18n( "Ignore duplicated delimiters" ) );
+ glyr->addMultiCellWidget( m_ignoreDuplicates, 2, 2, 2, 4 );
+
+ m_1stRowForFieldNames = new QCheckBox( page, "m_1stRowForFieldNames" );
+ m_1stRowForFieldNames->setText( i18n( "First row contains column names" ) );
+ glyr->addMultiCellWidget( m_1stRowForFieldNames, 3, 3, 2, 4 );
+
+ m_table = new KexiCSVImportDialogTable( plainPage(), "m_table" );
+ lyr->addWidget( m_table );
+
+ m_table->setSizePolicy( QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding, 1, 1) );
+ m_table->setNumRows( 0 );
+ m_table->setNumCols( 0 );
+
+/** @todo reuse Clipboard too! */
+/*
+if ( m_mode == Clipboard )
+ {
+ setCaption( i18n( "Inserting From Clipboard" ) );
+ QMimeSource * mime = QApplication::clipboard()->data();
+ if ( !mime )
+ {
+ KMessageBox::information( this, i18n("There is no data in the clipboard.") );
+ m_cancelled = true;
+ return;
+ }
+
+ if ( !mime->provides( "text/plain" ) )
+ {
+ KMessageBox::information( this, i18n("There is no usable data in the clipboard.") );
+ m_cancelled = true;
+ return;
+ }
+ m_fileArray = QByteArray(mime->encodedData( "text/plain" ) );
+ }
+ else if ( mode == File )
+ {*/
+ m_dateRegExp = QRegExp("(\\d{1,4})([/\\-\\.])(\\d{1,2})([/\\-\\.])(\\d{1,4})");
+ m_timeRegExp1 = QRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})");
+ m_timeRegExp2 = QRegExp("(\\d{1,2}):(\\d{1,2})");
+ m_fpNumberRegExp = QRegExp("[\\-]{0,1}\\d*[,\\.]\\d+");
+ QString caption( i18n("Open CSV Data File") );
+
+ if (m_mode == File) {
+ QStringList mimetypes( csvMimeTypes() );
+#ifdef Q_WS_WIN
+ //! @todo remove
+ QString recentDir = KGlobalSettings::documentPath();
+ m_fname = QFileDialog::getOpenFileName(
+ KFileDialog::getStartURL(":CSVImportExport", recentDir).path(),
+ KexiUtils::fileDialogFilterStrings(mimetypes, false),
+ page, "KexiCSVImportDialog", caption);
+ if ( !m_fname.isEmpty() ) {
+ //save last visited path
+ KURL url;
+ url.setPath( m_fname );
+ if (url.isLocalFile())
+ KRecentDirs::add(":CSVImportExport", url.directory());
+ }
+#else
+ m_fname = KFileDialog::getOpenFileName(":CSVImportExport", mimetypes.join(" "),
+ this, caption);
+#endif
+ //cancel action !
+ if ( m_fname.isEmpty() )
+ {
+ actionButton( Ok )->setEnabled( false );
+ m_cancelled = true;
+ if (parentWidget())
+ parentWidget()->raise();
+ return;
+ }
+ }
+ else if (m_mode == Clipboard) {
+ QCString subtype("plain");
+ m_clipboardData = QApplication::clipboard()->text(subtype, QClipboard::Clipboard);
+/* debug
+ for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++)
+ kdDebug() << i << ": "
+ << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i) << endl;
+*/
+ }
+ else {
+ return;
+ }
+
+ m_loadingProgressDlg = 0;
+ m_importingProgressDlg = 0;
+ if (m_mode == File) {
+ m_loadingProgressDlg = new KProgressDialog(
+ this, "m_loadingProgressDlg", i18n("Loading CSV Data"), i18n("Loading CSV Data from \"%1\"...")
+ .arg(QDir::convertSeparators(m_fname)), true);
+ m_loadingProgressDlg->progressBar()->setTotalSteps( m_maximumRowsForPreview+1 );
+ m_loadingProgressDlg->show();
+ }
+
+ if (m_mode==Clipboard) {
+ m_infoLbl->setIcon("editpaste");
+ }
+ //updateRowCountInfo();
+
+ m_table->setSelectionMode(QTable::NoSelection);
+
+ connect(m_formatCombo, SIGNAL(activated(int)),
+ this, SLOT(formatChanged(int)));
+ connect(m_delimiterWidget, SIGNAL(delimiterChanged(const QString&)),
+ this, SLOT(delimiterChanged(const QString&)));
+ connect(m_startAtLineSpinBox, SIGNAL(valueChanged ( int )),
+ this, SLOT(startlineSelected(int)));
+ connect(m_comboQuote, SIGNAL(activated(int)),
+ this, SLOT(textquoteSelected(int)));
+ connect(m_table, SIGNAL(currentChanged(int, int)),
+ this, SLOT(currentCellChanged(int, int)));
+ connect(m_table, SIGNAL(valueChanged(int,int)),
+ this, SLOT(cellValueChanged(int,int)));
+ connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)),
+ this, SLOT(ignoreDuplicatesChanged(int)));
+ connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)),
+ this, SLOT(slot1stRowForFieldNamesChanged(int)));
+
+ connect(this, SIGNAL(user1Clicked()), this, SLOT(optionsButtonClicked()));
+
+ installRecursiveEventFilter(this, this);
+
+ initLater();
+}
+
+KexiCSVImportDialog::~KexiCSVImportDialog()
+{
+ delete m_file;
+}
+
+void KexiCSVImportDialog::initLater()
+{
+ if (!openData())
+ return;
+
+// delimiterChanged(detectedDelimiter); // this will cause fillTable()
+ m_columnsAdjusted = false;
+ fillTable();
+ delete m_loadingProgressDlg;
+ m_loadingProgressDlg = 0;
+ if (m_dialogCancelled) {
+// m_loadingProgressDlg->hide();
+ // m_loadingProgressDlg->close();
+ QTimer::singleShot(0, this, SLOT(reject()));
+ return;
+ }
+
+ currentCellChanged(0, 0);
+
+// updateGeometry();
+ adjustSize();
+ KDialog::centerOnScreen( this );
+
+ if (m_loadingProgressDlg)
+ m_loadingProgressDlg->hide();
+ show();
+ m_table->setFocus();
+}
+
+bool KexiCSVImportDialog::openData()
+{
+ if (m_mode!=File) //data already loaded, no encoding stuff needed
+ return true;
+
+ delete m_inputStream;
+ m_inputStream = 0;
+ if (m_file) {
+ m_file->close();
+ delete m_file;
+ }
+ m_file = new QFile(m_fname);
+ if (!m_file->open(IO_ReadOnly))
+ {
+ m_file->close();
+ delete m_file;
+ m_file = 0;
+ KMessageBox::sorry( this, i18n("Cannot open input file <nobr>\"%1\"</nobr>.")
+ .arg(QDir::convertSeparators(m_fname)) );
+ actionButton( Ok )->setEnabled( false );
+ m_cancelled = true;
+ if (parentWidget())
+ parentWidget()->raise();
+ return false;
+ }
+ return true;
+}
+
+bool KexiCSVImportDialog::cancelled() const
+{
+ return m_cancelled;
+}
+
+void KexiCSVImportDialog::fillTable()
+{
+ KexiUtils::WaitCursor wc(true);
+ repaint();
+ m_blockUserEvents = true;
+ QPushButton *pb = actionButton(KDialogBase::Cancel);
+ if (pb)
+ pb->setEnabled(true); //allow to cancel
+ KexiUtils::WaitCursor wait;
+
+ if (m_table->numRows()>0) //to accept editor
+ m_table->setCurrentCell(0,0);
+
+ int row, column, maxColumn;
+ QString field = QString::null;
+
+ for (row = 0; row < m_table->numRows(); ++row)
+ for (column = 0; column < m_table->numCols(); ++column)
+ m_table->clearCell(row, column);
+
+ m_detectedTypes.clear();
+ m_detectedTypes.resize(1024, _NO_TYPE_YET);//_TEXT_TYPE);
+ m_uniquenessTest.clear();
+ m_uniquenessTest.resize(1024);
+ m_1stRowForFieldNamesDetected = true;
+
+ if (true != loadRows(field, row, column, maxColumn, true))
+ return;
+
+ m_1stRowForFieldNamesDetected = false;
+
+ // file with only one line without '\n'
+ if (field.length() > 0)
+ {
+ setText(row - m_startline, column, field, true);
+ ++row;
+ field = QString::null;
+ }
+
+ adjustRows( row - m_startline - (m_1stRowForFieldNames->isChecked()?1:0) );
+
+ maxColumn = QMAX( maxColumn, column );
+ m_table->setNumCols(maxColumn);
+
+ for (column = 0; column < m_table->numCols(); ++column)
+ {
+// QString header = m_table->horizontalHeader()->label(column);
+// if (header != i18n("Text") && header != i18n("Number") &&
+// header != i18n("Date") && header != i18n("Currency"))
+// const int detectedType = m_detectedTypes[column+1];
+// m_table->horizontalHeader()->setLabel(column, m_typeNames[ detectedType ]); //i18n("Text"));
+ updateColumnText(column);
+ if (!m_columnsAdjusted)
+ m_table->adjustColumn(column);
+ }
+ m_columnsAdjusted = true;
+
+ if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
+ if (_NUMBER_TYPE != m_detectedTypes[ m_primaryKeyColumn ]) {
+ m_primaryKeyColumn = -1;
+ }
+ }
+
+ m_prevSelectedCol = -1;
+ m_table->setCurrentCell(0,0);
+ currentCellChanged(0, 0);
+ if (m_primaryKeyColumn != -1)
+ m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
+
+ const int count = QMAX(0, m_table->numRows()-1+m_startline);
+ m_allRowsLoadedInPreview = count < m_maximumRowsForPreview && !m_stoppedAt_MAX_BYTES_TO_PREVIEW;
+ if (m_allRowsLoadedInPreview) {
+ m_startAtLineSpinBox->setMaxValue(count);
+ m_startAtLineSpinBox->setValue(m_startline+1);
+ }
+ m_startAtLineLabel->setText(i18n( "Start at line%1:").arg(
+ m_allRowsLoadedInPreview ? QString(" (1-%1)").arg(count)
+ : QString::null //we do not know what's real count
+ ));
+ updateRowCountInfo();
+
+ m_blockUserEvents = false;
+ repaint();
+ m_table->verticalScrollBar()->repaint();//avoid missing repaint
+ m_table->horizontalScrollBar()->repaint();//avoid missing repaint
+}
+
+QString KexiCSVImportDialog::detectDelimiterByLookingAtFirstBytesOfFile(QTextStream& inputStream)
+{
+ m_file->at(0);
+
+ // try to detect delimiter
+ // \t has priority, then ; then ,
+ const QIODevice::Offset origOffset = inputStream.device()->at();
+ QChar c, prevChar=0;
+ int detectedDelimiter = 0;
+ bool insideQuote = false;
+
+ //characters by priority
+ const int CH_TAB_AFTER_QUOTE = 500;
+ const int CH_SEMICOLON_AFTER_QUOTE = 499;
+ const int CH_COMMA_AFTER_QUOTE = 498;
+ const int CH_TAB = 200; // \t
+ const int CH_SEMICOLON = 199; // ;
+ const int CH_COMMA = 198; // ,
+
+ QValueList<int> tabsPerLine, semicolonsPerLine, commasPerLine;
+ int tabs = 0, semicolons = 0, commas = 0;
+ int line = 0;
+ for (uint i=0; !inputStream.atEnd() && i < MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER; i++) {
+ (*m_inputStream) >> c; // read one char
+ if (prevChar=='"') {
+ if (c!='"') //real quote (not double "")
+ insideQuote = !insideQuote;
+ }
+ if (insideQuote) {
+ prevChar = c;
+ continue;
+ }
+ if (c==' ')
+ continue;
+ if (c=='\n') {//end of line
+ //remember # of tabs/semicolons/commas in this line
+ tabsPerLine += tabs;
+ tabs = 0;
+ semicolonsPerLine += semicolons;
+ semicolons = 0;
+ commasPerLine += commas;
+ commas = 0;
+ line++;
+ }
+ else if (c=='\t') {
+ tabs++;
+ detectedDelimiter = QMAX( prevChar=='"' ? CH_TAB_AFTER_QUOTE : CH_TAB, detectedDelimiter );
+ }
+ else if (c==';') {
+ semicolons++;
+ detectedDelimiter = QMAX( prevChar=='"' ? CH_SEMICOLON_AFTER_QUOTE : CH_SEMICOLON, detectedDelimiter );
+ }
+ else if (c==',') {
+ commas++;
+ detectedDelimiter = QMAX( prevChar=='"' ? CH_COMMA_AFTER_QUOTE : CH_COMMA, detectedDelimiter );
+ }
+ prevChar = c;
+ }
+
+ inputStream.device()->at(origOffset); //restore orig. offset
+
+ //now, try to find a delimiter character that exists the same number of times in all the checked lines
+ //this detection method has priority over others
+ QValueList<int>::ConstIterator it;
+ if (tabsPerLine.count()>1) {
+ tabs = tabsPerLine.isEmpty() ? 0 : tabsPerLine.first();
+ for (it=tabsPerLine.constBegin(); it!=tabsPerLine.constEnd(); ++it) {
+ if (tabs != *it)
+ break;
+ }
+ if (tabs>0 && it==tabsPerLine.constEnd())
+ return "\t";
+ }
+ if (semicolonsPerLine.count()>1) {
+ semicolons = semicolonsPerLine.isEmpty() ? 0 : semicolonsPerLine.first();
+ for (it=semicolonsPerLine.constBegin(); it!=semicolonsPerLine.constEnd(); ++it) {
+ if (semicolons != *it)
+ break;
+ }
+ if (semicolons > 0 && it==semicolonsPerLine.constEnd())
+ return ";";
+ }
+ if (commasPerLine.count()>1) {
+ commas = commasPerLine.first();
+ for (it=commasPerLine.constBegin(); it!=commasPerLine.constEnd(); ++it) {
+ if (commas != *it)
+ break;
+ }
+ if (commas > 0 && it==commasPerLine.constEnd())
+ return ",";
+ }
+ //now return the winning character by looking at CH_* symbol
+ if (detectedDelimiter == CH_TAB_AFTER_QUOTE || detectedDelimiter == CH_TAB)
+ return "\t";
+ if (detectedDelimiter == CH_SEMICOLON_AFTER_QUOTE || detectedDelimiter == CH_SEMICOLON)
+ return ";";
+ if (detectedDelimiter == CH_COMMA_AFTER_QUOTE || detectedDelimiter == CH_COMMA)
+ return ",";
+
+ return KEXICSV_DEFAULT_FILE_DELIMITER; //<-- default
+}
+
+tristate KexiCSVImportDialog::loadRows(QString &field, int &row, int &column, int &maxColumn,
+ bool inGUI)
+{
+ enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
+ S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;
+ field = QString::null;
+ const bool ignoreDups = m_ignoreDuplicates->isChecked();
+ bool lastCharDelimiter = false;
+ bool nextRow = false;
+ row = column = 1;
+ maxColumn = 0;
+ QChar x;
+ const bool hadInputStream = m_inputStream!=0;
+ delete m_inputStream;
+ if ( m_mode == Clipboard ) {
+ m_inputStream = new QTextStream(m_clipboardData, IO_ReadOnly);
+ if (!hadInputStream)
+ m_delimiterWidget->setDelimiter(KEXICSV_DEFAULT_CLIPBOARD_DELIMITER);
+ }
+ else {
+ m_file->at(0); //always seek at 0 because loadRows() is called many times
+ m_inputStream = new QTextStream(m_file);
+ if (m_options.defaultEncodingExplicitySet) {
+ QTextCodec *codec = KGlobal::charsets()->codecForName(m_options.encoding);
+ if (codec)
+ m_inputStream->setCodec(codec); //QTextCodec::codecForName("CP1250"));
+ }
+ if (m_detectDelimiter) {
+ const QString delimiter( detectDelimiterByLookingAtFirstBytesOfFile(*m_inputStream) );
+ if (m_delimiterWidget->delimiter() != delimiter)
+ m_delimiterWidget->setDelimiter( delimiter );
+ }
+ }
+ const QChar delimiter(m_delimiterWidget->delimiter()[0]);
+ m_stoppedAt_MAX_BYTES_TO_PREVIEW = false;
+ int progressStep = 0;
+ if (m_importingProgressDlg)
+ progressStep = QMAX( 1, m_importingProgressDlg->progressBar()->totalSteps()/200 );
+ int offset = 0;
+ for (;!m_inputStream->atEnd(); offset++)
+ {
+//disabled: this breaks wide spreadsheets
+// if (column >= m_maximumRowsForPreview)
+// return true;
+
+ if (m_importingProgressDlg && ((offset % progressStep) < 5)) {
+ //update progr. bar dlg on final exporting
+ m_importingProgressDlg->progressBar()->setValue(offset);
+ qApp->processEvents();
+ if (m_importingProgressDlg->wasCancelled()) {
+ delete m_importingProgressDlg;
+ m_importingProgressDlg = 0;
+ return ::cancelled;
+ }
+ }
+
+ (*m_inputStream) >> x; // read one char
+
+ if (x == '\r') {
+ continue; // eat '\r', to handle RFC-compliant files
+ }
+ if (offset==0 && x.unicode()==0xfeff) {
+ // Ignore BOM, the "Byte Order Mark"
+ // (http://en.wikipedia.org/wiki/Byte_Order_Mark, // http://www.unicode.org/charts/PDF/UFFF0.pdf)
+ // Probably fixed in Qt4.
+ continue;
+ }
+
+ switch (state)
+ {
+ case S_START :
+ if (x == m_textquote)
+ {
+ state = S_QUOTED_FIELD;
+ }
+ else if (x == delimiter)
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = QString::null;
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ else if (x == '\n')
+ {
+ if (!inGUI) {
+ //fill remaining empty fields (database wants them explicitly)
+ for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
+ setText(row - m_startline, additionalColumn, QString::null, inGUI);
+ }
+ }
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ field += x;
+ state = S_MAYBE_NORMAL_FIELD;
+ }
+ break;
+ case S_QUOTED_FIELD :
+ if (x == m_textquote)
+ {
+ state = S_MAYBE_END_OF_QUOTED_FIELD;
+ }
+/*allow \n inside quoted fields
+ else if (x == '\n')
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = "";
+ if (x == '\n')
+ {
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ state = S_START;
+ }*/
+ else
+ {
+ field += x;
+ }
+ break;
+ case S_MAYBE_END_OF_QUOTED_FIELD :
+ if (x == m_textquote)
+ {
+ field += x; //no, this was just escaped quote character
+ state = S_QUOTED_FIELD;
+ }
+ else if (x == delimiter || x == '\n')
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = QString::null;
+ if (x == '\n')
+ {
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ state = S_START;
+ }
+ else
+ {
+ state = S_END_OF_QUOTED_FIELD;
+ }
+ break;
+ case S_END_OF_QUOTED_FIELD :
+ if (x == delimiter || x == '\n')
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = QString::null;
+ if (x == '\n')
+ {
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ state = S_START;
+ }
+ else
+ {
+ state = S_END_OF_QUOTED_FIELD;
+ }
+ break;
+ case S_MAYBE_NORMAL_FIELD :
+ if (x == m_textquote)
+ {
+ field = QString::null;
+ state = S_QUOTED_FIELD;
+ break;
+ }
+ case S_NORMAL_FIELD :
+ if (x == delimiter || x == '\n')
+ {
+ setText(row - m_startline, column, field, inGUI);
+ field = QString::null;
+ if (x == '\n')
+ {
+ nextRow = true;
+ maxColumn = QMAX( maxColumn, column );
+ column = 1;
+ }
+ else
+ {
+ if ((ignoreDups == false) || (lastCharDelimiter == false))
+ ++column;
+ lastCharDelimiter = true;
+ }
+ state = S_START;
+ }
+ else
+ {
+ field += x;
+ }
+ }
+ if (x != delimiter)
+ lastCharDelimiter = false;
+
+ if (nextRow) {
+ if (!inGUI && row==1 && m_1stRowForFieldNames->isChecked()) {
+ // do not save to the database 1st row if it contains column names
+ m_importingStatement->clearArguments();
+ }
+ else if (!saveRow(inGUI))
+ return false;
+
+ ++row;
+ }
+
+ if (m_firstFillTableCall && row==2
+ && !m_1stRowForFieldNames->isChecked() && m_1stRowForFieldNamesDetected)
+ {
+ //'1st row for field name' flag detected: reload table
+ m_1stRowForFieldNamesDetected = false;
+ m_table->setNumRows( 0 );
+ m_firstFillTableCall = false; //this trick is allowed only once, on startup
+ m_1stRowForFieldNames->setChecked(true); //this will reload table
+ //slot1stRowForFieldNamesChanged(1);
+ m_blockUserEvents = false;
+ repaint();
+ return false;
+ }
+
+ if (!m_importingProgressDlg && row % 20 == 0) {
+ qApp->processEvents();
+ //only for GUI mode:
+ if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCancelled()) {
+ delete m_loadingProgressDlg;
+ m_loadingProgressDlg = 0;
+ m_dialogCancelled = true;
+ reject();
+ return false;
+ }
+ }
+
+ if (!m_firstFillTableCall && m_loadingProgressDlg) {
+ m_loadingProgressDlg->progressBar()->setValue(QMIN(m_maximumRowsForPreview, row));
+ }
+
+ if ( inGUI && row > (m_maximumRowsForPreview + (m_1stRowForFieldNamesDetected?1:0)) ) {
+ kexipluginsdbg << "KexiCSVImportDialog::fillTable() loading stopped at row #"
+ << m_maximumRowsForPreview << endl;
+ break;
+ }
+ if (nextRow) {
+ nextRow = false;
+ //additional speedup: stop processing now if too many bytes were loaded for preview
+ kexipluginsdbg << offset << endl;
+ if (inGUI && offset >= m_maximumBytesForPreview && row >= 2) {
+ m_stoppedAt_MAX_BYTES_TO_PREVIEW = true;
+ return true;
+ }
+ }
+ }
+ return true;
+}
+
+void KexiCSVImportDialog::updateColumnText(int col)
+{
+ QString colName;
+ if (col<(int)m_columnNames.count() && (m_1stRowForFieldNames->isChecked() || m_changedColumnNames[col]))
+ colName = m_columnNames[ col ];
+ if (colName.isEmpty()) {
+ colName = i18n("Column %1").arg(col+1); //will be changed to a valid identifier on import
+ m_changedColumnNames[ col ] = false;
+ }
+ int detectedType = m_detectedTypes[col];
+ if (detectedType==_FP_NUMBER_TYPE)
+ detectedType=_NUMBER_TYPE; //we're simplifying that for now
+ else if (detectedType==_NO_TYPE_YET) {
+ m_detectedTypes[col]=_TEXT_TYPE; //entirely empty column
+ detectedType=_TEXT_TYPE;
+ }
+ m_table->horizontalHeader()->setLabel(col,
+ i18n("Column %1").arg(col+1) + " \n(" + m_typeNames[ detectedType ] + ") ");
+ m_table->setText(0, col, colName);
+ m_table->horizontalHeader()->adjustHeaderSize();
+
+ //check uniqueness
+ QValueList<int> *list = m_uniquenessTest[col];
+ if (m_primaryKeyColumn==-1 && list && !list->isEmpty()) {
+ qHeapSort(*list);
+ QValueList<int>::ConstIterator it=list->constBegin();
+ int prevValue = *it;
+ ++it;
+ for(; it!=list->constEnd() && prevValue!=(*it); ++it)
+ prevValue=(*it);
+ if (it!=list->constEnd()) {
+ //duplicates:
+ list->clear();
+ }
+ else {
+ //a candidate for PK (autodetected)!
+ if (-1==m_primaryKeyColumn) {
+ m_primaryKeyColumn=col;
+ }
+ }
+ }
+ if (list) //not needed now: conserve memory
+ list->clear();
+}
+
+void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text)
+{
+ int intValue;
+ const int type = m_detectedTypes[col];
+ if (row==1 || type!=_TEXT_TYPE) {
+ bool found = false;
+ if (text.isEmpty() && type==_NO_TYPE_YET)
+ found = true; //real type should be found later
+ //detect type because it's 1st row or all prev. rows were not text
+ //-FP number? (trying before "number" type is a must)
+ if (!found && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) {
+ bool ok = text.isEmpty() || m_fpNumberRegExp.exactMatch(text);
+ //if (!ok)
+ // text.toDouble(&ok);
+ if (ok && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) {
+ m_detectedTypes[col]=_FP_NUMBER_TYPE;
+ found = true; //yes
+ }
+ }
+ //-number?
+ if (!found && (row==1 || type==_NUMBER_TYPE || type==_NO_TYPE_YET)) {
+ bool ok = text.isEmpty();//empty values allowed
+ if (!ok)
+ intValue = text.toInt(&ok);
+ if (ok && (row==1 || type==_NO_TYPE_YET)) {
+ m_detectedTypes[col]=_NUMBER_TYPE;
+ found = true; //yes
+ }
+ }
+ //-date?
+ if (!found && (row==1 || type==_DATE_TYPE || type==_NO_TYPE_YET)) {
+ if ((row==1 || type==_NO_TYPE_YET)
+ && (text.isEmpty() || m_dateRegExp.exactMatch(text)))
+ {
+ m_detectedTypes[col]=_DATE_TYPE;
+ found = true; //yes
+ }
+ }
+ //-time?
+ if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
+ if ((row==1 || type==_NO_TYPE_YET)
+ && (text.isEmpty() || m_timeRegExp1.exactMatch(text) || m_timeRegExp2.exactMatch(text)))
+ {
+ m_detectedTypes[col]=_TIME_TYPE;
+ found = true; //yes
+ }
+ }
+ //-date/time?
+ if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) {
+ if (row==1 || type==_NO_TYPE_YET) {
+ bool detected = text.isEmpty();
+ if (!detected) {
+ const QStringList dateTimeList( QStringList::split(" ", text) );
+ bool ok = dateTimeList.count()>=2;
+//! @todo also support ISODateTime's "T" separator?
+//! @todo also support timezones?
+ if (ok) {
+ //try all combinations
+ QString datePart( dateTimeList[0].stripWhiteSpace() );
+ QString timePart( dateTimeList[1].stripWhiteSpace() );
+ ok = m_dateRegExp.exactMatch(datePart)
+ && (m_timeRegExp1.exactMatch(timePart) || m_timeRegExp2.exactMatch(timePart));
+ }
+ detected = ok;
+ }
+ if (detected) {
+ m_detectedTypes[col]=_DATETIME_TYPE;
+ found = true; //yes
+ }
+ }
+ }
+ if (!found && type==_NO_TYPE_YET && !text.isEmpty()) {
+ //eventually, a non-emptytext after a while
+ m_detectedTypes[col]=_TEXT_TYPE;
+ found = true; //yes
+ }
+ //default: text type (already set)
+ }
+ //check uniqueness for this value
+ QValueList<int> *list = m_uniquenessTest[col];
+ if (row==1 && (!list || !list->isEmpty()) && !text.isEmpty() && _NUMBER_TYPE == m_detectedTypes[col]) {
+ if (!list) {
+ list = new QValueList<int>();
+ m_uniquenessTest.insert(col, list);
+ }
+ list->append( intValue );
+ }
+ else {
+ //the value is empty or uniqueness test failed in the past
+ if (list && !list->isEmpty())
+ list->clear(); //indicate that uniqueness test failed
+ }
+}
+
+bool KexiCSVImportDialog::parseDate(const QString& text, QDate& date)
+{
+ if (!m_dateRegExp.exactMatch(text))
+ return false;
+ //dddd - dd - dddd
+ //1 2 3 4 5 <- pos
+ const int d1 = m_dateRegExp.cap(1).toInt(), d3 = m_dateRegExp.cap(3).toInt(), d5 = m_dateRegExp.cap(5).toInt();
+ if (m_dateRegExp.cap(2)=="/") //probably separator for american format mm/dd/yyyy
+ date = QDate(d5, d1, d3);
+ else {
+ if (d5 > 31) //d5 == year
+ date = QDate(d5, d3, d1);
+ else //d1 == year
+ date = QDate(d1, d3, d5);
+ }
+ return date.isValid();
+}
+
+bool KexiCSVImportDialog::parseTime(const QString& text, QTime& time)
+{
+ time = QTime::fromString(text, Qt::ISODate); //same as m_timeRegExp1
+ if (time.isValid())
+ return true;
+ if (m_timeRegExp2.exactMatch(text)) { //hh:mm:ss
+ time = QTime(m_timeRegExp2.cap(1).toInt(), m_timeRegExp2.cap(3).toInt(), m_timeRegExp2.cap(5).toInt());
+ return true;
+ }
+ return false;
+}
+
+void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI)
+{
+ if (!inGUI) {
+ //save text directly to database buffer
+ if (col==1) { //1st col
+ m_importingStatement->clearArguments();
+ if (m_implicitPrimaryKeyAdded)
+ *m_importingStatement << QVariant(); //id will be autogenerated here
+ }
+ const int detectedType = m_detectedTypes[col-1];
+ if (detectedType==_NUMBER_TYPE) {
+ *m_importingStatement << ( text.isEmpty() ? QVariant() : text.toInt() );
+//! @todo what about time and float/double types and different integer subtypes?
+ }
+ else if (detectedType==_FP_NUMBER_TYPE) {
+ //replace ',' with '.'
+ QCString t(text.latin1());
+ const int textLen = t.length();
+ for (int i=0; i<textLen; i++) {
+ if (t.at(i)==',') {
+ t.at(i) = '.';
+ break;
+ }
+ }
+ *m_importingStatement << ( t.isEmpty() ? QVariant() : t.toDouble() );
+ }
+ else if (detectedType==_DATE_TYPE) {
+ QDate date;
+ if (parseDate(text, date))
+ *m_importingStatement << date;
+ }
+ else if (detectedType==_TIME_TYPE) {
+ QTime time;
+ if (parseTime(text, time))
+ *m_importingStatement << time;
+ }
+ else if (detectedType==_DATETIME_TYPE) {
+ QStringList dateTimeList( QStringList::split(" ", text) );
+ if (dateTimeList.count()<2)
+ dateTimeList = QStringList::split("T", text); //also support ISODateTime's "T" separator
+//! @todo also support timezones?
+ if (dateTimeList.count()>=2) {
+ QString datePart( dateTimeList[0].stripWhiteSpace() );
+ QDate date;
+ if (parseDate(datePart, date)) {
+ QString timePart( dateTimeList[1].stripWhiteSpace() );
+ QTime time;
+ if (parseTime(timePart, time))
+ *m_importingStatement << QDateTime(date, time);
+ }
+ }
+ }
+ else //_TEXT_TYPE and the rest
+ *m_importingStatement << (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text);
+ return;
+ }
+ //save text to GUI (table view)
+ if (m_table->numCols() < col) {
+ m_table->setNumCols(col);
+ if ((int)m_columnNames.size() < m_table->numCols()) {
+ m_columnNames.resize(m_table->numCols()+10);
+ m_changedColumnNames.resize(m_table->numCols()+10);
+ }
+ }
+
+ if (m_1stRowForFieldNames->isChecked()) {
+ if ((row+m_startline)==1) {//this is for column name
+ if ((col-1) < (int)m_changedColumnNames.size() && false==m_changedColumnNames[col-1]) {
+ //this column has no custom name entered by a user
+ //-get the name from the data cell
+ QString colName(text.simplifyWhiteSpace());
+ if (!colName.isEmpty()) {
+ if (colName.left(1)>="0" && colName.left(1)<="9")
+ colName.prepend(i18n("Column")+" ");
+ m_columnNames[ col-1 ] = colName;
+ }
+ }
+ return;
+ }
+ }
+ else {
+ if ((row+m_startline)==1) {//this row is for column name
+ if (m_1stRowForFieldNamesDetected && !m_1stRowForFieldNames->isChecked()) {
+ QString f( text.simplifyWhiteSpace() );
+ if (f.isEmpty() || !f[0].isLetter())
+ m_1stRowForFieldNamesDetected = false; //this couldn't be a column name
+ }
+ }
+ row++; //1st row was for column names
+ }
+
+ if (row < 2) // skipped by the user
+ return;
+
+ if (m_table->numRows() < row) {
+// if (m_maximumRowsForPreview >= row+100)
+ m_table->setNumRows(row+100); /* We add more rows at a time to limit recalculations */
+ //else
+// m_table->setNumRows(m_maximumRowsForPreview);
+ m_table->verticalHeader()->setLabel(0, i18n("Column name")+" ");
+ m_adjustRows=true;
+ }
+
+ m_table->setText(row - 1, col - 1, (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text));
+ m_table->verticalHeader()->setLabel(row-1, QString::number(row-1));
+
+ detectTypeAndUniqueness(row-1, col-1, text);
+}
+
+bool KexiCSVImportDialog::saveRow(bool inGUI)
+{
+ if (inGUI) {
+ //nothing to do
+ return true;
+ }
+ //save db buffer
+ bool res = m_importingStatement->execute();
+//todo: move
+ m_importingStatement->clearArguments();
+ return res;
+// return m_conn->insertRecord(*m_destinationTableSchema, m_dbRowBuffer);
+}
+
+void KexiCSVImportDialog::adjustRows(int iRows)
+{
+ if (m_adjustRows)
+ {
+ m_table->setNumRows( iRows );
+ m_adjustRows=false;
+ for (int i = 0; i<iRows; i++)
+ m_table->adjustRow(i);
+ }
+}
+
+void KexiCSVImportDialog::formatChanged(int id)
+{
+ if (id==_PK_FLAG) {
+ if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) {
+ m_table->setPixmap(0, m_primaryKeyColumn, QPixmap());
+ }
+ if (m_primaryKeyField->isChecked()) {
+ m_primaryKeyColumn = m_table->currentColumn();
+ m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon);
+ }
+ else
+ m_primaryKeyColumn = -1;
+ return;
+ }
+ else {
+ m_detectedTypes[m_table->currentColumn()]=id;
+ m_primaryKeyField->setEnabled( _NUMBER_TYPE == id );
+ m_primaryKeyField->setChecked( m_primaryKeyColumn == m_table->currentColumn() && m_primaryKeyField->isEnabled() );
+ }
+ updateColumnText(m_table->currentColumn());
+}
+
+void KexiCSVImportDialog::delimiterChanged(const QString& delimiter)
+{
+ Q_UNUSED(delimiter);
+ m_columnsAdjusted = false;
+ m_detectDelimiter = false; //selected by hand: do not detect in the future
+ //delayed, otherwise combobox won't be repainted
+ fillTableLater();
+}
+
+void KexiCSVImportDialog::textquoteSelected(int)
+{
+ const QString tq(m_comboQuote->textQuote());
+ if (tq.isEmpty())
+ m_textquote = 0;
+ else
+ m_textquote = tq[0];
+
+ kexipluginsdbg << "KexiCSVImportDialog::textquoteSelected(): " << m_textquote << endl;
+
+ //delayed, otherwise combobox won't be repainted
+ fillTableLater();
+}
+
+void KexiCSVImportDialog::fillTableLater()
+{
+ m_table->setNumRows( 0 );
+ QTimer::singleShot(10, this, SLOT(fillTable()));
+}
+
+void KexiCSVImportDialog::startlineSelected(int startline)
+{
+// const int startline = line.toInt() - 1;
+ if (m_startline == (startline-1))
+ return;
+ m_startline = startline-1;
+ m_adjustRows=true;
+ fillTable();
+ m_table->setFocus();
+}
+
+void KexiCSVImportDialog::currentCellChanged(int, int col)
+{
+ if (m_prevSelectedCol==col)
+ return;
+ m_prevSelectedCol = col;
+ int type = m_detectedTypes[col];
+ if (type==_FP_NUMBER_TYPE)
+ type=_NUMBER_TYPE; //we're simplifying that for now
+
+ m_formatCombo->setCurrentItem( type );
+ m_formatLabel->setText( m_formatComboText.arg(col+1) );
+ m_primaryKeyField->setEnabled( _NUMBER_TYPE == m_detectedTypes[col]);
+ m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled()
+ m_primaryKeyField->setChecked( m_primaryKeyColumn == col );
+ m_primaryKeyField->blockSignals(false);
+}
+
+void KexiCSVImportDialog::cellValueChanged(int row,int col)
+{
+ if (row==0) {//column name has changed
+ m_columnNames[ col ] = m_table->text(row, col);
+ m_changedColumnNames.setBit( col );
+ }
+}
+
+void KexiCSVImportDialog::accept()
+{
+//! @todo MOVE MOST OF THIS TO CORE/ (KexiProject?) after KexiDialogBase code is moved to non-gui place
+
+ KexiGUIMessageHandler msg; //! @todo make it better integrated with main window
+
+ const uint numRows( m_table->numRows() );
+ if (numRows == 0)
+ return; //impossible
+
+ if (numRows == 1) {
+ if (KMessageBox::No == KMessageBox::questionYesNo(this,
+ i18n("Data set contains no rows. Do you want to import empty table?")))
+ return;
+ }
+
+ KexiProject* project = m_mainWin->project();
+ if (!project) {
+ msg.showErrorMessage(i18n("No project available."));
+ return;
+ }
+ m_conn = project->dbConnection(); //cache this pointer
+ if (!m_conn) {
+ msg.showErrorMessage(i18n("No database connection available."));
+ return;
+ }
+ KexiPart::Part *part = Kexi::partManager().partForMimeType("kexi/table");
+ if (!part) {
+ msg.showErrorMessage(&Kexi::partManager());
+ return;
+ }
+
+ //get suggested name based on the file name
+ QString suggestedName;
+ if (m_mode==File) {
+ suggestedName = KURL::fromPathOrURL(m_fname).fileName();
+ //remove extension
+ if (!suggestedName.isEmpty()) {
+ const int idx = suggestedName.findRev(".");
+ if (idx!=-1)
+ suggestedName = suggestedName.mid(0, idx ).simplifyWhiteSpace();
+ }
+ }
+
+ //-new part item
+ KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), suggestedName);
+ if (!partItemForSavedTable) {
+ // msg.showErrorMessage(project);
+ return;
+ }
+
+#define _ERR \
+ { project->deleteUnstoredItem(partItemForSavedTable); \
+ m_conn = 0; \
+ delete m_destinationTableSchema; \
+ m_destinationTableSchema = 0; \
+ return; }
+
+ //-ask for table name/title
+ // (THIS IS FROM KexiMainWindowImpl::saveObject())
+ bool allowOverwriting = true;
+ tristate res = m_mainWin->getNewObjectInfo( partItemForSavedTable, part, allowOverwriting );
+ if (~res || !res) {
+ //! @todo: err
+ _ERR;
+ }
+ //(allowOverwriting is now set to true, if user accepts overwriting,
+ // and overwriting will be needed)
+
+// KexiDB::SchemaData sdata(part->info()->projectPartID());
+// sdata.setName( partItem->name() );
+
+ //-create table schema (and thus schema object)
+ //-assign information (THIS IS FROM KexiDialogBase::storeNewData())
+ m_destinationTableSchema = new KexiDB::TableSchema(partItemForSavedTable->name());
+ m_destinationTableSchema->setCaption( partItemForSavedTable->caption() );
+ m_destinationTableSchema->setDescription( partItemForSavedTable->description() );
+ const uint numCols( m_table->numCols() );
+
+ m_implicitPrimaryKeyAdded = false;
+ //add PK if user wanted it
+ int msgboxResult;
+ if (m_primaryKeyColumn==-1
+ && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this,
+ i18n("No Primary Key (autonumber) has been defined.\n"
+ "Should it be automatically defined on import (recommended)?\n\n"
+ "Note: An imported table without a Primary Key may not be editable (depending on database type)."),
+ QString::null, KGuiItem(i18n("Add Database Primary Key to a Table", "Add Primary Key"), "key"),
+ KGuiItem(i18n("Do Not Add Database Primary Key to a Table", "Do Not Add")))))
+ {
+ if (msgboxResult == KMessageBox::Cancel)
+ _ERR; //cancel accepting
+
+ //add implicit PK field
+//! @todo make this field hidden (what about e.g. pgsql?)
+ m_implicitPrimaryKeyAdded = true;
+
+ QString fieldName("id");
+ QString fieldCaption("Id");
+
+ QStringList colnames;
+ for (uint col = 0; col < numCols; col++)
+ colnames.append( m_table->text(0, col).lower().simplifyWhiteSpace() );
+
+ if (colnames.find(fieldName)!=colnames.end()) {
+ int num = 1;
+ while (colnames.find(fieldName+QString::number(num))!=colnames.end())
+ num++;
+ fieldName += QString::number(num);
+ fieldCaption += QString::number(num);
+ }
+ KexiDB::Field *field = new KexiDB::Field(
+ fieldName,
+ KexiDB::Field::Integer,
+ KexiDB::Field::NoConstraints,
+ KexiDB::Field::NoOptions,
+ 0,0, //uint length=0, uint precision=0,
+ QVariant(), //QVariant defaultValue=QVariant(),
+ fieldCaption
+ ); //no description and width for now
+ field->setPrimaryKey(true);
+ field->setAutoIncrement(true);
+ m_destinationTableSchema->addField( field );
+ }
+
+ for (uint col = 0; col < numCols; col++) {
+ QString fieldCaption( m_table->text(0, col).simplifyWhiteSpace() );
+ QString fieldName( KexiUtils::string2Identifier( fieldCaption ) );
+ if (m_destinationTableSchema->field(fieldName)) {
+ QString fixedFieldName;
+ uint i = 2; //"apple 2, apple 3, etc. if there're many "apple" names
+ do {
+ fixedFieldName = fieldName + "_" + QString::number(i);
+ if (!m_destinationTableSchema->field(fixedFieldName))
+ break;
+ i++;
+ } while (true);
+ fieldName = fixedFieldName;
+ fieldCaption += (" " + QString::number(i));
+ }
+ const int detectedType = m_detectedTypes[col];
+ KexiDB::Field::Type fieldType;
+ if (detectedType==_DATE_TYPE)
+ fieldType = KexiDB::Field::Date;
+ if (detectedType==_TIME_TYPE)
+ fieldType = KexiDB::Field::Time;
+ if (detectedType==_DATETIME_TYPE)
+ fieldType = KexiDB::Field::DateTime;
+ else if (detectedType==_NUMBER_TYPE)
+ fieldType = KexiDB::Field::Integer;
+ else if (detectedType==_FP_NUMBER_TYPE)
+ fieldType = KexiDB::Field::Double;
+//! @todo what about time and float/double types and different integer subtypes?
+ else //_TEXT_TYPE and the rest
+ fieldType = KexiDB::Field::Text;
+//! @todo what about long text?
+
+ KexiDB::Field *field = new KexiDB::Field(
+ fieldName,
+ fieldType,
+ KexiDB::Field::NoConstraints,
+ KexiDB::Field::NoOptions,
+ 0,0, //uint length=0, uint precision=0,
+ QVariant(), //QVariant defaultValue=QVariant(),
+ fieldCaption
+ ); //no description and width for now
+
+ if ((int)col == m_primaryKeyColumn) {
+ field->setPrimaryKey(true);
+ field->setAutoIncrement(true);
+ }
+ m_destinationTableSchema->addField( field );
+ }
+
+ KexiDB::Transaction transaction = m_conn->beginTransaction();
+ if (transaction.isNull()) {
+ msg.showErrorMessage(m_conn);
+ _ERR;
+ }
+ KexiDB::TransactionGuard tg(transaction);
+
+ //-create physical table
+ if (!m_conn->createTable(m_destinationTableSchema, allowOverwriting)) {
+ msg.showErrorMessage(m_conn);
+ _ERR;
+ }
+
+#define _DROP_DEST_TABLE_AND_RETURN \
+ { \
+ if (m_importingProgressDlg) \
+ m_importingProgressDlg->hide(); \
+ project->deleteUnstoredItem(partItemForSavedTable); \
+ m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ \
+ m_destinationTableSchema = 0; \
+ m_conn = 0; \
+ return; \
+ }
+
+ m_importingStatement = m_conn->prepareStatement(
+ KexiDB::PreparedStatement::InsertStatement, *m_destinationTableSchema);
+ if (!m_importingStatement) {
+ msg.showErrorMessage(m_conn);
+ _DROP_DEST_TABLE_AND_RETURN;
+ }
+
+ if (m_file) {
+ if (!m_importingProgressDlg) {
+ m_importingProgressDlg = new KProgressDialog( this, "m_importingProgressDlg",
+ i18n("Importing CSV Data"), QString::null, true );
+ }
+ m_importingProgressDlg->setLabel(
+ i18n("Importing CSV Data from <nobr>\"%1\"</nobr> into \"%2\" table...")
+ .arg(QDir::convertSeparators(m_fname)).arg(m_destinationTableSchema->name()) );
+ m_importingProgressDlg->progressBar()->setTotalSteps( QFileInfo(*m_file).size() );
+ m_importingProgressDlg->show();
+ }
+
+ int row, column, maxColumn;
+ QString field = QString::null;
+
+ // main job
+ res = loadRows(field, row, column, maxColumn, false /*!gui*/ );
+
+ delete m_importingProgressDlg;
+ m_importingProgressDlg = 0;
+ if (true != res) {
+ //importing cancelled or failed
+ if (!res) //do not display err msg when res == cancelled
+ msg.showErrorMessage(m_conn);
+ _DROP_DEST_TABLE_AND_RETURN;
+ }
+
+ // file with only one line without '\n'
+ if (field.length() > 0)
+ {
+ setText(row - m_startline, column, field, false /*!gui*/);
+ //fill remaining empty fields (database wants them explicitly)
+ for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) {
+ setText(row - m_startline, additionalColumn, QString::null, false /*!gui*/);
+ }
+ if (!saveRow(false /*!gui*/)) {
+ msg.showErrorMessage(m_conn);
+ _DROP_DEST_TABLE_AND_RETURN;
+ }
+ ++row;
+ field = QString::null;
+ }
+
+ if (!tg.commit()) {
+ msg.showErrorMessage(m_conn);
+ _DROP_DEST_TABLE_AND_RETURN;
+ }
+
+ //-now we can store the item
+ partItemForSavedTable->setIdentifier( m_destinationTableSchema->id() );
+ project->addStoredItem( part->info(), partItemForSavedTable );
+
+ QDialog::accept();
+ KMessageBox::information(this, i18n("Data has been successfully imported to table \"%1\".")
+ .arg(m_destinationTableSchema->name()));
+ parentWidget()->raise();
+ m_conn = 0;
+}
+
+int KexiCSVImportDialog::getHeader(int col)
+{
+ QString header = m_table->horizontalHeader()->label(col);
+
+ if (header == i18n("Text type for column", "Text"))
+ return TEXT;
+ else if (header == i18n("Numeric type for column", "Number"))
+ return NUMBER;
+ else if (header == i18n("Currency type for column", "Currency"))
+ return CURRENCY;
+ else
+ return DATE;
+}
+
+QString KexiCSVImportDialog::getText(int row, int col)
+{
+ return m_table->text(row, col);
+}
+
+void KexiCSVImportDialog::ignoreDuplicatesChanged(int)
+{
+ fillTable();
+}
+
+void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int)
+{
+ m_adjustRows=true;
+ if (m_1stRowForFieldNames->isChecked() && m_startline>0 && m_startline>=(m_startAtLineSpinBox->maxValue()-1))
+ m_startline--;
+ fillTable();
+}
+
+void KexiCSVImportDialog::optionsButtonClicked()
+{
+ KexiCSVImportOptionsDialog dlg(m_options, this);
+ if (QDialog::Accepted != dlg.exec())
+ return;
+
+ KexiCSVImportOptions newOptions( dlg.options() );
+ if (m_options != newOptions) {
+ m_options = newOptions;
+ if (!openData())
+ return;
+ fillTable();
+ }
+}
+
+bool KexiCSVImportDialog::eventFilter ( QObject * watched, QEvent * e )
+{
+ QEvent::Type t = e->type();
+ // temporary disable keyboard and mouse events for time-consuming tasks
+ if (m_blockUserEvents && (t==QEvent::KeyPress || t==QEvent::KeyRelease
+ || t==QEvent::MouseButtonPress || t==QEvent::MouseButtonDblClick
+ || t==QEvent::Paint ))
+ return true;
+
+ if (watched == m_startAtLineSpinBox && t==QEvent::KeyPress) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) {
+ m_table->setFocus();
+ return true;
+ }
+ }
+ return QDialog::eventFilter( watched, e );
+}
+
+void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on)
+{
+ Q_UNUSED(on);
+ formatChanged(_PK_FLAG);
+}
+
+void KexiCSVImportDialog::updateRowCountInfo()
+{
+ m_infoLbl->setFileName( m_fname );
+ if (m_allRowsLoadedInPreview) {
+ m_infoLbl->setCommentText(
+ i18n("row count", "(rows: %1)").arg( m_table->numRows()-1+m_startline ) );
+ QToolTip::remove( m_infoLbl );
+ }
+ else {
+ m_infoLbl->setCommentText(
+ i18n("row count", "(rows: more than %1)").arg( m_table->numRows()-1+m_startline ) );
+ QToolTip::add( m_infoLbl->commentLabel(), i18n("Not all rows are visible on this preview") );
+ }
+}
+
+#include "kexicsvimportdialog.moc"
diff --git a/kexi/plugins/importexport/csv/kexicsvimportdialog.h b/kexi/plugins/importexport/csv/kexicsvimportdialog.h
new file mode 100644
index 000000000..1f7b159e2
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvimportdialog.h
@@ -0,0 +1,231 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This work is based on kspread/dialogs/kspread_dlg_csv.cc
+ and will be merged back with KOffice libraries.
+
+ Copyright (C) 2002-2003 Norbert Andres <nandres@web.de>
+ Copyright (C) 2002-2003 Ariya Hidayat <ariya@kde.org>
+ Copyright (C) 2002 Laurent Montel <montel@kde.org>
+ Copyright (C) 1999 David Faure <faure@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_CSVDIALOG_H
+#define KEXI_CSVDIALOG_H
+
+#include <qvaluevector.h>
+#include <qvaluelist.h>
+#include <qptrvector.h>
+#include <qregexp.h>
+#include <qbitarray.h>
+
+#include <kdialogbase.h>
+
+#include <kexiutils/tristate.h>
+#include <kexidb/connection.h>
+
+#include "kexicsvimportoptionsdlg.h"
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QButtonGroup;
+class QCheckBox;
+class QLabel;
+class QLineEdit;
+class QPushButton;
+class QRadioButton;
+class QTable;
+class QFile;
+class KComboBox;
+class KIntSpinBox;
+class KProgressDialog;
+
+class KexiMainWindow;
+class KexiCSVDelimiterWidget;
+class KexiCSVTextQuoteComboBox;
+class KexiCSVInfoLabel;
+
+/**
+ * @short Kexi CSV import dialog
+ *
+ * This is temporary solution for Kexi CSV import,
+ * based on kspread/dialogs/kspread_dlg_csv.h, cc.
+ *
+ * Provides dialog for managing CSV (comma separated value) data.
+ *
+ * Currently KexiCSVImportDialog is used for converting text into columns,
+ * inserting text file and pasting text from clipboard, where conversion
+ * from CSV (comma separated value) data is is all required.
+ * The different purposed mentioned above is determined
+ * using mode, which can be Column, File, or Clipboard respectively.
+*/
+class KexiCSVImportDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ enum Mode { Clipboard, File /*, Column*/ };
+ enum Header { TEXT, NUMBER, DATE, CURRENCY };
+
+ //! @todo what about making it kexidb-independent?
+ KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin, QWidget * parent,
+ const char * name = 0/*, QRect const & rect*/);
+
+ virtual ~KexiCSVImportDialog();
+
+ bool cancelled() const;
+ virtual bool eventFilter ( QObject * watched, QEvent * e );
+
+ protected:
+ bool openData();
+ virtual void accept();
+
+ private:
+ QGridLayout* MyDialogLayout;
+ QHBoxLayout* Layout1;
+ QTable* m_table;
+ KexiCSVDelimiterWidget* m_delimiterWidget;
+ bool m_detectDelimiter; //!< true if delimiter should be detected
+ //!< (true by default, set to false if user sets delimiter)
+ QString m_formatComboText;
+ QLabel* m_formatLabel;
+ KComboBox* m_formatCombo;
+ KIntSpinBox *m_startAtLineSpinBox;
+ KexiCSVTextQuoteComboBox* m_comboQuote;
+ QLabel* m_startAtLineLabel;
+ QLabel* TextLabel2;
+ QCheckBox* m_ignoreDuplicates;
+ QCheckBox* m_1stRowForFieldNames;
+ QCheckBox* m_primaryKeyField;
+
+ KexiMainWindow* m_mainWin;
+
+ void detectTypeAndUniqueness(int row, int col, const QString& text);
+ void setText(int row, int col, const QString& text, bool inGUI);
+
+ /*! Parses date from \a text and stores into \a date.
+ m_dateRegExp is used for clever detection;
+ if '/' separated is found, it's assumed the format is american mm/dd/yyyy.
+ This function supports omitted zeros, so 1/2/2006 is parsed properly too.
+ \return true on success. */
+ bool parseDate(const QString& text, QDate& date);
+
+ /*! Parses time from \a text and stores into \a date.
+ m_timeRegExp1 and m_timeRegExp2 are used for clever detection;
+ both hh:mm:ss and hh:mm are supported.
+ This function supports omitted zeros, so 1:2:3 is parsed properly too.
+ \return true on success. */
+ bool parseTime(const QString& text, QTime& time);
+
+ /*! Called after the first fillTable() when number of rows is unknown. */
+ void adjustRows(int iRows);
+
+ int getHeader(int col);
+ QString getText(int row, int col);
+ void updateColumnText(int col);
+ void updateRowCountInfo();
+ tristate loadRows(QString &field, int &row, int &columnm, int &maxColumn, bool inGUI);
+
+ /*! Detects delimiter by looking at first 4K bytes of the data. Used by loadRows().
+ The used algorithm:
+ 1. Look byte by byte and locate special characters that can be delimiters.
+ Special fact is taken into account: if there are '"' quotes used for text values,
+ delimiters that follow directly the closing quote has higher priority than the one
+ that follows other character. We do not assume that every text value is quoted.
+ Summing up, there is following hierarchy (from highest to lowest):
+ quote+tab, quote+semicolon, quote+comma, tab, semicolon, comma.
+ Space characters are skipped. Text inside quotes is skipped, as well as double
+ (escaped) quotes.
+ 2. While scanning the data, for every row following number of tabs, semicolons and commas
+ (only these outside of the quotes) are computed. On every line the values are appended
+ to a separate list (QValueList<int>).
+ 3. After scanning, all the values are checked on the QValueList<int> of tabs.
+ If the list has more one element (so there was more than one row) and all the values
+ (numbers of tabs) are equal, it's very probable the tab is a delimiter.
+ So, this character is returned as a delimiter.
+ 3a. The same algorithm as in 3. is performed for semicolon character.
+ 3b. The same algorithm as in 3. is performed for comma character.
+ 4. If the step 3. did not return a delimiter, a character found in step 1. with
+ the highest priority is retured as delimiter. */
+ QString detectDelimiterByLookingAtFirstBytesOfFile(QTextStream& inputStream);
+
+ /*! Callback, called whenever row is loaded in loadRows(). When inGUI is true,
+ nothing is performed, else database buffer is written back to the database. */
+ bool saveRow(bool inGUI);
+
+ bool m_cancelled;
+ bool m_adjustRows;
+ int m_startline;
+ QChar m_textquote;
+ QString m_clipboardData;
+ QByteArray m_fileArray;
+ Mode m_mode;
+ int m_prevSelectedCol;
+
+ //! vector of detected types, 0==text (the default), 1==number, 2==date
+//! @todo more types
+ QValueVector<int> m_detectedTypes;
+
+ //! m_detectedUniqueColumns[i]==true means that i-th column has unique values
+ //! (only for numeric type)
+ QPtrVector< QValueList<int> > m_uniquenessTest;
+
+ QRegExp m_dateRegExp, m_timeRegExp1, m_timeRegExp2, m_fpNumberRegExp;
+ QValueVector<QString> m_typeNames, m_columnNames;
+ QBitArray m_changedColumnNames;
+ bool m_columnsAdjusted : 1; //!< to call adjustColumn() only once
+ bool m_1stRowForFieldNamesDetected : 1; //!< used to force rerun fillTable() after 1st row
+ bool m_firstFillTableCall : 1; //!< used to know whether it's 1st fillTable() call
+ bool m_blockUserEvents : 1;
+ int m_primaryKeyColumn; //!< index of column with PK assigned (-1 if none)
+ int m_maximumRowsForPreview;
+ int m_maximumBytesForPreview;
+ QPixmap m_pkIcon;
+ QString m_fname;
+ QFile* m_file;
+ QTextStream *m_inputStream; //!< used in loadData()
+ KexiCSVImportOptions m_options;
+ KProgressDialog *m_loadingProgressDlg, *m_importingProgressDlg;
+ bool m_dialogCancelled;
+ KexiCSVInfoLabel *m_infoLbl;
+ KexiDB::Connection *m_conn; //!< (temp) database connection used for importing
+ KexiDB::TableSchema *m_destinationTableSchema; //!< (temp) dest. table schema used for importing
+ KexiDB::PreparedStatement::Ptr m_importingStatement;
+ QValueList<QVariant> m_dbRowBuffer; //!< (temp) used for importing
+ bool m_implicitPrimaryKeyAdded; //!< (temp) used for importing
+ bool m_allRowsLoadedInPreview; //!< we need to know whether all rows were loaded or it's just a partial data preview
+ bool m_stoppedAt_MAX_BYTES_TO_PREVIEW; //!< used to compute m_allRowsLoadedInPreview
+
+ private slots:
+ void fillTable();
+ void fillTableLater();
+ void initLater();
+ void formatChanged(int id);
+ void delimiterChanged(const QString& delimiter);
+ void startlineSelected(int line);
+ void textquoteSelected(int);
+ void currentCellChanged(int, int col);
+ void ignoreDuplicatesChanged(int);
+ void slot1stRowForFieldNamesChanged(int);
+ void cellValueChanged(int row,int col);
+ void optionsButtonClicked();
+ void slotPrimaryKeyFieldToggled(bool on);
+};
+
+#endif
diff --git a/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp
new file mode 100644
index 000000000..b381dde3f
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp
@@ -0,0 +1,140 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexicsvimportoptionsdlg.h"
+#include <widget/kexicharencodingcombobox.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtextcodec.h>
+#include <qcheckbox.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kcombobox.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kcharsets.h>
+
+KexiCSVImportOptions::KexiCSVImportOptions()
+{
+ kapp->config()->setGroup("ImportExport");
+ encoding = kapp->config()->readEntry("DefaultEncodingForImportingCSVFiles");
+ if (encoding.isEmpty()) {
+ encoding = QString::fromLatin1(KGlobal::locale()->encoding());
+ defaultEncodingExplicitySet = false;
+ }
+ else
+ defaultEncodingExplicitySet = true;
+
+ stripWhiteSpaceInTextValuesChecked
+ = kapp->config()->readBoolEntry("StripBlanksOffOfTextValuesWhenImportingCSVFiles", true);
+}
+
+KexiCSVImportOptions::~KexiCSVImportOptions()
+{
+}
+
+bool KexiCSVImportOptions::operator== ( const KexiCSVImportOptions & opt ) const
+{
+ return defaultEncodingExplicitySet==opt.defaultEncodingExplicitySet
+ && stripWhiteSpaceInTextValuesChecked==opt.stripWhiteSpaceInTextValuesChecked
+ && encoding==opt.encoding;
+}
+
+bool KexiCSVImportOptions::operator!= ( const KexiCSVImportOptions & opt ) const
+{
+ return !( *this==opt );
+}
+
+//----------------------------------
+
+KexiCSVImportOptionsDialog::KexiCSVImportOptionsDialog(
+ const KexiCSVImportOptions& options, QWidget* parent )
+ : KDialogBase(
+ KDialogBase::Plain,
+ i18n( "CSV Import Options" ),
+ Ok|Cancel,
+ Ok,
+ parent,
+ "KexiCSVImportOptionsDialog",
+ true,
+ false
+ )
+{
+ QGridLayout *lyr = new QGridLayout( plainPage(), 5, 3,
+ KDialogBase::marginHint(), KDialogBase::spacingHint());
+
+ m_encodingComboBox = new KexiCharacterEncodingComboBox(plainPage(), options.encoding);
+ lyr->addWidget( m_encodingComboBox, 0, 1 );
+
+ QLabel* lbl = new QLabel( m_encodingComboBox, i18n("Text encoding:"), plainPage());
+ lyr->addWidget( lbl, 0, 0 );
+
+ lyr->addItem( new QSpacerItem( 20, KDialogBase::spacingHint(), QSizePolicy::Fixed, QSizePolicy::Fixed ), 2, 1 );
+ lyr->addItem( new QSpacerItem( 121, KDialogBase::spacingHint(), QSizePolicy::Expanding, QSizePolicy::Minimum ), 0, 2 );
+
+ m_chkAlwaysUseThisEncoding = new QCheckBox(
+ i18n("Always use this encoding when importing CSV data files"), plainPage());
+ lyr->addWidget( m_chkAlwaysUseThisEncoding, 1, 1 );
+
+ m_chkStripWhiteSpaceInTextValues = new QCheckBox(
+ i18n("Strip leading and trailing blanks off of text values"), plainPage());
+ lyr->addWidget( m_chkStripWhiteSpaceInTextValues, 3, 1 );
+ lyr->addItem( new QSpacerItem( 20, KDialogBase::spacingHint(), QSizePolicy::Minimum, QSizePolicy::Expanding ), 4, 1 );
+
+ //update widgets
+ if (options.defaultEncodingExplicitySet) {
+ m_encodingComboBox->setSelectedEncoding(options.encoding);
+ m_chkAlwaysUseThisEncoding->setChecked(true);
+ }
+ m_chkStripWhiteSpaceInTextValues->setChecked(options.stripWhiteSpaceInTextValuesChecked);
+
+ adjustSize();
+ m_encodingComboBox->setFocus();
+}
+
+KexiCSVImportOptionsDialog::~KexiCSVImportOptionsDialog()
+{
+}
+
+KexiCSVImportOptions KexiCSVImportOptionsDialog::options() const
+{
+ KexiCSVImportOptions opt;
+ opt.encoding = m_encodingComboBox->selectedEncoding();
+ opt.stripWhiteSpaceInTextValuesChecked = m_chkStripWhiteSpaceInTextValues->isChecked();
+ return opt;
+}
+
+void KexiCSVImportOptionsDialog::accept()
+{
+ kapp->config()->setGroup("ImportExport");
+ if (m_chkAlwaysUseThisEncoding->isChecked())
+ kapp->config()->writeEntry("DefaultEncodingForImportingCSVFiles",
+ m_encodingComboBox->selectedEncoding());
+ else
+ kapp->config()->deleteEntry("DefaultEncodingForImportingCSVFiles");
+
+ kapp->config()->writeEntry("StripBlanksOffOfTextValuesWhenImportingCSVFiles",
+ m_chkStripWhiteSpaceInTextValues->isChecked());
+
+ KDialogBase::accept();
+}
+
+#include "kexicsvimportoptionsdlg.moc"
diff --git a/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h
new file mode 100644
index 000000000..e0567c9cf
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h
@@ -0,0 +1,62 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXICSVOPTIONSDIALOG_H
+#define KEXICSVOPTIONSDIALOG_H
+
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+
+class KexiCharacterEncodingComboBox;
+
+//! @short CSV Options
+class KexiCSVImportOptions
+{
+ public:
+ KexiCSVImportOptions();
+ ~KexiCSVImportOptions();
+
+ bool operator== ( const KexiCSVImportOptions & opt ) const;
+ bool operator!= ( const KexiCSVImportOptions & opt ) const;
+
+ QString encoding;
+ bool defaultEncodingExplicitySet;
+ bool stripWhiteSpaceInTextValuesChecked;
+};
+
+//! @short CSV Options dialog
+class KexiCSVImportOptionsDialog : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ KexiCSVImportOptionsDialog( const KexiCSVImportOptions& options, QWidget* parent = 0 );
+ virtual ~KexiCSVImportOptionsDialog();
+
+ KexiCSVImportOptions options() const;
+
+ protected slots:
+ virtual void accept();
+
+ protected:
+ KexiCharacterEncodingComboBox *m_encodingComboBox;
+ QCheckBox *m_chkAlwaysUseThisEncoding;
+ QCheckBox *m_chkStripWhiteSpaceInTextValues;
+};
+
+#endif
diff --git a/kexi/plugins/importexport/csv/kexicsvwidgets.cpp b/kexi/plugins/importexport/csv/kexicsvwidgets.cpp
new file mode 100644
index 000000000..8e3cf4c2c
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvwidgets.cpp
@@ -0,0 +1,233 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicsvwidgets.h"
+
+#include <qdir.h>
+#include <qlabel.h>
+#include <qlayout.h>
+
+#include <klocale.h>
+#include <klineedit.h>
+#include <kdialogbase.h>
+#include <kactivelabel.h>
+#include <kiconloader.h>
+#include <kmimetype.h>
+
+#define KEXICSV_OTHER_DELIMITER_INDEX 4
+
+KexiCSVDelimiterWidget::KexiCSVDelimiterWidget( bool lineEditOnBottom, QWidget * parent )
+ : QWidget(parent, "KexiCSVDelimiterWidget")
+ , m_availableDelimiters(KEXICSV_OTHER_DELIMITER_INDEX)
+
+{
+ QBoxLayout *lyr =
+ lineEditOnBottom ?
+ (QBoxLayout *)new QVBoxLayout( this, 0, KDialogBase::spacingHint() )
+ : (QBoxLayout *)new QHBoxLayout( this, 0, KDialogBase::spacingHint() );
+
+ m_availableDelimiters[0]=KEXICSV_DEFAULT_FILE_DELIMITER;
+ m_availableDelimiters[1]=";";
+ m_availableDelimiters[2]="\t";
+ m_availableDelimiters[3]=" ";
+
+ m_combo = new KComboBox(this, "KexiCSVDelimiterComboBox");
+ m_combo->insertItem( i18n("Comma \",\"") ); //<-- KEXICSV_DEFAULT_FILE_DELIMITER
+ m_combo->insertItem( i18n( "Semicolon \";\"" ) );
+ m_combo->insertItem( i18n( "Tabulator" ) );
+ m_combo->insertItem( i18n( "Space \" \"" ) );
+ m_combo->insertItem( i18n( "Other" ) );
+ lyr->addWidget(m_combo);
+ setFocusProxy(m_combo);
+
+ m_delimiterEdit = new KLineEdit( this, "m_delimiterEdit" );
+// m_delimiterEdit->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0, m_delimiterEdit->sizePolicy().hasHeightForWidth() ) );
+ m_delimiterEdit->setMaximumSize( QSize( 30, 32767 ) );
+ m_delimiterEdit->setMaxLength(1);
+ lyr->addWidget( m_delimiterEdit );
+ if (!lineEditOnBottom)
+ lyr->addStretch(2);
+
+ slotDelimiterChangedInternal(KEXICSV_DEFAULT_FILE_DELIMITER_INDEX); //this will init m_delimiter
+ connect(m_combo, SIGNAL(activated(int)),
+ this, SLOT(slotDelimiterChanged(int)));
+ connect(m_delimiterEdit, SIGNAL(returnPressed()),
+ this, SLOT(slotDelimiterLineEditReturnPressed()));
+ connect(m_delimiterEdit, SIGNAL(textChanged( const QString & )),
+ this, SLOT(slotDelimiterLineEditTextChanged( const QString & ) ));
+}
+
+void KexiCSVDelimiterWidget::slotDelimiterChanged(int index)
+{
+ slotDelimiterChangedInternal(index);
+ if (index==KEXICSV_OTHER_DELIMITER_INDEX)
+ m_delimiterEdit->setFocus();
+}
+
+void KexiCSVDelimiterWidget::slotDelimiterChangedInternal(int index)
+{
+ bool changed = false;
+ if (index > KEXICSV_OTHER_DELIMITER_INDEX)
+ return;
+ else if (index == KEXICSV_OTHER_DELIMITER_INDEX) {
+ changed = m_delimiter != m_delimiterEdit->text();
+ m_delimiter = m_delimiterEdit->text();
+ }
+ else {
+ changed = m_delimiter != m_availableDelimiters[index];
+ m_delimiter = m_availableDelimiters[index];
+ }
+ m_delimiterEdit->setEnabled(index == KEXICSV_OTHER_DELIMITER_INDEX);
+ if (changed)
+ emit delimiterChanged(m_delimiter);
+}
+
+void KexiCSVDelimiterWidget::slotDelimiterLineEditReturnPressed()
+{
+ if (m_combo->currentItem() != KEXICSV_OTHER_DELIMITER_INDEX)
+ return;
+ slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX);
+}
+
+void KexiCSVDelimiterWidget::slotDelimiterLineEditTextChanged( const QString & )
+{
+ slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX);
+}
+
+void KexiCSVDelimiterWidget::setDelimiter(const QString& delimiter)
+{
+ QValueVector<QString>::ConstIterator it = m_availableDelimiters.constBegin();
+ int index = 0;
+ for (; it != m_availableDelimiters.constEnd(); ++it, index++) {
+ if (*it == delimiter) {
+ m_combo->setCurrentItem(index);
+ slotDelimiterChangedInternal(index);
+ return;
+ }
+ }
+ //else: set other (custom) delimiter
+ m_delimiterEdit->setText(delimiter);
+ m_combo->setCurrentItem(KEXICSV_OTHER_DELIMITER_INDEX);
+ slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX);
+}
+
+//----------------------------------------------------
+
+KexiCSVTextQuoteComboBox::KexiCSVTextQuoteComboBox( QWidget * parent )
+ : KComboBox(parent, "KexiCSVTextQuoteComboBox")
+{
+ insertItem( "\"" );
+ insertItem( "'" );
+ insertItem( i18n( "None" ) );
+}
+
+QString KexiCSVTextQuoteComboBox::textQuote() const
+{
+ if (currentItem()==2)
+ return QString::null;
+ return currentText();
+}
+
+void KexiCSVTextQuoteComboBox::setTextQuote(const QString& textQuote)
+{
+ if (textQuote=="\"" || textQuote=="'")
+ setCurrentText(textQuote);
+ else if (textQuote.isEmpty())
+ setCurrentText(i18n( "None" ));
+}
+
+//----------------------------------------------------
+
+KexiCSVInfoLabel::KexiCSVInfoLabel( const QString& labelText, QWidget* parent )
+ : QWidget(parent, "KexiCSVInfoLabel")
+{
+ QVBoxLayout *vbox = new QVBoxLayout( this, 0, KDialogBase::spacingHint() );
+ QHBoxLayout *hbox = new QHBoxLayout( this );
+ vbox->addLayout(hbox);
+ m_leftLabel = new QLabel(labelText, this);
+ m_leftLabel->setMinimumWidth(130);
+ m_leftLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ m_leftLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak);
+ hbox->addWidget(m_leftLabel);
+ m_iconLbl = new QLabel(this);
+ m_iconLbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ m_iconLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
+ m_fnameLbl = new KActiveLabel(this);
+ m_fnameLbl->setFocusPolicy(NoFocus);
+ m_fnameLbl->setTextFormat(Qt::PlainText);
+ m_fnameLbl->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding,1,0));
+ m_fnameLbl->setLineWidth(1);
+ m_fnameLbl->setFrameStyle(QFrame::Box);
+ m_fnameLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak);
+ hbox->addSpacing(5);
+ hbox->addWidget(m_iconLbl);
+ hbox->addWidget(m_fnameLbl, 1, Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak);
+ hbox->addSpacing(10);
+ m_commentLbl = new KActiveLabel(this);
+ m_commentLbl->setFocusPolicy(NoFocus);
+ m_commentLbl->setTextFormat(Qt::PlainText);
+ m_commentLbl->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+ m_commentLbl->setLineWidth(1);
+ m_commentLbl->setFrameStyle(QFrame::Box);
+ m_commentLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak);
+ hbox->addWidget(m_commentLbl, 0, Qt::AlignVCenter | Qt::AlignRight | Qt::WordBreak);
+
+ m_separator = new QFrame(this);
+ m_separator->setFrameShape(QFrame::HLine);
+ m_separator->setFrameShadow(QFrame::Sunken);
+ vbox->addWidget(m_separator);
+}
+
+void KexiCSVInfoLabel::setFileName( const QString& fileName )
+{
+ m_fnameLbl->setText( QDir::convertSeparators(fileName) );
+ if (!fileName.isEmpty()) {
+ m_iconLbl->setPixmap(
+ KMimeType::pixmapForURL(KURL::fromPathOrURL(fileName), 0, KIcon::Desktop) );
+ }
+}
+
+void KexiCSVInfoLabel::setLabelText( const QString& text )
+{
+ m_fnameLbl->setText( text );
+// int lines = m_fnameLbl->lines();
+// m_fnameLbl->setFixedHeight(
+// QFontMetrics(m_fnameLbl->currentFont()).height() * lines );
+}
+
+void KexiCSVInfoLabel::setIcon(const QString& iconName)
+{
+ m_iconLbl->setPixmap( DesktopIcon(iconName) );
+}
+
+void KexiCSVInfoLabel::setCommentText( const QString& text )
+{
+ m_commentLbl->setText(text);
+}
+
+//----------------------------------------------------
+
+QStringList csvMimeTypes()
+{
+ QStringList mimetypes;
+ mimetypes << "text/x-csv" << "text/plain" << "all/allfiles";
+ return mimetypes;
+}
+
+#include "kexicsvwidgets.moc"
diff --git a/kexi/plugins/importexport/csv/kexicsvwidgets.h b/kexi/plugins/importexport/csv/kexicsvwidgets.h
new file mode 100644
index 000000000..f128b658f
--- /dev/null
+++ b/kexi/plugins/importexport/csv/kexicsvwidgets.h
@@ -0,0 +1,116 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_CSVWIDGETS_H
+#define KEXI_CSVWIDGETS_H
+
+#include <qvaluevector.h>
+#include <kcombobox.h>
+
+class KLineEdit;
+class KActiveLabel;
+class QLabel;
+
+#define KEXICSV_DEFAULT_FILE_TEXT_QUOTE "\""
+#define KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE ""
+#define KEXICSV_DEFAULT_FILE_DELIMITER ","
+#define KEXICSV_DEFAULT_CLIPBOARD_DELIMITER "\t"
+#define KEXICSV_DEFAULT_FILE_DELIMITER_INDEX 0
+
+//! \return a list of mimetypes usable for handling CSV format handling
+QStringList csvMimeTypes();
+
+/*! @short A helper widget showing a short text information with an icon.
+ See ctor for details.
+ Used by CSV import and export dialogs. */
+class KexiCSVInfoLabel : public QWidget
+{
+ public:
+ /* Sets up a new info label \a labelText label with text like "Preview of data from file:".
+ setFileName() can be used to display filename and setCommentAfterFileName() to display
+ additional comment.
+
+ The widget's layout can look like this:
+
+ \pre [icon] [labeltext] [filename] [comment]
+ */
+ KexiCSVInfoLabel( const QString& labelText, QWidget* parent );
+
+ void setFileName( const QString& fileName );
+ void setLabelText( const QString& text );
+ void setCommentText( const QString& text );
+// void setIconForFileName();
+
+ //! sets icon pixmap to \a iconName. Used wher setIconForFilename was false in ctor.
+ void setIcon(const QString& iconName);
+
+ QLabel* leftLabel() const { return m_leftLabel; }
+ KActiveLabel* fileNameLabel() const { return m_fnameLbl; }
+ KActiveLabel* commentLabel() const { return m_commentLbl; }
+ QFrame* separator() const { return m_separator; }
+
+ protected:
+ QLabel *m_leftLabel, *m_iconLbl;
+ KActiveLabel *m_fnameLbl, *m_commentLbl;
+ QFrame* m_separator;
+};
+
+//! @short A combo box widget providing a list of possible delimiters
+//! Used by CSV import and export dialogs
+class KexiCSVDelimiterWidget : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiCSVDelimiterWidget( bool lineEditOnBottom, QWidget * parent = 0 );
+
+ QString delimiter() const { return m_delimiter; }
+ void setDelimiter(const QString& delimiter);
+
+ signals:
+ void delimiterChanged(const QString& delimiter);
+
+ protected slots:
+ //! only called when a delimiter was set by user directly
+ void slotDelimiterChanged(int idx);
+ void slotDelimiterChangedInternal(int idx);
+ void slotDelimiterLineEditTextChanged( const QString & );
+ void slotDelimiterLineEditReturnPressed();
+
+ protected:
+ QString m_delimiter;
+ QValueVector<QString> m_availableDelimiters;
+ KComboBox* m_combo;
+ KLineEdit* m_delimiterEdit;
+};
+
+//! @short A combo box widget providing a list of possible quote characters
+//! Used by CSV import and export dialogs
+class KexiCSVTextQuoteComboBox : public KComboBox
+{
+ public:
+ KexiCSVTextQuoteComboBox( QWidget * parent = 0 );
+
+ QString textQuote() const;
+
+ //! Sets text quote. Only available are: ", ', and empty string.
+ void setTextQuote(const QString& textQuote);
+};
+
+#endif
diff --git a/kexi/plugins/macros/Makefile.am b/kexi/plugins/macros/Makefile.am
new file mode 100644
index 000000000..cf5fb0d46
--- /dev/null
+++ b/kexi/plugins/macros/Makefile.am
@@ -0,0 +1,9 @@
+if include_kunittest
+
+ # Unittest is disabled per default.
+ # TESTSDIR = tests
+
+endif
+
+METASOURCES = AUTO
+SUBDIRS = lib kexiactions kexipart
diff --git a/kexi/plugins/macros/configure.in.in b/kexi/plugins/macros/configure.in.in
new file mode 100644
index 000000000..52fb9f5a0
--- /dev/null
+++ b/kexi/plugins/macros/configure.in.in
@@ -0,0 +1,13 @@
+# Check for kunittest
+AC_MSG_CHECKING([for kunittest])
+
+# First we check if the console unittester could be compiled
+have_kunittest_header="no"
+KDE_CHECK_HEADER(kunittest/tester.h, have_kunittest_header="yes", , )
+AM_CONDITIONAL(include_kunittest, test "$have_kunittest_header" = "yes")
+
+# Second we check if the GUI-unittester could be compiled
+have_kunittestgui_header="no"
+KDE_CHECK_HEADER(kunittest/runnergui.h, have_kunittestgui_header="yes", , )
+AM_CONDITIONAL(include_kunittestgui, test "$have_kunittestgui_header" = "yes")
+
diff --git a/kexi/plugins/macros/kexiactions/Makefile.am b/kexi/plugins/macros/kexiactions/Makefile.am
new file mode 100644
index 000000000..4f42e5e95
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/Makefile.am
@@ -0,0 +1,27 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_LTLIBRARIES = libkeximacroactions.la
+
+libkeximacroactions_la_SOURCES = \
+ kexiaction.cpp \
+ openaction.cpp \
+ executeaction.cpp \
+ navigateaction.cpp \
+ messageaction.cpp \
+ datatableaction.cpp
+
+libkeximacroactions_la_CXXFLAGS = $(USE_EXCEPTIONS)
+
+libkeximacroactions_la_LDFLAGS = $(all_libraries)
+libkeximacroactions_la_LIBADD = \
+ $(top_builddir)/kexi/plugins/macros/lib/libkomacro.la \
+ $(top_builddir)/kexi/core/libkexicore.la \
+ $(LIB_QT) $(LIB_KDECORE) $(LIB_KDEUI)
+
+libkeximacroactions_la_METASOURCES = AUTO
+SUBDIRS = .
+
+INCLUDES = \
+ -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/kexi \
+ $(all_includes)
diff --git a/kexi/plugins/macros/kexiactions/datatableaction.cpp b/kexi/plugins/macros/kexiactions/datatableaction.cpp
new file mode 100644
index 000000000..90b13e4f4
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/datatableaction.cpp
@@ -0,0 +1,185 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "datatableaction.h"
+//#include "objectvariable.h"
+
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipartmanager.h>
+#include <core/kexipartinfo.h>
+#include <core/kexipart.h>
+#include <core/keximainwindow.h>
+#include <core/kexiinternalpart.h>
+
+#include <klocale.h>
+
+using namespace KexiMacro;
+
+namespace KexiMacro {
+
+ //static const QString OBJECT = "method";
+ //static const QString OBJECT = "type";
+ //static const QString OBJECT = "partitem";
+
+ template<class ACTIONIMPL>
+ class MethodVariable : public KexiVariable<ACTIONIMPL>
+ {
+ public:
+ MethodVariable(ACTIONIMPL* actionimpl)
+ : KexiVariable<ACTIONIMPL>(actionimpl, "method", i18n("Method"))
+ {
+ QStringList list;
+ list << "import" << "export";
+ this->appendChild( KSharedPtr<KoMacro::Variable>( new KoMacro::Variable(list, "@list") ) );
+
+ this->setVariant( list[0] );
+ }
+ };
+
+ template<class ACTIONIMPL>
+ class TypeVariable : public KexiVariable<ACTIONIMPL>
+ {
+ public:
+ TypeVariable(ACTIONIMPL* actionimpl)
+ : KexiVariable<ACTIONIMPL>(actionimpl, "type", i18n("Type"))
+ {
+ QStringList list;
+ list << "file" << "clipboard";
+ this->appendChild( KSharedPtr<KoMacro::Variable>( new KoMacro::Variable(list, "@list") ) );
+
+ this->setVariant( list[0] );
+ }
+ };
+
+ template<class ACTIONIMPL>
+ class PartItemVariable : public KexiVariable<ACTIONIMPL>
+ {
+ public:
+ PartItemVariable(ACTIONIMPL* actionimpl, const QString& partitem = QString::null)
+ : KexiVariable<ACTIONIMPL>(actionimpl, "partitem", i18n("Item"))
+ {
+ QStringList namelist;
+ if(actionimpl->mainWin()->project()) {
+ KexiPart::PartInfoList* parts = Kexi::partManager().partInfoList();
+ for(KexiPart::PartInfoListIterator it(*parts); it.current(); ++it) {
+ KexiPart::Info* info = it.current();
+ if(! info->isDataExportSupported())
+ continue;
+ KexiPart::ItemDict* items = actionimpl->mainWin()->project()->items(info);
+ if(items)
+ for(KexiPart::ItemDictIterator item_it = *items; item_it.current(); ++item_it)
+ namelist << info->objectName() + "." + item_it.current()->name();
+ }
+ for(QStringList::Iterator it = namelist.begin(); it != namelist.end(); ++it)
+ this->appendChild( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(*it)) );
+
+ //const QString name = info->objectName(); //info->groupName();
+ //this->appendChild( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(name)) );
+ }
+ const QString n =
+ namelist.contains(partitem)
+ ? partitem
+ : namelist.count() > 0 ? namelist[0] : "";
+ this->setVariant(n);
+ kdDebug()<<"##################### KexiActions::ObjectVariable() variant="<<this->variant()<<endl;
+ }
+ };
+
+}
+
+DataTableAction::DataTableAction()
+ : KexiAction("datatable", i18n("Data Table"))
+{
+ setVariable(KSharedPtr<KoMacro::Variable>( new MethodVariable<DataTableAction>(this) ));
+ setVariable(KSharedPtr<KoMacro::Variable>( new TypeVariable<DataTableAction>(this) ));
+ setVariable(KSharedPtr<KoMacro::Variable>( new PartItemVariable<DataTableAction>(this) ));
+}
+
+DataTableAction::~DataTableAction()
+{
+}
+
+bool DataTableAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name)
+{
+ kdDebug()<<"DataTableAction::notifyUpdated() name="<<name<<" macroitem.action="<<(macroitem->action() ? macroitem->action()->name() : "NOACTION")<<endl;
+ /*
+ KSharedPtr<KoMacro::Variable> variable = macroitem->variable(name, false);
+ if(! variable) {
+ kdWarning()<<"DataTableAction::notifyUpdated() No such variable="<<name<<" in macroitem."<<endl;
+ return false;
+ }
+ variable->clearChildren();
+ if(name == "method") {
+ const int partitem = macroitem->variant(OBJECT, true).toString();
+ macroitem->variable(OBJECT, true)->setChildren(
+ KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new ObjectVariable<ExecuteAction>(this, partitem)) );
+ }
+ */
+ return true;
+}
+
+void DataTableAction::activate(KSharedPtr<KoMacro::Context> context)
+{
+ if(! mainWin()->project()) {
+ kdWarning() << "ExecuteAction::activate(KSharedPtr<KoMacro::Context>) Invalid project" << endl;
+ return;
+ }
+
+ const QString method = context->variable("method")->variant().toString();
+ const QString type = context->variable("type")->variant().toString();
+
+ const QString partitem = context->variable("partitem")->variant().toString();
+ QString identifier;
+ if(! partitem.isEmpty()) {
+ QStringList parts = QStringList::split(".", partitem);
+ KexiPart::Part* part = Kexi::partManager().partForMimeType( QString("kexi/%1").arg(parts[0]) );
+ KexiPart::Item* item = part ? mainWin()->project()->item(part->info(), parts[1]) : 0;
+ if(! item)
+ throw KoMacro::Exception(i18n("No such item \"%1\"").arg(partitem));
+ identifier = QString::number(item->identifier());
+ }
+
+ QMap<QString,QString> args;
+ if(! identifier.isNull())
+ args.insert("itemId", identifier);
+
+ if(method == "import") {
+ args.insert("sourceType", type);
+ QDialog *dlg = KexiInternalPart::createModalDialogInstance(
+ "csv_importexport", "KexiCSVImportDialog", 0, mainWin(), 0, &args);
+ if (!dlg)
+ return; //error msg has been shown by KexiInternalPart
+ dlg->exec();
+ delete dlg;
+ }
+ else if(method == "export") {
+ args.insert("destinationType", type);
+ QDialog *dlg = KexiInternalPart::createModalDialogInstance(
+ "csv_importexport", "KexiCSVExportWizard", 0, mainWin(), 0, &args);
+ if (!dlg)
+ return; //error msg has been shown by KexiInternalPart
+ dlg->exec();
+ delete dlg;
+ }
+ else {
+ throw KoMacro::Exception(i18n("No such method \"%1\"").arg(method));
+ }
+}
+
+//#include "executeaction.moc"
diff --git a/kexi/plugins/macros/kexiactions/datatableaction.h b/kexi/plugins/macros/kexiactions/datatableaction.h
new file mode 100644
index 000000000..3b5b32c0c
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/datatableaction.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_DATATABLEACTION_H
+#define KEXIMACRO_DATATABLEACTION_H
+
+#include "kexiaction.h"
+
+class KexiMainWindow;
+
+namespace KoMacro {
+ class Context;
+}
+
+namespace KexiMacro {
+
+ /**
+ * The DataTableAction class implements a @a KoMacro::Action
+ * to provide functionality to import or export a datatable.
+ * The datatable is used to deal with comma separated values.
+ */
+ class DataTableAction : public KexiAction
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ DataTableAction();
+
+ /**
+ * Destructor.
+ */
+ virtual ~DataTableAction();
+
+ /**
+ * This function is called, when the @a KoMacro::Variable
+ * with name @p name used within the @a KoMacro::MacroItem
+ * @p macroitem got changed.
+ *
+ * @param macroitem The @a KoMacro::MacroItem instance where
+ * the variable defined with @p name is located in.
+ * @param name The name the @a KoMacro::Variable has.
+ * @return true if the update was successfully else false
+ * is returned.
+ */
+ virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name);
+
+ public slots:
+
+ /**
+ * Called if the @a Action should be executed within the
+ * defined @p context .
+ */
+ virtual void activate(KSharedPtr<KoMacro::Context> context);
+
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexiactions/executeaction.cpp b/kexi/plugins/macros/kexiactions/executeaction.cpp
new file mode 100644
index 000000000..1e7f24a25
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/executeaction.cpp
@@ -0,0 +1,96 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "executeaction.h"
+
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipartmanager.h>
+#include <core/kexipartinfo.h>
+#include <core/kexipart.h>
+#include <core/keximainwindow.h>
+
+#include <klocale.h>
+
+using namespace KexiMacro;
+
+namespace KexiMacro {
+ static const QString OBJECT = "object";
+ static const QString NAME = "name";
+}
+
+ExecuteAction::ExecuteAction()
+ : KexiAction("execute", i18n("Execute"))
+{
+ int conditions = ObjectVariable<ExecuteAction>::VisibleInNav | ObjectVariable<ExecuteAction>::Executable;
+ KSharedPtr<KoMacro::Variable> objvar = new ObjectVariable<ExecuteAction>(this, conditions);
+ setVariable(objvar);
+
+ setVariable(KSharedPtr<KoMacro::Variable>( new ObjectNameVariable<ExecuteAction>(this, objvar->variant().toString()) ));
+}
+
+ExecuteAction::~ExecuteAction()
+{
+}
+
+bool ExecuteAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name)
+{
+ kdDebug()<<"ExecuteAction::notifyUpdated() name="<<name<<" macroitem.action="<<(macroitem->action() ? macroitem->action()->name() : "NOACTION")<<endl;
+ KSharedPtr<KoMacro::Variable> variable = macroitem->variable(name, false);
+ if(! variable) {
+ kdWarning()<<"ExecuteAction::notifyUpdated() No such variable="<<name<<" in macroitem."<<endl;
+ return false;
+ }
+
+ variable->clearChildren();
+ if(name == OBJECT) {
+ const QString objectvalue = macroitem->variant(OBJECT, true).toString(); // e.g. "macro" or "script"
+ const QString objectname = macroitem->variant(NAME, true).toString(); // e.g. "macro1" or "macro2" if objectvalue above is "macro"
+ macroitem->variable(NAME, true)->setChildren(
+ KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new ObjectNameVariable<ExecuteAction>(this, objectvalue, objectname)) );
+ }
+
+ return true;
+}
+
+void ExecuteAction::activate(KSharedPtr<KoMacro::Context> context)
+{
+ if(! mainWin()->project()) {
+ kdWarning() << "ExecuteAction::activate(KSharedPtr<KoMacro::Context>) Invalid project" << endl;
+ return;
+ }
+
+ const QString mimetype = QString("kexi/%1").arg( context->variable("object")->variant().toString() );
+ const QString name = context->variable("name")->variant().toString();
+
+ KexiPart::Part* part = Kexi::partManager().partForMimeType(mimetype);
+ if(! part) {
+ throw KoMacro::Exception(i18n("No such mimetype \"%1\"").arg(mimetype));
+ }
+
+ KexiPart::Item* item = mainWin()->project()->item(part->info(), name);
+ if(! item) {
+ throw KoMacro::Exception(i18n("Failed to open part \"%1\" for mimetype \"%2\"").arg(name).arg(mimetype));
+ }
+
+ part->execute(item);
+}
+
+//#include "executeaction.moc"
diff --git a/kexi/plugins/macros/kexiactions/executeaction.h b/kexi/plugins/macros/kexiactions/executeaction.h
new file mode 100644
index 000000000..17a8ca883
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/executeaction.h
@@ -0,0 +1,78 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_EXECUTEACTION_H
+#define KEXIMACRO_EXECUTEACTION_H
+
+#include "kexiaction.h"
+
+class KexiMainWindow;
+
+namespace KoMacro {
+ class Context;
+}
+
+namespace KexiMacro {
+
+ /**
+ * The ExecuteAction class implements a @a KoMacro::Action
+ * to provide functionality to execute an object like
+ * e.g. a script or a macro.
+ */
+ class ExecuteAction : public KexiAction
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ ExecuteAction();
+
+ /**
+ * Destructor.
+ */
+ virtual ~ExecuteAction();
+
+ /**
+ * This function is called, when the @a KoMacro::Variable
+ * with name @p name used within the @a KoMacro::MacroItem
+ * @p macroitem got changed.
+ *
+ * @param macroitem The @a KoMacro::MacroItem instance where
+ * the variable defined with @p name is located in.
+ * @param name The name the @a KoMacro::Variable has.
+ * @return true if the update was successfully else false
+ * is returned.
+ */
+ virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name);
+
+ public slots:
+
+ /**
+ * Called if the @a Action should be executed within the
+ * defined @p context .
+ */
+ virtual void activate(KSharedPtr<KoMacro::Context> context);
+
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexiactions/kexiaction.cpp b/kexi/plugins/macros/kexiactions/kexiaction.cpp
new file mode 100644
index 000000000..521f8cfcf
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/kexiaction.cpp
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexiaction.h"
+#include "../lib/exception.h"
+
+#include <ksharedptr.h>
+
+using namespace KexiMacro;
+
+KexiAction::KexiAction(const QString& name, const QString& text)
+ : KoMacro::Action(name)
+{
+ m_mainwin = dynamic_cast< KexiMainWindow* >( KoMacro::Manager::self()->guiClient() );
+
+ if(! m_mainwin) {
+ throw KoMacro::Exception("Invalid KexiMainWindow instance.");
+ }
+
+ // Set the caption this action has.
+ setText(text);
+}
+
+KexiAction::~KexiAction()
+{
+}
+
+KexiMainWindow* KexiAction::mainWin() const
+{
+ return m_mainwin;
+}
diff --git a/kexi/plugins/macros/kexiactions/kexiaction.h b/kexi/plugins/macros/kexiactions/kexiaction.h
new file mode 100644
index 000000000..a61e2bc1d
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/kexiaction.h
@@ -0,0 +1,75 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_KEXIACTION_H
+#define KEXIMACRO_KEXIACTION_H
+
+#include "../lib/action.h"
+#include "../lib/variable.h"
+#include "../lib/macroitem.h"
+#include "../lib/context.h"
+
+#include "objectvariable.h"
+#include "objectnamevariable.h"
+
+#include <core/keximainwindow.h>
+
+namespace KexiMacro {
+
+ /**
+ * Template class to offer common functionality needed by all
+ * @a KoMacro::Action implementations Kexi provides.
+ *
+ * All the actions Kexi provides are inherited from this
+ * template class.
+ */
+ class KexiAction : public KoMacro::Action
+ {
+ public:
+
+ /**
+ * Constructor.
+ *
+ * @param name The unique name the @a KoMacro::Action has. This
+ * name will be used to identify the action.
+ * @param text The i18n-caption text used for display purposes.
+ */
+ KexiAction(const QString& name, const QString& text);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiAction();
+
+ /**
+ * @return the @a KexiMainWindow instance we are
+ * running in.
+ */
+ KexiMainWindow* mainWin() const;
+
+ private:
+
+ /// The @a KexiMainWindow instance.
+ KexiMainWindow* m_mainwin;
+
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexiactions/kexivariable.h b/kexi/plugins/macros/kexiactions/kexivariable.h
new file mode 100644
index 000000000..27dcc0efb
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/kexivariable.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_KEXIVARIABLE_H
+#define KEXIMACRO_KEXIVARIABLE_H
+
+#include "../lib/manager.h"
+#include "../lib/exception.h"
+#include "../lib/action.h"
+#include "../lib/variable.h"
+
+#include <ksharedptr.h>
+
+class KexiMainWindow;
+
+namespace KoMacro {
+ class Context;
+}
+
+namespace KexiMacro {
+
+ /**
+ * Template class to offer common functionality needed by all
+ * @a KoMacro::Variable implementations Kexi provides.
+ */
+ template<class ACTIONIMPL>
+ class KexiVariable : public KoMacro::Variable
+ {
+ public:
+
+ /**
+ * Constructor.
+ */
+ KexiVariable(ACTIONIMPL* actionimpl, const QString& name, const QString& caption)
+ : KoMacro::Variable()
+ , m_actionimpl(actionimpl)
+ {
+ setName(name);
+ setText(caption);
+ }
+
+ protected:
+
+ /**
+ * @return the @a KexiAction implementation this @a KexiVariable
+ * is a child of.
+ */
+ ACTIONIMPL* actionImpl() const
+ {
+ return m_actionimpl;
+ }
+
+ private:
+ /// The parent @a KexiAction implementation.
+ ACTIONIMPL* m_actionimpl;
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexiactions/messageaction.cpp b/kexi/plugins/macros/kexiactions/messageaction.cpp
new file mode 100644
index 000000000..1a4605cb1
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/messageaction.cpp
@@ -0,0 +1,50 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "messageaction.h"
+
+#include <core/keximainwindow.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+using namespace KexiMacro;
+
+MessageAction::MessageAction()
+ : KexiAction("message", i18n("Message"))
+{
+ setVariable("caption", i18n("Caption"), QString(""));
+ setVariable("message", i18n("Message"), QString(""));
+}
+
+MessageAction::~MessageAction()
+{
+}
+
+void MessageAction::activate(KSharedPtr<KoMacro::Context> context)
+{
+ kdDebug() << "MessageAction::activate(KSharedPtr<Context>)" << endl;
+ const QString caption = context->variable("caption")->variant().toString();
+ const QString message = context->variable("message")->variant().toString();
+ KMessageBox::information(mainWin(), message, caption);
+}
+
+//#include "messageaction.moc"
diff --git a/kexi/plugins/macros/kexiactions/messageaction.h b/kexi/plugins/macros/kexiactions/messageaction.h
new file mode 100644
index 000000000..543674bdf
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/messageaction.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_MESSAGEACTION_H
+#define KEXIMACRO_MESSAGEACTION_H
+
+
+#include "kexiaction.h"
+
+class KexiMainWindow;
+
+namespace KoMacro {
+ class Context;
+}
+
+namespace KexiMacro {
+
+ /**
+ * The ExecuteObject class implements a @a KoMacro::Action
+ * to provide functionality to execute an object like
+ * e.g. a script or a macro.
+ */
+ class MessageAction : public KexiAction
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ MessageAction();
+
+ /**
+ * Destructor.
+ */
+ virtual ~MessageAction();
+
+ public slots:
+
+ /**
+ * Called if the @a Action should be executed within the
+ * defined @param context .
+ */
+ virtual void activate(KSharedPtr<KoMacro::Context> context);
+
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexiactions/navigateaction.cpp b/kexi/plugins/macros/kexiactions/navigateaction.cpp
new file mode 100644
index 000000000..d3fe551ce
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/navigateaction.cpp
@@ -0,0 +1,158 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "navigateaction.h"
+
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipartmanager.h>
+#include <core/kexipartinfo.h>
+#include <core/kexipart.h>
+#include <core/keximainwindow.h>
+#include <core/kexidialogbase.h>
+
+#include <widget/kexidataawareview.h>
+#include <widget/tableview/kexidataawareobjectiface.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+using namespace KexiMacro;
+
+namespace KexiMacro {
+
+ template<class ACTIONIMPL>
+ class NavigateVariable : public KexiVariable<ACTIONIMPL>
+ {
+ public:
+ NavigateVariable(ACTIONIMPL* actionimpl)
+ : KexiVariable<ACTIONIMPL>(actionimpl, "record", i18n("Record"))
+ {
+ QStringList list;
+ list << "first" << "previous" << "next" << "last" << "goto";
+ this->appendChild( KSharedPtr<KoMacro::Variable>( new KoMacro::Variable(list, "@list") ) );
+
+ /*TODO should this actions belong to navigate? maybe it would be more wise to have
+ such kind of functionality in an own e.g. "Modify" action to outline, that
+ we are manipulating the database that way... */
+ //"add" << "save" << "delete" << "query" << "execute" << "cancel" << "reload"
+
+ this->setVariant( list[0] );
+ }
+ };
+
+}
+
+NavigateAction::NavigateAction()
+ : KexiAction("navigate", i18n("Navigate"))
+{
+ KoMacro::Variable* navvar = new NavigateVariable<NavigateAction>(this);
+ setVariable(KSharedPtr<KoMacro::Variable>( navvar ));
+
+ KoMacro::Variable* rowvar = new KexiVariable<NavigateAction>(this, "rownr", i18n("Row"));
+ rowvar->setVariant(0);
+ setVariable(KSharedPtr<KoMacro::Variable>(rowvar));
+
+ KoMacro::Variable* colvar = new KexiVariable<NavigateAction>(this, "colnr", i18n("Column"));
+ colvar->setVariant(0);
+ setVariable(KSharedPtr<KoMacro::Variable>(colvar));
+}
+
+NavigateAction::~NavigateAction()
+{
+}
+
+bool NavigateAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name)
+{
+ kdDebug()<<"NavigateAction::notifyUpdated() name="<<name<<" macroitem.action="<<(macroitem->action() ? macroitem->action()->name() : "NOACTION")<<endl;
+ KSharedPtr<KoMacro::Variable> variable = macroitem->variable(name, false);
+ if(! variable) {
+ kdWarning()<<"NavigateAction::notifyUpdated() No such variable="<<name<<" in macroitem."<<endl;
+ return false;
+ }
+
+ variable->clearChildren();
+ if(name == "goto") {
+ const int rownr = macroitem->variant("rownr", true).toInt(); // e.g. "macro" or "script"
+ const int colnr = macroitem->variant("colnr", true).toInt(); // e.g. "macro1" or "macro2" if objectvalue above is "macro"
+
+ macroitem->variable("rownr", true)->setChildren(
+ KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(rownr)) );
+ macroitem->variable("colnr", true)->setChildren(
+ KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(colnr)) );
+ }
+
+ return true;
+}
+
+void NavigateAction::activate(KSharedPtr<KoMacro::Context> context)
+{
+ KexiDialogBase* dialog = dynamic_cast<KexiDialogBase*>( mainWin()->activeWindow() );
+ if(! dialog) {
+ throw KoMacro::Exception(i18n("No window active."));
+ }
+
+ KexiViewBase* view = dialog->selectedView();
+ if(! view) {
+ throw KoMacro::Exception(i18n("No view selected for \"%1\".").arg(dialog->caption()));
+ }
+
+ KexiDataAwareView* dbview = dynamic_cast<KexiDataAwareView*>( view );
+ KexiDataAwareObjectInterface* dbobj = dbview ? dbview->dataAwareObject() : 0;
+ if(! dbview) {
+ throw KoMacro::Exception(i18n("The view for \"%1\" could not handle data.").arg(dialog->caption()));
+ }
+
+ const QString record = context->variable("record")->variant().toString();
+ if(record == "previous") {
+ dbobj->selectPrevRow();
+ }
+ else if(record == "next") {
+ dbobj->selectNextRow();
+ }
+ else if(record == "first") {
+ dbobj->selectFirstRow();
+ }
+ else if(record == "last") {
+ dbobj->selectLastRow();
+ }
+ else if(record == "goto") {
+ int rownr = context->variable("rownr")->variant().toInt() - 1;
+ int colnr = context->variable("colnr")->variant().toInt() - 1;
+ dbobj->setCursorPosition(rownr >= 0 ? rownr : dbobj->currentRow(), colnr >= 0 ? colnr : dbobj->currentColumn());
+ }
+ else {
+ /*
+ virtual void selectNextPage(); //!< page down action
+ virtual void selectPrevPage(); //!< page up action
+ void deleteAllRows();
+ void deleteCurrentRow();
+ void deleteAndStartEditCurrentCell();
+ void startEditOrToggleValue();
+ bool acceptRowEdit();
+ void cancelRowEdit();
+ void sortAscending();
+ void sortDescending();
+ */
+ throw KoMacro::Exception(i18n("Unknown record \"%1\" in view for \"%2\".").arg(record).arg(dialog->caption()));
+ }
+}
+
+//#include "navigateaction.moc"
diff --git a/kexi/plugins/macros/kexiactions/navigateaction.h b/kexi/plugins/macros/kexiactions/navigateaction.h
new file mode 100644
index 000000000..f7f74f86f
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/navigateaction.h
@@ -0,0 +1,78 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_NAVIGATEACTION_H
+#define KEXIMACRO_NAVIGATEACTION_H
+
+#include "kexiaction.h"
+
+class KexiMainWindow;
+
+namespace KoMacro {
+ class Context;
+}
+
+namespace KexiMacro {
+
+ /**
+ * The NavigateAction class implements a @a KoMacro::Action
+ * to provide functionality to execute an object like
+ * e.g. a script or a macro.
+ */
+ class NavigateAction : public KexiAction
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ NavigateAction();
+
+ /**
+ * Destructor.
+ */
+ virtual ~NavigateAction();
+
+ /**
+ * This function is called, when the @a KoMacro::Variable
+ * with name @p name used within the @a KoMacro::MacroItem
+ * @p macroitem got changed.
+ *
+ * @param macroitem The @a KoMacro::MacroItem instance where
+ * the variable defined with @p name is located in.
+ * @param name The name the @a KoMacro::Variable has.
+ * @return true if the update was successfully else false
+ * is returned.
+ */
+ virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name);
+
+ public slots:
+
+ /**
+ * Called if the @a Action should be executed within the
+ * defined @p context .
+ */
+ virtual void activate(KSharedPtr<KoMacro::Context> context);
+
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexiactions/objectnamevariable.h b/kexi/plugins/macros/kexiactions/objectnamevariable.h
new file mode 100644
index 000000000..eeaabe041
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/objectnamevariable.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_OBJECTNAMEVARIABLE_H
+#define KEXIMACRO_OBJECTNAMEVARIABLE_H
+
+#include "../lib/variable.h"
+
+#include "kexivariable.h"
+
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipartmanager.h>
+#include <core/kexipartinfo.h>
+
+#include <klocale.h>
+
+namespace KexiMacro {
+
+ /**
+ * The ViewVariable class provide a list of KexiPart::PartItem's
+ * supported by a KexiPart::Part as @a KoMacro::Variable .
+ */
+ template<class ACTIONIMPL>
+ class ObjectNameVariable : public KexiVariable<ACTIONIMPL>
+ {
+ public:
+ ObjectNameVariable(ACTIONIMPL* actionimpl, const QString& objectname = QString::null, const QString& name = QString::null)
+ : KexiVariable<ACTIONIMPL>(actionimpl, "name", i18n("Name"))
+ {
+ if(! actionimpl->mainWin()->project())
+ return;
+
+ QStringList namelist;
+ KexiPart::Info* info = Kexi::partManager().infoForMimeType( QString("kexi/%1").arg(objectname) );
+ if(info) {
+ KexiPart::ItemDict* items = actionimpl->mainWin()->project()->items(info);
+ if(items)
+ for(KexiPart::ItemDictIterator item_it = *items; item_it.current(); ++item_it)
+ namelist << item_it.current()->name();
+ }
+
+ if(namelist.count() <= 0)
+ namelist << "";
+
+ for(QStringList::Iterator it = namelist.begin(); it != namelist.end(); ++it)
+ this->appendChild( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(*it)) );
+
+ this->setVariant( (name.isNull() || ! namelist.contains(name)) ? namelist[0] : name );
+
+ kdDebug()<<"##################### KexiActions::ObjectNameVariable() objectname="<<objectname<<" name="<<name<<" value="<<this->variant()<<" children="<<namelist<<endl;
+ }
+
+ virtual ~ObjectNameVariable() {}
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexiactions/objectvariable.h b/kexi/plugins/macros/kexiactions/objectvariable.h
new file mode 100644
index 000000000..b61f24e38
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/objectvariable.h
@@ -0,0 +1,87 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_OBJECTVARIABLE_H
+#define KEXIMACRO_OBJECTVARIABLE_H
+
+#include "../lib/action.h"
+#include "../lib/variable.h"
+
+#include "kexivariable.h"
+
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipartmanager.h>
+#include <core/kexipartinfo.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+namespace KexiMacro {
+
+ /**
+ * The ObjectVariable class implements @a KoMacro::Variable to
+ * provide a variable list of Kexi-objects. Those Kexi-objects
+ * are KexiPart's like e.g. table, query, form or script.
+ */
+ template<class ACTIONIMPL>
+ class ObjectVariable : public KexiVariable<ACTIONIMPL>
+ {
+ public:
+
+ enum Conditions {
+ VisibleInNav = 1,
+ Executable = 2,
+ DataExport = 4
+ };
+
+ ObjectVariable(ACTIONIMPL* actionimpl, int conditions = VisibleInNav, const QString& objectname = QString::null)
+ : KexiVariable<ACTIONIMPL>(actionimpl, "object", i18n("Object"))
+ {
+ KexiPart::PartInfoList* parts = Kexi::partManager().partInfoList();
+ for(KexiPart::PartInfoListIterator it(*parts); it.current(); ++it) {
+ KexiPart::Info* info = it.current();
+
+ if(conditions & VisibleInNav && ! info->isVisibleInNavigator())
+ continue;
+ if(conditions & Executable && ! info->isExecuteSupported())
+ continue;
+ if(conditions & DataExport && ! info->isDataExportSupported())
+ continue;
+
+ const QString name = info->objectName(); //info->groupName();
+ this->appendChild( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(name)) );
+ }
+
+ if(! objectname.isNull())
+ this->setVariant( objectname );
+ else if(this->children().count() > 0)
+ this->setVariant( this->children()[0]->variant() );
+ else
+ this->setVariant( QString::null );
+
+ kdDebug()<<"##################### KexiActions::ObjectVariable() variant="<<this->variant()<<endl;
+ }
+
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexiactions/openaction.cpp b/kexi/plugins/macros/kexiactions/openaction.cpp
new file mode 100644
index 000000000..b67041bb3
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/openaction.cpp
@@ -0,0 +1,154 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "openaction.h"
+
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipartmanager.h>
+#include <core/kexipartinfo.h>
+#include <core/kexipart.h>
+#include <core/keximainwindow.h>
+
+#include <klocale.h>
+
+using namespace KexiMacro;
+
+namespace KexiMacro {
+
+ static const QString DATAVIEW = "data";
+ static const QString DESIGNVIEW = "design";
+ static const QString TEXTVIEW = "text";
+
+ static const QString OBJECT = "object";
+ static const QString NAME = "name";
+ static const QString VIEW = "view";
+
+ /**
+ * The ViewVariable class provide a list of viewmodes supported
+ * by a KexiPart::Part as @a KoMacro::Variable .
+ */
+ template<class ACTIONIMPL>
+ class ViewVariable : public KexiVariable<ACTIONIMPL>
+ {
+ public:
+ ViewVariable(ACTIONIMPL* actionimpl, const QString& objectname = QString::null, const QString& viewname = QString::null)
+ : KexiVariable<ACTIONIMPL>(actionimpl, VIEW, i18n("View"))
+ {
+ QStringList namelist;
+ KexiPart::Part* part = Kexi::partManager().partForMimeType( QString("kexi/%1").arg(objectname) );
+ if(part) {
+ int viewmodes = part->supportedViewModes();
+ if(viewmodes & Kexi::DataViewMode)
+ namelist << DATAVIEW;
+ if(viewmodes & Kexi::DesignViewMode)
+ namelist << DESIGNVIEW;
+ if(viewmodes & Kexi::TextViewMode)
+ namelist << TEXTVIEW;
+ for(QStringList::Iterator it = namelist.begin(); it != namelist.end(); ++it)
+ this->children().append( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(*it)) );
+ }
+ const QString n =
+ namelist.contains(viewname)
+ ? viewname
+ : namelist.count() > 0 ? namelist[0] : "";
+
+ this->setVariant(n);
+ }
+ };
+
+}
+
+OpenAction::OpenAction()
+ : KexiAction("open", i18n("Open"))
+{
+ const int conditions = ObjectVariable<OpenAction>::VisibleInNav;
+
+ KSharedPtr<KoMacro::Variable> objvar = new ObjectVariable<OpenAction>(this, conditions);
+ setVariable(objvar);
+
+ setVariable(KSharedPtr<KoMacro::Variable>( new ObjectNameVariable<OpenAction>(this, objvar->variant().toString()) ));
+ setVariable(KSharedPtr<KoMacro::Variable>( new ViewVariable<OpenAction>(this, objvar->variant().toString()) ));
+}
+
+OpenAction::~OpenAction()
+{
+}
+
+bool OpenAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name)
+{
+ kdDebug()<<"OpenAction::notifyUpdated() name="<<name<<" macroitem.action="<<(macroitem->action() ? macroitem->action()->name() : "NOACTION")<<endl;
+ KSharedPtr<KoMacro::Variable> variable = macroitem->variable(name, false);
+ if(! variable) {
+ kdWarning()<<"OpenAction::notifyUpdated() No such variable="<<name<<" in macroitem."<<endl;
+ return false;
+ }
+
+ variable->clearChildren();
+ if(name == OBJECT) {
+ const QString objectvalue = macroitem->variant(OBJECT, true).toString(); // e.g. "table" or "query"
+ const QString objectname = macroitem->variant(NAME, true).toString(); // e.g. "table1" or "table2" if objectvalue above is "table"
+ const QString viewname = macroitem->variant(VIEW, true).toString(); // "data", "design" or "text"
+
+ macroitem->variable(NAME, true)->setChildren(
+ KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new ObjectNameVariable<OpenAction>(this, objectvalue, objectname)) );
+ macroitem->variable(VIEW, true)->setChildren(
+ KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new ViewVariable<OpenAction>(this, objectvalue, viewname)) );
+ }
+
+ return true;
+}
+
+void OpenAction::activate(KSharedPtr<KoMacro::Context> context)
+{
+ if(! mainWin()->project()) {
+ throw KoMacro::Exception(i18n("No project loaded."));
+ }
+
+ const QString objectname = context->variable(OBJECT)->variant().toString();
+ const QString name = context->variable(NAME)->variant().toString();
+ KexiPart::Item* item = mainWin()->project()->itemForMimeType( QString("kexi/%1").arg(objectname).latin1(), name );
+ if(! item) {
+ throw KoMacro::Exception(i18n("No such object \"%1.%2\".").arg(objectname).arg(name));
+ }
+
+ // Determinate the viewmode.
+ const QString view = context->variable(VIEW)->variant().toString();
+ int viewmode;
+ if(view == DATAVIEW)
+ viewmode = Kexi::DataViewMode;
+ else if(view == DESIGNVIEW)
+ viewmode = Kexi::DesignViewMode;
+ else if(view == TEXTVIEW)
+ viewmode = Kexi::TextViewMode;
+ else {
+ throw KoMacro::Exception(i18n("No such viewmode \"%1\" in object \"%2.%3\".").arg(view).arg(objectname).arg(name));
+ }
+
+ // Try to open the object now.
+ bool openingCancelled;
+ if(! mainWin()->openObject(item, viewmode, openingCancelled)) {
+ if(! openingCancelled) {
+ throw KoMacro::Exception(i18n("Failed to open object \"%1.%2\".").arg(objectname).arg(name));
+ }
+ }
+}
+
+//#include "openaction.moc"
diff --git a/kexi/plugins/macros/kexiactions/openaction.h b/kexi/plugins/macros/kexiactions/openaction.h
new file mode 100644
index 000000000..b49f12382
--- /dev/null
+++ b/kexi/plugins/macros/kexiactions/openaction.h
@@ -0,0 +1,79 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACRO_OPENACTION_H
+#define KEXIMACRO_OPENACTION_H
+
+#include "kexiaction.h"
+
+class KexiMainWindow;
+
+namespace KoMacro {
+ class Context;
+}
+
+namespace KexiMacro {
+
+ /**
+ * The OpenAction class implements a @a KoMacro::Action
+ * to provide functionality to open any kind of Kexi
+ * object (e.g. table, query, form, script, ...).
+ */
+ class OpenAction : public KexiAction
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ */
+ OpenAction();
+
+ /**
+ * Destructor.
+ */
+ virtual ~OpenAction();
+
+ /**
+ * This function is called, when the @a KoMacro::Variable
+ * with name @p name used within the @a KoMacro::MacroItem
+ * @p macroitem got changed.
+ *
+ * @param macroitem The @a KoMacro::MacroItem instance where
+ * the variable defined with @p name is located in.
+ * @param name The name the @a KoMacro::Variable has.
+ * @return true if the update was successfully else false
+ * is returned.
+ */
+ virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name);
+
+ public slots:
+
+ /**
+ * Called if the @a Action should be executed within the
+ * defined @p context .
+ */
+ virtual void activate(KSharedPtr<KoMacro::Context> context);
+
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/kexipart/Makefile.am b/kexi/plugins/macros/kexipart/Makefile.am
new file mode 100644
index 000000000..51cff0eaa
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/Makefile.am
@@ -0,0 +1,32 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_macro.la
+
+kexihandler_macro_la_SOURCES = \
+ keximacropart.cpp keximacroview.cpp keximacroproperty.cpp keximacrodesignview.cpp keximacrotextview.cpp keximacroerrorbase.ui keximacroerror.cpp
+
+kexihandler_macro_la_LDFLAGS = \
+ $(KDE_PLUGIN) -module -no-undefined -Wnounresolved $(all_libraries) $(VER_INFO)
+
+kexihandler_macro_la_LIBADD = \
+ ../kexiactions/libkeximacroactions.la \
+ $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la
+
+INCLUDES = \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/lib/kofficecore/ \
+ -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget \
+ $(all_includes)
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=keximacrohandler.desktop
+
+SUBDIRS = .
+METASOURCES = AUTO
+
+noinst_HEADERS = \
+ keximacropart.h keximacroview.h keximacrodesignview.h keximacrotextview.h keximacroerror.h
diff --git a/kexi/plugins/macros/kexipart/keximacrodesignview.cpp b/kexi/plugins/macros/kexipart/keximacrodesignview.cpp
new file mode 100644
index 000000000..030be0cb4
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacrodesignview.cpp
@@ -0,0 +1,497 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "keximacrodesignview.h"
+#include "keximacroproperty.h"
+
+#include <qtimer.h>
+#include <qdom.h>
+#include <kdebug.h>
+
+#include <kexidialogbase.h>
+#include <kexidb/connection.h>
+#include <kexidb/error.h>
+
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipartmanager.h>
+#include <core/kexipartinfo.h>
+
+#include <widget/kexidatatable.h>
+#include <widget/tableview/kexitableview.h>
+#include <widget/tableview/kexitableviewdata.h>
+#include <widget/tableview/kexitableitem.h>
+#include <widget/tableview/kexidataawarepropertyset.h>
+
+#include <koproperty/set.h>
+#include <koproperty/property.h>
+
+#include "../lib/macro.h"
+#include "../lib/macroitem.h"
+#include "../lib/xmlhandler.h"
+
+/// constants used to name columns instead of hardcoding indices
+#define COLUMN_ID_ACTION 0
+#define COLUMN_ID_COMMENT 1
+
+/**
+* \internal d-pointer class to be more flexible on future extension of the
+* functionality without to much risk to break the binary compatibility.
+*/
+class KexiMacroDesignView::Private
+{
+ public:
+
+ /**
+ * The view used to display the actions
+ * a \a Macro has.
+ */
+ KexiDataTable* datatable;
+
+ /**
+ * For convenience. The table view ( datatable->tableView() ).
+ */
+ KexiTableView* tableview;
+
+ /**
+ * The \a KexiTableViewData data-model for the
+ * \a KexiTableView above.
+ */
+ KexiTableViewData* tabledata;
+
+ /**
+ * The \a KexiDataAwarePropertySet is used to display
+ * properties an action provides in the propertyview.
+ */
+ KexiDataAwarePropertySet* propertyset;
+
+ /// Boolean flag to avoid infinite recursion.
+ bool reloadsProperties;
+ /// Boolean flag to avoid infinite recursion.
+ bool updatesProperties;
+
+ /**
+ * Constructor.
+ *
+ * \param m The passed \a KoMacro::Manager instance our
+ * \a manager points to.
+ */
+ Private()
+ : propertyset(0)
+ , reloadsProperties(false)
+ , updatesProperties(false)
+ {
+ }
+
+ /**
+ * Destructor.
+ */
+ ~Private()
+ {
+ delete propertyset;
+ }
+
+};
+
+KexiMacroDesignView::KexiMacroDesignView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro)
+ : KexiMacroView(mainwin, parent, macro, "KexiMacroDesignView")
+ , d( new Private() )
+{
+ // The table's data-model.
+ d->tabledata = new KexiTableViewData();
+ d->tabledata->setSorting(-1); // disable sorting
+
+ // Add the "Action" column.
+ KexiTableViewColumn* actioncol = new KexiTableViewColumn(
+ "action", // name/identifier
+ KexiDB::Field::Enum, // fieldtype
+ KexiDB::Field::NoConstraints, // constraints
+ KexiDB::Field::NoOptions, // options
+ 0, // length
+ 0, // precision
+ QVariant(), // default value
+ i18n("Action"), // caption
+ QString::null, // description
+ 0 // width
+ );
+ d->tabledata->addColumn(actioncol);
+
+ QValueVector<QString> items;
+ items.append(""); // empty means no action
+
+ // Append the list of actions provided by Kexi.
+ QStringList actionnames = KoMacro::Manager::self()->actionNames();
+ QStringList::ConstIterator it, end( actionnames.constEnd() );
+ for( it = actionnames.constBegin(); it != end; ++it) {
+ KSharedPtr<KoMacro::Action> action = KoMacro::Manager::self()->action(*it);
+ items.append( action->text() );
+ }
+
+ actioncol->field()->setEnumHints(items);
+
+ // Add the "Comment" column.
+ d->tabledata->addColumn( new KexiTableViewColumn(
+ "comment", // name/identifier
+ KexiDB::Field::Text, // fieldtype
+ KexiDB::Field::NoConstraints, // constraints
+ KexiDB::Field::NoOptions, // options
+ 0, // length
+ 0, // precision
+ QVariant(), // default value
+ i18n("Comment"), // caption
+ QString::null, // description
+ 0 // width
+ ) );
+
+ // Create the tableview.
+ QHBoxLayout* layout = new QHBoxLayout(this);
+ d->datatable = new KexiDataTable(mainWin(), this, "Macro KexiDataTable", false /*not db aware*/);
+ layout->addWidget(d->datatable);
+ d->tableview = d->datatable->tableView();
+ d->tableview->setSpreadSheetMode();
+ d->tableview->setColumnStretchEnabled( true, COLUMN_ID_COMMENT ); //last column occupies the rest of the area
+
+ // We need to register our KexiMacroPropertyFactory to use our own
+ // KoProperty::Property implementation.
+ KexiMacroPropertyFactory::initFactory();
+
+ // Create the propertyset.
+ d->propertyset = new KexiDataAwarePropertySet(this, d->tableview);
+
+ // Connect signals the KexiDataTable provides to local slots.
+ connect(d->tabledata, SIGNAL(aboutToChangeCell(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)),
+ this, SLOT(beforeCellChanged(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)));
+ connect(d->tabledata, SIGNAL(rowUpdated(KexiTableItem*)),
+ this, SLOT(rowUpdated(KexiTableItem*)));
+ connect(d->tabledata, SIGNAL(rowInserted(KexiTableItem*,uint,bool)),
+ this, SLOT(rowInserted(KexiTableItem*,uint,bool)));
+ connect(d->tabledata, SIGNAL(rowDeleted()),
+ this, SLOT(rowDeleted()));
+
+ // Everything is ready. So, update the data now.
+ updateData();
+ setDirty(false);
+}
+
+KexiMacroDesignView::~KexiMacroDesignView()
+{
+ delete d;
+}
+
+void KexiMacroDesignView::updateData()
+{
+ kdDebug() << "KexiMacroDesignView::updateData()" << endl;
+
+ // Remove previous content of tabledata.
+ d->tabledata->deleteAllRows();
+ // Remove old property sets.
+ d->propertyset->clear();
+
+ // Add some empty rows
+ for (int i=0; i<50; i++) {
+ d->tabledata->append( d->tabledata->createItem() );
+ }
+
+ // Set the MacroItem's
+ QStringList actionnames = KoMacro::Manager::self()->actionNames();
+ KoMacro::MacroItem::List macroitems = macro()->items();
+ KoMacro::MacroItem::List::ConstIterator it(macroitems.constBegin()), end(macroitems.constEnd());
+ for(uint idx = 0; it != end; ++it, idx++) {
+ KexiTableItem* tableitem = d->tabledata->at(idx);
+ if(! tableitem) {
+ // If there exists no such item, add it.
+ tableitem = d->tabledata->createItem();
+ d->tabledata->append(tableitem);
+ }
+ // Set the action-column.
+ KSharedPtr<KoMacro::Action> action = (*it)->action();
+ if(action.data()) {
+ int i = actionnames.findIndex( action->name() );
+ if(i >= 0) {
+ tableitem->at(COLUMN_ID_ACTION) = i + 1;
+ //setAction(tableitem, action->name());
+ }
+ }
+ // Set the comment-column.
+ tableitem->at(COLUMN_ID_COMMENT) = (*it)->comment();
+ }
+
+ // set data for our spreadsheet: this will clear our sets
+ d->tableview->setData(d->tabledata);
+
+ // Add the property sets.
+ it = macroitems.constBegin();
+ for(uint idx = 0; it != end; ++it, idx++) {
+ updateProperties(idx, 0, *it);
+ }
+
+ // work around a bug in the KexiTableView where we lose the stretch-setting...
+ d->tableview->setColumnStretchEnabled( true, COLUMN_ID_COMMENT ); //last column occupies the rest of the area
+
+ propertySetReloaded(true);
+}
+
+bool KexiMacroDesignView::loadData()
+{
+ if(! KexiMacroView::loadData()) {
+ return false;
+ }
+ updateData(); // update the tableview's data.
+ return true;
+}
+
+KoProperty::Set* KexiMacroDesignView::propertySet()
+{
+ return d->propertyset->currentPropertySet();
+}
+
+void KexiMacroDesignView::beforeCellChanged(KexiTableItem* item, int colnum, QVariant& newvalue, KexiDB::ResultInfo* result)
+{
+ Q_UNUSED(result);
+ kdDebug() << "KexiMacroDesignView::beforeCellChanged() colnum=" << colnum << " newvalue=" << newvalue.toString() << endl;
+
+ int rowindex = d->tabledata->findRef(item);
+ if(rowindex < 0) {
+ kdWarning() << "KexiMacroDesignView::beforeCellChanged() No such item" << endl;
+ return;
+ }
+
+ // If the rowindex doesn't exists yet, we need to append new
+ // items till we are able to access the item we like to use.
+ for(int i = macro()->items().count(); i <= rowindex; i++) {
+ macro()->addItem( KSharedPtr<KoMacro::MacroItem>( new KoMacro::MacroItem() ) );
+ }
+
+ // Get the matching MacroItem.
+ KSharedPtr<KoMacro::MacroItem> macroitem = macro()->items()[rowindex];
+ if(! macroitem.data()) {
+ kdWarning() << "KexiMacroDesignView::beforeCellChanged() Invalid item for rowindex=" << rowindex << endl;
+ return;
+ }
+
+ // Handle the column that should be changed
+ switch(colnum) {
+ case COLUMN_ID_ACTION: { // The "Action" column
+ QString actionname;
+ bool ok;
+ int selectedindex = newvalue.toInt(&ok);
+ if(ok && selectedindex > 0) {
+ QStringList actionnames = KoMacro::Manager::self()->actionNames();
+ actionname = actionnames[ selectedindex - 1 ]; // first item is empty
+ }
+ KSharedPtr<KoMacro::Action> action = KoMacro::Manager::self()->action(actionname);
+ macroitem->setAction(action);
+ updateProperties(d->propertyset->currentRow(), d->propertyset->currentPropertySet(), macroitem);
+ propertySetReloaded(true);
+ } break;
+ case COLUMN_ID_COMMENT: { // The "Comment" column
+ macroitem->setComment( newvalue.toString() );
+ } break;
+ default:
+ kdWarning() << "KexiMacroDesignView::beforeCellChanged() No such column number " << colnum << endl;
+ return;
+ }
+
+ setDirty();
+}
+
+void KexiMacroDesignView::rowUpdated(KexiTableItem* item)
+{
+ int rowindex = d->tabledata->findRef(item);
+ kdDebug() << "KexiMacroDesignView::rowUpdated() rowindex=" << rowindex << endl;
+ //propertySetSwitched();
+ //propertySetReloaded(true);
+ //setDirty();
+}
+
+void KexiMacroDesignView::rowInserted(KexiTableItem*, uint row, bool)
+{
+ kdDebug() << "KexiMacroDesignView::rowInserted() rowindex=" << row << endl;
+ KoMacro::MacroItem::List& macroitems = macro()->items();
+
+ if(row < macroitems.count()) {
+ // If a new item was inserted, we need to insert a new item to our
+ // list of MacroItems too. If the new item was appended, we don't
+ // need to do anything yet cause the new item will be handled on
+ // beforeCellChanged() anyway.
+ kdDebug() << "KexiMacroDesignView::rowInserted() Inserting new MacroItem" << endl;
+ KSharedPtr<KoMacro::MacroItem> macroitem = KSharedPtr<KoMacro::MacroItem>( new KoMacro::MacroItem() );
+ KoMacro::MacroItem::List::Iterator it = macroitems.at(row);
+ macroitems.insert(it, macroitem);
+ }
+}
+
+void KexiMacroDesignView::rowDeleted()
+{
+ int rowindex = d->propertyset->currentRow();
+ if(rowindex < 0) {
+ kdWarning() << "KexiMacroDesignView::rowDeleted() No such item" << endl;
+ return;
+ }
+ kdDebug() << "KexiMacroDesignView::rowDeleted() rowindex=" << rowindex << endl;
+ KoMacro::MacroItem::List& macroitems = macro()->items();
+ macroitems.remove( macroitems.at(rowindex) );
+}
+
+bool KexiMacroDesignView::updateSet(KoProperty::Set* set, KSharedPtr<KoMacro::MacroItem> macroitem, const QString& variablename)
+{
+ kdDebug() << "KexiMacroDesignView::updateSet() variablename=" << variablename << endl;
+ KoProperty::Property* property = KexiMacroProperty::createProperty(macroitem, variablename);
+ if(! property)
+ return false;
+ set->addProperty(property);
+ return true;
+}
+
+void KexiMacroDesignView::updateProperties(int row, KoProperty::Set* set, KSharedPtr<KoMacro::MacroItem> macroitem)
+{
+ kdDebug() << "KexiMacroDesignView::updateProperties() row=" << row << endl;
+
+ if(row < 0 || d->updatesProperties) {
+ return; // ignore invalid rows and avoid infinite recursion.
+ }
+
+ KSharedPtr<KoMacro::Action> action = macroitem->action();
+ if(! action.data()) {
+ // don't display a propertyset if there is no action defined.
+ d->propertyset->remove(row);
+ return; // job done.
+ }
+
+ d->updatesProperties = true;
+
+ if(set) {
+ // we need to clear old data before adding the new content.
+ set->clear();
+ }
+ else {
+ // if there exists no such propertyset yet, create one.
+ set = new KoProperty::Set(d->propertyset, action->name());
+ d->propertyset->insert(row, set, true);
+ connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)),
+ this, SLOT(propertyChanged(KoProperty::Set&, KoProperty::Property&)));
+ }
+
+ // The caption.
+ KoProperty::Property* prop = new KoProperty::Property("this:classString", action->text());
+ prop->setVisible(false);
+ set->addProperty(prop);
+
+ // Display the list of variables.
+ QStringList varnames = action->variableNames();
+ for(QStringList::Iterator it = varnames.begin(); it != varnames.end(); ++it) {
+ if(updateSet(set, macroitem, *it)) {
+ KSharedPtr<KoMacro::Variable> variable = macroitem->variable(*it, true);
+ kdDebug()<<"KexiMacroDesignView::updateProperties() name=" << *it << " variable=" << variable->variant().toString() << endl;
+#if 0
+ macroitem->setVariable(*it, variable);
+#endif
+ }
+ }
+
+ d->updatesProperties = false;
+}
+
+void KexiMacroDesignView::propertyChanged(KoProperty::Set& set, KoProperty::Property& property)
+{
+ Q_UNUSED(set);
+ kdDebug() << "!!!!! KexiMacroDesignView::propertyChanged() propertyname=" << property.name() << endl;
+ setDirty();
+
+ /*
+ if(d->reloadsProperties) // be sure to don't update properties if we are still on reloading.
+ return;
+ d->reloadsProperties = true;
+
+ const int row = d->propertyset->currentRow();
+ const QCString name = property.name();
+ kdDebug() << "KexiMacroDesignView::propertyChanged() name=" << name << " row=" << row << endl;
+
+ //TODO reload is only needed if something changed!
+ bool dirty = true; bool reload = true;//dirtyvarnames.count()>0;
+
+ if(dirty || reload) { // Only reload properties if it's really needed.
+ setDirty();
+ if(reload) {
+ // The MacroItem which should be changed.
+ KSharedPtr<KoMacro::MacroItem> macroitem = macro()->items()[row];
+ // Update the properties.
+ updateProperties(row, &set, macroitem);
+ }
+ // It's needed to call the reload delayed cause in KoProperty::Editor
+ // QTimer::singleShot(10, this, SLOT(selectItemLater())); may lead
+ // to crashes if we are to fast.
+ QTimer::singleShot(50, this, SLOT(reloadPropertyLater()));
+ }
+
+ d->reloadsProperties = false;
+ */
+
+ /*
+ QStringList dirtyvarnames = macroitem->setVariable(name, KSharedPtr<KoMacro::Variable>(pv));
+ bool dirty = false;
+ bool reload = false;
+ for(QStringList::Iterator it = dirtyvarnames.begin(); it != dirtyvarnames.end(); ++it) {
+ KSharedPtr<KoMacro::Variable> variable = macroitem->variable(*it);
+ if(! variable.data()) {
+ kdDebug() << "KexiMacroDesignView::propertyChanged() name=" << name << " it=" << *it << " skipped cause such a variable is not known." << endl;
+ continue;
+ }
+
+ if(! set.contains( (*it).latin1() )) {
+ // If there exist no such property yet, we need to add it.
+ if(updateSet(&set, macroitem, *it))
+ reload = true; // we like to reload the whole set
+ continue;
+ }
+
+ kdDebug() << "KexiMacroDesignView::propertyChanged() set existing property=" << *it << endl;
+ KoProperty::Property& p = set.property((*it).latin1());
+ KoMacro::Variable::List children = variable->children();
+ if(children.count() > 0) {
+ QStringList keys, names;
+ KoMacro::Variable::List::Iterator childit(children.begin()), childend(children.end());
+ for(; childit != childend; ++childit) {
+ const QString s = (*childit)->variant().toString();
+ keys << s;
+ names << s;
+ }
+ p.setListData( new KoProperty::Property::ListData(keys, names) );
+ }
+ p.setValue(variable->variant());
+ dirty = true;
+ }
+
+ // If there are expired aka not any longer needed properties around, we
+ // need to reload the whole set.
+ for(KoProperty::Set::Iterator setit = set; setit.current(); ++setit) {
+ if(setit.currentKey() == name) continue; // don't remove ourself
+ if(setit.currentKey().left(5) == QCString("this:")) continue; // don't remove internal properties
+ if(setit.currentKey() == QCString("newrow")) continue; // also an internal used property
+ if(action.data() && action->hasVariable(setit.currentKey())) continue; // the property is still valid
+ reload = true; // we like to reload the whole set
+ }
+ */
+}
+
+void KexiMacroDesignView::reloadPropertyLater()
+{
+ propertySetReloaded(true);
+}
+
+#include "keximacrodesignview.moc"
+
diff --git a/kexi/plugins/macros/kexipart/keximacrodesignview.h b/kexi/plugins/macros/kexipart/keximacrodesignview.h
new file mode 100644
index 000000000..c3eca2d2b
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacrodesignview.h
@@ -0,0 +1,129 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIMACRODESIGNVIEW_H
+#define KEXIMACRODESIGNVIEW_H
+
+#include "keximacroview.h"
+
+// Forward declarations.
+namespace KoMacro {
+ class Action;
+ class Macro;
+ class MacroItem;
+}
+namespace KoProperty {
+ class Property;
+}
+namespace KexiDB {
+ class ResultInfo;
+}
+class KexiTableItem;
+
+/**
+ * The KexiScriptDesignView implements \a KexiMacroView to provide
+ * a GUI-Editor to edit a Macro.
+ */
+class KexiMacroDesignView : public KexiMacroView
+{
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ *
+ * \param mainwin The \a KexiMainWindow instance this \a KexiViewBase
+ * belongs to.
+ * \param parent The parent widget this widget should be displayed in.
+ * \param macro The \a KoMacro::Macro instance this view is for.
+ */
+ KexiMacroDesignView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiMacroDesignView();
+
+ /**
+ * Load the data from XML source and fill the internally
+ * used \a KoMacro::Macro instance.
+ */
+ virtual bool loadData();
+
+ /**
+ * \return the \a KoProperty::Set properties this view provides.
+ */
+ virtual KoProperty::Set* propertySet();
+
+ private slots:
+
+ /**
+ * Called before a cell changed in the internaly used
+ * \a KexiTableView .
+ */
+ void beforeCellChanged(KexiTableItem*, int, QVariant&, KexiDB::ResultInfo*);
+
+ /**
+ * Called if the passed \p item got updated.
+ */
+ void rowUpdated(KexiTableItem* item);
+
+ /**
+ * Called if a row got deleted.
+ */
+ void rowDeleted();
+
+ /**
+ * Called if a row got inserted.
+ */
+ void rowInserted(KexiTableItem* item, uint row, bool repaint);
+
+ /**
+ * Called if a property in the \a KoProperty got changed.
+ */
+ void propertyChanged(KoProperty::Set&, KoProperty::Property&);
+
+ /**
+ * Reloads the propertyset delayed.
+ */
+ void reloadPropertyLater();
+
+ private:
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+
+ /**
+ * Update the table's data.
+ */
+ void updateData();
+
+ /**
+ * Update the \a KoProperty::Set set with the passed \a KoMacro::MacroItem
+ * \p item and the variablename \p variablename .
+ */
+ bool updateSet(KoProperty::Set* set, KSharedPtr<KoMacro::MacroItem> item, const QString& variablename);
+
+ /**
+ * Update the properties of the \a KoProperty::Set \p set at
+ * row-number \p row with the \a KoMacro::MacroItem \p macroitem .
+ */
+ void updateProperties(int row, KoProperty::Set* set, KSharedPtr<KoMacro::MacroItem> macroitem);
+};
+
+#endif
diff --git a/kexi/plugins/macros/kexipart/keximacroerror.cpp b/kexi/plugins/macros/kexipart/keximacroerror.cpp
new file mode 100644
index 000000000..15f4df3f0
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacroerror.cpp
@@ -0,0 +1,130 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Tobi Krebs (tobi.krebs@gmail.com)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "keximacroerror.h"
+
+#include <core/kexiproject.h>
+#include <core/keximainwindow.h>
+
+#include <qtimer.h>
+
+/**
+* \internal d-pointer class to be more flexible on future extension of the
+* functionality without to much risk to break the binary compatibility.
+*/
+class KexiMacroError::Private
+{
+ public:
+ KexiMainWindow* const mainwin;
+ KSharedPtr<KoMacro::Context> context;
+
+ Private(KexiMainWindow* const m, KoMacro::Context* const c)
+ : mainwin(m)
+ , context(c)
+ {
+ }
+};
+
+KexiMacroError::KexiMacroError(KexiMainWindow* mainwin, KSharedPtr<KoMacro::Context> context)
+ : KexiMacroErrorBase(mainwin, "KexiMacroError" , /*WFlags*/ Qt::WDestructiveClose)
+ , d(new Private(mainwin, context))
+{
+ //setText(i18n("Execution failed")); //caption
+ //errortext, errorlist, continuebtn,cancelbtn, designerbtn
+
+ KoMacro::Exception* exception = context->exception();
+
+ iconlbl->setPixmap(KGlobal::instance()->iconLoader()->loadIcon("messagebox_critical", KIcon::Small, 32));
+ errorlbl->setText(i18n("<qt>Failed to execute the macro \"%1\".<br>%2</qt>").arg( context->macro()->name() ).arg( exception->errorMessage() ));
+
+ int i = 1;
+ KoMacro::MacroItem::List items = context->macro()->items();
+ for (KoMacro::MacroItem::List::ConstIterator mit = items.begin(); mit != items.end(); mit++)
+ {
+ KListViewItem* listviewitem = new KListViewItem(errorlist);
+ listviewitem->setText(0,QString("%1").arg(i++));
+ listviewitem->setText(1,i18n("Action"));
+ KSharedPtr<KoMacro::MacroItem> macroitem = *mit;
+
+ if (macroitem != 0 && macroitem->action() != 0)
+ {
+ listviewitem->setText(2,macroitem->action()->name());
+ }
+
+ if(macroitem == context->macroItem())
+ {
+ listviewitem->setOpen(true);
+ listviewitem->setSelected(true);
+ errorlist->setSelected(listviewitem, true);
+ errorlist->ensureItemVisible(listviewitem);
+ }
+
+ KoMacro::Variable::Map variables = macroitem->variables();
+ KoMacro::Variable::Map::ConstIterator vit;
+ for ( vit = variables.begin(); vit != variables.end(); ++vit ) {
+ KListViewItem* child = new KListViewItem(listviewitem);
+ child->setText(1,vit.key());
+ child->setText(2,vit.data()->toString());
+ }
+ }
+
+ connect(designerbtn, SIGNAL(clicked()), this, SLOT(designbtnClicked()));
+ connect(continuebtn, SIGNAL(clicked()), this, SLOT(continuebtnClicked()));
+}
+
+KexiMacroError::~KexiMacroError()
+{
+ delete d;
+}
+
+void KexiMacroError::designbtnClicked()
+{
+ if(! d->mainwin->project()) {
+ kdWarning() << QString("KexiMacroError::designbtnClicked(): No project open.") << endl;
+ return;
+ }
+
+ // We need to determinate the KexiPart::Item which should be opened.
+ KSharedPtr<KoMacro::Macro> macro = d->context->macro();
+ const QString name = macro->name();
+ KexiPart::Item* item = d->mainwin->project()->itemForMimeType("kexi/macro", name);
+ if(! item) {
+ kdWarning() << QString("KexiMacroError::designbtnClicked(): No such macro \"%1\"").arg(name) << endl;
+ return;
+ }
+
+ // Try to open the KexiPart::Item now.
+ bool openingCancelled;
+ if(! d->mainwin->openObject(item, Kexi::DesignViewMode, openingCancelled)) {
+ if(! openingCancelled) {
+ kdWarning() << QString("KexiMacroError::designbtnClicked(): Open macro \"%1\" in designview failed.").arg(name) << endl;
+ return;
+ }
+ }
+
+ close();
+}
+
+void KexiMacroError::continuebtnClicked()
+{
+ QTimer::singleShot(200, d->context, SLOT(activateNext()));
+ close();
+}
diff --git a/kexi/plugins/macros/kexipart/keximacroerror.h b/kexi/plugins/macros/kexipart/keximacroerror.h
new file mode 100644
index 000000000..641859b7b
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacroerror.h
@@ -0,0 +1,89 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Tobi Krebs (tobi.krebs@gmail.com)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KEXIMACROERROR_H
+#define KEXIMACROERROR_H
+
+#include <qwidget.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+
+#include <klistview.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kglobal.h>
+#include <kdebug.h>
+
+#include "../lib/context.h"
+#include "../lib/exception.h"
+#include "../lib/macro.h"
+#include "../lib/macroitem.h"
+
+#include "keximacroerrorbase.h"
+
+// Forward-declarations.
+class KexiMainWindow;
+
+/**
+* An error dialog used to displayed more detailed informations about
+* a @a KoMacro::Exception that got thrown within a @a KoMacro::Context
+* during execution.
+*/
+class KexiMacroError : public KexiMacroErrorBase
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ *
+ * @param mainwin The parent @a KexiMainWindow instance.
+ * @param context The @a KoMacro::Context where the error happened.
+ */
+ KexiMacroError(KexiMainWindow* mainwin, KSharedPtr<KoMacro::Context> context);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiMacroError();
+
+ private slots:
+
+ /**
+ * Called if the "Open Macrodesigner"-Button is clicked.
+ */
+ void designbtnClicked();
+
+ /**
+ * Called if the "continue"-Button is clicked.
+ */
+ void continuebtnClicked();
+
+ private:
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+
+};
+
+#endif
diff --git a/kexi/plugins/macros/kexipart/keximacroerrorbase.ui b/kexi/plugins/macros/kexipart/keximacroerrorbase.ui
new file mode 100644
index 000000000..74e47cfc4
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacroerrorbase.ui
@@ -0,0 +1,213 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiMacroErrorBase</class>
+<widget class="QDialog">
+ <property name="name">
+ <cstring>KexiMacroErrorBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>492</width>
+ <height>424</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Error</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout8</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>iconlbl</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignLeft</set>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>errorlbl</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="alignment">
+ <set>WordBreak|AlignVCenter</set>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="KListView">
+ <column>
+ <property name="text">
+ <string>No</string>
+ </property>
+ <property name="clickable">
+ <bool>false</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Name</string>
+ </property>
+ <property name="clickable">
+ <bool>false</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Value</string>
+ </property>
+ <property name="clickable">
+ <bool>false</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>errorlist</cstring>
+ </property>
+ <property name="allColumnsShowFocus">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>true</bool>
+ </property>
+ <property name="resizeMode">
+ <enum>LastColumn</enum>
+ </property>
+ <property name="fullWidth">
+ <bool>true</bool>
+ </property>
+ <property name="itemsMovable">
+ <bool>false</bool>
+ </property>
+ <property name="autoOpen">
+ <bool>false</bool>
+ </property>
+ <property name="tooltipColumn">
+ <number>1</number>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout1</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>designerbtn</cstring>
+ </property>
+ <property name="text">
+ <string>Open in design view</string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ <property name="autoDefault">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>continuebtn</cstring>
+ </property>
+ <property name="text">
+ <string>Continue</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>cancelbtn</cstring>
+ </property>
+ <property name="text">
+ <string>Abort</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ </vbox>
+</widget>
+<customwidgets>
+</customwidgets>
+<connections>
+ <connection>
+ <sender>cancelbtn</sender>
+ <signal>clicked()</signal>
+ <receiver>KexiMacroErrorBase</receiver>
+ <slot>close()</slot>
+ </connection>
+</connections>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klistview.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/plugins/macros/kexipart/keximacrohandler.desktop b/kexi/plugins/macros/kexipart/keximacrohandler.desktop
new file mode 100644
index 000000000..c08a7b2af
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacrohandler.desktop
@@ -0,0 +1,81 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+GenericName=Macros
+GenericName[bg]=Макроси
+GenericName[br]=Makroù
+GenericName[da]=Makroer
+GenericName[de]=Makros
+GenericName[el]=Μακροεντολές
+GenericName[eo]=Makrooj
+GenericName[et]=Makrod
+GenericName[fa]=کلان‌دستورها
+GenericName[fy]=Macro's
+GenericName[ga]=Macraí
+GenericName[hr]=Makroi
+GenericName[hu]=Makrók
+GenericName[it]=Macro
+GenericName[ja]=マクロ
+GenericName[km]=ម៉ាក្រូ​
+GenericName[lv]=Makross
+GenericName[nb]=Makroer
+GenericName[nds]=Makros
+GenericName[ne]=म्याक्रोस
+GenericName[nl]=Macro's
+GenericName[pl]=Makra
+GenericName[ru]=Макросы
+GenericName[se]=Makroat
+GenericName[sk]=Makrá
+GenericName[sl]=Makri
+GenericName[sr]=Макрои
+GenericName[sr@Latn]=Makroi
+GenericName[sv]=Makron
+GenericName[uk]=Макроси
+GenericName[uz]=Makros
+GenericName[uz@cyrillic]=Макрос
+GenericName[zh_TW]=巨集
+
+Name=Macros
+Name[bg]=Макроси
+Name[br]=Makroù
+Name[da]=Makroer
+Name[de]=Makros
+Name[el]=Μακροεντολές
+Name[eo]=Makrooj
+Name[et]=Makrod
+Name[fa]=کلان‌دستورها
+Name[fy]=Macro's
+Name[ga]=Macraí
+Name[hr]=Makroi
+Name[hu]=Makrók
+Name[it]=Macro
+Name[ja]=マクロ
+Name[km]=ម៉ាក្រូ​
+Name[lv]=Makross
+Name[nb]=Makroer
+Name[nds]=Makros
+Name[ne]=म्याक्रोस
+Name[nl]=Macro's
+Name[pl]=Makra
+Name[ru]=Макросы
+Name[se]=Makroat
+Name[sk]=Makrá
+Name[sl]=Makri
+Name[sr]=Макрои
+Name[sr@Latn]=Makroi
+Name[sv]=Makron
+Name[uk]=Макроси
+Name[uz]=Makros
+Name[uz@cyrillic]=Макрос
+Name[zh_TW]=巨集
+
+X-KDE-Library=kexihandler_macro
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=macro
+X-Kexi-TypeMime=kexi/macro
+X-Kexi-ItemIcon=macro
+X-Kexi-SupportsDataExport=false
+X-Kexi-SupportsPrinting=false
+X-Kexi-SupportsExecution=true
diff --git a/kexi/plugins/macros/kexipart/keximacropart.cpp b/kexi/plugins/macros/kexipart/keximacropart.cpp
new file mode 100644
index 000000000..c4f020e44
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacropart.cpp
@@ -0,0 +1,172 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "keximacropart.h"
+
+#include "keximacroview.h"
+#include "keximacrodesignview.h"
+#include "keximacrotextview.h"
+
+//#include "kexiviewbase.h"
+//#include "keximainwindow.h"
+//#include "kexiproject.h"
+
+#include <qdom.h>
+#include <qstringlist.h>
+#include <kgenericfactory.h>
+#include <kexipartitem.h>
+//#include <kxmlguiclient.h>
+//#include <kexidialogbase.h>
+//#include <kconfig.h>
+//#include <kdebug.h>
+
+#include "../lib/manager.h"
+#include "../lib/macro.h"
+#include "../lib/macroitem.h"
+#include "../lib/action.h"
+
+#include "../kexiactions/openaction.h"
+#include "../kexiactions/executeaction.h"
+#include "../kexiactions/navigateaction.h"
+#include "../kexiactions/messageaction.h"
+#include "../kexiactions/datatableaction.h"
+
+/**
+* \internal d-pointer class to be more flexible on future extension of the
+* functionality without to much risk to break the binary compatibility.
+*/
+class KexiMacroPart::Private
+{
+ public:
+};
+
+KexiMacroPart::KexiMacroPart(QObject *parent, const char *name, const QStringList &l)
+ : KexiPart::Part(parent, name, l)
+ , d( new Private() )
+{
+ //kdDebug() << "KexiMacroPart::KexiMacroPart() Ctor" << endl;
+
+ //registered ID
+ m_registeredPartID = (int)KexiPart::MacroObjectType;
+
+ //name of the instance.
+ m_names["instanceName"]
+ = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). "
+ "Use '_' character instead of spaces. First character should be a..z character. "
+ "If you cannot use latin characters in your language, use english word.",
+ "macro");
+
+ //describing caption
+ m_names["instanceCaption"] = i18n("Macro");
+
+ //supported viewmodes
+ m_supportedViewModes = Kexi::DesignViewMode | Kexi::TextViewMode;
+}
+
+KexiMacroPart::~KexiMacroPart()
+{
+ //kdDebug() << "KexiMacroPart::~KexiMacroPart() Dtor" << endl;
+ delete d;
+}
+
+bool KexiMacroPart::execute(KexiPart::Item* item, QObject* sender)
+{
+ KexiDialogBase* dialog = new KexiDialogBase(m_mainWin);
+ dialog->setId( item->identifier() );
+ KexiMacroView* view = dynamic_cast<KexiMacroView*>( createView(dialog, dialog, *item, Kexi::DataViewMode) );
+ if(! view) {
+ kdWarning() << "KexiMacroPart::execute() Failed to create a view." << endl;
+ return false;
+ }
+
+ if(! view->macro().data()) {
+ kdWarning() << "KexiMacroPart::execute() No such item " << item->name() << endl;
+ return false;
+ }
+
+ kdDebug() << "KexiMacroPart::execute() itemname=" << item->name() << endl;
+ view->loadData();
+ view->execute(sender);
+ view->deleteLater();
+ return true;
+}
+
+void KexiMacroPart::initPartActions()
+{
+ //kdDebug() << "KexiMacroPart::initPartActions()" << endl;
+
+ KoMacro::Manager::init(m_mainWin);
+ new KexiMacro::OpenAction;
+ new KexiMacro::ExecuteAction;
+ new KexiMacro::DataTableAction;
+ new KexiMacro::NavigateAction;
+ new KexiMacro::MessageAction;
+}
+
+void KexiMacroPart::initInstanceActions()
+{
+ //kdDebug() << "KexiMacroPart::initInstanceActions()" << endl;
+ //createSharedAction(Kexi::DesignViewMode, i18n("Execute Macro"), "exec", 0, "data_execute");
+}
+
+KexiViewBase* KexiMacroPart::createView(QWidget* parent, KexiDialogBase* dialog, KexiPart::Item& item, int viewMode, QMap<QString,QString>*)
+{
+ const QString itemname = item.name();
+ //kdDebug() << "KexiMacroPart::createView() itemname=" << itemname << endl;
+
+ if(! itemname.isNull()) {
+ KSharedPtr<KoMacro::Macro> macro = ::KoMacro::Manager::self()->getMacro(itemname);
+ if(! macro) {
+ // If we don't have a macro with that name yet, create one.
+ macro = ::KoMacro::Manager::self()->createMacro(itemname);
+ // and remember the new macro for later usage.
+ ::KoMacro::Manager::self()->addMacro(itemname, macro);
+ }
+
+ KexiMainWindow *win = dialog->mainWin();
+ if(win && win->project() && win->project()->dbConnection()) {
+ if(viewMode == Kexi::DesignViewMode) {
+ return new KexiMacroDesignView(win, parent, macro);
+ }
+ if(viewMode == Kexi::TextViewMode) {
+ return new KexiMacroTextView(win, parent, macro);
+ }
+ if(viewMode == Kexi::DataViewMode) {
+ // Called if the macro should be executed.
+ return new KexiMacroView(win, parent, macro);
+ }
+ }
+ }
+
+ //kdDebug() << "KexiMacroPart::createView() No view available." << endl;
+ return 0;
+}
+
+QString KexiMacroPart::i18nMessage(const QCString& englishMessage) const
+{
+ if(englishMessage=="Design of object \"%1\" has been modified.") {
+ return i18n("Design of macro \"%1\" has been modified.");
+ }
+ if(englishMessage=="Object \"%1\" already exists.") {
+ return i18n("Macro \"%1\" already exists.");
+ }
+ return englishMessage;
+}
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_macro, KGenericFactory<KexiMacroPart>("kexihandler_macro") )
+
+#include "keximacropart.moc"
diff --git a/kexi/plugins/macros/kexipart/keximacropart.h b/kexi/plugins/macros/kexipart/keximacropart.h
new file mode 100644
index 000000000..8d2d7af25
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacropart.h
@@ -0,0 +1,95 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIMACROPART_H
+#define KEXIMACROPART_H
+
+//#include <qcstring.h>
+
+#include <kexi.h>
+#include <kexipart.h>
+#include <kexidialogbase.h>
+
+/**
+ * Kexi Macro Plugin.
+ */
+class KexiMacroPart : public KexiPart::Part
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ *
+ * \param parent The parent QObject this part is child of.
+ * \param name The name this part has.
+ * \param args Optional list of arguments passed to this part.
+ */
+ KexiMacroPart(QObject *parent, const char *name, const QStringList& args);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiMacroPart();
+
+ /**
+ * Implementation of the KexiPart::Part::action method used to
+ * provide scripts as KAction's to the outside world.
+ */
+ virtual bool execute(KexiPart::Item* item, QObject* sender = 0);
+
+ /**
+ * \return the i18n message for the passed \p englishMessage string.
+ */
+ virtual QString i18nMessage(const QCString& englishMessage) const;
+
+ protected:
+
+ /**
+ * Create a new view.
+ *
+ * \param parent The parent QWidget the new view is displayed in.
+ * \param dialog The \a KexiDialogBase the view is child of.
+ * \param item The \a KexiPart::Item this view is for.
+ * \param viewMode The viewmode we like to have a view for.
+ */
+ virtual KexiViewBase* createView(QWidget *parent,
+ KexiDialogBase* dialog,
+ KexiPart::Item& item,
+ int viewMode = Kexi::DesignViewMode,
+ QMap<QString,QString>* staticObjectArgs = 0);
+
+ /**
+ * Initialize the part's actions.
+ */
+ virtual void initPartActions();
+
+ /**
+ * Initialize the instance actions.
+ */
+ virtual void initInstanceActions();
+
+ private:
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+};
+
+#endif
+
diff --git a/kexi/plugins/macros/kexipart/keximacroproperty.cpp b/kexi/plugins/macros/kexipart/keximacroproperty.cpp
new file mode 100644
index 000000000..2fdafd283
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacroproperty.cpp
@@ -0,0 +1,626 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "keximacroproperty.h"
+
+#include <qlayout.h>
+#include <qlineedit.h>
+#include <qlistbox.h>
+#include <qpainter.h>
+
+#include <kcombobox.h>
+#include <kpushbutton.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+
+#include "../lib/variable.h"
+#include "../lib/macroitem.h"
+
+#define KEXIMACRO_PROPERTYEDITORTYPE 5682
+
+/*************************************************************
+ * KexiMacroProperty
+ */
+
+/**
+* @internal d-pointer class to be more flexible on future extension of the
+* functionality without to much risk to break the binary compatibility.
+*/
+class KexiMacroProperty::Private
+{
+ public:
+ /** The @a KoMacro::MacroItem the custom property uses
+ internal. Together with the name we are able to identify
+ the used variable at runtime. */
+ KSharedPtr<KoMacro::MacroItem> macroitem;
+ /** The name the variable @a KoMacro::Variable is known
+ as in the @a KoMacro::MacroItem defined above. */
+ QString name;
+};
+
+KexiMacroProperty::KexiMacroProperty(KoProperty::Property* parent, KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name)
+ : KoProperty::CustomProperty(parent)
+ , d( new Private() )
+{
+ d->macroitem = macroitem;
+ d->name = name;
+ init();
+}
+
+KexiMacroProperty::~KexiMacroProperty()
+{
+ delete d;
+}
+
+void KexiMacroProperty::init()
+{
+ Q_ASSERT( d->macroitem != 0 );
+ //kdDebug() << "--------- KexiMacroProperty::set() macroitem=" << d->macroitem->name() << " name=" << d->name << endl;
+
+ KSharedPtr<KoMacro::Action> action = d->macroitem->action();
+ KSharedPtr<KoMacro::Variable> actionvariable = action->variable(d->name);
+ if(! actionvariable.data()) {
+ kdDebug() << "KexiMacroProperty::createProperty() Skipped cause there exists no such action=" << d->name << endl;
+ return;
+ }
+
+ KSharedPtr<KoMacro::Variable> variable = d->macroitem->variable(d->name, true/*checkaction*/);
+ if(! variable.data()) {
+ kdDebug() << "KexiMacroProperty::createProperty() Skipped cause there exists no such variable=" << d->name << endl;
+ return;
+ }
+
+ //TESTCASE!!!!!!!!!!!!!!!!!!!!!!
+ //if(! variable->isEnabled()) qFatal( QString("############## VARIABLE=%1").arg(variable->name()).latin1() );
+
+ Q_ASSERT(! d->name.isNull());
+ m_property->setName( d->name.latin1() );
+ m_property->setCaption( actionvariable->text() );
+ m_property->setDescription( action->comment() );
+ m_property->setValue( variable->variant(), true );
+ m_property->setType( KEXIMACRO_PROPERTYEDITORTYPE ); // use our own propertytype
+}
+
+KoProperty::Property* KexiMacroProperty::parentProperty() const
+{
+ return m_property;
+}
+
+void KexiMacroProperty::setValue(const QVariant &value, bool rememberOldValue)
+{
+ Q_UNUSED(rememberOldValue);
+ kdDebug()<<"KexiMacroProperty::setValue name="<<d->name<<" value="<<value<<" rememberOldValue="<<rememberOldValue<<endl;
+ if(! d->macroitem->setVariant(d->name, value)) { // takes care of the type-conversation
+ kdDebug()<<"KexiMacroProperty::setValue Update failed !!!"<<endl;
+ return;
+ }
+
+ // m_property->setValue() does check if the value changed by using
+ // this-value() and cause we already set it above, m_property->setValue()
+ // will be aborted. Well, we don't touch the properties value and handle
+ // it all via our CustomProperty class anyway. So, just ignore the property.
+ //m_property->setValue(this->value(), rememberOldValue, false/*useCustomProperty*/);
+
+ emit valueChanged();
+}
+
+QVariant KexiMacroProperty::value() const
+{
+ KSharedPtr<KoMacro::Variable> variable = d->macroitem->variable(d->name, true);
+ Q_ASSERT( variable.data() != 0 );
+ return variable.data() ? variable->variant() : QVariant();
+}
+
+bool KexiMacroProperty::handleValue() const
+{
+ return true; // we handle getting and setting of values and don't need KoProperty::Property for it.
+}
+
+KSharedPtr<KoMacro::MacroItem> KexiMacroProperty::macroItem() const
+{
+ return d->macroitem;
+}
+
+QString KexiMacroProperty::name() const
+{
+ return d->name;
+}
+
+KSharedPtr<KoMacro::Variable> KexiMacroProperty::variable() const
+{
+ return d->macroitem->variable(d->name, true/*checkaction*/);
+}
+
+KoProperty::Property* KexiMacroProperty::createProperty(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name)
+{
+ KoProperty::Property* property = new KoProperty::Property();
+ KexiMacroProperty* customproperty = new KexiMacroProperty(property, macroitem, name);
+ if(! customproperty->variable().data()) {
+ kdWarning() << "KexiMacroProperty::createProperty() No such variable" << endl;
+ delete customproperty; customproperty = 0;
+ delete property; property = 0;
+ return 0;
+ }
+ property->setCustomProperty(customproperty);
+ return property;
+}
+
+/*************************************************************
+ * KexiMacroPropertyFactory
+ */
+
+KexiMacroPropertyFactory::KexiMacroPropertyFactory(QObject* parent)
+ : KoProperty::CustomPropertyFactory(parent)
+{
+}
+
+KexiMacroPropertyFactory::~KexiMacroPropertyFactory()
+{
+}
+
+KoProperty::CustomProperty* KexiMacroPropertyFactory::createCustomProperty(KoProperty::Property* parent)
+{
+ kdDebug()<<"KexiMacroPropertyFactory::createCustomProperty parent="<<parent->name()<<endl;
+
+ KoProperty::CustomProperty* customproperty = parent->customProperty();
+ KexiMacroProperty* parentcustomproperty = dynamic_cast<KexiMacroProperty*>(customproperty);
+ if(! parentcustomproperty) {
+ kdWarning() << "KexiMacroPropertyFactory::createCustomProperty() parent=" << parent->name() << " has an invalid customproperty." << endl;
+ return 0;
+ }
+
+ KSharedPtr<KoMacro::MacroItem> macroitem = parentcustomproperty->macroItem();
+ Q_ASSERT( macroitem.data() != 0 );
+ const QString name = parentcustomproperty->name();
+ Q_ASSERT(! name.isEmpty());
+
+ KexiMacroProperty* macroproperty = new KexiMacroProperty(parent, macroitem, name);
+ if(! macroproperty->variable().data()) {
+ delete macroproperty; macroproperty = 0;
+ return 0;
+ }
+
+ return macroproperty;
+}
+
+KoProperty::Widget* KexiMacroPropertyFactory::createCustomWidget(KoProperty::Property* property)
+{
+ kdDebug()<<"KexiMacroPropertyFactory::createCustomWidget property="<<property->name()<<endl;
+ return new KexiMacroPropertyWidget(property);
+}
+
+void KexiMacroPropertyFactory::initFactory()
+{
+ CustomPropertyFactory* factory = KoProperty::FactoryManager::self()->factoryForEditorType(KEXIMACRO_PROPERTYEDITORTYPE);
+ if(! factory) {
+ factory = new KexiMacroPropertyFactory( KoProperty::FactoryManager::self() );
+ KoProperty::FactoryManager::self()->registerFactoryForEditor(KEXIMACRO_PROPERTYEDITORTYPE, factory);
+ }
+}
+
+/*************************************************************
+ * KexiMacroPropertyWidget
+ */
+
+/**
+* @internal implementation of a QListBoxItem to display the items of the
+* combobox used within @a KexiMacroPropertyWidget to handle variables
+* within a @a ListBox instance.
+*/
+class ListBoxItem : public QListBoxText
+{
+ public:
+ ListBoxItem(QListBox* listbox)
+ : QListBoxText(listbox), m_enabled(true) {}
+ ListBoxItem(QListBox* listbox, const QString& text, QListBoxItem* after)
+ : QListBoxText(listbox, text, after), m_enabled(true) {}
+ virtual ~ListBoxItem() {}
+ void setEnabled(bool enabled) { m_enabled = enabled; }
+ virtual int width(const QListBox* lb) const {
+ Q_ASSERT( dynamic_cast<KComboBox*>( lb->parent() ) );
+ return static_cast<KComboBox*>( lb->parent() )->lineEdit()->width() + 2;
+ }
+ virtual int height(const QListBox* lb) const {
+ Q_ASSERT( dynamic_cast<KComboBox*>( lb->parent() ) );
+ return m_enabled ? static_cast<KComboBox*>( lb->parent() )->height() + 2 : 0;
+ }
+ private:
+ bool m_enabled;
+};
+
+/**
+* @internal implementation of a @a ListBoxItem to provide an editable
+* @a KoProperty::Widget as QListBoxItem in a @a ListBox instance.
+*/
+class EditListBoxItem : public ListBoxItem
+{
+ public:
+
+ EditListBoxItem(QListBox* listbox, KexiMacroProperty* macroproperty)
+ : ListBoxItem(listbox)
+ , m_macroproperty(macroproperty)
+ , m_prop(0)
+ , m_widget(0)
+ {
+ init();
+ }
+
+ virtual ~EditListBoxItem() {
+ delete m_widget;
+ delete m_prop;
+ }
+
+ virtual QString text() const {
+ KSharedPtr<KoMacro::Variable> variable = m_macroproperty->variable();
+ Q_ASSERT( variable.data() );
+ //kdDebug()<<"EditListBoxItem::text() text="<<variable->toString()<<endl;
+ Q_ASSERT( variable->toString() != QString::null );
+ return variable->toString();
+ }
+
+ KoProperty::Widget* widget() const { return m_widget; }
+ KSharedPtr<KoMacro::MacroItem> macroItem() const { return m_macroproperty->macroItem(); }
+ KSharedPtr<KoMacro::Variable> variable() const { return m_macroproperty->variable(); }
+ KSharedPtr<KoMacro::Action> action() const { return m_macroproperty->macroItem()->action(); }
+
+ protected:
+ virtual void paint(QPainter* p) {
+ if(! m_widget) return;
+ Q_ASSERT( dynamic_cast<KComboBox*>( listBox()->parent() ) );
+ const int w = width(listBox());
+ const int h = height(listBox());
+ m_widget->setFixedSize(w - 2, h - 2);
+ p->drawPixmap(0, 0, QPixmap::grabWidget(m_widget), 1, 1, w - 1, h - 1);
+ }
+
+ private:
+ void init() {
+ KSharedPtr<KoMacro::MacroItem> macroitem = m_macroproperty->macroItem();
+ Q_ASSERT( macroitem.data() );
+ KSharedPtr<KoMacro::Action> action = m_macroproperty->macroItem()->action();
+ if(! action.data()) {
+ kdWarning() << "EditListBoxItem::EditListBoxItem() Skipped cause there exists no action for macroproperty=" << m_macroproperty->name() << endl;
+ return;
+ }
+ KoProperty::Property* parentproperty = m_macroproperty->parentProperty();
+ if(! parentproperty) {
+ kdWarning() << "EditListBoxItem::EditListBoxItem() No parentproperty defined" << endl;
+ return;
+ }
+ KSharedPtr<KoMacro::Variable> variable = m_macroproperty->variable();
+ if(! variable.data()) {
+ kdWarning() << "EditListBoxItem::EditListBoxItem() No variable defined for property=" << parentproperty->name() << endl;
+ return;
+ }
+
+ QVariant variant = variable->variant();
+
+ KSharedPtr<KoMacro::Variable> actionvariable = action->variable(m_macroproperty->name());
+ if(actionvariable.data()) {
+ QVariant actionvariant = actionvariable->variant();
+ Q_ASSERT( ! actionvariant.isNull() );
+ Q_ASSERT( variant.canCast(actionvariant.type()) );
+ variant.cast( actionvariant.type() ); //preserve type.
+ }
+
+ int type = KoProperty::Auto;
+ switch(variant.type()) {
+ case QVariant::UInt:
+ case QVariant::Int: {
+ type = KoProperty::Integer;
+ } break;
+ case QVariant::CString:
+ case QVariant::String: {
+ type = KoProperty::String;
+ } break;
+ default: {
+ kdWarning() << "EditListBoxItem::EditListBoxItem() name=" << variable->name() << " type=" << QVariant::typeToName(variant.type()) << endl;
+ } break;
+ }
+
+ QString name = variable->name();
+ Q_ASSERT(! name.isNull());
+ //if(name.isNull()) name = "aaaaaaaaaaaaaaaaa";//TESTCASE
+ m_prop = new KoProperty::Property(
+ name.latin1(), // name
+ variant, // value
+ variable->text(), // caption
+ QString::null, // description
+ type, // type
+ 0 //parentproperty // parent
+ );
+
+ m_widget = KoProperty::FactoryManager::self()->createWidgetForProperty(m_prop);
+ Q_ASSERT( m_widget != 0 );
+ //m_widget->reparent(listBox()->viewport(), 0, QPoint(0,0));
+ m_widget->reparent(listBox(), 0, QPoint(1,1));
+ //layout->addWidget(m_widget, 1);
+ m_widget->setMinimumHeight(5);
+ m_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ }
+
+ private:
+ KexiMacroProperty* m_macroproperty;
+ KoProperty::Property* m_prop;
+ KoProperty::Widget* m_widget;
+};
+
+/**
+* @internal implementation of a @a QListBox for the combobox used within
+* @a KexiMacroPropertyWidget to handle different variable-states.
+*/
+class ListBox : public QListBox
+{
+ public:
+ ListBox(KComboBox* parent, KexiMacroProperty* macroproperty)
+ : QListBox(parent)
+ , m_macroproperty(macroproperty)
+ , m_edititem(0)
+ {
+ viewport()->setBackgroundMode(PaletteBackground);
+ setVariableHeight(true);
+ update();
+ }
+
+ virtual ~ListBox() {}
+
+ void update() {
+ m_items.clear();
+ delete m_edititem;
+ m_edititem = 0;
+ clear();
+
+ m_edititem = new EditListBoxItem(this, m_macroproperty);
+ Q_ASSERT( m_edititem->widget() != 0 );
+
+ const QString name = m_macroproperty->name();
+ KoMacro::Variable::List children;
+ {
+ KoMacro::Variable::List actionchildren;
+
+ KSharedPtr<KoMacro::Variable> itemvar = m_macroproperty->macroItem()->variable(name,false);
+ //kdDebug() << "KexiMacroProperty::ListBox::update() itemvar="<<(itemvar.data() ? "name:"+itemvar->name()+" value:"+itemvar->toString() : "NULL")<<endl;
+ if(itemvar.data())
+ actionchildren = itemvar->children();
+
+ KSharedPtr<KoMacro::Action> action = m_edititem->action();
+ KSharedPtr<KoMacro::Variable> actionvar = action.data() ? action->variable(name) : KSharedPtr<KoMacro::Variable>();
+ //kdDebug() << "KexiMacroProperty::ListBox::update() actionvar="<<(actionvar.data() ? "name:"+actionvar->name()+" value:"+actionvar->toString() : "NULL")<<endl;
+ if(actionvar.data())
+ actionchildren += actionvar->children();
+
+ KoMacro::Variable::List::ConstIterator it(actionchildren.constBegin()), end(actionchildren.constEnd());
+ for(; it != end; ++it) {
+ if(name == (*it)->name()) {
+ KoMacro::Variable::List list = (*it)->children();
+ KoMacro::Variable::List::ConstIterator listit(list.constBegin()), listend(list.constEnd());
+ for(; listit != listend; ++listit)
+ children.append( *listit );
+ }
+ }
+
+ if(children.count() <= 0)
+ children = actionchildren;
+ }
+
+ /*
+ kdDebug() << "KexiMacroProperty::ListBox::update() name="<<name<<" childcount="<<children.count()<<endl;
+ KoMacro::Variable::List::ConstIterator listit(children.constBegin()), listend(children.constEnd());
+ for(; listit != listend; ++listit) {
+ kdDebug()<<" child name="<<(*listit)->name()<<" value="<<(*listit)->toString()<<" childcount="<<(*listit)->children().count()<<endl;
+ }
+ */
+
+ if(children.count() > 0) {
+ KoMacro::Variable::List::Iterator childit(children.begin()), childend(children.end());
+ for(; childit != childend; ++childit) {
+ const QString n = (*childit)->name();
+ //if(! n.startsWith("@")) continue;
+ const QVariant v = (*childit)->variant();
+
+ //kdDebug() << " child name=" << n << " value=" << v << endl;
+ switch( v.type() ) {
+ /* case QVariant::Map: {
+ const QMap<QString,QVariant> map = v.toMap();
+ for(QMap<QString,QVariant>::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it)
+ m_items.append(it.key());
+ } break; */
+ case QVariant::List: {
+ const QValueList<QVariant> list = v.toList();
+ for(QValueList<QVariant>::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) {
+ const QString s = (*it).toString().stripWhiteSpace();
+ if(! s.isEmpty())
+ m_items.append(s);
+ }
+ } break;
+ case QVariant::StringList: {
+ const QStringList list = v.toStringList();
+ for(QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it)
+ if(! (*it).isEmpty())
+ m_items.append(*it);
+ } break;
+ default: {
+ const QString s = v.toString().stripWhiteSpace();
+ if(! s.isEmpty())
+ m_items.append(s);
+ } break;
+ }
+ }
+ }
+
+ QListBoxItem* item = m_edititem;
+ const uint count = m_items.count();
+ for(uint i = 0; i < count; i++)
+ item = new ListBoxItem(this, m_items[i], item);
+ }
+
+ EditListBoxItem* editItem() const { return m_edititem; }
+ QStringList items() const { return m_items; }
+
+ virtual void hide () {
+ QListBox::hide();
+ for(uint i = 0; i < count(); i++)
+ static_cast<ListBoxItem*>( item(i) )->setEnabled(false);
+ }
+ virtual void show() {
+ update();
+ adjustSize();
+ QListBox::show();
+ }
+
+ private:
+ KexiMacroProperty* m_macroproperty;
+ EditListBoxItem* m_edititem;
+ QStringList m_items;
+};
+
+/**
+* @internal d-pointer class to be more flexible on future extension of the
+* functionality without to much risk to break the binary compatibility.
+*/
+class KexiMacroPropertyWidget::Private
+{
+ public:
+ KexiMacroProperty* macroproperty;
+ KComboBox* combobox;
+ ListBox* listbox;
+};
+
+KexiMacroPropertyWidget::KexiMacroPropertyWidget(KoProperty::Property* property, QWidget* parent)
+ : KoProperty::Widget(property, parent)
+ , d( new Private() )
+{
+ kdDebug() << "KexiMacroPropertyWidget::KexiMacroPropertyWidget() Ctor" << endl;
+
+ QHBoxLayout* layout = new QHBoxLayout(this, 0, 0);
+
+ d->macroproperty = dynamic_cast<KexiMacroProperty*>( property->customProperty() );
+ if(! d->macroproperty) {
+ kdWarning() << "KexiMacroPropertyWidget::KexiMacroPropertyWidget() Missing macroproperty for property=" << property->name() << endl;
+ return;
+ }
+
+ d->combobox = new KComboBox(this);
+ layout->addWidget(d->combobox);
+ d->listbox = new ListBox(d->combobox, d->macroproperty);
+ d->combobox->setEditable(true);
+ d->combobox->setListBox(d->listbox);
+ d->combobox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ d->combobox->setMinimumHeight(5);
+ d->combobox->setInsertionPolicy(QComboBox::NoInsertion);
+ d->combobox->setMinimumSize(10, 0); // to allow the combo to be resized to a small size
+ d->combobox->setAutoCompletion(false);
+ d->combobox->setContextMenuEnabled(false);
+
+ QVariant value = d->macroproperty->value();
+ int index = d->listbox->items().findIndex( value.toString() );
+ if(index >= 0) {
+ d->combobox->setCurrentItem(index + 1);
+ d->listbox->setCurrentItem(index + 1);
+ }
+ else {
+ Q_ASSERT( d->listbox->editItem()->widget() != 0 );
+ d->listbox->editItem()->widget()->setValue( d->macroproperty->value(), true );
+ //d->combobox->setCurrentItem(0);
+ }
+ kdDebug() << ">>> KexiMacroPropertyWidget::KexiMacroPropertyWidget() CurrentItem=" << d->combobox->currentItem() << endl;
+
+ d->combobox->setFocusProxy( d->listbox->editItem()->widget() );
+ setFocusWidget( d->combobox->lineEdit() );
+
+ connect(d->combobox, SIGNAL(textChanged(const QString&)),
+ this, SLOT(slotComboBoxChanged()));
+ connect(d->combobox, SIGNAL(activated(int)),
+ this, SLOT(slotComboBoxActivated()));
+ connect(d->listbox->editItem()->widget(), SIGNAL(valueChanged(Widget*)),
+ this, SLOT(slotWidgetValueChanged()));
+ connect(d->macroproperty, SIGNAL(valueChanged()),
+ this, SLOT(slotPropertyValueChanged()));
+}
+
+KexiMacroPropertyWidget::~KexiMacroPropertyWidget()
+{
+ kdDebug() << "KexiMacroPropertyWidget::~KexiMacroPropertyWidget() Dtor" << endl;
+ delete d;
+}
+
+QVariant KexiMacroPropertyWidget::value() const
+{
+ kdDebug()<<"KexiMacroPropertyWidget::value() value="<<d->macroproperty->value()<<endl;
+ return d->macroproperty->value();
+ /* QVariant value = d->combobox->currentText();
+ value.cast( d->macroproperty->value().type() );
+ return value; */
+}
+
+void KexiMacroPropertyWidget::setValue(const QVariant& value, bool emitChange)
+{
+ kdDebug()<<"KexiMacroPropertyWidget::setValue() value="<<value<<" emitChange="<<emitChange<<endl;
+
+ if(! emitChange)
+ d->combobox->blockSignals(true);
+
+ const QString s = value.toString();
+ d->combobox->setCurrentText( s.isNull() ? "" : s );
+
+ if(emitChange)
+ emit valueChanged(this);
+ else
+ d->combobox->blockSignals(false);
+}
+
+void KexiMacroPropertyWidget::setReadOnlyInternal(bool readOnly)
+{
+ Q_UNUSED(readOnly);
+ //kdDebug()<<"KexiMacroPropertyWidget::setReadOnlyInternal() readOnly="<<readOnly<<endl;
+}
+
+void KexiMacroPropertyWidget::slotComboBoxChanged()
+{
+ kdDebug()<<"KexiMacroPropertyWidget::slotComboBoxChanged()"<<endl;
+ const QVariant v = d->combobox->currentText();
+ d->macroproperty->setValue(v, true);
+ //emit valueChanged(this);
+}
+
+void KexiMacroPropertyWidget::slotComboBoxActivated()
+{
+ Q_ASSERT( d->listbox->editItem()->widget() );
+ const int index = d->combobox->currentItem();
+ QString text = (index == 0)
+ ? d->listbox->editItem()->widget()->value().toString()
+ : d->combobox->text(index);
+ kdDebug()<<"KexiMacroPropertyWidget::slotComboBoxActivated() index="<<index<<" text="<<text<<endl;
+ d->combobox->setCurrentText(text);
+ slotWidgetValueChanged();
+}
+
+void KexiMacroPropertyWidget::slotWidgetValueChanged()
+{
+ d->macroproperty->emitPropertyChanged();
+}
+
+void KexiMacroPropertyWidget::slotPropertyValueChanged()
+{
+ Q_ASSERT( d->listbox->editItem()->widget() );
+ const QVariant v = d->macroproperty->value();
+ kdDebug()<<"KexiMacroPropertyWidget::slotPropertyValueChanged() value="<<v<<endl;
+ d->listbox->editItem()->widget()->setValue(v, true);
+}
+
+#include "keximacroproperty.moc"
diff --git a/kexi/plugins/macros/kexipart/keximacroproperty.h b/kexi/plugins/macros/kexipart/keximacroproperty.h
new file mode 100644
index 000000000..19f7b7ac1
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacroproperty.h
@@ -0,0 +1,186 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIMACROPROPERTY_H
+#define KEXIMACROPROPERTY_H
+
+#include <ksharedptr.h>
+#include <koproperty/property.h>
+#include <koproperty/factory.h>
+#include <koproperty/customproperty.h>
+#include <koproperty/widget.h>
+
+namespace KoMacro {
+ class Variable;
+ class MacroItem;
+}
+
+class KexiMacroPropertyWidget;
+
+/**
+* Implementation of a @a KoProperty::CustomProperty to have
+* more control about the handling of our macro-properties.
+*/
+class KexiMacroProperty
+ : public QObject
+ , public KoProperty::CustomProperty
+{
+ Q_OBJECT
+
+ friend class KexiMacroPropertyWidget;
+
+ public:
+
+ /** Constructor. */
+ explicit KexiMacroProperty(KoProperty::Property* parent, KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name);
+ /** Destructor. */
+ virtual ~KexiMacroProperty();
+
+ /** @return the parent @a KoProperty::Property instance. */
+ KoProperty::Property* parentProperty() const;
+
+ /** This function is called by @ref KoProperty::Property::setValue()
+ when a custom property is set.
+ You don't have to modify the property value, it is done by Property class.
+ You just have to update child or parent properties value (m_property->parent()->setValue()).
+ Note that, when calling Property::setValue, you <b>need</b> to set
+ useCustomProperty (3rd parameter) to false, or there will be infinite recursion. */
+ virtual void setValue(const QVariant &value, bool rememberOldValue);
+
+ /** This function is called by @ref KoProperty::Property::value()
+ when a custom property is set and @ref handleValue() is true.
+ You should return property's value, taken from parent's value.*/
+ virtual QVariant value() const;
+
+ /** Tells whether CustomProperty should be used to get the property's value.
+ You should return true for child properties, and false for others. */
+ virtual bool handleValue() const;
+
+ /** \return the \a KoMacro::MacroItem this custom property has or
+ NULL if there was no item provided. */
+ KSharedPtr<KoMacro::MacroItem> macroItem() const;
+
+ /** \return the name the property has in the \a KoMacro::MacroItem
+ above. Is QString::null if there was no item provided. */
+ QString name() const;
+
+ /** \return the \a KoMacro::Variable which has the name @a name()
+ in the item @a macroItem() . If such a variable doesn't exists NULL
+ is returned. */
+ KSharedPtr<KoMacro::Variable> variable() const;
+
+ /** Factory function to create a new @a KoProperty::Property instance
+ that will use a @a KexiMacroProperty as container. */
+ static KoProperty::Property* createProperty(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name);
+
+ signals:
+
+ /** Emitted if @a setValue was called and the value changed. */
+ void valueChanged();
+
+ private:
+ /** \internal d-pointer class. */
+ class Private;
+ /** \internal d-pointer instance. */
+ Private* const d;
+ /** \internal initializer method. */
+ inline void init();
+};
+
+/**
+* Implementation of a @a KoProperty::CustomPropertyFactory to handle
+* creation of @a KexiMacroProperty and @a KexiMacroPropertyWidget
+* instances for our macro-properties.
+*/
+class KexiMacroPropertyFactory : public KoProperty::CustomPropertyFactory
+{
+ public:
+ /** Constructor. */
+ explicit KexiMacroPropertyFactory(QObject* parent);
+ /** Destructor. */
+ virtual ~KexiMacroPropertyFactory();
+
+ /** @return a new instance of custom property for @p parent.
+ Implement this for property types you want to support.
+ Use parent->type() to get type of the property. */
+ virtual KoProperty::CustomProperty* createCustomProperty(KoProperty::Property* parent);
+
+ /** @return a new instance of custom property for @p property.
+ Implement this for property editor types you want to support.
+ Use parent->type() to get type of the property. */
+ virtual KoProperty::Widget* createCustomWidget(KoProperty::Property* property);
+
+ /** Initializes this factory. The factory may register itself at
+ the @a KoProperty::FactoryManager if not alreadydone before. This
+ function should be called from within the @a KexiMacroDesignView
+ before the functionality provided with @a KexiMacroProperty and
+ @a KexiMacroPropertyWidget got used. */
+ static void initFactory();
+};
+
+/**
+ * Implementation of a @a KoProperty::Widget used to display and
+ * edit a @a KexiMacroProperty .
+ */
+class KexiMacroPropertyWidget : public KoProperty::Widget
+{
+ Q_OBJECT
+
+ public:
+ /** Constructor. */
+ explicit KexiMacroPropertyWidget(KoProperty::Property* property, QWidget* parent = 0);
+ /** Destructor. */
+ virtual ~KexiMacroPropertyWidget();
+
+ /** @return the value this widget has. */
+ virtual QVariant value() const;
+
+ /** Set the value @p value this widget has. If @p emitChange is true,
+ the @p KoProperty::Widget::valueChanged signal will be emitted. */
+ virtual void setValue(const QVariant& value, bool emitChange=true);
+
+ //virtual void drawViewer(QPainter *p, const QColorGroup &cg, const QRect &r, const QVariant &value);
+
+ protected:
+
+ /** Called if the value should be read only. */
+ virtual void setReadOnlyInternal(bool readOnly);
+
+ private slots:
+
+ /** Called if the text in the KComboBox changed. */
+ void slotComboBoxChanged();
+
+ /** Called if an item in the QListBox of the KComboBox got activated. */
+ void slotComboBoxActivated();
+
+ /** Called if the @a KoProperty::Widget of the EditListBoxItem got changed. */
+ void slotWidgetValueChanged();
+
+ /** Called if the value of a @a KexiMacroProperty changed to update
+ the widget and the displayed content. */
+ void slotPropertyValueChanged();
+
+ private:
+ /** \internal d-pointer class. */
+ class Private;
+ /** \internal d-pointer instance. */
+ Private* const d;
+};
+
+#endif
+
diff --git a/kexi/plugins/macros/kexipart/keximacrotextview.cpp b/kexi/plugins/macros/kexipart/keximacrotextview.cpp
new file mode 100644
index 000000000..95c94a479
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacrotextview.cpp
@@ -0,0 +1,90 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "keximacrotextview.h"
+
+#include <ktextedit.h>
+#include <kdebug.h>
+
+#include <kexidialogbase.h>
+#include <kexidb/connection.h>
+
+#include "../lib/macro.h"
+#include "../lib/xmlhandler.h"
+
+/**
+* \internal d-pointer class to be more flexible on future extension of the
+* functionality without to much risk to break the binary compatibility.
+*/
+class KexiMacroTextView::Private
+{
+ public:
+
+ /**
+ * The Editor used to display and edit the XML text.
+ */
+ KTextEdit* editor;
+
+};
+
+KexiMacroTextView::KexiMacroTextView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro)
+ : KexiMacroView(mainwin, parent, macro, "KexiMacroTextView")
+ , d( new Private() )
+{
+ QHBoxLayout* layout = new QHBoxLayout(this);
+ d->editor = new KTextEdit(this);
+ d->editor->setTextFormat(Qt::PlainText);
+ d->editor->setWordWrap(QTextEdit::NoWrap);
+ layout->addWidget(d->editor);
+
+ connect(d->editor, SIGNAL(textChanged()), this, SLOT(editorChanged()));
+}
+
+KexiMacroTextView::~KexiMacroTextView()
+{
+ delete d;
+}
+
+void KexiMacroTextView::editorChanged()
+{
+ setDirty(true);
+}
+
+bool KexiMacroTextView::loadData()
+{
+ QString data;
+ if(! loadDataBlock(data)) {
+ kexipluginsdbg << "KexiMacroTextView::loadData(): no DataBlock" << endl;
+ return false;
+ }
+
+ kdDebug() << QString("KexiMacroTextView::loadData()\n%1").arg(data) << endl;
+ //d->editor->blockSignals(true);
+ d->editor->setText(data);
+ //d->editor->blockSignals(false);
+ setDirty(false);
+ return true;
+}
+
+tristate KexiMacroTextView::storeData(bool /*dontAsk*/)
+{
+ kexipluginsdbg << QString("KexiMacroTextView::storeData() %1 [%2]\n%3").arg(parentDialog()->partItem()->name()).arg(parentDialog()->id()).arg(d->editor->text()) << endl;
+ return storeDataBlock( d->editor->text() );
+}
+
+#include "keximacrotextview.moc"
+
diff --git a/kexi/plugins/macros/kexipart/keximacrotextview.h b/kexi/plugins/macros/kexipart/keximacrotextview.h
new file mode 100644
index 000000000..66a2229c5
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacrotextview.h
@@ -0,0 +1,77 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIMACROTEXTVIEW_H
+#define KEXIMACROTEXTVIEW_H
+
+#include "keximacroview.h"
+
+// Forward declaration.
+namespace KoMacro {
+ class Macro;
+}
+
+/**
+ * The KexiMacroTextView implements \a KexiMacroView to provide
+ * a simple texteditor to edit the XML document of a Macro.
+ */
+class KexiMacroTextView : public KexiMacroView
+{
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ *
+ * \param mainwin The \a KexiMainWindow instance this \a KexiViewBase
+ * belongs to.
+ * \param parent The parent widget this widget should be displayed in.
+ * \param macro The \a KoMacro::Macro instance this view is for.
+ */
+ KexiMacroTextView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiMacroTextView();
+
+ /**
+ * Load the data and display it in the editor.
+ */
+ virtual bool loadData();
+
+ /**
+ * Try to store the modified data in the already opened and
+ * currently used \a KexiDB::SchemaData instance.
+ */
+ virtual tristate storeData(bool dontAsk = false);
+
+ private slots:
+
+ /**
+ * This slot got called if the text of the editor changed.
+ */
+ void editorChanged();
+
+ private:
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+};
+
+#endif
diff --git a/kexi/plugins/macros/kexipart/keximacroview.cpp b/kexi/plugins/macros/kexipart/keximacroview.cpp
new file mode 100644
index 000000000..352008297
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacroview.cpp
@@ -0,0 +1,175 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "keximacroview.h"
+
+#include <qdom.h>
+#include <kdebug.h>
+
+#include <kexidialogbase.h>
+#include <kexidb/connection.h>
+#include <kexidb/error.h>
+
+#include <core/kexi.h>
+#include <core/kexiproject.h>
+#include <core/kexipartmanager.h>
+#include <core/kexipartinfo.h>
+
+#include "../lib/macro.h"
+#include "../lib/xmlhandler.h"
+#include "../lib/exception.h"
+
+#include "keximacroerror.h"
+
+/**
+* \internal d-pointer class to be more flexible on future extension of the
+* functionality without to much risk to break the binary compatibility.
+*/
+class KexiMacroView::Private
+{
+ public:
+
+ /**
+ * The \a KoMacro::Manager instance used to access the
+ * Macro Framework.
+ */
+ KSharedPtr<KoMacro::Macro> macro;
+
+ /**
+ * Constructor.
+ *
+ * \param m The passed \a KoMacro::Manager instance our
+ * \a manager points to.
+ */
+ Private(KoMacro::Macro* const m)
+ : macro(m)
+ {
+ }
+
+};
+
+KexiMacroView::KexiMacroView(KexiMainWindow *mainwin, QWidget *parent, KoMacro::Macro* const macro, const char* name)
+ : KexiViewBase(mainwin, parent, (name ? name : "KexiMacroView"))
+ , d( new Private(macro) )
+{
+ //kdDebug() << "KexiMacroView::KexiMacroView() Ctor" << endl;
+ plugSharedAction( "data_execute", this, SLOT(execute()) );
+}
+
+KexiMacroView::~KexiMacroView()
+{
+ //kdDebug() << "KexiMacroView::~KexiMacroView() Dtor" << endl;
+ delete d;
+}
+
+KSharedPtr<KoMacro::Macro> KexiMacroView::macro() const
+{
+ return d->macro;
+}
+
+tristate KexiMacroView::beforeSwitchTo(int mode, bool& dontstore)
+{
+ kexipluginsdbg << "KexiMacroView::beforeSwitchTo mode=" << mode << " dontstore=" << dontstore << endl;
+ return true;
+}
+
+tristate KexiMacroView::afterSwitchFrom(int mode)
+{
+ kexipluginsdbg << "KexiMacroView::afterSwitchFrom mode=" << mode << endl;
+ loadData(); // reload the data
+ return true;
+}
+
+bool KexiMacroView::loadData()
+{
+ d->macro->clearItems();
+
+ QString data;
+ if(! loadDataBlock(data)) {
+ kexipluginsdbg << "KexiMacroView::loadData(): no DataBlock" << endl;
+ return false;
+ }
+
+ QString errmsg;
+ int errline, errcol;
+
+ QDomDocument domdoc;
+ bool parsed = domdoc.setContent(data, false, &errmsg, &errline, &errcol);
+
+ if(! parsed) {
+ kexipluginsdbg << "KexiMacroView::loadData() XML parsing error line: " << errline << " col: " << errcol << " message: " << errmsg << endl;
+ return false;
+ }
+
+ kexipluginsdbg << QString("KexiMacroView::loadData()\n%1").arg(domdoc.toString()) << endl;
+ QDomElement macroelem = domdoc.namedItem("macro").toElement();
+ if(macroelem.isNull()) {
+ kexipluginsdbg << "KexiMacroView::loadData() Macro domelement is null" << endl;
+ return false;
+ }
+
+ //kexipluginsdbg << "KexiMacroView::loadData()" << endl;
+ return d->macro->parseXML(macroelem);
+}
+
+KexiDB::SchemaData* KexiMacroView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ KexiDB::SchemaData *schema = KexiViewBase::storeNewData(sdata, cancel);
+ kexipluginsdbg << "KexiMacroView::storeNewData() new id:" << schema->id() << endl;
+
+ if(!schema || cancel) {
+ delete schema;
+ return 0;
+ }
+
+ if(! storeData()) {
+ kexipluginsdbg << "KexiMacroView::storeNewData() Failed to store the data." << endl;
+ //failure: remove object's schema data to avoid garbage
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ conn->removeObject( schema->id() );
+ delete schema;
+ return 0;
+ }
+
+ return schema;
+}
+
+tristate KexiMacroView::storeData(bool /*dontAsk*/)
+{
+ QDomDocument domdoc("macros");
+ QDomElement macroelem = d->macro->toXML();
+ domdoc.appendChild(macroelem);
+ const QString xml = domdoc.toString(2);
+ const QString name = QString("%1 [%2]").arg(parentDialog()->partItem()->name()).arg(parentDialog()->id());
+ kexipluginsdbg << QString("KexiMacroView::storeData %1\n%2").arg(name).arg(xml) << endl;
+ return storeDataBlock(xml);
+}
+
+void KexiMacroView::execute(QObject* sender)
+{
+ KSharedPtr<KoMacro::Context> context = d->macro->execute(sender);
+ if(context->hadException()) {
+ KexiMacroError* error = new KexiMacroError(
+ mainWin(), // The parent KexiMainWindow
+ context // The KoMacro::Context where the error occured.
+ );
+ error->exec();
+ }
+}
+
+#include "keximacroview.moc"
+
diff --git a/kexi/plugins/macros/kexipart/keximacroview.h b/kexi/plugins/macros/kexipart/keximacroview.h
new file mode 100644
index 000000000..beed842ec
--- /dev/null
+++ b/kexi/plugins/macros/kexipart/keximacroview.h
@@ -0,0 +1,140 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIMACROVIEW_H
+#define KEXIMACROVIEW_H
+
+#include <kexiviewbase.h>
+
+// Forward declarations.
+namespace KoMacro {
+ class Macro;
+}
+namespace KoProperty {
+ class Property;
+}
+namespace KexiDB {
+ class ResultInfo;
+}
+class KexiTableItem;
+
+/**
+ * The KexiMacroView implements \a KexiViewBase to provide
+ * a base KexiView instance for Macros.
+ *
+ * The \a KexiMacroDesignView and the \a KexiMacroTextView
+ * are inherited from this class.
+ */
+class KexiMacroView : public KexiViewBase
+{
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ *
+ * \param mainwin The \a KexiMainWindow instance this \a KexiViewBase
+ * belongs to.
+ * \param parent The parent widget this widget should be displayed in.
+ * \param macro The \a KoMacro::Macro instance this view is for.
+ */
+ KexiMacroView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro, const char* name = 0);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiMacroView();
+
+ /**
+ * \return the Macro instance.
+ */
+ KSharedPtr<KoMacro::Macro> macro() const;
+
+ /**
+ * Load the data from XML source and fill the internally
+ * used \a KoMacro::Macro instance.
+ */
+ virtual bool loadData();
+
+ /**
+ * Try to call \a storeData with new data we like to store. On
+ * success the matching \a KexiDB::SchemaData is returned.
+ *
+ * \param sdata The source \a KexiDB::SchemaData instance.
+ * \param cancel Cancel on failure and don't try to clean
+ * possible temporary created data up.
+ * \return The matching \a KexiDB::SchemaData instance or NULL
+ * if storing failed.
+ */
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+
+ /**
+ * Try to store the modified data in the already opened and
+ * currently used \a KexiDB::SchemaData instance.
+ */
+ virtual tristate storeData(bool dontAsk = false);
+
+ public slots:
+
+ /**
+ * This slot will be invoked if Kexi's menuitem Data=>Execute
+ * got activated and will execute the Macro.
+ */
+ void execute(QObject* sender = 0);
+
+ protected:
+
+ /**
+ * Called by \a KexiDialogBase::switchToViewMode() right before dialog
+ * is switched to new mode.
+ *
+ * \param mode The viewmode to which should be switched. This
+ * could be either Kexi::DataViewMode, Kexi::DesignViewMode
+ * or Kexi::TextViewMode.
+ * \param donstore This call-by-reference boolean value defines
+ * if \a storeData should be called for the old but still
+ * selected viewmode. Set \a dontstore to true (it's false
+ * by default) if you want to avoid data storing.
+ * \return true if you accept or false if a error occupied and view
+ * shouldn't change. If there is no error but switching
+ * should be just cancelled (probably after showing some
+ * info messages), you need to return cancelled.
+ */
+ virtual tristate beforeSwitchTo(int mode, bool& dontstore);
+
+ /**
+ * Called by \a KexiDialogBase::switchToViewMode() right after dialog
+ * is switched to new mode.
+ *
+ * \param mode The viewmode to which we switched. This could
+ * be either Kexi::DataViewMode, Kexi::DesignViewMode
+ * or Kexi::TextViewMode.
+ * \return true if you accept or false if a error occupied and view
+ * shouldn't change. If there is no error but switching
+ * should be just cancelled (probably after showing
+ * some info messages), you need to return cancelled.
+ */
+ virtual tristate afterSwitchFrom(int mode);
+
+ private:
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+};
+
+#endif
diff --git a/kexi/plugins/macros/lib/Makefile.am b/kexi/plugins/macros/lib/Makefile.am
new file mode 100644
index 000000000..fc7867b8f
--- /dev/null
+++ b/kexi/plugins/macros/lib/Makefile.am
@@ -0,0 +1,23 @@
+noinst_LTLIBRARIES = libkomacro.la
+
+libkomacro_la_SOURCES = \
+ exception.cpp \
+ variable.cpp \
+ metaparameter.cpp \
+ metamethod.cpp \
+ metaobject.cpp \
+ action.cpp \
+ macroitem.cpp \
+ macro.cpp \
+ context.cpp \
+ xmlhandler.cpp \
+ manager.cpp
+
+KDE_CXXFLAGS = $(USE_EXCEPTIONS)
+
+libkomacro_la_LDFLAGS = $(all_libraries) -Wnounresolved
+libkomacro_la_LIBADD = $(LIB_QT) $(LIB_KDECORE) $(LIB_KDEUI)
+
+libkomacro_la_METASOURCES = AUTO
+SUBDIRS = .
+INCLUDES = $(all_includes)
diff --git a/kexi/plugins/macros/lib/action.cpp b/kexi/plugins/macros/lib/action.cpp
new file mode 100644
index 000000000..e2dc0b64a
--- /dev/null
+++ b/kexi/plugins/macros/lib/action.cpp
@@ -0,0 +1,170 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "action.h"
+
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class Action::Private
+ {
+ public:
+
+ /**
+ * The name this @a Action has.
+ */
+ QString name;
+
+ /**
+ * The i18n-caption text this @a Action has.
+ */
+ QString text;
+
+ /**
+ * The comment the user is able to define for each action.
+ */
+ QString comment;
+
+ /**
+ * A map of @a Variable instances this @a Action
+ * provides accessible by there QString name.
+ */
+ Variable::Map varmap;
+
+ /**
+ * List of variablenames. This list provides a
+ * sorted order for the @a Variable instances
+ * defined in the map above.
+ */
+ QStringList varnames;
+
+ };
+
+}
+
+Action::Action(const QString& name, const QString& text)
+ : QObject()
+ , KShared()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+ kdDebug() << "Action::Action() name=" << name << endl;
+ d->name = name;
+ setText(text);
+
+ // Publish this action.
+ KoMacro::Manager::self()->publishAction( KSharedPtr<Action>(this) );
+}
+
+Action::~Action()
+{
+ //kdDebug() << QString("Action::~Action() name=\"%1\"").arg(name()) << endl;
+
+ // destroy the private d-pointer instance.
+ delete d;
+}
+
+const QString Action::toString() const
+{
+ return QString("Action:%1").arg(name());
+}
+
+const QString Action::name() const
+{
+ return d->name;
+}
+
+void Action::setName(const QString& name)
+{
+ d->name = name;
+}
+
+const QString Action::text() const
+{
+ return d->text;
+}
+
+void Action::setText(const QString& text)
+{
+ d->text = text;
+}
+
+const QString Action::comment() const
+{
+ return d->comment;
+}
+
+void Action::setComment(const QString& comment)
+{
+ d->comment = comment;
+}
+
+bool Action::hasVariable(const QString& name) const
+{
+ return d->varmap.contains(name);
+}
+
+KSharedPtr<Variable> Action::variable(const QString& name) const
+{
+ return d->varmap.contains(name) ? d->varmap[name] : KSharedPtr<Variable>(0);
+}
+
+Variable::Map Action::variables() const
+{
+ return d->varmap;
+}
+
+QStringList Action::variableNames() const
+{
+ return d->varnames;
+}
+
+void Action::setVariable(KSharedPtr<Variable> variable)
+{
+ const QString name = variable->name();
+ if(! d->varmap.contains(name)) {
+ d->varnames.append(name);
+ }
+ d->varmap.replace(name, variable);
+}
+
+void Action::setVariable(const QString& name, const QString& text, const QVariant& variant)
+{
+ Variable* variable = new Variable(variant);
+ variable->setName(name);
+ variable->setText(text);
+ setVariable( KSharedPtr<Variable>(variable) );
+}
+
+void Action::removeVariable(const QString& name)
+{
+ if(d->varmap.contains(name)) {
+ d->varmap.remove(name);
+ d->varnames.remove(name);
+ }
+}
+
+#include "action.moc"
diff --git a/kexi/plugins/macros/lib/action.h b/kexi/plugins/macros/lib/action.h
new file mode 100644
index 000000000..5200c1a49
--- /dev/null
+++ b/kexi/plugins/macros/lib/action.h
@@ -0,0 +1,187 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_ACTION_H
+#define KOMACRO_ACTION_H
+
+#include "manager.h"
+#include "context.h"
+#include "variable.h"
+
+#include <qobject.h>
+#include <ksharedptr.h>
+#include <qstringlist.h>
+
+namespace KoMacro {
+
+ /**
+ * The Action class extendes KAction to implement some additional
+ * functionality KAction doesn't provide.
+ */
+ class KOMACRO_EXPORT Action
+ : public QObject // Qt functionality like signals and slots
+ , public KShared // shared reference-counting
+ {
+ Q_OBJECT
+
+ /// Property to get/set the name.
+ Q_PROPERTY(QString name READ name WRITE setName)
+
+ /// Property to get/set the text.
+ Q_PROPERTY(QString text READ text WRITE setText)
+
+ /// Property to get/set the comment.
+ Q_PROPERTY(QString comment READ comment WRITE setComment)
+
+ public:
+
+ /**
+ * Shared pointer to implement reference-counting.
+ */
+ typedef QMap<QString, KSharedPtr<Action> > Map;
+
+ /**
+ * Constructor.
+ *
+ * @param name The unique name this @a Action has.
+ * @param text The i18n-caption text this @a Action has.
+ */
+ explicit Action(const QString& name, const QString& text = QString::null);
+
+ /**
+ * Destructor.
+ */
+ virtual ~Action();
+
+ /**
+ * @return a string representation of the functionality
+ * this action provides.
+ */
+ virtual const QString toString() const;
+
+ /**
+ * The name this @a Action has.
+ */
+ const QString name() const;
+
+ /**
+ * Set the name of the @a Action to @p name .
+ */
+ void setName(const QString& name);
+
+ /**
+ * @return the i18n-caption text this @a Action has.
+ */
+ const QString text() const;
+
+ /**
+ * Set the i18n-caption text this @a Action has.
+ */
+ void setText(const QString& text);
+
+ /**
+ * @return the comment associated with this action.
+ */
+ const QString comment() const;
+
+ /**
+ * Set the @p comment associated with this action.
+ */
+ void setComment(const QString& comment);
+
+ /**
+ * @return true if there exists a variable with the
+ * name @p name else false is returned.
+ */
+ bool hasVariable(const QString& name) const;
+
+ /**
+ * @return the variable @a Variable defined for the
+ * name @p name . If there exists no @a Variable with
+ * such a name, NULL is returned.
+ */
+ KSharedPtr<Variable> variable(const QString& name) const;
+
+ /**
+ * @return the map of variables this @a Action provides.
+ */
+ Variable::Map variables() const;
+
+ /**
+ * @return a list of variablenames this @a Action provides.s
+ */
+ QStringList variableNames() const;
+
+ /**
+ * Append the @a Variable @p variable to list of variables
+ * this @a Action provides.
+ */
+ void setVariable(KSharedPtr<Variable> variable);
+
+ /**
+ * Set the variable.
+ *
+ * @param name The name the variable should have.
+ * @param text The i18n-caption used for display.
+ * @param variant The QVariant value.
+ */
+ void setVariable(const QString& name, const QString& text, const QVariant& variant);
+
+ /**
+ * Remove the variable defined with @p name . If there exists
+ * no such variable, nothing is done.
+ */
+ void removeVariable(const QString& name);
+
+ /**
+ * This function is called, when the @a KoMacro::Variable
+ * with name @p name used within the @a KoMacro::MacroItem
+ * @p macroitem got changed.
+ *
+ * @param macroitem The @a KoMacro::MacroItem instance where
+ * the variable defined with @p name is located in.
+ * @param name The name the @a KoMacro::Variable has.
+ * @return true if the update was successfully else false
+ * is returned.
+ */
+ virtual bool notifyUpdated(const KSharedPtr<MacroItem> &macroitem, const QString& name) {
+ Q_UNUSED(macroitem);
+ Q_UNUSED(name);
+ return true; // The default implementation does nothing.
+ }
+
+ public slots:
+
+ /**
+ * Called if the @a Action should be executed within the
+ * defined @p context .
+ */
+ virtual void activate(KSharedPtr<Context> context) = 0;
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/context.cpp b/kexi/plugins/macros/lib/context.cpp
new file mode 100644
index 000000000..135c10c9a
--- /dev/null
+++ b/kexi/plugins/macros/lib/context.cpp
@@ -0,0 +1,261 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "context.h"
+#include "action.h"
+#include "macro.h"
+#include "macroitem.h"
+#include "exception.h"
+
+//#include <qtimer.h>
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class Context::Private
+ {
+ public:
+
+ /**
+ * The @a Macro instance that owns this @a Context .
+ */
+ KSharedPtr<Macro> macro;
+
+ /**
+ * List of @a Action instances that are children of the
+ * macro.
+ */
+ QValueList<KSharedPtr<MacroItem > > items;
+
+ /**
+ * The currently selected @a MacroItem or NULL if there
+ * is now @a MacroItem selected yet.
+ */
+ KSharedPtr<MacroItem> macroitem;
+
+ /**
+ * Map of all @a Variable instance that are defined within
+ * this context.
+ */
+ QMap<QString, KSharedPtr<Variable > > variables;
+
+ /**
+ * The @a Exception instance thrown at the last @a activate()
+ * call or NULL if there was no exception thrown yet.
+ */
+ Exception* exception;
+
+ /// Constructor.
+ explicit Private(KSharedPtr<Macro> m)
+ : macro(m) // remember the macro
+ , items(m->items()) // set d-pointer children to macro children
+ , exception(0) // no exception yet.
+ {
+ }
+
+ /// Destructor.
+ ~Private()
+ {
+ delete exception;
+ }
+ };
+
+}
+//Constructor with initialization of our Private.object (d-pointer)
+Context::Context(KSharedPtr<Macro> macro)
+ : QObject()
+ , d( new Private(macro) ) // create the private d-pointer instance.
+{
+}
+
+//Destructor.
+Context::~Context()
+{
+ delete d;
+}
+
+//return if we have (d-pointer) variables
+bool Context::hasVariable(const QString& name) const
+{
+ //Use QMap?s contains to check if a variable with name exists
+ return d->variables.contains(name);
+}
+
+//return variable with name or throw an exception if none is found in variables
+KSharedPtr<Variable> Context::variable(const QString& name) const
+{
+ //Use QMap?s contains to check if a variable with name exists in context
+ if (d->variables.contains(name)) {
+ //return it
+ return d->variables[name];
+ }
+ //if there is a macroitem try to get variable from it
+ if(d->macroitem.data()) {
+ KSharedPtr<Variable> v = d->macroitem->variable(name, true);
+ if(v.data()) {
+ return v;
+ }
+ }
+ //none found throw exception
+ throw Exception(QString("Variable name='%1' does not exist.").arg(name));
+}
+
+//return a map of our (d-pointer) variables
+Variable::Map Context::variables() const
+{
+ return d->variables;
+}
+
+//set a variable
+void Context::setVariable(const QString& name, KSharedPtr<Variable> variable)
+{
+ //debuging infos
+ kdDebug() << QString("KoMacro::Context::setVariable name='%1' variable='%2'").arg(name).arg(variable->toString()) << endl;
+ //Use QMap?s replace to set/replace the variable named name
+ d->variables.replace(name, variable);
+}
+
+//return the associated Macro
+KSharedPtr<Macro> Context::macro() const
+{
+ return d->macro;
+}
+
+//return the currently selected MacroItem
+KSharedPtr<MacroItem> Context::macroItem() const
+{
+ return d->macroitem;
+}
+
+//return if this context had an exception
+bool Context::hadException() const
+{
+ return d->exception != 0;
+}
+
+//return the (d-pointer) exception
+Exception* Context::exception() const
+{
+ return d->exception;
+}
+
+//try to activate all action?s in this context
+void Context::activate(QValueList<KSharedPtr<MacroItem > >::ConstIterator it)
+{
+ //debuging infos
+ kdDebug() << "Context::activate()" << endl;
+ //Q_ASSIGN(d->macro);
+
+ //set end to constEnd
+ QValueList<KSharedPtr<MacroItem > >::ConstIterator end(d->items.constEnd());
+ //loop through actions
+ for(;it != end; ++it) {
+ // fetch the MacroItem we are currently pointing to.
+ d->macroitem = KSharedPtr<MacroItem>(*it);
+ //skip empty macroitems
+ if(! d->macroitem.data()) {
+ kdDebug() << "Context::activate() Skipping empty MacroItem" << endl;
+ continue;
+ }
+
+ // fetch the Action, the MacroItem points to.
+ KSharedPtr<Action> action = d->macroitem->action();
+ //skip macroitems without an action
+ if(! action.data()) {
+ kdDebug() << "Context::activate() Skipping MacroItem with no action" << endl;
+ continue;
+ }
+
+ try {
+ // activate the action
+ action->activate(this);
+ }
+ //catch exceptions
+ catch(Exception& e) {
+ //create a new exception from caugth one and set internal exception
+ d->exception = new Exception(e);
+ //add new tracemessages
+ //the macro name
+ d->exception->addTraceMessage( QString("macro=%1").arg(d->macro->name()) );
+ //the action name
+ d->exception->addTraceMessage( QString("action=%1").arg(action->name()) );
+ //and all variables wich belong to the action/macro
+ QStringList variables = action->variableNames();
+ for(QStringList::Iterator vit = variables.begin(); vit != variables.end(); ++vit) {
+ KSharedPtr<Variable> v = d->macroitem->variable(*vit, true);
+ d->exception->addTraceMessage( QString("%1=%2").arg(*vit).arg(v->toString()) );
+ }
+ return; // abort execution
+ }
+ }
+
+ // The run is done. So, let's remove the currently selected item to
+ // outline, that we did the job and there stays no dangling item.
+ d->macroitem = KSharedPtr<MacroItem>(0);
+}
+
+//try to activated an context
+void Context::activate(KSharedPtr<Context> context)
+{
+ //setup context
+ delete d->exception; d->exception = 0;
+
+ if(context->hadException()) {
+ // if the context in which this context should run in already had an exception,
+ // we adopt this exception and abort the execution.
+ d->exception = new Exception( *context->exception() );
+ return;
+ }
+
+ // Merge the passed context into this context
+ Variable::Map variables = context->variables();
+ //copy variables
+ Variable::Map::ConstIterator it, end( variables.constEnd() );
+ for( it = variables.constBegin(); it != end; ++it)
+ setVariable(it.key(), it.data());
+
+ //activate copied context.
+ activate(d->items.constBegin());
+}
+
+//try to continue activation of a context
+void Context::activateNext()
+{
+ //setup/clear context,
+ //allows us to continue activation even when an exception happend before
+ delete d->exception; d->exception = 0;
+
+ if(! d->macroitem) { // if no MacroItem is defined, we don't need to try to continue execution
+ return;
+ }
+
+ //find the macroitem from which to continue
+ QValueList<KSharedPtr<MacroItem > >::ConstIterator it = d->items.find(d->macroitem);
+ if (it != d->items.constEnd()) {
+ activate(++it); // try to continue the execution.
+ }
+}
+
+#include "context.moc"
diff --git a/kexi/plugins/macros/lib/context.h b/kexi/plugins/macros/lib/context.h
new file mode 100644
index 000000000..dd467dad4
--- /dev/null
+++ b/kexi/plugins/macros/lib/context.h
@@ -0,0 +1,141 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_CONTEXT_H
+#define KOMACRO_CONTEXT_H
+
+#include <qobject.h>
+#include <ksharedptr.h>
+
+#include "variable.h"
+
+namespace KoMacro {
+
+ // Forward declaration.
+ class Macro;
+ class MacroItem;
+ class Action;
+ class Exception;
+
+ /**
+ * The context of an execution. If a @a Macro got executed it creates
+ * an instance of this class and passes it around all it's children
+ * as local execution context.
+ */
+ class KOMACRO_EXPORT Context
+ : public QObject
+ , public KShared
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ *
+ * @param macro The @a Macro this @a Context belongs to.
+ */
+ explicit Context(KSharedPtr<Macro> macro);
+
+ /**
+ * Destructor.
+ */
+ ~Context();
+
+ /**
+ * @return true if there exists a variable with name @p name
+ * else false got returned.
+ */
+ bool hasVariable(const QString& name) const;
+
+ /**
+ * @return the @a Variable defined with name @p name or
+ * NULL if there exists no such variable.
+ */
+ KSharedPtr<Variable> variable(const QString& name) const;
+
+ /**
+ * @return a map of all @a Variable instance that are defined
+ * within this context.
+ */
+ Variable::Map variables() const;
+
+ /**
+ * Set the variable @p variable defined with name @p name . If
+ * there exists already a variable with that name replace it.
+ */
+ void setVariable(const QString& name, KSharedPtr<Variable> variable);
+
+ /**
+ * @return the associated macro
+ */
+ KSharedPtr<Macro> macro() const;
+
+ /**
+ * @return the currently selected @a MacroItem instance
+ * or NULL if there is no @a MacroItem selected yet.
+ */
+ KSharedPtr<MacroItem> macroItem() const;
+
+ /**
+ * @return true if the last @a activate() stopped with an
+ * exception else false is returned.
+ */
+ bool hadException() const;
+
+ /**
+ * @return the @a Exception instance that was thrown on
+ * the last call of @a activate() . If there was no
+ * exception NULL is returned.
+ */
+ Exception* exception() const;
+
+ private slots:
+
+ /**
+ * A @a Context does take care of an execution-chain which
+ * should be activated one after another. The @a Context
+ * remembers what @a Action should be executed next and
+ * calling this slot just activates those @a Action .
+ */
+ virtual void activate(QValueList<KSharedPtr <MacroItem> >::ConstIterator it);
+
+ public slots:
+
+ /**
+ * This slot extends the slot above with the passed
+ * @a Context @p context which will be used as
+ * parent context for this context.
+ */
+ virtual void activate(KSharedPtr<Context> context);
+
+ /**
+ * This slot continues execution.
+ */
+ virtual void activateNext();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/exception.cpp b/kexi/plugins/macros/lib/exception.cpp
new file mode 100644
index 000000000..7cfc7d717
--- /dev/null
+++ b/kexi/plugins/macros/lib/exception.cpp
@@ -0,0 +1,97 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "exception.h"
+
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class Exception::Private
+ {
+ public:
+
+ /// A describing errormessage.
+ const QString errormessage;
+
+ /// A more detailed list of tracemessages.
+ QString tracemessages;
+
+ /**
+ * Constructor.
+ */
+ Private(const QString& errormessage)
+ : errormessage(errormessage)
+ {
+ }
+
+ };
+
+}
+
+//constructor
+Exception::Exception(const QString& errormessage)
+ : d( new Private(errormessage) ) // create the private d-pointer instance.
+{
+ //debuging infos
+ kdDebug() << QString("Exception errormessage=\"%1\"").arg(errormessage) << endl;
+}
+
+//copy constructor
+Exception::Exception (const Exception& e)
+ : d( new Private( e.errorMessage() ) )
+{
+ d->tracemessages = e.traceMessages();
+}
+
+//deconstructor
+Exception::~Exception()
+{
+ delete d;
+}
+
+//get d-pointer errormessage
+const QString Exception::errorMessage() const
+{
+ return d->errormessage;
+}
+
+//get d-pointer tracemessages
+const QString Exception::traceMessages() const
+{
+ return d->tracemessages;
+}
+
+//add a Qstring to d-pointer tracemessages
+void Exception::addTraceMessage(const QString& tracemessage)
+{
+ //no tracemessages till now
+ if(d->tracemessages.isEmpty())
+ d->tracemessages = tracemessage;
+ //append to existing ones
+ else
+ d->tracemessages += "\n" + tracemessage;
+}
+
diff --git a/kexi/plugins/macros/lib/exception.h b/kexi/plugins/macros/lib/exception.h
new file mode 100644
index 000000000..73504de04
--- /dev/null
+++ b/kexi/plugins/macros/lib/exception.h
@@ -0,0 +1,84 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_EXCEPTION_H
+#define KOMACRO_EXCEPTION_H
+
+#include <qstring.h>
+#include <qstringlist.h>
+
+#include "komacro_export.h"
+
+namespace KoMacro {
+
+ /**
+ * Base Exception class. All exceptions we like to use within KoMacro
+ * need to inheritate from this exception.
+ */
+ class KOMACRO_EXPORT Exception
+ {
+ public:
+
+ /**
+ * Constructor.
+ *
+ * @param errormessage A describing errormessage why the
+ * exception got thrown.
+ */
+ explicit Exception(const QString& errormessage);
+
+ /**
+ * Copy-constructor.
+ */
+ Exception(const Exception&);
+
+ /**
+ * Destructor.
+ */
+ virtual ~Exception();
+
+ /**
+ * @return a describing errormessage.
+ */
+ const QString errorMessage() const;
+
+ /**
+ * @return a stringlist of traces. This are normaly just
+ * simple strings to show the way the exception was gone
+ * from bottom-up where the error was thrown till where
+ * we finally catched the error to display it to the
+ * user.
+ */
+ const QString traceMessages() const;
+
+ /**
+ * Add the message @p tracemessage to the list of traces.
+ */
+ void addTraceMessage(const QString& tracemessage);
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/komacro_export.h b/kexi/plugins/macros/lib/komacro_export.h
new file mode 100644
index 000000000..cc4b41a80
--- /dev/null
+++ b/kexi/plugins/macros/lib/komacro_export.h
@@ -0,0 +1,39 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_EXPORT_H_
+#define KOMACRO_EXPORT_H_
+
+#ifdef __cplusplus
+# include <kdeversion.h> /* this will also include <kdelibs_export.h>, if available */
+#endif
+
+/* KDE_EXPORT will be defined multiple times without this on kdelibs 3.3 (tested on 3.3.1) */
+#include <kdemacros.h>
+
+/* workaround for KDElibs < 3.2 on !win32 */
+#ifndef KDE_EXPORT
+# define KDE_EXPORT
+#endif
+
+#ifndef KOMACRO_EXPORT
+# define KOMACRO_EXPORT KDE_EXPORT
+#endif
+
+#endif
diff --git a/kexi/plugins/macros/lib/macro.cpp b/kexi/plugins/macros/lib/macro.cpp
new file mode 100644
index 000000000..688cc7b07
--- /dev/null
+++ b/kexi/plugins/macros/lib/macro.cpp
@@ -0,0 +1,126 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "macro.h"
+#include "macroitem.h"
+#include "manager.h"
+#include "context.h"
+#include "variable.h"
+
+#include <qdom.h>
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class Macro::Private
+ {
+ public:
+
+ /**
+ * A list of @a MacroItem instances.
+ */
+ QValueList<KSharedPtr<MacroItem > > itemlist;
+
+ /**
+ * The name the @a Macro has.
+ */
+ QString name;
+
+ };
+
+}
+
+//constructor, initalize internal (d-pointer) name
+Macro::Macro(const QString& name)
+ : QObject()
+ , KShared()
+ , XMLHandler(this)
+ , d( new Private() ) // create the private d-pointer instance.
+{
+ d->name = name;
+}
+
+//destructor
+Macro::~Macro()
+{
+ // destroy the private d-pointer instance.
+ delete d;
+}
+
+//get internal (d-pointer) name
+const QString Macro::name() const
+{
+ return d->name;
+}
+
+//set internal (d-pointer) name
+void Macro::setName(const QString& name)
+{
+ d->name = name;
+}
+
+//get an "extended" name
+const QString Macro::toString() const
+{
+ return QString("Macro:%1").arg(name());
+}
+
+//get (d-pointer) itemlist
+QValueList<KSharedPtr<MacroItem > >& Macro::items() const
+{
+ return d->itemlist;
+}
+
+//add a macroitem to internal (d-pointer) itemlist
+void Macro::addItem(KSharedPtr<MacroItem> item)
+{
+ d->itemlist.append(item);
+}
+//clear internal (d-pointer) itemlist
+void Macro::clearItems()
+{
+ d->itemlist.clear();
+}
+
+//run our macro
+KSharedPtr<Context> Macro::execute(QObject* sender)
+{
+ kdDebug() << "Macro::execute(KSharedPtr<Context>)" << endl;
+
+ //create context in which macro can/should run
+ KSharedPtr<Context> c = KSharedPtr<Context>( new Context(this) );
+ if(sender) {
+ // set the sender-variable if we got a sender QObject.
+ c->setVariable("[sender]", KSharedPtr<Variable>( new Variable(sender) ));
+ }
+ //connect(context, SIGNAL(activated()), this, SIGNAL(activated()));
+
+ //call activate in the context of the macro
+ c->activate( c );
+
+ return c;
+}
+
+#include "macro.moc"
diff --git a/kexi/plugins/macros/lib/macro.h b/kexi/plugins/macros/lib/macro.h
new file mode 100644
index 000000000..da38e05bc
--- /dev/null
+++ b/kexi/plugins/macros/lib/macro.h
@@ -0,0 +1,130 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_MACRO_H
+#define KOMACRO_MACRO_H
+
+#include <qobject.h>
+#include <ksharedptr.h>
+
+#include "action.h"
+#include "xmlhandler.h"
+
+namespace KoMacro {
+
+ // Forward declarations.
+ class Manager;
+ class MacroItem;
+ class Context;
+
+ /**
+ * The Macro class implements all the action-handling. Internaly the
+ * Macro provides a collection of @a MacroItem instances which each
+ * of them points to an @a Action instance.
+ */
+ class KOMACRO_EXPORT Macro
+ : public QObject // Qt functionality like signals and slots
+ , public KShared // shared reference-counting
+ , public XMLHandler // to (un-)serialize from/to XML
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * A QMap of @a Macro instances accessible by there unique name. Each
+ * class should use this typemap rather then the QMap direct. That
+ * way we are more flexible on future changes.
+ */
+ typedef QMap<QString, KSharedPtr<Macro > > Map;
+
+ /**
+ * Constructor.
+ *
+ * @param name The internal name this @a Macro has. This
+ * name will be used as unique identifier.
+ */
+ explicit Macro(const QString& name);
+
+ /**
+ * Destructor.
+ */
+ virtual ~Macro();
+
+ /**
+ * @return the name this @a Macro instance has.
+ */
+ const QString name() const;
+
+ /**
+ * Set the @p name this @a Macro instance has.
+ */
+ void setName(const QString& name);
+
+ /**
+ * @return a string-representation of the macro.
+ */
+ virtual const QString toString() const;
+
+ /**
+ * @return a list of @a MacroItem instances which
+ * are children of this @a Macro .
+ */
+ QValueList< KSharedPtr<MacroItem> >& items() const;
+
+ /**
+ * Add the @a MacroItem @p item to the list of items
+ * this @a Macro has.
+ */
+ void addItem(KSharedPtr<MacroItem> item);
+
+ /**
+ * Removes all @a MacroItem instances this @a Macro has.
+ */
+ void clearItems();
+
+ /**
+ * Connect the Qt signal @p signal of the QObject @p sender
+ * with this @a Macro . If the signal got emitted this
+ * @a Macro instance will be activated and the in the
+ * signal passed arguments are transfered into the
+ * activation @a Context .
+ */
+ //void connectSignal(const QObject* sender, const char* signal);
+
+ public slots:
+
+ /**
+ * Called if the @a Macro should be executed.
+ *
+ * @param context The @a Context this @a Macro should
+ * be executed in.
+ */
+ virtual KSharedPtr<Context> execute(QObject* sender);
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/macroitem.cpp b/kexi/plugins/macros/lib/macroitem.cpp
new file mode 100644
index 000000000..4027f2ccc
--- /dev/null
+++ b/kexi/plugins/macros/lib/macroitem.cpp
@@ -0,0 +1,217 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "macroitem.h"
+
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class MacroItem::Private
+ {
+ public:
+ /**
+ * The @a Action this @a MacroItem has.
+ */
+ KSharedPtr<Action> action;
+
+ /**
+ * The comment this @a MacroItem has.
+ */
+ QString comment;
+
+ /**
+ * The @a QMap of @a Variable this @a MacroItem has.
+ */
+ Variable::Map variables;
+
+ /**
+ * define a @a QVariant -cast as inline for better performance
+ * @return the casted @a QVariant by passing a @param variant and its
+ * expected QVariant::Type @param type.
+ */
+ inline const QVariant cast(const QVariant& variant, QVariant::Type type) const
+ {
+ // If ok is true the QVariant v holds our new and to the correct type
+ // casted variant value. If ok is false the as argument passed variant
+ // QVariant contains the (maybe uncasted string to prevent data-loosing
+ // what would happen if we e.g. would expect an integer and cast it to
+ // an incompatible non-int string) value.
+ bool ok = false;
+ QVariant v;
+
+ // Try to cast the passed variant to the expected variant-type.
+ switch(type) {
+ case QVariant::Bool: {
+ const QString s = variant.toString();
+ ok = (s == "true" || s == "false" || s == "0" || s == "1" || s == "-1");
+ v = QVariant( variant.toBool(), 0 );
+ } break;
+ case QVariant::Int: {
+ v = variant.toInt(&ok);
+ // Check if the cast is correct.
+ Q_ASSERT(!ok || v.toString() == variant.toString());
+ } break;
+ case QVariant::UInt: {
+ v = variant.toUInt(&ok);
+ Q_ASSERT(!ok || v.toString() == variant.toString());
+ } break;
+ case QVariant::LongLong: {
+ v = variant.toLongLong(&ok);
+ Q_ASSERT(!ok || v.toString() == variant.toString());
+ } break;
+ case QVariant::ULongLong: {
+ v = variant.toULongLong(&ok);
+ Q_ASSERT(!ok || v.toString() == variant.toString());
+ } break;
+ case QVariant::Double: {
+ v = variant.toDouble(&ok);
+ Q_ASSERT(!ok || v.toString() == variant.toString());
+ } break;
+ case QVariant::String: {
+ ok = true; // cast will always be successfully
+ v = variant.toString();
+ } break;
+ default: {
+ // If we got another type we try to let Qt handle it...
+ ok = v.cast(type);
+ kdWarning()<<"MacroItem::Private::cast() Unhandled ok="<<ok<<" type="<<type<<" value="<<v<<endl;
+ } break;
+ }
+
+ return ok ? v : variant;
+ }
+
+ };
+
+}
+
+MacroItem::MacroItem()
+ : KShared()
+ , d( new Private() )
+{
+}
+
+MacroItem::~MacroItem()
+{
+ delete d;
+}
+
+QString MacroItem::comment() const
+{
+ return d->comment;
+}
+
+void MacroItem::setComment(const QString& comment)
+{
+ d->comment = comment;
+}
+
+KSharedPtr<Action> MacroItem::action() const
+{
+ return d->action;
+}
+
+void MacroItem::setAction(KSharedPtr<Action> action)
+{
+ d->action = action;
+}
+
+QVariant MacroItem::variant(const QString& name, bool checkaction) const
+{
+ KSharedPtr<Variable> v = variable(name, checkaction);
+ return v.data() ? v->variant() : QVariant();
+}
+
+KSharedPtr<Variable> MacroItem::variable(const QString& name, bool checkaction) const
+{
+ if(d->variables.contains(name))
+ return d->variables[name];
+ if(checkaction && d->action.data())
+ return d->action->variable(name);
+ return KSharedPtr<Variable>(0);
+}
+
+Variable::Map MacroItem::variables() const
+{
+ return d->variables;
+}
+
+bool MacroItem::setVariant(const QString& name, const QVariant& variant)
+{
+ // Let's look if there is an action defined for the variable. If that's
+ // the case, we try to use that action to preserve the type of the variant.
+ KSharedPtr<Variable> actionvariable = d->action ? d->action->variable(name) : KSharedPtr<Variable>(0);
+
+ // If we know the expected type, we try to cast the variant to the expected
+ // type else the variant stays untouched (so, it will stay a string).
+ const QVariant v = actionvariable.data()
+ ? d->cast(variant, actionvariable->variant().type()) // try to cast the variant
+ : variant; // don't cast anything, just leave the string-type...
+
+ // Now let's try to determinate the variable which should be changed.
+ KSharedPtr<Variable> variable = d->variables[name];
+ if(! variable.data()) {
+ // if there exists no such variable yet, create one.
+ kdDebug() << "MacroItem::setVariable() Creating new variable name=" << name << endl;
+
+ variable = KSharedPtr<Variable>( new Variable() );
+ variable->setName(name);
+ d->variables.replace(name, variable);
+ }
+
+ // Remember the previous value for the case we like to restore it.
+ const QVariant oldvar = variable->variant();
+
+ // Set the variable.
+ variable->setVariant(v);
+
+ // Now we inform the referenced action that a variable changed. If
+ // notifyUpdated() returns false, the action rejects the new variable
+ // and we need to restore the previous value.
+ if(d->action && ! d->action->notifyUpdated(this, name)) {
+ kdWarning() << "MacroItem::setVariable() Notify failed for variable name=" << name << endl;
+ variable->setVariant(oldvar);
+ return false; // the action rejected the changed variable whyever...
+ }
+
+ // Job done successfully. The variable is changed to the new value.
+ return true;
+}
+
+KSharedPtr<Variable> MacroItem::addVariable(const QString& name, const QVariant& variant)
+{
+ Q_ASSERT(! d->variables.contains(name) );
+ // Create a new Variable.
+ KSharedPtr<Variable> variable = KSharedPtr<Variable>( new Variable() );
+ variable->setName(name);
+
+ // Put it into the Variable-map.
+ d->variables.replace(name, variable);
+
+ // Set the variant of the Variable.
+ this->setVariant(name, variant);
+ return variable;
+}
diff --git a/kexi/plugins/macros/lib/macroitem.h b/kexi/plugins/macros/lib/macroitem.h
new file mode 100644
index 000000000..8f3e15022
--- /dev/null
+++ b/kexi/plugins/macros/lib/macroitem.h
@@ -0,0 +1,142 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_MACROITEM_H
+#define KOMACRO_MACROITEM_H
+
+#include <qobject.h>
+
+#include <ksharedptr.h>
+
+// Forward declarations.
+class QDomElement;
+
+#include "action.h"
+#include "context.h"
+
+namespace KoMacro {
+
+ // Forward-declarations.
+ //class Action;
+
+ /**
+ * The MacroItem class is an item in a @a Macro and represents one
+ * single execution step. Each MacroItem points to 0..1 @a Action
+ * instances which implement the execution. So, the MacroItem provides
+ * a simple state-pattern on the one hand (depending on the for this
+ * MacroItem choosen @a Action implementation) and holds the by the
+ * user defined modifications like e.g. the comment on the other hand.
+ */
+ class KOMACRO_EXPORT MacroItem : public KShared
+ {
+
+ public:
+
+ /**
+ * A list of \a MacroItem instances.
+ */
+ typedef QValueList<KSharedPtr<MacroItem > > List;
+
+ /**
+ * Constructor.
+ */
+ explicit MacroItem();
+
+ /**
+ * Destructor.
+ */
+ ~MacroItem();
+
+ /**
+ * @return the comment defined by the user for
+ * this @a MacroItem .
+ */
+ QString comment() const;
+
+ /**
+ * Set the comment @param comment defined by the user for this
+ * @a MacroItem .
+ */
+ void setComment(const QString& comment);
+
+ /**
+ * @return the @a Action this @a MacroItem points
+ * to. This method will return NULL if there is
+ * no @a Action defined yet else the returned
+ * @a Action will be used to implement the execution.
+ */
+ KSharedPtr<Action> action() const;
+
+ /**
+ * Set the @a Action @param action this @a MacroItem points to.
+ */
+ void setAction(KSharedPtr<Action> action);
+
+ /**
+ * @return @a Variant from the @a Variable identified with
+ * the name @param name . If this @a MacroItem doesn't
+ * have a @a Variable with that name NULL is
+ * returned.
+ * If the boolean value @param checkaction is true, we
+ * also look if our @a Action may know about
+ * such a @param name in the case this @a MacroItem
+ * doesn't have such a name.
+ */
+ QVariant variant(const QString& name, bool checkaction = false) const;
+
+ /**
+ * @return the @a Variable instance identified with
+ * the name @param name . If this @a MacroItem doesn't
+ * have a @a Variable with that name NULL is
+ * returned.
+ * If the boolean value @param checkaction is true, we
+ * also look if our @a Action may know about
+ * such a @param name in the case this @a MacroItem
+ * doesn't have such a name.
+ */
+ KSharedPtr<Variable> variable(const QString& name, bool checkaction = false) const;
+
+ /**
+ * @return a map of @a Variable instances.
+ */
+ QMap<QString, KSharedPtr<Variable> > variables() const;
+
+ /**
+ * Set the @a QVariant @param variant as variable with the variablename
+ * @param name .
+ * @return a bool for successfull setting.
+ */
+ bool setVariant(const QString& name, const QVariant& variant);
+
+ /**
+ * Add a new variable with the vaiablename @param name and the given
+ * @a QVariant @param variant to our @a MacroItem instance.
+ */
+ KSharedPtr<Variable> addVariable(const QString& name, const QVariant& variant);
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/manager.cpp b/kexi/plugins/macros/lib/manager.cpp
new file mode 100644
index 000000000..77ad98b16
--- /dev/null
+++ b/kexi/plugins/macros/lib/manager.cpp
@@ -0,0 +1,170 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "manager.h"
+#include "action.h"
+#include "function.h"
+#include "macro.h"
+#include "exception.h"
+
+#include <qobject.h>
+#include <qwidget.h>
+#include <qdom.h>
+#include <kxmlguibuilder.h>
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class Manager::Private
+ {
+ public:
+ KXMLGUIClient* const xmlguiclient;
+ QMap<QString, KSharedPtr<Macro > > macros;
+
+ QStringList actionnames;
+ QMap<QString, KSharedPtr<Action> > actions;
+
+ QMap<QString, QGuardedPtr<QObject> > objects;
+
+ Private(KXMLGUIClient* const xmlguiclient)
+ : xmlguiclient(xmlguiclient)
+ {
+ }
+ };
+
+ /// Pointer to our static singleton.
+ static ::KoMacro::Manager* _self = 0;
+
+ /// Automatically deletes our singleton on termination.
+ static KStaticDeleter< ::KoMacro::Manager > _manager;
+
+}
+
+void Manager::init(KXMLGUIClient* xmlguiclient)
+{
+ if(! _self) {
+ ::KoMacro::Manager* manager = new ::KoMacro::Manager(xmlguiclient);
+ _manager.setObject(_self, manager);
+ }
+ else {
+ throw Exception("Already initialized.");
+ }
+}
+
+Manager* Manager::self()
+{
+ //Q_ASSERT(_self);
+ return _self;
+}
+
+Manager::Manager(KXMLGUIClient* const xmlguiclient)
+ : d( new Private(xmlguiclient) ) // create the private d-pointer instance.
+{
+ kdDebug() << "Manager::Manager() Ctor" << endl;
+ QObject* obj = dynamic_cast<QObject*>(xmlguiclient);
+ if(obj) {
+ d->objects.replace(obj->name(), obj);
+ }
+
+ //TESTCASE
+ d->objects.replace("TestCase", new QWidget());
+}
+
+Manager::~Manager()
+{
+ // destroy the private d-pointer instance.
+ delete d;
+}
+
+KXMLGUIClient* Manager::guiClient() const
+{
+ return d->xmlguiclient;
+}
+
+bool Manager::hasMacro(const QString& macroname)
+{
+ return d->macros.contains(macroname);
+}
+
+KSharedPtr<Macro> Manager::getMacro(const QString& macroname)
+{
+ return d->macros[macroname];
+}
+
+void Manager::addMacro(const QString& macroname, KSharedPtr<Macro> macro)
+{
+ d->macros.replace(macroname, macro);
+}
+
+void Manager::removeMacro(const QString& macroname)
+{
+ d->macros.remove(macroname);
+}
+
+KSharedPtr<Macro> Manager::createMacro(const QString& macroname)
+{
+ KSharedPtr<Macro> macro = KSharedPtr<Macro>( new Macro(macroname) );
+ return macro;
+}
+
+KSharedPtr<Action> Manager::action(const QString& name) const
+{
+ return d->actions[name];
+}
+
+Action::Map Manager::actions() const
+{
+ return d->actions;
+}
+
+QStringList Manager::actionNames() const
+{
+ return d->actionnames;
+}
+
+void Manager::publishAction(KSharedPtr<Action> action)
+{
+ const QString name = action->name();
+ if(! d->actions.contains(name)) {
+ d->actionnames.append(name);
+ }
+ d->actions.replace(name, action);
+}
+
+void Manager::publishObject(const QString& name, QObject* object)
+{
+ Q_ASSERT(! d->objects.contains(name));
+ d->objects.replace(name, object);
+}
+
+QGuardedPtr<QObject> Manager::object(const QString& name) const
+{
+ return d->objects[name];
+}
+
+QMap<QString, QGuardedPtr<QObject> > Manager::objects() const
+{
+ return d->objects;
+}
diff --git a/kexi/plugins/macros/lib/manager.h b/kexi/plugins/macros/lib/manager.h
new file mode 100644
index 000000000..964f9d7c9
--- /dev/null
+++ b/kexi/plugins/macros/lib/manager.h
@@ -0,0 +1,219 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_MANAGER_H
+#define KOMACRO_MANAGER_H
+
+#include <qmap.h>
+#include <qguardedptr.h>
+#include <ksharedptr.h>
+#include <kxmlguiclient.h>
+#include <kstaticdeleter.h>
+
+#include "komacro_export.h"
+
+class QObject;
+class QDomElement;
+
+namespace KoMacro {
+
+ // Forward declarations.
+ class Action;
+ class Macro;
+
+ /**
+ * The Manager class acts as window-wide manager for macros.
+ *
+ * Example how KoMacro could be used.
+ * @code
+ * // We have a class that inheritates from QObject and
+ * // implements some public signals and slots that will
+ * // be accessible by Macros once a class-instance
+ * // got published.
+ * class PublishedObject : public QObject {};
+ *
+ * // Somewhere we have our KMainWindow.
+ * KMainWindow* mainwindow = new KMainWindow();
+ *
+ * // Create a new KoMacro::Manager instance to access the
+ * // Macro-framework.
+ * KoMacro::Manager* manager = new KoMacro::Manager( mainwindow );
+ *
+ * // Now we like to publish a QObject
+ * PublishedObject* publishedobject = new PublishedObject();
+ * manager->publishObject(publishedobject);
+ *
+ * // ... here we are able to use manager->createAction() to
+ * // create Action instances on the fly and work with them.
+ *
+ * // Finally free the publishedobject instance we created. We
+ * // need to free it manualy cause PublishedObject doesn't
+ * // got a QObject parent as argument.
+ * delete publishedobject;
+ *
+ * // Finally free the manager-instance. It's always needed
+ * // to free the instance by yourself!
+ * delete manager;
+ * @endcode
+ */
+ class KOMACRO_EXPORT Manager
+ {
+ friend class KStaticDeleter< ::KoMacro::Manager >;
+ private:
+
+ /**
+ * Constructor.
+ *
+ * @param xmlguiclient The KXMLGUIClient instance this
+ * @a Manager is associated with.
+ */
+ explicit Manager(KXMLGUIClient* const xmlguiclient);
+
+ /**
+ * Destructor.
+ */
+ virtual ~Manager();
+
+ public:
+
+ /**
+ * Initialize this \a Manager singleton. This function
+ * needs to be called exactly once to initialize the
+ * \a Manager singleton before \a self() got used.
+ */
+ static void init(KXMLGUIClient* xmlguiclient);
+
+ /**
+ * @return a pointer to a Manager singleton-instance. The
+ * static method \a init() needs to be called exactly once
+ * before calling this method else we may return NULL .
+ */
+ static Manager* self();
+
+ /**
+ * @return the KXMLGUIClient instance this @a Manager is
+ * associated with.
+ */
+ KXMLGUIClient* guiClient() const;
+
+ /**
+ * \return true if we carry a \a Macro with the
+ * defined \p macroname .
+ */
+ bool hasMacro(const QString& macroname);
+
+ /**
+ * \return the \a Macro defined with \p macroname
+ * or NULL if we don't have such a \a Macro.
+ */
+ KSharedPtr<Macro> getMacro(const QString& macroname);
+
+ /**
+ * Add a new \a Macro to the list of known macros. If
+ * there exists already a \a Macro instance with the
+ * defined \p macroname then the already existing one
+ * will be replace.
+ *
+ * \param macroname The name the \a Macro will be
+ * accessible as.
+ * \param macro The \a Macro instance.
+ */
+ void addMacro(const QString& macroname, KSharedPtr<Macro> macro);
+
+ /**
+ * Remove the \a Macro defined with \p macroname . If
+ * we don't know about a \a Macro with that \p macroname
+ * nothing happens.
+ */
+ void removeMacro(const QString& macroname);
+
+ /**
+ * Factory function to create a new \a Macro instances.
+ * The returned new \a Macro instance will not be added
+ * to the list of known macros. Use \a addMacro if you
+ * like to attach the returned new \a Macro to this
+ * \a Manager instance.
+ */
+ KSharedPtr<Macro> createMacro(const QString& macroname);
+
+#if 0
+ /**
+ * Factory method to create @a Action instances from the
+ * defined @p element .
+ *
+ * @param element The serialized QDomElement that should
+ * be used to create the @a Action instance.
+ * @return A new @a Action instance or NULL if the
+ * defined @p element is not valid.
+ *
+ * @deprecated Moved to common XMLReader/XMLWriter classes. Use Macro::xmlHandler() !
+ */
+ KSharedPtr<Action> createAction(const QDomElement& element);
+#endif
+
+ /**
+ * @return the @a Action which was published under the
+ * name @p name or returns an empty @a KSharedPtr<Action> object
+ * if there was no such @a Action published.
+ */
+ KSharedPtr<Action> action(const QString& name) const;
+
+ /**
+ * @return a map of all published actions.
+ */
+ QMap<QString, KSharedPtr<Action> > actions() const;
+
+ /**
+ * @return a list of all published actions.
+ */
+ QStringList actionNames() const;
+
+ /**
+ * Publish the @a Action @p action . The published @a Action
+ * will be accessible via it's unique name.
+ */
+ void publishAction(KSharedPtr<Action> action);
+
+ /**
+ * Publish the passed QObject @p object. Those object will
+ * provide it's slots as callable functions.
+ */
+ void publishObject(const QString& name, QObject* object);
+
+ /**
+ * @return the publish QObject defined with name @p name
+ * or NULL if there exists no such object.
+ */
+ QGuardedPtr<QObject> object(const QString& name) const;
+
+ /**
+ * @return a map of the published QObject instances.
+ */
+ QMap<QString, QGuardedPtr<QObject> > objects() const;
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/metamethod.cpp b/kexi/plugins/macros/lib/metamethod.cpp
new file mode 100644
index 000000000..8aa4dc54c
--- /dev/null
+++ b/kexi/plugins/macros/lib/metamethod.cpp
@@ -0,0 +1,344 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "metamethod.h"
+#include "metaobject.h"
+#include "metaparameter.h"
+#include "variable.h"
+#include "exception.h"
+
+#include <qobject.h>
+#include <qmetaobject.h>
+
+// to access the Qt3 QUObject API.
+#include <private/qucom_p.h>
+#include <private/qucomextra_p.h>
+
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class MetaMethod::Private
+ {
+ public:
+
+ /**
+ * The signature this @a MetaMethod has.
+ */
+ QString signature;
+
+ /**
+ * The signature tagname this @a MetaMethod has.
+ */
+ QString signaturetag;
+
+ /**
+ * The signature arguments this @a MetaMethod has.
+ */
+ QString signaturearguments;
+
+ /**
+ * Cached signature arguments parsed into a list
+ * of @a MetaParameter instances.
+ */
+ MetaParameter::List arguments;
+
+ /**
+ * The @a MetaObject this @a MetaMethod belongs to or is NULL
+ * if this @a MetaMethod doesn't belong to any @a MetaObject
+ * yet.
+ */
+ KSharedPtr<MetaObject> object;
+
+ /**
+ * The @a MetaMethod::Type this method provides access
+ * to.
+ */
+ MetaMethod::Type type;
+ };
+
+}
+
+MetaMethod::MetaMethod(const QString& signature, Type type, KSharedPtr<MetaObject> object)
+ : KShared()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+ d->signature = signature;
+ d->object = object;
+ d->type = type;
+
+ int startpos = d->signature.find("(");
+ int endpos = d->signature.findRev(")");
+ if(startpos < 0 || startpos > endpos) {
+ throw Exception(QString("Invalid signature \"%1\"").arg(d->signature));
+ }
+
+ d->signaturetag = d->signature.left(startpos).stripWhiteSpace();
+ if(d->signaturetag.isEmpty()) {
+ throw Exception(QString("Invalid tagname in signature \"%1\"").arg(d->signature));
+ }
+
+ d->signaturearguments = d->signature.mid(startpos + 1, endpos - startpos - 1).stripWhiteSpace();
+
+ do {
+ int commapos = d->signaturearguments.find(",");
+ int starttemplatepos = d->signaturearguments.find("<");
+ if(starttemplatepos >= 0 && (commapos < 0 || starttemplatepos < commapos)) {
+ int endtemplatepos = d->signaturearguments.find(">", starttemplatepos);
+ if(endtemplatepos <= 0) {
+ throw Exception(QString("No closing template-definiton in signature \"%1\"").arg(d->signature));
+ }
+ commapos = d->signaturearguments.find(",", endtemplatepos);
+ }
+
+ if(commapos > 0) {
+ QString s = d->signaturearguments.left(commapos).stripWhiteSpace();
+ if(! s.isEmpty()) {
+ d->arguments.append( new MetaParameter(s) );
+ }
+ d->signaturearguments = d->signaturearguments.right(d->signaturearguments.length() - commapos - 1);
+ }
+ else {
+ QString s = d->signaturearguments.stripWhiteSpace();
+ if(! s.isEmpty()) {
+ d->arguments.append( new MetaParameter(s) );
+ }
+ break;
+ }
+ } while(true);
+}
+
+MetaMethod::~MetaMethod()
+{
+ delete d;
+}
+
+KSharedPtr<MetaObject> const MetaMethod::object() const
+{
+ return d->object;
+}
+
+const QString MetaMethod::signature() const
+{
+ return d->signature;
+}
+
+const QString MetaMethod::signatureTag() const
+{
+ return d->signaturetag;
+}
+
+const QString MetaMethod::signatureArguments() const
+{
+ return d->signaturearguments;
+}
+
+MetaMethod::Type MetaMethod::type() const
+{
+ return d->type;
+}
+
+MetaParameter::List MetaMethod::arguments() const
+{
+ return d->arguments;
+}
+
+QUObject* MetaMethod::toQUObject(Variable::List arguments)
+{
+ uint argsize = d->arguments.size();
+
+ if(arguments.size() <= argsize) {
+ throw Exception(QString("To less arguments for slot with siganture \"%1\"").arg(d->signature));
+ }
+
+ // The first item in the QUObject-array is for the returnvalue
+ // while everything >=1 are the passed parameters.
+ QUObject* uo = new QUObject[ argsize + 1 ];
+
+ uo[0] = QUObject(); // empty placeholder for the returnvalue.
+
+ for(uint i = 0; i < argsize; i++) {
+ KSharedPtr<MetaParameter> metaargument = d->arguments[i];
+ KSharedPtr<Variable> variable = arguments[i + 1];
+
+ if ( !variable ) {
+ throw Exception(QString("Variable is undefined !"));
+ }
+
+ if(metaargument->type() != variable->type()) {
+ throw Exception(QString("Wrong variable type in method \"%1\". Expected \"%2\" but got \"%3\"").arg(d->signature).arg(metaargument->type()).arg(variable->type()));
+ }
+
+ switch(metaargument->type()) {
+
+ case Variable::TypeNone: {
+ kdDebug() << "Variable::TypeNone" << endl;
+ uo[i + 1] = QUObject();
+ } break;
+
+ case Variable::TypeVariant: {
+ kdDebug() << "Variable::TypeVariant" << endl;
+
+ const QVariant variant = variable->variant();
+ switch(metaargument->variantType()) {
+ case QVariant::String: {
+ const QString s = variant.toString();
+ static_QUType_QString.set( &(uo[i + 1]), s );
+ } break;
+ case QVariant::Int: {
+ const int j = variant.toInt();
+ static_QUType_int.set( &(uo[i + 1]), j );
+ } break;
+ case QVariant::Bool: {
+ const bool b = variant.toBool();
+ static_QUType_bool.set( &(uo[i + 1]), b );
+ } break;
+ case QVariant::Double: {
+ const double d = variant.toDouble();
+ static_QUType_double.set( &(uo[i + 1]), d );
+ } break;
+ case QVariant::Invalid: {
+ static_QUType_QVariant.set( &(uo[i + 1]), variant );
+ }
+
+ /*FIXME
+ static_QUType_charstar
+ static_QUType_ptr.get(uo); QObject *qobj = (QObject *)(ptr);
+ */
+
+ default: {
+ throw Exception(QString("Invalid parameter !!!!!!!!!!!!!!!!!!!!!!!"));
+ } break;
+ }
+ } break;
+
+ case Variable::TypeObject: {
+ kdDebug() << "Variable::TypeObject" << endl;
+
+ const QObject* obj = arguments[i + 1]->object();
+ if(! obj) { //FIXME: move check to MetaParameter?!
+ throw Exception(QString("No QObject !"));
+ }
+ static_QUType_ptr.set( &(uo[i + 1]), obj );
+ } break;
+
+ default: {
+ throw Exception(QString("Invalid variable type"));
+ } break;
+ }
+
+ }
+
+ return uo;
+}
+
+KSharedPtr<Variable> MetaMethod::toVariable(QUObject* uo)
+{
+ const QString desc( uo->type->desc() );
+
+ if(desc == "null") {
+ return new Variable();
+ }
+
+ if(desc == "QString") {
+ const QString s = static_QUType_QString.get(uo);
+ return new Variable(s);
+ }
+
+ if(desc == "int") {
+ const int j = static_QUType_int.get(uo);
+ return new Variable(j);
+ }
+
+ if(desc == "bool") {
+ const bool b = static_QUType_bool.get(uo);
+ return new Variable(b);
+ }
+
+ if(desc == "double") {
+ const double d = static_QUType_double.get(uo);
+ return new Variable(d);
+ }
+
+ if(desc == "QVariant") {
+ QVariant v = static_QUType_QVariant.get(uo);
+ return new Variable(v);
+ }
+
+ throw Exception(QString("Invalid parameter '%1'").arg(desc));
+}
+
+Variable::List MetaMethod::toVariableList(QUObject* uo)
+{
+ Variable::List list;
+
+ MetaParameter::List::ConstIterator it, end( d->arguments.constEnd() );
+ for( it = d->arguments.constBegin(); it != end; ++it) {
+ list.append( toVariable(uo) );
+ uo++;
+ }
+
+ return list;
+}
+
+KSharedPtr<Variable> MetaMethod::invoke(Variable::List arguments)
+{
+ kdDebug() << "KSharedPtr<Variable> MetaMethod::invoke(Variable::List arguments)" << endl;
+
+ if(! d->object) {
+ throw Exception("MetaObject is undefined.");
+ }
+
+ QObject* obj = d->object->object();
+ KSharedPtr<Variable> returnvalue;
+ QUObject* qu = 0;
+
+ try {
+ qu = toQUObject(arguments);
+
+ switch( d->type ) {
+ case Signal: {
+ int index = d->object->indexOfSignal( d->signature.latin1() );
+ obj->qt_emit(index, qu);
+ } break;
+ case Slot: {
+ int index = d->object->indexOfSlot( d->signature.latin1() );
+ obj->qt_invoke(index, qu);
+ } break;
+ default: {
+ throw Exception("Unknown type.");
+ } break;
+ }
+ returnvalue = toVariable( &qu[0] );
+ }
+ catch(Exception& e) {
+ delete [] qu; // free the QUObject array and
+ kdDebug() << "EXCEPTION in KoMacro::MetaMethod::invoke(Variable::List)" << endl;
+ throw Exception(e); // re-throw exception
+ }
+
+ delete [] qu;
+ return returnvalue;
+}
diff --git a/kexi/plugins/macros/lib/metamethod.h b/kexi/plugins/macros/lib/metamethod.h
new file mode 100644
index 000000000..df53ac60c
--- /dev/null
+++ b/kexi/plugins/macros/lib/metamethod.h
@@ -0,0 +1,150 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_METAMETHOD_H
+#define KOMACRO_METAMETHOD_H
+
+#include <qstring.h>
+#include <qvaluelist.h>
+#include <ksharedptr.h>
+
+#include "komacro_export.h"
+
+struct QUObject;
+
+namespace KoMacro {
+
+ // forward declarations.
+ class Variable;
+ class MetaObject;
+ class MetaParameter;
+ class MetaProxy;
+
+ /**
+ * Class to provide abstract methods for the undocumented
+ * Qt3 QUObject-API functionality.
+ *
+ * The design tried to limit future porting to Qt4 by providing a
+ * somewhat similar API to the Qt4 QMeta* stuff.
+ */
+ class KOMACRO_EXPORT MetaMethod : public KShared
+ {
+ public:
+
+ /**
+ * The type of method this @a MetaMethod provides
+ * access to.
+ */
+ enum Type {
+ Signal, /// The @a MetaMethod points to a Qt signal.
+ Slot, /// The @a MetaMethod points to a Qt slot.
+ Unknown /// The @a MetaMethod is not known.
+ };
+
+ /**
+ * Constructor.
+ *
+ * @param signature The signature this @a MetaMethod has. This
+ * includes the tagname and the arguments and could look like
+ * "myslot(const QString&, int)".
+ * @param type The @a MetaMethod::Type the @a MethodMethod
+ * has.
+ * @param object The @a MetaObject this @a MethodMethod
+ * belongs to. Each @a MethodMethod is associated with
+ * exactly one @a MetaObject .
+ */
+ explicit MetaMethod(const QString& signature, Type type = Unknown, KSharedPtr<MetaObject> object = 0);
+
+ /**
+ * Destructor.
+ */
+ ~MetaMethod();
+
+ /**
+ * @return the @a MetaObject instance this @a MethodMethod
+ * belongs to.
+ */
+ KSharedPtr<MetaObject> const object() const;
+
+ /**
+ * @return the signature this @a MetaMethod has. It could
+ * be something like "mySlot(const QString&,int)".
+ */
+ const QString signature() const;
+
+ /**
+ * @return the signatures tagname this @a MetaMethod has.
+ * At the signature "mySlot(const QString&,int)" the
+ * tagname would be "mySlot".
+ */
+ const QString signatureTag() const;
+
+ /**
+ * @return the signatures arguments this @a MetaMethod has.
+ * At the signature "mySlot(const QString&,int)" the
+ * arguments are "const QString&,int".
+ */
+ const QString signatureArguments() const;
+
+ /**
+ * @return the @a Type of method this @a MetaMethod provides
+ * access to.
+ */
+ Type type() const;
+
+ /**
+ * @return the signature arguments as parsed list of
+ * @a MetaParameter instances.
+ */
+ QValueList< KSharedPtr<MetaParameter> > arguments() const;
+
+ /**
+ * Translate the passed @p arguments list of @a Variable instances
+ * into a Qt3 QUObject* array.
+ */
+ QUObject* toQUObject(QValueList< KSharedPtr<Variable> > arguments);
+
+ /**
+ * Translate the passed @p uo QUObject reference into an internal used
+ * @a Variable instances.
+ */
+ KSharedPtr<Variable> toVariable(QUObject* uo);
+
+ /**
+ * Translate the passed @p uo QUObject array into an internal used
+ * list of @a Variable instances.
+ */
+ QValueList< KSharedPtr<Variable> > toVariableList(QUObject* uo);
+
+ /**
+ * Invoke the @a MetaMethod with the optional arguments
+ * @p arguments and return a variable.
+ */
+ KSharedPtr<Variable> invoke(QValueList< KSharedPtr<Variable> > arguments);
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/metaobject.cpp b/kexi/plugins/macros/lib/metaobject.cpp
new file mode 100644
index 000000000..000f4181a
--- /dev/null
+++ b/kexi/plugins/macros/lib/metaobject.cpp
@@ -0,0 +1,151 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "metaobject.h"
+#include "metamethod.h"
+#include "variable.h"
+#include "exception.h"
+
+#include <qguardedptr.h>
+#include <qmetaobject.h>
+
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class MetaObject::Private
+ {
+ public:
+
+ /**
+ * The QObject instance this @a MetaObject belongs to.
+ */
+ QGuardedPtr<QObject> const object;
+
+ /**
+ * Constructor.
+ */
+ Private(QObject* const object)
+ : object(object)
+ {
+ }
+ };
+
+}
+
+MetaObject::MetaObject(QObject* const object)
+ : KShared()
+ , d( new Private(object) ) // create the private d-pointer instance.
+{
+}
+
+MetaObject::~MetaObject()
+{
+ delete d;
+}
+
+QObject* const MetaObject::object() const
+{
+ if(! d->object) {
+ throw Exception(QString("Object is undefined."));
+ }
+ return d->object;
+}
+
+/*
+QStrList MetaObject::signalNames() const
+{
+ return object()->metaObject()->signalNames();
+}
+
+QStrList MetaObject::slotNames() const
+{
+ return object()->metaObject()->slotNames();
+}
+*/
+
+int MetaObject::indexOfSignal(const char* signal) const
+{
+ QMetaObject* metaobject = object()->metaObject();
+ int signalid = metaobject->findSignal(signal, false);
+ if(signalid < 0) {
+ throw Exception(QString("Invalid signal \"%1\"").arg(signal));
+ }
+ return signalid;
+}
+
+int MetaObject::indexOfSlot(const char* slot) const
+{
+ QMetaObject* metaobject = object()->metaObject();
+ int slotid = metaobject->findSlot(slot, false);
+ if(slotid < 0) {
+ throw Exception(QString("Invalid slot \"%1\"").arg(slot));
+ }
+ return slotid;
+}
+
+KSharedPtr<MetaMethod> MetaObject::method(int index)
+{
+ QObject* obj = object();
+ MetaMethod::Type type = MetaMethod::Slot;
+ QMetaObject* metaobject = obj->metaObject();
+
+ const QMetaData* metadata = metaobject->slot(index, true);
+ if(! metadata) {
+ // Try to get a signal with that index iff we failed to determinate
+ // a matching slot.
+
+ metadata = metaobject->signal(index, true);
+ if(! metadata) {
+ throw Exception(QString("Invalid method index \"%1\" in object \"%2\"").arg(index).arg(obj->name()));
+ }
+ type = MetaMethod::Signal;
+ }
+
+ if(metadata->access != QMetaData::Public) {
+ throw Exception(QString("Not allowed to access method \"%1\" in object \"%2\"").arg(metadata->name).arg(obj->name()));
+ }
+
+ return new MetaMethod(metadata->name, type, this);
+}
+
+KSharedPtr<MetaMethod> MetaObject::signal(const char* signal)
+{
+ return method( indexOfSignal(signal) );
+}
+
+KSharedPtr<MetaMethod> MetaObject::slot(const char* slot)
+{
+ return method( indexOfSlot(slot) );
+}
+
+KSharedPtr<Variable> MetaObject::invokeMethod(int index, Variable::List arguments)
+{
+ // kdDebug() << "MetaObject::invokeMethod(int index, Variable::List arguments)" << endl;
+ KSharedPtr<MetaMethod> m = method(index);
+ // kdDebug() << "MetaObject::invokeMethod(int index, Variable::List arguments) return" << endl;
+ return m->invoke(arguments);
+}
+
diff --git a/kexi/plugins/macros/lib/metaobject.h b/kexi/plugins/macros/lib/metaobject.h
new file mode 100644
index 000000000..8b6115742
--- /dev/null
+++ b/kexi/plugins/macros/lib/metaobject.h
@@ -0,0 +1,118 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_METAOBJECT_H
+#define KOMACRO_METAOBJECT_H
+
+#include <qobject.h>
+#include <ksharedptr.h>
+
+#include "komacro_export.h"
+
+namespace KoMacro {
+
+ // forward declarations.
+ class Variable;
+ class MetaMethod;
+
+ /**
+ * Class to provide abstract access to extended QObject functionality
+ * like the undocumented QUObject-API in Qt3.
+ *
+ * The design tried to limit future porting to Qt4 by providing a
+ * somewhat similar API to the Qt4 QMeta* stuff.
+ */
+ class KOMACRO_EXPORT MetaObject : public KShared
+ {
+ public:
+
+ /**
+ * Constructor.
+ *
+ * @param object The QObject instance this @a MetaObject provides
+ * abstract access to.
+ */
+ explicit MetaObject(QObject* const object);
+
+ /**
+ * Destructor.
+ */
+ ~MetaObject();
+
+ /**
+ * @return the QObject this @a MetaObject provides abstract
+ * access to.
+ */
+ QObject* const object() const;
+
+ //QStrList signalNames() const;
+ //QStrList slotNames() const;
+
+ /**
+ * @return the index of the signal @p signal .
+ */
+ int indexOfSignal(const char* signal) const;
+
+ /**
+ * @return the index of the slot @p slot .
+ */
+ int indexOfSlot(const char* slot) const;
+
+ /**
+ * @return the @a MetaMethod that matches to the
+ * index @p index .
+ */
+ KSharedPtr<MetaMethod> method(int index);
+
+ /**
+ * @return a @a MetaMethod for the signal @p signal .
+ */
+ KSharedPtr<MetaMethod> signal(const char* signal);
+
+ /**
+ * @return a @a MetaMethod for the slot @p slot .
+ */
+ KSharedPtr<MetaMethod> slot(const char* slot);
+
+//KSharedPtr<MetaMethod> addSlot(const char* slot);
+//void connectSignal(QObject* obj, const char* signal);
+
+ /**
+ * Invoke the @a MetaMethod that has the index @p index .
+ *
+ * @param index The index the signal or slot has. Use
+ * @a indexOfSignal() and @a indexOfSlot() to determinate
+ * those index.
+ * @param arguments The optional arguments passed to the
+ * method.
+ * @return The returnvalue the method provides and that got
+ * returned if the execution is done.
+ */
+ KSharedPtr<Variable> invokeMethod(int index, QValueList< KSharedPtr<Variable> > arguments);
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/metaparameter.cpp b/kexi/plugins/macros/lib/metaparameter.cpp
new file mode 100644
index 000000000..7f072b2bc
--- /dev/null
+++ b/kexi/plugins/macros/lib/metaparameter.cpp
@@ -0,0 +1,146 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "metaparameter.h"
+#include "exception.h"
+#include "variable.h"
+
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class MetaParameter::Private
+ {
+ public:
+
+ /**
+ * The signatures argument that represents this MetaParameter.
+ * This could be something like "const QString&", "int" or
+ * "QMap &lt; QString, QVariant &gt; ".
+ */
+ QString signatureargument;
+
+ /**
+ * The type of the @a Variable .
+ */
+ MetaParameter::Type type;
+
+ /**
+ * If the @a MetaParameter::Type is a Variant this QVariant::Type
+ * is used to defined what kind of Variant it is.
+ */
+ QVariant::Type varianttype;
+
+ };
+
+}
+
+MetaParameter::MetaParameter(const QString& signatureargument)
+ : KShared()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+ d->type = TypeNone;
+
+ if(! signatureargument.isNull()) {
+ setSignatureArgument( signatureargument );
+ }
+}
+
+MetaParameter::~MetaParameter()
+{
+ delete d;
+}
+
+MetaParameter::Type MetaParameter::type() const
+{
+ return d->type;
+}
+
+const QString MetaParameter::typeName() const
+{
+ switch( d->type ) {
+ case TypeNone:
+ return "None";
+ case TypeVariant:
+ return "Variant";
+ case TypeObject:
+ return "Object";
+ }
+ return QString::null;
+}
+
+void MetaParameter::setType(MetaParameter::Type type)
+{
+ d->type = type;
+ d->varianttype = QVariant::Invalid;
+}
+
+QVariant::Type MetaParameter::variantType() const
+{
+ return d->varianttype;
+}
+
+void MetaParameter::setVariantType(QVariant::Type varianttype)
+{
+ d->type = TypeVariant;
+ d->varianttype = varianttype;
+}
+
+void MetaParameter::setSignatureArgument(const QString& signatureargument)
+{
+ d->signatureargument = signatureargument;
+
+ QString argument = signatureargument;
+ if(argument.startsWith("const")) {
+ argument = argument.mid(5).stripWhiteSpace();
+ }
+
+ if(argument.endsWith("&")) {
+ argument = argument.left( argument.length() - 1 ).stripWhiteSpace();
+ }
+
+ if(argument.isEmpty()) {
+ throw Exception(QString("Empty signature argument passed."));
+ }
+ if(argument == "QVariant") {
+ setVariantType( QVariant::Invalid );
+ }
+
+ QVariant::Type type = argument.isNull() ? QVariant::Invalid : QVariant::nameToType(argument.latin1());
+ if (type != QVariant::Invalid) {
+ setVariantType( type );
+ }
+ else {
+ setType( TypeObject );
+ }
+}
+
+bool MetaParameter::validVariable(KSharedPtr<Variable> variable) const
+{
+ if( type() != variable->type() ) {
+ return false;
+ }
+ return true;
+}
diff --git a/kexi/plugins/macros/lib/metaparameter.h b/kexi/plugins/macros/lib/metaparameter.h
new file mode 100644
index 000000000..ab2a4004e
--- /dev/null
+++ b/kexi/plugins/macros/lib/metaparameter.h
@@ -0,0 +1,136 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_METAPARAMETER_H
+#define KOMACRO_METAPARAMETER_H
+
+#include <qstring.h>
+#include <qvariant.h>
+#include <qobject.h>
+#include <ksharedptr.h>
+
+#include "komacro_export.h"
+
+namespace KoMacro {
+
+ // Forward declarations.
+ class Variable;
+
+ /**
+ * Class to provide abstract methods for the undocumented
+ * Qt3 QUObject-API functionality.
+ *
+ * The design tried to limit future porting to Qt4 by providing a
+ * somewhat similar API to the Qt4 QMeta* stuff.
+ */
+ class KOMACRO_EXPORT MetaParameter : public KShared
+ {
+
+ /**
+ * Property to get the type of the variable.
+ */
+ Q_PROPERTY(Type type READ type)
+
+ /**
+ * Property to get the type of the variable as string.
+ */
+ Q_PROPERTY(QString typeName READ typeName)
+
+ public:
+
+ /**
+ * List of @a MetaParameter instances.
+ */
+ typedef QValueList<KSharedPtr <MetaParameter > > List;
+
+ /**
+ * Constructor.
+ *
+ * @param signatureargument The signatures argument
+ * that will be used to determinate the arguments
+ * type. This could be something like "const QString&",
+ * "int" or "QMap &lt; QString, QVariant &gt; ".
+ */
+ explicit MetaParameter(const QString& signatureargument = QString::null);
+
+ /**
+ * Destructor.
+ */
+ ~MetaParameter();
+
+ /**
+ * Possible types the @a MetaParameter could provide.
+ */
+ enum Type {
+ TypeNone = 0, /// None type, the @a MetaParameter is empty.
+ TypeVariant, /// The @a MetaParameter is a QVariant.
+ TypeObject /// The @a MetaParameter is a QObject.
+ };
+
+ /**
+ * @return the @a MetaParameter::Type this variable has.
+ */
+ Type type() const;
+
+ /**
+ * @return the @a MetaParameter::Type as string. The typename
+ * could be "None", "Variant" or "Object".
+ */
+ const QString typeName() const;
+
+ /**
+ * Set the @a MetaParameter::Type this variable is.
+ */
+ void setType(Type type);
+
+ /**
+ * @return the @a MetaParameter::Type this variable is.
+ */
+ QVariant::Type variantType() const;
+
+ /**
+ * Set the @a MetaParameter::Type this variable is.
+ */
+ void setVariantType(QVariant::Type varianttype);
+
+ /**
+ * @return true if the passed @a Variable @p variable is
+ * valid for this @a MetaParameter . Valid means, that
+ * the variable has a castable type.
+ */
+ bool validVariable(KSharedPtr<Variable> variable) const;
+
+ protected:
+
+ /**
+ * @internal used method to set the signature argument. Those
+ * argument will be used to determinate the arguments type.
+ */
+ void setSignatureArgument(const QString& signatureargument);
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/variable.cpp b/kexi/plugins/macros/lib/variable.cpp
new file mode 100644
index 000000000..598b8b46f
--- /dev/null
+++ b/kexi/plugins/macros/lib/variable.cpp
@@ -0,0 +1,246 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "variable.h"
+#include "exception.h"
+
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class Variable::Private
+ {
+ public:
+
+ /**
+ * The name this @a Variable has.
+ */
+ QString name;
+
+ /**
+ * The i18n-caption used for display purposes only
+ * this @a Variable has.
+ */
+ QString text;
+
+ /**
+ * If @a Variable::Type is @a Variable::TypeVariant this QVariant
+ * holds the value else it's invalid.
+ */
+ QVariant variant;
+
+ /**
+ * If @a Variable::Type is @a Variable::TypeObject this QObject is
+ * the value else it's NULL.
+ */
+ const QObject* object;
+
+ /**
+ * Optional list of children this @a Variable has.
+ */
+ // TODO Dow we use this or is it for the future??
+ Variable::List children;
+
+ /**
+ * Defines if the variable is enabled or disabled.
+ */
+ bool enabled;
+
+ explicit Private()
+ : enabled(true)
+ {
+ }
+ };
+
+}
+
+Variable::Variable()
+ : MetaParameter()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+ setType(TypeNone);
+ d->object = 0;
+}
+
+Variable::Variable(const QVariant& variant, const QString& name, const QString& text)
+ : MetaParameter()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+ setVariantType(variant.type());
+ d->variant = variant;
+ d->object = 0;
+ d->name = name;
+ d->text = text;
+}
+
+Variable::Variable(const QObject* object)
+ : MetaParameter()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+ setType(TypeObject);
+ d->object = object;
+}
+
+Variable::Variable(const QDomElement& element)
+ : MetaParameter()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+
+ QString typesignature = element.attribute("type", "const QString&");
+ QString value = element.text();
+
+ setSignatureArgument( typesignature );
+
+ switch( type() ) {
+ case KoMacro::MetaParameter::TypeVariant: {
+ //kdDebug() << QString("KoMacro::Variable(QDomElement) KoMacro::MetaParameter::TypeVariant") << endl;
+ // Set the variant without overwritting the previously detected varianttype.
+ setVariant( QVariant(value), false );
+ } break;
+ case KoMacro::MetaParameter::TypeObject: {
+ //kdDebug() << QString("KoMacro::Variable(QDomElement) KoMacro::MetaParameter::TypeObject") << endl;
+ //TODO setObject();
+ } break;
+ default: {
+ kdWarning() << QString("KoMacro::Variable(QDomElement) KoMacro::MetaParameter::TypeNone") << endl;
+ } break;
+ }
+}
+
+Variable::~Variable()
+{
+ delete d;
+}
+
+QString Variable::name() const
+{
+ return d->name;
+}
+
+void Variable::setName(const QString& name)
+{
+ d->name = name;
+}
+
+QString Variable::text() const
+{
+ return d->text;
+}
+
+void Variable::setText(const QString& text)
+{
+ d->text = text;
+}
+
+const QVariant Variable::variant() const
+{
+ //Q_ASSERT( type() == MetaParameter::TypeVariant );
+ //Q_ASSERT( variantType() != QVariant::Invalid );
+ //if(variantType() == QVariant::Invalid) return QVariant();
+ return d->variant;
+}
+
+void Variable::setVariant(const QVariant& variant, bool detecttype)
+{
+ if(detecttype) {
+ setVariantType( variant.type() );
+ }
+ d->variant = variant;
+}
+
+const QObject* Variable::object() const
+{
+ Q_ASSERT( ! d->object );
+ return d->object;
+}
+
+void Variable::setObject(const QObject* object)
+{
+ setType(TypeObject);
+ d->object = object;
+}
+
+Variable::operator QVariant () const
+{
+ return variant();
+}
+
+Variable::operator const QObject* () const
+{
+ return object();
+}
+
+const QString Variable::toString() const
+{
+ switch( type() ) {
+ case KoMacro::MetaParameter::TypeVariant: {
+ return variant().toString();
+ } break;
+ case KoMacro::MetaParameter::TypeObject: {
+ return QString("[%1]").arg( object()->name() );
+ } break;
+ default: {
+ throw Exception("Type is undefined.");
+ } break;
+ }
+ return QString::null;
+}
+
+int Variable::toInt() const
+{
+ return variant().toInt();
+}
+
+Variable::List Variable::children() const
+{
+ return d->children;
+}
+
+void Variable::appendChild(KSharedPtr<Variable> variable)
+{
+ d->children.append(variable);
+}
+
+void Variable::clearChildren()
+{
+ d->children.clear();
+}
+
+void Variable::setChildren(const Variable::List& children)
+{
+ d->children = children;
+}
+
+/*
+bool Variable::isEnabled() const
+{
+ return d->enabled;
+}
+
+void Variable::setEnabled(const bool enabled)
+{
+ d->enabled = enabled;
+}
+*/
diff --git a/kexi/plugins/macros/lib/variable.h b/kexi/plugins/macros/lib/variable.h
new file mode 100644
index 000000000..26e9619ee
--- /dev/null
+++ b/kexi/plugins/macros/lib/variable.h
@@ -0,0 +1,222 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_VARIABLE_H
+#define KOMACRO_VARIABLE_H
+
+#include <qobject.h>
+#include <qdom.h>
+#include <qvariant.h>
+#include <ksharedptr.h>
+
+#include "metaparameter.h"
+
+namespace KoMacro {
+
+ /**
+ * A variable value used to provide abstract access to variables. The
+ * class handles QVariant and QObject and provides access to them.
+ * Variable inherits KShared and implements reference couting. So, it's
+ * not needed to take care of memory-managment.
+ */
+ class KOMACRO_EXPORT Variable : public MetaParameter
+ {
+
+ /**
+ * Property to get and set a QVariant as variable.
+ */
+ Q_PROPERTY(QVariant variant READ variant WRITE setVariant)
+
+ /**
+ * Property to get and set a QObject as variable.
+ */
+ Q_PROPERTY(QObject* object READ object WRITE setObject)
+
+ /**
+ * Property to get a string-representation of the variable.
+ */
+ Q_PROPERTY(QString string READ toString)
+
+ public:
+
+ /**
+ * A list of variables.
+ */
+ typedef QValueList<KSharedPtr<Variable > > List;
+
+ /**
+ * A map of variables.
+ */
+ typedef QMap<QString, KSharedPtr<Variable > > Map;
+
+ /**
+ * Default constructor.
+ */
+ explicit Variable();
+
+ /**
+ * Constructor from the QVariant @p variant .
+ *
+ * @param variant The value this variable has.
+ * @param name The unique @a name() this variable has.
+ * @param text The describing @a text() this variable has.
+ */
+ Variable(const QVariant& variant, const QString& name = QString::null, const QString& text = QString::null);
+
+ /**
+ * Constructor from the QObject @p object .
+ *
+ * @param object The value this variable has.
+ */
+ Variable(const QObject* object);
+
+ /**
+ * Constructor from the QDomElement @p element .
+ * @deprecated replaced with methods of @a XMLHandler.
+ * @param element The QDomElement that may optional contains the
+ * variable content or other additional informations.
+ */
+ Variable(const QDomElement& element);
+
+ /**
+ * Destructor.
+ */
+ virtual ~Variable();
+
+ /**
+ * @return the name this @a Variable has.
+ */
+ QString name() const;
+
+ /**
+ * Set the name @param name this @a Variable has.
+ */
+ void setName(const QString& name);
+
+ /**
+ * @return the caption this @a Variable has.
+ */
+ QString text() const;
+
+ /**
+ * Set the caption @param text this @a Variable has.
+ */
+ void setText(const QString& text);
+
+ /**
+ * Set the QObject @param object this variable has. A
+ * previously remembered value will be overwritten and
+ * the new type is a @a TypeObject .
+ */
+ void setObject(const QObject* object);
+
+ /**
+ * @return the QVariant this variable has. If this
+ * variable isn't a @a TypeVariant an invalid QVariant
+ * got returned.
+ */
+ const QVariant variant() const;
+
+ /**
+ * Set the QVariant @param variant this variable has. A
+ * previously remembered value will be overwritten and
+ * the new type is a @a TypeVariant . If @param detecttype is
+ * true the method tries to set the @a variantType according
+ * to the passed QVariant. If false the variantType won't
+ * be changed.
+ */
+ void setVariant(const QVariant& variant, bool detecttype = true);
+
+ /**
+ * @return the QObject this variable has. If this
+ * variable isn't a @a TypeObject NULL got returned.
+ */
+ const QObject* object() const;
+
+ /**
+ * Implicit conversion to QVariant operator. This method
+ * calls @a variant() internaly.
+ */
+ operator QVariant () const;
+
+ /**
+ * Implicit conversion to QObject operator. This method
+ * calls @a object() internaly.
+ */
+ operator const QObject* () const;
+
+ /**
+ * @return a string-represenation of the variable.
+ */
+ const QString toString() const;
+
+ /**
+ * @return a integer-represenation of the variable.
+ */
+ int toInt() const;
+
+ /**
+ * @return the optional list of @a Variable instances
+ * that are children of this @a Variable .
+ *
+ * @note that the list is returned call-by-reference. The
+ * list is accessed as getter/setter (read/write). So,
+ * don't set this method to const!
+ */
+ List children() const;
+
+ /**
+ * Append a @a Variable to the list of children this
+ * @a Variable has.
+ */
+ void appendChild(KSharedPtr<Variable> variable);
+
+ /**
+ * Clear the list of children this @a Variable has.
+ */
+ void clearChildren();
+
+ /**
+ * Set the children this @a Variable has.
+ */
+ void setChildren(const List& children);
+
+#if 0
+ /**
+ * @return true if this @a Variable is enabled else
+ * false is returned.
+ */
+ bool isEnabled() const;
+
+ /**
+ * Set this @a Variable to be enabled if @param enabled is
+ * true else the variable is disabled.
+ */
+ void setEnabled(const bool enabled);
+#endif
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/lib/xmlhandler.cpp b/kexi/plugins/macros/lib/xmlhandler.cpp
new file mode 100644
index 000000000..b35759e1d
--- /dev/null
+++ b/kexi/plugins/macros/lib/xmlhandler.cpp
@@ -0,0 +1,226 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "xmlhandler.h"
+#include "macro.h"
+#include "macroitem.h"
+#include "action.h"
+
+#include <qdom.h>
+#include <kdebug.h>
+
+using namespace KoMacro;
+
+namespace KoMacro {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class XMLHandler::Private
+ {
+ public:
+
+ /**
+ * The @a Macro instance this @a XMLHandler
+ * manages.
+ */
+ Macro* const macro;
+
+ /**
+ * Constructor.
+ *
+ * @param macro The @a Macro instance this
+ * @a XMLHandler manages.
+ */
+ Private(Macro* const macro)
+ : macro(macro)
+ {
+ }
+ };
+
+}
+
+XMLHandler::XMLHandler(Macro* const macro)
+ : d( new Private(macro) )
+{
+}
+
+XMLHandler::~XMLHandler()
+{
+ delete d;
+}
+
+bool XMLHandler::parseXML(const QDomElement& element)
+{
+ // Remove old items. We should clear first.
+ d->macro->clearItems();
+
+ // We expect a <macro> element. Do we really need to be such strict or
+ // would it be more wise to trust the application in that case?
+ if(element.tagName() != "macro") {
+ kdDebug() << QString("XMLHandler::parseXML() Invalid tagname \"%1\"").arg(element.tagName()) << endl;
+ return false;
+ }
+
+ // To be flexible with the xml-scheme, we need a version-number for xml.
+ // If there is more than one version, parsing should update old macro-data, so that it
+ // could write out in the newer version in toXML().
+ if( element.attribute("xmlversion") != "1"){
+ kdDebug() << QString("XMLHandler::parseXML() Invalid xml-version \"%1\"").arg(element.attribute("xmlversion")) << endl;
+ return false;
+ }
+
+ // Do we need to load the macro's name?
+ // d->macro->setName(element.attribute("name"));
+
+ // Iterate through the child nodes the passed QDomElement has and
+ // build the MacroItem elements.
+ for(QDomNode itemnode = element.firstChild(); ! itemnode.isNull(); itemnode = itemnode.nextSibling()) {
+ // The tagname should be "item"
+ if(itemnode.nodeName() == "item") {
+ // The node is an element.
+ const QDomElement itemelem = itemnode.toElement();
+
+ // Create a new MacroItem
+ KSharedPtr<MacroItem> item = new MacroItem();
+
+ // Add the new item to our Macro.
+ d->macro->addItem( item );
+
+ // Each MacroItem may point to an Action instance. We
+ // try to determinate this action now and if it's defined
+ // and available, we set it.
+ KSharedPtr<Action> action = Manager::self()->action( itemelem.attribute("action") );
+ if(action.data()) {
+ item->setAction(action);
+ }
+
+ // Set the comment
+ item->setComment( itemelem.attribute("comment") );
+
+ // Iterate through the children this item has and try
+ // to fill the list of variables our new MacroItem has.
+ for(QDomNode childnode = itemnode.firstChild(); ! childnode.isNull(); childnode = childnode.nextSibling()) {
+ // The tagname should be "variable"
+ if(childnode.nodeName() == "variable") {
+ // The node is an element.
+ const QDomElement childelem = childnode.toElement();
+
+ // The name the variable has.
+ const QString name = childelem.attribute("name");
+ // The value the variable has.
+ const QString value = childelem.text();
+
+ // Store the new variable in our macroitem.
+ item->addVariable(name, value);
+ }
+ }
+ }
+ }
+
+ // Job was done successfully.
+ return true;
+}
+
+QDomElement XMLHandler::toXML()
+{
+ // The QDomDocument provides us the functionality to create new QDomElement instances.
+ QDomDocument document;
+
+ // Create the Macro-QDomElement. This element will be returned.
+ QDomElement macroelem = document.createElement("macro");
+
+ // Set the Macro-XML-Version, it should be the newest Version.
+ macroelem.setAttribute("xmlversion","1");
+
+ // Do we need to store the macro's name? Normaly the application
+ // could/should take care of it cause we don't know how the app
+ // may store the XML and cause we don't like to introduce
+ // redundancy at this point.
+ //macroelem.setAttribute("name",d->macro->name());
+
+ // The list of MacroItem-children a Macro provides.
+ QValueList<KSharedPtr<MacroItem > > items = d->macro->items();
+
+ // Create an iterator...
+ QValueList<KSharedPtr<MacroItem > >::ConstIterator it(items.constBegin()), end(items.constEnd());
+ // ...and iterate over the list of children the Macro provides.
+ for(;it != end; ++it) {
+ // We are iterating over MacroItem instances.
+ KSharedPtr<MacroItem> item = *it;
+
+ // Flag to determinate if we really need to remember this item what
+ // is only the case if comment or action is defined.
+ bool append = false;
+
+ // Each MacroItem will have an own node.
+ QDomElement itemelem = document.createElement("item");
+
+ // Each MacroItem could point to an Action provided by the Manager.
+ const KSharedPtr<Action> action = item->action();
+ if( action ) {
+ append = true;
+
+ // Remember the name of the action.
+ itemelem.setAttribute("action", action->name());
+
+ // Each MacroItem could have a list of variables. We
+ // iterate through that list and build a element
+ // for each single variable.
+ QMap<QString, KSharedPtr<Variable > > varmap = item->variables();
+
+ for(QMap<QString, KSharedPtr<Variable > >::ConstIterator vit = varmap.constBegin(); vit != varmap.constEnd(); ++vit) {
+ const KSharedPtr<Variable> v = vit.data();
+ if(! v.data()) {
+ // skip if the variable is NULL.
+ continue;
+ }
+ // Create an own element for the variable. The tagname will be
+ // the name of the variable.
+ QDomElement varelement = document.createElement("variable");
+
+ // Remember the name the value has.
+ varelement.setAttribute("name", vit.key());
+
+ // Remember the value as textnode.
+ varelement.appendChild(document.createTextNode(v->toString()));
+
+ // Add the new variable-element to our MacroItem.
+ itemelem.appendChild(varelement);
+ }
+ }
+
+ // Each MacroItem could have an optional comment.
+ const QString comment = item->comment();
+ if(! comment.isEmpty()) {
+ append = true;
+ itemelem.setAttribute("comment", item->comment());
+ }
+
+ // Check if we really need to remember the item.
+ if(append) {
+ macroelem.appendChild(itemelem);
+ }
+
+ }
+
+ // Job done. Return the macro's element.
+ return macroelem;
+}
diff --git a/kexi/plugins/macros/lib/xmlhandler.h b/kexi/plugins/macros/lib/xmlhandler.h
new file mode 100644
index 000000000..b6978d0fd
--- /dev/null
+++ b/kexi/plugins/macros/lib/xmlhandler.h
@@ -0,0 +1,77 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACRO_XMLHANDLER_H
+#define KOMACRO_XMLHANDLER_H
+
+#include "komacro_export.h"
+
+class QObject;
+class QDomElement;
+
+namespace KoMacro {
+
+ // Forward declarations.
+ class Macro;
+
+ /**
+ * The XMLHandler class manages the (un-)serialization of
+ * a @a Macro instance to/from XML.
+ */
+ class KOMACRO_EXPORT XMLHandler
+ {
+ public:
+
+ /**
+ * Constructor to init a @a XMLHandler .
+ * @param macro The @a Macro instance which will
+ * be managed.
+ */
+ XMLHandler(Macro* const macro);
+
+ /**
+ * Destructor to @a XMLHandler .
+ */
+ ~XMLHandler();
+
+ /**
+ * Reads a given @a QDomElement, extracts given
+ * Actions into the managed Macro-Instance.
+ * @param element The @a QDomElement within
+ * the @a Macro.
+ * @return Return true when parsing is successfull.
+ */
+ bool parseXML(const QDomElement& element);
+
+ /**
+ * Converts the macro to a @a QDomElement.
+ * @return The resulten @a QDomElement from
+ * the @a Macro.
+ */
+ QDomElement toXML();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/Makefile.am b/kexi/plugins/macros/tests/Makefile.am
new file mode 100644
index 000000000..36dbd76fe
--- /dev/null
+++ b/kexi/plugins/macros/tests/Makefile.am
@@ -0,0 +1,28 @@
+if include_kunittestgui
+ GUIBINPROGRAM = komacrotestgui
+else
+ GUIBINPROGRAM =
+endif
+
+bin_PROGRAMS = komacrotest $(GUIBINPROGRAM)
+
+komacrotest_SOURCES = komacrotest.cpp testobject.cpp testaction.cpp actiontests.cpp macrotests.cpp macroitemtests.cpp variabletests.cpp xmlhandlertests.cpp xmlhandlertests2.cpp
+komacrotest_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+komacrotest_LDADD = -lkunittest ../lib/libkomacro.la $(LIB_KDEUI) $(LIB_KPARTS)
+
+if include_kunittestgui
+ komacrotestgui_SOURCES = komacrotestgui.cpp testobject.cpp testaction.cpp actiontests.cpp macrotests.cpp macroitemtests.cpp variabletests.cpp xmlhandlertests.cpp xmlhandlertests2.cpp
+ komacrotestgui_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+ komacrotestgui_LDADD = -lkunittestgui ../lib/libkomacro.la $(LIB_KDEUI) $(LIB_KPARTS)
+endif
+
+KDE_CXXFLAGS = $(USE_EXCEPTIONS)
+INCLUDES = -I$(srcdir)/tests -I$(srcdir)../ $(all_includes)
+METASOURCES = AUTO
+
+guicheck: komacrotestgui
+ kunittest ./komacrotestgui
+
+check: komacrotest
+ echo $(srcdir)
+ kunittest ./komacrotest
diff --git a/kexi/plugins/macros/tests/actiontests.cpp b/kexi/plugins/macros/tests/actiontests.cpp
new file mode 100644
index 000000000..0150ecfd7
--- /dev/null
+++ b/kexi/plugins/macros/tests/actiontests.cpp
@@ -0,0 +1,211 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "actiontests.h"
+#include "testobject.h"
+#include "testaction.h"
+#include "komacrotestbase.h"
+
+#include "../lib/action.h"
+#include "../lib/function.h"
+#include "../lib/manager.h"
+#include "../lib/macro.h"
+#include "../lib/variable.h"
+#include "../lib/metaobject.h"
+#include "../lib/metamethod.h"
+#include "../lib/metaparameter.h"
+#include "../lib/exception.h"
+#include "../lib/macroitem.h"
+
+#include <ostream>
+
+#include <qstringlist.h>
+#include <qdom.h>
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kxmlguiclient.h>
+
+using namespace KUnitTest;
+using namespace KoMacroTest;
+
+namespace KoMacroTest {
+
+ /**
+ * Register KoMacroTest::CommonTests as TestSuite.
+ */
+
+ KUNITTEST_SUITE("KoMacroTestSuite");
+ KUNITTEST_REGISTER_TESTER(ActionTests);
+
+
+ class ActionTests::Private
+ {
+ public:
+ /**
+ * An KXMLGUIClient instance created on @a setUp() and
+ * passed to the @a KoMacro::Manager to bridge to the
+ * app-functionality.
+ */
+ KXMLGUIClient* xmlguiclient;
+
+ /**
+ * An @a TestObject instance used internaly to test
+ * handling and communication with from QObject
+ * inheritated instances.
+ */
+ TestAction* testaction;
+
+ QDomDocument* doomdocument;
+
+ KSharedPtr<KoMacro::Macro> macro;
+
+ QValueList< KSharedPtr<KoMacro::MacroItem> > items;
+
+ KSharedPtr<KoMacro::Action> actionptr;
+
+ Private()
+ : xmlguiclient(0)
+ , testaction(0)
+ , doomdocument(0)
+ , macro(0)
+ , actionptr(0)
+ {
+ }
+ };
+}
+
+typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype;
+
+
+ActionTests::ActionTests()
+ : KUnitTest::SlotTester()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+}
+
+ActionTests::~ActionTests()
+{
+ delete d->xmlguiclient;
+ delete d;
+}
+
+
+void ActionTests::setUp()
+{
+ d->xmlguiclient = new KXMLGUIClient();
+
+ if (::KoMacro::Manager::self() == 0) {
+ ::KoMacro::Manager::init( d->xmlguiclient );
+ }
+
+ d->testaction = new TestAction();
+ ::KoMacro::Manager::self()->publishAction(d->testaction);
+
+ d->doomdocument = new QDomDocument();
+
+ QString const xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\" >"
+ "<item action=\"testaction\" >"
+ "</item>"
+ "</macro>");
+
+ d->doomdocument->setContent(xml);
+ d->macro = KoMacro::Manager::self()->createMacro("testMacro");
+ d->macro->parseXML(d->doomdocument->documentElement());
+ d->macro->execute(this);
+ d->items = d->macro->items();
+ d->actionptr = d->items[0]->action();
+}
+
+void ActionTests::tearDown()
+{
+ delete d->actionptr;
+ delete d->macro;
+ delete d->doomdocument;
+ delete d->xmlguiclient;
+}
+
+
+void ActionTests::testMacro()
+{
+ kdDebug()<<"===================== testMacro() ======================" << endl;
+
+ //fetch Items and ..
+ //QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+
+ //... check that there is one
+ KOMACROTEST_XASSERT( d->items.count(), sizetype(0) );
+}
+
+void ActionTests::testAction()
+{
+ kdDebug()<<"===================== testAction() ======================" << endl;
+
+ //get it
+ //KSharedPtr<KoMacro::Action> actionptr = d->items[0]->action();
+ //-> check that it is not null
+ KOMACROTEST_XASSERT(sizetype(d->actionptr.data()), sizetype(0));
+}
+
+void ActionTests::testText()
+{
+ kdDebug()<<"===================== testText() ======================" << endl;
+
+ //KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+
+ const QString leetSpeech = "']['3 $']['";
+
+ //check i18n text
+ KOMACROTEST_ASSERT(d->actionptr->text(),QString("Test"));
+ //change it
+ d->actionptr->setText(leetSpeech);
+ //retest it
+ KOMACROTEST_ASSERT(d->actionptr->text(),QString(leetSpeech));
+}
+
+
+void ActionTests::testName()
+{
+ kdDebug()<<"===================== testName() ======================" << endl;
+
+ //KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+
+ //check name
+ KOMACROTEST_ASSERT(d->actionptr->name(),QString("testaction"));
+ //change it
+ d->actionptr->setName("ActionJackson");
+ //retest it
+ KOMACROTEST_ASSERT(d->actionptr->name(),QString("ActionJackson"));
+}
+
+void ActionTests::testComment()
+{
+ kdDebug()<<"===================== testComment() ======================" << endl;
+
+ //KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+
+ //check comment
+ KOMACROTEST_XASSERT(d->actionptr->comment(),QString("No Comment!"));
+ //set comment
+ d->actionptr->setComment("Stringtest");
+ //check comment again
+ KOMACROTEST_ASSERT(d->actionptr->comment(),QString("Stringtest"));
+}
+
+#include "actiontests.moc"
diff --git a/kexi/plugins/macros/tests/actiontests.h b/kexi/plugins/macros/tests/actiontests.h
new file mode 100644
index 000000000..48b5a2525
--- /dev/null
+++ b/kexi/plugins/macros/tests/actiontests.h
@@ -0,0 +1,89 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_ACTIONTESTS_H
+#define KOMACROTEST_ACTIONTESTS_H
+
+#include <kunittest/tester.h>
+
+namespace KoMacroTest {
+
+ /**
+ * The common testsuite used to test common @a KoMacro
+ * functionality.
+ */
+ class ActionTests : public KUnitTest::SlotTester
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ ActionTests();
+
+ /**
+ * Destructor.
+ */
+ virtual ~ActionTests();
+
+ public slots:
+
+ /**
+ * This slot got called by KUnitTest before testing
+ * starts.
+ */
+ void setUp();
+
+ /**
+ * This slot got called by KUnitTest after all tests
+ * are done.
+ */
+ void tearDown();
+
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testMacro();
+
+ /**
+ * Test the @a KoMacro::Action functionality.
+ */
+ void testAction();
+
+ void testText();
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testName();
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testComment();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/commontests.cpp b/kexi/plugins/macros/tests/commontests.cpp
new file mode 100644
index 000000000..84c596aa6
--- /dev/null
+++ b/kexi/plugins/macros/tests/commontests.cpp
@@ -0,0 +1,907 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "commontests.h"
+#include "testobject.h"
+#include "testaction.h"
+#include "komacrotestbase.h"
+
+#include "../lib/action.h"
+#include "../lib/function.h"
+#include "../lib/manager.h"
+#include "../lib/macro.h"
+#include "../lib/variable.h"
+#include "../lib/metaobject.h"
+#include "../lib/metamethod.h"
+#include "../lib/metaparameter.h"
+#include "../lib/exception.h"
+#include "../lib/macroitem.h"
+
+#include <ostream>
+#include <climits>
+
+#include <qstringlist.h>
+#include <qdom.h>
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kxmlguiclient.h>
+
+using namespace KUnitTest;
+using namespace KoMacroTest;
+
+namespace KoMacroTest {
+
+ /**
+ * Register KoMacroTest::CommonTests as TestSuite.
+ */
+ KUNITTEST_SUITE("CommonTestsSuite")
+ KUNITTEST_REGISTER_TESTER(CommonTests);
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class CommonTests::Private
+ {
+ public:
+
+ /**
+ * An KXMLGUIClient instance created on @a setUp() and
+ * passed to the @a KoMacro::Manager to bridge to the
+ * app-functionality.
+ */
+ KXMLGUIClient* xmlguiclient;
+
+ /**
+ * An @a TestObject instance used internaly to test
+ * handling and communication with from QObject
+ * inheritated instances.
+ */
+ TestObject* testobject;
+
+ TestAction* testaction;
+
+ QDomDocument* doomdocument;
+
+ /**
+ * Constructor.
+ */
+ Private()
+ : xmlguiclient(0)
+ , testobject(0)
+ , testaction(0)
+ , doomdocument(0)
+ {
+ }
+ };
+
+}
+
+typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype;
+
+CommonTests::CommonTests()
+ : KUnitTest::SlotTester()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+}
+
+CommonTests::~CommonTests()
+{
+ delete d->xmlguiclient;
+ delete d;
+}
+
+void CommonTests::setUp()
+{
+ d->xmlguiclient = new KXMLGUIClient();
+ ::KoMacro::Manager::init( d->xmlguiclient );
+
+ d->testobject = new TestObject( this );
+ ::KoMacro::Manager::self()->publishObject("TestObject", d->testobject);
+
+ d->testaction = new TestAction();
+ ::KoMacro::Manager::self()->publishAction(d->testaction);
+
+ d->doomdocument = new QDomDocument();
+
+ QString const xml = QString("<!DOCTYPE macros>"
+
+ "<macro xmlversion=\"1\">"
+
+ "<item action=\"testaction\" >"
+ "</item>"
+ "</macro>");
+
+ d->doomdocument->setContent(xml);
+}
+
+void CommonTests::tearDown()
+{
+ delete d->doomdocument;
+ delete d->testobject;
+ delete d->xmlguiclient;
+}
+
+void CommonTests::testManager()
+{
+ kdDebug()<<"===================== testManager() ======================" << endl;
+
+ //check if manager-guiClient equals xmlguiclient
+ KOMACROTEST_ASSERT( ::KoMacro::Manager::self()->guiClient(), d->xmlguiclient );
+ //check if manger-object equals testobject
+ KOMACROTEST_ASSERT( dynamic_cast<TestObject*>( (QObject*)::KoMacro::Manager::self()->object("TestObject") ), d->testobject );
+}
+/*
+void CommonTests::testAction()
+{
+ const QString testString = "teststring";
+ const QString testInt = "testint";
+ const QString testBool = "testbool";
+
+ //TODO: CLEANUP!!!!!!
+ //TODO: test manipulation of action and macroitem and context.
+
+ kdDebug()<<"===================== testAction() ======================" << endl;
+
+ //Publish TestAction for the first time.
+
+ QDomElement const domelement = d->doomdocument->documentElement();
+
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+
+ //Is our XML parseable ?
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),true);
+
+ //??
+ macro->execute(this);
+
+ //create list of KSharedPtr from the childs of the macro
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = macro->items();
+
+
+ //check that there is one
+ KOMACROTEST_ASSERT( items.count(), sizetype(1) );
+ //fetch the first one
+ KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+ //How do we know that an action exist ?
+ //-> check that it is not null
+ KOMACROTEST_XASSERT(sizetype(actionptr.data()), sizetype(0));
+ //fetch the "teststring"-variable
+ KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable("teststring");
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ //check that it is " "
+ KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("testString"));
+
+ //fetch the "testint"-variable
+ variableptr = actionptr->variable("testint");
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ //check that it is " "
+ KOMACROTEST_ASSERT(variableptr->variant().toInt(),int(0));
+
+ //fetch the "testbool"-variable
+ variableptr = actionptr->variable("testbool");
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ //check that it is " "
+ KOMACROTEST_ASSERT(variableptr->variant().toBool(),true);
+
+ actionptr->setVariable("teststring", "STRINGTEST", "TestString");
+ variableptr = actionptr->variable("teststring");
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ //check that it is " "
+ KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("TestString"));
+
+ actionptr->setVariable("testint","INTTEST",INT_MAX);
+ variableptr = actionptr->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX));
+
+ actionptr->setVariable("testbool","BOOLTEST", "false");
+ variableptr = actionptr->variable("testbool");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(variableptr->variant().toBool(),false);
+
+ //create new macroitem for testing
+ KoMacro::MacroItem* macroitem = new KoMacro::MacroItem();
+ //set the action
+ macroitem->setAction(d->testaction);
+ //append the macroitem to testitems
+ items.append(macroitem);
+ //increased ??
+ KOMACROTEST_ASSERT( items.count(), sizetype(2) );
+
+ //Manipulate the macroitem
+ macroitem->setVariable("teststring", "TeStString");
+ variableptr = macroitem->variable("teststring");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("TeStString"));
+
+ macroitem->setVariable("testint",INT_MIN);
+ variableptr = macroitem->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MIN));
+
+ macroitem->setVariable("testint",-1);
+ variableptr = macroitem->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(-1));
+
+
+ //commontests.cpp: In member function 'void KoMacroTest::CommonTests::testAction()':
+ //commontests.cpp:249: error: call of overloaded 'setVariable(const char [8], int)' is ambiguous
+ //../lib/macroitem.h:131: note: candidates are: QStringList KoMacro::MacroItem::setVariable(const QString&, KSharedPtr<KoMacro::Variable>)
+ //../lib/macroitem.h:137: note: QStringList KoMacro::MacroItem::setVariable(const QString&, const QVariant&)
+
+ macroitem->setVariable("testint",(int) 0);
+ variableptr = macroitem->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(0));
+
+
+ macroitem->setVariable("testint",1);
+ variableptr = macroitem->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(1));
+
+ macroitem->setVariable("testint",INT_MAX);
+ variableptr = macroitem->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX));
+
+ macroitem->setVariable("testbool","false");
+ variableptr = macroitem->variable("testbool");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(variableptr->variant().toBool(),false);
+
+ //secondway for appending an macroitem
+ //add the manipulated macroitem
+ macro->addItem(macroitem);
+ //increased ??
+ KOMACROTEST_ASSERT( items.count(), sizetype(3));
+} */
+
+void CommonTests::testXmlhandler()
+{
+ kdDebug()<<"===================== testXmlhandler() ======================" << endl;
+
+ // Local Init
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomElement domelement;
+
+ // Save old doomdocument
+ QString xmlOld = d->doomdocument->toString();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >testString</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testbla\" >somethingwrong</variable>" // TODO Is here a kdDebug-msg enough?
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ // Set the XML-document with the above string.
+ d->doomdocument->setContent(xml);
+ domelement = d->doomdocument->documentElement();
+ //Is our XML parseable ?
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),true);
+
+ // Test-XML-document with bad root element.
+ xml = QString("<!DOCTYPE macros>"
+ "<maro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >testString</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</maro>");
+ d->doomdocument->setContent(xml);
+ domelement = d->doomdocument->documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),false);
+
+ // Test-XML-document with wrong macro-xmlversion.
+ xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"2\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >testString</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ d->doomdocument->setContent(xml);
+ domelement = d->doomdocument->documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),false);
+
+ // TODO Test-XML-document if it has a wrong structure like wrong parathesis
+ // or missing end tag (is this critical??).
+ /*xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >testString</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "</item>"
+ "</macro>");
+ d->doomdocument->setContent(xml);
+ domelement = d->doomdocument->documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),false);*/
+
+ // Test-XML-document with wrong item- and variable-tags.
+ // TODO Could this happen??
+ xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<iem action=\"testaction\" >"
+ "<vle name=\"teststring\" >testString</variable>"
+ "<v name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ d->doomdocument->setContent(xml);
+ domelement = d->doomdocument->documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),true); //should be false?
+
+ // TODO Test-XML-document with maximum field-size.
+ xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >testString</variable>"
+ "<variable name=\"testint\" > 0 </variable>" // the value should be INT_MAX
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>" // DBL_MAX
+ "</item>"
+ "</macro>");
+ d->doomdocument->setContent(xml);
+ domelement = d->doomdocument->documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),true);
+
+ // TODO Test-XML-document with minimum field-size.
+ xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >testString</variable>"
+ "<variable name=\"testint\" >0</variable>" // INT_MIN
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>" // DBL_MIN
+ "</item>"
+ "</macro>");
+ d->doomdocument->setContent(xml);
+ domelement = d->doomdocument->documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),true);
+
+ // TODO Part 2: Read the parsen macro and make a comparison to the XML-document.
+
+ // TODO Part 3: From a Macro to XML.
+
+ // RODO Part 4: Compare the transformed XML with the given macro.
+
+ // Set back xml-string for other tests.
+ d->doomdocument->setContent(xmlOld);
+}
+
+void CommonTests::testFunction()
+{
+//TODO: CLEANUP!!!!!!
+/*
+ kdDebug()<<"===================== testFunction() ======================" << endl;
+
+ //create a QDomDocument
+ QDomDocument domdocument = QDomDocument();
+ //create some data
+ QString const comment = "Describe what the function does";
+ QString const name = "myfunc";
+ QString const text = "My Function";
+ QString const receiver = "TestObject";
+ QString const argument1 = "Some string";
+ int const argument2 = 12345;
+
+ //set "Function"-content in QDocument
+ domdocument.setContent(QString(
+ "<function name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver + "\" slot=\"myslot(const QString &amp; , int)\">"
+ "<argument>" + argument1 + "</argument>"
+ "<argument>" + QString("%1").arg(argument2) + "</argument>"
+ "</function>"
+ ));
+
+ //create an KomacroFunction with our data, and put it into a KSharedPtr
+ KSharedPtr<KoMacro::Action> functionptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //cast KSharedPtr to KoMacro-"Function"
+ KoMacro::Function* func = dynamic_cast<KoMacro::Function*>( functionptr.data() );
+ //check that function is not null
+ KOMACROTEST_XASSERT((int) func, 0);
+
+ //check domElement
+ KOMACROTEST_ASSERT( func->domElement(), domdocument.documentElement() );
+ //check name
+ KOMACROTEST_ASSERT( QString(func->name()), name );
+ //check text
+ KOMACROTEST_ASSERT( func->text(), text );
+ //check comment
+ KOMACROTEST_ASSERT( func->comment(), comment );
+ //check receiver
+ KOMACROTEST_ASSERT( func->receiver(), receiver );
+ //check slot (arguments)
+ KOMACROTEST_ASSERT( QString(func->slot()), QString("myslot(const QString&,int)") );
+
+ //create KoMacro-MetaObject from receiverObject
+ KSharedPtr<KoMacro::MetaObject> receivermetaobject = func->receiverObject();
+ //check that receivermetaobject.data is not null
+ KOMACROTEST_XASSERT((int) receivermetaobject.data(), 0);
+
+ //create KoMacro-MetaMethod from receiverObject
+ KSharedPtr<KoMacro::MetaMethod> receivermetamethod = receivermetaobject->slot( func->slot().latin1() );
+ //check that receivermetamethod.data is not null
+ KOMACROTEST_XASSERT((int) receivermetamethod.data(), 0);
+
+ //create list of variables from func
+ KoMacro::Variable::List funcvariables = func->variables();
+ //counter for hardcoded tests see below ...
+ uint i = 0;
+ KoMacro::Variable::List::ConstIterator it, end( funcvariables.constEnd() );
+ for( it = funcvariables.constBegin(); it != end; ++it) {
+ kdDebug() << "VARIABLE => " << (*it ? (*it)->toString() : "<NULL>") << endl;
+ //hardcoded test:
+ // firstrun we have a QString, secondrun we have an int
+ switch(i) {
+ case 0: { // returnvalue
+ KOMACROTEST_ASSERT(*it, KSharedPtr<KoMacro::Variable>(NULL));
+ } break;
+ case 1: { // first parameter
+ //check first variable of func is the same as argument1
+ //QString const argument1 = "Some string";
+ KOMACROTEST_ASSERT((*it)->toString(), argument1);
+ } break;
+ case 2: { // second parameter
+ //check second variable of func is the same as argument2
+ //int const argument2 = 12345;
+ KOMACROTEST_ASSERT((*it)->toInt(), argument2);
+ } break;
+ default: {
+ } break;
+ }
+ i++;
+ }
+
+ //check that we have two arguments + one returnvalue in func
+ KOMACROTEST_ASSERT( funcvariables.count(), uint(3) );
+
+ // check that the first argument (the returnvalue) is empty
+ KOMACROTEST_ASSERT( funcvariables[0], KSharedPtr<KoMacro::Variable>(NULL) );
+
+ //create a KoMacro-Variable-Ptr from first func argument
+ KSharedPtr<KoMacro::Variable> stringvar = funcvariables[1];
+ //check that it is not null
+ KOMACROTEST_XASSERT((int) stringvar.data(),0);
+ //check via QVariant type that stringvar is from Type Variant
+ KOMACROTEST_ASSERT( stringvar->type(), KoMacro::MetaParameter::TypeVariant );
+ //check via metaparameter that variant is from type string
+ KOMACROTEST_ASSERT( stringvar->variantType(), QVariant::String );
+ //chech that stringvar equals argument1
+ KOMACROTEST_ASSERT( stringvar->toString(), argument1 );
+
+ //create a KoMacro-Variable-Ptr from second func argument
+ KSharedPtr<KoMacro::Variable> intvar = funcvariables[2];
+ //check that it is not null
+ KOMACROTEST_XASSERT((int) intvar.data(), 0);
+ //check via QVariant type that stringvar is from Type Variant
+ KOMACROTEST_ASSERT( intvar->type(), KoMacro::MetaParameter::TypeVariant );
+ //check that intvar is An String -> we create an string from int because of xml
+ KOMACROTEST_ASSERT( intvar->variantType(), QVariant::String );
+ //check that intvar equals argument2
+ KOMACROTEST_ASSERT( intvar->toInt(), argument2 );
+
+ //returnvalue see testobject ....
+ KSharedPtr<KoMacro::Variable> funcreturnvalue = receivermetamethod->invoke( funcvariables );
+ kdDebug() << "CommonTests::testFunction() RETURNVALUE =====> " << funcreturnvalue->toString() << endl;
+ KOMACROTEST_ASSERT( funcreturnvalue->toInt(), argument2 );
+
+ //check returnvalue
+ //func->setReturnValue(new KoMacro::Variable("54321"));
+ //KOMACROTEST_ASSERT( func->returnValue()->toString(), QString("54321") );
+*/
+}
+
+void CommonTests::testIntFunction()
+{
+//TODO: CLEANUP!!!!!!
+/*
+ kdDebug()<<"===================== testIntFunction() ======================" << endl;
+
+ //create a QDomDocument
+ QDomDocument domdocument = QDomDocument();
+
+ //set "Function"-content in QDocument
+ domdocument.setContent(QString(
+ "<function name=\"myfunc\" text=\"My Function\" comment=\"comment\" receiver=\"TestObject\" slot=\"myslot(const QString &amp; , int)\">"
+ "<argument>Some string</argument>"
+ "<argument>12345</argument>"
+ "</function>"
+ ));
+
+ //create an KomacroFunction with our data, and put it into a KSharedPtr
+ KSharedPtr<KoMacro::Action> functionptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //Cast data to function
+ KoMacro::Function* func = dynamic_cast<KoMacro::Function*>( functionptr.data() );
+ //check that it is not null
+ KOMACROTEST_XASSERT((int) func, 0);
+ //execute the function
+ func->activate();
+ //Check returnvalue is same value we entered
+ //KOMACROTEST_ASSERT(func->returnValue()->toString(),QString("12345"));
+*/
+}
+
+void CommonTests::testDoubleFunction()
+{
+//TODO: CLEANUP!!!!!!
+/*
+ kdDebug()<<"===================== testDoubleFunction() ======================" << endl;
+
+ //create a QDomDocument
+ QDomDocument domdocument = QDomDocument();
+
+ //set "Function"-content in QDocument
+ domdocument.setContent(QString(
+ "<function name=\"myfunc\" text=\"My Function\" comment=\"comment\" receiver=\"TestObject\" slot=\"myslot(const QString &amp; , double)\">"
+ "<argument>Some string</argument>"
+ "<argument>12.56</argument>"
+ "</function>"
+ ));
+
+ //create an KomacroFunction with our data, and put it into a KSharedPtr
+ KSharedPtr<KoMacro::Action> functionptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //Cast data to function
+ KoMacro::Function* func = dynamic_cast<KoMacro::Function*>( functionptr.data() );
+ //check that it is not null
+ KOMACROTEST_XASSERT((int) func, 0);
+ //execute the function
+ func->activate();
+ //Check returnvalue is same value we entered
+ //KOMACROTEST_ASSERT(func->returnValue()->toString(),QString("12.56"));
+*/
+}
+
+void CommonTests::testQStringFunction()
+{
+//TODO: CLEANUP!!!!!!
+/*
+ kdDebug()<<"===================== testQStringFunction() ======================" << endl;
+
+ //create a QDomDocument
+ QDomDocument domdocument = QDomDocument();
+
+ //set "Function"-content in QDocument
+ domdocument.setContent(QString(
+ "<function name=\"myfunc\" text=\"My Function\" comment=\"comment\" receiver=\"TestObject\" slot=\"myslot(const QString &amp;)\">"
+ "<argument>Some string</argument>"
+ "</function>"
+ ));
+
+ //create an KomacroFunction with our data, and put it into a KSharedPtr
+ KSharedPtr<KoMacro::Action> functionptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //Cast data to function
+ KoMacro::Function* func = dynamic_cast<KoMacro::Function*>( functionptr.data() );
+ //check that it is not null
+ KOMACROTEST_XASSERT((int) func, 0);
+ //execute the function func->activate();
+ //Check returnvalue is same value we entered
+ //KOMACROTEST_ASSERT(func->returnValue()->toString(),QString("Some string"));
+*/
+}
+
+void CommonTests::testMacro()
+{
+//TODO: CLEANUP!!!!!!
+ kdDebug()<<"===================== testMacro() ======================" << endl;
+
+ QDomElement const domelement = d->doomdocument->documentElement();
+
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ //Is our XML parseable ?
+ KOMACROTEST_ASSERT(macro->parseXML(domelement),true);
+
+// //create a QDomDocument
+// QDomDocument domdocument = QDomDocument();
+//
+// //Fully fleged content this time with macro,function and action
+// domdocument.setContent(QString(
+// "<macro name=\"mymacro\" icon=\"myicon\" text=\"My Macro\" comment=\"Some comment to describe the Macro.\">"
+// "<action name=\"myaction\" text=\"My Action\" comment=\"Just some comment\" />"
+// "<function name=\"myfunc\" text=\"My Function\" comment=\"Describe what the function does\" receiver=\"TestObject\" slot=\"myslot(const QString &amp;)\">"
+// "<argument>The myfunc argument string</argument>"
+// "</function>"
+// "</macro>"
+// ));
+//
+// //create Macro
+// // KSharedPtr<KoMacro::Action> macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+// //cast data to Macro
+// KoMacro::Macro* macro = dynamic_cast<KoMacro::Macro*>( macroptr.data() );
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(macro.data()), sizetype(0));
+ //check that domeElement given to manager is the sam as in the macro
+// KOMACROTEST_ASSERT( macro->toXML(), d->doomdocument->documentElement() );
+ //check the name
+ KOMACROTEST_ASSERT( QString(macro->name()), QString("testMacro") );
+
+ /**
+ @deprecated values no longer exist
+
+ //check the text
+ KOMACROTEST_ASSERT( macro->text(), QString("My Macro") );
+ //check iconname
+ KOMACROTEST_ASSERT( QString(macro->icon()), QString("myicon") );
+ //check comment
+ KOMACROTEST_ASSERT( macro->comment(), QString("Some comment to describe the Macro.") );
+ */
+
+ //create list of KsharedPtr from the childs of the macro
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = macro->items();
+ //check that there is one
+ KOMACROTEST_ASSERT( items.count(), sizetype(1) );
+ //fetch the first one
+ KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+ //How do we know that an action exist ?
+ //-> check that it is not null
+ KOMACROTEST_XASSERT(sizetype(actionptr.data()), sizetype(0));
+ //check that it has the right name
+ KOMACROTEST_ASSERT( QString(actionptr->name()), QString("testaction") );
+ //check that it has the right text
+ KOMACROTEST_ASSERT( actionptr->text(), QString("Test") );
+ //check that it has the right comment
+// KOMACROTEST_ASSERT( actionptr->comment(), QString("") );
+/*
+ //fetch the second one
+ KSharedPtr<KoMacro::Action> myfuncptr = children[1];
+ //cast it to function
+
+ KoMacro::Function* myfunc = dynamic_cast<KoMacro::Function*>( myfuncptr.data() );
+ //check that it isn?t null
+ KOMACROTEST_XASSERT((int) myfunc, 0);
+
+ //check it?s name
+ KOMACROTEST_ASSERT( QString(myfunc->name()), QString("myfunc"));
+
+ //check it?s text
+ KOMACROTEST_ASSERT( myfunc->text(), QString("My Function") );
+ //check it?s comment
+ KOMACROTEST_ASSERT( myfunc->comment(), QString("Describe what the function does") );
+ //check it?s receiver object
+ KOMACROTEST_ASSERT( myfunc->receiver(), QString("TestObject") );
+ //check it?s slot
+ KOMACROTEST_ASSERT( myfunc->slot(), QString("myslot(const QString&)") );
+
+ //exceute it
+ myfunc->activate();
+*/
+ //create another macro
+ KSharedPtr<KoMacro::Macro> yanMacro = KoMacro::Manager::self()->createMacro("testMacro2");
+
+ KOMACROTEST_ASSERT(yanMacro->parseXML(domelement),true);
+ //create two more macros
+ //KSharedPtr<KoMacro::Action> yanActionptr2 = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //KSharedPtr<KoMacro::Action> yanActionptr3 = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+
+ //check that they aren?t null
+ KOMACROTEST_XASSERT(sizetype(yanMacro.data()), sizetype(0));
+ //KOMACROTEST_XASSERT((int) yanActionptr2.data(), 0);
+ //KOMACROTEST_XASSERT((int) yanActionptr3.data(), 0);
+
+ //create a list of the children from yanMacro
+ //QValueList< KSharedPtr<KoMacro::Action> > yanChildren = yanMacro->children();
+ //check that there are two
+ //KOMACROTEST_ASSERT(yanChildren.count(), uint(2));
+/*
+ {
+ //keep oldsize
+ const int oldsize = yanChildren.count();
+ //add a new child to the macro
+ yanMacro->addChild(yanActionptr2);
+ //get the children
+ yanChildren = yanMacro->children();
+ //get count of children
+ const int size = yanChildren.count();
+ //check count has changed
+ KOMACROTEST_XASSERT(size, oldsize);
+ }
+
+ {
+ //keep oldsize
+ const int oldsize = yanChildren.count();
+ //add a new child to the macro
+ yanMacro->addChild(yanActionptr3);
+ //get the children
+ yanChildren = yanMacro->children();
+ //get count of children
+ const int size = yanChildren.count();
+ //check count has changed
+ KOMACROTEST_XASSERT(size, oldsize);
+ //check the hasChildren function
+ KOMACROTEST_ASSERT(yanMacro->hasChildren(), true);
+ }
+*/
+
+}
+
+void CommonTests::testDom() {
+//TODO: CLEANUP!!!!!!
+ kdDebug()<<"===================== testDom() ======================" << endl;
+/*
+ //create a QDomDocument
+ QDomDocument domdocument = QDomDocument();
+ //create data for various documents
+ QString const comment = "Describe what the function does";
+ QString const name = "myfunc";
+ QString const text = "My Function";
+ QString const receiver1 = "TestObject";
+ QString const receiver2 = "GibtsNich";
+
+ //create wrong Argument tag
+ domdocument.setContent(QString(
+ "<function name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver1 + "\" slot=\"myslot(const QString &amp; , int)\">"
+ "<Arg0ment>Some string</Arg0ment>"
+ "<Arg0ment>12345</Arg0ment>"
+ "</function>"
+ ));
+ //create functiom
+ KSharedPtr<KoMacro::Action> macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //try to execute function and catch exception
+ KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate());
+
+ //create wrong receiver
+ domdocument.setContent(QString(
+ "<function name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver2 + "\" slot=\"myslot(const QString &amp; , int)\">"
+ "<argument>Some string</argument>"
+ "<argument>12345</argument>"
+ "</function>"
+ ));
+ //create function
+ macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //try to execute function and catch exception
+ KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate());
+
+ //create "wrong" number of parameters
+ domdocument.setContent(QString(
+ "<function name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver1 + "\" slot=\"myslot(const QString &amp; , int, double)\">"
+ "<argument>Some string</argument>"
+ "<argument>12345</argument>"
+ "<argument>12345.25</argument>"
+ "</function>"
+ ));
+ //create function
+ macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //try to execute function and catch exception
+ KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate());
+
+ //create wrong function tag
+ domdocument.setContent(QString(
+ "<NoFunction name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver1 + "\" slot=\"myslot(const QString &amp; , int, double)\">"
+ "<argument>Some string</argument>"
+ "<argument>12345</argument>"
+ "<argument>12345.25</argument>"
+ "</NoFunction>"
+ ));
+ //try to create function and catch exception
+ KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ));
+
+ //create empty function
+ domdocument.setContent(QString(
+ "<function name=\"\" text=\"\" comment=\"\" receiver=\"\" slot=\"\">"
+ "<argument> </argument>"
+ "<argument> </argument>"
+ "<argument> </argument>"
+ "</function>"
+ ));
+ //create function
+ macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //try to execute function and catch exception
+ KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate());
+
+
+ //create empty function
+ domdocument.setContent(QString(
+ "<function>"
+ "</function>"
+ ));
+ //create function
+ macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //try to execute function and catch exception
+ KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate());
+*/
+}
+
+void CommonTests::testVariables()
+{
+//TODO: CLEANUP!!!!!!
+ kdDebug()<<"===================== testVariables() ======================" << endl;
+/*
+ //create a QDomDocument
+ QDomDocument domdocument = QDomDocument();
+ //create data
+ domdocument.setContent(QString(
+ "<macro name=\"mymacro123\" text=\"My Macro 123\">"
+ "<function name=\"func1\" text=\"Function1\" receiver=\"TestObject\" slot=\"myslot(const QString &amp;)\" >"
+ "<argument>$MyArgumentVariable</argument>"
+ "<return>$MyReturnVariable</return>"
+ "</function>"
+ "</macro>"
+ ));
+
+ //create an macro
+ KSharedPtr<KoMacro::Action> macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() );
+ //cast data to macro
+ KoMacro::Macro* macro = dynamic_cast<KoMacro::Macro*>( macroptr.data() );
+ //check that it is not null
+ KOMACROTEST_XASSERT((int) macro, 0);
+
+ //create a list of its children
+ QValueList< KSharedPtr<KoMacro::Action> > children = macro->children();
+ //Check that there are two children. The first child is always the returnvalue.
+ KOMACROTEST_ASSERT( children.count(), uint(2) );
+ //fetch the children
+ KSharedPtr<KoMacro::Action> func1ptr = children[1];
+
+ //create new context
+ KSharedPtr<KoMacro::Context> context = new KoMacro::Context(macroptr);
+
+ {
+ //try to execute function with non-functional variable
+ KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, func1ptr->activate(context));
+
+ KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, context->variable("$MyReturnVariable333"));
+ }
+
+ {
+ //set variable to be a QString
+ context->setVariable("$MyArgumentVariable", new KoMacro::Variable("Some string"));
+ //execute function
+ func1ptr->activate(context);
+ //fetch return value
+ KSharedPtr<KoMacro::Variable> returnvariable = context->variable("$MyReturnVariable");
+ //check that it is not null
+ KOMACROTEST_XASSERT( (int) returnvariable.data(), 0);
+ //check that it is "Some String"
+ KOMACROTEST_ASSERT(returnvariable->toString(),QString("Some string"));
+ }
+
+ {
+ //set variable to be an Int
+ context->setVariable("$MyArgumentVariable", new KoMacro::Variable( 12345 ));
+ //execute function
+ func1ptr->activate(context);
+ //fetch return value
+ KSharedPtr<KoMacro::Variable> returnvariable = context->variable("$MyReturnVariable");
+ //check that it is not null
+ KOMACROTEST_XASSERT( (int) returnvariable.data(), 0);
+ //check that it is 12345
+ KOMACROTEST_ASSERT(returnvariable->toInt(),12345);
+ }
+*/
+}
+
+#include "commontests.moc"
diff --git a/kexi/plugins/macros/tests/commontests.h b/kexi/plugins/macros/tests/commontests.h
new file mode 100644
index 000000000..a3199ce29
--- /dev/null
+++ b/kexi/plugins/macros/tests/commontests.h
@@ -0,0 +1,118 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_COMMONTESTS_H
+#define KOMACROTEST_COMMONTESTS_H
+
+#include <kunittest/tester.h>
+
+namespace KoMacroTest {
+
+ /**
+ * The common testsuite used to test common @a KoMacro
+ * functionality.
+ */
+ class CommonTests : public KUnitTest::SlotTester
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ CommonTests();
+
+ /**
+ * Destructor.
+ */
+ virtual ~CommonTests();
+
+ public slots:
+
+ /**
+ * This slot got called by KUnitTest before testing
+ * starts.
+ */
+ void setUp();
+
+ /**
+ * This slot got called by KUnitTest after all tests
+ * are done.
+ */
+ void tearDown();
+
+ /**
+ * Test the @a KoMacro::Manager functionality.
+ */
+ void testManager();
+
+ /**
+ * Test the @a KoMacro::Action functionality.
+ */
+ //void testAction();
+
+ /**
+ * Test the @a KoMacro::XmlHandler functionality.
+ */
+ void testXmlhandler();
+
+ /**
+ * Test the @a KoMacro::Function functionality.
+ */
+ void testFunction();
+
+ /**
+ * Test the @a KoMacro::Function functionality with an int.
+ */
+ void testIntFunction();
+
+ /**
+ * Test the @a KoMacro::Function functionality with a double.
+ */
+ void testDoubleFunction();
+
+ /**
+ * Test the @a KoMacro::Function functionality with a QString.
+ */
+ void testQStringFunction();
+
+ /**
+ * Test the @a KoMacro::Macro functionality.
+ */
+ void testMacro();
+
+ /**
+ * Test the @a KoMacro::Dom functionality.
+ */
+ void testDom();
+ /**
+ * Test the @a KoMacro::Variable functionality.
+ */
+ void testVariables();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/komacrotest.cpp b/kexi/plugins/macros/tests/komacrotest.cpp
new file mode 100644
index 000000000..55d017a9a
--- /dev/null
+++ b/kexi/plugins/macros/tests/komacrotest.cpp
@@ -0,0 +1,58 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include <kaboutdata.h>
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <klocale.h>
+#include <kunittest/runner.h>
+
+static const char description[] =
+ I18N_NOOP("KoMacroTester");
+static const char version[] = "0.1";
+static KCmdLineOptions options[] =
+{
+ KCmdLineLastOption
+};
+
+int main( int argc, char** argv )
+{
+ try {
+ KAboutData about("KoMacroTester",
+ I18N_NOOP("KoMacroTester"),
+ version,
+ description,
+ KAboutData::License_LGPL,
+ "(C) 2005 Sebastian Sauer", 0, 0, "mail@dipe.org");
+
+ KCmdLineArgs::init(argc, argv, &about);
+ KCmdLineArgs::addCmdLineOptions( options );
+ KApplication app;
+
+ //create an new "Console"-runner
+ KUnitTest::Runner * runner = KUnitTest::Runner::self();
+ //start our Testsuite
+ runner->runTests();
+ //done
+ return 0;
+ }
+ // mmh seems we forgot to catch an exception...
+ catch(...) {
+ qFatal("Unhandled Exception!");
+ }
+}
diff --git a/kexi/plugins/macros/tests/komacrotestbase.h b/kexi/plugins/macros/tests/komacrotestbase.h
new file mode 100644
index 000000000..d423e0864
--- /dev/null
+++ b/kexi/plugins/macros/tests/komacrotestbase.h
@@ -0,0 +1,90 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+#ifndef KOMACROTEST_BASE_H
+#define KOMACROTEST_BASE_H
+
+//Our own extended Macros from KUnittest
+/**
+* Macro to perform an equality check and exits the method if the check failed.
+*
+* @param actual The actual value.
+* @param expected The expected value.
+*/
+#define KOMACROTEST_ASSERT(actual, expected) \
+ { \
+ std::cout << QString("Testing: %1 == %2").arg(#actual).arg(#expected).latin1() << std::endl; \
+ check( __FILE__, __LINE__, #actual, actual, expected, false ); \
+ if(actual != expected) \
+ { \
+ kdWarning() << QString("==============> FAILED") << endl; \
+ return; \
+ } \
+ }
+
+/**
+* Macro to perform a check that is expected to fail and that exits the method if the check failed.
+*
+* @param actual The actual value.
+* @param notexpected The not expected value.
+*/
+#define KOMACROTEST_XASSERT(actual, notexpected) \
+ { \
+ std::cout << QString("Testing: %1 != %2").arg(#actual).arg(#notexpected).latin1() << std::endl; \
+ check( __FILE__, __LINE__, #actual, actual, notexpected, true ); \
+ if(actual == notexpected) \
+ { \
+ kdWarning() << QString("==============> FAILED") << endl; \
+ return; \
+ } \
+ }
+
+/**
+* Macro to test that @p expression throws an exception that is catched with the
+* @p exceptionCatch exception.
+*
+* @param exceptionCatch The exception that is expected to be thrown.
+* @param expression The expression that is executed within a try-catch block to
+* check for the @p exceptionCatch .
+*/
+#define KOMACROTEST_ASSERTEXCEPTION(exceptionCatch, expression) \
+ { \
+ try { \
+ expression; \
+ } \
+ catch(exceptionCatch) { \
+ setExceptionRaised(true); \
+ } \
+ if(exceptionRaised()) { \
+ success(QString(__FILE__) + "[" + QString::number(__LINE__) + "]: passed " + #expression); \
+ setExceptionRaised(false); \
+ } \
+ else { \
+ failure(QString(__FILE__) + "[" + QString::number(__LINE__) + QString("]: failed to throw an exception on: ") + #expression); \
+ return; \
+ } \
+ }
+
+#endif
+
+//Used more tha once at various places
+//names of variables from testaction
+namespace KoMacroTest {
+ static const QString TESTSTRING = "teststring";
+ static const QString TESTINT = "testint";
+ static const QString TESTBOOL = "testbool";
+}
diff --git a/kexi/plugins/macros/tests/komacrotestgui.cpp b/kexi/plugins/macros/tests/komacrotestgui.cpp
new file mode 100644
index 000000000..abf4459d2
--- /dev/null
+++ b/kexi/plugins/macros/tests/komacrotestgui.cpp
@@ -0,0 +1,60 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include <kaboutdata.h>
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <klocale.h>
+
+#include "kunittest/runnergui.h"
+
+static const char description[] =
+ I18N_NOOP("KoMacroTestgui.");
+
+static const char version[] = "0.1";
+
+static const KCmdLineOptions options[] =
+{
+ KCmdLineLastOption
+};
+
+int main( int argc, char** argv )
+{
+ try {
+ KAboutData const about("KomacroTests", I18N_NOOP("KomacroTests"), version, description,
+ KAboutData::License_LGPL, "(C) 2005 Tobi Krebs", 0, 0,
+ "Tobi.Krebs@gmail.com");
+
+ KCmdLineArgs::init(argc, argv, &about);
+ KCmdLineArgs::addCmdLineOptions( options );
+ //create new kapplication
+ KApplication app;
+ //create new kunitrunnergui
+ KUnitTest::RunnerGUI runner(0);
+ //show the ui
+ runner.show();
+ //set ui mainwidget
+ app.setMainWidget(&runner);
+ //return exitcode of ui
+ return app.exec();
+ }
+ // mmh seems we forgot to catch an exception...
+ catch(...) {
+ qFatal("Unhandled Exception!");
+ }
+}
diff --git a/kexi/plugins/macros/tests/macroitemtests.cpp b/kexi/plugins/macros/tests/macroitemtests.cpp
new file mode 100644
index 000000000..366318e17
--- /dev/null
+++ b/kexi/plugins/macros/tests/macroitemtests.cpp
@@ -0,0 +1,243 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "macroitemtests.h"
+#include "testaction.h"
+#include "komacrotestbase.h"
+
+#include "../lib/action.h"
+#include "../lib/manager.h"
+#include "../lib/macro.h"
+#include "../lib/variable.h"
+#include "../lib/metaobject.h"
+#include "../lib/metamethod.h"
+#include "../lib/metaparameter.h"
+#include "../lib/exception.h"
+#include "../lib/macroitem.h"
+
+#include <ostream>
+
+#include <qstringlist.h>
+#include <qdom.h>
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kxmlguiclient.h>
+
+using namespace KUnitTest;
+using namespace KoMacroTest;
+
+namespace KoMacroTest {
+
+ /**
+ * Register KoMacroTest::CommonTests as TestSuite.
+ */
+
+ KUNITTEST_SUITE("KoMacroTestSuite");
+ KUNITTEST_REGISTER_TESTER(MacroitemTests);
+
+
+ class MacroitemTests::Private
+ {
+ public:
+ /**
+ * An KXMLGUIClient instance created on @a setUp() and
+ * passed to the @a KoMacro::Manager to bridge to the
+ * app-functionality.
+ */
+ KXMLGUIClient* xmlguiclient;
+
+ /**
+ * An @a TestObject instance used internaly to test
+ * handling and communication with from QObject
+ * inheritated instances.
+ */
+ TestAction* testaction;
+
+ QDomDocument* doomdocument;
+
+ KSharedPtr<KoMacro::Macro> macro;
+
+ Private()
+ : xmlguiclient(0)
+ , testaction(0)
+ , doomdocument(0)
+ , macro(0)
+ {
+ }
+ };
+}
+
+typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype;
+
+MacroitemTests::MacroitemTests()
+ : KUnitTest::SlotTester()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+}
+
+MacroitemTests::~MacroitemTests()
+{
+ delete d->xmlguiclient;
+ delete d;
+}
+
+void MacroitemTests::setUp()
+{
+ d->xmlguiclient = new KXMLGUIClient();
+
+ if (::KoMacro::Manager::self() == 0) {
+ ::KoMacro::Manager::init( d->xmlguiclient );
+ }
+
+ d->testaction = new TestAction();
+ ::KoMacro::Manager::self()->publishAction(d->testaction);
+
+ d->doomdocument = new QDomDocument();
+
+ QString const xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\" >"
+ "<item action=\"testaction\" >"
+ "</item>"
+ "</macro>");
+
+ d->doomdocument->setContent(xml);
+ d->macro = KoMacro::Manager::self()->createMacro("testMacro");
+ d->macro->parseXML(d->doomdocument->documentElement());
+ d->macro->execute(this);
+}
+
+void MacroitemTests::tearDown()
+{
+ delete d->macro;
+ delete d->doomdocument;
+ delete d->xmlguiclient;
+}
+
+
+void MacroitemTests::testMacro()
+{
+ kdDebug()<<"===================== testMacroitem() ======================" << endl;
+ kdDebug()<<"===================== testMacro() ======================" << endl;
+
+ //fetch Items and ..
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+
+ //... check that there is one
+ KOMACROTEST_XASSERT( items.count(), sizetype(0) );
+}
+
+void MacroitemTests::testMacroItemString()
+{
+
+
+ kdDebug()<<"===================== testMacroItemString() ======================" << endl;
+
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+ KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+ KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable(TESTSTRING);
+
+ //create new macroitem for testing
+ KoMacro::MacroItem* macroitem = new KoMacro::MacroItem();
+ //set the action
+ macroitem->setAction(d->testaction);
+
+ //append the macroitem to testitems
+ items.append(macroitem);
+
+ //increased ??
+ KOMACROTEST_ASSERT( items.count(), sizetype(2) );
+
+ //Manipulate the macroitem
+ macroitem->setVariable(TESTSTRING, "TeStString");
+ variableptr = macroitem->variable(TESTSTRING);
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("TeStString"));
+
+
+ //secondway for appending an macroitem
+ //add the manipulated macroitem
+ d->macro->addItem(macroitem);
+
+ //increased ??
+ KOMACROTEST_ASSERT( items.count(), sizetype(3));
+
+}
+
+void MacroitemTests::testMacroItemInt()
+{
+
+
+ kdDebug()<<"===================== testMacroItemInt() ======================" << endl;
+
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+ KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+
+ //create new macroitem for testing
+ KoMacro::MacroItem* macroitem = new KoMacro::MacroItem();
+ //set the action
+ macroitem->setAction(d->testaction);
+ items.append(macroitem);
+
+ macroitem->setVariable(TESTINT,INT_MIN);
+ KSharedPtr<KoMacro::Variable> variableptr = macroitem->variable(TESTINT);
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MIN));
+
+ macroitem->setVariable(TESTINT,-1);
+ variableptr = macroitem->variable(TESTINT);
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(-1));
+
+ macroitem->setVariable(TESTINT,QVariant(0));
+ variableptr = macroitem->variable(TESTINT);
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(0));
+
+ macroitem->setVariable(TESTINT,1);
+ variableptr = macroitem->variable(TESTINT);
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(1));
+
+ macroitem->setVariable(TESTINT,INT_MAX);
+ variableptr = macroitem->variable(TESTINT);
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX));
+}
+
+void MacroitemTests::testMacroItemBool()
+{
+
+
+ kdDebug()<<"===================== testMacroItemBool() ======================" << endl;
+
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+ KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+
+ //create new macroitem for testing
+ KoMacro::MacroItem* macroitem = new KoMacro::MacroItem();
+ //set the action
+ macroitem->setAction(d->testaction);
+ items.append(macroitem);
+
+ macroitem->setVariable(TESTBOOL,"false");
+ KSharedPtr<KoMacro::Variable> variableptr = macroitem->variable(TESTBOOL);
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(variableptr->variant().toBool(),false);
+}
+#include "macroitemtests.moc"
diff --git a/kexi/plugins/macros/tests/macroitemtests.h b/kexi/plugins/macros/tests/macroitemtests.h
new file mode 100644
index 000000000..3e44eebda
--- /dev/null
+++ b/kexi/plugins/macros/tests/macroitemtests.h
@@ -0,0 +1,87 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_ACTIONTESTS_H
+#define KOMACROTEST_ACTIONTESTS_H
+
+#include <kunittest/tester.h>
+
+namespace KoMacroTest {
+
+ /**
+ * The common testsuite used to test common @a KoMacro
+ * functionality.
+ */
+ class MacroitemTests : public KUnitTest::SlotTester
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ MacroitemTests();
+
+ /**
+ * Destructor.
+ */
+ virtual ~MacroitemTests();
+
+ public slots:
+
+ /**
+ * This slot got called by KUnitTest before testing
+ * starts.
+ */
+ void setUp();
+
+ /**
+ * This slot got called by KUnitTest after all tests
+ * are done.
+ */
+ void tearDown();
+
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testMacro();
+
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testMacroItemString();
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testMacroItemInt();
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testMacroItemBool();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/macrotests.cpp b/kexi/plugins/macros/tests/macrotests.cpp
new file mode 100644
index 000000000..ed222df2e
--- /dev/null
+++ b/kexi/plugins/macros/tests/macrotests.cpp
@@ -0,0 +1,192 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "macrotests.h"
+#include "testobject.h"
+#include "testaction.h"
+#include "komacrotestbase.h"
+
+#include "../lib/action.h"
+#include "../lib/function.h"
+#include "../lib/manager.h"
+#include "../lib/macro.h"
+#include "../lib/variable.h"
+#include "../lib/metaobject.h"
+#include "../lib/metamethod.h"
+#include "../lib/metaparameter.h"
+#include "../lib/exception.h"
+#include "../lib/macroitem.h"
+
+#include <ostream>
+
+#include <qstringlist.h>
+#include <qdom.h>
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kxmlguiclient.h>
+
+using namespace KUnitTest;
+using namespace KoMacroTest;
+
+namespace KoMacroTest {
+
+ /**
+ * Register KoMacroTest::CommonTests as TestSuite.
+ */
+ KUNITTEST_SUITE("KoMacroTestSuite")
+ KUNITTEST_REGISTER_TESTER(MacroTests);
+
+ class MacroTests::Private
+ {
+ public:
+ /**
+ * An KXMLGUIClient instance created on @a setUp() and
+ * passed to the @a KoMacro::Manager to bridge to the
+ * app-functionality.
+ */
+ KXMLGUIClient* xmlguiclient;
+
+ /**
+ * An @a TestObject instance used internaly to test
+ * handling and communication with from QObject
+ * inheritated instances.
+ */
+ TestObject* testobject;
+
+ TestAction* testaction;
+
+ QDomDocument* doomdocument;
+
+ Private()
+ : xmlguiclient(0)
+ , testobject(0)
+ , testaction(0)
+ , doomdocument(0)
+ {
+ }
+ };
+}
+
+typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype;
+
+
+MacroTests::MacroTests()
+ : KUnitTest::SlotTester()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+}
+
+MacroTests::~MacroTests()
+{
+ delete d->xmlguiclient;
+ delete d;
+}
+
+
+void MacroTests::setUp()
+{
+ d->xmlguiclient = new KXMLGUIClient();
+ //::KoMacro::Manager::init( d->xmlguiclient );
+ if (::KoMacro::Manager::self() == 0) {
+ ::KoMacro::Manager::init( d->xmlguiclient );
+ }
+ d->testobject = new TestObject( this );
+ ::KoMacro::Manager::self()->publishObject("TestObject", d->testobject);
+
+ d->testaction = new TestAction();
+ ::KoMacro::Manager::self()->publishAction(d->testaction);
+
+ d->doomdocument = new QDomDocument();
+
+ QString const xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\" >"
+ "<item action=\"testaction\" >"
+ "</item>"
+ "</macro>");
+
+ d->doomdocument->setContent(xml);
+}
+
+void MacroTests::tearDown()
+{
+ delete d->doomdocument;
+ delete d->testobject;
+ delete d->xmlguiclient;
+}
+
+void MacroTests::testMacro()
+{
+ kdDebug()<<"===================== testMacro() ======================" << endl;
+
+ QDomElement const domelement = d->doomdocument->documentElement();
+
+ KSharedPtr<KoMacro::Macro> macro1 = KoMacro::Manager::self()->createMacro("testMacro");
+ KSharedPtr<KoMacro::Macro> macro2 = KoMacro::Manager::self()->createMacro("testMacro");
+ //Is our XML parseable ?
+ KOMACROTEST_ASSERT(macro1->parseXML(domelement),true);
+ KOMACROTEST_ASSERT(macro2->parseXML(domelement),true);
+
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(macro1.data()), sizetype(0));
+ KOMACROTEST_XASSERT(sizetype(macro2.data()), sizetype(0));
+
+ //check macro1 == macro2
+ KOMACROTEST_ASSERT(macro1->name(), macro2->name() );
+
+ //create list of KsharedPtr from the childs of the macro
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items1 = macro1->items();
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items2 = macro2->items();
+
+ //check that there is one
+ KOMACROTEST_XASSERT( items1.count(), sizetype(0) );
+ KOMACROTEST_XASSERT( items2.count(), sizetype(0) );
+
+ //check macro1 == macro2
+ KOMACROTEST_ASSERT( items1.count(), items2.count() );
+
+ //check the name
+ KOMACROTEST_ASSERT( QString(macro1->name()), QString("testMacro") );
+
+ {
+ const QString tmp1 = QString("test");
+ macro1->setName(tmp1);
+
+ //check the name changed
+ KOMACROTEST_XASSERT( QString(macro1->name()), QString("testMacro") );
+ //check the name
+ KOMACROTEST_ASSERT( QString(macro1->name()), QString("test") );
+ }
+
+ //fetch the first one
+ KSharedPtr<KoMacro::Action> actionptr = items1[0]->action();
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(actionptr.data()), sizetype(0));
+ //check that it has the right name
+ KOMACROTEST_ASSERT( QString(actionptr->name()), QString("testaction") );
+ //check that it has the right text
+ KOMACROTEST_ASSERT( actionptr->text(), QString("Test") );
+
+ //try to clear items
+ macro1->clearItems();
+ //get items
+ items1 = macro1->items();
+ //check that they are deleted
+ KOMACROTEST_ASSERT( items1.count(), sizetype(0) );
+}
+#include "macrotests.moc"
diff --git a/kexi/plugins/macros/tests/macrotests.h b/kexi/plugins/macros/tests/macrotests.h
new file mode 100644
index 000000000..ed8d0f218
--- /dev/null
+++ b/kexi/plugins/macros/tests/macrotests.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_MACROTESTS_H
+#define KOMACROTEST_MACROTESTS_H
+
+#include <kunittest/tester.h>
+
+namespace KoMacroTest {
+
+ /**
+ * The common testsuite used to test common @a KoMacro
+ * functionality.
+ */
+ class MacroTests : public KUnitTest::SlotTester
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ MacroTests();
+
+ /**
+ * Destructor.
+ */
+ virtual ~MacroTests();
+
+ public slots:
+
+ /**
+ * This slot got called by KUnitTest before testing
+ * starts.
+ */
+ void setUp();
+
+ /**
+ * This slot got called by KUnitTest after all tests
+ * are done.
+ */
+ void tearDown();
+
+ /**
+ * Test the @a KoMacro::Action functionality.
+ */
+ void testMacro();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/testaction.cpp b/kexi/plugins/macros/tests/testaction.cpp
new file mode 100644
index 000000000..4063aa1bb
--- /dev/null
+++ b/kexi/plugins/macros/tests/testaction.cpp
@@ -0,0 +1,61 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "testaction.h"
+
+#include "../lib/action.h"
+#include "../lib/context.h"
+#include "../lib/macroitem.h"
+#include "../lib/variable.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+
+using namespace KoMacroTest;
+
+TestAction::TestAction()
+ : KoMacro::Action("testaction", "Test")
+{
+ setVariable("teststring", "Stringtest", QString("testString"));
+ setVariable("testint", "Inttest", int(0));
+ setVariable("testdouble", "Doubletest", double(0.5));
+ setVariable("testbool", "Booltest", QVariant(true,0));
+}
+
+TestAction::~TestAction()
+{
+}
+
+bool TestAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name)
+{
+ Q_UNUSED(macroitem);
+ Q_UNUSED(name);
+ return true;
+}
+
+void TestAction::activate(KSharedPtr<KoMacro::Context> context)
+{
+ kdDebug() << "TestAction::activate(KSharedPtr<Context>)" << endl;
+ const QString teststring = context->variable("teststring")->variant().toString();
+ const int testint = context->variable("testint")->variant().toInt();
+ const bool testbool = context->variable("testbool")->variant().toBool();
+}
+
+#include "testaction.moc"
diff --git a/kexi/plugins/macros/tests/testaction.h b/kexi/plugins/macros/tests/testaction.h
new file mode 100644
index 000000000..9eebff3c8
--- /dev/null
+++ b/kexi/plugins/macros/tests/testaction.h
@@ -0,0 +1,78 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_TESTACTION_H
+#define KOMACROTEST_TESTACTION_H
+
+#include <ksharedptr.h>
+
+#include "../lib/action.h"
+
+namespace KoMacro {
+ class Context;
+ class MacroItem;
+}
+
+namespace KoMacroTest {
+
+ /**
+ * This TestAction implements a @a KoMacro::Action to
+ * test the functionality provided by this class.
+ */
+ class TestAction : public KoMacro::Action
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ TestAction();
+
+ /**
+ * Destructor.
+ */
+ virtual ~TestAction();
+
+ /**
+ * This function is called, when the @a KoMacro::Variable
+ * with name @p name used within the @a KoMacro::MacroItem
+ * @p macroitem got changed.
+ *
+ * @param macroitem The @a KoMacro::MacroItem instance where
+ * the variable defined with @p name is located in.
+ * @param name The name the @a KoMacro::Variable has.
+ * @return true if the update was successfully else false
+ * is returned.
+ */
+ virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name);
+
+ public slots:
+
+ /**
+ * Called if the @a Action should be executed within the
+ * defined @p context .
+ */
+ virtual void activate(KSharedPtr<KoMacro::Context> context);
+
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/testobject.cpp b/kexi/plugins/macros/tests/testobject.cpp
new file mode 100644
index 000000000..39cadb7ac
--- /dev/null
+++ b/kexi/plugins/macros/tests/testobject.cpp
@@ -0,0 +1,117 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "testobject.h"
+
+//#include "../lib/manager.h"
+//#include "../lib/action.h"
+//#include "../lib/function.h"
+//#include "../lib/macro.h"
+//#include "../lib/metaobject.h"
+
+//#include <qstringlist.h>
+//#include <qdom.h>
+
+#include <kdebug.h>
+//#include <kxmlguiclient.h>
+
+using namespace KoMacroTest;
+
+namespace KoMacroTest {
+
+ /**
+ * @internal d-pointer class to be more flexible on future extension of the
+ * functionality without to much risk to break the binary compatibility.
+ */
+ class TestObject::Private
+ {
+ public:
+
+ /**
+ * The @a KUnitTest::Tester instance that likes to test
+ * our TestObject instance.
+ */
+ KUnitTest::Tester* const tester;
+ Private(KUnitTest::Tester* const tester)
+ : tester(tester)
+ {
+ }
+ };
+
+}
+
+TestObject::TestObject(KUnitTest::Tester* const tester)
+ : QObject()
+ , d( new Private(tester) ) // create the private d-pointer instance.
+{
+ setName("TestObject");
+}
+
+TestObject::~TestObject()
+{
+ delete d;
+}
+
+//testObject without arguments
+void TestObject::myslot()
+{
+ QString s = "CALLED => TestObject::myslot()";
+ //be loud
+ kdDebug() << s << endl;
+ //add some extra Debuginfos to TestResults see tester.h
+ d->tester->results()->addDebugInfo(s);
+}
+
+//testobject with QString and int argument
+//int is returnvalue
+int TestObject::myslot(const QString&, int i)
+{
+ QString s = "CALLED => TestObject::myslot(const QString&, int)";
+ //be loud
+ kdDebug() << s << endl;
+ //add some extra debuginfos to TestResults
+ d->tester->results()->addDebugInfo(s);
+ return i;
+}
+
+//testobject with QString argument
+//QString is returnvalue
+QString TestObject::myslot(const QString& s)
+{
+ QString t = QString("CALLED => TestObject::myslot(const QString& s) s=%1").arg(s);
+ //be loud
+ kdDebug() << t << endl;
+ //add some extra Debuginfos to TestResults
+ d->tester->results()->addDebugInfo(t);
+ return s;
+}
+
+//testobject with QString and double argument
+//double is returnvalue
+double TestObject::myslot(const QString&, double d)
+{
+ QString s = "CALLED => TestObject::myslot(const QString&, double)";
+ //be loud
+ kdDebug() << s << endl;
+ //add some extra Debuginfos to TestResults
+ this->d->tester->results()->addDebugInfo(s);
+ return d;
+}
+
+#include "testobject.moc"
diff --git a/kexi/plugins/macros/tests/testobject.h b/kexi/plugins/macros/tests/testobject.h
new file mode 100644
index 000000000..da5e8ae2f
--- /dev/null
+++ b/kexi/plugins/macros/tests/testobject.h
@@ -0,0 +1,85 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_TESTOBJECT_H
+#define KOMACROTEST_TESTOBJECT_H
+
+#include <qobject.h>
+#include <kunittest/tester.h>
+
+namespace KoMacroTest {
+
+ /**
+ * The TestObject class is used to test handling and communication
+ * of external from QObject inheritated classes.
+ */
+ class TestObject : public QObject
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ *
+ * @param tester The @a KUnitTest::Tester instance
+ * that likes to test our TestObject instance.
+ */
+ TestObject(KUnitTest::Tester* const tester);
+
+ /**
+ * Destructor.
+ */
+ virtual ~TestObject();
+
+ public slots:
+
+ /**
+ * This slot got published to the KoMacro-framework
+ * and will be called to test the functionality.
+ */
+ void myslot();
+
+ /**
+ * This slot got published to the KoMacro-framework
+ * and will be called to test the functionality.
+ */
+ int myslot(const QString&, int);
+
+ /**
+ * This slot got published to the KoMacro-framework
+ * and will be called to test the functionality.
+ */
+ QString myslot(const QString&);
+
+ /**
+ * This slot got published to the KoMacro-framework
+ * and will be called to test the functionality.
+ * @return is @param d
+ */
+ double myslot(const QString&, double d);
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/variabletests.cpp b/kexi/plugins/macros/tests/variabletests.cpp
new file mode 100644
index 000000000..8bc8d9c75
--- /dev/null
+++ b/kexi/plugins/macros/tests/variabletests.cpp
@@ -0,0 +1,236 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "variabletests.h"
+#include "testobject.h"
+#include "testaction.h"
+#include "komacrotestbase.h"
+
+#include "../lib/action.h"
+#include "../lib/function.h"
+#include "../lib/manager.h"
+#include "../lib/macro.h"
+#include "../lib/variable.h"
+#include "../lib/metaobject.h"
+#include "../lib/metamethod.h"
+#include "../lib/metaparameter.h"
+#include "../lib/exception.h"
+#include "../lib/macroitem.h"
+
+#include <ostream>
+
+#include <qstringlist.h>
+#include <qdom.h>
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kxmlguiclient.h>
+
+using namespace KUnitTest;
+using namespace KoMacroTest;
+
+namespace KoMacroTest {
+
+ /**
+ * Register KoMacroTest::CommonTests as TestSuite.
+ */
+
+ KUNITTEST_SUITE("KoMacroTestSuite");
+ KUNITTEST_REGISTER_TESTER(VariableTests);
+
+
+ class VariableTests::Private
+ {
+ public:
+ /**
+ * An KXMLGUIClient instance created on @a setUp() and
+ * passed to the @a KoMacro::Manager to bridge to the
+ * app-functionality.
+ */
+ KXMLGUIClient* xmlguiclient;
+
+ /**
+ * An @a TestObject instance used internaly to test
+ * handling and communication with from QObject
+ * inheritated instances.
+ */
+ TestAction* testaction;
+
+ QDomDocument* doomdocument;
+
+ KSharedPtr<KoMacro::Macro> macro;
+
+ Private()
+ : xmlguiclient(0)
+ , testaction(0)
+ , doomdocument(0)
+ , macro(0)
+ {
+ }
+ };
+}
+
+typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype;
+
+/******************************************************************************
+* This is an xtra big TODO:
+* - get rid of all double declarations
+* - create xtra-class for Variable/Macroitem tests
+* - add comments
+******************************************************************************/
+VariableTests::VariableTests()
+ : KUnitTest::SlotTester()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+}
+
+VariableTests::~VariableTests()
+{
+ delete d->xmlguiclient;
+ delete d;
+}
+
+
+void VariableTests::setUp()
+{
+ d->xmlguiclient = new KXMLGUIClient();
+
+ if (::KoMacro::Manager::self() == 0) {
+ ::KoMacro::Manager::init( d->xmlguiclient );
+ }
+
+ d->testaction = new TestAction();
+ ::KoMacro::Manager::self()->publishAction(d->testaction);
+
+ d->doomdocument = new QDomDocument();
+
+ QString const xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\" >"
+ "<item action=\"testaction\" >"
+ "</item>"
+ "</macro>");
+
+ d->doomdocument->setContent(xml);
+ d->macro = KoMacro::Manager::self()->createMacro("testMacro");
+ d->macro->parseXML(d->doomdocument->documentElement());
+ d->macro->execute(this);
+}
+
+void VariableTests::tearDown()
+{
+ delete d->macro;
+ delete d->doomdocument;
+ delete d->xmlguiclient;
+}
+
+void VariableTests::testMacro()
+{
+ kdDebug()<<"===================== testVariable() ===================" << endl;
+ kdDebug()<<"===================== testMacro() ======================" << endl;
+
+ //fetch Items and ..
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+
+ //... check that there is one
+ KOMACROTEST_XASSERT( items.count(), sizetype(0) );
+}
+
+void VariableTests::testVariableString() {
+ kdDebug()<<"===================== testVariableString() ======================" << endl;
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+ KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+
+ //fetch the "teststring"-variable
+ KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable(TESTSTRING);
+ //So there is a variable, does hasVariable() work ?
+ KOMACROTEST_ASSERT(actionptr->hasVariable(TESTSTRING),true);
+ //check count of variables
+ KOMACROTEST_ASSERT(sizetype(actionptr->variableNames().count()),sizetype(4));
+ //remove one
+ actionptr->removeVariable(TESTSTRING);
+ //Decreased ??
+ KOMACROTEST_ASSERT(sizetype(actionptr->variableNames().count()),sizetype(3));
+ //add one
+ actionptr->setVariable(variableptr);
+ //increased ??
+ KOMACROTEST_ASSERT(sizetype(actionptr->variableNames().count()),sizetype(4));
+
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ //check that it is "testString"
+ KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("testString"));
+
+ actionptr->setVariable("teststring", "STRINGTEST", "TestString");
+ variableptr = actionptr->variable("teststring");
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ //check that it is " "
+ KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("TestString"));
+}
+
+void VariableTests::testVariableInt() {
+ kdDebug()<<"===================== testVariableInt() ======================" << endl;
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+ KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+
+ //fetch the "testint"-variable
+ KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable(TESTINT);
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ //check that it is 0
+ KOMACROTEST_ASSERT(variableptr->variant().toInt(),int(0));
+
+ actionptr->setVariable(TESTINT,"INTTEST",INT_MAX);
+ variableptr = actionptr->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX));
+
+ actionptr->setVariable(TESTINT,"INTTEST",INT_MAX+1);
+ variableptr = actionptr->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX+1));
+
+ actionptr->setVariable(TESTINT,"INTTEST",INT_MIN);
+ variableptr = actionptr->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MIN));
+
+ actionptr->setVariable(TESTINT,"INTTEST",INT_MIN-1);
+ variableptr = actionptr->variable("testint");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MIN-1));
+}
+
+void VariableTests::testVariableBool() {
+ kdDebug()<<"===================== testVariableBool() ======================" << endl;
+ QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items();
+ KSharedPtr<KoMacro::Action> actionptr = items[0]->action();
+
+ //fetch the "testbool"-variable
+ KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable(TESTBOOL);
+ //check that it is not null
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ //check that it is " "
+ KOMACROTEST_ASSERT(variableptr->variant().toBool(),true);
+
+ actionptr->setVariable("testbool","BOOLTEST", "false");
+ variableptr = actionptr->variable("testbool");
+ KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0));
+ KOMACROTEST_ASSERT(variableptr->variant().toBool(),false);
+}
+#include "variabletests.moc"
diff --git a/kexi/plugins/macros/tests/variabletests.h b/kexi/plugins/macros/tests/variabletests.h
new file mode 100644
index 000000000..5bc7f1443
--- /dev/null
+++ b/kexi/plugins/macros/tests/variabletests.h
@@ -0,0 +1,87 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org)
+ * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_VARIABLETESTS_H
+#define KOMACROTEST_VARIABLETESTS_H
+
+#include <kunittest/tester.h>
+
+namespace KoMacroTest {
+
+ /**
+ * The common testsuite used to test common @a KoMacro
+ * functionality.
+ */
+ class VariableTests : public KUnitTest::SlotTester
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ VariableTests();
+
+ /**
+ * Destructor.
+ */
+ virtual ~VariableTests();
+
+ public slots:
+
+ /**
+ * This slot got called by KUnitTest before testing
+ * starts.
+ */
+ void setUp();
+
+ /**
+ * This slot got called by KUnitTest after all tests
+ * are done.
+ */
+ void tearDown();
+
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testMacro();
+
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testVariableString();
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testVariableInt();
+ /**
+ * Subtest for the @a KoMacro::Action functionality.
+ */
+ void testVariableBool();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+ };
+
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/xmlhandlertests.cpp b/kexi/plugins/macros/tests/xmlhandlertests.cpp
new file mode 100644
index 000000000..9a0ebcb1d
--- /dev/null
+++ b/kexi/plugins/macros/tests/xmlhandlertests.cpp
@@ -0,0 +1,619 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "xmlhandlertests.h"
+#include "testaction.h"
+#include "komacrotestbase.h"
+
+#include "../lib/action.h"
+#include "../lib/manager.h"
+#include "../lib/macro.h"
+#include "../lib/variable.h"
+#include "../lib/macroitem.h"
+
+#include <ostream>
+#include <cfloat>
+
+#include <qdom.h>
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kxmlguiclient.h>
+
+using namespace KUnitTest;
+using namespace KoMacroTest;
+
+namespace KoMacroTest {
+
+ /**
+ * Register KoMacroTest::CommonTests as TestSuite.
+ */
+ KUNITTEST_SUITE("KoMacroTestSuite")
+ KUNITTEST_REGISTER_TESTER(XMLHandlerTests);
+
+ class XMLHandlerTests::Private
+ {
+ public:
+ /**
+ * An KXMLGUIClient instance created on @a setUp() and
+ * passed to the @a KoMacro::Manager to bridge to the
+ * app-functionality.
+ */
+ KXMLGUIClient* xmlguiclient;
+
+ /**
+ * An @a TestObject instance used internaly to test
+ * handling and communication with from QObject
+ * inheritated instances.
+ */
+ KSharedPtr<KoMacro::Action> testaction;
+
+ Private()
+ : xmlguiclient(0)
+ , testaction(0)
+ {
+ }
+ };
+}
+
+XMLHandlerTests::XMLHandlerTests()
+ : KUnitTest::SlotTester()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+}
+
+XMLHandlerTests::~XMLHandlerTests()
+{
+ delete d->xmlguiclient;
+ delete d;
+}
+
+
+void XMLHandlerTests::setUp()
+{
+ d->xmlguiclient = new KXMLGUIClient();
+
+ //Singelton more or less ...
+ if (::KoMacro::Manager::self() == 0) {
+ ::KoMacro::Manager::init( d->xmlguiclient );
+ }
+
+ d->testaction = new TestAction();
+ ::KoMacro::Manager::self()->publishAction(d->testaction);
+}
+
+void XMLHandlerTests::tearDown()
+{
+ delete d->xmlguiclient;
+}
+
+/**
+* Test the @a KoMacro::XMLHandler parseXML() and toXML()-function.
+*/
+void XMLHandlerTests::testParseAndToXML()
+{
+ kdDebug()<<"===================== testParseAndToXML() ======================" << endl;
+
+ // 1.Test - Correct DomElement.
+ testCorrectDomElement();
+ // 2.Test - XML-document with bad root element.
+ testBadRoot();
+ // 3.Test - XML-document with a missing Variable.
+ testMissingVariable();
+ // 4.Test - One more Variable in XML-Document.
+ testMoreVariables();
+ // 5.Test - XML-document with wrong macro-xmlversion.
+ testWrongVersion();
+ // 6.Test - XML-document if it has a wrong structure like wrong parathesis
+ // or missing end tag.
+ testWrongXMLStruct();
+ // 7.Test-XML-document with maximum field-size.
+ testMaxNum();
+ // 8.Test-XML-document with maximum+1 field-size.
+ testMaxNum2();
+ // 9.Test-XML-document with minimum field-size.
+ testMinNum();
+ // 10.Test-XML-document with minimum-1 field-size.
+ testMinNum2();
+ // 11.Test - With a to big number.
+ testBigNumber();
+ // 12.Test - With two MacroItems.
+ testTwoMacroItems();
+}
+
+/***************************************************************************
+* Begin of Sub-methos of testParseXML().
+***************************************************************************/
+// 1.Test - Correct DomElement.
+void XMLHandlerTests::testCorrectDomElement()
+{
+ // Local Init
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ // Set the XML-document with the above string.
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ // Is our XML parseable by calling parseXML()?
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ // Is the parsen content in the Macro correct ?
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testbool"] = true;
+ isvariableok["testdouble"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ // Transform back by calling toXML().
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+
+ // Test the Compare-method when a Variable will change, it must fail.
+ macro->items().first()->variable("teststring")->setVariant("bla");
+ isvariableok.replace("teststring",false);
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+}
+
+// 2.Test - XML-document with bad root element.
+void XMLHandlerTests::testBadRoot()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<maro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</maro>");
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_XASSERT(macro->parseXML(elem),true);
+
+ //no assertMacroContentEqToXML(), because parsing failed.
+ assertMacroContentEqToXML(macro,elem,true,false,QMap<QString,bool>());
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,true,false,QMap<QString,bool>());
+}
+
+// 3.Test - XML-document with a missing Variable.
+void XMLHandlerTests::testMissingVariable()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testdouble"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+}
+
+// 4.Test - One more Variable in XML-Document.
+void XMLHandlerTests::testMoreVariables()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "<variable name=\"testbla\" >somethingwrong</variable>"
+ "</item>"
+ "</macro>");
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testbool"] = true;
+ isvariableok["testdouble"] = true;
+ isvariableok["testbla"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+}
+
+// 5.Test - XML-document with wrong macro-xmlversion.
+void XMLHandlerTests::testWrongVersion()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"2\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_XASSERT(macro->parseXML(elem),true);
+
+ //no assertMacroContentEqToXML(), because parsing failed.
+ assertMacroContentEqToXML(macro,elem,true,false,QMap<QString,bool>());
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,true,false,QMap<QString,bool>());
+}
+
+// 6.Test - XML-document if it has a wrong structure like wrong parathesis
+// or missing end tag.
+void XMLHandlerTests::testWrongXMLStruct()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "macro xmlversion=\"1\">>"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "</item>"
+ "</macro>");
+ KOMACROTEST_XASSERT(doomdocument.setContent(xml),true);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_XASSERT(macro->parseXML(elem),true);
+
+ //no assertMacroContentEqToXML(), because parsing failed.
+ assertMacroContentEqToXML(macro,elem,true,false,QMap<QString,bool>());
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,true,false,QMap<QString,bool>());
+}
+
+// 7.Test-XML-document with maximum field-size.
+void XMLHandlerTests::testMaxNum()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > %1 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %2 </variable>"
+ "</item>"
+ "</macro>").arg(INT_MAX).arg(DBL_MAX);
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testbool"] = true;
+ isvariableok["testdouble"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+}
+
+// 8.Test-XML-document with maximum+1 field-size.
+void XMLHandlerTests::testMaxNum2()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > %1 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %2 </variable>"
+ "</item>"
+ "</macro>").arg(INT_MAX+1).arg(DBL_MAX+1);
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testbool"] = true;
+ isvariableok["testdouble"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+}
+
+// 9.Test-XML-document with minimum field-size.
+void XMLHandlerTests::testMinNum()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > %1 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %2 </variable>"
+ "</item>"
+ "</macro>").arg(INT_MIN).arg(DBL_MIN);
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testbool"] = true;
+ isvariableok["testdouble"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+}
+
+// 10.Test-XML-document with minimum+1 field-size.
+void XMLHandlerTests::testMinNum2()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > %1 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %2 </variable>"
+ "</item>"
+ "</macro>").arg(INT_MIN-1).arg(DBL_MIN-1);
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testbool"] = true;
+ isvariableok["testdouble"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+}
+
+// 11.Test - With a to big number.
+void XMLHandlerTests::testBigNumber()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > 0123456789012345678901234567890123456789 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %1 </variable>"
+ "</item>"
+ "</macro>").arg(DBL_MAX+1);
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testbool"] = true;
+ isvariableok["testdouble"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+}
+
+// 12.Test - With two MacroItems.
+void XMLHandlerTests::testTwoMacroItems()
+{
+ KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro");
+ QDomDocument doomdocument;
+
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "<variable name=\"testbla\" >somethingwrong</variable>"
+ "</item>"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >testBBstring2</variable>"
+ "<variable name=\"testint\" >4</variable>"
+ "<variable name=\"testbool\" >false</variable>"
+ "<variable name=\"testdouble\" >0.7</variable>"
+ "<variable name=\"testbla\" >somethingwrong2</variable>"
+ "</item>"
+ "</macro>");
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+ KOMACROTEST_ASSERT(macro->parseXML(elem),true);
+
+ QMap<QString,bool> isvariableok;
+ isvariableok["teststring"] = true;
+ isvariableok["testint"] = true;
+ isvariableok["testbool"] = true;
+ isvariableok["testdouble"] = true;
+ assertMacroContentEqToXML(macro,elem,false,true,isvariableok);
+
+ const QDomElement elem2 = macro->toXML();
+ assertMacroContentEqToXML(macro,elem2,false,true,isvariableok);
+}
+/***************************************************************************
+* End of Sub-methos of testParseAndToXML().
+***************************************************************************/
+
+/**
+* Compares a XML-Element with a Macro. Call sub-asserts.
+* @p macro The parsen @a Macro.
+* @p elem The given @a QDomElement which is parsen.
+* @p isitemsempty Bool for expectation of an empty @a MacroItem -List.
+* @p isactionset Bool for expectation that the @a Action -names are equal.
+* @p isvariableok QMap of Bools for comparing each @a Variable .
+*/
+void XMLHandlerTests::assertMacroContentEqToXML(const KSharedPtr<KoMacro::Macro> macro,
+ const QDomElement& elem,
+ const bool isitemsempty,
+ const bool isactionset,
+ const QMap<QString, bool> isvariableok)
+{
+ // Make an Iterator over the MacroItems of the Macro.
+ const QValueList<KSharedPtr<KoMacro::MacroItem > > macroitems = macro->items();
+ QValueList<KSharedPtr<KoMacro::MacroItem > >::ConstIterator
+ mit(macroitems.constBegin()), end(macroitems.constEnd());
+
+ //1.comparison - Is the MacroItem-list empty?
+ {
+ if( isitemsempty ) {
+ KOMACROTEST_XASSERT(macroitems.empty(),false);
+ kdDebug() << "There is no correct MacroItem parsen." << endl;
+ return;
+ }
+ else {
+ KOMACROTEST_ASSERT(macroitems.empty(),false);
+ }
+ }
+
+ // Got to the first item-elements of the elem (there is only one in the tests).
+ QDomNode itemnode = elem.firstChild();
+
+ // Iterate over the MacroItems and item-elements.
+ while(mit != end && ! itemnode.isNull()) {
+ const KSharedPtr<KoMacro::MacroItem> macroitem = *mit;
+ const QDomElement itemelem = itemnode.toElement();
+
+ //2.comparison - Is the Action-name equal?
+ {
+ if( ! isactionset) {
+ KOMACROTEST_XASSERT(macroitem->action()->name() == itemelem.attribute("action"),true);
+ kdDebug() << "Action-name not equal: "
+ << macroitem->action()->name()
+ << " != " << itemelem.attribute("action") << endl;
+ return;
+ }
+ else {
+ KOMACROTEST_ASSERT(macroitem->action()->name() == itemelem.attribute("action"),true);
+ }
+ }
+
+ // Go down to MacroItem->Variable and item->variable and compare them.
+ QMap<QString, KSharedPtr<KoMacro::Variable > > mvariables = macroitem->variables();
+ QDomNode varnode = itemelem.firstChild();
+
+ while ( ! varnode.isNull()) {
+ const QDomElement varelem = varnode.toElement();
+ const KSharedPtr<KoMacro::Variable> varitem = mvariables.find(varelem.attribute("name")).data();
+
+ //3.comparison - Is the content of the Variable
+ // in the MacroItem and and item equal?
+ {
+ const bool var = *isvariableok.find(varelem.attribute("name"));
+ if( ! var ) {
+ KOMACROTEST_XASSERT(varitem->variant() == QVariant(varelem.text()), !var);
+ kdDebug() << "The content of the Variable: " << varitem->name()
+ << " is not equal." << varitem->variant()
+ << "!=" << varelem.text() << endl;
+ }
+ else {
+ KOMACROTEST_ASSERT(varitem->variant() == QVariant(varelem.text()), var);
+ }
+
+ }
+
+ // Erase the MacroItem from the map, because it is parsen correctly.
+ mvariables.erase(varitem->name());
+ // Go to next Variable in node-tree.
+ varnode = varnode.nextSibling();
+ }
+
+ //4.comparison - Is every MacroItem parsen?
+ {
+ KOMACROTEST_ASSERT(mvariables.empty(),true);
+ kdDebug() << "There are non-filled variable in the MacroItem: " << mvariables.count() <<endl;
+ }
+
+ // Go to next MacroItem and next item-element.
+ mit++;
+ itemnode = itemnode.nextSibling();
+ }
+}
+
+// Prints a QMap of Variables to kdDebug().
+void XMLHandlerTests::printMvariables(const QMap<QString, KSharedPtr<KoMacro::Variable > > mvariables, const QString s)
+{
+ //QValueList<QString>::ConstIterator kit (keys.constBegin()), end(keys.constEnd());
+ QMap<QString, KSharedPtr<KoMacro::Variable > >::ConstIterator mvit (mvariables.constBegin()), end(mvariables.constEnd());
+ while(mvit != end){
+ const KoMacro::Variable * v = *mvit;
+ kdDebug() << s << ": " << v->name() << endl;
+ mvit++;
+ }
+}
+
+#include "xmlhandlertests.moc"
diff --git a/kexi/plugins/macros/tests/xmlhandlertests.h b/kexi/plugins/macros/tests/xmlhandlertests.h
new file mode 100644
index 000000000..c78a8c79d
--- /dev/null
+++ b/kexi/plugins/macros/tests/xmlhandlertests.h
@@ -0,0 +1,122 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_XMLHandlerTests_H
+#define KOMACROTEST_XMLHandlerTests_H
+
+#include <kunittest/tester.h>
+#include "../lib/macro.h"
+
+namespace KoMacroTest {
+
+ /**
+ * The common testsuite used to test common @a KoMacro
+ * functionality.
+ */
+ class XMLHandlerTests : public KUnitTest::SlotTester
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ XMLHandlerTests();
+
+ /**
+ * Destructor.
+ */
+ virtual ~XMLHandlerTests();
+
+ public slots:
+
+ /**
+ * This slot got called by KUnitTest before testing
+ * starts.
+ */
+ void setUp();
+
+ /**
+ * This slot got called by KUnitTest after all tests
+ * are done.
+ */
+ void tearDown();
+
+ /**
+ * Test the @a KoMacro::XMLHandler parseXML()-function.
+ */
+ void testParseAndToXML();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+
+ /**
+ * Compares a XML-Element with a Macro. Call sub-asserts.
+ * @p macro The parsen @a Macro.
+ * @p domelement The given @a QDomElement which is parsen.
+ * @p isitemsempty Bool for expectation of an empty @a MacroItem -List.
+ * @p isactionset Bool for expectation that the @a Action -names are equal.
+ * @p isvariableok QMap of Bools for comparing each @a Variable .
+ */
+ void assertMacroContentEqToXML(const KSharedPtr<KoMacro::Macro> macro,
+ const QDomElement& elem,
+ const bool isitemsempty,
+ const bool isactionset,
+ const QMap<QString, bool> isvariableok);
+
+ // Prints a QMap of Variables to kdDebug().
+ void printMvariables(const QMap<QString, KSharedPtr<KoMacro::Variable > > mvariables, const QString s);
+
+ /**
+ * Sub-methods of testParseXML() and testToXML().
+ * Test the correct parsing of a @a QDomElement into a @a Macro
+ * respectively expected failure of parsing. Then transform it
+ * back and compare it.
+ */
+ // 1.Test - Correct DomElement.
+ void testCorrectDomElement();
+ // 2.Test - XML-document with bad root element.
+ void testBadRoot();
+ // 3.Test - XML-document with a missing Variable.
+ void testMissingVariable();
+ // 4.Test - One more Variable in XML-Document.
+ void testMoreVariables();
+ // 5.Test - XML-document with wrong macro-xmlversion.
+ void testWrongVersion();
+ // 6.Test - XML-document if it has a wrong structure like
+ // wrong parathesis or missing end tag.
+ void testWrongXMLStruct();
+ // 7.Test-XML-document with maximum field-size.
+ void testMaxNum();
+ // 8.Test-XML-document with maximum+1 field-size.
+ void testMaxNum2();
+ // 9.Test-XML-document with minimum field-size.
+ void testMinNum();
+ // 10.Test-XML-document with minimum-1 field-size.
+ void testMinNum2();
+ // 11.Test - With a to big number.
+ void testBigNumber();
+ // 12.Test - With two MacroItems.
+ void testTwoMacroItems();
+ };
+}
+
+#endif
diff --git a/kexi/plugins/macros/tests/xmlhandlertests2.cpp b/kexi/plugins/macros/tests/xmlhandlertests2.cpp
new file mode 100644
index 000000000..2234eaae1
--- /dev/null
+++ b/kexi/plugins/macros/tests/xmlhandlertests2.cpp
@@ -0,0 +1,1161 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "xmlhandlertests2.h"
+#include "testaction.h"
+#include "komacrotestbase.h"
+
+#include "../lib/action.h"
+#include "../lib/manager.h"
+#include "../lib/macro.h"
+#include "../lib/variable.h"
+#include "../lib/macroitem.h"
+
+#include <ostream>
+#include <cfloat>
+
+#include <qdom.h>
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kxmlguiclient.h>
+
+using namespace KUnitTest;
+using namespace KoMacroTest;
+
+namespace KoMacroTest {
+
+ /**
+ * Register KoMacroTest::CommonTests as TestSuite.
+ */
+ KUNITTEST_SUITE("KoMacroTestSuite")
+ KUNITTEST_REGISTER_TESTER(XMLHandlerTests2);
+
+ class XMLHandlerTests2::Private
+ {
+ public:
+ /**
+ * An KXMLGUIClient instance created on @a setUp() and
+ * passed to the @a KoMacro::Manager to bridge to the
+ * app-functionality.
+ */
+ KXMLGUIClient* xmlguiclient;
+
+ /**
+ * @a Macro instance as a container for the macroitems;
+ */
+ KSharedPtr<KoMacro::Macro> macro; // container for manually created items
+ KSharedPtr<KoMacro::Macro> macro2; // container for parsen items
+ KSharedPtr<KoMacro::Macro> macro3; // container for parsen items after back-converting by toXML() and again parseXML()
+
+ /**
+ * An @a TestObject instance used internaly to test
+ * handling and communication with from QObject
+ * inheritated instances.
+ */
+ KSharedPtr<KoMacro::Action> testaction;
+ KSharedPtr<KoMacro::Action> action2; // action of the parsen macro2
+ KSharedPtr<KoMacro::Action> action3; // action of the parsen macro3
+ KSharedPtr<KoMacro::Action> testaction_2; // for test12
+ KSharedPtr<KoMacro::Action> action2_2; // action of the parsen macro2, for test12
+ KSharedPtr<KoMacro::Action> action3_2; // action of the parsen macro3, for test12
+
+ /**
+ * Represents a @a QValuList of @a MacroItem which are parsen in the
+ * correspondig @a Macro .
+ */
+ QValueList<KSharedPtr<KoMacro::MacroItem > > macroitems2; // items of macro2
+ QValueList<KSharedPtr<KoMacro::MacroItem > > macroitems3; // items of macro3
+
+ /**
+ * @a MacroItem instances which ist fillen manually from the given XML
+ * and parsen by the @a XMLHandler over the XML.
+ */
+ KSharedPtr<KoMacro::MacroItem> macroitem; // created manually from XML
+ KSharedPtr<KoMacro::MacroItem> macroitem2; // parsen from XML in macro2
+ KSharedPtr<KoMacro::MacroItem> macroitem3; // parsen from XML in macro3
+ KSharedPtr<KoMacro::MacroItem> macroitem_2; // created manually from XML, for test12
+ KSharedPtr<KoMacro::MacroItem> macroitem2_2;// parsen from XML in macro2, for test12
+ KSharedPtr<KoMacro::MacroItem> macroitem3_2;// parsen from XML in macro3, for test12
+
+ Private()
+ : xmlguiclient(0)
+ , testaction(0)
+ {
+ }
+ };
+}
+
+XMLHandlerTests2::XMLHandlerTests2()
+ : KUnitTest::SlotTester()
+ , d( new Private() ) // create the private d-pointer instance.
+{
+}
+
+XMLHandlerTests2::~XMLHandlerTests2()
+{
+ delete d->xmlguiclient;
+ delete d;
+}
+
+
+void XMLHandlerTests2::setUp()
+{
+ d->xmlguiclient = new KXMLGUIClient();
+
+ //Singelton more or less ...
+ if (::KoMacro::Manager::self() == 0) {
+ ::KoMacro::Manager::init( d->xmlguiclient );
+ }
+
+ d->macro = KoMacro::Manager::self()->createMacro("testMacro");
+ d->macro2 = KoMacro::Manager::self()->createMacro("testMacro");
+ d->macro3 = KoMacro::Manager::self()->createMacro("testMacro");
+
+ d->testaction = new TestAction();
+ d->testaction_2 = new TestAction();
+ ::KoMacro::Manager::self()->publishAction(d->testaction);
+ ::KoMacro::Manager::self()->publishAction(d->testaction_2);
+}
+
+void XMLHandlerTests2::tearDown()
+{
+ delete d->xmlguiclient;
+}
+
+/**
+* Test the @a KoMacro::XMLHandler parseXML() and toXML()-function.
+*/
+void XMLHandlerTests2::testParseAndToXML()
+{
+ kdDebug()<<"===================== testParseAndToXML2() ======================" << endl;
+
+ // 1.Test - Correct DomElement.
+ testCorrectDomElement();
+ // 2.Test - XML-document with bad root element.
+ testBadRoot();
+ // 3.Test - XML-document with a missing Variable.
+ testMissingVariable();
+ // 4.Test - One more Variable in XML-Document.
+ testMoreVariables();
+ // 5.Test - XML-document with wrong macro-xmlversion.
+ testWrongVersion();
+ // 6.Test - XML-document if it has a wrong structure like wrong parathesis
+ // or missing end tag.
+ testWrongXMLStruct();
+ // 7.Test-XML-document with maximum field-size.
+ testMaxNum();
+ // 8.Test-XML-document with maximum+1 field-size.
+ testMaxNum2();
+ // 9.Test-XML-document with minimum field-size.
+ testMinNum();
+ // 10.Test-XML-document with minimum-1 field-size.
+ testMinNum2();
+ // 11.Test - With a to big number.
+ testBigNumber();
+ // 12.Test - With two MacroItems.
+ testTwoMacroItems();
+}
+
+
+/***************************************************************************
+* Begin of Sub-methos of testParseXML().
+***************************************************************************/
+// 1.Test - Correct DomElement.
+void XMLHandlerTests2::testCorrectDomElement()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1);
+
+ {
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *d->macroitems2.constBegin();
+ d->action2 = d->macroitem2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true);
+ }
+ }
+ // Change varint and the belonging Variable in the parsen macro2test
+ // and test it in the macro3 below
+ varint->setVariant(117);
+ d->macroitem2->variable("testint")->setVariant(117);
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1);
+
+ {
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *d->macroitems3.constBegin();
+ d->action3 = d->macroitem3->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")), true);
+ }
+ }
+}
+
+
+// 2.Test - XML-document with bad root element.
+void XMLHandlerTests2::testBadRoot()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<maro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</maro>");
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_XASSERT(d->macro2->parseXML(elem),true);
+}
+
+// 3.Test - XML-document with a missing Variable.
+void XMLHandlerTests2::testMissingVariable()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1);
+
+ {
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *d->macroitems2.constBegin();
+ d->action2 = d->macroitem2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)3);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true);
+ }
+ }
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1);
+
+ {
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *d->macroitems3.constBegin();
+ d->action3 = d->macroitem3->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)3);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")), true);
+ }
+ }
+}
+
+// 4.Test - One more Variable in XML-Document.
+void XMLHandlerTests2::testMoreVariables()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "<variable name=\"testbla\" >somethingwrong</variable>"
+ "</item>"
+ "</macro>");
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6));
+ KSharedPtr<KoMacro::Variable> varbla = d->macroitem->addVariable("testbla","somethingwrong");
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1);
+
+ {
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *d->macroitems2.constBegin();
+ d->action2 = d->macroitem2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)5);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbla, d->macroitem2->variable("testbla")), true);
+ }
+ }
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1);
+
+ {
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *d->macroitems3.constBegin();
+ d->action3 = d->macroitem3->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)5);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbla, d->macroitem3->variable("testbla")), true);
+ }
+ }
+}
+
+
+// 5.Test - XML-document with wrong macro-xmlversion.
+void XMLHandlerTests2::testWrongVersion()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<maro xmlversion=\"2\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</maro>");
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_XASSERT(d->macro2->parseXML(elem),true);
+}
+
+
+// 6.Test - XML-document if it has a wrong structure like wrong parathesis
+// or missing end tag.
+void XMLHandlerTests2::testWrongXMLStruct()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "maro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</maro>");
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_XASSERT(d->macro2->parseXML(elem),true);
+}
+
+// 7.Test-XML-document with maximum field-size.
+void XMLHandlerTests2::testMaxNum()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > %1 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %2 </variable>"
+ "</item>"
+ "</macro>").arg(INT_MAX).arg(DBL_MAX);
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(INT_MAX));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(DBL_MAX));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1);
+
+ {
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *d->macroitems2.constBegin();
+ d->action2 = d->macroitem2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true);
+// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true);
+ }
+ }
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1);
+
+ {
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *d->macroitems3.constBegin();
+ d->action3 = d->macroitem3->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true);
+// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true);
+ }
+ }
+}
+
+// 8.Test-XML-document with maximum+1 field-size.
+void XMLHandlerTests2::testMaxNum2()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > %1 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %2 </variable>"
+ "</item>"
+ "</macro>").arg(INT_MAX+1).arg(DBL_MAX+1);
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(INT_MAX+1));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(DBL_MAX+1));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1);
+
+ {
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *d->macroitems2.constBegin();
+ d->action2 = d->macroitem2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true);
+// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")),true);
+ }
+ }
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1);
+
+ {
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *d->macroitems3.constBegin();
+ d->action3 = d->macroitem3->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true);
+// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true);
+ }
+ }
+}
+
+// 9.Test-XML-document with minimum field-size.
+void XMLHandlerTests2::testMinNum()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > %1 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %2 </variable>"
+ "</item>"
+ "</macro>").arg(INT_MIN).arg(DBL_MIN);
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(INT_MIN));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(DBL_MIN));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1);
+
+ {
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *d->macroitems2.constBegin();
+ d->action2 = d->macroitem2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true);
+// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")),true);
+ }
+ }
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1);
+
+ {
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *d->macroitems3.constBegin();
+ d->action3 = d->macroitem3->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true);
+// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true);
+ }
+ }
+}
+
+// 10.Test-XML-document with minimum+1 field-size.
+void XMLHandlerTests2::testMinNum2()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" > %1 </variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" > %2 </variable>"
+ "</item>"
+ "</macro>").arg(INT_MIN-1).arg(DBL_MIN-1);
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(INT_MIN-1));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(DBL_MIN-1));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1);
+
+ {
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *d->macroitems2.constBegin();
+ d->action2 = d->macroitem2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true);
+// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")),true);
+ }
+ }
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1);
+
+ {
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *d->macroitems3.constBegin();
+ d->action3 = d->macroitem3->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true);
+// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true);
+ }
+ }
+}
+
+// 11.Test - With a to big number.
+void XMLHandlerTests2::testBigNumber()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0123456789012345678901234567890123456789</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "</item>"
+ "</macro>");
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+
+ d->macroitem->setAction(d->testaction);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ //TODO //KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0123456789012345678901234567890123456789));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6));
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1);
+
+ {
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *d->macroitems2.constBegin();
+ d->action2 = d->macroitem2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true);
+ //KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true);
+ }
+ }
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1);
+
+ {
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *d->macroitems3.constBegin();
+ d->action3 = d->macroitem3->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true);
+ //KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")), true);
+ }
+ }
+}
+
+// 12.Test - With two MacroItems.
+void XMLHandlerTests2::testTwoMacroItems()
+{
+ // Clear macroitems in the macros.
+ d->macro->clearItems();
+ d->macro2->clearItems();
+ d->macro3->clearItems();
+
+ // Part 1: From XML to a Macro.
+ // Test-XML-document with normal allocated variables.
+ const QString xml = QString("<!DOCTYPE macros>"
+ "<macro xmlversion=\"1\">"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >test_string</variable>"
+ "<variable name=\"testint\" >0</variable>"
+ "<variable name=\"testbool\" >true</variable>"
+ "<variable name=\"testdouble\" >0.6</variable>"
+ "<variable name=\"testbla\" >somethingwrong</variable>"
+ "</item>"
+ "<item action=\"testaction\" >"
+ "<variable name=\"teststring\" >testString2</variable>"
+ "<variable name=\"testint\" >4</variable>"
+ "<variable name=\"testbool\" >false</variable>"
+ "<variable name=\"testdouble\" >0.7</variable>"
+ "<variable name=\"testbla\" >somethingwrong2</variable>"
+ "</item>"
+ "</macro>");
+ // Set the XML-document with the above string.
+ QDomDocument doomdocument;
+ doomdocument.setContent(xml);
+ const QDomElement elem = doomdocument.documentElement();
+
+ // Create a MacroItem with the TestAction for macro2 and add it to macro.
+ d->macroitem = new KoMacro::MacroItem();
+ d->macroitem_2 = new KoMacro::MacroItem();
+ d->macro->addItem(d->macroitem);
+ d->macro->addItem(d->macroitem_2);
+
+ d->macroitem->setAction(d->testaction);
+ d->macroitem_2->setAction(d->testaction_2);
+
+ // Push the Variables into the macroitem.
+ KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string"));
+ KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0));
+ KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true));
+ KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6));
+ KSharedPtr<KoMacro::Variable> varbla = d->macroitem->addVariable("testbla","somethingwrong");
+
+ // Push the Variables into the macroitem4.
+ KSharedPtr<KoMacro::Variable> varstring_2 = d->macroitem_2->addVariable("teststring",QVariant("testString2"));
+ KSharedPtr<KoMacro::Variable> varint_2 = d->macroitem_2->addVariable("testint",QVariant(4));
+ KSharedPtr<KoMacro::Variable> varbool_2 = d->macroitem_2->addVariable("testbool",QVariant(false));
+ KSharedPtr<KoMacro::Variable> vardouble_2 = d->macroitem_2->addVariable("testdouble",QVariant(0.7));
+ KSharedPtr<KoMacro::Variable> varbla_2 = d->macroitem_2->addVariable("testbla","somethingwrong2");
+
+ // Is our XML parseable into a 2. Macro by calling parseXML()?
+ KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems2 = d->macro2->items();
+ // 1a.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)2);
+
+ {
+ QValueList<KSharedPtr<KoMacro::MacroItem > >::ConstIterator mit2(d->macroitems2.constBegin());
+ // 2a.comparison - Test if the Action is correct?
+ d->macroitem2 = *mit2;
+ mit2++;
+ d->macroitem2_2 = *mit2;
+ d->action2 = d->macroitem2->action();
+ d->action2_2 = d->macroitem2_2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true);
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction_2,d->action2_2),true);
+
+ // 3a.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)5);
+ KOMACROTEST_ASSERT(d->macroitem2_2->variables().size(),(sizetypemap)5);
+ {
+ // 4a.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")),true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")),true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbla, d->macroitem2->variable("testbla")),true);
+
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring_2,d->macroitem2_2->variable("teststring")),true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint_2, d->macroitem2_2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool_2, d->macroitem2_2->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble_2,d->macroitem2_2->variable("testdouble")),true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbla_2, d->macroitem2_2->variable("testbla")),true);
+ }
+ }
+
+ // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison.
+ const QDomElement elem2 = d->macro2->toXML();
+ KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true);
+
+ // Go down to the MacroItem of macro2.
+ d->macroitems3 = d->macro3->items();
+ // 1b.comparison - Test if the MacroItems have the correct number?
+ KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)2);
+
+ {
+ QValueList<KSharedPtr<KoMacro::MacroItem > >::ConstIterator mit3(d->macroitems3.constBegin());
+ // 2b.comparison - Test if the Action is correct?
+ d->macroitem3 = *mit3;
+ mit3++;
+ d->macroitem3_2 = *mit3;
+ d->action3 = d->macroitem3->action();
+ d->action3_2 = d->macroitem3_2->action();
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true);
+ KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3_2),true);
+
+ // 3b.comparison - Test if the Variables have the correct number?
+ KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)5);
+ KOMACROTEST_ASSERT(d->macroitem3_2->variables().size(),(sizetypemap)5);
+ {
+ // 4b.comparison - Test if the Variables are equal.
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")),true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbla, d->macroitem3->variable("testbla")),true);
+
+ KOMACROTEST_ASSERT(assertVariablesEqual(varstring_2,d->macroitem3_2->variable("teststring")),true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varint_2, d->macroitem3_2->variable("testint")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbool_2, d->macroitem3_2->variable("testbool")), true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(vardouble_2,d->macroitem3_2->variable("testdouble")),true);
+ KOMACROTEST_ASSERT(assertVariablesEqual(varbla_2, d->macroitem3_2->variable("testbla")),true);
+ }
+ }
+}
+
+/***************************************************************************
+* End of Sub-methos of testParseAndToXML2().
+***************************************************************************/
+
+bool XMLHandlerTests2::assertActionsEqual(KSharedPtr<KoMacro::Action> action,
+ KSharedPtr<KoMacro::Action> action2)
+{
+ return action->name() == action2->name();
+}
+
+bool XMLHandlerTests2::assertVariablesEqual(KSharedPtr<KoMacro::Variable> var,
+ KSharedPtr<KoMacro::Variable> var2)
+{
+ if ( var->variant() != var2->variant() ) kdDebug() << "Variable1: " << var->variant() << " and Variable2: " << var2->variant() << endl;
+ return var->variant() == var2->variant();
+}
+
+#include "xmlhandlertests2.moc"
diff --git a/kexi/plugins/macros/tests/xmlhandlertests2.h b/kexi/plugins/macros/tests/xmlhandlertests2.h
new file mode 100644
index 000000000..0a3fee3a7
--- /dev/null
+++ b/kexi/plugins/macros/tests/xmlhandlertests2.h
@@ -0,0 +1,132 @@
+/***************************************************************************
+ * This file is part of the KDE project
+ * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KOMACROTEST_XMLHandlerTests2_H
+#define KOMACROTEST_XMLHandlerTests2_H
+
+#include <kunittest/tester.h>
+#include "../lib/macro.h"
+
+namespace KoMacroTest {
+
+ /**
+ * The common testsuite used to test common @a KoMacro
+ * functionality.
+ */
+ class XMLHandlerTests2 : public KUnitTest::SlotTester
+ {
+ Q_OBJECT
+ public:
+
+ /**
+ * Constructor.
+ */
+ XMLHandlerTests2();
+
+ /**
+ * Destructor.
+ */
+ virtual ~XMLHandlerTests2();
+
+ public slots:
+
+ /**
+ * This slot got called by KUnitTest before testing
+ * starts.
+ */
+ void setUp();
+
+ /**
+ * This slot got called by KUnitTest after all tests
+ * are done.
+ */
+ void tearDown();
+
+ /**
+ * Test the @a KoMacro::XMLHandler parseXML()-function.
+ */
+ void testParseAndToXML();
+
+ private:
+ /// @internal d-pointer class.
+ class Private;
+ /// @internal d-pointer instance.
+ Private* const d;
+
+ typedef QMap<QString,KoMacro::Variable>::size_type sizetypemap;
+ typedef QValueList<KSharedPtr<KoMacro::MacroItem > >::size_type sizetypelist;
+
+ /**
+ * Compares a XML-Element with a Macro. Call sub-asserts.
+ * @p macro The parsen @a Macro.
+ * @p domelement The given @a QDomElement which is parsen.
+ * @p isitemsempty Bool for expectation of an empty @a MacroItem -List.
+ * @p isactionset Bool for expectation that the @a Action -names are equal.
+ * @p isvariableok QMap of Bools for comparing each @a Variable .
+ */
+/* void assertMacroContentEqToXML(const KSharedPtr<KoMacro::Macro> macro,
+ const QDomElement& elem,
+ const bool isitemsempty,
+ const bool isactionset,
+ const QMap<QString, bool> isvariableok);
+
+ // Prints a QMap of Variables to kdDebug().
+ void printMvariables(const QMap<QString, KSharedPtr<KoMacro::Variable > > mvariables, const QString s);
+*/
+ /**
+ * Sub-methods of testParseXML() and testToXML().
+ * Test the correct parsing of a @a QDomElement into a @a Macro
+ * respectively expected failure of parsing. Then transform it
+ * back and compare it.
+ */
+ // 1.Test - Correct DomElement.
+ void testCorrectDomElement();
+ // 2.Test - XML-document with bad root element.
+ void testBadRoot();
+ // 3.Test - XML-document with a missing Variable.
+ void testMissingVariable();
+ // 4.Test - One more Variable in XML-Document.
+ void testMoreVariables();
+ // 5.Test - XML-document with wrong macro-xmlversion.
+ void testWrongVersion();
+ // 6.Test - XML-document if it has a wrong structure like
+ // wrong parathesis or missing end tag.
+ void testWrongXMLStruct();
+ // 7.Test-XML-document with maximum field-size.
+ void testMaxNum();
+ // 8.Test-XML-document with maximum+1 field-size.
+ void testMaxNum2();
+ // 9.Test-XML-document with minimum field-size.
+ void testMinNum();
+ // 10.Test-XML-document with minimum-1 field-size.
+ void testMinNum2();
+ // 11.Test - With a to big number.
+ void testBigNumber();
+ // 12.Test - With two MacroItems.
+ void testTwoMacroItems();
+
+
+ bool assertActionsEqual(KSharedPtr<KoMacro::Action> action,
+ KSharedPtr<KoMacro::Action> action2);
+
+ bool assertVariablesEqual(KSharedPtr<KoMacro::Variable> var,
+ KSharedPtr<KoMacro::Variable> var2);
+ };
+}
+
+#endif
diff --git a/kexi/plugins/migration/Makefile.am b/kexi/plugins/migration/Makefile.am
new file mode 100644
index 000000000..e496773e4
--- /dev/null
+++ b/kexi/plugins/migration/Makefile.am
@@ -0,0 +1,20 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_migration.la
+
+kexihandler_migration_la_SOURCES = keximigrationpart.cpp
+
+kexihandler_migration_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module
+kexihandler_migration_la_LIBADD = ../../core/libkexicore.la \
+ ../../migration/libkeximigrate.la
+
+INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/migration \
+ -I$(top_srcdir)/kexi/kexiDB $(all_includes)
+
+METASOURCES = AUTO
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=keximigrationhandler.desktop
+
+include ../Makefile.common
diff --git a/kexi/plugins/migration/keximigrationhandler.desktop b/kexi/plugins/migration/keximigrationhandler.desktop
new file mode 100644
index 000000000..4ca99b050
--- /dev/null
+++ b/kexi/plugins/migration/keximigrationhandler.desktop
@@ -0,0 +1,102 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+GenericName=Migration Plugin
+GenericName[bg]=Приставка за мигриране
+GenericName[ca]=Connector de migració
+GenericName[cy]=Ategyn Mudo
+GenericName[da]=Migrationsplugin
+GenericName[de]=Migrations-Modul
+GenericName[el]=Πρόσθετο μεταφοράς
+GenericName[eo]=Migradkromaĵo
+GenericName[es]=Complemento para migración
+GenericName[et]=Migreerumisplugin
+GenericName[eu]=Migraziorako plugina
+GenericName[fa]=وصلۀ جابه‌جایی
+GenericName[fi]=Yhdistämisliitännäinen
+GenericName[fr]=Module de migration
+GenericName[fy]=Migraasjeplugin
+GenericName[ga]=Breiseán Migration
+GenericName[gl]=Plugin de Migración
+GenericName[he]=תוסף Migration
+GenericName[hr]=Migracijski dodatak
+GenericName[hu]=Migrálási modul
+GenericName[is]=Gagnaflutnings íforrit
+GenericName[it]=Plugin di migrazione
+GenericName[ja]=データ移行プラグイン
+GenericName[km]=កម្មវិធី​ជំនួយ​ផ្លាស់ប្ដូរ​កន្លែង
+GenericName[lv]=Migrācijas spraudnis
+GenericName[ms]=Plugin Migrasi
+GenericName[nb]=Programtillegg for migrering
+GenericName[nds]=Datenutlagern-Moduul
+GenericName[ne]=माइग्रेसन प्लगइन
+GenericName[nl]=Migratieplugin
+GenericName[nn]=Programtillegg for migrering
+GenericName[pl]=Wtyczka migracji
+GenericName[pt]='Plugin' de Migração
+GenericName[pt_BR]=Plugin de Migração
+GenericName[ru]=Миграция
+GenericName[sk]=Modul pre migráciu
+GenericName[sl]=Vstavek za prehod
+GenericName[sr]=Миграциони прикључак
+GenericName[sr@Latn]=Migracioni priključak
+GenericName[sv]=Övergångsinsticksprogram
+GenericName[uk]=Втулок міграції
+GenericName[uz]=Migratsiya plagini
+GenericName[uz@cyrillic]=Миграция плагини
+GenericName[zh_CN]=升迁插件
+GenericName[zh_TW]=轉移外掛程式
+Name=Migration Plugin
+Name[bg]=Приставка за мигриране
+Name[ca]=Connector de migració
+Name[cy]=Ategyn Mudo
+Name[da]=Migrationsplugin
+Name[de]=Migrations-Modul
+Name[el]=Πρόσθετο μεταφοράς
+Name[eo]=Migradkromaĵo
+Name[es]=Complemento para migración
+Name[et]=Migreerumisplugin
+Name[eu]=Migraziorako plugina
+Name[fa]=وصلۀ جابه‌جایی
+Name[fi]=Yhdistämisliitännäinen
+Name[fr]=Module de migration
+Name[fy]=Migrationplugin
+Name[ga]=Breiseán Migration
+Name[gl]=Plugin de Migración
+Name[he]=תוסף Migration
+Name[hi]=माइग्रेशन प्लगइन
+Name[hr]=Migracijski dodatak
+Name[hu]=Migrálási modul
+Name[is]=Gagnaflutnings íforrit
+Name[it]=Plugin di migrazione
+Name[ja]=データ移行プラグイン
+Name[km]=កម្មវិធី​ជំនួយ​សម្រាប់​ផ្លាស់ប្ដូរ
+Name[lv]=Migrācijas spraudnis
+Name[ms]=Plugin Migrasi
+Name[nb]=Programtillegg for migrering
+Name[nds]=Datenutlagern-Moduul
+Name[ne]=माइग्रेसन प्लगइन
+Name[nl]=Migratieplugin
+Name[nn]=Programtillegg for migrering
+Name[pl]=Wtyczka migracji
+Name[pt]='Plugin' de Migração
+Name[pt_BR]=Plugin de Migração
+Name[ru]=Модуль миграции
+Name[sk]=Modul pre migráciu
+Name[sl]=Vstavek za prehod
+Name[sr]=Миграциони прикључак
+Name[sr@Latn]=Migracioni priključak
+Name[sv]=Övergångsinsticksprogram
+Name[uk]=Втулок міграції
+Name[uz]=Migratsiya plagini
+Name[uz@cyrillic]=Миграция плагини
+Name[zh_CN]=升迁插件
+Name[zh_TW]=轉移外掛程式
+X-KDE-Library=kexihandler_migration
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=migration
+X-Kexi-GroupIcon=migration
+X-Kexi-ItemIcon=migration
+X-Kexi-NoObject=true
diff --git a/kexi/plugins/migration/keximigrationpart.cpp b/kexi/plugins/migration/keximigrationpart.cpp
new file mode 100644
index 000000000..0f6c408ba
--- /dev/null
+++ b/kexi/plugins/migration/keximigrationpart.cpp
@@ -0,0 +1,46 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "keximigrationpart.h"
+
+#include <migration/importwizard.h>
+
+#include <kgenericfactory.h>
+
+KexiMigrationPart::KexiMigrationPart(QObject *parent, const char *name, const QStringList &args)
+ : KexiInternalPart(parent, name, args)
+{
+}
+
+KexiMigrationPart::~KexiMigrationPart()
+{
+}
+
+QWidget *KexiMigrationPart::createWidget(const char* /*widgetClass*/, KexiMainWindow* mainWin,
+ QWidget *parent, const char *objName, QMap<QString,QString>* args )
+{
+ Q_UNUSED( mainWin );
+
+ KexiMigration::ImportWizard *w = new KexiMigration::ImportWizard(parent, args);
+ w->setName(objName);
+ return w;
+}
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_migration,
+ KGenericFactory<KexiMigrationPart>("kexihandler_migration") )
diff --git a/kexi/plugins/migration/keximigrationpart.h b/kexi/plugins/migration/keximigrationpart.h
new file mode 100644
index 000000000..528aac823
--- /dev/null
+++ b/kexi/plugins/migration/keximigrationpart.h
@@ -0,0 +1,38 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXI_MIGRATION_PART_H
+#define KEXI_MIGRATION_PART_H
+
+#include <core/kexiinternalpart.h>
+
+/*! @short Internal part for data/project migration wizard. */
+class KexiMigrationPart : public KexiInternalPart
+{
+ public:
+ KexiMigrationPart(QObject *parent, const char *name, const QStringList &args);
+ virtual ~KexiMigrationPart();
+
+ /*! Reimplement this if your internal part has to return widgets
+ or QDialog objects. */
+ virtual QWidget *createWidget(const char* /*widgetClass*/, KexiMainWindow* mainWin,
+ QWidget *parent, const char *objName = 0, QMap<QString,QString>* args = 0);
+};
+
+#endif
diff --git a/kexi/plugins/queries/Makefile.am b/kexi/plugins/queries/Makefile.am
new file mode 100644
index 000000000..c0b620d49
--- /dev/null
+++ b/kexi/plugins/queries/Makefile.am
@@ -0,0 +1,29 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_query.la
+
+kexihandler_query_la_SOURCES = kexiquerypart.cpp kexiquerydesignersql.cpp \
+ kexiquerydesignersqlhistory.cpp kexiquerydesignerguieditor.cpp \
+ kexiqueryview.cpp
+kexihandler_query_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined
+kexihandler_query_la_LIBADD = ../../core/libkexicore.la \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/kexi/widget/tableview/libkexidatatable.la \
+ $(top_builddir)/kexi/widget/relations/libkexirelationsview.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la
+
+INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/widget/tableview \
+ -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore \
+ -I$(top_srcdir)/kexi/kexidb $(all_includes)
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=kexiqueryhandler.desktop
+
+rcdir = $(kde_datadir)/kexi
+rc_DATA = kexiquerypartui.rc kexiquerypartinstui.rc
+
+METASOURCES = AUTO
+
+include ../Makefile.common
diff --git a/kexi/plugins/queries/kexiaddparamdialog.cpp b/kexi/plugins/queries/kexiaddparamdialog.cpp
new file mode 100644
index 000000000..fb40f9a28
--- /dev/null
+++ b/kexi/plugins/queries/kexiaddparamdialog.cpp
@@ -0,0 +1,47 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <klocale.h>
+#include <kcombobox.h>
+#include <klineedit.h>
+#include <qvbox.h>
+#include <kexidataprovider.h>
+#include "kexiaddparamdialog.h"
+#include "kexiaddparamdialog.moc"
+#include "kexiaddparamwidget.h"
+
+KexiAddParamDialog::KexiAddParamDialog(QWidget *parent)
+ : KDialogBase(parent, "kexiaddparamdialog", true, i18n("Add Parameter"), KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true)
+{
+ m_wid=new KexiAddParamWidget(makeVBoxMainWidget());
+ for (int i=1;i<=KexiDataProvider::Parameter::maxType;i++)
+ m_wid->typecombo->insertItem(KexiDataProvider::Parameter::typeDescription[i]);
+}
+
+KexiAddParamDialog::~KexiAddParamDialog()
+{
+}
+
+QString KexiAddParamDialog::parameterName() {
+ return m_wid->paramname->text();
+}
+
+int KexiAddParamDialog::parameterType() {
+ return m_wid->typecombo->currentItem()+1;
+}
diff --git a/kexi/plugins/queries/kexiaddparamdialog.h b/kexi/plugins/queries/kexiaddparamdialog.h
new file mode 100644
index 000000000..79558a7cd
--- /dev/null
+++ b/kexi/plugins/queries/kexiaddparamdialog.h
@@ -0,0 +1,40 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIADDPARAMDIALOG_H
+#define KEXIADDPARAMDIALOG_H
+
+#include <kdialogbase.h>
+
+class KexiAddParamWidget;
+
+class KexiAddParamDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ KexiAddParamDialog(QWidget *parent);
+ virtual ~KexiAddParamDialog();
+ QString parameterName();
+ int parameterType();
+ private:
+ KexiAddParamWidget *m_wid;
+};
+
+#endif
diff --git a/kexi/plugins/queries/kexiaddparamwidget.ui b/kexi/plugins/queries/kexiaddparamwidget.ui
new file mode 100644
index 000000000..43ec25f1f
--- /dev/null
+++ b/kexi/plugins/queries/kexiaddparamwidget.ui
@@ -0,0 +1,135 @@
+<!DOCTYPE UI><UI version="3.1" stdsetdef="1">
+<class>KexiAddParamWidget</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiAddParamWidget</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>496</width>
+ <height>205</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Parameter</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLayoutWidget" row="0" column="0">
+ <property name="name">
+ <cstring>layout3</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout1</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>kexi_</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ </widget>
+ <widget class="KLineEdit">
+ <property name="name">
+ <cstring>paramname</cstring>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ <spacer row="3" column="0">
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLayoutWidget" row="2" column="0">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1_4</cstring>
+ </property>
+ <property name="text">
+ <string>Message:</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit">
+ <property name="name">
+ <cstring>message</cstring>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QLayoutWidget" row="1" column="0">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1_3</cstring>
+ </property>
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ <widget class="QComboBox">
+ <property name="name">
+ <cstring>typecombo</cstring>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ </grid>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klineedit.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/plugins/queries/kexidynamicqueryparameterdialog.cpp b/kexi/plugins/queries/kexidynamicqueryparameterdialog.cpp
new file mode 100644
index 000000000..4a77f37c8
--- /dev/null
+++ b/kexi/plugins/queries/kexidynamicqueryparameterdialog.cpp
@@ -0,0 +1,63 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#include "kexidynamicqueryparameterdialog.h"
+#include "kexidynamicqueryparameterdialog.moc"
+
+#include <qvbox.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <qlineedit.h>
+#include <qobjectlist.h>
+
+KexiDynamicQueryParameterDialog::KexiDynamicQueryParameterDialog(QWidget *parent,
+ KexiDataProvider::Parameters *values, const KexiDataProvider::ParameterList &list):
+ KDialogBase(parent, "paramddialog", true, i18n("Query Parameters"),
+ KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true )
+{
+ m_values=values;
+ int y;
+ m_mainView=new QVBox(this);
+
+ for (KexiDataProvider::ParameterList::const_iterator it=list.begin();
+ it!=list.end();++it) {
+ QLineEdit *le=new QLineEdit(m_mainView,(*it).name.utf8());
+ le->setText((*values)[(*it).name]);
+ }
+
+ setMainWidget(m_mainView);
+}
+
+KexiDynamicQueryParameterDialog::~KexiDynamicQueryParameterDialog() {}
+
+void KexiDynamicQueryParameterDialog::slotOk() {
+ QObjectList *l=queryList(0,"kexi_.*",true,true);
+ QObjectListIt it(*l);
+ QObject *obj;
+ kdDebug()<<"KexiDynamicQueryParameterDialog::slotOk()"<<endl;
+ while ((obj=it.current())!=0) {
+ kdDebug()<<"KexiDynamicQueryParameterDialog::slotOk()::loop"<<endl;
+ (*m_values)[QString().fromUtf8(obj->name())]=
+ (dynamic_cast<QLineEdit*>(obj))->text();
+ ++it;
+ }
+ delete l;
+ KDialogBase::slotOk();
+}
diff --git a/kexi/plugins/queries/kexidynamicqueryparameterdialog.h b/kexi/plugins/queries/kexidynamicqueryparameterdialog.h
new file mode 100644
index 000000000..b315e4f9a
--- /dev/null
+++ b/kexi/plugins/queries/kexidynamicqueryparameterdialog.h
@@ -0,0 +1,45 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _KEXI_DYNAMIC_QUERY_PARAMETER_DIALOG_H_
+#define _KEXI_DYNAMIC_QUERY_PARAMETER_DIALOG_H_
+
+
+#include <kdialogbase.h>
+#include <kexidataprovider.h>
+
+class QVBox;
+
+class KexiDynamicQueryParameterDialog : public KDialogBase
+{
+ Q_OBJECT
+public:
+ KexiDynamicQueryParameterDialog(QWidget *parent,KexiDataProvider::Parameters *, const KexiDataProvider::ParameterList &);
+ virtual ~KexiDynamicQueryParameterDialog();
+
+protected:
+ virtual void slotOk();
+private:
+//temporary only. Later a different widget will be used
+ QVBox *m_mainView;
+ KexiDataProvider::Parameters *m_values;
+
+};
+
+#endif
diff --git a/kexi/plugins/queries/kexiparameterlisteditor.ui b/kexi/plugins/queries/kexiparameterlisteditor.ui
new file mode 100644
index 000000000..ac4a3230c
--- /dev/null
+++ b/kexi/plugins/queries/kexiparameterlisteditor.ui
@@ -0,0 +1,88 @@
+<!DOCTYPE UI><UI version="3.1" stdsetdef="1">
+<class>KexiParameterListEditor</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiParameterListEditor</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>190</width>
+ <height>480</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Parameters:</string>
+ </property>
+ </widget>
+ <widget class="KListView">
+ <column>
+ <property name="text">
+ <string>Name</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Type</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>list</cstring>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout1</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>addParameter</cstring>
+ </property>
+ <property name="text">
+ <string>Add</string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>deleteParameter</cstring>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klistview.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/plugins/queries/kexiquerydesignerguieditor.cpp b/kexi/plugins/queries/kexiquerydesignerguieditor.cpp
new file mode 100644
index 000000000..d67573e8a
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerydesignerguieditor.cpp
@@ -0,0 +1,1803 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiquerydesignerguieditor.h"
+
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qdom.h>
+#include <qregexp.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <kexidb/field.h>
+#include <kexidb/queryschema.h>
+#include <kexidb/connection.h>
+#include <kexidb/parser/parser.h>
+#include <kexidb/parser/sqlparser.h>
+#include <kexidb/utils.h>
+#include <kexidb/roweditbuffer.h>
+#include <kexiutils/identifier.h>
+#include <kexiproject.h>
+#include <keximainwindow.h>
+#include <kexiinternalpart.h>
+#include <kexitableview.h>
+#include <kexitableitem.h>
+#include <kexitableviewdata.h>
+#include <kexidragobjects.h>
+#include <kexidialogbase.h>
+#include <kexidatatable.h>
+#include <kexi.h>
+#include <kexisectionheader.h>
+#include <widget/tableview/kexidataawarepropertyset.h>
+#include <widget/relations/kexirelationwidget.h>
+#include <widget/relations/kexirelationviewtable.h>
+#include <koproperty/property.h>
+#include <koproperty/set.h>
+
+#include "kexiquerypart.h"
+
+//! @todo remove KEXI_NO_QUERY_TOTALS later
+#define KEXI_NO_QUERY_TOTALS
+
+//! indices for table columns
+#define COLUMN_ID_COLUMN 0
+#define COLUMN_ID_TABLE 1
+#define COLUMN_ID_VISIBLE 2
+#ifdef KEXI_NO_QUERY_TOTALS
+# define COLUMN_ID_SORTING 3
+# define COLUMN_ID_CRITERIA 4
+#else
+# define COLUMN_ID_TOTALS 3
+# define COLUMN_ID_SORTING 4
+# define COLUMN_ID_CRITERIA 5
+#endif
+
+/*! @internal */
+class KexiQueryDesignerGuiEditor::Private
+{
+public:
+ Private()
+ : fieldColumnIdentifiers(101, false/*case insens.*/)
+ {
+ droppedNewItem = 0;
+ slotTableAdded_enabled = true;
+ }
+
+ bool changeSingleCellValue(KexiTableItem &item, int columnNumber,
+ const QVariant& value, KexiDB::ResultInfo* result)
+ {
+ data->clearRowEditBuffer();
+ if (!data->updateRowEditBuffer(&item, columnNumber, value)
+ || !data->saveRowChanges(item, true))
+ {
+ if (result)
+ *result = *data->result();
+ return false;
+ }
+ return true;
+ }
+
+ KexiTableViewData *data;
+ KexiDataTable *dataTable;
+ QGuardedPtr<KexiDB::Connection> conn;
+
+ KexiRelationWidget *relations;
+ KexiSectionHeader *head;
+ QSplitter *spl;
+
+ /*! Used to remember in slotDroppedAtRow() what data was dropped,
+ so we can create appropriate prop. set in slotRowInserted()
+ This information is cached and entirely refreshed on updateColumnsData(). */
+ KexiTableViewData *fieldColumnData, *tablesColumnData;
+
+ /*! Collects identifiers selected in 1st (field) column,
+ so we're able to distinguish between table identifiers selected from
+ the dropdown list, and strings (e.g. expressions) entered by hand.
+ This information is cached and entirely refreshed on updateColumnsData().
+ The dict is filled with (char*)1 values (doesn't matter what it is);
+ */
+ QDict<char> fieldColumnIdentifiers;
+
+ KexiDataAwarePropertySet* sets;
+ KexiTableItem *droppedNewItem;
+
+ QString droppedNewTable, droppedNewField;
+
+ bool slotTableAdded_enabled : 1;
+};
+
+static bool isAsterisk(const QString& tableName, const QString& fieldName)
+{
+ return tableName=="*" || fieldName.endsWith("*");
+}
+
+//! @internal \return true if sorting is allowed for \a fieldName and \a tableName
+static bool sortingAllowed(const QString& fieldName, const QString& tableName) {
+ return ! (fieldName=="*" || (fieldName.isEmpty() && tableName=="*"));
+}
+
+//=========================================================
+
+KexiQueryDesignerGuiEditor::KexiQueryDesignerGuiEditor(
+ KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : KexiViewBase(mainWin, parent, name)
+ , d( new Private() )
+{
+ d->conn = mainWin->project()->dbConnection();
+
+ d->spl = new QSplitter(Vertical, this);
+ d->spl->setChildrenCollapsible(false);
+ d->relations = new KexiRelationWidget(mainWin, d->spl, "relations");
+ connect(d->relations, SIGNAL(tableAdded(KexiDB::TableSchema&)),
+ this, SLOT(slotTableAdded(KexiDB::TableSchema&)));
+ connect(d->relations, SIGNAL(tableHidden(KexiDB::TableSchema&)),
+ this, SLOT(slotTableHidden(KexiDB::TableSchema&)));
+ connect(d->relations, SIGNAL(tableFieldDoubleClicked(KexiDB::TableSchema*,const QString&)),
+ this, SLOT(slotTableFieldDoubleClicked(KexiDB::TableSchema*,const QString&)));
+
+ d->head = new KexiSectionHeader(i18n("Query Columns"), Vertical, d->spl);
+ d->dataTable = new KexiDataTable(mainWin, d->head, "guieditor_dataTable", false);
+ d->dataTable->dataAwareObject()->setSpreadSheetMode();
+
+ d->data = new KexiTableViewData(); //just empty data
+ d->sets = new KexiDataAwarePropertySet( this, d->dataTable->dataAwareObject() );
+ initTableColumns();
+ initTableRows();
+
+ QValueList<int> c;
+ c << COLUMN_ID_COLUMN << COLUMN_ID_TABLE << COLUMN_ID_CRITERIA;
+ if (d->dataTable->tableView()/*sanity*/) {
+ d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_VISIBLE);
+ d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_SORTING);
+ d->dataTable->tableView()->maximizeColumnsWidth( c );
+ d->dataTable->tableView()->setDropsAtRowEnabled(true);
+ connect(d->dataTable->tableView(), SIGNAL(dragOverRow(KexiTableItem*,int,QDragMoveEvent*)),
+ this, SLOT(slotDragOverTableRow(KexiTableItem*,int,QDragMoveEvent*)));
+ connect(d->dataTable->tableView(), SIGNAL(droppedAtRow(KexiTableItem*,int,QDropEvent*,KexiTableItem*&)),
+ this, SLOT(slotDroppedAtRow(KexiTableItem*,int,QDropEvent*,KexiTableItem*&)));
+ connect(d->dataTable->tableView(), SIGNAL(newItemAppendedForAfterDeletingInSpreadSheetMode()),
+ this, SLOT(slotNewItemAppendedForAfterDeletingInSpreadSheetMode()));
+ }
+ connect(d->data, SIGNAL(aboutToChangeCell(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)),
+ this, SLOT(slotBeforeCellChanged(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)));
+ connect(d->data, SIGNAL(rowInserted(KexiTableItem*,uint,bool)),
+ this, SLOT(slotRowInserted(KexiTableItem*,uint,bool)));
+ connect(d->relations, SIGNAL(tablePositionChanged(KexiRelationViewTableContainer*)),
+ this, SLOT(slotTablePositionChanged(KexiRelationViewTableContainer*)));
+ connect(d->relations, SIGNAL(aboutConnectionRemove(KexiRelationViewConnection*)),
+ this, SLOT(slotAboutConnectionRemove(KexiRelationViewConnection*)));
+
+ QVBoxLayout *l = new QVBoxLayout(this);
+ l->addWidget(d->spl);
+
+ addChildView(d->relations);
+ addChildView(d->dataTable);
+ setViewWidget(d->dataTable, true);
+ d->relations->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
+ d->head->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
+ updateGeometry();
+ d->spl->setSizes(QValueList<int>()<< 800<<400);
+}
+
+KexiQueryDesignerGuiEditor::~KexiQueryDesignerGuiEditor()
+{
+}
+
+void
+KexiQueryDesignerGuiEditor::initTableColumns()
+{
+ KexiTableViewColumn *col1 = new KexiTableViewColumn("column", KexiDB::Field::Enum, i18n("Column"),
+ i18n("Describes field name or expression for the designed query."));
+ col1->setRelatedDataEditable(true);
+
+ d->fieldColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text);
+ col1->setRelatedData( d->fieldColumnData );
+ d->data->addColumn(col1);
+
+ KexiTableViewColumn *col2 = new KexiTableViewColumn("table", KexiDB::Field::Enum, i18n("Table"),
+ i18n("Describes table for a given field. Can be empty."));
+ d->tablesColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text);
+ col2->setRelatedData( d->tablesColumnData );
+ d->data->addColumn(col2);
+
+ KexiTableViewColumn *col3 = new KexiTableViewColumn("visible", KexiDB::Field::Boolean, i18n("Visible"),
+ i18n("Describes visibility for a given field or expression."));
+ col3->field()->setDefaultValue( QVariant(false, 0) );
+ col3->field()->setNotNull( true );
+ d->data->addColumn(col3);
+
+#ifndef KEXI_NO_QUERY_TOTALS
+ KexiTableViewColumn *col4 = new KexiTableViewColumn("totals", KexiDB::Field::Enum, i18n("Totals"),
+ i18n("Describes a way of computing totals for a given field or expression."));
+ QValueVector<QString> totalsTypes;
+ totalsTypes.append( i18n("Group by") );
+ totalsTypes.append( i18n("Sum") );
+ totalsTypes.append( i18n("Average") );
+ totalsTypes.append( i18n("Min") );
+ totalsTypes.append( i18n("Max") );
+ //todo: more like this
+ col4->field()->setEnumHints(totalsTypes);
+ d->data->addColumn(col4);
+#endif
+
+ KexiTableViewColumn *col5 = new KexiTableViewColumn("sort", KexiDB::Field::Enum, i18n("Sorting"),
+ i18n("Describes a way of sorting for a given field."));
+ QValueVector<QString> sortTypes;
+ sortTypes.append( "" );
+ sortTypes.append( i18n("Ascending") );
+ sortTypes.append( i18n("Descending") );
+ col5->field()->setEnumHints(sortTypes);
+ d->data->addColumn(col5);
+
+ KexiTableViewColumn *col6 = new KexiTableViewColumn("criteria", KexiDB::Field::Text, i18n("Criteria"),
+ i18n("Describes the criteria for a given field or expression."));
+ d->data->addColumn(col6);
+
+// KexiTableViewColumn *col7 = new KexiTableViewColumn(i18n("Or"), KexiDB::Field::Text);
+// d->data->addColumn(col7);
+}
+
+void KexiQueryDesignerGuiEditor::initTableRows()
+{
+ d->data->deleteAllRows();
+ //const int columns = d->data->columnsCount();
+ for (int i=0; i<(int)d->sets->size(); i++) {
+ KexiTableItem* item;
+ d->data->append(item = d->data->createItem());
+ item->at(COLUMN_ID_VISIBLE) = QVariant(false, 0);
+ }
+ d->dataTable->dataAwareObject()->setData(d->data);
+
+ updateColumnsData();
+}
+
+void KexiQueryDesignerGuiEditor::updateColumnsData()
+{
+ d->dataTable->dataAwareObject()->acceptRowEdit();
+
+ QStringList sortedTableNames;
+ for (TablesDictIterator it(*d->relations->tables());it.current();++it)
+ sortedTableNames += it.current()->schema()->name();
+ qHeapSort( sortedTableNames );
+
+ //several tables can be hidden now, so remove rows for these tables
+ QValueList<int> rowsToDelete;
+ for (int r = 0; r<(int)d->sets->size(); r++) {
+ KoProperty::Set *set = d->sets->at(r);
+ if (set) {
+ QString tableName = (*set)["table"].value().toString();
+ QString fieldName = (*set)["field"].value().toString();
+ const bool allTablesAsterisk = tableName=="*" && d->relations->tables()->isEmpty();
+ const bool fieldNotFound = tableName!="*"
+ && !(*set)["isExpression"].value().toBool()
+ && sortedTableNames.end() == qFind( sortedTableNames.begin(), sortedTableNames.end(), tableName );
+
+ if (allTablesAsterisk || fieldNotFound) {
+ //table not found: mark this line for later removal
+ rowsToDelete += r;
+ }
+ }
+ }
+ d->data->deleteRows( rowsToDelete );
+
+ //update 'table' and 'field' columns
+ d->tablesColumnData->deleteAllRows();
+ d->fieldColumnData->deleteAllRows();
+ d->fieldColumnIdentifiers.clear();
+
+ KexiTableItem *item = d->fieldColumnData->createItem(); //new KexiTableItem(2);
+ (*item)[COLUMN_ID_COLUMN]="*";
+ (*item)[COLUMN_ID_TABLE]="*";
+ d->fieldColumnData->append( item );
+ d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache
+
+// tempData()->clearQuery();
+ tempData()->unregisterForTablesSchemaChanges();
+ for (QStringList::const_iterator it = sortedTableNames.constBegin();
+ it!=sortedTableNames.constEnd(); ++it)
+ {
+ //table
+/*! @todo what about query? */
+ KexiDB::TableSchema *table = d->relations->tables()->find(*it)->schema()->table();
+ d->conn->registerForTableSchemaChanges(*tempData(), *table); //this table will be used
+ item = d->tablesColumnData->createItem(); //new KexiTableItem(2);
+ (*item)[COLUMN_ID_COLUMN]=table->name();
+ (*item)[COLUMN_ID_TABLE]=(*item)[COLUMN_ID_COLUMN];
+ d->tablesColumnData->append( item );
+ //fields
+ item = d->fieldColumnData->createItem(); //new KexiTableItem(2);
+ (*item)[COLUMN_ID_COLUMN]=table->name()+".*";
+ (*item)[COLUMN_ID_TABLE]=(*item)[COLUMN_ID_COLUMN];
+ d->fieldColumnData->append( item );
+ d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache
+ for (KexiDB::Field::ListIterator t_it = table->fieldsIterator();t_it.current();++t_it) {
+ item = d->fieldColumnData->createItem(); // new KexiTableItem(2);
+ (*item)[COLUMN_ID_COLUMN]=table->name()+"."+t_it.current()->name();
+ (*item)[COLUMN_ID_TABLE]=QString(" ") + t_it.current()->name();
+ d->fieldColumnData->append( item );
+ d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache
+ }
+ }
+//TODO
+}
+
+KexiRelationWidget *KexiQueryDesignerGuiEditor::relationView() const
+{
+ return d->relations;
+}
+
+KexiQueryPart::TempData *
+KexiQueryDesignerGuiEditor::tempData() const
+{
+ return static_cast<KexiQueryPart::TempData*>(parentDialog()->tempData());
+}
+
+static QString msgCannotSwitch_EmptyDesign() {
+ return i18n("Cannot switch to data view, because query design is empty.\n"
+ "First, please create your design.");
+}
+
+bool
+KexiQueryDesignerGuiEditor::buildSchema(QString *errMsg)
+{
+ //build query schema
+ KexiQueryPart::TempData * temp = tempData();
+ if (temp->query()) {
+ temp->clearQuery();
+ } else {
+ temp->setQuery( new KexiDB::QuerySchema() );
+ }
+
+ //add tables
+ for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) {
+/*! @todo what about query? */
+ temp->query()->addTable( it.current()->schema()->table() );
+ }
+
+ //add fields, also build:
+ // -WHERE expression
+ // -ORDER BY list
+ KexiDB::BaseExpr *whereExpr = 0;
+ const uint count = QMIN(d->data->count(), d->sets->size());
+ bool fieldsFound = false;
+ KexiTableViewData::Iterator it(d->data->iterator());
+ for (uint i=0; i<count && it.current(); ++it, i++) {
+ if (!it.current()->at(COLUMN_ID_TABLE).isNull() && it.current()->at(COLUMN_ID_COLUMN).isNull()) {
+ //show message about missing field name, and set focus to that cell
+ kexipluginsdbg << "no field provided!" << endl;
+ d->dataTable->dataAwareObject()->setCursorPosition(i,0);
+ if (errMsg)
+ *errMsg = i18n("Select column for table \"%1\"")
+ .arg(it.current()->at(COLUMN_ID_TABLE).toString());
+ return false;
+ }
+
+ KoProperty::Set *set = d->sets->at(i);
+ if (set) {
+ QString tableName = (*set)["table"].value().toString().stripWhiteSpace();
+ QString fieldName = (*set)["field"].value().toString();
+ QString fieldAndTableName = fieldName;
+ KexiDB::Field *currentField = 0; // will be set if this column is a single field
+ KexiDB::QueryColumnInfo* currentColumn = 0;
+ if (!tableName.isEmpty())
+ fieldAndTableName.prepend(tableName+".");
+ const bool fieldVisible = (*set)["visible"].value().toBool();
+ QString criteriaStr = (*set)["criteria"].value().toString();
+ QCString alias = (*set)["alias"].value().toCString();
+ if (!criteriaStr.isEmpty()) {
+ int token;
+ KexiDB::BaseExpr *criteriaExpr = parseExpressionString(criteriaStr, token,
+ true/*allowRelationalOperator*/);
+ if (!criteriaExpr) {//for sanity
+ if (errMsg)
+ *errMsg = i18n("Invalid criteria \"%1\"").arg(criteriaStr);
+ delete whereExpr;
+ return false;
+ }
+ //build relational expression for column variable
+ KexiDB::VariableExpr *varExpr = new KexiDB::VariableExpr(fieldAndTableName);
+ criteriaExpr = new KexiDB::BinaryExpr(KexiDBExpr_Relational, varExpr, token, criteriaExpr);
+ //critera ok: add it to WHERE section
+ if (whereExpr)
+ whereExpr = new KexiDB::BinaryExpr(KexiDBExpr_Logical, whereExpr, AND, criteriaExpr);
+ else //first expr.
+ whereExpr = criteriaExpr;
+ }
+ if (tableName.isEmpty()) {
+ if ((*set)["isExpression"].value().toBool()==true) {
+ //add expression column
+ int dummyToken;
+ KexiDB::BaseExpr *columnExpr = parseExpressionString(fieldName, dummyToken,
+ false/*!allowRelationalOperator*/);
+ if (!columnExpr) {
+ if (errMsg)
+ *errMsg = i18n("Invalid expression \"%1\"").arg(fieldName);
+ return false;
+ }
+ temp->query()->addExpression(columnExpr, fieldVisible);
+ if (fieldVisible)
+ fieldsFound = true;
+ if (!alias.isEmpty())
+ temp->query()->setColumnAlias( temp->query()->fieldCount()-1, alias );
+ }
+ //TODO
+ }
+ else if (tableName=="*") {
+ //all tables asterisk
+ temp->query()->addAsterisk( new KexiDB::QueryAsterisk( temp->query(), 0 ), fieldVisible );
+ if (fieldVisible)
+ fieldsFound = true;
+ continue;
+ }
+ else {
+ KexiDB::TableSchema *t = d->conn->tableSchema(tableName);
+ if (fieldName=="*") {
+ //single-table asterisk: <tablename> + ".*" + number
+ temp->query()->addAsterisk( new KexiDB::QueryAsterisk( temp->query(), t ), fieldVisible );
+ if (fieldVisible)
+ fieldsFound = true;
+ } else {
+ if (!t) {
+ kexipluginswarn << "query designer: NO TABLE '"
+ << (*set)["table"].value().toString() << "'" << endl;
+ continue;
+ }
+ currentField = t->field( fieldName );
+ if (!currentField) {
+ kexipluginswarn << "query designer: NO FIELD '" << fieldName << "'" << endl;
+ continue;
+ }
+ if (!fieldVisible && criteriaStr.isEmpty() && (*set)["isExpression"]
+ && (*set)["sorting"].value().toString()!="nosorting")
+ {
+ kexipluginsdbg << "invisible field with sorting: do not add it to the fields list" << endl;
+ continue;
+ }
+ temp->query()->addField(currentField, fieldVisible);
+ currentColumn = temp->query()->expandedOrInternalField(
+ temp->query()->fieldsExpanded().count() - 1 );
+ if (fieldVisible)
+ fieldsFound = true;
+ if (!alias.isEmpty())
+ temp->query()->setColumnAlias( temp->query()->fieldCount()-1, alias );
+ }
+ }
+ }
+ else {//!set
+ kexipluginsdbg << it.current()->at(COLUMN_ID_TABLE).toString() << endl;
+ }
+ }
+ if (!fieldsFound) {
+ if (errMsg)
+ *errMsg = msgCannotSwitch_EmptyDesign();
+ return false;
+ }
+ if (whereExpr)
+ kexipluginsdbg << "KexiQueryDesignerGuiEditor::buildSchema(): setting CRITERIA: "
+ << whereExpr->debugString() << endl;
+
+ //set always, because if whereExpr==NULL,
+ //this will clear prev. expr
+ temp->query()->setWhereExpression( whereExpr );
+
+ //add relations (looking for connections)
+ for (ConnectionListIterator it(*d->relations->connections()); it.current(); ++it) {
+ KexiRelationViewTableContainer *masterTable = it.current()->masterTable();
+ KexiRelationViewTableContainer *detailsTable = it.current()->detailsTable();
+
+/*! @todo what about query? */
+ temp->query()->addRelationship(
+ masterTable->schema()->table()->field(it.current()->masterField()),
+ detailsTable->schema()->table()->field(it.current()->detailsField()) );
+ }
+
+ // Add sorting information (ORDER BY) - we can do that only now
+ // after all QueryColumnInfo items are instantiated
+ KexiDB::OrderByColumnList orderByColumns;
+ it = d->data->iterator();
+ int fieldNumber = -1; //field number (empty rows are omitted)
+ for (uint i=0/*row number*/; i<count && it.current(); ++it, i++) {
+ KoProperty::Set *set = d->sets->at(i);
+ if (!set)
+ continue;
+ fieldNumber++;
+ KexiDB::Field *currentField = 0;
+ KexiDB::QueryColumnInfo *currentColumn = 0;
+ QString sortingString( (*set)["sorting"].value().toString() );
+ if (sortingString!="ascending" && sortingString!="descending")
+ continue;
+ if (!(*set)["visible"].value().toBool()) {
+ // this row defines invisible field but contains sorting information,
+ // what means KexiDB::Field should be used as a reference for this sorting
+ // Note1: alias is not supported here.
+
+ // Try to find a field (not mentioned after SELECT):
+ currentField = temp->query()->findTableField( (*set)["field"].value().toString() );
+ if (!currentField) {
+ kexipluginswarn << "KexiQueryDesignerGuiEditor::buildSchema(): NO FIELD '"
+ << (*set)["field"].value().toString()
+ << " available for sorting" << endl;
+ continue;
+ }
+ orderByColumns.appendField(*currentField, sortingString=="ascending");
+ continue;
+ }
+ currentField = temp->query()->field( (uint)fieldNumber );
+ if (!currentField || currentField->isExpression() || currentField->isQueryAsterisk())
+//! @todo support expressions here
+ continue;
+//! @todo ok, but not for expressions
+ QString aliasString( (*set)["alias"].value().toString() );
+ currentColumn = temp->query()->columnInfo(
+ (*set)["table"].value().toString() + "."
+ + (aliasString.isEmpty() ? currentField->name() : aliasString) );
+ if (currentField && currentColumn) {
+ if (currentColumn->visible)
+ orderByColumns.appendColumn(*currentColumn, sortingString=="ascending");
+ else if (currentColumn->field)
+ orderByColumns.appendField(*currentColumn->field, sortingString=="ascending");
+ }
+ }
+ temp->query()->setOrderByColumnList( orderByColumns );
+
+ temp->query()->debug();
+ temp->registerTableSchemaChanges(temp->query());
+ //TODO?
+ return true;
+}
+
+tristate
+KexiQueryDesignerGuiEditor::beforeSwitchTo(int mode, bool &dontStore)
+{
+ kexipluginsdbg << "KexiQueryDesignerGuiEditor::beforeSwitch()" << mode << endl;
+
+ if (!d->dataTable->dataAwareObject()->acceptRowEdit())
+ return cancelled;
+
+ if (mode==Kexi::DesignViewMode) {
+ return true;
+ }
+ else if (mode==Kexi::DataViewMode) {
+// if (!d->dataTable->dataAwareObject()->acceptRowEdit())
+ // return cancelled;
+
+ if (!dirty() && parentDialog()->neverSaved()) {
+ KMessageBox::information(this, msgCannotSwitch_EmptyDesign());
+ return cancelled;
+ }
+ if (dirty() || !tempData()->query()) {
+ //remember current design in a temporary structure
+ dontStore=true;
+ QString errMsg;
+ //build schema; problems are not allowed
+ if (!buildSchema(&errMsg)) {
+ KMessageBox::sorry(this, errMsg);
+ return cancelled;
+ }
+ }
+ //TODO
+ return true;
+ }
+ else if (mode==Kexi::TextViewMode) {
+ dontStore=true;
+ //build schema; ignore problems
+ buildSchema();
+/* if (tempData()->query && tempData()->query->fieldCount()==0) {
+ //no fields selected: let's add "*" (all-tables asterisk),
+ // otherwise SQL statement will be invalid
+ tempData()->query->addAsterisk( new KexiDB::QueryAsterisk( tempData()->query ) );
+ }*/
+ //todo
+ return true;
+ }
+
+ return false;
+}
+
+tristate
+KexiQueryDesignerGuiEditor::afterSwitchFrom(int mode)
+{
+ const bool was_dirty = dirty();
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ if (mode==Kexi::NoViewMode || (mode==Kexi::DataViewMode && !tempData()->query())) {
+ //this is not a SWITCH but a fresh opening in this view mode
+ if (!m_dialog->neverSaved()) {
+ if (!loadLayout()) {
+ //err msg
+ parentDialog()->setStatus(conn,
+ i18n("Query definition loading failed."),
+ i18n("Query design may be corrupted so it could not be opened even in text view.\n"
+ "You can delete the query and create it again."));
+ return false;
+ }
+ // Invalid queries case:
+ // KexiDialogBase::switchToViewMode() first opens DesignViewMode,
+ // and then KexiQueryPart::loadSchemaData() doesn't allocate QuerySchema object
+ // do we're carefully looking at parentDialog()->schemaData()
+ KexiDB::QuerySchema * q = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
+ if (q) {
+ KexiDB::ResultInfo result;
+ showFieldsForQuery( q, result );
+ if (!result.success) {
+ parentDialog()->setStatus(&result, i18n("Query definition loading failed."));
+ tempData()->proposeOpeningInTextViewModeBecauseOfProblems = true;
+ return false;
+ }
+ }
+//! @todo load global query properties
+ }
+ }
+ else if (mode==Kexi::TextViewMode || mode==Kexi::DataViewMode) {
+ // Switch from text or data view. In the second case, the design could be changed as well
+ // because there could be changes made in the text view before switching to the data view.
+ if (tempData()->queryChangedInPreviousView) {
+ //previous view changed query data
+ //-clear and regenerate GUI items
+ initTableRows();
+ //todo
+ if (tempData()->query()) {
+ //there is a query schema to show
+ showTablesForQuery( tempData()->query() );
+ //-show fields
+ KexiDB::ResultInfo result;
+ showFieldsAndRelationsForQuery( tempData()->query(), result );
+ if (!result.success) {
+ parentDialog()->setStatus(&result, i18n("Query definition loading failed."));
+ return false;
+ }
+ }
+ else {
+ d->relations->clear();
+ }
+ }
+//! @todo load global query properties
+ }
+
+ if (mode==Kexi::DataViewMode) {
+ //this is just a SWITCH from data view
+ //set cursor if needed:
+ if (d->dataTable->dataAwareObject()->currentRow()<0
+ || d->dataTable->dataAwareObject()->currentColumn()<0)
+ {
+ d->dataTable->dataAwareObject()->ensureCellVisible(0,0);
+ d->dataTable->dataAwareObject()->setCursorPosition(0,0);
+ }
+ }
+ tempData()->queryChangedInPreviousView = false;
+ setFocus(); //to allow shared actions proper update
+ if (!was_dirty)
+ setDirty(false);
+ return true;
+}
+
+
+KexiDB::SchemaData*
+KexiQueryDesignerGuiEditor::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ if (!d->dataTable->dataAwareObject()->acceptRowEdit()) {
+ cancel = true;
+ return 0;
+ }
+ QString errMsg;
+ KexiQueryPart::TempData * temp = tempData();
+ if (!temp->query() || !(viewMode()==Kexi::DesignViewMode && !temp->queryChangedInPreviousView)) {
+ //only rebuild schema if it has not been rebuilt previously
+ if (!buildSchema(&errMsg)) {
+ KMessageBox::sorry(this, errMsg);
+ cancel = true;
+ return 0;
+ }
+ }
+ (KexiDB::SchemaData&)*temp->query() = sdata; //copy main attributes
+
+ bool ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *temp->query(), true /*newObject*/ );
+ m_dialog->setId( temp->query()->id() );
+
+ if (ok)
+ ok = storeLayout();
+
+// temp->query = 0; //will be returned, so: don't keep it
+ if (!ok) {
+ temp->setQuery( 0 );
+// delete query;
+ return 0;
+ }
+ return temp->takeQuery(); //will be returned, so: don't keep it in temp
+}
+
+tristate KexiQueryDesignerGuiEditor::storeData(bool dontAsk)
+{
+ if (!d->dataTable->dataAwareObject()->acceptRowEdit())
+ return cancelled;
+
+ const bool was_dirty = dirty();
+ tristate res = KexiViewBase::storeData(dontAsk); //this clears dirty flag
+ if (true == res)
+ res = buildSchema();
+ if (true == res)
+ res = storeLayout();
+ if (true != res) {
+ if (was_dirty)
+ setDirty(true);
+ }
+ return res;
+}
+
+void KexiQueryDesignerGuiEditor::showTablesForQuery(KexiDB::QuerySchema *query)
+{
+//replaced by code below that preserves geometries d->relations->clear();
+
+ // instead of hiding all tables and showing some tables,
+ // show only these new and hide these unncecessary; the same for connections)
+ d->slotTableAdded_enabled = false; //speedup
+ d->relations->removeAllConnections(); //connections will be recreated
+ d->relations->hideAllTablesExcept( query->tables() );
+ for (KexiDB::TableSchema::ListIterator it(*query->tables()); it.current(); ++it) {
+ d->relations->addTable( it.current() );
+ }
+
+ d->slotTableAdded_enabled = true;
+ updateColumnsData();
+}
+
+void KexiQueryDesignerGuiEditor::addConnection(
+ KexiDB::Field *masterField, KexiDB::Field *detailsField)
+{
+ SourceConnection conn;
+ conn.masterTable = masterField->table()->name(); //<<<TODO
+ conn.masterField = masterField->name();
+ conn.detailsTable = detailsField->table()->name();
+ conn.detailsField = detailsField->name();
+ d->relations->addConnection( conn );
+}
+
+void KexiQueryDesignerGuiEditor::showFieldsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result)
+{
+ showFieldsOrRelationsForQueryInternal(query, true, false, result);
+}
+
+void KexiQueryDesignerGuiEditor::showRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result)
+{
+ showFieldsOrRelationsForQueryInternal(query, false, true, result);
+}
+
+void KexiQueryDesignerGuiEditor::showFieldsAndRelationsForQuery(KexiDB::QuerySchema *query,
+ KexiDB::ResultInfo& result)
+{
+ showFieldsOrRelationsForQueryInternal(query, true, true, result);
+}
+
+void KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(
+ KexiDB::QuerySchema *query, bool showFields, bool showRelations, KexiDB::ResultInfo& result)
+{
+ result.clear();
+ const bool was_dirty = dirty();
+
+ //1. Show explicitly declared relations:
+ if (showRelations) {
+ KexiDB::Relationship *rel;
+ for (KexiDB::Relationship::ListIterator it(*query->relationships());
+ (rel=it.current()); ++it)
+ {
+//! @todo: now only sigle-field relationships are implemented!
+ KexiDB::Field *masterField = rel->masterIndex()->fields()->first();
+ KexiDB::Field *detailsField = rel->detailsIndex()->fields()->first();
+ addConnection(masterField, detailsField);
+ }
+ }
+
+ //2. Collect information about criterias
+ // --this must be top level chain of AND's
+ // --this will also show joins as: [table1.]field1 = [table2.]field2
+ QDict<KexiDB::BaseExpr> criterias(101, false);
+ KexiDB::BaseExpr* e = query->whereExpression();
+ KexiDB::BaseExpr* eItem = 0;
+ while (e) {
+ //eat parentheses because the expression can be (....) AND (... AND ... )
+ while (e && e->toUnary() && e->token()=='(')
+ e = e->toUnary()->arg();
+
+ if (e->toBinary() && e->token()==AND) {
+ eItem = e->toBinary()->left();
+ e = e->toBinary()->right();
+ }
+ else {
+ eItem = e;
+ e = 0;
+ }
+
+ //eat parentheses
+ while (eItem && eItem->toUnary() && eItem->token()=='(')
+ eItem = eItem->toUnary()->arg();
+
+ if (!eItem)
+ continue;
+
+ kexidbg << eItem->toString() << endl;
+ KexiDB::BinaryExpr* binary = eItem->toBinary();
+ if (binary && eItem->exprClass()==KexiDBExpr_Relational) {
+ KexiDB::Field *leftField = 0, *rightField = 0;
+ if (eItem->token()=='='
+ && binary->left()->toVariable()
+ && binary->right()->toVariable()
+ && (leftField = query->findTableField( binary->left()->toString() ))
+ && (rightField = query->findTableField( binary->right()->toString() )))
+ {
+//! @todo move this check to parser on QuerySchema creation
+//! or to QuerySchema creation (WHERE expression should be then simplified
+//! by removing joins
+
+ //this is relationship defined as following JOIN: [table1.]field1 = [table2.]field2
+ if (showRelations) {
+//! @todo testing primary key here is too simplified; maybe look ar isForeignKey() or indices..
+//! @todo what about multifield joins?
+ if (leftField->isPrimaryKey())
+ addConnection(leftField /*master*/, rightField /*details*/);
+ else
+ addConnection(rightField /*master*/, leftField /*details*/);
+//! @todo addConnection() should have "bool oneToOne" arg, for 1-to-1 relations
+ }
+ }
+ else if (binary->left()->toVariable()) {
+ //this is: variable , op , argument
+ //store variable -> argument:
+ criterias.insert(binary->left()->toVariable()->name, binary->right());
+ }
+ else if (binary->right()->toVariable()) {
+ //this is: argument , op , variable
+ //store variable -> argument:
+ criterias.insert(binary->right()->toVariable()->name, binary->left());
+ }
+ }
+ } //while
+
+ if (!showFields)
+ return;
+
+ //3. show fields (including * and table.*)
+ uint row_num = 0;
+ KexiDB::Field *field;
+ QPtrDict<char> usedCriterias(101); // <-- used criterias will be saved here
+ // so in step 4. we will be able to add
+ // remaining invisible columns with criterias
+ for (KexiDB::Field::ListIterator it(*query->fields());
+ (field = it.current()); ++it, row_num++)
+ {
+ //append a new row
+ QString tableName, fieldName, columnAlias, criteriaString;
+ KexiDB::BinaryExpr *criteriaExpr = 0;
+ KexiDB::BaseExpr *criteriaArgument = 0;
+ if (field->isQueryAsterisk()) {
+ if (field->table()) {//single-table asterisk
+ tableName = field->table()->name();
+ fieldName = "*";
+ }
+ else {//all-tables asterisk
+ tableName = "*";
+ fieldName = "";
+ }
+ }
+ else {
+ columnAlias = query->columnAlias(row_num);
+ if (field->isExpression()) {
+// if (columnAlias.isEmpty()) {
+// columnAlias = i18n("expression", "expr%1").arg(row_num); //TODO
+// }
+// if (columnAlias.isEmpty())
+//TODO: ok? perhaps do not allow to omit aliases?
+ fieldName = field->expression()->toString();
+// else
+// fieldName = columnAlias + ": " + field->expression()->toString();
+ }
+ else {
+ tableName = field->table()->name();
+ fieldName = field->name();
+ criteriaArgument = criterias[fieldName];
+ if (!criteriaArgument) {//try table.field
+ criteriaArgument = criterias[tableName+"."+fieldName];
+ }
+ if (criteriaArgument) {//criteria expression is just a parent of argument
+ criteriaExpr = criteriaArgument->parent()->toBinary();
+ usedCriterias.insert(criteriaArgument, (char*)1); //save info. about used criteria
+ }
+ }
+ }
+ //create new row data
+ KexiTableItem *newItem = createNewRow(tableName, fieldName, true /* visible*/);
+ if (criteriaExpr) {
+//! @todo fix for !INFIX operators
+ if (criteriaExpr->token()=='=')
+ criteriaString = criteriaArgument->toString();
+ else
+ criteriaString = criteriaExpr->tokenToString() + " " + criteriaArgument->toString();
+ (*newItem)[COLUMN_ID_CRITERIA] = criteriaString;
+ }
+ d->dataTable->dataAwareObject()->insertItem(newItem, row_num);
+ //OK, row inserted: create a new set for it
+ KoProperty::Set &set = *createPropertySet( row_num, tableName, fieldName, true/*new one*/ );
+ if (!columnAlias.isEmpty())
+ set["alias"].setValue(columnAlias, false);
+ if (!criteriaString.isEmpty())
+ set["criteria"].setValue( criteriaString, false );
+ if (field->isExpression()) {
+// (*newItem)[COLUMN_ID_COLUMN] = ;
+ if (!d->changeSingleCellValue(*newItem, COLUMN_ID_COLUMN,
+ QVariant(columnAlias + ": " + field->expression()->toString()), &result))
+ return; //problems with setting column expression
+ }
+ }
+
+ //4. show ORDER BY information
+ d->data->clearRowEditBuffer();
+ KexiDB::OrderByColumnList &orderByColumns = query->orderByColumnList();
+ QMap<KexiDB::QueryColumnInfo*,int> columnsOrder(
+ query->columnsOrder(KexiDB::QuerySchema::UnexpandedListWithoutAsterisks) );
+ for (KexiDB::OrderByColumn::ListConstIterator orderByColumnsIt( orderByColumns.constBegin() );
+ orderByColumnsIt!=orderByColumns.constEnd(); ++orderByColumnsIt)
+ {
+ KexiDB::QueryColumnInfo *column = (*orderByColumnsIt).column();
+ KexiTableItem *rowItem = 0;
+ KoProperty::Set *rowPropertySet = 0;
+ if (column) {
+ //sorting for visible column
+ if (column->visible) {
+ if (columnsOrder.contains(column)) {
+ const int columnPosition = columnsOrder[ column ];
+ rowItem = d->data->at( columnPosition );
+ rowPropertySet = d->sets->at( columnPosition );
+ kexipluginsdbg << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal():\n\t"
+ "Setting \"" << (*orderByColumnsIt).debugString() << "\" sorting for row #"
+ << columnPosition << endl;
+ }
+ }
+ }
+ else if ((*orderByColumnsIt).field()) {
+ //this will be presented as invisible field: create new row
+ field = (*orderByColumnsIt).field();
+ QString tableName( field->table() ? field->table()->name() : QString::null );
+ rowItem = createNewRow( tableName, field->name(), false /* !visible*/);
+ d->dataTable->dataAwareObject()->insertItem(rowItem, row_num);
+ rowPropertySet = createPropertySet( row_num, tableName, field->name(), true /*newOne*/ );
+ propertySetSwitched();
+ kexipluginsdbg << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal():\n\t"
+ "Setting \"" << (*orderByColumnsIt).debugString() << "\" sorting for invisible field "
+ << field->name() << ", table " << tableName << " -row #" << row_num << endl;
+ row_num++;
+ }
+ //alter sorting for either existing or new row
+ if (rowItem && rowPropertySet) {
+ d->data->updateRowEditBuffer(rowItem, COLUMN_ID_SORTING,
+ (*orderByColumnsIt).ascending() ? 1 : 2); // this will automatically update "sorting" property
+ // in slotBeforeCellChanged()
+ d->data->saveRowChanges(*rowItem, true);
+ (*rowPropertySet)["sorting"].clearModifiedFlag(); // this property should look "fresh"
+ if (!rowItem->at(COLUMN_ID_VISIBLE).toBool()) //update
+ (*rowPropertySet)["visible"].setValue(QVariant(false,0), false/*rememberOldValue*/);
+ }
+ }
+
+ //5. Show fields for unused criterias (with "Visible" column set to false)
+ KexiDB::BaseExpr *criteriaArgument; // <-- contains field or table.field
+ for (QDictIterator<KexiDB::BaseExpr> it(criterias); (criteriaArgument = it.current()); ++it) {
+ if (usedCriterias[it.current()])
+ continue;
+ //unused: append a new row
+ KexiDB::BinaryExpr *criteriaExpr = criteriaArgument->parent()->toBinary();
+ if (!criteriaExpr) {
+ kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): "
+ "criteriaExpr is not a binary expr" << endl;
+ continue;
+ }
+ KexiDB::VariableExpr *columnNameArgument = criteriaExpr->left()->toVariable(); //left or right
+ if (!columnNameArgument) {
+ columnNameArgument = criteriaExpr->right()->toVariable();
+ if (!columnNameArgument) {
+ kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): "
+ "columnNameArgument is not a variable (table or table.field) expr" << endl;
+ continue;
+ }
+ }
+ KexiDB::Field* field = 0;
+ if (-1 == columnNameArgument->name.find('.') && query->tables()->count()==1) {
+ //extreme case: only field name provided for one-table query:
+ field = query->tables()->first()->field(columnNameArgument->name);
+ }
+ else {
+ field = query->findTableField(columnNameArgument->name);
+ }
+
+ if (!field) {
+ kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): "
+ "no columnInfo found in the query for name \"" << columnNameArgument->name << endl;
+ continue;
+ }
+ QString tableName, fieldName, columnAlias, criteriaString;
+//! @todo what about ALIAS?
+ tableName = field->table()->name();
+ fieldName = field->name();
+ //create new row data
+ KexiTableItem *newItem = createNewRow(tableName, fieldName, false /* !visible*/);
+ if (criteriaExpr) {
+//! @todo fix for !INFIX operators
+ if (criteriaExpr->token()=='=')
+ criteriaString = criteriaArgument->toString();
+ else
+ criteriaString = criteriaExpr->tokenToString() + " " + criteriaArgument->toString();
+ (*newItem)[COLUMN_ID_CRITERIA] = criteriaString;
+ }
+ d->dataTable->dataAwareObject()->insertItem(newItem, row_num);
+ //OK, row inserted: create a new set for it
+ KoProperty::Set &set = *createPropertySet( row_num++, tableName, fieldName, true/*new one*/ );
+//! @todo if (!columnAlias.isEmpty())
+//! @todo set["alias"].setValue(columnAlias, false);
+//// if (!criteriaString.isEmpty())
+ set["criteria"].setValue( criteriaString, false );
+ set["visible"].setValue( QVariant(false,1), false );
+ }
+
+ //current property set has most probably changed
+ propertySetSwitched();
+
+ if (!was_dirty)
+ setDirty(false);
+ //move to 1st column, 1st row
+ d->dataTable->dataAwareObject()->ensureCellVisible(0,0);
+// tempData()->registerTableSchemaChanges(query);
+}
+
+bool KexiQueryDesignerGuiEditor::loadLayout()
+{
+ QString xml;
+// if (!loadDataBlock( xml, "query_layout" )) {
+ loadDataBlock( xml, "query_layout" );
+ //TODO errmsg
+// return false;
+// }
+ if (xml.isEmpty()) {
+ //in a case when query layout was not saved, build layout by hand
+ // -- dynamic cast because of a need for handling invalid queries
+ // (as in KexiQueryDesignerGuiEditor::afterSwitchFrom()):
+ KexiDB::QuerySchema * q = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
+ if (q) {
+ showTablesForQuery( q );
+ KexiDB::ResultInfo result;
+ showRelationsForQuery( q, result );
+ if (!result.success) {
+ parentDialog()->setStatus(&result, i18n("Query definition loading failed."));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ QDomDocument doc;
+ doc.setContent(xml);
+ QDomElement doc_el = doc.documentElement(), el;
+ if (doc_el.tagName()!="query_layout") {
+ //TODO errmsg
+ return false;
+ }
+
+ const bool was_dirty = dirty();
+
+ //add tables and relations to the relation view
+ for (el = doc_el.firstChild().toElement(); !el.isNull(); el=el.nextSibling().toElement()) {
+ if (el.tagName()=="table") {
+ KexiDB::TableSchema *t = d->conn->tableSchema(el.attribute("name"));
+ int x = el.attribute("x","-1").toInt();
+ int y = el.attribute("y","-1").toInt();
+ int width = el.attribute("width","-1").toInt();
+ int height = el.attribute("height","-1").toInt();
+ QRect rect;
+ if (x!=-1 || y!=-1 || width!=-1 || height!=-1)
+ rect = QRect(x,y,width,height);
+ d->relations->addTable( t, rect );
+ }
+ else if (el.tagName()=="conn") {
+ SourceConnection src_conn;
+ src_conn.masterTable = el.attribute("mtable");
+ src_conn.masterField = el.attribute("mfield");
+ src_conn.detailsTable = el.attribute("dtable");
+ src_conn.detailsField = el.attribute("dfield");
+ d->relations->addConnection(src_conn);
+ }
+ }
+
+ if (!was_dirty)
+ setDirty(false);
+ return true;
+}
+
+bool KexiQueryDesignerGuiEditor::storeLayout()
+{
+ KexiQueryPart::TempData * temp = tempData();
+
+ // Save SQL without driver-escaped keywords.
+ KexiDB::Connection* dbConn = mainWin()->project()->dbConnection();
+ if (m_dialog->schemaData()) //set this instance as obsolete (only if it's stored)
+ dbConn->setQuerySchemaObsolete( m_dialog->schemaData()->name() );
+
+ KexiDB::Connection::SelectStatementOptions options;
+ options.identifierEscaping = KexiDB::Driver::EscapeKexi|KexiDB::Driver::EscapeAsNecessary;
+ options.addVisibleLookupColumns = false;
+ QString sqlText = dbConn->selectStatement( *temp->query(), options );
+ if (!storeDataBlock( sqlText, "sql" )) {
+ return false;
+ }
+
+ //serialize detailed XML query definition
+ QString xml = "<query_layout>", tmp;
+ for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) {
+ KexiRelationViewTableContainer *table_cont = it.current();
+/*! @todo what about query? */
+ tmp = QString("<table name=\"")+QString(table_cont->schema()->name())+"\" x=\""
+ +QString::number(table_cont->x())
+ +"\" y=\""+QString::number(table_cont->y())
+ +"\" width=\""+QString::number(table_cont->width())
+ +"\" height=\""+QString::number(table_cont->height())
+ +"\"/>";
+ xml += tmp;
+ }
+
+ KexiRelationViewConnection *con;
+ for (ConnectionListIterator it(*d->relations->connections()); (con = it.current()); ++it) {
+ tmp = QString("<conn mtable=\"") + QString(con->masterTable()->schema()->name())
+ + "\" mfield=\"" + con->masterField() + "\" dtable=\""
+ + QString(con->detailsTable()->schema()->name())
+ + "\" dfield=\"" + con->detailsField() + "\"/>";
+ xml += tmp;
+ }
+ xml += "</query_layout>";
+ if (!storeDataBlock( xml, "query_layout" )) {
+ return false;
+ }
+
+// mainWin()->project()->reloadPartItem( m_dialog );
+
+ return true;
+}
+
+QSize KexiQueryDesignerGuiEditor::sizeHint() const
+{
+ QSize s1 = d->relations->sizeHint();
+ QSize s2 = d->head->sizeHint();
+ return QSize(QMAX(s1.width(),s2.width()), s1.height()+s2.height());
+}
+
+KexiTableItem*
+KexiQueryDesignerGuiEditor::createNewRow(const QString& tableName, const QString& fieldName,
+ bool visible) const
+{
+ KexiTableItem *newItem = d->data->createItem();
+ QString key;
+ if (tableName=="*")
+ key="*";
+ else {
+ if (!tableName.isEmpty())
+ key = (tableName+".");
+ key += fieldName;
+ }
+ (*newItem)[COLUMN_ID_COLUMN]=key;
+ (*newItem)[COLUMN_ID_TABLE]=tableName;
+ (*newItem)[COLUMN_ID_VISIBLE]=QVariant(visible, 1);
+#ifndef KEXI_NO_QUERY_TOTALS
+ (*newItem)[COLUMN_ID_TOTALS]=QVariant(0);
+#endif
+ return newItem;
+}
+
+void KexiQueryDesignerGuiEditor::slotDragOverTableRow(
+ KexiTableItem * /*item*/, int /*row*/, QDragMoveEvent* e)
+{
+ if (e->provides("kexi/field")) {
+ e->acceptAction(true);
+ }
+}
+
+void
+KexiQueryDesignerGuiEditor::slotDroppedAtRow(KexiTableItem * /*item*/, int /*row*/,
+ QDropEvent *ev, KexiTableItem*& newItem)
+{
+ QString sourceMimeType;
+ QString srcTable;
+ QString srcField;
+
+ if (!KexiFieldDrag::decodeSingle(ev,sourceMimeType,srcTable,srcField))
+ return;
+ //insert new row at specific place
+ newItem = createNewRow(srcTable, srcField, true /* visible*/);
+ d->droppedNewItem = newItem;
+ d->droppedNewTable = srcTable;
+ d->droppedNewField = srcField;
+ //TODO
+}
+
+void KexiQueryDesignerGuiEditor::slotNewItemAppendedForAfterDeletingInSpreadSheetMode()
+{
+ KexiTableItem *item = d->data->last();
+ if (item)
+ item->at(COLUMN_ID_VISIBLE) = QVariant(false, 0); //the same init as in initTableRows()
+}
+
+void KexiQueryDesignerGuiEditor::slotRowInserted(KexiTableItem* item, uint row, bool /*repaint*/)
+{
+ if (d->droppedNewItem && d->droppedNewItem==item) {
+ createPropertySet( row, d->droppedNewTable, d->droppedNewField, true );
+ propertySetSwitched();
+ d->droppedNewItem=0;
+ }
+}
+
+void KexiQueryDesignerGuiEditor::slotTableAdded(KexiDB::TableSchema & /*t*/)
+{
+ if (!d->slotTableAdded_enabled)
+ return;
+ updateColumnsData();
+ setDirty();
+ d->dataTable->setFocus();
+}
+
+void KexiQueryDesignerGuiEditor::slotTableHidden(KexiDB::TableSchema & /*t*/)
+{
+ updateColumnsData();
+ setDirty();
+}
+
+/*! @internal generates smallest unique alias */
+QCString KexiQueryDesignerGuiEditor::generateUniqueAlias() const
+{
+//TODO: add option for using non-i18n'd "expr" prefix?
+ const QCString expStr
+ = i18n("short for 'expression' word (only latin letters, please)", "expr").latin1();
+//TODO: optimization: cache it?
+ QAsciiDict<char> aliases(101);
+ for (int r = 0; r<(int)d->sets->size(); r++) {
+ KoProperty::Set *set = d->sets->at(r);
+ if (set) {
+ const QCString a = (*set)["alias"].value().toCString().lower();
+ if (!a.isEmpty())
+ aliases.insert(a,(char*)1);
+ }
+ }
+ int aliasNr=1;
+ for (;;aliasNr++) {
+ if (!aliases[expStr+QString::number(aliasNr).latin1()])
+ break;
+ }
+ return expStr+QString::number(aliasNr).latin1();
+}
+
+//! @todo this is primitive, temporary: reuse SQL parser
+KexiDB::BaseExpr*
+KexiQueryDesignerGuiEditor::parseExpressionString(const QString& fullString, int& token,
+ bool allowRelationalOperator)
+{
+ QString str = fullString.stripWhiteSpace();
+ int len = 0;
+ //KexiDB::BaseExpr *expr = 0;
+ //1. get token
+ token = 0;
+ //2-char-long tokens
+ if (str.startsWith(">="))
+ token = GREATER_OR_EQUAL;
+ else if (str.startsWith("<="))
+ token = LESS_OR_EQUAL;
+ else if (str.startsWith("<>"))
+ token = NOT_EQUAL;
+ else if (str.startsWith("!="))
+ token = NOT_EQUAL2;
+ else if (str.startsWith("=="))
+ token = '=';
+
+ if (token!=0)
+ len = 2;
+ else if (str.startsWith("=") //1-char-long tokens
+ || str.startsWith("<")
+ || str.startsWith(">"))
+ {
+ token = str[0].latin1();
+ len = 1;
+ }
+ else {
+ if (allowRelationalOperator)
+ token = '=';
+ }
+
+ if (!allowRelationalOperator && token!=0)
+ return 0;
+
+ //1. get expression after token
+ if (len>0)
+ str = str.mid(len).stripWhiteSpace();
+ if (str.isEmpty())
+ return 0;
+
+ KexiDB::BaseExpr *valueExpr = 0;
+ QRegExp re;
+ if (str.length()>=2 &&
+ (
+ (str.startsWith("\"") && str.endsWith("\""))
+ || (str.startsWith("'") && str.endsWith("'")))
+ )
+ {
+ valueExpr = new KexiDB::ConstExpr(CHARACTER_STRING_LITERAL, str.mid(1,str.length()-2));
+ }
+ else if (str.startsWith("[") && str.endsWith("]")) {
+ valueExpr = new KexiDB::QueryParameterExpr(str.mid(1,str.length()-2));
+ }
+ else if ((re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})")).exactMatch( str ))
+ {
+ valueExpr = new KexiDB::ConstExpr(DATE_CONST, QDate::fromString(
+ re.cap(1).rightJustify(4, '0')+"-"+re.cap(2).rightJustify(2, '0')
+ +"-"+re.cap(3).rightJustify(2, '0'), Qt::ISODate));
+ }
+ else if ((re = QRegExp("(\\d{1,2}):(\\d{1,2})")).exactMatch( str )
+ || (re = QRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch( str ))
+ {
+ QString res = re.cap(1).rightJustify(2, '0')+":"+re.cap(2).rightJustify(2, '0')
+ +":"+re.cap(3).rightJustify(2, '0');
+// kexipluginsdbg << res << endl;
+ valueExpr = new KexiDB::ConstExpr(TIME_CONST, QTime::fromString(res, Qt::ISODate));
+ }
+ else if ((re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2})")).exactMatch( str )
+ || (re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch( str ))
+ {
+ QString res = re.cap(1).rightJustify(4, '0')+"-"+re.cap(2).rightJustify(2, '0')
+ +"-"+re.cap(3).rightJustify(2, '0')
+ +"T"+re.cap(4).rightJustify(2, '0')+":"+re.cap(5).rightJustify(2, '0')
+ +":"+re.cap(6).rightJustify(2, '0');
+// kexipluginsdbg << res << endl;
+ valueExpr = new KexiDB::ConstExpr(DATETIME_CONST,
+ QDateTime::fromString(res, Qt::ISODate));
+ }
+ else if (str[0]>='0' && str[0]<='9' || str[0]=='-' || str[0]=='+') {
+ //number
+ QString decimalSym = KGlobal::locale()->decimalSymbol();
+ bool ok;
+ int pos = str.find('.');
+ if (pos==-1) {//second chance: local decimal symbol
+ pos = str.find(decimalSym);
+ }
+ if (pos>=0) {//real const number
+ const int left = str.left(pos).toInt(&ok);
+ if (!ok)
+ return 0;
+ const int right = str.mid(pos+1).toInt(&ok);
+ if (!ok)
+ return 0;
+ valueExpr = new KexiDB::ConstExpr(REAL_CONST, QPoint(left,right)); //decoded to QPoint
+ }
+ else {
+ //integer const
+ const Q_LLONG val = str.toLongLong(&ok);
+ if (!ok)
+ return 0;
+ valueExpr = new KexiDB::ConstExpr(INTEGER_CONST, val);
+ }
+ }
+ else if (str.lower()=="null") {
+ valueExpr = new KexiDB::ConstExpr(SQL_NULL, QVariant());
+ }
+ else {//identfier
+ if (!KexiUtils::isIdentifier(str))
+ return 0;
+ valueExpr = new KexiDB::VariableExpr(str);
+ //find first matching field for name 'str':
+ for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) {
+/*! @todo what about query? */
+ if (it.current()->schema()->table() && it.current()->schema()->table()->field(str)) {
+ valueExpr->toVariable()->field = it.current()->schema()->table()->field(str);
+ break;
+ }
+ }
+ }
+ return valueExpr;
+}
+
+void KexiQueryDesignerGuiEditor::slotBeforeCellChanged(KexiTableItem *item, int colnum,
+ QVariant& newValue, KexiDB::ResultInfo* result)
+{
+ if (colnum == COLUMN_ID_COLUMN) {
+ if (newValue.isNull()) {
+ d->data->updateRowEditBuffer(item, COLUMN_ID_TABLE, QVariant(), false/*!allowSignals*/);
+ d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(false,1));//invisible
+ d->data->updateRowEditBuffer(item, COLUMN_ID_SORTING, QVariant());
+#ifndef KEXI_NO_QUERY_TOTALS
+ d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant());//remove totals
+#endif
+ d->data->updateRowEditBuffer(item, COLUMN_ID_CRITERIA, QVariant());//remove crit.
+ d->sets->removeCurrentPropertySet();
+ }
+ else {
+ //auto fill 'table' column
+ QString fieldId( newValue.toString().stripWhiteSpace() ); //tmp, can look like "table.field"
+ QString fieldName; //"field" part of "table.field" or expression string
+ QString tableName; //empty for expressions
+ QCString alias;
+ QString columnValueForExpr; //for setting pretty printed "alias: expr" in 1st column
+ const bool isExpression = !d->fieldColumnIdentifiers[fieldId];
+ if (isExpression) {
+ //this value is entered by hand and doesn't match
+ //any value in the combo box -- we're assuming this is an expression
+ //-table remains null
+ //-find "alias" in something like "alias : expr"
+ const int id = fieldId.find(':');
+ if (id>0) {
+ alias = fieldId.left(id).stripWhiteSpace().latin1();
+ if (!KexiUtils::isIdentifier(alias)) {
+ result->success = false;
+ result->allowToDiscardChanges = true;
+ result->column = colnum;
+ result->msg = i18n("Entered column alias \"%1\" is not a valid identifier.")
+ .arg(alias);
+ result->desc = i18n("Identifiers should start with a letter or '_' character");
+ return;
+ }
+ }
+ fieldName = fieldId.mid(id+1).stripWhiteSpace();
+ //check expr.
+ KexiDB::BaseExpr *e;
+ int dummyToken;
+ if ((e = parseExpressionString(fieldName, dummyToken, false/*allowRelationalOperator*/)))
+ {
+ fieldName = e->toString(); //print it prettier
+ //this is just checking: destroy expr. object
+ delete e;
+ }
+ else {
+ result->success = false;
+ result->allowToDiscardChanges = true;
+ result->column = colnum;
+ result->msg = i18n("Invalid expression \"%1\"").arg(fieldName);
+ return;
+ }
+ }
+ else {//not expr.
+ //this value is properly selected from combo box list
+ if (fieldId=="*") {
+ tableName = "*";
+ }
+ else {
+ if (!KexiDB::splitToTableAndFieldParts(
+ fieldId, tableName, fieldName, KexiDB::SetFieldNameIfNoTableName))
+ {
+ kexipluginswarn << "KexiQueryDesignerGuiEditor::slotBeforeCellChanged(): no 'field' or 'table.field'" << endl;
+ return;
+ }
+ }
+ }
+ bool saveOldValue = true;
+ KoProperty::Set *set = d->sets->findPropertySetForItem(*item); //*propertyBuffer();
+ if (!set) {
+ saveOldValue = false; // no old val.
+ const int row = d->data->findRef(item);
+ if (row<0) {
+ result->success = false;
+ return;
+ }
+ set = createPropertySet( row, tableName, fieldName, true );
+ propertySetSwitched();
+ }
+ d->data->updateRowEditBuffer(item, COLUMN_ID_TABLE, QVariant(tableName), false/*!allowSignals*/);
+ d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(true,1));
+#ifndef KEXI_NO_QUERY_TOTALS
+ d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant(0));
+#endif
+ if (!sortingAllowed(fieldName, tableName)) {
+ // sorting is not available for "*" or "table.*" rows
+//! @todo what about expressions?
+ d->data->updateRowEditBuffer(item, COLUMN_ID_SORTING, QVariant());
+ }
+ //update properties
+ (*set)["field"].setValue(fieldName, saveOldValue);
+ if (isExpression) {
+ //-no alias but it's needed:
+ if (alias.isEmpty()) //-try oto get old alias
+ alias = (*set)["alias"].value().toCString();
+ if (alias.isEmpty()) //-generate smallest unique alias
+ alias = generateUniqueAlias();
+ }
+ (*set)["isExpression"].setValue(QVariant(isExpression,1), saveOldValue);
+ if (!alias.isEmpty()) {
+ (*set)["alias"].setValue(alias, saveOldValue);
+ //pretty printed "alias: expr"
+ newValue = QString(alias) + ": " + fieldName;
+ }
+ (*set)["caption"].setValue(QString::null, saveOldValue);
+ (*set)["table"].setValue(tableName, saveOldValue);
+ updatePropertiesVisibility(*set);
+ }
+ }
+ else if (colnum==COLUMN_ID_TABLE) {
+ if (newValue.isNull()) {
+ if (!item->at(COLUMN_ID_COLUMN).toString().isEmpty())
+ d->data->updateRowEditBuffer(item, COLUMN_ID_COLUMN, QVariant(), false/*!allowSignals*/);
+ d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(false,1));//invisible
+#ifndef KEXI_NO_QUERY_TOTALS
+ d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant());//remove totals
+#endif
+ d->data->updateRowEditBuffer(item, COLUMN_ID_CRITERIA, QVariant());//remove crit.
+ d->sets->removeCurrentPropertySet();
+ }
+ //update property
+ KoProperty::Set *set = d->sets->findPropertySetForItem(*item);
+ if (set) {
+ if ((*set)["isExpression"].value().toBool()==false) {
+ (*set)["table"] = newValue;
+ (*set)["caption"] = QString::null;
+ }
+ else {
+ //do not set table for expr. columns
+ newValue = QVariant();
+ }
+// KoProperty::Set &set = *propertyBuffer();
+ updatePropertiesVisibility(*set);
+ }
+ }
+ else if (colnum==COLUMN_ID_VISIBLE) {
+ bool saveOldValue = true;
+ if (!propertySet()) {
+ saveOldValue = false;
+ createPropertySet( d->dataTable->dataAwareObject()->currentRow(),
+ item->at(COLUMN_ID_TABLE).toString(), item->at(COLUMN_ID_COLUMN).toString(), true );
+#ifndef KEXI_NO_QUERY_TOTALS
+ d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant(0));//totals
+#endif
+ propertySetSwitched();
+ }
+ KoProperty::Set &set = *propertySet();
+ set["visible"].setValue(newValue, saveOldValue);
+ }
+#ifndef KEXI_NO_QUERY_TOTALS
+ else if (colnum==COLUMN_ID_TOTALS) {
+ //TODO:
+ //unused yet
+ setDirty(true);
+ }
+#endif
+ else if (colnum==COLUMN_ID_SORTING) {
+ KoProperty::Set *set = d->sets->findPropertySetForItem(*item);
+ QString table( set->property("table").value().toString() );
+ QString field( set->property("field").value().toString() );
+ if (newValue.toInt()==0 || sortingAllowed(field, table)) {
+ KoProperty::Property &property = set->property("sorting");
+ QString key( property.listData()->keysAsStringList()[ newValue.toInt() ] );
+ kexipluginsdbg << "new key=" << key << endl;
+ property.setValue(key, true);
+ }
+ else { //show msg: sorting is not available
+ result->success = false;
+ result->allowToDiscardChanges = true;
+ result->column = colnum;
+ result->msg = i18n("Could not set sorting for multiple columns (%1)")
+ .arg(table=="*" ? table : (table+".*"));
+ }
+ }
+ else if (colnum==COLUMN_ID_CRITERIA) {
+//! @todo this is primitive, temporary: reuse SQL parser
+ QString operatorStr, argStr;
+ KexiDB::BaseExpr* e = 0;
+ const QString str = newValue.toString().stripWhiteSpace();
+ int token;
+ QString field, table;
+ KoProperty::Set *set = d->sets->findPropertySetForItem(*item);
+ if (set) {
+ field = (*set)["field"].value().toString();
+ table = (*set)["table"].value().toString();
+ }
+ if (!str.isEmpty() && (!set || table=="*" || field.find("*")!=-1)) {
+ //asterisk found! criteria not allowed
+ result->success = false;
+ result->allowToDiscardChanges = true;
+ result->column = colnum;
+ if (propertySet())
+ result->msg = i18n("Could not set criteria for \"%1\"")
+ .arg(table=="*" ? table : field);
+ else
+ result->msg = i18n("Could not set criteria for empty row");
+ //moved to result->allowToDiscardChanges handler //d->dataTable->dataAwareObject()->cancelEditor(); //prevents further editing of this cell
+ }
+ else if (str.isEmpty() || (e = parseExpressionString(str, token, true/*allowRelationalOperator*/)))
+ {
+ if (e) {
+ QString tokenStr;
+ if (token!='=') {
+ KexiDB::BinaryExpr be(KexiDBExpr_Relational, 0, token, 0);
+ tokenStr = be.tokenToString() + " ";
+ }
+ (*set)["criteria"] = tokenStr + e->toString(); //print it prettier
+ //this is just checking: destroy expr. object
+ delete e;
+ }
+ else if (str.isEmpty()) {
+ (*set)["criteria"] = QVariant(); //clear it
+ }
+ setDirty(true);
+ }
+ else {
+ result->success = false;
+ result->allowToDiscardChanges = true;
+ result->column = colnum;
+ result->msg = i18n("Invalid criteria \"%1\"").arg(newValue.toString());
+ }
+ }
+}
+
+void KexiQueryDesignerGuiEditor::slotTablePositionChanged(KexiRelationViewTableContainer*)
+{
+ setDirty(true);
+}
+
+void KexiQueryDesignerGuiEditor::slotAboutConnectionRemove(KexiRelationViewConnection*)
+{
+ setDirty(true);
+}
+
+void KexiQueryDesignerGuiEditor::slotTableFieldDoubleClicked(
+ KexiDB::TableSchema* table, const QString& fieldName )
+{
+ if (!table || (!table->field(fieldName) && fieldName!="*"))
+ return;
+ int row_num;
+ //find last filled row in the GUI table
+ for (row_num=d->sets->size()-1; row_num>=0 && !d->sets->at(row_num); row_num--)
+ ;
+ row_num++; //after
+ //add row
+ KexiTableItem *newItem = createNewRow(table->name(), fieldName, true /* visible*/);
+ d->dataTable->dataAwareObject()->insertItem(newItem, row_num);
+ d->dataTable->dataAwareObject()->setCursorPosition(row_num, 0);
+ //create buffer
+ createPropertySet( row_num, table->name(), fieldName, true/*new one*/ );
+ propertySetSwitched();
+ d->dataTable->setFocus();
+}
+
+KoProperty::Set *KexiQueryDesignerGuiEditor::propertySet()
+{
+ return d->sets->currentPropertySet();
+}
+
+void KexiQueryDesignerGuiEditor::updatePropertiesVisibility(KoProperty::Set& set)
+{
+ const bool asterisk = isAsterisk(
+ set["table"].value().toString(), set["field"].value().toString()
+ );
+#ifndef KEXI_NO_UNFINISHED
+ set["caption"].setVisible( !asterisk );
+#endif
+ set["alias"].setVisible( !asterisk );
+/*always invisible #ifndef KEXI_NO_UNFINISHED
+ set["sorting"].setVisible( !asterisk );
+#endif*/
+ propertySetReloaded(true);
+}
+
+KoProperty::Set*
+KexiQueryDesignerGuiEditor::createPropertySet( int row,
+ const QString& tableName, const QString& fieldName, bool newOne )
+{
+ //const bool asterisk = isAsterisk(tableName, fieldName);
+ QString typeName = "KexiQueryDesignerGuiEditor::Column";
+ KoProperty::Set *set = new KoProperty::Set(d->sets, typeName);
+ KoProperty::Property *prop;
+
+ //meta-info for property editor
+ set->addProperty(prop = new KoProperty::Property("this:classString", i18n("Query column")) );
+ prop->setVisible(false);
+//! \todo add table_field icon (add buff->addProperty(prop = new KexiProperty("this:iconName", "table_field") );
+// prop->setVisible(false);
+
+ set->addProperty(prop = new KoProperty::Property("table", QVariant(tableName)) );
+ prop->setVisible(false);//always hidden
+
+ set->addProperty(prop = new KoProperty::Property("field", QVariant(fieldName)) );
+ prop->setVisible(false);//always hidden
+
+ set->addProperty(prop = new KoProperty::Property("caption", QVariant(QString::null), i18n("Caption") ) );
+#ifdef KEXI_NO_UNFINISHED
+ prop->setVisible(false);
+#endif
+
+ set->addProperty(prop = new KoProperty::Property("alias", QVariant(QString::null), i18n("Alias")) );
+
+ set->addProperty(prop = new KoProperty::Property("visible", QVariant(true, 4)) );
+ prop->setVisible(false);
+
+/*TODO:
+ set->addProperty(prop = new KexiProperty("totals", QVariant(QString::null)) );
+ prop->setVisible(false);*/
+
+ //sorting
+ QStringList slist, nlist;
+ slist << "nosorting" << "ascending" << "descending";
+ nlist << i18n("None") << i18n("Ascending") << i18n("Descending");
+ set->addProperty(prop = new KoProperty::Property("sorting",
+ slist, nlist, *slist.at(0), i18n("Sorting")));
+ prop->setVisible(false);
+
+ set->addProperty(prop = new KoProperty::Property("criteria", QVariant(QString::null)) );
+ prop->setVisible(false);
+
+ set->addProperty(prop = new KoProperty::Property("isExpression", QVariant(false, 1)) );
+ prop->setVisible(false);
+
+ connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)),
+ this, SLOT(slotPropertyChanged(KoProperty::Set&, KoProperty::Property&)));
+
+ d->sets->insert(row, set, newOne);
+
+ updatePropertiesVisibility(*set);
+ return set;
+}
+
+void KexiQueryDesignerGuiEditor::setFocus()
+{
+ d->dataTable->setFocus();
+}
+
+void KexiQueryDesignerGuiEditor::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property)
+{
+ const QCString& pname = property.name();
+/*
+ * TODO (js) use KexiProperty::setValidator(QString) when implemented as described in TODO #60
+ */
+ if (pname=="alias" || pname=="name") {
+ const QVariant& v = property.value();
+ if (!v.toString().stripWhiteSpace().isEmpty() && !KexiUtils::isIdentifier( v.toString() )) {
+ KMessageBox::sorry(this,
+ KexiUtils::identifierExpectedMessage(property.caption(), v.toString()));
+ property.resetValue();
+ }
+ if (pname=="alias") {
+ if (set["isExpression"].value().toBool()==true) {
+ //update value in column #1
+ d->dataTable->dataAwareObject()->acceptEditor();
+// d->dataTable->dataAwareObject()->setCursorPosition(d->dataTable->dataAwareObject()->currentRow(),0);
+ //d->dataTable->dataAwareObject()->startEditCurrentCell();
+ d->data->updateRowEditBuffer(d->dataTable->dataAwareObject()->selectedItem(),
+ 0, QVariant(set["alias"].value().toString() + ": " + set["field"].value().toString()));
+ d->data->saveRowChanges(*d->dataTable->dataAwareObject()->selectedItem(), true);
+// d->dataTable->dataAwareObject()->acceptRowEdit();
+ }
+ }
+ }
+}
+
+void KexiQueryDesignerGuiEditor::slotNewItemStored(KexiPart::Item& item)
+{
+ d->relations->objectCreated(item.mimeType(), item.name().latin1());
+}
+
+void KexiQueryDesignerGuiEditor::slotItemRemoved(const KexiPart::Item& item)
+{
+ d->relations->objectDeleted(item.mimeType(), item.name().latin1());
+}
+
+void KexiQueryDesignerGuiEditor::slotItemRenamed(const KexiPart::Item& item, const QCString& oldName)
+{
+ d->relations->objectRenamed(item.mimeType(), oldName, item.name().latin1());
+}
+
+#include "kexiquerydesignerguieditor.moc"
+
diff --git a/kexi/plugins/queries/kexiquerydesignerguieditor.h b/kexi/plugins/queries/kexiquerydesignerguieditor.h
new file mode 100644
index 000000000..03acb7f66
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerydesignerguieditor.h
@@ -0,0 +1,170 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIQUERYDESIGNERGUIEDITOR_H
+#define KEXIQUERYDESIGNERGUIEDITOR_H
+
+#include <qguardedptr.h>
+#include <qsplitter.h>
+
+#include <kexiviewbase.h>
+#include "kexiquerypart.h"
+
+class KexiMainWindow;
+class KexiTableViewData;
+class KexiDataTable;
+class KexiTableItem;
+class KexiRelationWidget;
+class KexiSectionHeader;
+class KexiDataAwarePropertySet;
+class KexiRelationViewTableContainer;
+class KexiRelationViewConnection;
+
+namespace KexiPart
+{
+ class Item;
+}
+
+namespace KoProperty {
+ class Property;
+ class Set;
+}
+
+namespace KexiDB
+{
+ class Connection;
+ class QuerySchema;
+ class TableSchema;
+ class ResultInfo;
+}
+
+//! Design view of the Query Designer
+class KexiQueryDesignerGuiEditor : public KexiViewBase
+{
+ Q_OBJECT
+
+ public:
+ KexiQueryDesignerGuiEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0);
+ virtual ~KexiQueryDesignerGuiEditor();
+
+// KexiDB::QuerySchema *schema();
+
+ KexiRelationWidget *relationView() const;
+
+ virtual QSize sizeHint() const;
+
+ public slots:
+ virtual void setFocus();
+
+ protected:
+ void initTableColumns(); //!< Called just once.
+ void initTableRows(); //!< Called to have all rows empty.
+//unused void addRow(const QString &tbl, const QString &field);
+// void restore();
+ virtual tristate beforeSwitchTo(int mode, bool &dontStore);
+ virtual tristate afterSwitchFrom(int mode);
+
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+ virtual tristate storeData(bool dontAsk = false);
+
+ /*! Updates data in columns depending on tables that are currently inserted.
+ Tabular Data in combo box popups is updated as well. */
+ void updateColumnsData();
+
+ /*! \return property buffer associated with currently selected row (i.e. field)
+ or 0 if current row is empty. */
+ virtual KoProperty::Set *propertySet();
+
+ KoProperty::Set* createPropertySet( int row,
+ const QString& tableName, const QString& fieldName, bool newOne = false );
+
+ /*! Builds query schema out of information provided by gui.
+ The schema is stored in temp->query member.
+ \a errMsg is optional error message returned.
+ \return true on proper schema creation. */
+ bool buildSchema(QString *errMsg = 0);
+
+ KexiQueryPart::TempData * tempData() const;
+
+ /*! Helper: allocates and initializes new table view's row. Doesn't insert it, just returns.
+ \a tableName and \a fieldName shoudl be provided.
+ \a visible flag sets value for "Visible" column. */
+ KexiTableItem* createNewRow(const QString& tableName, const QString& fieldName,
+ bool visible) const;
+
+ KexiDB::BaseExpr* parseExpressionString(const QString& fullString, int& token,
+ bool allowRelationalOperator);
+
+ QCString generateUniqueAlias() const;
+ void updatePropertiesVisibility(KoProperty::Set& buf);
+
+ protected slots:
+ void slotDragOverTableRow(KexiTableItem *item, int row, QDragMoveEvent* e);
+ void slotDroppedAtRow(KexiTableItem *item, int row,
+ QDropEvent *ev, KexiTableItem*& newItem);
+ //! Reaction on appending a new item after deleting one
+ void slotNewItemAppendedForAfterDeletingInSpreadSheetMode();
+ void slotTableAdded(KexiDB::TableSchema &t);
+ void slotTableHidden(KexiDB::TableSchema &t);
+
+ //! Called before cell change in tableview.
+ void slotBeforeCellChanged(KexiTableItem *item, int colnum,
+ QVariant& newValue, KexiDB::ResultInfo* result);
+
+ void slotRowInserted(KexiTableItem* item, uint row, bool repaint);
+ void slotTablePositionChanged(KexiRelationViewTableContainer*);
+ void slotAboutConnectionRemove(KexiRelationViewConnection*);
+ void slotTableFieldDoubleClicked( KexiDB::TableSchema* table, const QString& fieldName );
+
+ /*! Loads layout of relation GUI diagram. */
+ bool loadLayout();
+
+ /*! Stores layout of relation GUI diagram. */
+ bool storeLayout();
+
+ void showTablesForQuery(KexiDB::QuerySchema *query);
+ //! @internal
+ void showFieldsOrRelationsForQueryInternal(
+ KexiDB::QuerySchema *query, bool showFields, bool showRelations, KexiDB::ResultInfo& result);
+ //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, true, true)
+ void showFieldsAndRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result);
+ //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, true, false)
+ void showFieldsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result);
+ //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, false, true)
+ void showRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result);
+
+ void addConnection(KexiDB::Field *masterField, KexiDB::Field *detailsField);
+
+ void slotPropertyChanged(KoProperty::Set& list, KoProperty::Property& property);
+
+// void slotObjectCreated(const QCString &mime, const QCString& name);
+ void slotNewItemStored(KexiPart::Item&);
+ void slotItemRemoved(const KexiPart::Item& item);
+ void slotItemRenamed(const KexiPart::Item& item, const QCString& oldName);
+
+ private:
+ class Private;
+ Private *d;
+
+ friend class KexiQueryView; // for storeNewData() and storeData() only
+};
+
+#endif
+
diff --git a/kexi/plugins/queries/kexiquerydesignersql.cpp b/kexi/plugins/queries/kexiquerydesignersql.cpp
new file mode 100644
index 000000000..469d551c5
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerydesignersql.cpp
@@ -0,0 +1,542 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qsplitter.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qtimer.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <kiconloader.h>
+
+#include <kexiutils/utils.h>
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/parser/parser.h>
+
+#include <kexiproject.h>
+#include <keximainwindow.h>
+
+#include "kexiquerydesignersqleditor.h"
+#include "kexiquerydesignersqlhistory.h"
+#include "kexiquerydesignersql.h"
+#include "kexiquerypart.h"
+
+#include "kexisectionheader.h"
+
+
+static bool compareSQL(const QString& sql1, const QString& sql2)
+{
+ //TODO: use reformatting functions here
+ return sql1.stripWhiteSpace()==sql2.stripWhiteSpace();
+}
+
+//===================
+
+//! @internal
+class KexiQueryDesignerSQLView::Private
+{
+ public:
+ Private() :
+ history(0)
+ , historyHead(0)
+ , statusPixmapOk( DesktopIcon("button_ok") )
+ , statusPixmapErr( DesktopIcon("button_cancel") )
+ , statusPixmapInfo( DesktopIcon("messagebox_info") )
+ , parsedQuery(0)
+ , heightForStatusMode(-1)
+ , heightForHistoryMode(-1)
+ , eventFilterForSplitterEnabled(true)
+ , justSwitchedFromNoViewMode(false)
+ , slotTextChangedEnabled(true)
+ {
+ }
+ KexiQueryDesignerSQLEditor *editor;
+ KexiQueryDesignerSQLHistory *history;
+ QLabel *pixmapStatus, *lblStatus;
+ QHBox *status_hbox;
+ QVBox *history_section;
+ KexiSectionHeader *head, *historyHead;
+ QPixmap statusPixmapOk, statusPixmapErr, statusPixmapInfo;
+ QSplitter *splitter;
+ KToggleAction *action_toggle_history;
+ //! For internal use, this pointer is usually copied to TempData structure,
+ //! when switching out of this view (then it's cleared).
+ KexiDB::QuerySchema *parsedQuery;
+ //! For internal use, statement passed in switching to this view
+ QString origStatement;
+ //! needed to remember height for both modes, between switching
+ int heightForStatusMode, heightForHistoryMode;
+ //! helper for slotUpdateMode()
+ bool action_toggle_history_was_checked : 1;
+ //! helper for eventFilter()
+ bool eventFilterForSplitterEnabled : 1;
+ //! helper for beforeSwitchTo()
+ bool justSwitchedFromNoViewMode : 1;
+ //! helper for slotTextChanged()
+ bool slotTextChangedEnabled : 1;
+};
+
+//===================
+
+KexiQueryDesignerSQLView::KexiQueryDesignerSQLView(KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : KexiViewBase(mainWin, parent, name)
+ , d( new Private() )
+{
+ d->splitter = new QSplitter(this);
+ d->splitter->setOrientation(Vertical);
+ d->head = new KexiSectionHeader(i18n("SQL Query Text"), Vertical, d->splitter);
+ d->editor = new KexiQueryDesignerSQLEditor(mainWin, d->head, "sqle");
+// d->editor->installEventFilter(this);//for keys
+ connect(d->editor, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
+ addChildView(d->editor);
+ setViewWidget(d->editor);
+ d->splitter->setFocusProxy(d->editor);
+ setFocusProxy(d->editor);
+
+ d->history_section = new QVBox(d->splitter);
+
+ d->status_hbox = new QHBox(d->history_section);
+ d->status_hbox->installEventFilter(this);
+ d->splitter->setResizeMode(d->history_section, QSplitter::KeepSize);
+ d->status_hbox->setSpacing(0);
+ d->pixmapStatus = new QLabel(d->status_hbox);
+ d->pixmapStatus->setFixedWidth(d->statusPixmapOk.width()*3/2);
+ d->pixmapStatus->setAlignment(AlignHCenter | AlignTop);
+ d->pixmapStatus->setMargin(d->statusPixmapOk.width()/4);
+ d->pixmapStatus->setPaletteBackgroundColor( palette().active().color(QColorGroup::Base) );
+
+ d->lblStatus = new QLabel(d->status_hbox);
+ d->lblStatus->setAlignment(AlignLeft | AlignTop | WordBreak);
+ d->lblStatus->setMargin(d->statusPixmapOk.width()/4);
+ d->lblStatus->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
+ d->lblStatus->resize(d->lblStatus->width(),d->statusPixmapOk.width()*3);
+ d->lblStatus->setPaletteBackgroundColor( palette().active().color(QColorGroup::Base) );
+
+ QHBoxLayout *b = new QHBoxLayout(this);
+ b->addWidget(d->splitter);
+
+ plugSharedAction("querypart_check_query", this, SLOT(slotCheckQuery()));
+ plugSharedAction("querypart_view_toggle_history", this, SLOT(slotUpdateMode()));
+ d->action_toggle_history = static_cast<KToggleAction*>( sharedAction( "querypart_view_toggle_history" ) );
+
+ d->historyHead = new KexiSectionHeader(i18n("SQL Query History"), Vertical, d->history_section);
+ d->historyHead->installEventFilter(this);
+ d->history = new KexiQueryDesignerSQLHistory(d->historyHead, "sql_history");
+
+ static const QString msg_back = i18n("Back to Selected Query");
+ static const QString msg_clear = i18n("Clear History");
+ d->historyHead->addButton("select_item", msg_back, this, SLOT(slotSelectQuery()));
+ d->historyHead->addButton("editclear", msg_clear, d->history, SLOT(clear()));
+ d->history->popupMenu()->insertItem(SmallIcon("select_item"), msg_back, this, SLOT(slotSelectQuery()));
+ d->history->popupMenu()->insertItem(SmallIcon("editclear"), msg_clear, d->history, SLOT(clear()));
+ connect(d->history, SIGNAL(currentItemDoubleClicked()), this, SLOT(slotSelectQuery()));
+
+ d->heightForHistoryMode = -1; //height() / 2;
+ //d->historyHead->hide();
+ d->action_toggle_history_was_checked = !d->action_toggle_history->isChecked(); //to force update
+ slotUpdateMode();
+ slotCheckQuery();
+}
+
+KexiQueryDesignerSQLView::~KexiQueryDesignerSQLView()
+{
+ delete d;
+}
+
+KexiQueryDesignerSQLEditor *KexiQueryDesignerSQLView::editor() const
+{
+ return d->editor;
+}
+
+void KexiQueryDesignerSQLView::setStatusOk()
+{
+ d->pixmapStatus->setPixmap(d->statusPixmapOk);
+ setStatusText("<h2>"+i18n("The query is correct")+"</h2>");
+ d->history->addEvent(d->editor->text().stripWhiteSpace(), true, QString::null);
+}
+
+void KexiQueryDesignerSQLView::setStatusError(const QString& msg)
+{
+ d->pixmapStatus->setPixmap(d->statusPixmapErr);
+ setStatusText("<h2>"+i18n("The query is incorrect")+"</h2><p>"+msg+"</p>");
+ d->history->addEvent(d->editor->text().stripWhiteSpace(), false, msg);
+}
+
+void KexiQueryDesignerSQLView::setStatusEmpty()
+{
+ d->pixmapStatus->setPixmap(d->statusPixmapInfo);
+ setStatusText(i18n("Please enter your query and execute \"Check query\" function to verify it."));
+}
+
+void KexiQueryDesignerSQLView::setStatusText(const QString& text)
+{
+ if (!d->action_toggle_history->isChecked()) {
+ QSimpleRichText rt(text, d->lblStatus->font());
+ rt.setWidth(d->lblStatus->width());
+ QValueList<int> sz = d->splitter->sizes();
+ const int newHeight = rt.height()+d->lblStatus->margin()*2;
+ if (sz[1]<newHeight) {
+ sz[1] = newHeight;
+ d->splitter->setSizes(sz);
+ }
+ d->lblStatus->setText(text);
+ }
+}
+
+tristate
+KexiQueryDesignerSQLView::beforeSwitchTo(int mode, bool &dontStore)
+{
+//TODO
+ dontStore = true;
+ if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
+ QString sqlText = d->editor->text().stripWhiteSpace();
+ KexiQueryPart::TempData * temp = tempData();
+ if (sqlText.isEmpty()) {
+ //special case: empty SQL text
+ if (temp->query()) {
+ temp->queryChangedInPreviousView = true; //query changed
+ temp->setQuery(0);
+// delete temp->query; //safe?
+// temp->query = 0;
+ }
+ }
+ else {
+ const bool designViewWasVisible = parentDialog()->viewForMode(mode)!=0;
+ //should we check SQL text?
+ if (designViewWasVisible
+ && !d->justSwitchedFromNoViewMode //unchanged, but we should check SQL text
+ && compareSQL(d->origStatement, d->editor->text())) {
+ //statement unchanged! - nothing to do
+ temp->queryChangedInPreviousView = false;
+ }
+ else {
+ //yes: parse SQL text
+ if (!slotCheckQuery()) {
+ if (KMessageBox::No==KMessageBox::warningYesNo(this, "<p>"+i18n("The query you entered is incorrect.")
+ +"</p><p>"+i18n("Do you want to cancel any changes made to this SQL text?")+"</p>"
+ +"</p><p>"+i18n("Answering \"No\" allows you to make corrections.")+"</p>"))
+ {
+ return cancelled;
+ }
+ //do not change original query - it's invalid
+ temp->queryChangedInPreviousView = false;
+ //this view is no longer _just_ switched from "NoViewMode"
+ d->justSwitchedFromNoViewMode = false;
+ return true;
+ }
+ //this view is no longer _just_ switched from "NoViewMode"
+ d->justSwitchedFromNoViewMode = false;
+ //replace old query schema with new one
+ temp->setQuery( d->parsedQuery ); //this will also delete temp->query()
+// delete temp->query; //safe?
+// temp->query = d->parsedQuery;
+ d->parsedQuery = 0;
+ temp->queryChangedInPreviousView = true;
+ }
+ }
+ }
+
+ //TODO
+ /*
+ if (d->doc) {
+ KexiDB::Parser *parser = new KexiDB::Parser(mainWin()->project()->dbConnection());
+ parser->parse(getQuery());
+ d->doc->setSchema(parser->select());
+
+ if(parser->operation() == KexiDB::Parser::OP_Error)
+ {
+ d->history->addEvent(getQuery(), false, parser->error().error());
+ kdDebug() << "KexiQueryDesignerSQLView::beforeSwitchTo(): syntax error!" << endl;
+ return false;
+ }
+ delete parser;
+ }
+
+ setDirty(true);*/
+// if (parentDialog()->hasFocus())
+ d->editor->setFocus();
+ return true;
+}
+
+tristate
+KexiQueryDesignerSQLView::afterSwitchFrom(int mode)
+{
+ kdDebug() << "KexiQueryDesignerSQLView::afterSwitchFrom()" << endl;
+// if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
+ if (mode==Kexi::NoViewMode) {
+ //User opened text view _directly_.
+ //This flag is set to indicate for beforeSwitchTo() that even if text has not been changed,
+ //SQL text should be invalidated.
+ d->justSwitchedFromNoViewMode = true;
+ }
+ KexiQueryPart::TempData * temp = tempData();
+ KexiDB::QuerySchema *query = temp->query();
+ if (!query) {//try to just get saved schema, instead of temporary one
+ query = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
+ }
+
+ if (mode!=0/*failure only if it is switching from prev. view*/ && !query) {
+ //TODO msg
+ return false;
+ }
+
+ if (!query) {
+ //no valid query schema delivered: just load sql text, no matter if it's valid
+ if (!loadDataBlock( d->origStatement, "sql", true /*canBeEmpty*/ ))
+ return false;
+ }
+ else {
+ // Use query with Kexi keywords (but not driver-specific keywords) escaped.
+ temp->setQuery( query );
+// temp->query = query;
+ KexiDB::Connection* conn = mainWin()->project()->dbConnection();
+ KexiDB::Connection::SelectStatementOptions options;
+ options.identifierEscaping = KexiDB::Driver::EscapeKexi;
+ options.addVisibleLookupColumns = false;
+ d->origStatement = conn->selectStatement(*query, options).stripWhiteSpace();
+ }
+
+ d->slotTextChangedEnabled = false;
+ d->editor->setText( d->origStatement );
+ d->slotTextChangedEnabled = true;
+ QTimer::singleShot(100, d->editor, SLOT(setFocus()));
+ return true;
+}
+
+QString
+KexiQueryDesignerSQLView::sqlText() const
+{
+ return d->editor->text();
+}
+
+bool KexiQueryDesignerSQLView::slotCheckQuery()
+{
+ QString sqlText( d->editor->text().stripWhiteSpace() );
+ if (sqlText.isEmpty()) {
+ delete d->parsedQuery;
+ d->parsedQuery = 0;
+ setStatusEmpty();
+ return true;
+ }
+
+ kdDebug() << "KexiQueryDesignerSQLView::slotCheckQuery()" << endl;
+ //KexiQueryPart::TempData * temp = tempData();
+ KexiDB::Parser *parser = mainWin()->project()->sqlParser();
+ const bool ok = parser->parse( sqlText );
+ delete d->parsedQuery;
+ d->parsedQuery = parser->query();
+ if (!d->parsedQuery || !ok || !parser->error().type().isEmpty()) {
+ KexiDB::ParserError err = parser->error();
+ setStatusError(err.error());
+ d->editor->jump(err.at());
+ delete d->parsedQuery;
+ d->parsedQuery = 0;
+ return false;
+ }
+
+ setStatusOk();
+ return true;
+}
+
+void KexiQueryDesignerSQLView::slotUpdateMode()
+{
+ if (d->action_toggle_history->isChecked() == d->action_toggle_history_was_checked)
+ return;
+
+ d->eventFilterForSplitterEnabled = false;
+
+ QValueList<int> sz = d->splitter->sizes();
+ d->action_toggle_history_was_checked = d->action_toggle_history->isChecked();
+ int heightToSet = -1;
+ if (d->action_toggle_history->isChecked()) {
+ d->status_hbox->hide();
+ d->historyHead->show();
+ d->history->show();
+ if (d->heightForHistoryMode==-1)
+ d->heightForHistoryMode = m_dialog->height() / 2;
+ heightToSet = d->heightForHistoryMode;
+ d->heightForStatusMode = sz[1]; //remember
+ }
+ else {
+ if (d->historyHead)
+ d->historyHead->hide();
+ d->status_hbox->show();
+ if (d->heightForStatusMode>=0) {
+ heightToSet = d->heightForStatusMode;
+ } else {
+ d->heightForStatusMode = d->status_hbox->height();
+ }
+ if (d->heightForHistoryMode>=0)
+ d->heightForHistoryMode = sz[1];
+ }
+
+ if (heightToSet>=0) {
+ sz[1] = heightToSet;
+ d->splitter->setSizes(sz);
+ }
+ d->eventFilterForSplitterEnabled = true;
+ slotCheckQuery();
+}
+
+void KexiQueryDesignerSQLView::slotTextChanged()
+{
+ if (!d->slotTextChangedEnabled)
+ return;
+ setDirty(true);
+ setStatusEmpty();
+}
+
+bool KexiQueryDesignerSQLView::eventFilter( QObject *o, QEvent *e )
+{
+ if (d->eventFilterForSplitterEnabled) {
+ if (e->type()==QEvent::Resize && o && o==d->historyHead && d->historyHead->isVisible()) {
+ d->heightForHistoryMode = d->historyHead->height();
+ }
+ else if (e->type()==QEvent::Resize && o && o==d->status_hbox && d->status_hbox->isVisible()) {
+ d->heightForStatusMode = d->status_hbox->height();
+ }
+ }
+ return KexiViewBase::eventFilter(o, e);
+}
+
+void KexiQueryDesignerSQLView::updateActions(bool activated)
+{
+ if (activated) {
+ slotUpdateMode();
+ }
+ setAvailable("querypart_check_query", true);
+ setAvailable("querypart_view_toggle_history", true);
+ KexiViewBase::updateActions(activated);
+}
+
+void KexiQueryDesignerSQLView::slotSelectQuery()
+{
+ QString sql = d->history->selectedStatement();
+ if (!sql.isEmpty()) {
+ d->editor->setText( sql );
+ }
+}
+
+KexiQueryPart::TempData *
+KexiQueryDesignerSQLView::tempData() const
+{
+ return dynamic_cast<KexiQueryPart::TempData*>(parentDialog()->tempData());
+}
+
+KexiDB::SchemaData*
+KexiQueryDesignerSQLView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ Q_UNUSED( cancel );
+
+ //here: we won't store query layout: it will be recreated 'by hand' in GUI Query Editor
+ bool queryOK = slotCheckQuery();
+ bool ok = true;
+ KexiDB::SchemaData* query = 0;
+ if (queryOK) {
+ //query is ok
+ if (d->parsedQuery) {
+ query = d->parsedQuery; //will be returned, so: don't keep it
+ d->parsedQuery = 0;
+ }
+ else {//empty query
+ query = new KexiDB::SchemaData(); //just empty
+ }
+
+ (KexiDB::SchemaData&)*query = sdata; //copy main attributes
+ ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
+ if (ok) {
+ m_dialog->setId( query->id() );
+ ok = storeDataBlock( d->editor->text(), "sql" );
+ }
+ }
+ else {
+ //query is not ok
+//#if 0
+ //TODO: allow saving invalid queries
+ //TODO: just ask this question:
+ query = new KexiDB::SchemaData(); //just empty
+
+ ok = (KMessageBox::questionYesNo(this, i18n("Do you want to save invalid query?"),
+ 0, KStdGuiItem::yes(), KStdGuiItem::no(), "askBeforeSavingInvalidQueries"/*config entry*/)==KMessageBox::Yes);
+ if (ok) {
+ (KexiDB::SchemaData&)*query = sdata; //copy main attributes
+ ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
+ }
+ if (ok) {
+ m_dialog->setId( query->id() );
+ ok = storeDataBlock( d->editor->text(), "sql" );
+ }
+//#else
+ //ok = false;
+//#endif
+ }
+ if (!ok) {
+ delete query;
+ query = 0;
+ }
+ return query;
+}
+
+tristate KexiQueryDesignerSQLView::storeData(bool dontAsk)
+{
+ tristate res = KexiViewBase::storeData(dontAsk);
+ if (~res)
+ return res;
+ if (res == true) {
+ res = storeDataBlock( d->editor->text(), "sql" );
+#if 0
+ bool queryOK = slotCheckQuery();
+ if (queryOK) {
+ res = storeDataBlock( d->editor->text(), "sql" );
+ }
+ else {
+ //query is not ok
+ //TODO: allow saving invalid queries
+ //TODO: just ask this question:
+ res = false;
+ }
+#endif
+ }
+ if (res == true) {
+ QString empty_xml;
+ res = storeDataBlock( empty_xml, "query_layout" ); //clear
+ }
+ if (!res)
+ setDirty(true);
+ return res;
+}
+
+
+/*void KexiQueryDesignerSQLView::slotHistoryHeaderButtonClicked(const QString& buttonIdentifier)
+{
+ if (buttonIdentifier=="select_query") {
+ slotSelectQuery();
+ }
+ else if (buttonIdentifier=="clear_history") {
+ d->history->clear();
+ }
+}*/
+
+#include "kexiquerydesignersql.moc"
+
diff --git a/kexi/plugins/queries/kexiquerydesignersql.h b/kexi/plugins/queries/kexiquerydesignersql.h
new file mode 100644
index 000000000..f31c838f6
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerydesignersql.h
@@ -0,0 +1,82 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIQUERYDESIGNERSQL_H
+#define KEXIQUERYDESIGNERSQL_H
+
+#include <kexiviewbase.h>
+#include "kexiquerypart.h"
+
+class KexiQueryDesignerSQLEditor;
+class KexiQueryDesignerSQLViewPrivate;
+
+//! The KexiQueryDesignerSQLView class for editing Queries in text mode.
+/*! It is a view containing SQL text editor
+ and SQL history/status widget splitted vertically.
+ Depending on user's will, the widget can be in "sql history"
+ mode or in "sql status" mode. */
+class KexiQueryDesignerSQLView : public KexiViewBase
+{
+ Q_OBJECT
+
+ public:
+ KexiQueryDesignerSQLView(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0);
+ virtual ~KexiQueryDesignerSQLView();
+
+ QString sqlText() const;
+ KexiQueryDesignerSQLEditor *editor() const;
+
+ virtual bool eventFilter ( QObject *o, QEvent *e );
+
+ protected:
+ KexiQueryPart::TempData * tempData() const;
+
+ virtual tristate beforeSwitchTo(int mode, bool &dontStore);
+ virtual tristate afterSwitchFrom(int mode);
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+ virtual tristate storeData(bool dontAsk = false);
+
+ void setStatusOk();
+ void setStatusError(const QString& msg);
+ void setStatusEmpty();
+ void setStatusText(const QString& text);
+
+ virtual void updateActions(bool activated);
+
+ protected slots:
+ /*! Performs query checking (by text parsing). \return true and sets d->parsedQuery
+ to the new query schema object on success. */
+ bool slotCheckQuery();
+ void slotUpdateMode();
+ void slotTextChanged();
+// void slotHistoryHeaderButtonClicked(const QString& buttonIdentifier);
+ void slotSelectQuery();
+
+ signals:
+ void queryShortcut();
+
+ private:
+ class Private;
+ Private *d;
+
+ friend class KexiQueryView; // for storeNewData() and storeData() only
+};
+
+#endif
diff --git a/kexi/plugins/queries/kexiquerydesignersqlhistory.cpp b/kexi/plugins/queries/kexiquerydesignersqlhistory.cpp
new file mode 100644
index 000000000..d86caf83c
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerydesignersqlhistory.cpp
@@ -0,0 +1,373 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qpainter.h>
+#include <qclipboard.h>
+#include <qregexp.h>
+
+#include <kpopupmenu.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <kapplication.h>
+
+#include "kexiquerydesignersqlhistory.h"
+
+KexiQueryDesignerSQLHistory::KexiQueryDesignerSQLHistory(QWidget *parent, const char *name)
+ : QScrollView(parent, name)
+{
+ viewport()->setPaletteBackgroundColor(white);
+
+ m_selected = 0;
+ m_history = new History();
+ m_history->setAutoDelete(true);
+
+ m_popup = new KPopupMenu(this);
+ m_popup->insertItem(SmallIcon("editcopy"), i18n("Copy to Clipboard"), this, SLOT(slotToClipboard()));
+}
+
+KexiQueryDesignerSQLHistory::~KexiQueryDesignerSQLHistory()
+{
+}
+
+void
+KexiQueryDesignerSQLHistory::drawContents(QPainter *p, int cx, int cy, int cw, int ch)
+{
+ QRect clipping(cx, cy, cw, ch);
+
+ int y = 0;
+ for(HistoryEntry *it = m_history->first(); it; it = m_history->next())
+ {
+// it->drawItem(p, visibleWidth());
+ if(clipping.intersects(it->geometry(y, visibleWidth(), fontMetrics())))
+ {
+ p->saveWorldMatrix();
+ p->translate(0, y);
+ it->drawItem(p, visibleWidth(), colorGroup());
+ p->restoreWorldMatrix();
+ }
+ y += it->geometry(y, visibleWidth(), fontMetrics()).height() + 5;
+ }
+}
+
+void
+KexiQueryDesignerSQLHistory::contentsMousePressEvent(QMouseEvent * e)
+{
+ int y = 0;
+ HistoryEntry *popupHistory = 0;
+ int pos;
+ for(QPtrListIterator<HistoryEntry> it(*m_history); it.current(); ++it)
+ {
+ if(it.current()->isSelected())
+ {
+ //clear
+ it.current()->setSelected(false, colorGroup());
+ updateContents(it.current()->geometry(y, visibleWidth(), fontMetrics()));
+ }
+
+ if(it.current()->geometry(y, visibleWidth(), fontMetrics()).contains(e->pos()))
+ {
+ popupHistory = it.current();
+ pos = y;
+ }
+ y += it.current()->geometry(y, visibleWidth(), fontMetrics()).height() + 5;
+ }
+
+ //now do update
+ if (popupHistory) {
+ if (m_selected && m_selected != popupHistory) {
+ m_selected->setSelected(false, colorGroup());
+ updateContents(m_selected->geometry(pos, visibleWidth(), fontMetrics()));
+ }
+ m_selected = popupHistory;
+ m_selected->setSelected(true, colorGroup());
+ updateContents(m_selected->geometry(pos, visibleWidth(), fontMetrics()));
+ if(e->button() == RightButton) {
+ m_popup->exec(e->globalPos());
+ }
+ }
+}
+
+void
+KexiQueryDesignerSQLHistory::contentsMouseDoubleClickEvent(QMouseEvent * e)
+{
+ contentsMousePressEvent(e);
+ if (m_selected)
+ emit currentItemDoubleClicked();
+}
+
+void
+KexiQueryDesignerSQLHistory::addEvent(const QString& q, bool s, const QString &error)
+{
+ HistoryEntry *he=m_history->last();
+ if (he) {
+ if (he->statement()==q) {
+ he->updateTime(QTime::currentTime());
+ repaint();
+ return;
+ }
+ }
+ addEntry(new HistoryEntry(s, QTime::currentTime(), q, error));
+}
+
+void
+KexiQueryDesignerSQLHistory::addEntry(HistoryEntry *e)
+{
+ m_history->append(e);
+// m_history->prepend(e);
+
+ int y = 0;
+ for(HistoryEntry *it = m_history->first(); it; it = m_history->next())
+ {
+ y += it->geometry(y, visibleWidth(), fontMetrics()).height() + 5;
+ }
+
+ resizeContents(visibleWidth() - 1, y);
+ if (m_selected) {
+ m_selected->setSelected(false, colorGroup());
+ }
+ m_selected = e;
+ m_selected->setSelected(true, colorGroup());
+ ensureVisible(0,y+5);
+ updateContents();
+/* ensureVisible(0, 0);
+ if (m_selected) {
+ m_selected->setSelected(false, colorGroup());
+ }
+ m_selected = e;
+ m_selected->setSelected(true, colorGroup());
+// updateContents();
+ updateContents(m_selected->geometry(0, visibleWidth(), fontMetrics()));*/
+}
+
+/*void
+KexiQueryDesignerSQLHistory::contextMenu(const QPoint &pos, HistoryEntry *)
+{
+ KPopupMenu p(this);
+ p.insertItem(SmallIcon("editcopy"), i18n("Copy to Clipboard"), this, SLOT(slotToClipboard()));
+
+
+#ifndef KEXI_NO_UNFINISHED
+ p.insertSeparator();
+ p.insertItem(SmallIcon("edit"), i18n("Edit"), this, SLOT(slotEdit()));
+ p.insertItem(SmallIcon("reload"), i18n("Requery"));
+#endif
+
+ p.exec(pos);
+}*/
+
+void
+KexiQueryDesignerSQLHistory::slotToClipboard()
+{
+ if(!m_selected)
+ return;
+
+ QApplication::clipboard()->setText(m_selected->statement(), QClipboard::Clipboard);
+}
+
+void
+KexiQueryDesignerSQLHistory::slotEdit()
+{
+ emit editRequested(m_selected->statement());
+}
+
+QString
+KexiQueryDesignerSQLHistory::selectedStatement() const
+{
+ return m_selected ? m_selected->statement() : QString::null;
+}
+
+void
+KexiQueryDesignerSQLHistory::setHistory(History *h)
+{
+ m_history = h;
+ update();
+}
+
+void KexiQueryDesignerSQLHistory::clear()
+{
+ m_selected = 0;
+ m_history->clear();
+ updateContents();
+}
+
+KPopupMenu* KexiQueryDesignerSQLHistory::popupMenu() const
+{
+ return m_popup;
+}
+
+//==================================
+
+HistoryEntry::HistoryEntry(bool succeed, const QTime &execTime, const QString &statement, /*int ,*/ const QString &err)
+{
+ m_succeed = succeed;
+ m_execTime = execTime;
+ m_statement = statement;
+ m_error = err;
+ m_selected = false;
+ highlight(QColorGroup());
+}
+
+void
+HistoryEntry::drawItem(QPainter *p, int width, const QColorGroup &cg)
+{
+ p->setPen(QColor(200, 200, 200));
+ p->setBrush(QColor(200, 200, 200));
+ p->drawRect(2, 2, 200, 20);
+ p->setPen(QColor(0, 0, 0));
+
+ if(m_succeed)
+ p->drawPixmap(4, 4, SmallIcon("button_ok"));
+ else
+ p->drawPixmap(4, 4, SmallIcon("button_cancel"));
+
+ p->drawText(22, 2, 180, 20, Qt::AlignLeft | Qt::AlignVCenter, m_execTime.toString());
+ p->setPen(QColor(200, 200, 200));
+ p->setBrush(QColor(255, 255, 255));
+ m_formated->setWidth(width - 2);
+ QRect content(2, 21, width - 2, m_formated->height());
+// QRect content = p->fontMetrics().boundingRect(2, 21, width - 2, 0, Qt::WordBreak | Qt::AlignLeft | Qt::AlignVCenter, m_statement);
+// QRect content(2, 21, width - 2, p->fontMetrics().height() + 4);
+// content = QRect(2, 21, width - 2, m_for.height());
+
+ if(m_selected)
+ p->setBrush(cg.highlight());
+
+ p->drawRect(content);
+
+ if(!m_selected)
+ p->setPen(cg.text());
+ else
+ p->setPen(cg.highlightedText());
+
+ content.setX(content.x() + 2);
+ content.setWidth(content.width() - 2);
+// p->drawText(content, Qt::WordBreak | Qt::AlignLeft | Qt::AlignVCenter, m_statement);
+ m_formated->draw(p, content.x(), content.y(), content, cg);
+}
+
+void
+HistoryEntry::highlight(const QColorGroup &cg)
+{
+ QString statement;
+ QString text;
+ bool quote = false;
+ bool dblquote = false;
+
+ statement = m_statement;
+ statement.replace("<", "&lt;");
+ statement.replace(">", "&gt;");
+ statement.replace("\r\n", "<br>"); //(js) first win32 specific pair
+ statement.replace("\n", "<br>"); // now single \n
+ statement.replace(" ", "&nbsp;");
+ statement.replace("\t", "&nbsp;&nbsp;&nbsp;");
+
+ // getting quoting...
+ if(!m_selected)
+ {
+ for(int i=0; i < (int)statement.length(); i++)
+ {
+ QString beginTag;
+ QString endTag;
+ QChar curr = QChar(statement[i]);
+
+ if(curr == "'" && !dblquote && QChar(statement[i-1]) != "\\")
+ {
+ if(!quote)
+ {
+ quote = true;
+ beginTag += "<font color=\"#ff0000\">";
+ }
+ else
+ {
+ quote = false;
+ endTag += "</font>";
+ }
+ }
+ if(curr == "\"" && !quote && QChar(statement[i-1]) != "\\")
+ {
+ if(!dblquote)
+ {
+ dblquote = true;
+ beginTag += "<font color=\"#ff0000\">";
+ }
+ else
+ {
+ dblquote = false;
+ endTag += "</font>";
+ }
+ }
+ if(QRegExp("[0-9]").exactMatch(QString(curr)) && !quote && !dblquote)
+ {
+ beginTag += "<font color=\"#0000ff\">";
+ endTag += "</font>";
+ }
+
+ text += beginTag + curr + endTag;
+ }
+ }
+ else
+ {
+ text = QString("<font color=\"%1\">%2").arg(cg.highlightedText().name()).arg(statement);
+ }
+
+ QRegExp keywords("\\b(SELECT|UPDATE|INSERT|DELETE|DROP|FROM|WHERE|AND|OR|NOT|NULL|JOIN|LEFT|RIGHT|ON|INTO|TABLE)\\b");
+ keywords.setCaseSensitive(false);
+ text = text.replace(keywords, "<b>\\1</b>");
+
+ if(!m_error.isEmpty())
+// text += ("<br>"+i18n("Error: %1").arg(m_error));
+// text += QString("<br><font face=\"") + KGlobalSettings::generalFont().family() + QString("\" size=\"-1\">") + i18n("Error: %1").arg(m_error) + "</font>";
+ text += QString("<br><font face=\"") + KGlobalSettings::generalFont().family() + QString("\">") + i18n("Error: %1").arg(m_error) + "</font>";
+
+ kdDebug() << "HistoryEntry::highlight() text:" << text << endl;
+// m_formated = new QSimpleRichText(text, QFont("courier", 8));
+ m_formated = new QSimpleRichText(text, KGlobalSettings::fixedFont());
+
+}
+
+void
+HistoryEntry::setSelected(bool selected, const QColorGroup &cg)
+{
+ m_selected = selected;
+ highlight(cg);
+}
+
+QRect
+HistoryEntry::geometry(int y, int width, QFontMetrics f)
+{
+ Q_UNUSED( f );
+
+// int h = 21 + f.boundingRect(2, 21, width - 2, 0, Qt::WordBreak | Qt::AlignLeft | Qt::AlignVCenter, m_statement).height();
+// return QRect(0, y, width, h);
+ m_formated->setWidth(width - 2);
+ return QRect(0, y, width, m_formated->height() + 21);
+}
+
+void HistoryEntry::updateTime(const QTime &execTime) {
+ m_execTime=execTime;
+}
+
+HistoryEntry::~HistoryEntry()
+{
+}
+
+#include "kexiquerydesignersqlhistory.moc"
diff --git a/kexi/plugins/queries/kexiquerydesignersqlhistory.h b/kexi/plugins/queries/kexiquerydesignersqlhistory.h
new file mode 100644
index 000000000..a8d0c2e08
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerydesignersqlhistory.h
@@ -0,0 +1,104 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIQUERYDESIGNERSQLHISTORY_H
+#define KEXIQUERYDESIGNERSQLHISTORY_H
+
+#include <qscrollview.h>
+#include <qdatetime.h>
+#include <qptrlist.h>
+#include <qmap.h>
+#include <qsimplerichtext.h>
+
+class QSimpleRichText;
+class KPopupMenu;
+
+class HistoryEntry
+{
+ public:
+ HistoryEntry(bool success, const QTime &time, const QString &statement, /*int y,*/ const QString &error = QString::null);
+ ~HistoryEntry();
+
+ QRect geometry(int y, int width, QFontMetrics f);
+ void drawItem(QPainter *p, int width, const QColorGroup &cg);
+
+ void setSelected(bool selected, const QColorGroup &cg);
+ bool isSelected() const { return m_selected; }
+ void highlight(const QColorGroup &selected);
+
+ QString statement() { return m_statement; }
+ void updateTime(const QTime &execTime);
+
+ private:
+ bool m_succeed;
+ QTime m_execTime;
+ QString m_statement;
+ QString m_error;
+ QSimpleRichText *m_formated;
+
+ int m_y;
+ bool m_selected;
+};
+
+typedef QPtrList<HistoryEntry> History;
+
+class KexiQueryDesignerSQLHistory : public QScrollView
+{
+ Q_OBJECT
+
+ public:
+ KexiQueryDesignerSQLHistory(QWidget *parent, const char *name=0);
+ virtual ~KexiQueryDesignerSQLHistory();
+
+ KPopupMenu* popupMenu() const;
+
+// void contextMenu(const QPoint &pos, HistoryEntry *e);
+
+ void setHistory(History *h);
+
+ QString selectedStatement() const;
+
+ public slots:
+ void addEvent(const QString& q, bool s, const QString &error);
+
+ void slotToClipboard();
+ void slotEdit();
+
+ void clear();
+
+// HistoryItem itemAt(int y);
+
+ protected:
+ void addEntry(HistoryEntry *e);
+ virtual void drawContents(QPainter *p, int cx, int cy, int cw, int ch);
+ virtual void contentsMousePressEvent(QMouseEvent * e);
+ virtual void contentsMouseDoubleClickEvent(QMouseEvent * e);
+
+ signals:
+ void editRequested(const QString &text);
+ void currentItemDoubleClicked();
+
+ private:
+ History *m_history;
+ HistoryEntry *m_selected;
+ KPopupMenu *m_popup;
+};
+
+#endif
diff --git a/kexi/plugins/queries/kexiqueryhandler.desktop b/kexi/plugins/queries/kexiqueryhandler.desktop
new file mode 100644
index 000000000..4a4f478e7
--- /dev/null
+++ b/kexi/plugins/queries/kexiqueryhandler.desktop
@@ -0,0 +1,111 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+GenericName=Queries
+GenericName[bg]=Заявки
+GenericName[ca]=Consultes
+GenericName[cs]=Dotazy
+GenericName[cy]=Ymholiadau
+GenericName[da]=Forespørgsler
+GenericName[de]=Abfragen
+GenericName[el]=Ερωτήματα
+GenericName[eo]=Serĉmendoj
+GenericName[es]=Consultas
+GenericName[et]=Päringud
+GenericName[eu]=Kontsultak
+GenericName[fa]=پرس‌و‌جوها
+GenericName[fi]=Kyselyt
+GenericName[fr]=Requêtes
+GenericName[ga]=Iarratais
+GenericName[gl]=Pesquisas
+GenericName[he]=שאילתות
+GenericName[hi]=क्वैरीज़
+GenericName[hr]=Upiti
+GenericName[hu]=Lekérdezések
+GenericName[is]=Fyrirspurnir
+GenericName[it]=Interrogazioni
+GenericName[ja]=クエリ
+GenericName[km]=សំណួរ​
+GenericName[lt]=Užklausos
+GenericName[lv]=Vaicājumi
+GenericName[ms]=Pertanyaan
+GenericName[nb]=Spørringer
+GenericName[nds]=Affragen
+GenericName[ne]=क्वेरीहरू
+GenericName[nn]=Spørjingar
+GenericName[pl]=Zapytania
+GenericName[pt]=Pesquisas
+GenericName[pt_BR]=Consultas
+GenericName[ru]=Запросы
+GenericName[se]=Jearahusat
+GenericName[sk]=Otázky
+GenericName[sl]=Poizvedbe
+GenericName[sr]=Упити
+GenericName[sr@Latn]=Upiti
+GenericName[sv]=Förfrågningar
+GenericName[ta]=கேள்விகள்
+GenericName[tr]=Sorgular
+GenericName[uk]=Запити
+GenericName[uz]=Soʻrovlar
+GenericName[uz@cyrillic]=Сўровлар
+GenericName[zh_CN]=查询
+GenericName[zh_TW]=查詢
+Name=Queries
+Name[bg]=Заявки
+Name[ca]=Consultes
+Name[cs]=Dotazy
+Name[cy]=Ymholiadau
+Name[da]=Forespørgsler
+Name[de]=Abfragen
+Name[el]=Ερωτήματα
+Name[eo]=Serĉmendoj
+Name[es]=Consultas
+Name[et]=Päringud
+Name[eu]=Kontsultak
+Name[fa]=پرس‌و‌جوها
+Name[fi]=Kyselyt
+Name[fr]=Requêtes
+Name[ga]=Iarratais
+Name[gl]=Pesquisas
+Name[he]=שאילתות
+Name[hi]=क्वैरीज़
+Name[hr]=Upiti
+Name[hu]=Lekérdezések
+Name[is]=Fyrirspurnir
+Name[it]=Interrogazioni
+Name[ja]=クエリ
+Name[km]=សំណួរ​
+Name[lt]=Užklausos
+Name[lv]=Vaicājumi
+Name[ms]=Pertanyaan
+Name[nb]=Spørringer
+Name[nds]=Affragen
+Name[ne]=क्वेरीहरू
+Name[nn]=Spørjingar
+Name[pl]=Zapytania
+Name[pt]=Procuras
+Name[pt_BR]=Consultas
+Name[ru]=Запросы
+Name[se]=Jearahusat
+Name[sk]=Otázky
+Name[sl]=Poizvedbe
+Name[sr]=Упити
+Name[sr@Latn]=Upiti
+Name[sv]=Förfrågningar
+Name[ta]=கேள்விகள்
+Name[tg]=Талаботҳо
+Name[tr]=Sorgular
+Name[uk]=Запити
+Name[uz]=Talablar
+Name[uz@cyrillic]=Талаблар
+Name[zh_CN]=查询
+Name[zh_TW]=查詢
+X-KDE-Library=kexihandler_query
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=query
+X-Kexi-TypeMime=kexi/query
+X-Kexi-ItemIcon=query
+X-Kexi-SupportsDataExport=true
+X-Kexi-SupportsPrinting=true
diff --git a/kexi/plugins/queries/kexiquerypart.cpp b/kexi/plugins/queries/kexiquerypart.cpp
new file mode 100644
index 000000000..6cecfcf1f
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerypart.cpp
@@ -0,0 +1,310 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiquerypart.h"
+
+#include <kdebug.h>
+#include <kgenericfactory.h>
+
+#include <keximainwindow.h>
+#include <kexidialogbase.h>
+#include <kexiproject.h>
+#include <kexipartinfo.h>
+
+#include <kexidb/cursor.h>
+#include <kexidb/parser/parser.h>
+
+#include "kexiqueryview.h"
+#include "kexiquerydesignerguieditor.h"
+#include "kexiquerydesignersql.h"
+
+//------------------------------------------------
+
+KexiQueryPart::KexiQueryPart(QObject *parent, const char *name, const QStringList &l)
+ : KexiPart::Part(parent, name, l)
+{
+ // REGISTERED ID:
+ m_registeredPartID = (int)KexiPart::QueryObjectType;
+
+ m_names["instanceName"]
+ = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). "
+ "Use '_' character instead of spaces. First character should be a..z character. "
+ "If you cannot use latin characters in your language, use english word.",
+ "query");
+ m_names["instanceCaption"] = i18n("Query");
+ m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode | Kexi::TextViewMode;
+}
+
+KexiQueryPart::~KexiQueryPart()
+{
+}
+
+KexiDialogTempData*
+KexiQueryPart::createTempData(KexiDialogBase* dialog)
+{
+ KexiQueryPart::TempData *data = new KexiQueryPart::TempData(dialog, dialog->mainWin()->project()->dbConnection());
+ data->listenerInfoString = dialog->part()->instanceCaption() + " \"" + dialog->partItem()->name() + "\"";
+ return data;
+}
+
+KexiViewBase*
+KexiQueryPart::createView(QWidget *parent, KexiDialogBase* dialog, KexiPart::Item &item, int viewMode, QMap<QString,QString>*)
+{
+ Q_UNUSED( item );
+
+ kdDebug() << "KexiQueryPart::createView()" << endl;
+
+ if (viewMode == Kexi::DataViewMode) {
+ return new KexiQueryView(dialog->mainWin(), parent, "dataview");
+ }
+ else if (viewMode == Kexi::DesignViewMode) {
+ KexiQueryDesignerGuiEditor* view = new KexiQueryDesignerGuiEditor(
+ dialog->mainWin(), parent, "guieditor");
+ //needed for updating tables combo box:
+ KexiProject *prj = dialog->mainWin()->project();
+ connect(prj, SIGNAL(newItemStored(KexiPart::Item&)),
+ view, SLOT(slotNewItemStored(KexiPart::Item&)));
+ connect(prj, SIGNAL(itemRemoved(const KexiPart::Item&)),
+ view, SLOT(slotItemRemoved(const KexiPart::Item&)));
+ connect(prj, SIGNAL(itemRenamed(const KexiPart::Item&, const QCString&)),
+ view, SLOT(slotItemRenamed(const KexiPart::Item&, const QCString&)));
+
+// connect(dialog->mainWin()->project(), SIGNAL(tableCreated(KexiDB::TableSchema&)),
+// view, SLOT(slotTableCreated(KexiDB::TableSchema&)));
+ return view;
+ }
+ else if (viewMode == Kexi::TextViewMode) {
+ return new KexiQueryDesignerSQLView(dialog->mainWin(), parent, "sqldesigner");
+ }
+
+ return 0;
+}
+
+bool
+KexiQueryPart::remove(KexiMainWindow *win, KexiPart::Item &item)
+{
+ if (!win || !win->project() || !win->project()->dbConnection())
+ return false;
+ KexiDB::Connection *conn = win->project()->dbConnection();
+ KexiDB::QuerySchema *sch = conn->querySchema(item.identifier());
+ if (sch)
+ return conn->dropQuery( sch );
+ //last chance: just remove item
+ return conn->removeObject( item.identifier() );
+}
+
+#if 0
+KexiPart::DataSource *
+KexiQueryPart::dataSource()
+{
+ return new KexiQueryDataSource(this);
+}
+
+void KexiQueryPart::initPartActions( KActionCollection *col )
+{
+}
+
+void KexiQueryPart::initInstanceActions( int mode, KActionCollection *col )
+{
+ if (mode==Kexi::DataViewMode) {
+ }
+ else if (mode==Kexi::DesignViewMode) {
+ }
+ else if (mode==Kexi::TextViewMode) {
+// new KAction(i18n("Check Query"), "test_it", 0, this, SLOT(slotCheckQuery()), col, "querypart_check_query");
+
+//TODO new KAction(i18n("Execute Query"), "?????", 0, this, SLOT(checkQuery()), col, "querypart_execute_query");
+ }
+}
+#endif
+
+void KexiQueryPart::initPartActions()
+{
+}
+
+void KexiQueryPart::initInstanceActions()
+{
+// new KAction(i18n("Check Query"), "test_it", 0, this, SLOT(slotCheckQuery()),
+// m_instanceGuiClients[Kexi::DesignViewMode]->actionCollection(), "querypart_check_query");
+
+ KAction *a = createSharedAction(Kexi::TextViewMode, i18n("Check Query"), "test_it",
+ Key_F9, "querypart_check_query");
+ a->setToolTip(i18n("Check Query"));
+ a->setWhatsThis(i18n("Checks query for validity."));
+
+ a = createSharedToggleAction(
+ Kexi::TextViewMode, i18n("Show SQL History"), "view_top_bottom"/*TODO other icon*/,
+ 0, "querypart_view_toggle_history");
+ a->setWhatsThis(i18n("Shows or hides SQL editor's history."));
+
+// setActionAvailable("querypart_check_query", true);
+}
+
+KexiDB::SchemaData*
+KexiQueryPart::loadSchemaData(KexiDialogBase *dlg, const KexiDB::SchemaData& sdata, int viewMode)
+{
+ KexiQueryPart::TempData * temp = static_cast<KexiQueryPart::TempData*>(dlg->tempData());
+ QString sqlText;
+ if (!loadDataBlock( dlg, sqlText, "sql" )) {
+ return 0;
+ }
+ KexiDB::Parser *parser = dlg->mainWin()->project()->sqlParser();
+ parser->parse( sqlText );
+ KexiDB::QuerySchema *query = parser->query();
+ //error?
+ if (!query) {
+ if (viewMode==Kexi::TextViewMode) {
+ //for SQL view, no parsing is initially needed:
+ //-just make a copy:
+ return KexiPart::Part::loadSchemaData(dlg, sdata, viewMode);
+ }
+ /* Set this to true on data loading loadSchemaData() to indicate that TextView mode
+ could be used instead of DataView or DesignView, because there are problems
+ with opening object. */
+ temp->proposeOpeningInTextViewModeBecauseOfProblems = true;
+ //todo
+ return 0;
+ }
+ query->debug();
+ (KexiDB::SchemaData&)*query = sdata; //copy main attributes
+
+ temp->registerTableSchemaChanges(query);
+
+ query->debug();
+ return query;
+}
+
+QString KexiQueryPart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const
+{
+ Q_UNUSED(dlg);
+ if (englishMessage=="Design of object \"%1\" has been modified.")
+ return i18n("Design of query \"%1\" has been modified.");
+ if (englishMessage=="Object \"%1\" already exists.")
+ return i18n("Query \"%1\" already exists.");
+
+ return englishMessage;
+}
+
+tristate KexiQueryPart::rename(KexiMainWindow *win, KexiPart::Item &item, const QString& newName)
+{
+ Q_UNUSED(newName);
+ if (!win->project()->dbConnection())
+ return false;
+ win->project()->dbConnection()->setQuerySchemaObsolete( item.name() );
+ return true;
+}
+
+//----------------
+
+KexiQueryPart::TempData::TempData(KexiDialogBase* parent, KexiDB::Connection *conn)
+ : KexiDialogTempData(parent)
+ , KexiDB::Connection::TableSchemaChangeListenerInterface()
+ , queryChangedInPreviousView(false)
+ , m_query(0)
+{
+ this->conn = conn;
+}
+
+KexiQueryPart::TempData::~TempData()
+{
+ conn->unregisterForTablesSchemaChanges(*this);
+}
+
+void KexiQueryPart::TempData::clearQuery()
+{
+ if (!m_query)
+ return;
+ unregisterForTablesSchemaChanges();
+ m_query->clear();
+}
+
+void KexiQueryPart::TempData::unregisterForTablesSchemaChanges()
+{
+ conn->unregisterForTablesSchemaChanges(*this);
+}
+
+void KexiQueryPart::TempData::registerTableSchemaChanges(KexiDB::QuerySchema *q)
+{
+ if (!q)
+ return;
+ for (KexiDB::TableSchema::ListIterator it(*q->tables());
+ it.current(); ++it)
+ {
+ conn->registerForTableSchemaChanges(*this, *it.current());
+ }
+}
+
+tristate KexiQueryPart::TempData::closeListener()
+{
+ KexiDialogBase* dlg = static_cast<KexiDialogBase*>(parent());
+ return dlg->mainWin()->closeDialog(dlg);
+}
+
+KexiDB::QuerySchema *KexiQueryPart::TempData::takeQuery()
+{
+ KexiDB::QuerySchema *query = m_query;
+ m_query = 0;
+ return query;
+}
+
+void KexiQueryPart::TempData::setQuery(KexiDB::QuerySchema *query)
+{
+ if (m_query && m_query == query)
+ return;
+ if (m_query
+ /* query not owned by dialog */
+ && (static_cast<KexiDialogBase*>(parent())->schemaData() != static_cast<KexiDB::SchemaData*>( m_query )))
+ {
+ delete m_query;
+ }
+ m_query = query;
+}
+
+//----------------
+
+#if 0
+KexiQueryDataSource::KexiQueryDataSource(KexiPart::Part *part)
+ : KexiPart::DataSource(part)
+{
+}
+
+KexiQueryDataSource::~KexiQueryDataSource()
+{
+}
+
+KexiDB::FieldList *
+KexiQueryDataSource::fields(KexiProject *, const KexiPart::Item &)
+{
+ return 0;
+}
+
+KexiDB::Cursor *
+KexiQueryDataSource::cursor(KexiProject *, const KexiPart::Item &, bool)
+{
+ return 0;
+}
+#endif
+
+//----------------
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_query, KGenericFactory<KexiQueryPart>("kexihandler_query") )
+
+#include "kexiquerypart.moc"
+
diff --git a/kexi/plugins/queries/kexiquerypart.h b/kexi/plugins/queries/kexiquerypart.h
new file mode 100644
index 000000000..6b16f28d5
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerypart.h
@@ -0,0 +1,118 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIQUERYPART_H
+#define KEXIQUERYPART_H
+
+#include <qmap.h>
+
+#include <kexidialogbase.h>
+#include <kexipart.h>
+#include <kexipartitem.h>
+//#include <kexipartdatasource.h>
+
+#include <kexidb/queryschema.h>
+#include <kexidb/connection.h>
+
+class KexiMainWin;
+namespace KexiDB
+{
+ class QuerySchema;
+ class Connection;
+}
+
+class KexiProject;
+
+//! @short Kexi Query Designer Plugin.
+class KexiQueryPart : public KexiPart::Part
+{
+ Q_OBJECT
+
+ public:
+ KexiQueryPart(QObject *parent, const char *name, const QStringList &);
+ virtual ~KexiQueryPart();
+
+ virtual bool remove(KexiMainWindow *win, KexiPart::Item &item);
+
+ //! @short Temporary data kept in memory while switching between Query Dialog's views
+ class TempData : public KexiDialogTempData,
+ public KexiDB::Connection::TableSchemaChangeListenerInterface
+ {
+ public:
+ TempData(KexiDialogBase* parent, KexiDB::Connection *conn);
+ virtual ~TempData();
+ virtual tristate closeListener();
+ void clearQuery();
+ void unregisterForTablesSchemaChanges();
+ void registerTableSchemaChanges(KexiDB::QuerySchema *q);
+
+ /*! Assigns query \a query for this data.
+ Existing query (available using query()) is deleted but only
+ if it is not owned by parent dialog (i.e. != KexiDialogBase::schemaData()).
+ \a query can be 0.
+ If \a query is equal to existing query, nothing is performed.
+ */
+ void setQuery(KexiDB::QuerySchema *query);
+
+ //! \return query associated with this data
+ KexiDB::QuerySchema *query() const { return m_query; }
+
+ //! Takes query associated with this data (without deleting) and returns it.
+ //! After this call query() == 0
+ KexiDB::QuerySchema *takeQuery();
+
+ //! Connection used for retrieving definition of the query
+ KexiDB::Connection *conn;
+
+ /*! true, if \a query member has changed in previous view.
+ Used on view switching. We're checking this flag to see if we should
+ rebuild internal structure for DesignViewMode of regenerated sql text
+ in TextViewMode after switch from other view. */
+ bool queryChangedInPreviousView : 1;
+
+ protected:
+ KexiDB::QuerySchema *m_query;
+ };
+
+ virtual QString i18nMessage(const QCString& englishMessage,
+ KexiDialogBase* dlg) const;
+
+ /*! Renames stored data pointed by \a item to \a newName.
+ Reimplemented to mark the query obsolete by using KexiDB::Connection::setQuerySchemaObsolete(). */
+ virtual tristate rename(KexiMainWindow * win, KexiPart::Item & item, const QString& newName);
+
+ protected:
+ virtual KexiDialogTempData* createTempData(KexiDialogBase* dialog);
+
+ virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode = Kexi::DataViewMode, QMap<QString,QString>* staticObjectArgs = 0);
+
+// virtual void initPartActions( KActionCollection *col );
+// virtual void initInstanceActions( int mode, KActionCollection *col );
+
+ virtual void initPartActions();
+ virtual void initInstanceActions();
+
+ virtual KexiDB::SchemaData* loadSchemaData(KexiDialogBase *dlg,
+ const KexiDB::SchemaData& sdata, int viewMode);
+};
+
+#endif
+
diff --git a/kexi/plugins/queries/kexiquerypartinstui.rc b/kexi/plugins/queries/kexiquerypartinstui.rc
new file mode 100644
index 000000000..405c43777
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerypartinstui.rc
@@ -0,0 +1,24 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexiquerypartinst" version="4">
+
+<MenuBar>
+ <Menu name="view" noMerge="1">
+ <text>&amp;View</text>
+ <Action name="querypart_view_toggle_history"/>
+ </Menu>
+ <Menu name="data">
+ <text>&amp;Data</text>
+ <Action name="querypart_check_query"/>
+ <Action name="querypart_execute_query"/>
+ <Merge/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="designToolBar" fullWidth="false" noMerge="0">
+ <text>Design</text>
+ <Action name="querypart_check_query"/>
+ <Action name="querypart_execute_query"/>
+ <Action name="querypart_view_toggle_history"/>
+</ToolBar>
+
+</kpartgui>
diff --git a/kexi/plugins/queries/kexiquerypartui.rc b/kexi/plugins/queries/kexiquerypartui.rc
new file mode 100644
index 000000000..5b384aeaa
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerypartui.rc
@@ -0,0 +1,11 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexiquerypart" version="2">
+
+<MenuBar>
+ <!-- (not needed - it is autogenerated!) <Menu name="widgets">
+ <text>&amp;Create</text>
+ <Action name="querypart_create"/>
+ </Menu -->
+</MenuBar>
+
+</kpartgui>
diff --git a/kexi/plugins/queries/kexiqueryview.cpp b/kexi/plugins/queries/kexiqueryview.cpp
new file mode 100644
index 000000000..cf3fee967
--- /dev/null
+++ b/kexi/plugins/queries/kexiqueryview.cpp
@@ -0,0 +1,154 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kexiproject.h>
+#include <kexidb/connection.h>
+#include <kexidb/parser/parser.h>
+#include <kexidb/cursor.h>
+#include <keximainwindow.h>
+#include <kexiutils/utils.h>
+
+#include "kexiqueryview.h"
+#include "kexiquerydesignersql.h"
+#include "kexiquerydesignerguieditor.h"
+#include "kexiquerypart.h"
+#include <widget/tableview/kexitableview.h>
+#include <widget/kexiqueryparameters.h>
+
+//! @internal
+class KexiQueryView::Private
+{
+ public:
+ Private()
+ : cursor(0)
+// , queryHasBeenChangedInViewMode( Kexi::NoViewMode )
+ {}
+ ~Private() {}
+ KexiDB::Cursor *cursor;
+ /*! Used in storeNewData(), storeData() to decide whether
+ we should ask other view to save changes.
+ Stores information about view mode. */
+// int queryHasBeenChangedInViewMode;
+};
+
+//---------------------------------------------------------------------------------
+
+KexiQueryView::KexiQueryView(KexiMainWindow *win, QWidget *parent, const char *name)
+ : KexiDataTable(win, parent, name)
+ , d( new Private() )
+{
+ tableView()->setInsertingEnabled(false); //default
+}
+
+KexiQueryView::~KexiQueryView()
+{
+ if (d->cursor)
+ d->cursor->connection()->deleteCursor(d->cursor);
+ delete d;
+}
+
+tristate KexiQueryView::executeQuery(KexiDB::QuerySchema *query)
+{
+ if (!query)
+ return false;
+ KexiUtils::WaitCursor wait;
+ KexiDB::Cursor *oldCursor = d->cursor;
+ KexiDB::debug( query->parameters() );
+ bool ok;
+ QValueList<QVariant> params;
+ {
+ KexiUtils::WaitCursorRemover remover;
+ params = KexiQueryParameters::getParameters(this,
+ *mainWin()->project()->dbConnection()->driver(), *query, ok);
+ }
+ if (!ok) {//input cancelled
+ return cancelled;
+ }
+ d->cursor = mainWin()->project()->dbConnection()->executeQuery(*query, params);
+ if (!d->cursor) {
+ parentDialog()->setStatus(parentDialog()->mainWin()->project()->dbConnection(),
+ i18n("Query executing failed."));
+ //todo: also provide server result and sql statement
+ return false;
+ }
+ setData(d->cursor);
+
+//! @todo remove close() when dynamic cursors arrive
+ d->cursor->close();
+
+ if (oldCursor)
+ oldCursor->connection()->deleteCursor(oldCursor);
+
+//! @todo maybe allow writing and inserting for single-table relations?
+ tableView()->setReadOnly( true );
+//! @todo maybe allow writing and inserting for single-table relations?
+ //set data model itself read-only too
+ tableView()->data()->setReadOnly( true );
+ tableView()->setInsertingEnabled( false );
+ return true;
+}
+
+tristate KexiQueryView::afterSwitchFrom(int mode)
+{
+ if (mode==Kexi::NoViewMode) {
+ KexiDB::QuerySchema *querySchema = static_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
+ const tristate result = executeQuery(querySchema);
+ if (true != result)
+ return result;
+ }
+ else if (mode==Kexi::DesignViewMode || Kexi::TextViewMode) {
+ KexiQueryPart::TempData * temp = static_cast<KexiQueryPart::TempData*>(parentDialog()->tempData());
+
+ //remember what view we should use to store data changes, if needed
+// if (temp->queryChangedInPreviousView)
+// d->queryHasBeenChangedInViewMode = mode;
+// else
+// d->queryHasBeenChangedInViewMode = Kexi::NoViewMode;
+
+ const tristate result = executeQuery(temp->query());
+ if (true != result)
+ return result;
+ }
+ return true;
+}
+
+KexiDB::SchemaData* KexiQueryView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ KexiViewBase * view = parentDialog()->viewThatRecentlySetDirtyFlag();
+ if (dynamic_cast<KexiQueryDesignerGuiEditor*>(view))
+ return dynamic_cast<KexiQueryDesignerGuiEditor*>(view)->storeNewData(sdata, cancel);
+ if (dynamic_cast<KexiQueryDesignerSQLView*>(view))
+ return dynamic_cast<KexiQueryDesignerSQLView*>(view)->storeNewData(sdata, cancel);
+ return 0;
+}
+
+tristate KexiQueryView::storeData(bool dontAsk)
+{
+ KexiViewBase * view = parentDialog()->viewThatRecentlySetDirtyFlag();
+ if (dynamic_cast<KexiQueryDesignerGuiEditor*>(view))
+ return dynamic_cast<KexiQueryDesignerGuiEditor*>(view)->storeData(dontAsk);
+ if (dynamic_cast<KexiQueryDesignerSQLView*>(view))
+ return dynamic_cast<KexiQueryDesignerSQLView*>(view)->storeData(dontAsk);
+ return false;
+}
+
+
+#include "kexiqueryview.moc"
+
diff --git a/kexi/plugins/queries/kexiqueryview.h b/kexi/plugins/queries/kexiqueryview.h
new file mode 100644
index 000000000..f0083738f
--- /dev/null
+++ b/kexi/plugins/queries/kexiqueryview.h
@@ -0,0 +1,58 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIQUERYVIEW_H
+#define KEXIQUERYVIEW_H
+
+#include <kexidatatable.h>
+
+namespace KexiDB
+{
+ class QuerySchema;
+}
+class KexiMainWindow;
+
+class KexiQueryView : public KexiDataTable
+{
+ Q_OBJECT
+
+ public:
+ KexiQueryView(KexiMainWindow *win, QWidget *parent, const char *name=0);
+ ~KexiQueryView();
+
+ protected:
+ virtual tristate afterSwitchFrom(int mode);
+
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+
+ virtual tristate storeData(bool dontAsk = false);
+
+ /*! Executes query \a query, filling the table view with query results.
+ \return true on success, false on failure and cancelled when user has
+ cancelled execution (for example when she pressed the Cancel button
+ of the "Enter Query Parameter" input dialog. */
+ tristate executeQuery(KexiDB::QuerySchema *query);
+
+ class Private;
+ Private *d;
+};
+
+#endif
+
diff --git a/kexi/plugins/relations/Makefile.am b/kexi/plugins/relations/Makefile.am
new file mode 100644
index 000000000..6e35ab3a1
--- /dev/null
+++ b/kexi/plugins/relations/Makefile.am
@@ -0,0 +1,28 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_relation.la
+
+#kexihandler_relation_la_SOURCES = kexirelationhandler.cpp kexirelationhandlerproxy.cpp kexirelationview.cpp \
+# kexirelationviewtable.cpp kexirelationdialog.cpp \
+# kexirelationviewconnection.cpp
+kexihandler_relation_la_SOURCES = kexirelationpartimpl.cpp \
+ kexirelationmaindlg.cpp
+
+kexihandler_relation_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module
+kexihandler_relation_la_LIBADD = ../../core/libkexicore.la \
+ ../../widget/relations/libkexirelationsview.la
+
+INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/widget/relations \
+ -I$(top_srcdir)/kexi/tableview \
+ -I$(top_srcdir)/kexi/kexidb $(all_includes)
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=kexirelationhandler.desktop
+
+rcdir = $(kde_datadir)/kexi
+rc_DATA = kexirelationpartui.rc kexirelationpartinstui.rc
+
+METASOURCES = AUTO
+
+include ../Makefile.common
diff --git a/kexi/plugins/relations/kexirelationhandler.desktop b/kexi/plugins/relations/kexirelationhandler.desktop
new file mode 100644
index 000000000..7232d316b
--- /dev/null
+++ b/kexi/plugins/relations/kexirelationhandler.desktop
@@ -0,0 +1,110 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+GenericName=Relationships
+GenericName[bg]=Релации
+GenericName[ca]=Relacions
+GenericName[cs]=Vztahy
+GenericName[cy]=Perthnasau
+GenericName[de]=Beziehungen
+GenericName[el]=Συσχετίσεις
+GenericName[eo]=Rilatoj
+GenericName[es]=Relaciones
+GenericName[et]=Sõltuvused
+GenericName[eu]=Erlazioak
+GenericName[fa]=روابط
+GenericName[fi]=Yhteyssuhteet
+GenericName[fr]=Relations
+GenericName[fy]=Relaasjes
+GenericName[ga]=Gaolta
+GenericName[gl]=Relacións
+GenericName[he]=יחסים
+GenericName[hi]=रिलेशनशिप
+GenericName[hr]=Poveznice
+GenericName[hu]=Kapcsolatok
+GenericName[is]=Tengsl
+GenericName[it]=Relazioni
+GenericName[ja]=リレーションシップ
+GenericName[km]=ទំនាក់ទំនង​
+GenericName[lv]=Relācijas
+GenericName[ms]=Hubungan
+GenericName[nb]=Relasjoner
+GenericName[nds]=Betöög
+GenericName[ne]=सम्बन्धहरू
+GenericName[nl]=Relaties
+GenericName[nn]=Relasjonar
+GenericName[pl]=Relacje
+GenericName[pt]=Relações
+GenericName[pt_BR]=Relacionamentos
+GenericName[ru]=Связи
+GenericName[se]=Relašuvnnat
+GenericName[sk]=Vzťahy
+GenericName[sl]=Razmerja
+GenericName[sr]=Односи
+GenericName[sr@Latn]=Odnosi
+GenericName[sv]=Förhållanden
+GenericName[ta]=உறவுமுறைகள்
+GenericName[tr]=İlişkiler
+GenericName[uk]=Взаємозвязки
+GenericName[uz]=Aloqalar
+GenericName[uz@cyrillic]=Алоқалар
+GenericName[zh_CN]=关系
+GenericName[zh_TW]=關係
+Name=Relationships
+Name[bg]=Релации
+Name[ca]=Relacions
+Name[cs]=Vztahy
+Name[cy]=Perthnasau
+Name[de]=Beziehungen
+Name[el]=Συσχετίσεις
+Name[eo]=Rilatoj
+Name[es]=Relaciones
+Name[et]=Sõltuvused
+Name[eu]=Erlazioak
+Name[fa]=روابط
+Name[fi]=Yhteydet
+Name[fr]=Relations
+Name[fy]=Relaasjes
+Name[ga]=Gaolta
+Name[gl]=Relacións
+Name[he]=יחסים
+Name[hi]=रिलेशनशिप
+Name[hr]=Poveznice
+Name[hu]=Kapcsolatok
+Name[is]=Tengsl
+Name[it]=Relazioni
+Name[ja]=リレーションシップ
+Name[km]=ទំនាក់ទំនង​
+Name[lv]=Relācijas
+Name[ms]=Hubungan
+Name[nb]=Relasjoner
+Name[nds]=Betöög
+Name[ne]=सम्बन्धहरू
+Name[nl]=Relaties
+Name[nn]=Relasjonar
+Name[pl]=Relacje
+Name[pt]=Relações
+Name[pt_BR]=Relações
+Name[ru]=Взаимосвязи
+Name[se]=Relašuvnnat
+Name[sk]=Vzťahy
+Name[sl]=Razmerja
+Name[sr]=Односи
+Name[sr@Latn]=Odnosi
+Name[sv]=Förhållanden
+Name[ta]=உறவுகள்
+Name[tg]=Муносибатҳо
+Name[tr]=İlişkiler
+Name[uk]=Взаємозвязки
+Name[uz]=Aloqalar
+Name[uz@cyrillic]=Алоқалар
+Name[zh_CN]=关系
+Name[zh_TW]=關係
+X-KDE-Library=kexihandler_relation
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=relation
+X-Kexi-TypeMime=kexi/relation
+X-Kexi-ItemIcon=relations
+X-Kexi-NoObject=true
diff --git a/kexi/plugins/relations/kexirelationmaindlg.cpp b/kexi/plugins/relations/kexirelationmaindlg.cpp
new file mode 100644
index 000000000..6b14fffab
--- /dev/null
+++ b/kexi/plugins/relations/kexirelationmaindlg.cpp
@@ -0,0 +1,81 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexirelationmaindlg.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kiconloader.h>
+
+#include <qlayout.h>
+
+#include <kexidb/connection.h>
+
+#include "keximainwindow.h"
+#include "kexiproject.h"
+#include "kexirelationwidget.h"
+#include "kexirelationview.h"
+
+KexiRelationMainDlg::KexiRelationMainDlg(KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : KexiViewBase(mainWin, parent, name)
+{
+ kdDebug() << "KexiRelationMainDlg()" << endl;
+// setIcon(SmallIcon("relation"));
+ m_defaultIconName = "relation";
+ setCaption( i18n("Relationships") );
+// setDocID( win->generatePrivateDocID() );
+
+ m_rel = new KexiRelationWidget(mainWin, this);
+ //the view can receive some our actions
+ addActionProxyChild( m_rel );
+// addActionProxyChild( m_view->relationView() );
+
+ QVBoxLayout *g = new QVBoxLayout(this);
+ g->addWidget(m_rel);
+
+ //show all tables
+ KexiDB::Connection *conn = mainWin->project()->dbConnection();
+ QStringList tables = conn->tableNames();
+ for (QStringList::ConstIterator it = tables.constBegin(); it!=tables.constEnd(); ++it) {
+ m_rel->addTable( *it );
+ }
+}
+
+KexiRelationMainDlg::~KexiRelationMainDlg()
+{
+}
+
+QSize KexiRelationMainDlg::sizeHint() const
+{
+ return QSize(600,300);
+}
+
+QWidget*
+KexiRelationMainDlg::mainWidget()
+{
+ return m_rel;
+}
+
+QString KexiRelationMainDlg::itemIcon()
+{
+ return "relation";
+}
+
+#include "kexirelationmaindlg.moc"
+
diff --git a/kexi/plugins/relations/kexirelationmaindlg.h b/kexi/plugins/relations/kexirelationmaindlg.h
new file mode 100644
index 000000000..791d6544c
--- /dev/null
+++ b/kexi/plugins/relations/kexirelationmaindlg.h
@@ -0,0 +1,47 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIRELATIONMAINDLG_H
+#define KEXIRELATIONMAINDLG_H
+
+#include <kexiviewbase.h>
+
+class KexiMainWindow;
+class KexiRelationWidget;
+
+class KexiRelationMainDlg : public KexiViewBase
+{
+ Q_OBJECT
+
+ public:
+ KexiRelationMainDlg(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0);
+ ~KexiRelationMainDlg();
+
+ virtual QSize sizeHint() const;
+
+ virtual QWidget* mainWidget();
+
+ virtual QString itemIcon();
+
+ private:
+ KexiRelationWidget *m_rel;
+};
+
+#endif
+
diff --git a/kexi/plugins/relations/kexirelationpartimpl.cpp b/kexi/plugins/relations/kexirelationpartimpl.cpp
new file mode 100644
index 000000000..a2a7c2138
--- /dev/null
+++ b/kexi/plugins/relations/kexirelationpartimpl.cpp
@@ -0,0 +1,85 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexirelationmaindlg.h"
+#include "kexirelationpartimpl.h"
+
+#include <kexirelationwidget.h>
+#include <kexidialogbase.h>
+#include <keximainwindow.h>
+
+#include <kgenericfactory.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+
+KexiRelationPartImpl::KexiRelationPartImpl(QObject *parent, const char *name, const QStringList &args)
+ : KexiInternalPart(parent, name, args)
+{
+ kdDebug() << "KexiRelationPartImpl()" << endl;
+}
+
+KexiRelationPartImpl::~KexiRelationPartImpl()
+{
+}
+
+/*QWidget *
+KexiRelationPartImpl::createWidget(const char* , KexiMainWindow* mainWin,
+ QWidget *parent, const char *objName)
+{
+ return new KexiRelationWidget(mainWin, parent, objName);
+}*/
+
+/*KexiDialogBase *
+KexiRelationPartImpl::createDialog(KexiMainWindow* mainWin, const char *)
+{
+ kdDebug() << "KexiRelationPartImpl::createDialog()" << endl;
+ KexiDialogBase * dlg = new KexiDialogBase(mainWin, i18n("Relations"));
+ dlg->setIcon(SmallIcon("relation"));
+ dlg->setDocID( mainWin->generatePrivateDocID() );
+
+ KexiRelationMainDlg *view = new KexiRelationMainDlg(mainWin, 0, "relations");
+ dlg->addView(view);
+// dlg->show();
+// dlg->registerDialog();
+
+ return dlg;
+}*/
+
+KexiViewBase *
+KexiRelationPartImpl::createView(KexiMainWindow* mainWin, QWidget *parent, const char *)
+{
+// kdDebug() << "KexiRelationPartImpl::createDialog()" << endl;
+// KexiDialogBase * dlg = new KexiDialogBase(mainWin, i18n("Relations"));
+// dlg->setIcon(SmallIcon("relation"));
+// dlg->setDocID( mainWin->generatePrivateDocID() );
+
+ KexiRelationMainDlg *view = new KexiRelationMainDlg(mainWin, parent, "relations");
+// dlg->addView(view);
+// dlg->show();
+// dlg->registerDialog();
+
+ return view;
+}
+
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_relation,
+ KGenericFactory<KexiRelationPartImpl>("kexihandler_relation") )
+
+#include "kexirelationpartimpl.moc"
+
diff --git a/kexi/plugins/relations/kexirelationpartimpl.h b/kexi/plugins/relations/kexirelationpartimpl.h
new file mode 100644
index 000000000..b5b5438e7
--- /dev/null
+++ b/kexi/plugins/relations/kexirelationpartimpl.h
@@ -0,0 +1,46 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIRELATIONPARTIMPL_H
+#define KEXIRELATIONPARTIMPL_H
+
+#include <kexiinternalpart.h>
+
+class KexiRelationPartImpl : public KexiInternalPart
+{
+ Q_OBJECT
+
+ public:
+ KexiRelationPartImpl(QObject *parent, const char *name, const QStringList &args);
+ virtual ~KexiRelationPartImpl();
+
+ protected:
+// virtual QWidget *createWidget(const char* widgetClass, KexiMainWindow* mainWin,
+// QWidget *parent, const char *objName=0);
+
+ virtual KexiViewBase *createView(KexiMainWindow* mainWin, QWidget *parent,
+ const char *objName=0);
+
+ //virtual KexiDialogBase *createWindow(KexiMainWindow *parent);
+ //virtual QWidget *createWidget(QWidget *parent, KexiMainWindow *win);
+};
+
+#endif
+
+
diff --git a/kexi/plugins/relations/kexirelationpartinstui.rc b/kexi/plugins/relations/kexirelationpartinstui.rc
new file mode 100644
index 000000000..cad23d567
--- /dev/null
+++ b/kexi/plugins/relations/kexirelationpartinstui.rc
@@ -0,0 +1,6 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexirelationpartinst" version="1">
+
+<!-- TODO -->
+
+</kpartgui>
diff --git a/kexi/plugins/relations/kexirelationpartui.rc b/kexi/plugins/relations/kexirelationpartui.rc
new file mode 100644
index 000000000..67002e713
--- /dev/null
+++ b/kexi/plugins/relations/kexirelationpartui.rc
@@ -0,0 +1,14 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexirelationpart" version="2">
+<MenuBar>
+ <Menu name="project">
+ <text>&amp;Project</text>
+ <Action name="relations"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="projectToolBar" fullWidth="false">
+ <text>Project</text>
+ <Action name="relations"/>
+</ToolBar>
+</kpartgui>
diff --git a/kexi/plugins/reports/Makefile.am b/kexi/plugins/reports/Makefile.am
new file mode 100644
index 000000000..4f8e54c41
--- /dev/null
+++ b/kexi/plugins/reports/Makefile.am
@@ -0,0 +1,51 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_report.la kexireportwidgets.la
+
+kexihandler_report_la_SOURCES = kexireports.cpp
+kexihandler_report_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined
+kexihandler_report_la_LIBADD = ../../core/libkexicore.la \
+ $(top_builddir)/kexi/widget/utils/libkexiguiutils.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/kexi/formeditor/libkformdesigner.la \
+ $(top_builddir)/kexi/plugins/forms/libkexiformutils.la \
+ ./libkexireportutils.la
+
+kexireportwidgets_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined
+kexireportwidgets_la_SOURCES = reportwidgets.cpp kexireportfactory.cpp
+kexireportwidgets_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la \
+ $(top_builddir)/kexi/plugins/forms/libkexiformutils.la \
+ $(top_builddir)/kexi/plugins/forms/widgets/libkexiformutilswidgets.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ ./libkexireportutils.la
+
+lib_LTLIBRARIES = libkexireportutils.la
+libkexireportutils_la_SOURCES = \
+ kexireportpart.cpp kexireportview.cpp kexireportform.cpp
+libkexireportutils_la_LDFLAGS = $(all_libraries) $(VER_INFO) -no-undefined
+libkexireportutils_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/formeditor/libkformdesigner.la \
+ $(top_builddir)/kexi/plugins/forms/widgets/libkexiformutilswidgets.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/kexi/plugins/forms/libkexiformutils.la
+
+kformdesignerservicesdir=$(kde_servicesdir)/kformdesigner
+kformdesignerservices_DATA = kformdesigner_kexireportfactory.desktop
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=kexireporthandler.desktop
+
+rcdir = $(kde_datadir)/kexi
+rc_DATA = kexireportpartui.rc kexireportpartinstui.rc
+
+SUBDIRS = .
+
+INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore \
+ -I$(top_srcdir)/kexi/widget/utils \
+ -I$(top_srcdir)/kexi/widget \
+ -I$(top_srcdir)/kexi/formeditor -I$(top_srcdir)/kexi/plugins/forms $(all_includes)
+
+METASOURCES = AUTO
+
+include ../Makefile.common
diff --git a/kexi/plugins/reports/kexireportfactory.cpp b/kexi/plugins/reports/kexireportfactory.cpp
new file mode 100644
index 000000000..0ac782c45
--- /dev/null
+++ b/kexi/plugins/reports/kexireportfactory.cpp
@@ -0,0 +1,227 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <qpopupmenu.h>
+#include <qvaluevector.h>
+
+#include <kgenericfactory.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <klineedit.h>
+
+#include <container.h>
+#include <form.h>
+#include <formmanager.h>
+#include <widgetlibrary.h>
+
+#include "reportwidgets.h"
+#include "kexireportfactory.h"
+
+KexiReportFactory::KexiReportFactory(QObject *parent, const char *name, const QStringList &)
+ : KFormDesigner::WidgetFactory(parent, name)
+{
+ KFormDesigner::WidgetInfo *wView = new KFormDesigner::WidgetInfo(this);
+ wView->setPixmap("report");
+ wView->setClassName("KexiReportForm");
+ wView->setName(i18n("Report"));
+ wView->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "report"));
+ wView->setDescription(i18n("A report"));
+ addClass(wView);
+
+ KFormDesigner::WidgetInfo *wLabel = new KFormDesigner::WidgetInfo(this);
+ wLabel->setPixmap("label");
+ wLabel->setClassName("Label");
+ wLabel->setName(i18n("Label"));
+ wLabel->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "label"));
+ wLabel->setDescription(i18n("A label to display text"));
+ addClass(wLabel);
+
+ KFormDesigner::WidgetInfo *wPicLabel = new KFormDesigner::WidgetInfo(this);
+ wPicLabel->setPixmap("pixmaplabel");
+ wPicLabel->setClassName("PicLabel");
+ wPicLabel->setName(i18n("Picture Label"));
+ wPicLabel->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "picture"));
+ wPicLabel->setDescription(i18n("A label to display images or icons"));
+ addClass(wPicLabel);
+
+ KFormDesigner::WidgetInfo *wLine = new KFormDesigner::WidgetInfo(this);
+ wLine->setPixmap("line");
+ wLine->setClassName("ReportLine");
+ wLine->setName(i18n("Line"));
+ wLine->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "line"));
+ wLine->setDescription(i18n("A simple line"));
+ addClass(wLine);
+
+ KFormDesigner::WidgetInfo *wSubReport = new KFormDesigner::WidgetInfo(this);
+ wSubReport->setPixmap("report");
+ wSubReport->setClassName("KexiSubReport");
+ wSubReport->setName(i18n("Sub Report"));
+ wSubReport->setNamePrefix(
+ i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "subReport"));
+ wSubReport->setDescription(i18n("A report embedded in another report"));
+ addClass(wSubReport);
+}
+
+KexiReportFactory::~KexiReportFactory()
+{
+}
+
+QString
+KexiReportFactory::name()
+{
+ return "kexireportwidgets";
+}
+
+QWidget*
+KexiReportFactory::createWidget(const QCString &c, QWidget *p, const char *n,
+ KFormDesigner::Container *container, int options)
+{
+ Q_UNUSED(options);
+ kexipluginsdbg << "KexiReportFactory::create() " << this << endl;
+
+ QString text( container->form()->library()->textForWidgetName(n, c) );
+
+ if(c == "Label")
+ return new Label(text, p, n);
+ else if(c == "PicLabel")
+ return new PicLabel(DesktopIcon("image"), p, n);
+ else if(c == "ReportLine")
+ return new ReportLine(p, n);
+ else if(c == "KexiSubReport")
+ return new KexiSubReport(p, n);
+
+ return 0;
+}
+
+bool
+KexiReportFactory::createMenuActions(const QCString &classname, QWidget *w,
+ QPopupMenu *menu, KFormDesigner::Container *container)
+{
+ Q_UNUSED(w);
+ Q_UNUSED(container);
+ if(classname == "Label") {
+ /*! @todo use KAction */
+ menu->insertItem(SmallIconSet("edit"), i18n("Edit Rich Text"), this, SLOT(editText()));
+ return true;
+ }
+ return false;
+}
+
+bool
+KexiReportFactory::startEditing(const QCString &c, QWidget *w, KFormDesigner::Container *container)
+{
+ m_container = container;
+
+ if(c == "Label") {
+ QLabel *label = static_cast<QLabel*>(w);
+ if(label->textFormat() == RichText) {
+ m_widget = w;
+ editText();
+ }
+ else
+ createEditor(c, label->text(), label, container, label->geometry(), label->alignment());
+ return true;
+ }
+ return false;
+}
+
+bool
+KexiReportFactory::isPropertyVisibleInternal(const QCString &classname, QWidget *w, const QCString &property, bool isTopLevel)
+{
+ if(classname == "Label") {
+ if(property == "pixmap")
+ return false;
+ }
+ else if(classname == "PicLabel") {
+ if((property == "text") || (property == "indent") || (property == "textFormat") || (property == "font") || (property == "alignment"))
+ return false;
+ }
+
+ return WidgetFactory::isPropertyVisibleInternal(classname, w, property, isTopLevel);
+}
+
+QValueList<QCString>
+KexiReportFactory::autoSaveProperties(const QCString &classname)
+{
+ QValueList<QCString> l;
+
+ if(classname == "Label")
+ l << "text";
+ else if(classname == "PicLabel")
+ l << "pixmap";
+
+ return l;
+}
+
+/*
+void
+KexiReportFactory::changeText(const QString &text)
+{
+ QWidget *w = WidgetFactory::m_widget;
+ changeProperty("text", text, m_container);
+
+ int width = w->sizeHint().width();
+
+ if(w->width() < width)
+ w->resize(width, w->height() );
+}
+
+void
+KexiReportFactory::resizeEditor(QWidget *widget, const QCString &)
+{
+ QSize s = widget->size();
+ QPoint p = widget->pos();
+ QRect r;
+
+ m_editor->resize(s);
+ m_editor->move(p);
+}*/
+
+void
+KexiReportFactory::editText()
+{
+ QCString classname = m_widget->className();
+ QString text;
+
+ if(classname == "Label")
+ text = ((QLabel*)m_widget)->text();
+
+ if(editRichText(m_widget, text)) {
+ changeProperty("textFormat", "RichText", m_container->form());
+ changeProperty("text", text, m_container->form());
+ }
+
+ if(classname == "Label")
+ m_widget->resize(m_widget->sizeHint());
+}
+
+bool
+KexiReportFactory::previewWidget(const QCString &, QWidget *, KFormDesigner::Container *)
+{
+ return false;
+}
+
+KFORMDESIGNER_WIDGET_FACTORY(KexiReportFactory, kexireportwidgets)
+
+#include "kexireportfactory.moc"
+
diff --git a/kexi/plugins/reports/kexireportfactory.h b/kexi/plugins/reports/kexireportfactory.h
new file mode 100644
index 000000000..c6b917029
--- /dev/null
+++ b/kexi/plugins/reports/kexireportfactory.h
@@ -0,0 +1,62 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIREPORTFACTORY_H
+#define KEXIREPORTFACTORY_H
+
+#include <widgetfactory.h>
+
+//! Kexi Factory (DB widgets + subform)
+class KexiReportFactory : public KFormDesigner::WidgetFactory
+{
+ Q_OBJECT
+
+ public:
+ KexiReportFactory(QObject *parent, const char *name, const QStringList &args);
+ virtual ~KexiReportFactory();
+
+ virtual QString name();
+ virtual QWidget *createWidget(const QCString &classname, QWidget *parent, const char *name, KFormDesigner::Container *container,
+ int options = DefaultOptions);
+
+ virtual bool createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu,
+ KFormDesigner::Container *container);
+ virtual bool startEditing(const QCString &classname, QWidget *w, KFormDesigner::Container *container);
+ virtual bool previewWidget(const QCString &, QWidget *, KFormDesigner::Container *);
+
+ //virtual void saveSpecialProperty(const QString &classname, const QString &name, const QVariant &value, QWidget *w,
+ //QDomElement &parentNode, QDomDocument &parent) {}
+ //virtual void readSpecialProperty(const QCString &classname, QDomElement &node, QWidget *w, KFormDesigner::ObjectTreeItem *item) {}
+ virtual QValueList<QCString> autoSaveProperties(const QCString &classname);
+
+ public slots:
+ void editText();
+
+ protected:
+ virtual bool isPropertyVisibleInternal(const QCString &, QWidget *, const QCString &, bool isTopLevel);
+// virtual void changeText(const QString &newText);
+// virtual void resizeEditor(QWidget *widget, const QCString &classname);
+
+ private:
+ QWidget *m_widget;
+ KFormDesigner::Container *m_container;
+};
+
+#endif
+
diff --git a/kexi/plugins/reports/kexireportform.cpp b/kexi/plugins/reports/kexireportform.cpp
new file mode 100644
index 000000000..d5dd6f55b
--- /dev/null
+++ b/kexi/plugins/reports/kexireportform.cpp
@@ -0,0 +1,188 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qobjectlist.h>
+#include <qpainter.h>
+#include <qcursor.h>
+
+#include <kdebug.h>
+
+#include "kexireportform.h"
+
+KexiReportForm::KexiReportForm(QWidget *parent, const char *name/*, KexiDB::Connection *conn*/)
+ : QWidget(parent, name)
+{
+ //m_conn = conn;
+ kexipluginsdbg << "KexiReportForm::KexiReportForm(): " << endl;
+ setCursor(QCursor(Qt::ArrowCursor)); //to avoid keeping Size cursor when moving from form's boundaries
+ setBackgroundColor(white);
+}
+
+KexiReportForm::~KexiReportForm()
+{
+ kexipluginsdbg << "KexiReportForm::~KexiReportForm(): close" << endl;
+}
+
+//repaint all children widgets
+static void repaintAll(QWidget *w)
+{
+ QObjectList *list = w->queryList("QWidget");
+ QObjectListIt it(*list);
+ for (QObject *obj; (obj=it.current()); ++it ) {
+ static_cast<QWidget*>(obj)->repaint();
+ }
+ delete list;
+}
+
+void
+KexiReportForm::drawRect(const QRect& r, int type)
+{
+ QValueList<QRect> l;
+ l.append(r);
+ drawRects(l, type);
+}
+
+void
+KexiReportForm::drawRects(const QValueList<QRect> &list, int type)
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ if (prev_rect.isValid()) {
+ //redraw prev. selection's rectangle
+ p.drawPixmap( QPoint(prev_rect.x()-2, prev_rect.y()-2), buffer, QRect(prev_rect.x()-2, prev_rect.y()-2, prev_rect.width()+4, prev_rect.height()+4));
+ }
+ p.setBrush(QBrush::NoBrush);
+ if(type == 1) // selection rect
+ p.setPen(QPen(white, 1, Qt::DotLine));
+ else if(type == 2) // insert rect
+ p.setPen(QPen(white, 2));
+ p.setRasterOp(XorROP);
+
+ prev_rect = QRect();
+ QValueList<QRect>::ConstIterator endIt = list.constEnd();
+ for(QValueList<QRect>::ConstIterator it = list.constBegin(); it != endIt; ++it) {
+ p.drawRect(*it);
+ prev_rect = prev_rect.unite(*it);
+ }
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+}
+
+void
+KexiReportForm::initBuffer()
+{
+ repaintAll(this);
+ buffer.resize( width(), height() );
+ buffer = QPixmap::grabWindow( winId() );
+ prev_rect = QRect();
+}
+
+void
+KexiReportForm::clearForm()
+{
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ //redraw entire form surface
+ p.drawPixmap( QPoint(0,0), buffer, QRect(0,0,buffer.width(), buffer.height()) );
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+
+ repaintAll(this);
+}
+
+void
+KexiReportForm::highlightWidgets(QWidget *from, QWidget *to)//, const QPoint &point)
+{
+ QPoint fromPoint, toPoint;
+ if(from && from->parentWidget() && (from != this))
+ fromPoint = from->parentWidget()->mapTo(this, from->pos());
+ if(to && to->parentWidget() && (to != this))
+ toPoint = to->parentWidget()->mapTo(this, to->pos());
+
+ QPainter p;
+ p.begin(this, true);
+ bool unclipped = testWFlags( WPaintUnclipped );
+ setWFlags( WPaintUnclipped );
+
+ if (prev_rect.isValid()) {
+ //redraw prev. selection's rectangle
+ p.drawPixmap( QPoint(prev_rect.x(), prev_rect.y()), buffer, QRect(prev_rect.x(), prev_rect.y(), prev_rect.width(), prev_rect.height()));
+ }
+
+ p.setPen( QPen(Qt::red, 2) );
+
+ if(to)
+ {
+ QPixmap pix1 = QPixmap::grabWidget(from);
+ QPixmap pix2 = QPixmap::grabWidget(to);
+
+ if((from != this) && (to != this))
+ p.drawLine( from->parentWidget()->mapTo(this, from->geometry().center()), to->parentWidget()->mapTo(this, to->geometry().center()) );
+
+ p.drawPixmap(fromPoint.x(), fromPoint.y(), pix1);
+ p.drawPixmap(toPoint.x(), toPoint.y(), pix2);
+
+ if(to == this)
+ p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4);
+ else
+ p.drawRoundRect(toPoint.x(), toPoint.y(), to->width(), to->height(), 5, 5);
+ }
+
+ if(from == this)
+ p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4);
+ else
+ p.drawRoundRect(fromPoint.x(), fromPoint.y(), from->width(), from->height(), 5, 5);
+
+ if((to == this) || (from == this))
+ prev_rect = QRect(0, 0, buffer.width(), buffer.height());
+ else if(to)
+ {
+ prev_rect.setX( (fromPoint.x() < toPoint.x()) ? (fromPoint.x() - 5) : (toPoint.x() - 5) );
+ prev_rect.setY( (fromPoint.y() < toPoint.y()) ? (fromPoint.y() - 5) : (toPoint.y() - 5) );
+ prev_rect.setRight( (fromPoint.x() < toPoint.x()) ? (toPoint.x() + to->width() + 10) : (fromPoint.x() + from->width() + 10) );
+ prev_rect.setBottom( (fromPoint.y() < toPoint.y()) ? (toPoint.y() + to->height() + 10) : (fromPoint.y() + from->height() + 10) ) ;
+ }
+ else
+ prev_rect = QRect(fromPoint.x()- 5, fromPoint.y() -5, from->width() + 10, from->height() + 10);
+
+ if (!unclipped)
+ clearWFlags( WPaintUnclipped );
+ p.end();
+}
+
+QSize
+KexiReportForm::sizeHint() const
+{
+ //todo: find better size (user configured?)
+ return QSize(400,300);
+}
+
+#include "kexireportform.moc"
+
diff --git a/kexi/plugins/reports/kexireportform.h b/kexi/plugins/reports/kexireportform.h
new file mode 100644
index 000000000..8b03c2ecf
--- /dev/null
+++ b/kexi/plugins/reports/kexireportform.h
@@ -0,0 +1,60 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIREPORTFORM_H
+#define KEXIREPORTFORM_H
+
+#include <qwidget.h>
+#include <qpixmap.h>
+
+#include <formeditor/form.h>
+
+//! The report top widget
+class KEXIREPORTUTILS_EXPORT KexiReportForm : public QWidget, public KFormDesigner::FormWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiReportForm(QWidget *parent, const char *name="kexi_dbform");
+ virtual ~KexiReportForm();
+
+ /*QString datasource() const { return m_ds; }
+ bool navigatorShown() const { return m_nav; }
+ void setDatasource(const QString &s) { m_ds = s; }
+ void showRecordNavigator(bool s) { m_nav = s; }*/
+
+ virtual void drawRect(const QRect& r, int type);
+ virtual void drawRects(const QValueList<QRect> &list, int type);
+ virtual void initBuffer();
+ virtual void clearForm();
+ virtual void highlightWidgets(QWidget *from, QWidget *to/*, const QPoint &p*/);
+
+ virtual QSize sizeHint() const;
+
+ private:
+ /*QString m_ds;
+ bool m_nav;
+ KexiDB::Connection *m_conn;*/
+
+ QPixmap buffer; //!< stores grabbed entire form's area for redraw
+ QRect prev_rect; //!< previously selected rectangle
+};
+
+#endif
diff --git a/kexi/plugins/reports/kexireporthandler.desktop b/kexi/plugins/reports/kexireporthandler.desktop
new file mode 100644
index 000000000..e9606dbf6
--- /dev/null
+++ b/kexi/plugins/reports/kexireporthandler.desktop
@@ -0,0 +1,108 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+GenericName=Reports
+GenericName[bg]=Отчети
+GenericName[ca]=Informes
+GenericName[cs]=Sestavy
+GenericName[cy]=Adroddiadau
+GenericName[da]=Rapporter
+GenericName[de]=Berichte
+GenericName[el]=Αναφορές
+GenericName[eo]=Raportoj
+GenericName[es]=Informes
+GenericName[et]=Aruanded
+GenericName[eu]=Txostenak
+GenericName[fa]=گزارشها
+GenericName[fi]=Raportit
+GenericName[fr]=Rapports
+GenericName[fy]=Rapporten
+GenericName[ga]=Tuairiscí
+GenericName[gl]=Informes
+GenericName[he]=דו"חות
+GenericName[hr]=Izvještaji
+GenericName[hu]=Jelentések
+GenericName[is]=Skýrslur
+GenericName[it]=Rapporti
+GenericName[ja]=レポート
+GenericName[km]=របាយការណ៍
+GenericName[lt]=Ataskaitos
+GenericName[lv]=Atskaites
+GenericName[ms]=Laporan
+GenericName[nb]=Rapporter
+GenericName[nds]=Berichten
+GenericName[ne]=प्रतिवेदनहरू
+GenericName[nl]=Rapporten
+GenericName[nn]=Rapportar
+GenericName[pl]=Raporty
+GenericName[pt]=Relatórios
+GenericName[pt_BR]=Relatórios
+GenericName[ru]=Отчёты
+GenericName[se]=Raporttat
+GenericName[sk]=Správy
+GenericName[sl]=Poročila
+GenericName[sr]=Извештаји
+GenericName[sr@Latn]=Izveštaji
+GenericName[sv]=Rapporter
+GenericName[uk]=Звіти
+GenericName[uz]=Hisobotlar
+GenericName[uz@cyrillic]=Ҳисоботлар
+GenericName[zh_CN]=报表
+GenericName[zh_TW]=報告
+Name=Reports
+Name[bg]=Отчети
+Name[ca]=Informes
+Name[cs]=Sestavy
+Name[cy]=Adroddiadau
+Name[da]=Rapporter
+Name[de]=Berichte
+Name[el]=Αναφορές
+Name[eo]=Raportoj
+Name[es]=Informes
+Name[et]=Aruanded
+Name[eu]=Txostenak
+Name[fa]=گزارشها
+Name[fi]=Raportit
+Name[fr]=Rapports
+Name[fy]=Rapporten
+Name[ga]=Tuairiscí
+Name[gl]=Informes
+Name[he]=דו"חות
+Name[hr]=Izvještaji
+Name[hu]=Jelentések
+Name[is]=Skýrslur
+Name[it]=Rapporti
+Name[ja]=レポート
+Name[km]=របាយការណ៍
+Name[lt]=Ataskaitos
+Name[lv]=Atskaites
+Name[ms]=Laporan
+Name[nb]=Rapporter
+Name[nds]=Berichten
+Name[ne]=प्रतिवेदनहरू
+Name[nl]=Rapporten
+Name[nn]=Rapportar
+Name[pl]=Raporty
+Name[pt]=Relatórios
+Name[pt_BR]=Relatórios
+Name[ru]=Отчёты
+Name[se]=Raporttat
+Name[sk]=Správy
+Name[sl]=Poročila
+Name[sr]=Извештаји
+Name[sr@Latn]=Izveštaji
+Name[sv]=Rapporter
+Name[uk]=Звіти
+Name[uz]=Hisobotlar
+Name[uz@cyrillic]=Ҳисоботлар
+Name[zh_CN]=报表
+Name[zh_TW]=報告
+X-KDE-Library=kexihandler_report
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=report
+X-Kexi-TypeMime=kexi/report
+X-Kexi-ItemIcon=report
+X-Kexi-SupportsDataExport=false
+X-Kexi-SupportsPrinting=false
diff --git a/kexi/plugins/reports/kexireportpart.cpp b/kexi/plugins/reports/kexireportpart.cpp
new file mode 100644
index 000000000..ad83cbf45
--- /dev/null
+++ b/kexi/plugins/reports/kexireportpart.cpp
@@ -0,0 +1,141 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+#include <kgenericfactory.h>
+
+#include "kexiviewbase.h"
+#include "keximainwindow.h"
+#include "kexiproject.h"
+#include <kexipartitem.h>
+#include <kexidialogbase.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/fieldlist.h>
+#include <kexidb/field.h>
+
+#include <form.h>
+#include <formIO.h>
+#include <widgetlibrary.h>
+
+#include <kexiformmanager.h>
+#include <kexiformpart.h>
+
+#include "kexireportview.h"
+#include "kexireportpart.h"
+
+KFormDesigner::WidgetLibrary* KexiReportPart::static_reportsLibrary = 0L;
+
+KexiReportPart::KexiReportPart(QObject *parent, const char *name, const QStringList &l)
+ : KexiPart::Part(parent, name, l)
+{
+ // REGISTERED ID:
+ m_registeredPartID = (int)KexiPart::ReportObjectType;
+
+ kexipluginsdbg << "KexiReportPart::KexiReportPart()" << endl;
+ m_names["instanceName"]
+ = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). "
+ "Use '_' character instead of spaces. First character should be a..z character. "
+ "If you cannot use latin characters in your language, use english word.",
+ "report");
+ m_names["instanceCaption"] = i18n("Report");
+ m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode;
+
+ // Only create form manager if it's not yet created.
+ // KexiFormPart could have created is already.
+ KFormDesigner::FormManager *formManager = KFormDesigner::FormManager::self();
+ if (!formManager)
+ formManager = new KexiFormManager(this, "kexi_form_and_report_manager");
+
+ // Create and store a handle to report' library. Forms will have their own library too.
+/* @todo add configuration for supported factory groups */
+ QStringList supportedFactoryGroups;
+ supportedFactoryGroups += "kexi-report";
+ static_reportsLibrary = KFormDesigner::FormManager::createWidgetLibrary(
+ formManager, supportedFactoryGroups);
+ static_reportsLibrary->setAdvancedPropertiesVisible(false);
+}
+
+KexiReportPart::~KexiReportPart()
+{
+}
+
+KFormDesigner::WidgetLibrary* KexiReportPart::library()
+{
+ return static_reportsLibrary;
+}
+
+void
+KexiReportPart::initPartActions()
+{
+}
+
+void
+KexiReportPart::initInstanceActions()
+{
+ KFormDesigner::FormManager::self()->createActions(
+ library(), actionCollectionForMode(Kexi::DesignViewMode), guiClient());
+}
+
+KexiDialogTempData*
+KexiReportPart::createTempData(KexiDialogBase* dialog)
+{
+ return new KexiReportPart::TempData(dialog);
+}
+
+KexiViewBase*
+KexiReportPart::createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int, QMap<QString,QString>*)
+{
+ kexipluginsdbg << "KexiReportPart::createView()" << endl;
+ KexiMainWindow *win = dialog->mainWin();
+ if (!win || !win->project() || !win->project()->dbConnection())
+ return 0;
+
+ KexiReportView *view = new KexiReportView(win, parent, item.name().latin1(),
+ win->project()->dbConnection() );
+
+ return view;
+}
+
+QString
+KexiReportPart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const
+{
+ Q_UNUSED(dlg);
+ if (englishMessage=="Design of object \"%1\" has been modified.")
+ return i18n("Design of report \"%1\" has been modified.");
+ if (englishMessage=="Object \"%1\" already exists.")
+ return i18n("Report \"%1\" already exists.");
+ return englishMessage;
+}
+
+//---------------
+
+KexiReportPart::TempData::TempData(QObject* parent)
+ : KexiDialogTempData(parent)
+{
+}
+
+KexiReportPart::TempData::~TempData()
+{
+}
+
+#include "kexireportpart.moc"
+
diff --git a/kexi/plugins/reports/kexireportpart.h b/kexi/plugins/reports/kexireportpart.h
new file mode 100644
index 000000000..19731e574
--- /dev/null
+++ b/kexi/plugins/reports/kexireportpart.h
@@ -0,0 +1,88 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIREPORTPART_H
+#define KEXIREPORTPART_H
+
+#include <kexi.h>
+#include <kexipart.h>
+#include <kexidialogbase.h>
+
+namespace KFormDesigner
+{
+ class FormManager;
+ class WidgetLibrary;
+ class Form;
+}
+
+namespace KexiDB
+{
+ class FieldList;
+}
+
+/*! @short Kexi Report Plugin
+ It just creates a \ref KexiReportView. See there for most of code. */
+class KEXIREPORTUTILS_EXPORT KexiReportPart : public KexiPart::Part
+{
+ Q_OBJECT
+
+ public:
+ KexiReportPart(QObject *parent, const char *name, const QStringList &);
+ virtual ~KexiReportPart();
+
+ //! \return a pointer to Reports Widget Library.
+ static KFormDesigner::WidgetLibrary* library();
+
+// KFormDesigner::FormManager *manager() { return m_manager; }
+
+ void generateForm(KexiDB::FieldList *list, QDomDocument &domDoc);
+
+ class TempData : public KexiDialogTempData
+ {
+ public:
+ TempData(QObject* parent);
+ ~TempData();
+ QGuardedPtr<KFormDesigner::Form> form;
+ QGuardedPtr<KFormDesigner::Form> previewForm;
+ QString tempForm;
+ QPoint scrollViewContentsPos; //!< to preserve contents pos after switching to other view
+ int resizeMode; //!< form's window's resize mode -one of KexiFormView::ResizeMode items
+ };
+
+ virtual QString i18nMessage(const QCString& englishMessage,
+ KexiDialogBase* dlg) const;
+
+ protected:
+ virtual KexiDialogTempData* createTempData(KexiDialogBase* dialog);
+
+ virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode = Kexi::DataViewMode, QMap<QString,QString>* staticObjectArgs = 0);
+
+ virtual void initPartActions();
+ virtual void initInstanceActions();
+
+ static KFormDesigner::WidgetLibrary* static_reportsLibrary;
+
+ private:
+// QGuardedPtr<KFormDesigner::FormManager> m_manager;
+};
+
+#endif
+
diff --git a/kexi/plugins/reports/kexireportpartinstui.rc b/kexi/plugins/reports/kexireportpartinstui.rc
new file mode 100644
index 000000000..9bfc8fe3f
--- /dev/null
+++ b/kexi/plugins/reports/kexireportpartinstui.rc
@@ -0,0 +1,37 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexireportpartinst" version="1">
+
+<MenuBar>
+ <Menu name="edit">
+ <Action name="fompart_clear_contents"/>
+ </Menu>
+ <Menu name="format" noMerge="1">
+ <text>&amp;Format</text>
+ <Action name="snap_to_grid"/>
+ <Separator/>
+ <Action name="reportpart_align_menu"/>
+ <Action name="reportpart_adjust_size_menu"/>
+ <Separator/>
+ <Action name="reportpart_format_raise"/>
+ <Action name="reportpart_format_lower"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="widgets" fullWidth="false">
+ <text>Widgets</text>
+ <Action name="pointer"/>
+ <Separator/>
+ <Action name="library_widget_KexiSubReport"/>
+ <Separator/>
+ <Action name="library_widget_Label"/>
+ <Action name="library_widget_PicLabel"/>
+ <Action name="library_widget_ReportLine"/>
+</ToolBar>
+<ToolBar name="format" fullWidth="false" noMerge="1">
+<text>Format Toolbar</text>
+ <Action name="reportpart_align_menu"/>
+ <Action name="reportpart_adjust_size_menu"/>
+</ToolBar>
+
+</kpartgui>
+
diff --git a/kexi/plugins/reports/kexireportpartui.rc b/kexi/plugins/reports/kexireportpartui.rc
new file mode 100644
index 000000000..a81b09b1b
--- /dev/null
+++ b/kexi/plugins/reports/kexireportpartui.rc
@@ -0,0 +1,6 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexireportpart" version="1">
+
+
+</kpartgui>
+
diff --git a/kexi/plugins/reports/kexireports.cpp b/kexi/plugins/reports/kexireports.cpp
new file mode 100644
index 000000000..51b030542
--- /dev/null
+++ b/kexi/plugins/reports/kexireports.cpp
@@ -0,0 +1,24 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kgenericfactory.h>
+
+#include "kexireportpart.h"
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_report, KGenericFactory<KexiReportPart>("kexihandler_report") )
diff --git a/kexi/plugins/reports/kexireportview.cpp b/kexi/plugins/reports/kexireportview.cpp
new file mode 100644
index 000000000..6b7f46b59
--- /dev/null
+++ b/kexi/plugins/reports/kexireportview.cpp
@@ -0,0 +1,477 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexireportview.h"
+
+#include <kdebug.h>
+
+#include <form.h>
+#include <formIO.h>
+#include <formmanager.h>
+#include <objecttree.h>
+#include <widgetpropertyset.h>
+#include <container.h>
+
+#include <kexidialogbase.h>
+//#include <kexidatasourcewizard.h>
+#include <kexidb/fieldlist.h>
+#include <kexidb/connection.h>
+
+#include "kexireportform.h"
+#include <utils/kexirecordnavigator.h>
+
+#define NO_DSWIZARD
+
+KexiReportScrollView::KexiReportScrollView(QWidget *parent, bool preview)
+ : KexiScrollView(parent, preview)
+{
+ if(preview) {
+ setRecordNavigatorVisible(true);
+ recordNavigator()->setLabelText(i18n("Page:"));
+ recordNavigator()->setInsertingButtonVisible(false);
+ }
+ connect(this, SIGNAL(resizingStarted()), this, SLOT(slotResizingStarted()));
+}
+
+KexiReportScrollView::~KexiReportScrollView()
+{
+}
+
+void
+KexiReportScrollView::show()
+{
+ KexiScrollView::show();
+
+ //now get resize mode settings for entire form
+ if (m_preview) {
+ KexiReportView* fv = dynamic_cast<KexiReportView*>(parent());
+ int resizeMode = fv ? fv->resizeMode() : KexiReportView::ResizeAuto;
+ if (resizeMode == KexiReportView::ResizeAuto)
+ setResizePolicy(AutoOneFit);
+ }
+}
+
+void
+KexiReportScrollView::slotResizingStarted()
+{
+ if(m_form && KFormDesigner::FormManager::self())
+ setSnapToGrid(KFormDesigner::FormManager::self()->snapWidgetsToGrid(), m_form->gridSize());
+ else
+ setSnapToGrid(false);
+}
+
+//////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////
+
+KexiReportView::KexiReportView(KexiMainWindow *win, QWidget *parent, const char *name,
+ KexiDB::Connection *conn)
+ : KexiViewBase(win, parent, name), m_propertySet(0), m_conn(conn)
+ , m_resizeMode(KexiReportView::ResizeDefault)
+{
+ QHBoxLayout *l = new QHBoxLayout(this);
+ l->setAutoAdd(true);
+
+ m_scrollView = new KexiReportScrollView(this, viewMode()==Kexi::DataViewMode);
+ setViewWidget(m_scrollView);
+// m_scrollView->show();
+
+ m_reportform = new KexiReportForm(m_scrollView->viewport(), name/*, conn*/);
+// m_reportform->resize(QSize(400, 300));
+ m_scrollView->setWidget(m_reportform);
+ m_scrollView->setResizingEnabled(viewMode()!=Kexi::DataViewMode);
+
+// initForm();
+
+ if (viewMode()==Kexi::DataViewMode) {
+ m_scrollView->viewport()->setPaletteBackgroundColor(m_reportform->palette().active().background());
+#if 0
+ connect(reportPart()->manager(), SIGNAL(noFormSelected()), SLOT(slotNoFormSelected()));
+#endif
+ }
+ else {
+ connect(KFormDesigner::FormManager::self(), SIGNAL(propertySetSwitched(KoProperty::Set *, bool)),
+ this, SLOT(slotPropertySetSwitched(KoProperty::Set *, bool)));
+ connect(KFormDesigner::FormManager::self(), SIGNAL(dirty(KFormDesigner::Form *, bool)),
+ this, SLOT(slotDirty(KFormDesigner::Form *, bool)));
+
+ // action stuff
+ /*connect(reportPart()->manager(), SIGNAL(widgetSelected(KFormDesigner::Form*, bool)), SLOT(slotWidgetSelected(KFormDesigner::Form*, bool)));
+ connect(reportPart()->manager(), SIGNAL(formWidgetSelected(KFormDesigner::Form*)), SLOT(slotFormWidgetSelected(KFormDesigner::Form*)));
+ connect(reportPart()->manager(), SIGNAL(undoEnabled(bool, const QString&)), this, SLOT(setUndoEnabled(bool)));
+ connect(reportPart()->manager(), SIGNAL(redoEnabled(bool, const QString&)), this, SLOT(setRedoEnabled(bool)));*/
+
+ plugSharedAction("edit_copy", KFormDesigner::FormManager::self(), SLOT(copyWidget()));
+ plugSharedAction("edit_cut", KFormDesigner::FormManager::self(), SLOT(cutWidget()));
+ plugSharedAction("edit_paste", KFormDesigner::FormManager::self(), SLOT(pasteWidget()));
+ plugSharedAction("edit_delete", KFormDesigner::FormManager::self(), SLOT(deleteWidget()));
+ plugSharedAction("edit_select_all", KFormDesigner::FormManager::self(), SLOT(selectAll()));
+ plugSharedAction("reportpart_clear_contents", KFormDesigner::FormManager::self(), SLOT(clearWidgetContent()));
+ plugSharedAction("edit_undo", KFormDesigner::FormManager::self(), SLOT(undo()));
+ plugSharedAction("edit_redo", KFormDesigner::FormManager::self(), SLOT(redo()));
+
+ plugSharedAction("reportpart_format_raise", KFormDesigner::FormManager::self(), SLOT(bringWidgetToFront()) );
+ plugSharedAction("reportpart_format_lower", KFormDesigner::FormManager::self(), SLOT(sendWidgetToBack()) );
+
+ plugSharedAction("reportpart_align_menu", KFormDesigner::FormManager::self(), 0 );
+ plugSharedAction("reportpart_align_to_left", KFormDesigner::FormManager::self(),SLOT(alignWidgetsToLeft()) );
+ plugSharedAction("reportpart_align_to_right", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToRight()) );
+ plugSharedAction("reportpart_align_to_top", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToTop()) );
+ plugSharedAction("reportpart_align_to_bottom", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToBottom()) );
+ plugSharedAction("reportpart_align_to_grid", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToGrid()) );
+
+ plugSharedAction("reportpart_adjust_size_menu", KFormDesigner::FormManager::self(), 0 );
+ plugSharedAction("reportpart_adjust_to_fit", KFormDesigner::FormManager::self(), SLOT(adjustWidgetSize()) );
+ plugSharedAction("reportpart_adjust_size_grid", KFormDesigner::FormManager::self(), SLOT(adjustSizeToGrid()) );
+ plugSharedAction("reportpart_adjust_height_small", KFormDesigner::FormManager::self(), SLOT(adjustHeightToSmall()) );
+ plugSharedAction("reportpart_adjust_height_big", KFormDesigner::FormManager::self(), SLOT(adjustHeightToBig()) );
+ plugSharedAction("reportpart_adjust_width_small", KFormDesigner::FormManager::self(), SLOT(adjustWidthToSmall()) );
+ plugSharedAction("reportpart_adjust_width_big", KFormDesigner::FormManager::self(), SLOT(adjustWidthToBig()) );
+ }
+
+ initForm();
+
+ connect(this, SIGNAL(focus(bool)), this, SLOT(slotFocus(bool)));
+ /// @todo skip this if ther're no borders
+// m_reportform->resize( m_reportform->size()+QSize(m_scrollView->verticalScrollBar()->width(), m_scrollView->horizontalScrollBar()->height()) );
+}
+
+KexiReportView::~KexiReportView()
+{
+ // Important: form window is closed.
+ // Set property set to 0 because there is *only one* instance of a property set class
+ // in Kexi, so the main window wouldn't know the set in fact has been changed.
+ m_propertySet = 0;
+ propertySetSwitched();
+}
+
+KFormDesigner::Form*
+KexiReportView::form() const
+{
+ if(viewMode()==Kexi::DataViewMode)
+ return tempData()->previewForm;
+ else
+ return tempData()->form;
+}
+
+void
+KexiReportView::setForm(KFormDesigner::Form *f)
+{
+ if(viewMode()==Kexi::DataViewMode)
+ tempData()->previewForm = f;
+ else
+ tempData()->form = f;
+}
+
+void
+KexiReportView::initForm()
+{
+ setForm( new KFormDesigner::Form(KexiReportPart::library()) );
+ form()->createToplevel(m_reportform, m_reportform);
+
+ // Show the form wizard if this is a new Form
+// KexiDB::FieldList *fields = 0;
+ if(parentDialog()->id() < 0)
+ {
+#ifndef NO_DSWIZARD
+ KexiDataSourceWizard *w = new KexiDataSourceWizard(mainWin(), (QWidget*)mainWin(), "datasource_wizard");
+ if(!w->exec())
+ fields = 0;
+ else
+ fields = w->fields();
+ delete w;
+#endif
+ }
+
+/* if(fields)
+ {
+ @todo generate a report from a table or a query
+ QDomDocument dom;
+ reportPart()->generateForm(fields, dom);
+ KFormDesigner::FormIO::loadFormFromDom(form(), m_reportform, dom);
+ }
+ else*/
+ loadForm();
+
+ KFormDesigner::FormManager::self()->importForm(form(), viewMode()==Kexi::DataViewMode);
+ m_scrollView->setForm(form());
+ m_scrollView->refreshContentsSize();
+}
+
+void
+KexiReportView::loadForm()
+{
+
+//@todo also load m_resizeMode !
+
+ kexipluginsdbg << "KexiReportForm::loadForm() Loading the form with id : " << parentDialog()->id() << endl;
+ // If we are previewing the Form, use the tempData instead of the form stored in the db
+ if(viewMode()==Kexi::DataViewMode && !tempData()->tempForm.isNull() ) {
+ KFormDesigner::FormIO::loadFormFromString(form(), m_reportform, tempData()->tempForm);
+ return;
+ }
+
+ // normal load
+ QString data;
+ loadDataBlock(data);
+ KFormDesigner::FormIO::loadFormFromString(form(), m_reportform, data);
+}
+
+void
+KexiReportView::slotPropertySetSwitched(KoProperty::Set *set, bool forceReload)
+{
+ m_propertySet = set;
+ if (forceReload)
+ propertySetReloaded(true/*preservePrevSelection*/);
+ else
+ propertySetSwitched();
+}
+
+tristate
+KexiReportView::beforeSwitchTo(int mode, bool &dontStore)
+{
+ if (mode!=viewMode() && viewMode()!=Kexi::DataViewMode) {
+ //remember our pos
+ tempData()->scrollViewContentsPos
+ = QPoint(m_scrollView->contentsX(), m_scrollView->contentsY());
+ }
+
+ // we don't store on db, but in our TempData
+ dontStore = true;
+ if(dirty() && (mode == Kexi::DataViewMode) && form()->objectTree())
+ KFormDesigner::FormIO::saveFormToString(form(), tempData()->tempForm);
+
+ return true;
+}
+
+tristate
+KexiReportView::afterSwitchFrom(int mode)
+{
+ if (mode != 0 && mode != Kexi::DesignViewMode) {
+ //preserve contents pos after switching to other view
+ m_scrollView->setContentsPos(tempData()->scrollViewContentsPos.x(),
+ tempData()->scrollViewContentsPos.y());
+ }
+// if (mode == Kexi::DesignViewMode) {
+ //m_scrollView->move(0,0);
+ //m_scrollView->setContentsPos(0,0);
+ //m_scrollView->moveChild(m_reportform, 0, 0);
+// }
+
+ if((mode == Kexi::DesignViewMode) && viewMode()==Kexi::DataViewMode) {
+ // The form may have been modified, so we must recreate the preview
+ delete m_reportform; // also deletes form()
+ m_reportform = new KexiReportForm(m_scrollView->viewport());
+ m_scrollView->setWidget(m_reportform);
+
+ initForm();
+#if 0
+ slotNoFormSelected();
+#endif
+
+ //reset position
+ m_scrollView->setContentsPos(0,0);
+ m_reportform->move(0,0);
+ }
+ return true;
+}
+
+void
+KexiReportView::slotDirty(KFormDesigner::Form *dirtyForm, bool isDirty)
+{
+ if(dirtyForm == form())
+ KexiViewBase::setDirty(isDirty);
+}
+
+KexiDB::SchemaData*
+KexiReportView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ KexiDB::SchemaData *s = KexiViewBase::storeNewData(sdata, cancel);
+ kexipluginsdbg << "KexiReportForm::storeNewData(): new id:" << s->id() << endl;
+
+ if (!s || cancel) {
+ delete s;
+ return 0;
+ }
+ if (!storeData()) {
+ //failure: remove object's schema data to avoid garbage
+ m_conn->removeObject( s->id() );
+ delete s;
+ return 0;
+ }
+ return s;
+}
+
+tristate
+KexiReportView::storeData(bool dontAsk)
+{
+ Q_UNUSED(dontAsk)
+ kexipluginsdbg << "KexiReportForm::storeData(): " << parentDialog()->partItem()->name() << " [" << parentDialog()->id() << "]" << endl;
+ QString data;
+ KFormDesigner::FormIO::saveFormToString(tempData()->form, data);
+ if (!storeDataBlock(data))
+ return false;
+ tempData()->tempForm = QString();
+
+ return true;
+}
+
+#if 0
+/// Action stuff /////////////////
+void
+KexiReportView::slotWidgetSelected(KFormDesigner::Form *f, bool multiple)
+{
+ if(f != form())
+ return;
+
+ enableFormActions();
+ // Enable edit actions
+ setAvailable("edit_copy", true);
+ setAvailable("edit_cut", true);
+ setAvailable("edit_clear", true);
+
+ // 'Align Widgets' menu
+ setAvailable("reportpart_align_menu", multiple);
+ setAvailable("reportpart_align_to_left", multiple);
+ setAvailable("reportpart_align_to_right", multiple);
+ setAvailable("reportpart_align_to_top", multiple);
+ setAvailable("reportpart_align_to_bottom", multiple);
+
+ setAvailable("reportpart_adjust_size_menu", true);
+ setAvailable("reportpart_adjust_width_small", multiple);
+ setAvailable("reportpart_adjust_width_big", multiple);
+ setAvailable("reportpart_adjust_height_small", multiple);
+ setAvailable("reportpart_adjust_height_big", multiple);
+
+ setAvailable("reportpart_format_raise", true);
+ setAvailable("reportpart_format_lower", true);
+}
+
+void
+KexiReportView::slotFormWidgetSelected(KFormDesigner::Form *f)
+{
+ if(f != form())
+ return;
+
+ disableWidgetActions();
+ enableFormActions();
+}
+
+void
+KexiReportView::slotNoFormSelected() // == form in preview mode
+{
+ disableWidgetActions();
+
+ // Disable paste action
+ setAvailable("edit_paste", false);
+ setAvailable("edit_undo", false);
+ setAvailable("edit_redo", false);
+}
+
+void
+KexiReportView::enableFormActions()
+{
+ setAvailable("edit_paste", KFormDesigner::FormManager::self()->isPasteEnabled());
+}
+
+void
+KexiReportView::disableWidgetActions()
+{
+ // Disable edit actions
+ setAvailable("edit_copy", false);
+ setAvailable("edit_cut", false);
+ setAvailable("edit_clear", false);
+
+ // Disable format functions
+ setAvailable("reportpart_align_menu", false);
+ setAvailable("reportpart_align_to_left", false);
+ setAvailable("reportpart_align_to_right", false);
+ setAvailable("reportpart_align_to_top", false);
+ setAvailable("reportpart_align_to_bottom", false);
+
+ setAvailable("reportpart_adjust_size_menu", false);
+ setAvailable("reportpart_adjust_width_small", false);
+ setAvailable("reportpart_adjust_width_big", false);
+ setAvailable("reportpart_adjust_height_small", false);
+ setAvailable("reportpart_adjust_height_big", false);
+
+ setAvailable("reportpart_format_raise", false);
+ setAvailable("reportpart_format_lower", false);
+}
+
+void
+KexiReportView::setUndoEnabled(bool enabled)
+{
+ setAvailable("edit_undo", enabled);
+}
+
+void
+KexiReportView::setRedoEnabled(bool enabled)
+{
+ setAvailable("edit_redo", enabled);
+}
+#endif
+
+QSize
+KexiReportView::preferredSizeHint(const QSize& otherSize)
+{
+ return (m_reportform->size()
+ +QSize(m_scrollView->verticalScrollBar()->isVisible() ? m_scrollView->verticalScrollBar()->width()*3/2 : 10,
+ m_scrollView->horizontalScrollBar()->isVisible() ? m_scrollView->horizontalScrollBar()->height()*3/2 : 10))
+ .expandedTo( KexiViewBase::preferredSizeHint(otherSize) );
+}
+
+void
+KexiReportView::resizeEvent( QResizeEvent *e )
+{
+ if (viewMode()==Kexi::DataViewMode) {
+ m_scrollView->refreshContentsSizeLater(
+ e->size().width()!=e->oldSize().width(),
+ e->size().height()!=e->oldSize().height()
+ );
+ }
+ KexiViewBase::resizeEvent(e);
+ m_scrollView->updateNavPanelGeometry();
+}
+
+void
+KexiReportView::show()
+{
+ KexiViewBase::show();
+
+//moved from KexiFormScrollView::show():
+
+ //now get resize mode settings for entire form
+ // if (resizeMode() == KexiFormView::ResizeAuto)
+ if (viewMode()==Kexi::DataViewMode) {
+ if (resizeMode() == ResizeAuto)
+ m_scrollView->setResizePolicy(QScrollView::AutoOneFit);
+ }
+}
+
+void
+KexiReportView::slotFocus(bool in)
+{
+ if(in && form() && KFormDesigner::FormManager::self() && KFormDesigner::FormManager::self()->activeForm() != form())
+ KFormDesigner::FormManager::self()->windowChanged(form()->widget());//m_dbform);
+}
+
+
+#include "kexireportview.moc"
+
diff --git a/kexi/plugins/reports/kexireportview.h b/kexi/plugins/reports/kexireportview.h
new file mode 100644
index 000000000..b600c06d9
--- /dev/null
+++ b/kexi/plugins/reports/kexireportview.h
@@ -0,0 +1,130 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIREPORTVIEW_H
+#define KEXIREPORTVIEW_H
+
+#include <qscrollview.h>
+#include <qtimer.h>
+
+#include <kexiviewbase.h>
+
+#include "kexiscrollview.h"
+#include "kexireportpart.h"
+
+class KexiReportForm;
+
+class KEXIREPORTUTILS_EXPORT KexiReportScrollView : public KexiScrollView
+{
+ Q_OBJECT
+
+ public:
+ KexiReportScrollView(QWidget *parent, bool preview);
+ virtual ~KexiReportScrollView();
+
+ void setForm(KFormDesigner::Form *form) { m_form = form; }
+
+ public slots:
+ /*! Reimplemented to update resize policy. */
+ virtual void show();
+
+ protected slots:
+ void slotResizingStarted();
+
+ private:
+ KFormDesigner::Form *m_form;
+};
+
+
+//! The FormPart's view
+/*! This class presents a single view used inside KexiDialogBase.
+ It takes care of saving/loading report, of enabling actions when needed.
+ One KexiReportView object is instantiated for data view mode (preview == true in constructor),
+ and second KexiReportView object is instantiated for design view mode
+ (preview == false in constructor). */
+class KEXIREPORTUTILS_EXPORT KexiReportView : public KexiViewBase
+{
+ Q_OBJECT
+
+ public:
+ enum ResizeMode {
+ ResizeAuto = 0,
+ ResizeDefault = ResizeAuto,
+ ResizeFixed = 1,
+ NoResize = 2 /*! @todo */
+ };
+
+ KexiReportView(KexiMainWindow *win, QWidget *parent, const char *name, KexiDB::Connection *conn);
+ virtual ~KexiReportView();
+
+ KexiDB::Connection* connection() { return m_conn; }
+
+ virtual QSize preferredSizeHint(const QSize& otherSize);
+
+ int resizeMode() const { return m_resizeMode; }
+
+ public slots:
+ /*! Reimplemented to update resize policy. */
+ virtual void show();
+
+ protected slots:
+ void slotPropertySetSwitched(KoProperty::Set *set, bool forceReload = false);
+ void slotDirty(KFormDesigner::Form *f, bool isDirty);
+ void slotFocus(bool in);
+
+ /*void slotWidgetSelected(KFormDesigner::Form *form, bool multiple);
+ void slotFormWidgetSelected(KFormDesigner::Form *form);
+ void slotNoFormSelected();
+
+ void setUndoEnabled(bool enabled);
+ void setRedoEnabled(bool enabled); */
+
+ protected:
+ virtual tristate beforeSwitchTo(int mode, bool &dontStore);
+ virtual tristate afterSwitchFrom(int mode);
+ virtual KoProperty::Set* propertySet() { return m_propertySet; }
+
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+ virtual tristate storeData(bool dontAsk = false);
+
+ KexiReportPart::TempData* tempData() const {
+ return static_cast<KexiReportPart::TempData*>(parentDialog()->tempData()); }
+ KexiReportPart* reportPart() const { return static_cast<KexiReportPart*>(part()); }
+
+ void disableWidgetActions();
+ void enableFormActions();
+
+ KFormDesigner::Form* form() const;
+ void setForm(KFormDesigner::Form *f);
+
+ void initForm();
+ void loadForm();
+
+ virtual void resizeEvent ( QResizeEvent * );
+
+ private:
+ KexiReportForm *m_reportform;
+ KexiReportScrollView *m_scrollView;
+ KoProperty::Set *m_propertySet;
+ KexiDB::Connection *m_conn;
+ int m_resizeMode;
+};
+
+#endif
diff --git a/kexi/plugins/reports/kformdesigner_kexireportfactory.desktop b/kexi/plugins/reports/kformdesigner_kexireportfactory.desktop
new file mode 100644
index 000000000..46ccbd61e
--- /dev/null
+++ b/kexi/plugins/reports/kformdesigner_kexireportfactory.desktop
@@ -0,0 +1,53 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=KFormDesigner/WidgetFactory
+
+Name=Kexi Report Widgets
+Name[bg]=Графични елементи за отчети на Kexi
+Name[ca]=Estris d'informe de Kexi
+Name[cy]=Celfigion Adroddiad i Kexi
+Name[da]=Kexi Rapportkontroller
+Name[de]=Kexi Bericht-Elemente
+Name[el]=Γραφικά συστατικά αναφορών Kexi
+Name[eo]=Kexi-raportfenestraĵo
+Name[es]=Widgets para informes de Kexi
+Name[et]=Kexi aruandevidinad
+Name[eu]=Kexi-ren txosten-trepetak
+Name[fa]=عناصر گزارش Kexi
+Name[fi]=Kexi Raporttielementit
+Name[fr]=Éléments de rapport Kexi
+Name[fy]=Kexi Rapport-widgets
+Name[gl]=Elementos de Informe de Kexi
+Name[he]=פריטי דו"חות של Kexi
+Name[hr]=Kexi widgeti izvještaja
+Name[hu]=Kexi jelentéskezelő grafikus elemek
+Name[is]=Kexi skýrslu hlutar
+Name[it]=Oggetti dei rapporti per Kexi
+Name[ja]=Kexi レポートウィジェット
+Name[km]=ធាតុ​ក្រាហ្វិក​របាយការណ៍​​សម្រាប់ Kexi
+Name[lv]=Kexi atskaišu logdaļas
+Name[ms]=Widget Laporan Kexi
+Name[nb]=Skjermelement for Kexi-rapport
+Name[nds]=Bericht-Elementen för Kexi
+Name[ne]=केक्सी प्रतिवेदन विजेटहरू
+Name[nl]=Kexi Rapportwidgets
+Name[nn]=Skjermelement for Kexi-rapport
+Name[pl]=Kontrolki raportów dla Kexi
+Name[pt]=Elementos de Relatório do Kexi
+Name[pt_BR]=Widgets de Relatório do Kexi
+Name[ru]=Элементы управления для отчётов Kexi
+Name[se]=Kexi-raportaáđat
+Name[sk]=Moduly správ Kexi
+Name[sl]=Gradniki za poročila za Kexi
+Name[sr]=Kexi-јеве контроле за извештаје
+Name[sr@Latn]=Kexi-jeve kontrole za izveštaje
+Name[sv]=Kexi-rapportkomponenter
+Name[uk]=Віджети звітів Kexi
+Name[uz]=Kexi hisobot vidjetlari
+Name[uz@cyrillic]=Kexi ҳисобот виджетлари
+Name[zh_CN]=Kexi 报表部件
+Name[zh_TW]=Kexi 報告視窗元件
+
+X-KDE-Library=kformdesigner_kexireportwidgets
+X-KFormDesigner-FactoryGroup=kexi-report
+X-KFormDesigner-WidgetFactoryVersion=2
diff --git a/kexi/plugins/reports/reportwidgets.cpp b/kexi/plugins/reports/reportwidgets.cpp
new file mode 100644
index 000000000..5437325a2
--- /dev/null
+++ b/kexi/plugins/reports/reportwidgets.cpp
@@ -0,0 +1,181 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <qpainter.h>
+
+#include <form.h>
+#include <formIO.h>
+#include <formmanager.h>
+#include <kexidb/utils.h>
+#include <kexidb/connection.h>
+#include <kexipart.h>
+
+#include "kexireportview.h"
+#include "reportwidgets.h"
+
+Label::Label(const QString &text, QWidget *parent, const char *name)
+: QLabel(text, parent, name)
+{
+ setPaletteBackgroundColor(white);
+}
+
+////////////////////////////////////////////////////////////////////
+
+ReportLine::ReportLine(QWidget *parent, const char *name)
+: QWidget(parent, name)
+{
+ m_lineStyle = (ReportLineStyle)Qt::SolidLine;
+ m_lineWidth = 1;
+ m_capStyle = (CapStyle)Qt::FlatCap;
+ m_color = paletteForegroundColor();
+ setPaletteBackgroundColor(white);
+}
+
+ReportLine::ReportLineStyle
+ReportLine::lineStyle() const
+{
+ return m_lineStyle;
+}
+
+void
+ReportLine::setLineStyle(ReportLineStyle style)
+{
+ m_lineStyle = style;
+ update();
+}
+
+int
+ReportLine::lineWidth() const
+{
+ return m_lineWidth;
+}
+
+void
+ReportLine::setLineWidth(int width)
+{
+ m_lineWidth = width;
+ update();
+}
+
+QColor
+ReportLine::color() const
+{
+ return m_color;
+}
+
+void
+ReportLine::setColor(const QColor &color)
+{
+ m_color = color;
+ update();
+}
+
+ReportLine::CapStyle
+ReportLine::capStyle() const
+{
+ return m_capStyle;
+}
+
+void
+ReportLine::setCapStyle(CapStyle capStyle)
+{
+ m_capStyle = capStyle;
+ update();
+}
+
+void
+ReportLine::paintEvent (QPaintEvent *ev)
+{
+ QPainter p(this);
+ if(!ev->erased())
+ p.eraseRect(0, 0, width(), height());
+ QPen pen(m_color, m_lineWidth, (Qt::PenStyle)m_lineStyle);
+ pen.setCapStyle((Qt::PenCapStyle)m_capStyle);
+ p.setPen(pen);
+ p.drawLine(0, 0, width() -1, height() - 1);
+}
+
+////////////////////////////////////////////////////////////////////
+
+
+PicLabel::PicLabel(const QPixmap &pix, QWidget *parent, const char *name)
+ : QLabel(parent, name)
+{
+ setPixmap(pix);
+ setScaledContents(false);
+ setPaletteBackgroundColor(white);
+}
+
+bool
+PicLabel::setProperty(const char *name, const QVariant &value)
+{
+ if(QString(name) == "pixmap")
+ resize(value.toPixmap().height(), value.toPixmap().width());
+ return QLabel::setProperty(name, value);
+}
+
+////////////////////////////////////////////////////////////////////
+
+KexiSubReport::KexiSubReport(QWidget *parent, const char *name)
+: QScrollView(parent, name), m_form(0), m_widget(0)
+{
+ setFrameStyle(QFrame::Plain | QFrame::Box);
+ viewport()->setPaletteBackgroundColor(white);
+}
+
+void
+KexiSubReport::setReportName(const QString &name)
+{
+ if(name.isEmpty())
+ return;
+
+ // we need a KexiReportView*
+ QWidget *w = parentWidget();
+ while(w && !w->isA("KexiReportView"))
+ w = w->parentWidget();
+ KexiReportView *view = (KexiReportView*)w;
+ if(!view)
+ return;
+
+ // we check if there is a form with this name
+ int id = KexiDB::idForObjectName(*(view->connection()), name, KexiPart::ReportObjectType);
+ if((id == 0) || (id == view->parentDialog()->id())) // == our form
+ return; // because of recursion when loading
+
+ // we create the container widget
+ delete m_widget;
+ m_widget = new QWidget(viewport(), "kexisubreport_widget");
+ m_widget->show();
+ addChild(m_widget);
+ m_form = new Form(KexiReportPart::library(), this->name());
+ m_form->createToplevel(m_widget);
+
+ // and load the sub form
+ QString data;
+ tristate res = view->connection()->loadDataBlock(id, data , QString::null);
+ if(res != true)
+ return;
+
+ KFormDesigner::FormIO::loadFormFromString(m_form, m_widget, data);
+ m_form->setDesignMode(false);
+
+ m_reportName = name;
+}
+
+#include "reportwidgets.moc"
+
diff --git a/kexi/plugins/reports/reportwidgets.h b/kexi/plugins/reports/reportwidgets.h
new file mode 100644
index 000000000..f8140b0c0
--- /dev/null
+++ b/kexi/plugins/reports/reportwidgets.h
@@ -0,0 +1,117 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIREPORTWIDGETS_H
+#define KEXIREPORTWIDGETS_H
+
+#include <qlabel.h>
+#include <qscrollview.h>
+
+namespace KFormDesigner {
+ class Form;
+ class FormManager;
+}
+
+using KFormDesigner::Form;
+
+//! A form embedded as a widget inside other form
+class KexiSubReport : public QScrollView
+{
+ Q_OBJECT
+ Q_PROPERTY(QString reportName READ reportName WRITE setReportName DESIGNABLE true);
+
+ public:
+ KexiSubReport(QWidget *parent, const char *name);
+ ~KexiSubReport() {}
+
+ //! \return the name of the subreport inside the db
+ QString reportName() const { return m_reportName; }
+ void setReportName(const QString &name);
+
+ private:
+// KFormDesigner::FormManager *m_manager;
+ Form *m_form;
+ QWidget *m_widget;
+ QString m_reportName;
+};
+
+//! A simple label inside a report
+class Label : public QLabel
+{
+ Q_OBJECT
+
+ public:
+ Label(const QString &text, QWidget *parent, const char *name);
+ ~Label() {}
+};
+
+//! A simple picture label inside a report
+class PicLabel : public QLabel
+{
+ Q_OBJECT
+
+ public:
+ PicLabel(const QPixmap &pix, QWidget *parent, const char *name);
+ ~PicLabel() {}
+
+ virtual bool setProperty(const char *name, const QVariant &value);
+};
+
+//! A line
+class ReportLine : public QWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(ReportLineStyle lineStyle READ lineStyle WRITE setLineStyle)
+ Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth)
+ Q_PROPERTY(QColor color READ color WRITE setColor)
+ Q_PROPERTY(CapStyle capStyle READ capStyle WRITE setCapStyle)
+
+ public:
+ enum ReportLineStyle { NoLine = Qt::NoPen, Solid = Qt::SolidLine, Dash = Qt::DashLine, Dot = Qt::DotLine,
+ DashDot = Qt::DashDotLine, DashDotDot = Qt::DashDotDotLine };
+ enum CapStyle { Flat = Qt::FlatCap, Square = Qt::SquareCap, Round = Qt::RoundCap };
+
+ ReportLine(QWidget *parent, const char *name);
+ ~ReportLine(){;}
+
+ ReportLineStyle lineStyle() const;
+ void setLineStyle(ReportLineStyle style);
+
+ int lineWidth() const;
+ void setLineWidth(int width);
+
+ QColor color() const;
+ void setColor(const QColor &color);
+
+ CapStyle capStyle() const;
+ void setCapStyle(CapStyle capStyle);
+
+ protected:
+ virtual void paintEvent (QPaintEvent *ev);
+
+ private:
+ ReportLineStyle m_lineStyle;
+ int m_lineWidth;
+ CapStyle m_capStyle;
+ QColor m_color;
+};
+
+
+#endif
+
diff --git a/kexi/plugins/scripting/Makefile.am b/kexi/plugins/scripting/Makefile.am
new file mode 100644
index 000000000..ccd1bd645
--- /dev/null
+++ b/kexi/plugins/scripting/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = kexiscripting kexiapp kexidb scripts
diff --git a/kexi/plugins/scripting/README b/kexi/plugins/scripting/README
new file mode 100644
index 000000000..e754b9cd4
--- /dev/null
+++ b/kexi/plugins/scripting/README
@@ -0,0 +1,28 @@
+Kexi Scripting README
+---------------------
+
+The code in this directory implements a scripting plugin for
+Kexi. The Kross Scripting Framework located at koffice/libs/kross
+is used to embed scripting interpreters and access Kexi
+functionality from within those interpreters.
+
+See also http://www.kexi-project.org/wiki/wikiview/index.php?Scripting
+
+/kexiscripting/
+The scripting-plugin which will be loaded by Kexi at startup to
+embed Kross into Kexi.
+
+/kexiapp/
+Access to a running Kexi application. Kexi itself takes care of
+publishing it's KexiMainWindowImpl instance and the kexiapp-plugin
+provides access to some of the applications functionality at runtime.
+
+/kexidb/
+Kross-plugin to provide nearly the whole KexiDB-framework to scripting
+interpreters. That way we are able to read/write from/to all by KexiDB
+supported databases.
+
+/scripts/
+Kexi-dependend scripts. This directory holds our in python or ruby
+written scripting extensions. Those extensions are just plugins for
+Kexi to extend it's functionality.
diff --git a/kexi/plugins/scripting/kexiapp/Makefile.am b/kexi/plugins/scripting/kexiapp/Makefile.am
new file mode 100644
index 000000000..a2702a265
--- /dev/null
+++ b/kexi/plugins/scripting/kexiapp/Makefile.am
@@ -0,0 +1,21 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+INCLUDES = -I$(top_srcdir)/kexi $(KROSS_INCLUDES) $(all_includes)
+
+kde_module_LTLIBRARIES = krosskexiapp.la
+
+krosskexiapp_la_SOURCES = \
+ kexiapppart.cpp \
+ kexiappmainwindow.cpp \
+ kexiappmodule.cpp
+
+krosskexiapp_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module
+krosskexiapp_la_LIBADD = \
+ $(LIB_QT) \
+ $(LIB_KDECORE) \
+ $(LIB_KROSS_API) \
+ $(LIB_KROSS_MAIN) \
+ $(top_builddir)/kexi/core/libkexicore.la
+
+METASOURCES = AUTO
+SUBDIRS = .
diff --git a/kexi/plugins/scripting/kexiapp/kexiappmainwindow.cpp b/kexi/plugins/scripting/kexiapp/kexiappmainwindow.cpp
new file mode 100644
index 000000000..4d82bc5de
--- /dev/null
+++ b/kexi/plugins/scripting/kexiapp/kexiappmainwindow.cpp
@@ -0,0 +1,106 @@
+/***************************************************************************
+ * kexiappmainwindow.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexiappmainwindow.h"
+#include "kexiapppart.h"
+
+#include "core/keximainwindow.h"
+#include "core/kexiproject.h"
+#include "core/kexi.h"
+#include "kexidb/connection.h"
+
+#include "main/manager.h"
+
+//#include <kdebug.h>
+
+namespace Kross { namespace KexiApp {
+
+ /// \internal
+ class KexiAppMainWindowPrivate
+ {
+ public:
+ KexiMainWindow* mainwindow;
+
+ KexiProject* project() {
+ KexiProject* project = mainwindow->project();
+ if(! project)
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception("No project loaded.") );
+ return project;
+ }
+ };
+
+}}
+
+using namespace Kross::KexiApp;
+
+KexiAppMainWindow::KexiAppMainWindow(KexiMainWindow* mainwindow)
+ : Kross::Api::Class<KexiAppMainWindow>("KexiAppMainWindow")
+ , d(new KexiAppMainWindowPrivate())
+{
+ d->mainwindow = mainwindow;
+
+ this->addFunction0<Kross::Api::Variant>("isConnected", this, &KexiAppMainWindow::isConnected);
+ this->addFunction0<Kross::Api::Object>("getConnection", this, &KexiAppMainWindow::getConnection);
+
+ this->addFunction1<Kross::Api::List, Kross::Api::Variant>("getPartItems", this, &KexiAppMainWindow::getPartItems);
+ this->addFunction1<Kross::Api::Variant, KexiAppPartItem>("openPartItem", this, &KexiAppMainWindow::openPartItem);
+}
+
+KexiAppMainWindow::~KexiAppMainWindow()
+{
+ delete d;
+}
+
+const QString KexiAppMainWindow::getClassName() const
+{
+ return "Kross::KexiApp::KexiAppMainWindow";
+}
+
+bool KexiAppMainWindow::isConnected()
+{
+ return d->project()->isConnected();
+}
+
+Kross::Api::Object::Ptr KexiAppMainWindow::getConnection()
+{
+ ::KexiDB::Connection* connection = d->project()->dbConnection();
+ if(! connection)
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception("No connection established.") );
+ Kross::Api::Module::Ptr module = Kross::Api::Manager::scriptManager()->loadModule("krosskexidb");
+ if(! module)
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception("Could not load \"krosskexidb\" module.") );
+ return module->get("KexiDBConnection", connection);
+}
+
+Kross::Api::List* KexiAppMainWindow::getPartItems(const QString& mimetype)
+{
+ if(mimetype.isNull()) return 0; // just to be sure...
+ KexiPart::ItemDict* items = d->project()->itemsForMimeType( mimetype.latin1() );
+ if(! items) return 0;
+ return new Kross::Api::ListT<KexiAppPartItem>( *items );
+}
+
+bool KexiAppMainWindow::openPartItem(KexiAppPartItem* partitem)
+{
+ bool openingCancelled;
+ KexiDialogBase* dialog = partitem
+ ? d->mainwindow->openObject(partitem->item(), Kexi::DataViewMode, openingCancelled)
+ : 0;
+ return (dialog != 0 && ! openingCancelled);
+}
diff --git a/kexi/plugins/scripting/kexiapp/kexiappmainwindow.h b/kexi/plugins/scripting/kexiapp/kexiappmainwindow.h
new file mode 100644
index 000000000..fd02c193c
--- /dev/null
+++ b/kexi/plugins/scripting/kexiapp/kexiappmainwindow.h
@@ -0,0 +1,91 @@
+/***************************************************************************
+ * kexiappmainwindow.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIAPP_KEXIAPPMAINWINDOW_H
+#define KROSS_KEXIAPP_KEXIAPPMAINWINDOW_H
+
+#include <qstring.h>
+#include <qvariant.h>
+
+#include <api/object.h>
+#include <api/variant.h>
+#include <api/list.h>
+#include <api/class.h>
+
+// Forward declarations.
+class KexiMainWindow;
+
+namespace Kross { namespace KexiApp {
+
+ // Forward declarations.
+ class KexiAppPartItem;
+ class KexiAppMainWindowPrivate;
+
+ /**
+ * Class to handle Kexi's mainwindow instance.
+ */
+ class KexiAppMainWindow : public Kross::Api::Class<KexiAppMainWindow>
+ {
+ public:
+
+ /**
+ * Constructor.
+ *
+ * \param mainwindow The \a KexiMainWindow instance
+ * this class provides access to.
+ */
+ KexiAppMainWindow(KexiMainWindow* mainwindow);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiAppMainWindow();
+
+ /// \see Kross::Api::Object::getClassName
+ virtual const QString getClassName() const;
+
+ /** \return true if Kexi is connected with a project else
+ false is returned. */
+ bool isConnected();
+
+ /** \return the \a Kross::KexiDB::KexiDBConnection object that
+ belongs to the opened project or throw an exception if there
+ was no project opened (no connection established). Cause the
+ KexiApp-module doesn't know anything about the KexiDB-module
+ we have to use the base-class \a Kross::Api::Object to pass
+ the \a Kross::KexiDB::KexiDBConnection object around. */
+ Kross::Api::Object::Ptr getConnection();
+
+ /** \return a list of \a KexiAppPartItem objects for the defined
+ \p mimetype string. */
+ Kross::Api::List* getPartItems(const QString& mimetype);
+
+ /** Try to open the defined \a KexiAppPartItem and \return true
+ on success else false. */
+ bool openPartItem(KexiAppPartItem* partitem);
+
+ private:
+ /// Private d-pointer class.
+ KexiAppMainWindowPrivate* d;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexiapp/kexiappmodule.cpp b/kexi/plugins/scripting/kexiapp/kexiappmodule.cpp
new file mode 100644
index 000000000..cb6644963
--- /dev/null
+++ b/kexi/plugins/scripting/kexiapp/kexiappmodule.cpp
@@ -0,0 +1,97 @@
+/***************************************************************************
+ * kexiappmodule.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexiappmodule.h"
+#include "kexiappmainwindow.h"
+
+#include "core/keximainwindow.h"
+
+#include <api/object.h>
+#include <api/qtobject.h>
+#include <main/manager.h>
+
+#include <kdebug.h>
+
+// The as version() published versionnumber of this kross-module.
+#define KROSS_KEXIAPP_VERSION 1
+
+extern "C"
+{
+ /**
+ * Exported an loadable function as entry point to use
+ * the \a KexiAppModule.
+ */
+ Kross::Api::Object* KDE_EXPORT init_module(Kross::Api::Manager* manager)
+ {
+ return new Kross::KexiApp::KexiAppModule(manager);
+ }
+}
+
+namespace Kross { namespace KexiApp {
+
+ /// \internal
+ class KexiAppModulePrivate
+ {
+ public:
+ Kross::Api::Manager* manager;
+ };
+
+}}
+
+using namespace Kross::KexiApp;
+
+KexiAppModule::KexiAppModule(Kross::Api::Manager* manager)
+ : Kross::Api::Module("KexiApp")
+ , d(new KexiAppModulePrivate())
+{
+ kdDebug() << "Kross::KexiApp::KexiAppModule Ctor" << endl;
+
+ d->manager = manager;
+
+ Kross::Api::Object::Ptr mainwinobject = manager->getChild("KexiMainWindow");
+ if(mainwinobject) {
+ Kross::Api::QtObject* mainwinqtobject = dynamic_cast< Kross::Api::QtObject* >( mainwinobject.data() );
+ if(mainwinqtobject) {
+ ::KexiMainWindow* mainwin = dynamic_cast< ::KexiMainWindow* >( mainwinqtobject->getObject() );
+ if(mainwin) {
+ addChild( "version", new Kross::Api::Variant(KROSS_KEXIAPP_VERSION) );
+ addChild( new KexiAppMainWindow(mainwin) );
+ return;
+ }
+ else kdDebug()<<"Kross::KexiApp::KexiAppModule: Failed to determinate KexiMainWindow instance"<<endl;
+ }
+ else kdDebug()<<"Kross::KexiApp::KexiAppModule: Failed to cast 'KexiMainWindow' to a QtObject"<<endl;
+ }
+ else kdDebug()<<"Kross::KexiApp::KexiAppModule: No such object 'KexiMainWindow'"<<endl;
+
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception("There was no 'KexiMainWindow' published.") );
+}
+
+KexiAppModule::~KexiAppModule()
+{
+ kdDebug() << "Kross::KexiApp::KexiAppModule Dtor" << endl;
+ delete d;
+}
+
+
+const QString KexiAppModule::getClassName() const
+{
+ return "Kross::KexiApp::KexiAppModule";
+}
+
diff --git a/kexi/plugins/scripting/kexiapp/kexiappmodule.h b/kexi/plugins/scripting/kexiapp/kexiappmodule.h
new file mode 100644
index 000000000..08ed71f0e
--- /dev/null
+++ b/kexi/plugins/scripting/kexiapp/kexiappmodule.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+ * kexiappmodule.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIAPP_KEXIAPPMODULE_H
+#define KROSS_KEXIAPP_KEXIAPPMODULE_H
+
+#include <qstring.h>
+#include <qvariant.h>
+
+#include <api/module.h>
+
+namespace Kross { namespace Api {
+ class Manager;
+}}
+
+namespace Kross {
+
+/**
+ * Wrapper around the Kexi-application to access runtime
+ * information a running Kexi-application likes to
+ * provide.
+ */
+namespace KexiApp {
+
+ class KexiAppModulePrivate;
+
+ /**
+ * The Kexi-application module which provides us the
+ * main entrypoint to communicate with a running
+ * Kexi-application.
+ */
+ class KexiAppModule : public Kross::Api::Module
+ {
+ public:
+
+ /**
+ * Constructor.
+ *
+ * \param manager The \a Kross::Api::Manager singleton
+ * instance used to access this module.
+ */
+ KexiAppModule(Kross::Api::Manager* manager);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiAppModule();
+
+ /// \see Kross::Api::Object::getClassName
+ virtual const QString getClassName() const;
+
+ private:
+ /// Private d-pointer class.
+ KexiAppModulePrivate* d;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexiapp/kexiapppart.cpp b/kexi/plugins/scripting/kexiapp/kexiapppart.cpp
new file mode 100644
index 000000000..23d6c2f5e
--- /dev/null
+++ b/kexi/plugins/scripting/kexiapp/kexiapppart.cpp
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * kexiapppart.cpp
+ * This file is part of the KDE project
+ * copyright (C)2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexiapppart.h"
+
+#include "core/kexipart.h"
+#include "core/kexipartitem.h"
+//#include "core/kexiproject.h"
+
+using namespace Kross::KexiApp;
+
+KexiAppPartItem::KexiAppPartItem(KexiPart::Item* item)
+ : Kross::Api::Class<KexiAppPartItem>("KexiAppPartItem")
+{
+ this->addFunction0<Kross::Api::Variant>("identifier", item, &::KexiPart::Item::identifier );
+
+ this->addFunction1<void, Kross::Api::Variant>("setIdentifier", item, &::KexiPart::Item::setIdentifier );
+
+ this->addFunction0<Kross::Api::Variant>("mimeType", item, &::KexiPart::Item::mimeType );
+ this->addFunction1<void, Kross::Api::Variant>("setMimeType", item, &::KexiPart::Item::setMimeType );
+
+ this->addFunction0<Kross::Api::Variant>("name", item, &::KexiPart::Item::name );
+ this->addFunction1<void, Kross::Api::Variant>("setName", item, &::KexiPart::Item::setName );
+
+ this->addFunction0<Kross::Api::Variant>("caption", item, &::KexiPart::Item::caption );
+ this->addFunction1<void, Kross::Api::Variant>("setCaption", item, &::KexiPart::Item::setCaption );
+
+ this->addFunction0<Kross::Api::Variant>("description", item, &::KexiPart::Item::description );
+ this->addFunction1<void, Kross::Api::Variant>("setDescription", item, &::KexiPart::Item::setDescription );
+}
diff --git a/kexi/plugins/scripting/kexiapp/kexiapppart.h b/kexi/plugins/scripting/kexiapp/kexiapppart.h
new file mode 100644
index 000000000..5f55d6bf1
--- /dev/null
+++ b/kexi/plugins/scripting/kexiapp/kexiapppart.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+ * kexiapppart.h
+ * This file is part of the KDE project
+ * copyright (C)2006 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIAPP_KEXIAPPPART_H
+#define KROSS_KEXIAPP_KEXIAPPPART_H
+
+#include <qstring.h>
+#include <qvariant.h>
+
+#include <api/object.h>
+#include <api/variant.h>
+#include <api/class.h>
+
+// Forward declarations.
+namespace KexiPart {
+ class Item;
+ class Part;
+}
+
+namespace Kross { namespace KexiApp {
+
+ /**
+ * Class to handle Kexi Part::Item instance.
+ */
+ class KexiAppPartItem : public Kross::Api::Class<KexiAppPartItem>
+ {
+ public:
+ KexiAppPartItem(KexiPart::Item*);
+ virtual ~KexiAppPartItem() {}
+ virtual const QString getClassName() const { return "Kross::KexiApp::KexiAppPartItem"; }
+
+ KexiPart::Item* item() { return m_item; }
+ private:
+ KexiPart::Item* m_item;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb.doxyfile b/kexi/plugins/scripting/kexidb.doxyfile
new file mode 100644
index 000000000..e40a378e4
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb.doxyfile
@@ -0,0 +1,324 @@
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+PROJECT_NAME = KrossKexiDB
+PROJECT_NUMBER = 1.0.
+OUTPUT_DIRECTORY = kexidbdocs
+CREATE_SUBDIRS = NO
+OUTPUT_LANGUAGE = English
+USE_WINDOWS_ENCODING = NO
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited
+# members of a class in the documentation of that class as if those members were
+# ordinary class members. Constructors, destructors and assignment operators of
+# the base classes will not be shown.
+INLINE_INHERITED_MEMB = NO
+##INLINE_INHERITED_MEMB = YES
+
+FULL_PATH_NAMES = NO
+#STRIP_FROM_PATH = /home/snoopy/
+STRIP_FROM_INC_PATH =
+SHORT_NAMES = YES
+JAVADOC_AUTOBRIEF = NO
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+DETAILS_AT_TOP = YES
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# reimplements.
+INHERIT_DOCS = YES
+
+DISTRIBUTE_GROUP_DOC = NO
+TAB_SIZE = 8
+ALIASES =
+OPTIMIZE_OUTPUT_FOR_C = NO
+OPTIMIZE_OUTPUT_JAVA = NO
+SUBGROUPING = NO
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL = NO
+EXTRACT_PRIVATE = YES
+EXTRACT_STATIC = NO
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_LOCAL_METHODS = YES
+HIDE_UNDOC_MEMBERS = YES
+HIDE_UNDOC_CLASSES = YES
+HIDE_FRIEND_COMPOUNDS = YES
+HIDE_IN_BODY_DOCS = YES
+INTERNAL_DOCS = NO
+
+CASE_SENSE_NAMES = YES
+HIDE_SCOPE_NAMES = YES
+SHOW_INCLUDE_FILES = NO
+INLINE_INFO = NO
+SORT_MEMBER_DOCS = NO
+SORT_BRIEF_DOCS = NO
+SORT_BY_SCOPE_NAME = NO
+GENERATE_TODOLIST = NO
+GENERATE_TESTLIST = NO
+GENERATE_BUGLIST = NO
+GENERATE_DEPRECATEDLIST= NO
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+SHOW_USED_FILES = NO
+SHOW_DIRECTORIES = NO
+FILE_VERSION_FILTER =
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+QUIET = NO
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = NO
+WARN_IF_DOC_ERROR = YES
+WARN_NO_PARAMDOC = NO
+WARN_FORMAT = "$file:$line: $text"
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+INPUT = ../kexidb/
+IMAGE_PATH =
+FILE_PATTERNS = *.cpp *.h *.dox
+RECURSIVE = YES
+
+EXCLUDE =
+EXCLUDE_SYMLINKS = YES
+EXCLUDE_PATTERNS = config.h *.moc.cpp
+
+EXAMPLE_PATH =
+EXAMPLE_PATTERNS = *
+EXAMPLE_RECURSIVE = NO
+
+INPUT_FILTER =
+FILTER_PATTERNS =
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+STRIP_CODE_COMMENTS = YES
+
+REFERENCED_BY_RELATION = NO
+REFERENCES_RELATION = NO
+VERBATIM_HEADERS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+ALPHABETICAL_INDEX = NO
+COLS_IN_ALPHA_INDEX = 4
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_HEADER =
+HTML_FOOTER =
+HTML_STYLESHEET =
+HTML_ALIGN_MEMBERS = YES
+GENERATE_HTMLHELP = NO
+CHM_FILE =
+HHC_LOCATION =
+GENERATE_CHI = NO
+BINARY_TOC = NO
+TOC_EXPAND = NO
+DISABLE_INDEX = NO
+ENUM_VALUES_PER_LINE = 4
+GENERATE_TREEVIEW = NO
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+GENERATE_LATEX = YES
+LATEX_OUTPUT = latex
+LATEX_CMD_NAME = latex
+MAKEINDEX_CMD_NAME = makeindex
+PAPER_TYPE = a4wide
+EXTRA_PACKAGES =
+LATEX_HEADER =
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+COMPACT_LATEX = YES
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+##PDF_HYPERLINKS = NO
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+##LATEX_BATCHMODE = NO
+LATEX_BATCHMODE = YES
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+GENERATE_XML = NO
+XML_OUTPUT = xml
+XML_SCHEMA =
+XML_DTD =
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+GENERATE_PERLMOD = NO
+PERLMOD_LATEX = NO
+PERLMOD_PRETTY = YES
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = NO
+EXPAND_ONLY_PREDEF = NO
+SEARCH_INCLUDES = NO
+INCLUDE_PATH =
+INCLUDE_FILE_PATTERNS =
+PREDEFINED =
+EXPAND_AS_DEFINED =
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+TAGFILES =
+GENERATE_TAGFILE = krosskexidb.tag
+ALLEXTERNALS = NO
+EXTERNAL_GROUPS = NO
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+HAVE_DOT = NO
+
+CLASS_DIAGRAMS = NO
+HIDE_UNDOC_RELATIONS = NO
+CLASS_GRAPH = NO
+COLLABORATION_GRAPH = NO
+GROUP_GRAPHS = NO
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similiar to the OMG's Unified Modeling
+# Language.
+UML_LOOK = NO
+
+TEMPLATE_RELATIONS = NO
+INCLUDE_GRAPH = NO
+INCLUDED_BY_GRAPH = NO
+##CALL_GRAPH = YES
+GRAPHICAL_HIERARCHY = NO
+DIRECTORY_GRAPH = NO
+DOT_IMAGE_FORMAT = png
+DOT_PATH =
+DOTFILE_DIRS =
+MAX_DOT_GRAPH_WIDTH = 1024
+MAX_DOT_GRAPH_HEIGHT = 1024
+MAX_DOT_GRAPH_DEPTH = 1000
+DOT_TRANSPARENT = NO
+DOT_MULTI_TARGETS = NO
+GENERATE_LEGEND = NO
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+SEARCHENGINE = NO
diff --git a/kexi/plugins/scripting/kexidb/Makefile.am b/kexi/plugins/scripting/kexidb/Makefile.am
new file mode 100644
index 000000000..12f825010
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/Makefile.am
@@ -0,0 +1,30 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+INCLUDES = -I$(top_srcdir)/kexi $(KROSS_INCLUDES) $(all_includes)
+
+kde_module_LTLIBRARIES = krosskexidb.la
+
+krosskexidb_la_SOURCES = \
+ kexidbfield.cpp \
+ kexidbfieldlist.cpp \
+ kexidbschema.cpp \
+ kexidbparser.cpp \
+ kexidbcursor.cpp \
+ kexidbtransaction.cpp \
+ kexidbconnectiondata.cpp \
+ kexidbconnection.cpp \
+ kexidbdriver.cpp \
+ kexidbdrivermanager.cpp \
+ kexidbmodule.cpp
+
+krosskexidb_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module
+krosskexidb_la_LIBADD = \
+ $(LIB_QT) \
+ $(LIB_KDECORE) \
+ $(LIB_KROSS_API) \
+ $(LIB_KROSS_MAIN) \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la
+
+METASOURCES = AUTO
+SUBDIRS = .
diff --git a/kexi/plugins/scripting/kexidb/kexidbconnection.cpp b/kexi/plugins/scripting/kexidb/kexidbconnection.cpp
new file mode 100644
index 000000000..d3b7cc76f
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbconnection.cpp
@@ -0,0 +1,221 @@
+/***************************************************************************
+ * kexidbconnection.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexidbconnection.h"
+#include "kexidbconnectiondata.h"
+#include "kexidbdrivermanager.h"
+#include "kexidbdriver.h"
+#include "kexidbcursor.h"
+#include "kexidbfieldlist.h"
+#include "kexidbschema.h"
+#include "kexidbtransaction.h"
+#include "kexidbparser.h"
+
+#include <api/exception.h>
+
+#include <kdebug.h>
+
+#include <kexidb/transaction.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBConnection::KexiDBConnection(::KexiDB::Connection* connection, KexiDBDriver* driver, KexiDBConnectionData* connectiondata)
+ : Kross::Api::Class<KexiDBConnection>("KexiDBConnection")
+ , m_connection(connection)
+ , m_connectiondata(connectiondata ? connectiondata : new KexiDBConnectionData(connection->data()))
+ , m_driver(driver ? driver : new KexiDBDriver(connection->driver()))
+{
+ this->addFunction0< Kross::Api::Variant >("hadError", this, &KexiDBConnection::hadError);
+ this->addFunction0< Kross::Api::Variant >("lastError", this, &KexiDBConnection::lastError);
+
+ this->addFunction0< KexiDBConnectionData >("data", this, &KexiDBConnection::data);
+ this->addFunction0< KexiDBDriver >("driver", this, &KexiDBConnection::driver);
+
+ this->addFunction0< Kross::Api::Variant >("connect", this, &KexiDBConnection::connect);
+ this->addFunction0< Kross::Api::Variant >("isConnected", this, &KexiDBConnection::isConnected);
+ this->addFunction0< Kross::Api::Variant >("disconnect", this, &KexiDBConnection::disconnect);
+
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("databaseExists", this, &KexiDBConnection::databaseExists);
+ this->addFunction0< Kross::Api::Variant >("currentDatabase", this, &KexiDBConnection::currentDatabase);
+ this->addFunction0< Kross::Api::Variant >("databaseNames", this, &KexiDBConnection::databaseNames);
+ this->addFunction0< Kross::Api::Variant >("isDatabaseUsed", this, &KexiDBConnection::isDatabaseUsed);
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("useDatabase", this, &KexiDBConnection::useDatabase);
+ this->addFunction0< Kross::Api::Variant >("closeDatabase", this, &KexiDBConnection::closeDatabase);
+
+ this->addFunction0< Kross::Api::Variant >("tableNames", this, &KexiDBConnection::tableNames);
+ this->addFunction0< Kross::Api::Variant >("queryNames", this, &KexiDBConnection::queryNames);
+
+ this->addFunction1< KexiDBCursor, Kross::Api::Variant >("executeQueryString", this, &KexiDBConnection::executeQueryString);
+ this->addFunction1< KexiDBCursor, KexiDBQuerySchema >("executeQuerySchema", this, &KexiDBConnection::executeQuerySchema);
+
+ addFunction("insertRecord", &KexiDBConnection::insertRecord);
+
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("createDatabase", this, &KexiDBConnection::createDatabase);
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("dropDatabase", this, &KexiDBConnection::dropDatabase);
+
+ this->addFunction1< Kross::Api::Variant, KexiDBTableSchema >("createTable", this, &KexiDBConnection::createTable);
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("dropTable", this, &KexiDBConnection::dropTable);
+ this->addFunction2< Kross::Api::Variant, KexiDBTableSchema, KexiDBTableSchema >("alterTable", this, &KexiDBConnection::alterTable);
+ this->addFunction2< Kross::Api::Variant, KexiDBTableSchema, Kross::Api::Variant >("alterTableName", this, &KexiDBConnection::alterTableName);
+
+ this->addFunction1< KexiDBTableSchema, Kross::Api::Variant >("tableSchema", this, &KexiDBConnection::tableSchema);
+ this->addFunction1< Kross::Api::Variant, KexiDBTableSchema >("isEmptyTable", this, &KexiDBConnection::isEmptyTable);
+ this->addFunction1< KexiDBQuerySchema, Kross::Api::Variant >("querySchema", this, &KexiDBConnection::querySchema);
+
+ this->addFunction0< Kross::Api::Variant >("autoCommit", this, &KexiDBConnection::autoCommit);
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("setAutoCommit", this, &KexiDBConnection::setAutoCommit);
+
+ this->addFunction0< KexiDBTransaction >("beginTransaction", this, &KexiDBConnection::beginTransaction);
+ this->addFunction1< Kross::Api::Variant, KexiDBTransaction >("commitTransaction", this, &KexiDBConnection::commitTransaction);
+ this->addFunction1< Kross::Api::Variant, KexiDBTransaction >("rollbackTransaction", this, &KexiDBConnection::rollbackTransaction);
+ this->addFunction0< KexiDBTransaction >("defaultTransaction", this, &KexiDBConnection::defaultTransaction);
+ this->addFunction1< void, KexiDBTransaction >("setDefaultTransaction", this, &KexiDBConnection::setDefaultTransaction);
+ this->addFunction0<Kross::Api::List>("transactions", this, &KexiDBConnection::transactions);
+
+ this->addFunction0< KexiDBParser >("parser", this, &KexiDBConnection::parser);
+}
+
+KexiDBConnection::~KexiDBConnection() {
+}
+
+const QString KexiDBConnection::getClassName() const {
+ return "Kross::KexiDB::KexiDBConnection";
+}
+
+::KexiDB::Connection* KexiDBConnection::connection() const {
+ if(! m_connection)
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("KexiDB::Connection is NULL.")) );
+ //if(m_connection->error())
+ // throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("KexiDB::Connection error: %1").arg(m_connection->errorMsg())) );
+ return m_connection;
+}
+
+bool KexiDBConnection::hadError() const { return connection()->error(); }
+const QString KexiDBConnection::lastError() const { return connection()->errorMsg(); }
+
+KexiDBConnectionData* KexiDBConnection::data() { return m_connectiondata.data(); }
+KexiDBDriver* KexiDBConnection::driver() { return m_driver.data(); }
+
+bool KexiDBConnection::connect() { return connection()->connect(); }
+bool KexiDBConnection::isConnected() { return connection()->isConnected(); }
+bool KexiDBConnection::disconnect() { return connection()->disconnect(); }
+
+bool KexiDBConnection::isReadOnly() const { return connection()->isReadOnly(); }
+
+bool KexiDBConnection::databaseExists(const QString& dbname) { return connection()->databaseExists(dbname); }
+const QString KexiDBConnection::currentDatabase() const { return connection()->currentDatabase(); }
+const QStringList KexiDBConnection::databaseNames() const { return connection()->databaseNames(); }
+bool KexiDBConnection::isDatabaseUsed() const { return connection()->isDatabaseUsed(); }
+bool KexiDBConnection::useDatabase(const QString& dbname) { return connection()->databaseExists(dbname) && m_connection->useDatabase(dbname); }
+bool KexiDBConnection::closeDatabase() { return connection()->closeDatabase(); }
+
+const QStringList KexiDBConnection::allTableNames() const { return connection()->tableNames(true); }
+const QStringList KexiDBConnection::tableNames() const { return connection()->tableNames(false); }
+
+const QStringList KexiDBConnection::queryNames() const {
+ bool ok = true;
+ QStringList queries = connection()->objectNames(::KexiDB::QueryObjectType, &ok);
+ if(! ok) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Failed to determinate querynames.")) );
+ return queries;
+}
+
+KexiDBCursor* KexiDBConnection::executeQueryString(const QString& sqlquery) {
+ // The ::KexiDB::Connection::executeQuery() method does not check if we pass a valid SELECT-statement
+ // or e.g. a DROP TABLE operation. So, let's check for such dangerous operations right now.
+ ::KexiDB::Parser parser( connection() );
+ if(! parser.parse(sqlquery))
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Failed to parse query: %1 %2").arg(parser.error().type()).arg(parser.error().error())) );
+ if( parser.query() == 0 || parser.operation() != ::KexiDB::Parser::OP_Select )
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Invalid query operation \"%1\"").arg(parser.operationString()) ) );
+ ::KexiDB::Cursor* cursor = connection()->executeQuery(sqlquery);
+ return cursor ? new KexiDBCursor(cursor) : 0;
+}
+
+KexiDBCursor* KexiDBConnection::executeQuerySchema(KexiDBQuerySchema* queryschema) {
+ ::KexiDB::Cursor* cursor = connection()->executeQuery( *queryschema->queryschema() );
+ return cursor ? new KexiDBCursor(cursor) : 0;
+}
+
+/*TODO
+bool KexiDBConnection::insertRecordIntoFieldlist(KexiDBFieldList* fieldlist, QValueList<QVariant> values) {
+ return connection()->insertRecord(*fieldlist->fieldlist(), values);
+}
+
+bool KexiDBConnection::insertRecordIntoTable(KexiDBTableSchema* tableschema, QValueList<QVariant> values) {
+ return connection()->insertRecord(*tableschema->tableschema(), values);
+}
+*/
+Kross::Api::Object::Ptr KexiDBConnection::insertRecord(Kross::Api::List::Ptr args) {
+ QValueList<QVariant> values = Kross::Api::Variant::toList(args->item(1));
+ Kross::Api::Object::Ptr obj = args->item(0);
+ if(obj->getClassName() == "Kross::KexiDB::KexiDBFieldList")
+ return new Kross::Api::Variant(
+ QVariant(connection()->insertRecord(
+ *Kross::Api::Object::fromObject<KexiDBFieldList>(obj)->fieldlist(),
+ values
+ ), 0));
+ return new Kross::Api::Variant(
+ QVariant(connection()->insertRecord(
+ *Kross::Api::Object::fromObject<KexiDBTableSchema>(obj)->tableschema(),
+ values
+ ), 0));
+}
+
+bool KexiDBConnection::createDatabase(const QString& dbname) { return connection()->createDatabase(dbname); }
+bool KexiDBConnection::dropDatabase(const QString& dbname) { return connection()->dropDatabase(dbname); }
+
+bool KexiDBConnection::createTable(KexiDBTableSchema* tableschema) { return connection()->createTable(tableschema->tableschema(), false); }
+bool KexiDBConnection::dropTable(const QString& tablename) { return true == connection()->dropTable(tablename); }
+bool KexiDBConnection::alterTable(KexiDBTableSchema* fromschema, KexiDBTableSchema* toschema) { return true == connection()->alterTable(*fromschema->tableschema(), *toschema->tableschema()); }
+bool KexiDBConnection::alterTableName(KexiDBTableSchema* tableschema, const QString& newtablename) { return connection()->alterTableName(*tableschema->tableschema(), newtablename); }
+
+KexiDBTableSchema* KexiDBConnection::tableSchema(const QString& tablename) const {
+ ::KexiDB::TableSchema* tableschema = connection()->tableSchema(tablename);
+ return tableschema ? new KexiDBTableSchema(tableschema) : 0;
+}
+
+bool KexiDBConnection::isEmptyTable(KexiDBTableSchema* tableschema) const {
+ bool success;
+ bool notempty = connection()->isEmpty(*tableschema->tableschema(), success);
+ return (! (success && notempty));
+}
+
+KexiDBQuerySchema* KexiDBConnection::querySchema(const QString& queryname) const {
+ ::KexiDB::QuerySchema* queryschema = connection()->querySchema(queryname);
+ return queryschema ? new KexiDBQuerySchema(queryschema) : 0;
+}
+
+bool KexiDBConnection::autoCommit() const { return connection()->autoCommit(); }
+bool KexiDBConnection::setAutoCommit(bool enabled) { return connection()->setAutoCommit(enabled); }
+
+KexiDBTransaction* KexiDBConnection::beginTransaction() {
+ ::KexiDB::Transaction t = connection()->beginTransaction();
+ return new KexiDBTransaction(t);
+}
+
+bool KexiDBConnection::commitTransaction(KexiDBTransaction* transaction) { return connection()->commitTransaction( transaction->transaction() ); }
+bool KexiDBConnection::rollbackTransaction(KexiDBTransaction* transaction) { return connection()->rollbackTransaction( transaction->transaction() ); }
+KexiDBTransaction* KexiDBConnection::defaultTransaction() { return new KexiDBTransaction( connection()->defaultTransaction() ); }
+void KexiDBConnection::setDefaultTransaction(KexiDBTransaction* transaction) { connection()->setDefaultTransaction( transaction->transaction() ); }
+
+Kross::Api::List* KexiDBConnection::transactions() {
+ return new Kross::Api::ListT<KexiDBTransaction>( connection()->transactions() );
+}
+
+KexiDBParser* KexiDBConnection::parser() { return new KexiDBParser(this, new ::KexiDB::Parser(connection())); }
diff --git a/kexi/plugins/scripting/kexidb/kexidbconnection.h b/kexi/plugins/scripting/kexidb/kexidbconnection.h
new file mode 100644
index 000000000..7e1a7d3a6
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbconnection.h
@@ -0,0 +1,194 @@
+/***************************************************************************
+ * kexidbconnection.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBCONNECTION_H
+#define KROSS_KEXIDB_KEXIDBCONNECTION_H
+
+#include <qstring.h>
+#include <ksharedptr.h>
+
+#include <api/object.h>
+#include <api/variant.h>
+#include <api/list.h>
+#include <api/class.h>
+
+//#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+
+namespace Kross { namespace KexiDB {
+
+ // Forward declarations.
+ class KexiDBDriver;
+ class KexiDBConnectionData;
+ class KexiDBCursor;
+ class KexiDBTableSchema;
+ class KexiDBQuerySchema;
+ class KexiDBTransaction;
+ class KexiDBParser;
+
+ /**
+ * A connection to a database.
+ *
+ * Example (in Python) ;
+ * @code
+ * # Import the kexidb module.
+ * import krosskexidb
+ * # Get the drivermanager.
+ * drivermanager = krosskexidb.DriverManager()
+ * # We need a connectiondata object.
+ * connectiondata = drivermanager.createConnectionData()
+ * # Fill the new connectiondata object with what we need to connect.
+ * connectiondata.setFileName("/home/user/kexisqlite3file.kexi")
+ * # Create the database-driver to access the SQLite3 backend.
+ * driver = drivermanager.driver("SQLite3")
+ * # Create the connection now.
+ * connection = driver.createConnection(connectiondata)
+ * # Establish the connection.
+ * if not connection.connect(): raise("Failed to connect with db")
+ * # Open database for usage. The filebased driver uses the filename as databasename.
+ * if not connection.useDatabase("/home/user/kexisqlite3file.kexi"): raise("Failed to use db")
+ * @endcode
+ */
+ class KexiDBConnection : public Kross::Api::Class<KexiDBConnection>
+ {
+ public:
+ KexiDBConnection(::KexiDB::Connection* connection, KexiDBDriver* driver = 0, KexiDBConnectionData* connectiondata = 0);
+ virtual ~KexiDBConnection();
+ virtual const QString getClassName() const;
+
+ private:
+
+ /** Return true if there was an error during last operation on the database. */
+ bool hadError() const;
+ /** Return the last errormessage. */
+ const QString lastError() const;
+
+ /** Return the KexiDBConnectionData object used to create this connection. */
+ KexiDBConnectionData* data();
+ /** Return the KexiDBDriver object this connection belongs too. */
+ KexiDBDriver* driver();
+
+ /** Try to connect and return true if we are successfully connected now. */
+ bool connect();
+ /** Return true if we are connected. */
+ bool isConnected();
+ /** Disconnect and return true if we are successfully disconnected now. */
+ bool disconnect();
+
+ /** Returns true if the connection is read-only. */
+ bool isReadOnly() const;
+
+ /** Return true if the as argument passed databasename exists. */
+ bool databaseExists(const QString& dbname);
+ /** Return the name of currently used database for this connection or empty
+ string if there is no used database. */
+ const QString currentDatabase() const;
+ /** Return list of database names for opened connection. */
+ const QStringList databaseNames() const;
+ /** Return true if connection is properly established. */
+ bool isDatabaseUsed() const;
+ /** Opens an existing database specified by the as argument passed databasename
+ and returns true if the database is used now. */
+ bool useDatabase(const QString& dbname);
+ /** Closes currently used database for this connection. */
+ bool closeDatabase();
+
+ /** Return names of all table schemas stored in currently used database include the
+ internal KexiDB system table names (kexi__*) */
+ const QStringList allTableNames() const;
+ /** Return names of all table schemas without the internal KexiDB system table names (kexi__*) */
+ const QStringList tableNames() const;
+ /** Return names of all query schemas stored in currently used database. */
+ const QStringList queryNames() const;
+
+ /** Executes query described by the as argument passed sqlstatement-string. Returns the
+ opened cursor created for results of this query. */
+ KexiDBCursor* executeQueryString(const QString& sqlquery);
+ /** Executes query described by the as argument passed KexiDBQuerySchema object. Returns
+ the opened cursor created for results of this query. */
+ KexiDBCursor* executeQuerySchema(KexiDBQuerySchema* queryschema);
+
+//TODO replace following method with a proxymethod.
+ /** Inserts the as argument passed KexiDBField object. */
+ Kross::Api::Object::Ptr insertRecord(Kross::Api::List::Ptr);
+
+ /** Creates new database with the as argument passed databasename. */
+ bool createDatabase(const QString& dbname);
+ /** Drops the as argument passed databasename. */
+ bool dropDatabase(const QString& dbname);
+
+ /** Creates table defined by the as argument passed KexiTableSchema object. */
+ bool createTable(KexiDBTableSchema* tableschema);
+ /** Drops table defined by the as argument passed KexiDBTableSchema object. */
+ bool dropTable(const QString& tablename);
+ /** Alters the as first argument passed KexiDBTableSchema object using the as
+ second argument passed KexiDBTableSchema. */
+ bool alterTable(KexiDBTableSchema* fromschema, KexiDBTableSchema* toschema);
+ /** Alters the tablename of the as first argument passed KexiDBTableSchema into
+ the as second argument passed new tablename. */
+ bool alterTableName(KexiDBTableSchema* tableschema, const QString& newtablename);
+
+ /** Returns the KexiDBTableSchema object of the table matching to the as argument
+ passed tablename. */
+ KexiDBTableSchema* tableSchema(const QString& tablename) const;
+ /** Returns true if there is at least one valid record in the as argument passed tablename. */
+ bool isEmptyTable(KexiDBTableSchema* tableschema) const;
+ /** Returns the KexiDBQuerySchema object of the query matching to the as argument passed queryname. */
+ KexiDBQuerySchema* querySchema(const QString& queryname) const;
+
+ /** Return true if the \"auto commit\" option is on. */
+ bool autoCommit() const;
+ /** Set the auto commit option. This does not affect currently started transactions and can
+ be changed even when connection is not established. */
+ bool setAutoCommit(bool enabled);
+
+ /** Creates new transaction handle and starts a new transaction. */
+ KexiDBTransaction* beginTransaction();
+ /** Commits the as rgument passed KexiDBTransaction object. */
+ bool commitTransaction(KexiDBTransaction* transaction);
+ /** Rollback the as rgument passed KexiDBTransaction object. */
+ bool rollbackTransaction(KexiDBTransaction* transaction);
+ /** Return the KEXIDBTransaction object for default transaction for this connection. */
+ KexiDBTransaction* defaultTransaction();
+ /** Sets default transaction that will be used as context for operations on data in opened
+ database for this connection. */
+ void setDefaultTransaction(KexiDBTransaction* transaction);
+
+ /** Return list of currently active KexiDBTransaction objects. */
+ Kross::Api::List* transactions();
+
+ /** Return a KexiDBParser object. */
+ KexiDBParser* parser();
+
+ private:
+ ::KexiDB::Connection* connection() const;
+ ::KexiDB::Connection* m_connection;
+
+ KSharedPtr<KexiDBConnectionData> m_connectiondata;
+ KSharedPtr<KexiDBDriver> m_driver;
+
+ /// Initialize the class instance.
+ void initialize();
+
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbconnectiondata.cpp b/kexi/plugins/scripting/kexidb/kexidbconnectiondata.cpp
new file mode 100644
index 000000000..61b81d3e2
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbconnectiondata.cpp
@@ -0,0 +1,112 @@
+/***************************************************************************
+ * kexidbconnectiondata.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexidbconnectiondata.h"
+
+#include <qvariant.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBConnectionData::KexiDBConnectionData(::KexiDB::ConnectionData* data)
+ : Kross::Api::Class<KexiDBConnectionData>("KexiDBConnectionData")
+ , m_data(data)
+{
+ this->addFunction0< Kross::Api::Variant >("caption", this, &KexiDBConnectionData::caption);
+ this->addFunction1< void, Kross::Api::Variant >("setCaption", this, &KexiDBConnectionData::setCaption);
+
+ this->addFunction0< Kross::Api::Variant >("description", this, &KexiDBConnectionData::description);
+ this->addFunction1< void, Kross::Api::Variant >("setDescription", this, &KexiDBConnectionData::setDescription);
+
+ this->addFunction0< Kross::Api::Variant >("driverName", this, &KexiDBConnectionData::driverName);
+ this->addFunction1< void, Kross::Api::Variant >("setDriverName", this, &KexiDBConnectionData::setDriverName);
+
+ this->addFunction0< Kross::Api::Variant >("localSocketFileUsed", this, &KexiDBConnectionData::localSocketFileUsed);
+ this->addFunction1< void, Kross::Api::Variant >("setLocalSocketFileUsed", this, &KexiDBConnectionData::setLocalSocketFileUsed);
+
+ this->addFunction0< Kross::Api::Variant >("localSocketFileName", this, &KexiDBConnectionData::localSocketFileName);
+ this->addFunction1< void, Kross::Api::Variant >("setLocalSocketFileName", this, &KexiDBConnectionData::setLocalSocketFileName);
+
+ this->addFunction0< Kross::Api::Variant >("databaseName", this, &KexiDBConnectionData::databaseName);
+ this->addFunction1< void, Kross::Api::Variant >("setDatabaseName", this, &KexiDBConnectionData::setDatabaseName);
+
+ this->addFunction0< Kross::Api::Variant >("hostName", this, &KexiDBConnectionData::hostName);
+ this->addFunction1< void, Kross::Api::Variant >("setHostName", this, &KexiDBConnectionData::setHostName);
+
+ this->addFunction0< Kross::Api::Variant >("port", this, &KexiDBConnectionData::port);
+ this->addFunction1< void, Kross::Api::Variant >("setPort", this, &KexiDBConnectionData::setPort);
+
+ this->addFunction0< Kross::Api::Variant >("password", this, &KexiDBConnectionData::password);
+ this->addFunction1< void, Kross::Api::Variant >("setPassword", this, &KexiDBConnectionData::setPassword);
+
+ this->addFunction0< Kross::Api::Variant >("userName", this, &KexiDBConnectionData::userName);
+ this->addFunction1< void, Kross::Api::Variant >("setUserName", this, &KexiDBConnectionData::setUserName);
+
+ this->addFunction0< Kross::Api::Variant >("fileName", this, &KexiDBConnectionData::fileName);
+ this->addFunction1< void, Kross::Api::Variant >("setFileName", this, &KexiDBConnectionData::setFileName);
+
+ this->addFunction0< Kross::Api::Variant >("dbPath", this, &KexiDBConnectionData::dbPath);
+ this->addFunction0< Kross::Api::Variant >("dbFileName", this, &KexiDBConnectionData::dbFileName);
+ this->addFunction0< Kross::Api::Variant >("serverInfoString", this, &KexiDBConnectionData::serverInfoString);
+}
+
+KexiDBConnectionData::~KexiDBConnectionData()
+{
+ //delete m_data;
+}
+
+const QString KexiDBConnectionData::getClassName() const
+{
+ return "Kross::KexiDB::KexiDBConnectionData";
+}
+
+const QString KexiDBConnectionData::caption() const { return m_data->caption; }
+void KexiDBConnectionData::setCaption(const QString& name) { m_data->caption = name; }
+
+const QString KexiDBConnectionData::description() const { return m_data->description; }
+void KexiDBConnectionData::setDescription(const QString& desc) { m_data->description = desc; }
+
+const QString KexiDBConnectionData::driverName() const { return m_data->driverName; }
+void KexiDBConnectionData::setDriverName(const QString& driver) { m_data->driverName = driver; }
+
+bool KexiDBConnectionData::localSocketFileUsed() const { return m_data->useLocalSocketFile; }
+void KexiDBConnectionData::setLocalSocketFileUsed(bool used) { m_data->useLocalSocketFile = used; }
+const QString KexiDBConnectionData::localSocketFileName() const { return m_data->localSocketFileName; }
+void KexiDBConnectionData::setLocalSocketFileName(const QString& socketfilename) { m_data->localSocketFileName = socketfilename; }
+
+const QString KexiDBConnectionData::databaseName() const { return m_dbname; }
+void KexiDBConnectionData::setDatabaseName(const QString& dbname) { m_dbname = dbname; }
+
+const QString KexiDBConnectionData::hostName() const { return m_data->hostName; }
+void KexiDBConnectionData::setHostName(const QString& hostname) { m_data->hostName = hostname; }
+
+int KexiDBConnectionData::port() const { return m_data->port; }
+void KexiDBConnectionData::setPort(int p) { m_data->port = p; }
+
+const QString KexiDBConnectionData::password() const { return m_data->password; }
+void KexiDBConnectionData::setPassword(const QString& passwd) { m_data->password = passwd; }
+
+const QString KexiDBConnectionData::userName() const { return m_data->userName; }
+void KexiDBConnectionData::setUserName(const QString& username) { m_data->userName = username; }
+
+const QString KexiDBConnectionData::fileName() const { return m_data->fileName(); }
+void KexiDBConnectionData::setFileName(const QString& filename) { m_data->setFileName(filename); }
+
+const QString KexiDBConnectionData::dbPath() const { return m_data->dbPath(); }
+const QString KexiDBConnectionData::dbFileName() const { return m_data->dbFileName(); }
+const QString KexiDBConnectionData::serverInfoString() const { return m_data->serverInfoString(true); }
diff --git a/kexi/plugins/scripting/kexidb/kexidbconnectiondata.h b/kexi/plugins/scripting/kexidb/kexidbconnectiondata.h
new file mode 100644
index 000000000..aaddffbd8
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbconnectiondata.h
@@ -0,0 +1,126 @@
+/***************************************************************************
+ * kexidbconnectiondata.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBCONNECTIONDATA_H
+#define KROSS_KEXIDB_KEXIDBCONNECTIONDATA_H
+
+#include <qstring.h>
+
+#include <api/object.h>
+#include <api/variant.h>
+#include <api/list.h>
+#include <api/class.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/connectiondata.h>
+
+namespace Kross { namespace KexiDB {
+
+ /**
+ * A KexiDBConnectionData is used to store the details needed for
+ * a connection with a database.
+ */
+ class KexiDBConnectionData : public Kross::Api::Class<KexiDBConnectionData>
+ {
+ friend class KexiDBDriverManager;
+ public:
+ KexiDBConnectionData(::KexiDB::ConnectionData* data);
+ virtual ~KexiDBConnectionData();
+ operator ::KexiDB::ConnectionData& () { return *m_data; }
+ operator ::KexiDB::ConnectionData* () { return m_data; }
+ virtual const QString getClassName() const;
+ ::KexiDB::ConnectionData* data() { return m_data; }
+
+ private:
+
+ /** Return the connection name. */
+ const QString caption() const;
+ /** Set the connection name. */
+ void setCaption(const QString& name);
+
+ /** Return the description. */
+ const QString description() const;
+ /** Set the description. */
+ void setDescription(const QString& desc);
+
+ /** Return drivername. */
+ const QString driverName() const;
+ /** Set the drivername. */
+ void setDriverName(const QString& driver);
+
+ /** Return true if a local socket file is used else false. */
+ bool localSocketFileUsed() const;
+ /** Set if the local socket file should be used. */
+ void setLocalSocketFileUsed(bool used);
+ /** Return the local socket filename. */
+ const QString localSocketFileName() const;
+ /** Set the local socket filename. */
+ void setLocalSocketFileName(const QString& socketfilename);
+
+ // For serverbased drivers
+
+ /** Return the database name. */
+ const QString databaseName() const;
+ /** Set the database name. */
+ void setDatabaseName(const QString& dbname);
+
+ /** Return the hostname. */
+ const QString hostName() const;
+ /** Set the hostname. */
+ void setHostName(const QString& hostname);
+
+ /** Return the port number. */
+ int port() const;
+ /** Set the port number. */
+ void setPort(int p);
+
+ /** Return the password. */
+ const QString password() const;
+ /** Set the password. */
+ void setPassword(const QString& passwd);
+
+ /** Return the username. */
+ const QString userName() const;
+ /** Set the username. */
+ void setUserName(const QString& username);
+
+ // For filebased drivers
+
+ /** Return the filename. */
+ const QString fileName() const;
+ /** Set the filename. */
+ void setFileName(const QString& filename);
+
+ /** Return the database path. */
+ const QString dbPath() const;
+ /** Return the database filename. */
+ const QString dbFileName() const;
+
+ /** Return a user-friendly string representation. */
+ const QString serverInfoString() const;
+
+ private:
+ ::KexiDB::ConnectionData* m_data;
+ QString m_dbname;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbcursor.cpp b/kexi/plugins/scripting/kexidb/kexidbcursor.cpp
new file mode 100644
index 000000000..3bc1763d3
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbcursor.cpp
@@ -0,0 +1,139 @@
+/***************************************************************************
+ * kexidbcursor.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexidbcursor.h"
+#include "kexidbconnection.h"
+
+#include <kexidb/tableschema.h>
+#include <kexidb/queryschema.h>
+
+#include <kdebug.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBCursor::KexiDBCursor(::KexiDB::Cursor* cursor)
+ : Kross::Api::Class<KexiDBCursor>("KexiDBCursor")
+ , m_cursor(cursor)
+{
+ this->addFunction0<Kross::Api::Variant>("open", this, &KexiDBCursor::open );
+ this->addFunction0<Kross::Api::Variant>("isOpened", this, &KexiDBCursor::isOpened );
+ this->addFunction0<Kross::Api::Variant>("reopen", this, &KexiDBCursor::reopen );
+ this->addFunction0<Kross::Api::Variant>("close", this, &KexiDBCursor::close );
+ this->addFunction0<Kross::Api::Variant>("moveFirst", this, &KexiDBCursor::moveFirst );
+ this->addFunction0<Kross::Api::Variant>("moveLast", this, &KexiDBCursor::moveLast );
+ this->addFunction0<Kross::Api::Variant>("movePrev", this, &KexiDBCursor::movePrev );
+ this->addFunction0<Kross::Api::Variant>("moveNext", this, &KexiDBCursor::moveNext );
+ this->addFunction0<Kross::Api::Variant>("bof", this, &KexiDBCursor::bof );
+ this->addFunction0<Kross::Api::Variant>("eof", this, &KexiDBCursor::eof );
+ this->addFunction0<Kross::Api::Variant>("at", this, &KexiDBCursor::at );
+ this->addFunction0<Kross::Api::Variant>("fieldCount", this, &KexiDBCursor::fieldCount );
+ this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("value", this, &KexiDBCursor::value );
+ this->addFunction2<Kross::Api::Variant, Kross::Api::Variant, Kross::Api::Variant>("setValue", this, &KexiDBCursor::setValue );
+ this->addFunction0<Kross::Api::Variant>("save", this, &KexiDBCursor::save );
+}
+
+KexiDBCursor::~KexiDBCursor()
+{
+ ///@todo check ownership
+ //delete m_cursor;
+
+ clearBuffers();
+}
+
+void KexiDBCursor::clearBuffers()
+{
+ QMap<Q_LLONG, Record*>::ConstIterator
+ it( m_modifiedrecords.constBegin() ), end( m_modifiedrecords.constEnd() );
+ for( ; it != end; ++it)
+ delete it.data();
+ m_modifiedrecords.clear();
+}
+
+const QString KexiDBCursor::getClassName() const
+{
+ return "Kross::KexiDB::KexiDBCursor";
+}
+
+bool KexiDBCursor::open() { return m_cursor->open(); }
+bool KexiDBCursor::isOpened() { return m_cursor->isOpened(); }
+bool KexiDBCursor::reopen() { return m_cursor->reopen(); }
+bool KexiDBCursor::close() { return m_cursor->close(); }
+
+bool KexiDBCursor::moveFirst() { return m_cursor->moveFirst(); }
+bool KexiDBCursor::moveLast() { return m_cursor->moveLast(); }
+bool KexiDBCursor::movePrev() { return m_cursor->movePrev(); }
+bool KexiDBCursor::moveNext() { return m_cursor->moveNext(); }
+
+bool KexiDBCursor::bof() { return m_cursor->bof(); }
+bool KexiDBCursor::eof() { return m_cursor->eof(); }
+
+Q_LLONG KexiDBCursor::at() { return m_cursor->at(); }
+uint KexiDBCursor::fieldCount() { return m_cursor->fieldCount(); }
+
+QVariant KexiDBCursor::value(uint index)
+{
+ return m_cursor->value(index);
+}
+
+bool KexiDBCursor::setValue(uint index, QVariant value)
+{
+ ::KexiDB::QuerySchema* query = m_cursor->query();
+ if(! query) {
+ kdDebug() << "Invalid query in KexiDBCursor::setValue index=" << index << " value=" << value << endl;
+ return false;
+ }
+
+ ::KexiDB::QueryColumnInfo* column = query->fieldsExpanded().at(index);
+ if(! column) {
+ kdDebug() << "Invalid column in KexiDBCursor::setValue index=" << index << " value=" << value << endl;
+ return false;
+ }
+
+ const Q_LLONG position = m_cursor->at();
+ if(! m_modifiedrecords.contains(position))
+ m_modifiedrecords.replace(position, new Record(m_cursor));
+ m_modifiedrecords[position]->buffer->insert(*column, value);
+ return true;
+}
+
+bool KexiDBCursor::save()
+{
+ if(m_modifiedrecords.count() < 1)
+ return true;
+
+ //It is needed to close the cursor before we are able to update the rows
+ //since else the database could be locked (e.g. at the case of SQLite a
+ //KexiDB: Object ERROR: 6: SQLITE_LOCKED would prevent updating).
+ //Maybe it works fine with other drivers like MySQL or Postqre?
+ m_cursor->close();
+
+ bool ok = true;
+ QMap<Q_LLONG, Record*>::ConstIterator
+ it( m_modifiedrecords.constBegin() ), end( m_modifiedrecords.constEnd() );
+ for( ; it != end; ++it) {
+ bool b = m_cursor->updateRow(it.data()->rowdata, * it.data()->buffer, m_cursor->isBuffered());
+ if(ok) {
+ ok = b;
+ //break;
+ }
+ }
+ //m_cursor->close();
+ clearBuffers();
+ return ok;
+}
diff --git a/kexi/plugins/scripting/kexidb/kexidbcursor.h b/kexi/plugins/scripting/kexidb/kexidbcursor.h
new file mode 100644
index 000000000..6e92a38ec
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbcursor.h
@@ -0,0 +1,159 @@
+/***************************************************************************
+ * kexidbcursor.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBCURSOR_H
+#define KROSS_KEXIDB_KEXIDBCURSOR_H
+
+#include <qstring.h>
+
+#include <api/object.h>
+#include <api/variant.h>
+#include <api/list.h>
+#include <api/class.h>
+
+#include <kexidb/cursor.h>
+#include <kexidb/roweditbuffer.h>
+
+namespace Kross { namespace KexiDB {
+
+ // Forward declaration.
+ class KexiDBConnection;
+
+ /**
+ * The cursor provides a control structure for the successive traversal
+ * of records in a result set as returned e.g. by a query.
+ *
+ * Example (in Python) that shows how to iterate over the result of a query;
+ * @code
+ * # Once we have a KexiDBConnection object we are able to execute a query string and get a cursor as result.
+ * cursor = connection.executeQueryString("SELECT * from emp")
+ * # Let's check if the query was successfully.
+ * if not cursor: raise("Query failed")
+ * # Walk through all items in the table.
+ * while(not cursor.eof()):
+ * # Iterate over the fields the record has.
+ * for i in range( cursor.fieldCount() ):
+ * # Print some information.
+ * print "%s %s %s" % (cursor.at(), i, cursor.value(i))
+ * # and move on to the next record.
+ * cursor.moveNext()
+ * @endcode
+ *
+ * Example (in Python) that shows how to use a cursor to strip
+ * all whitespaces at the beginning and the end from the values
+ * in a table;
+ * @code
+ * import krosskexidb
+ * drivermanager = krosskexidb.DriverManager()
+ * connectiondata = drivermanager.createConnectionDataByFile("/home/me/kexiprojectfile.kexi")
+ * driver = drivermanager.driver( connectiondata.driverName() )
+ * connection = driver.createConnection(connectiondata)
+ * if not connection.connect(): raise "Failed to connect"
+ * if not connection.useDatabase( connectiondata.databaseName() ):
+ * if not connection.useDatabase( connectiondata.fileName() ):
+ * raise "Failed to use database"
+ *
+ * table = connection.tableSchema("emp")
+ * query = table.query()
+ * cursor = connection.executeQuerySchema(query)
+ * if not cursor: raise("Query failed")
+ * while(not cursor.eof()):
+ * for i in range( cursor.fieldCount() ):
+ * v = str( cursor.value(i) )
+ * if v.startswith(' ') or v.endswith(' '):
+ * cursor.setValue(i, v.strip())
+ * cursor.moveNext()
+ * if not cursor.save(): raise "Failed to save changes"
+ * @endcode
+ */
+ class KexiDBCursor : public Kross::Api::Class<KexiDBCursor>
+ {
+ public:
+ KexiDBCursor(::KexiDB::Cursor* cursor);
+ virtual ~KexiDBCursor();
+ virtual const QString getClassName() const;
+
+ private:
+
+ /** Opens the cursor. */
+ bool open();
+ /** Returns true if the cursor is opened else false. */
+ bool isOpened();
+ /** Closes and then opens again the same cursor. */
+ bool reopen();
+ /** Closes previously opened cursor. */
+ bool close();
+
+ /** Moves current position to the first record and retrieves it. */
+ bool moveFirst();
+ /** Moves current position to the last record and retrieves it. */
+ bool moveLast();
+ /** Moves current position to the previous record and retrieves it. */
+ bool movePrev();
+ /** Moves current position to the next record and retrieves it. */
+ bool moveNext();
+
+ /** Returns true if current position is before first record. */
+ bool bof();
+ /** Returns true if current position is after last record. */
+ bool eof();
+
+ /** Returns current internal position of the cursor's query. Records
+ are numbered from 0; the value -1 means that the cursor does not
+ point to a valid record. */
+ Q_LLONG at();
+ /** Returns the number of fields available for this cursor. */
+ uint fieldCount();
+ /** Returns the value stored in the passed column number (counting from 0). */
+ QVariant value(uint index);
+ /** Set the value for the field defined with index. The new value is buffered
+ and does not got written as long as save() is not called. */
+ bool setValue(uint index, QVariant value);
+
+ /** Save any changes done with setValue(). You should call this only once at
+ the end of all value/setValue iterations cause the cursor is closed once
+ the changes got saved successfully. */
+ bool save();
+
+ private:
+ ::KexiDB::Cursor* m_cursor;
+
+ class Record {
+ public:
+ ::KexiDB::RowData rowdata;
+ ::KexiDB::RowEditBuffer* buffer;
+ Record(::KexiDB::Cursor* cursor)
+ : buffer( new ::KexiDB::RowEditBuffer(true) )
+ {
+ cursor->storeCurrentRow(rowdata);
+ }
+ ~Record()
+ {
+ delete buffer;
+ }
+ };
+ QMap<Q_LLONG, Record*> m_modifiedrecords;
+
+ void clearBuffers();
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbdriver.cpp b/kexi/plugins/scripting/kexidb/kexidbdriver.cpp
new file mode 100644
index 000000000..f019b237f
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbdriver.cpp
@@ -0,0 +1,70 @@
+/***************************************************************************
+ * kexidbdriver.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexidbdriver.h"
+#include "kexidbdrivermanager.h"
+
+#include <qvaluelist.h>
+#include <qptrlist.h>
+#include <kdebug.h>
+
+#include <kexidb/connection.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBDriver::KexiDBDriver(::KexiDB::Driver* driver)
+ : Kross::Api::Class<KexiDBDriver>("KexiDBDriver")
+ , m_driver(driver)
+{
+ this->addFunction0<Kross::Api::Variant>("isValid", this, &KexiDBDriver::isValid );
+ this->addFunction0<Kross::Api::Variant>("versionMajor", this, &KexiDBDriver::versionMajor );
+ this->addFunction0<Kross::Api::Variant>("versionMinor", this, &KexiDBDriver::versionMinor );
+ this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("escapeString", this, &KexiDBDriver::escapeString);
+ this->addFunction0<Kross::Api::Variant>("isFileDriver", this, &KexiDBDriver::isFileDriver );
+ this->addFunction0<Kross::Api::Variant>("fileDBDriverMimeType", this, &KexiDBDriver::fileDBDriverMimeType );
+ this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("isSystemObjectName", this, &KexiDBDriver::isSystemObjectName );
+ this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("isSystemDatabaseName", this, &KexiDBDriver::isSystemDatabaseName );
+ this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("isSystemFieldName", this, &KexiDBDriver::isSystemFieldName );
+ this->addFunction2<Kross::Api::Variant, Kross::Api::Variant, Kross::Api::Variant> ("valueToSQL", this, &KexiDBDriver::valueToSQL );
+
+ this->addFunction1<KexiDBConnection, KexiDBConnectionData>("createConnection", this, &KexiDBDriver::createConnection);
+ this->addFunction0< Kross::Api::ListT< KexiDBConnection > >("connectionsList", this, &KexiDBDriver::connectionsList);
+}
+
+KexiDBDriver::~KexiDBDriver()
+{
+}
+
+const QString KexiDBDriver::getClassName() const
+{
+ return "Kross::KexiDB::KexiDBDriver";
+}
+
+bool KexiDBDriver::isValid() { return m_driver->isValid(); }
+int KexiDBDriver::versionMajor() { return m_driver->version().major; }
+int KexiDBDriver::versionMinor() { return m_driver->version().minor; }
+QString KexiDBDriver::escapeString(const QString& s) { return m_driver->escapeString(s); }
+bool KexiDBDriver::isFileDriver() { return m_driver->isFileDriver(); }
+QString KexiDBDriver::fileDBDriverMimeType() { return m_driver->fileDBDriverMimeType(); }
+bool KexiDBDriver::isSystemObjectName(const QString& name) { return m_driver->isSystemObjectName(name); }
+bool KexiDBDriver::isSystemDatabaseName(const QString& name) { return m_driver->isSystemDatabaseName(name); }
+bool KexiDBDriver::isSystemFieldName(const QString& name) { return m_driver->isSystemFieldName(name); }
+QString KexiDBDriver::valueToSQL(const QString& fieldtype, const QVariant& value) { return m_driver->valueToSQL(fieldtype, value); }
+KexiDBConnection* KexiDBDriver::createConnection(KexiDBConnectionData* data) { return new KexiDBConnection( m_driver->createConnection(*data) ); }
+QPtrList< ::KexiDB::Connection > KexiDBDriver::connectionsList() { return m_driver->connectionsList(); }
diff --git a/kexi/plugins/scripting/kexidb/kexidbdriver.h b/kexi/plugins/scripting/kexidb/kexidbdriver.h
new file mode 100644
index 000000000..edf7283c7
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbdriver.h
@@ -0,0 +1,114 @@
+/***************************************************************************
+ * kexidbdriver.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBDRIVER_H
+#define KROSS_KEXIDB_KEXIDBDRIVER_H
+
+#include <qstring.h>
+#include <qvaluelist.h>
+//#include <qguardedptr.h>
+
+#include <api/object.h>
+#include <api/variant.h>
+#include <api/list.h>
+#include <api/class.h>
+
+#include <kexidb/driver.h>
+
+#include "kexidbconnection.h"
+#include "kexidbconnectiondata.h"
+
+namespace Kross { namespace KexiDB {
+
+ /**
+ * Drivers are the implementations Kexi uses to access the
+ * driver-backends.
+ *
+ * Example (in Python) ;
+ * @code
+ * # Import the kexidb module.
+ * import krosskexidb
+ * # Get the drivermanager.
+ * drivermanager = krosskexidb.DriverManager()
+ * # Create the driver now.
+ * driver = drivermanager.driver("SQLite3")
+ * # Check if the driver is valid.
+ * if not driver.isValid(): raise "Invalid driver"
+ * # Create a connectiondata object.
+ * connectiondata = drivermanager.createConnectionData()
+ * # Fill the new connectiondata object with what we need to connect.
+ * connectiondata.setFileName("/home/user/kexisqlite3file.kexi")
+ * # Print the list of connections before.
+ * print driver.connectionsList()
+ * # Create the connection now.
+ * connection = driver.createConnection(connectiondata)
+ * # Print the list of connections again. This includes our just created connection now.
+ * print driver.connectionsList()
+ * @endcode
+ */
+ class KexiDBDriver : public Kross::Api::Class<KexiDBDriver>
+ {
+ public:
+ KexiDBDriver(::KexiDB::Driver* driver);
+ virtual ~KexiDBDriver();
+ virtual const QString getClassName() const;
+
+ private:
+
+ /** Return true if this driver is valid else false. */
+ bool isValid();
+ /** The drivers major versionnumber. */
+ int versionMajor();
+ /** The drivers minor versionnumber. */
+ int versionMinor();
+ /** Driver-specific SQL string escaping. For example the " or ' char may
+ need to be escaped for values used within SQL-statements. */
+ QString escapeString(const QString& s);
+ /** Returns true if this driver is file-based. */
+ bool isFileDriver();
+ /** Return a name of MIME type of files handled by this driver if it is a
+ file-based database's driver otherwise returns null string. */
+ QString fileDBDriverMimeType();
+ /** Returns true if the passed string is a system object's name, eg. name
+ of build-in system table that cannot be used or created by a user. */
+ bool isSystemObjectName(const QString& name);
+ /** Returns true if the passed string is a system database's name, eg. name
+ of build-in, system database that cannot be used or created by a user. */
+ bool isSystemDatabaseName(const QString& name);
+ /** Returns true if the passed string is a system field's name, build-in
+ system field that cannot be used or created by a user. */
+ bool isSystemFieldName(const QString& name);
+ /** The as second argument passed string got escaped to be usable within
+ a SQL-statement and those escaped string got returned by the method.
+ The first argument defines the fieldtype to what we should escape the
+ second argument to. */
+ QString valueToSQL(const QString& fieldtype, const QVariant& value);
+ /** Create a new KexiDBConnection object and return it. */
+ KexiDBConnection* createConnection(KexiDBConnectionData* data);
+ /** Return a list of KexiDBConnection objects. */
+ QPtrList< ::KexiDB::Connection > connectionsList();
+
+ private:
+ ::KexiDB::Driver* m_driver;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbdrivermanager.cpp b/kexi/plugins/scripting/kexidb/kexidbdrivermanager.cpp
new file mode 100644
index 000000000..66a0df268
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbdrivermanager.cpp
@@ -0,0 +1,178 @@
+/***************************************************************************
+ * kexidbdrivermanager.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexidbdrivermanager.h"
+#include "kexidbdriver.h"
+#include "kexidbconnectiondata.h"
+#include "kexidbfield.h"
+#include "kexidbschema.h"
+
+#include <api/exception.h>
+
+#include <qguardedptr.h>
+#include <kdebug.h>
+#include <kmimetype.h>
+
+#include <kexidb/driver.h>
+#include <kexidb/connectiondata.h>
+#include <kexidb/field.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/queryschema.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBDriverManager::KexiDBDriverManager()
+ : Kross::Api::Class<KexiDBDriverManager>("DriverManager")
+{
+ //krossdebug( QString("Kross::KexiDB::KexiDBDriverManager::KexiDBDriverManager()") );
+
+ this->addFunction0< Kross::Api::Variant >("driverNames", this, &KexiDBDriverManager::driverNames);
+
+ this->addFunction1< KexiDBDriver, Kross::Api::Variant >("driver", this, &KexiDBDriverManager::driver);
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("lookupByMime", this, &KexiDBDriverManager::lookupByMime);
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("mimeForFile", this, &KexiDBDriverManager::mimeForFile);
+
+ this->addFunction0< KexiDBConnectionData >("createConnectionData", this, &KexiDBDriverManager::createConnectionData);
+ this->addFunction1< KexiDBConnectionData, Kross::Api::Variant >("createConnectionDataByFile", this, &KexiDBDriverManager::createConnectionDataByFile);
+ this->addFunction0< KexiDBField >("field", this, &KexiDBDriverManager::field);
+ this->addFunction1< KexiDBTableSchema, Kross::Api::Variant >("tableSchema", this, &KexiDBDriverManager::tableSchema);
+ this->addFunction0< KexiDBQuerySchema>("querySchema", this, &KexiDBDriverManager::querySchema);
+}
+
+KexiDBDriverManager::~KexiDBDriverManager() {
+ //krossdebug( QString("Kross::KexiDB::KexiDBDriverManager::~KexiDBDriverManager()") );
+}
+
+const QString KexiDBDriverManager::getClassName() const {
+ return "Kross::KexiDB::KexiDBDriverManager";
+}
+
+KexiDB::DriverManager& KexiDBDriverManager::driverManager()
+{
+ if(m_drivermanager.error())
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("KexiDB::DriverManager error: %1").arg(m_drivermanager.errorMsg())) );
+ return m_drivermanager;
+}
+
+const QStringList KexiDBDriverManager::driverNames() {
+ return driverManager().driverNames();
+}
+
+KexiDBDriver* KexiDBDriverManager::driver(const QString& drivername) {
+ QGuardedPtr< ::KexiDB::Driver > driver = driverManager().driver(drivername); // caching is done by the DriverManager
+ if(! driver) return 0;
+ if(driver->error()) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("KexiDB::Driver error for drivername '%1': %2").arg(drivername).arg(driver->errorMsg())) );
+ return new KexiDBDriver(driver);
+}
+
+const QString KexiDBDriverManager::lookupByMime(const QString& mimetype) {
+ return driverManager().lookupByMime(mimetype);
+}
+
+const QString KexiDBDriverManager::mimeForFile(const QString& filename) {
+ QString mimename = KMimeType::findByFileContent( filename )->name();
+ if(mimename.isEmpty() || mimename=="application/octet-stream" || mimename=="text/plain")
+ mimename = KMimeType::findByURL(filename)->name();
+ return mimename;
+}
+
+KexiDBConnectionData* KexiDBDriverManager::createConnectionData() {
+ return new KexiDBConnectionData( new ::KexiDB::ConnectionData() );
+}
+
+KexiDBConnectionData* KexiDBDriverManager::createConnectionDataByFile(const QString& filename) {
+ //! @todo reuse the original code!
+
+ QString mimename = KMimeType::findByFileContent(filename)->name();
+ if(mimename.isEmpty() || mimename=="application/octet-stream" || mimename=="text/plain")
+ mimename = KMimeType::findByURL(filename)->name();
+
+ if(mimename == "application/x-kexiproject-shortcut" || mimename == "application/x-kexi-connectiondata") {
+ KConfig config(filename, true, false);
+ QString groupkey;
+ QStringList groups(config.groupList());
+ QStringList::ConstIterator it, end( groups.constEnd() );
+ for( it = groups.constBegin(); it != end; ++it) {
+ if((*it).lower()!="file information") {
+ groupkey = *it;
+ break;
+ }
+ }
+ if(groupkey.isNull()) {
+ kdDebug() << "No groupkey in KexiDBDriverManager::createConnectionDataByFile filename=" << filename << endl;
+ return 0;
+ }
+
+ config.setGroup(groupkey);
+ //QString type( config.readEntry("type", "database").lower() );
+ //bool isDatabaseShortcut = (type == "database");
+
+ ::KexiDB::ConnectionData* data = new ::KexiDB::ConnectionData();
+ int version = config.readNumEntry("version", 2); //KexiDBShortcutFile_version
+ data->setFileName(QString::null);
+ data->caption = config.readEntry("caption");
+ data->description = config.readEntry("comment");
+ QString dbname = config.readEntry("name");
+ data->driverName = config.readEntry("engine");
+ data->hostName = config.readEntry("server");
+ data->port = config.readNumEntry("port", 0);
+ data->useLocalSocketFile = config.readBoolEntry("useLocalSocketFile", false);
+ data->localSocketFileName = config.readEntry("localSocketFile");
+
+ if(version >= 2 && config.hasKey("encryptedPassword")) {
+ data->password = config.readEntry("encryptedPassword");
+ uint len = data->password.length();
+ for (uint i=0; i<len; i++)
+ data->password[i] = QChar( data->password[i].unicode() - 47 - i );
+ }
+ if(data->password.isEmpty())
+ data->password = config.readEntry("password");
+
+ data->savePassword = ! data->password.isEmpty();
+ data->userName = config.readEntry("user");
+
+ KexiDBConnectionData* c = new KexiDBConnectionData(data);
+ c->setDatabaseName(dbname);
+ return c;
+ }
+
+ QString const drivername = driverManager().lookupByMime(mimename);
+ if(! drivername) {
+ kdDebug() << "No driver in KexiDBDriverManager::createConnectionDataByFile filename=" << filename << " mimename=" << mimename << endl;
+ return 0;
+ }
+
+ ::KexiDB::ConnectionData* data = new ::KexiDB::ConnectionData();
+ data->setFileName(filename);
+ data->driverName = drivername;
+ return new KexiDBConnectionData(data);
+}
+
+KexiDBField* KexiDBDriverManager::field() {
+ return new KexiDBField( new ::KexiDB::Field() );
+}
+
+KexiDBTableSchema* KexiDBDriverManager::tableSchema(const QString& tablename) {
+ return new KexiDBTableSchema( new ::KexiDB::TableSchema(tablename) );
+}
+
+KexiDBQuerySchema* KexiDBDriverManager::querySchema() {
+ return new KexiDBQuerySchema( new ::KexiDB::QuerySchema() );
+}
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbdrivermanager.h b/kexi/plugins/scripting/kexidb/kexidbdrivermanager.h
new file mode 100644
index 000000000..b6e311083
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbdrivermanager.h
@@ -0,0 +1,105 @@
+/***************************************************************************
+ * kexidbdrivermanager.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBDRIVERMANAGER_H
+#define KROSS_KEXIDB_KEXIDBDRIVERMANAGER_H
+
+#include <qstring.h>
+
+#include <api/object.h>
+#include <api/variant.h>
+#include <api/list.h>
+#include <api/class.h>
+
+#include <kexidb/drivermanager.h>
+
+namespace Kross { namespace KexiDB {
+
+ // Forward declarations.
+ class KexiDBDriver;
+ class KexiDBConnectionData;
+ class KexiDBField;
+ class KexiDBTableSchema;
+ class KexiDBQuerySchema;
+
+ /**
+ * The drivermanager is the base class to access KexiDBDriver objects and provides
+ * common functionality to deal with the KexiDB module.
+ *
+ * Example (in Python) ;
+ * @code
+ * # Import the kexidb module.
+ * import krosskexidb
+ * # Get the drivermanager.
+ * drivermanager = krosskexidb.DriverManager()
+ * # Let's determinate the mimetype (e.g. "application/x-sqlite3").
+ * mimetype = drivermanager.mimeForFile("/home/user/mykexidbfile.kexi")
+ * # Now we use that mimetype to get the name of the driver to handle that file (e.g. "SQLite3")
+ * drivername = drivermanager.lookupByMime(mimetype)
+ * # We are able to create the driver now.
+ * driver = drivermanager.driver(drivername)
+ * @endcode
+ */
+ class KexiDBDriverManager : public Kross::Api::Class<KexiDBDriverManager>
+ {
+ public:
+ KexiDBDriverManager();
+ virtual ~KexiDBDriverManager();
+ virtual const QString getClassName() const;
+
+ private:
+
+ /** Returns a list with avaible drivernames. */
+ const QStringList driverNames();
+
+ /** Return the to the defined drivername matching KexiDBDriver object. */
+ KexiDBDriver* driver(const QString& drivername);
+
+ /** Return the to the defined mimetype-string matching drivername. */
+ const QString lookupByMime(const QString& mimetype);
+
+ /** Return the matching mimetype for the defined file. */
+ const QString mimeForFile(const QString& filename);
+
+ /** Return a new KexiDBConnectionData object. */
+ KexiDBConnectionData* createConnectionData();
+
+ /** Create and return a KexiDBConnectionData object. Fill the content of the
+ KexiDBConnectionData object with the defined file as. The file could be e.g.
+ a *.kexi file or a *.kexis file. */
+ KexiDBConnectionData* createConnectionDataByFile(const QString& filename);
+
+ /** Return a new KexiDBField object. */
+ KexiDBField* field();
+
+ /** Return a new KexiDBTableSchema object. */
+ KexiDBTableSchema* tableSchema(const QString& tablename);
+
+ /** Return a new KexiDBQuerySchema object. */
+ KexiDBQuerySchema* querySchema();
+
+ private:
+ inline ::KexiDB::DriverManager& driverManager();
+ ::KexiDB::DriverManager m_drivermanager;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbfield.cpp b/kexi/plugins/scripting/kexidb/kexidbfield.cpp
new file mode 100644
index 000000000..949b5e1a6
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbfield.cpp
@@ -0,0 +1,147 @@
+/***************************************************************************
+ * kexidbfield.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+
+#include "kexidbfield.h"
+
+#include <api/variant.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBField::KexiDBField(::KexiDB::Field* field)
+ : Kross::Api::Class<KexiDBField>("KexiDBField")
+ , m_field(field)
+{
+ this->addFunction0< Kross::Api::Variant >("type", this, &KexiDBField::type);
+ this->addFunction1< void, Kross::Api::Variant >("setType", this, &KexiDBField::setType);
+
+ this->addFunction0< Kross::Api::Variant >("subType", this, &KexiDBField::subType);
+ this->addFunction1< void, Kross::Api::Variant >("setSubType", this, &KexiDBField::setSubType);
+
+ this->addFunction0< Kross::Api::Variant >("variantType", this, &KexiDBField::variantType);
+ this->addFunction0< Kross::Api::Variant >("typeGroup", this, &KexiDBField::typeGroup);
+
+ this->addFunction0< Kross::Api::Variant >("isAutoInc", this, &KexiDBField::isAutoInc);
+ this->addFunction1< void, Kross::Api::Variant >("setAutoInc", this, &KexiDBField::setAutoInc);
+
+ this->addFunction0< Kross::Api::Variant >("isUniqueKey", this, &KexiDBField::isUniqueKey);
+ this->addFunction1< void, Kross::Api::Variant >("setUniqueKey", this, &KexiDBField::setUniqueKey);
+
+ this->addFunction0< Kross::Api::Variant >("isPrimaryKey", this, &KexiDBField::isPrimaryKey);
+ this->addFunction1< void, Kross::Api::Variant >("setPrimaryKey", this, &KexiDBField::setPrimaryKey);
+
+ this->addFunction0< Kross::Api::Variant >("isForeignKey", this, &KexiDBField::isForeignKey);
+ this->addFunction1< void, Kross::Api::Variant >("setForeignKey", this, &KexiDBField::setForeignKey);
+
+ this->addFunction0< Kross::Api::Variant >("isNotNull", this, &KexiDBField::isNotNull);
+ this->addFunction1< void, Kross::Api::Variant >("setNotNull", this, &KexiDBField::setNotNull);
+
+ this->addFunction0< Kross::Api::Variant >("isNotEmpty", this, &KexiDBField::isNotEmpty);
+ this->addFunction1< void, Kross::Api::Variant >("setNotEmpty", this, &KexiDBField::setNotEmpty);
+
+ this->addFunction0< Kross::Api::Variant >("isIndexed", this, &KexiDBField::isIndexed);
+ this->addFunction1< void, Kross::Api::Variant >("setIndexed", this, &KexiDBField::setIndexed);
+
+ this->addFunction0< Kross::Api::Variant >("isUnsigned", this, &KexiDBField::isUnsigned);
+ this->addFunction1< void, Kross::Api::Variant >("setUnsigned", this, &KexiDBField::setUnsigned);
+
+ this->addFunction0< Kross::Api::Variant >("name", this, &KexiDBField::name);
+ this->addFunction1< void, Kross::Api::Variant >("setName", this, &KexiDBField::setName);
+
+ this->addFunction0< Kross::Api::Variant >("caption", this, &KexiDBField::caption);
+ this->addFunction1< void, Kross::Api::Variant >("setCaption", this, &KexiDBField::setCaption);
+
+ this->addFunction0< Kross::Api::Variant >("description", this, &KexiDBField::description);
+ this->addFunction1< void, Kross::Api::Variant >("setDescription", this, &KexiDBField::setDescription);
+
+ this->addFunction0< Kross::Api::Variant >("length", this, &KexiDBField::length);
+ this->addFunction1< void, Kross::Api::Variant >("setLength", this, &KexiDBField::setLength);
+
+ this->addFunction0< Kross::Api::Variant >("precision", this, &KexiDBField::precision);
+ this->addFunction1< void, Kross::Api::Variant >("setPrecision", this, &KexiDBField::setPrecision);
+
+ this->addFunction0< Kross::Api::Variant >("width", this, &KexiDBField::width);
+ this->addFunction1< void, Kross::Api::Variant >("setWidth", this, &KexiDBField::setWidth);
+
+ this->addFunction0< Kross::Api::Variant >("defaultValue", this, &KexiDBField::defaultValue);
+ this->addFunction1< void, Kross::Api::Variant >("setDefaultValue", this, &KexiDBField::setDefaultValue);
+}
+
+KexiDBField::~KexiDBField()
+{
+}
+
+const QString KexiDBField::getClassName() const
+{
+ return "Kross::KexiDB::KexiDBField";
+}
+
+const QString KexiDBField::type() { return m_field->typeString(); }
+void KexiDBField::setType(const QString type) { m_field->setType( ::KexiDB::Field::typeForString(type) ); }
+
+const QString KexiDBField::subType() { return m_field->subType(); }
+void KexiDBField::setSubType(const QString& subtype) { m_field->setSubType(subtype); }
+
+const QString KexiDBField::variantType() { return QVariant::typeToName( m_field->variantType() ); }
+const QString KexiDBField::typeGroup() { return m_field->typeGroupString(); }
+
+bool KexiDBField::isAutoInc() { return m_field->isAutoIncrement(); }
+void KexiDBField::setAutoInc(bool autoinc) { m_field->setAutoIncrement(autoinc); }
+
+bool KexiDBField::isUniqueKey() { return m_field->isUniqueKey(); }
+void KexiDBField::setUniqueKey(bool unique) { m_field->setUniqueKey(unique); }
+
+bool KexiDBField::isPrimaryKey() { return m_field->isPrimaryKey(); }
+void KexiDBField::setPrimaryKey(bool primary) { m_field->setPrimaryKey(primary); }
+
+bool KexiDBField::isForeignKey() { return m_field->isForeignKey(); }
+void KexiDBField::setForeignKey(bool foreign) { m_field->setForeignKey(foreign); }
+
+bool KexiDBField::isNotNull() { return m_field->isNotNull(); }
+void KexiDBField::setNotNull(bool notnull) { m_field->setNotNull(notnull); }
+
+bool KexiDBField::isNotEmpty() { return m_field->isNotEmpty(); }
+void KexiDBField::setNotEmpty(bool notempty) { m_field->setNotEmpty(notempty); }
+
+bool KexiDBField::isIndexed() { return m_field->isIndexed(); }
+void KexiDBField::setIndexed(bool indexed) { m_field->setIndexed(indexed); }
+
+bool KexiDBField::isUnsigned() { return m_field->isUnsigned(); }
+void KexiDBField::setUnsigned(bool isunsigned) { m_field->setUnsigned(isunsigned); }
+
+const QString KexiDBField::name() { return m_field->name(); }
+void KexiDBField::setName(const QString& name) { m_field->setName(name); }
+
+const QString KexiDBField::caption() { return m_field->caption(); }
+void KexiDBField::setCaption(const QString& caption) { m_field->setCaption(caption); }
+
+const QString KexiDBField::description() { return m_field->description(); }
+void KexiDBField::setDescription(const QString& desc) { m_field->setDescription(desc); }
+
+uint KexiDBField::length() { return m_field->length(); }
+void KexiDBField::setLength(uint length) { m_field->setLength(length); }
+
+uint KexiDBField::precision() { return m_field->precision(); }
+void KexiDBField::setPrecision(uint precision) { m_field->setPrecision(precision); }
+
+uint KexiDBField::width() { return m_field->width(); }
+void KexiDBField::setWidth(uint width) { m_field->setWidth(width); }
+
+QVariant KexiDBField::defaultValue() { return m_field->defaultValue(); }
+void KexiDBField::setDefaultValue(const QVariant& defaultvalue) { m_field->setDefaultValue(defaultvalue); }
diff --git a/kexi/plugins/scripting/kexidb/kexidbfield.h b/kexi/plugins/scripting/kexidb/kexidbfield.h
new file mode 100644
index 000000000..a4c2ef238
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbfield.h
@@ -0,0 +1,148 @@
+/***************************************************************************
+ * kexidbfield.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBFIELD_H
+#define KROSS_KEXIDB_KEXIDBFIELD_H
+
+#include <qstring.h>
+
+#include <api/object.h>
+#include <api/list.h>
+#include <api/class.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/field.h>
+
+namespace Kross { namespace KexiDB {
+
+ /**
+ * A field in a record.
+ */
+ class KexiDBField : public Kross::Api::Class<KexiDBField>
+ {
+ public:
+ KexiDBField(::KexiDB::Field* field);
+ virtual ~KexiDBField();
+ virtual const QString getClassName() const;
+ ::KexiDB::Field* field() { return m_field; }
+
+ private:
+
+ /** Returns the type string for this field, e.g. "Integer" for Integer type. */
+ const QString type();
+ /** Sets the type string for this field, e.g. "Integer" for Integer type. */
+ void setType(const QString type);
+
+ /** Returns the optional subtype for this field. Subtype is a string providing
+ additional hint for field's type. E.g. for BLOB type, it can be a MIME type or
+ certain QVariant type name, for example: "QPixmap", "QColor" or "QFont". */
+ const QString subType();
+ /** Sets the optional subtype for this field. */
+ void setSubType(const QString& subtype);
+
+ /** Returns the QVariant::typeName which is equivalent to the type this field has. */
+ const QString variantType();
+ /** Returns type group string for this field, e.g. "IntegerGroup" for IntegerGroup type. */
+ const QString typeGroup();
+
+ /** Returns true if the field is autoincrement (e.g. integer/numeric). */
+ bool isAutoInc();
+ /** Sets auto increment flag. */
+ void setAutoInc(bool autoinc);
+
+ /** Returns true if the field is member of single-field unique key. */
+ bool isUniqueKey();
+ /** Specifies whether the field has single-field unique constraint or not. */
+ void setUniqueKey(bool unique);
+
+ /** Returns true if the field is member of single-field primary key. */
+ bool isPrimaryKey();
+ /** Specifies whether the field is single-field primary key or not. */
+ void setPrimaryKey(bool primary);
+
+ /** Returns true if the field is member of single-field foreign key. */
+ bool isForeignKey();
+ /** Sets whether the field has to be declared with single-field foreign key. */
+ void setForeignKey(bool foreign);
+
+ /** Returns true if the field is not allowed to be null. */
+ bool isNotNull();
+ /** Specifies whether the field has single-field unique constraint or not. */
+ void setNotNull(bool notnull);
+
+ /** Returns true if the field is not allowed to be empty. */
+ bool isNotEmpty();
+ /** Specifies whether the field has single-field unique constraint or not. */
+ void setNotEmpty(bool notempty);
+
+ /** Returns true if the field is indexed using single-field database index. */
+ bool isIndexed();
+ /** Specifies whether the field is indexed or not. */
+ void setIndexed(bool indexed);
+
+ /** Returns true if the field is an unsigned integer. */
+ bool isUnsigned();
+ /** Specifies whether the field is an unsigned integer or not. */
+ void setUnsigned(bool isunsigned);
+
+ /** Returns the name of this field. */
+ const QString name();
+ /** Sets the name of this field. */
+ void setName(const QString& name);
+
+ /** Returns the caption of this field. */
+ const QString caption();
+ /** Sets the caption of this field. */
+ void setCaption(const QString& caption);
+
+ /** Returns the descriptive text for this field. */
+ const QString description();
+ /** Set the description for this field. */
+ void setDescription(const QString& desc);
+
+ /** Returns the length of text if the field type is text. */
+ uint length();
+ /** Sets the length for this field. Only works for Text Type (not including LongText). */
+ void setLength(uint length);
+
+ /** Returns precision for numeric and other fields that have both length and
+ precision (floating point types). */
+ uint precision();
+ /** Sets the precision for numeric and other fields. */
+ void setPrecision(uint precision);
+
+ /** Returns the width of this field (usually in pixels or points).
+ 0 (the default) means there is no hint for the width. */
+ uint width();
+ /** Sets the width of this field. */
+ void setWidth(uint width);
+
+ /** Returns the default value this field has. */
+ QVariant defaultValue();
+ /** Sets the default value this field has. */
+ void setDefaultValue(const QVariant& defaultvalue);
+
+ private:
+ ::KexiDB::Field* m_field;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbfieldlist.cpp b/kexi/plugins/scripting/kexidb/kexidbfieldlist.cpp
new file mode 100644
index 000000000..f36bf0b05
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbfieldlist.cpp
@@ -0,0 +1,100 @@
+/***************************************************************************
+ * kexidbfieldlist.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexidbfieldlist.h"
+#include "kexidbfield.h"
+
+#include <api/variant.h>
+#include <api/exception.h>
+
+#include <kdebug.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBFieldList::KexiDBFieldList(::KexiDB::FieldList* fieldlist)
+ : Kross::Api::Class<KexiDBFieldList>("KexiDBFieldList")
+ , m_fieldlist(fieldlist)
+{
+ this->addFunction0< Kross::Api::Variant >("fieldCount", this, &KexiDBFieldList::fieldCount);
+ this->addFunction1< KexiDBField, Kross::Api::Variant >("field", this, &KexiDBFieldList::field);
+ this->addFunction1< KexiDBField, Kross::Api::Variant >("fieldByName", this, &KexiDBFieldList::fieldByName);
+
+ this->addFunction0< Kross::Api::List >("fields", this, &KexiDBFieldList::fields);
+
+ this->addFunction1< Kross::Api::Variant, KexiDBField >("hasField", this, &KexiDBFieldList::hasField);
+ this->addFunction0< Kross::Api::Variant >("names", this, &KexiDBFieldList::names);
+
+ this->addFunction1< void, KexiDBField >("addField", this, &KexiDBFieldList::addField);
+ this->addFunction2< void, Kross::Api::Variant, KexiDBField >("insertField", this, &KexiDBFieldList::insertField);
+ this->addFunction1< void, KexiDBField >("removeField", this, &KexiDBFieldList::removeField);
+ this->addFunction0< void >("clear", this, &KexiDBFieldList::clear);
+ this->addFunction1< void, KexiDBFieldList >("setFields", this, &KexiDBFieldList::setFields);
+
+ this->addFunction1< KexiDBFieldList, Kross::Api::Variant >("subList", this, &KexiDBFieldList::subList);
+}
+
+KexiDBFieldList::~KexiDBFieldList()
+{
+}
+
+const QString KexiDBFieldList::getClassName() const
+{
+ return "Kross::KexiDB::KexiDBFieldList";
+}
+
+uint KexiDBFieldList::fieldCount() {
+ return m_fieldlist->fieldCount();
+}
+
+KexiDBField* KexiDBFieldList::field(uint index) {
+ ::KexiDB::Field* field = m_fieldlist->field(index);
+ return field ? new KexiDBField(field) : 0;
+}
+
+KexiDBField* KexiDBFieldList::fieldByName(const QString& name) {
+ ::KexiDB::Field* field = m_fieldlist->field(name);
+ return field ? new KexiDBField(field) : 0;
+}
+
+Kross::Api::List* KexiDBFieldList::fields() {
+ return new Kross::Api::ListT<KexiDBField>( *m_fieldlist->fields() );
+}
+
+bool KexiDBFieldList::hasField(KexiDBField* field) { return m_fieldlist->hasField( field->field() ); }
+const QStringList KexiDBFieldList::names() const { return m_fieldlist->names(); }
+void KexiDBFieldList::addField(KexiDBField* field) { m_fieldlist->addField( field->field() ); }
+void KexiDBFieldList::insertField(uint index, KexiDBField* field) { m_fieldlist->insertField(index, field->field()); }
+void KexiDBFieldList::removeField(KexiDBField* field) { m_fieldlist->removeField( field->field() ); }
+void KexiDBFieldList::clear() { m_fieldlist->clear(); }
+
+void KexiDBFieldList::setFields(KexiDBFieldList* fieldlist) {
+ m_fieldlist->clear();
+ ::KexiDB::FieldList* fl = fieldlist->fieldlist();
+ for(::KexiDB::Field::ListIterator it = *fl->fields(); it.current(); ++it)
+ m_fieldlist->addField( it.current() );
+}
+
+KexiDBFieldList* KexiDBFieldList::subList(QValueList<QVariant> list) {
+ QValueList<QVariant>::ConstIterator it( list.constBegin() ), end( list.constEnd() );
+ QStringList sl;
+ for(; it != end; ++it) sl.append( (*it).toString() );
+ ::KexiDB::FieldList* fl = m_fieldlist->subList(sl);
+ return fl ? new Kross::KexiDB::KexiDBFieldList(fl) : 0;
+}
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbfieldlist.h b/kexi/plugins/scripting/kexidb/kexidbfieldlist.h
new file mode 100644
index 000000000..ee990eb32
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbfieldlist.h
@@ -0,0 +1,104 @@
+/***************************************************************************
+ * kexidbfieldlist.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBFIELDLIST_H
+#define KROSS_KEXIDB_KEXIDBFIELDLIST_H
+
+#include <qstring.h>
+
+#include <api/object.h>
+#include <api/list.h>
+#include <api/class.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/fieldlist.h>
+
+namespace Kross { namespace KexiDB {
+
+ // Forward declarations.
+ class KexiDBField;
+ class KexiDBFieldList;
+
+ /**
+ * A list of fields. The KexiDBFieldList can be used to handle KexiDBField objects
+ * in a backend-independent way.
+ *
+ * Example (in Python) ;
+ * @code
+ * # Get the tableschema for the "dept" table.
+ * table = connection.tableSchema("dept")
+ * # Create a KexiDBFieldList based on the table and filled with the selected fields.
+ * subfields = ["deptno","name","loc"]
+ * fieldlist = table.fieldlist().subList(subfields)
+ * # Create the "SELECT * from dept;" queryschema.
+ * query = table.query()
+ * # We change the queryschema to "SELECT deptno,name,loc FROM dept;" now.
+ * query.fieldlist().setFields(fieldlist)
+ * # and change the query to "SELECT deptno,name,loc FROM dept WHERE deptno=5;"
+ * query.setWhereExpression("deptno=5")
+ * # Execute the query and get a KexiDBCursor object as result which could be used to iterate through the result.
+ * cursor = connection.executeQuerySchema(query)
+ * @endcode
+ */
+ class KexiDBFieldList : public Kross::Api::Class<KexiDBFieldList>
+ {
+ public:
+ KexiDBFieldList(::KexiDB::FieldList* fieldlist);
+ virtual ~KexiDBFieldList();
+ virtual const QString getClassName() const;
+ ::KexiDB::FieldList* fieldlist() { return m_fieldlist; }
+
+ private:
+
+ /** Returns the number of fields. */
+ uint fieldCount();
+ /** Return the field specified by the index-number passed as an argument. */
+ KexiDBField* field(uint index);
+ /** Return the field specified by the as an argument passed fieldname. */
+ KexiDBField* fieldByName(const QString& name);
+
+ /** Returns a list of all fields. */
+ Kross::Api::List* fields();
+ /** Returns true if the KexiDBField object passed as an argument is in the field list. */
+ bool hasField(KexiDBField* field);
+ /** Return a list of field names. */
+ const QStringList names() const;
+
+ /** Adds the KexiDBField object passed as an argument to the field list. */
+ void addField(KexiDBField* field);
+ /** Inserts the KexiDBField object passed as the second argument
+ into the field list at the position defined by the first argument. */
+ void insertField(uint index, KexiDBField* field);
+ /** Removes the KexiDBField object passed as an argument from the field list. */
+ void removeField(KexiDBField* field);
+ /** Removes all KexiDBField objects from the fieldlist. */
+ void clear();
+ /** Set the fieldlist to the as argument passed list of fields. */
+ void setFields(KexiDBFieldList* fieldlist);
+ /** Creates and returns list that contain fields selected by name. */
+ KexiDBFieldList* subList(QValueList<QVariant> list);
+
+ private:
+ ::KexiDB::FieldList* m_fieldlist;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbmodule.cpp b/kexi/plugins/scripting/kexidb/kexidbmodule.cpp
new file mode 100644
index 000000000..36f7b71f0
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbmodule.cpp
@@ -0,0 +1,74 @@
+/***************************************************************************
+ * kexidbmodule.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#include "kexidbmodule.h"
+#include "kexidbdrivermanager.h"
+#include "kexidbconnection.h"
+
+//#include <api/object.h>
+//#include <api/variant.h>
+#include <main/manager.h>
+
+#include <kdebug.h>
+
+// The as version() published versionnumber of this kross-module.
+#define KROSS_KEXIDB_VERSION 1
+
+extern "C"
+{
+ /**
+ * Exported an loadable function as entry point to use
+ * the \a KexiDBModule.
+ */
+ Kross::Api::Object* KDE_EXPORT init_module(Kross::Api::Manager* manager)
+ {
+ return new Kross::KexiDB::KexiDBModule(manager);
+ }
+}
+
+using namespace Kross::KexiDB;
+
+KexiDBModule::KexiDBModule(Kross::Api::Manager* /*manager*/)
+ : Kross::Api::Module("KexiDB")
+ //, m_manager(manager)
+{
+ //kdDebug() << "Kross::KexiDB::KexiDBModule Ctor" << endl;
+ addChild( "version", new Kross::Api::Variant(KROSS_KEXIDB_VERSION) );
+ addChild( new KexiDBDriverManager() );
+}
+
+KexiDBModule::~KexiDBModule()
+{
+ //kdDebug() << "Kross::KexiDB::KexiDBModule Dtor" << endl;
+}
+
+const QString KexiDBModule::getClassName() const
+{
+ return "Kross::KexiDB::KexiDBModule";
+}
+
+Kross::Api::Object::Ptr KexiDBModule::get(const QString& name, void* p)
+{
+ if(name == "KexiDBConnection") {
+ ::KexiDB::Connection* connection = (::KexiDB::Connection*)p;
+ if(connection)
+ return new KexiDBConnection(connection);
+ }
+ return 0;
+}
diff --git a/kexi/plugins/scripting/kexidb/kexidbmodule.h b/kexi/plugins/scripting/kexidb/kexidbmodule.h
new file mode 100644
index 000000000..b91b60479
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbmodule.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+ * kexidbmodule.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBMODULE_H
+#define KROSS_KEXIDB_KEXIDBMODULE_H
+
+#include <qstring.h>
+#include <qvariant.h>
+
+#include <api/module.h>
+
+namespace Kross { namespace Api {
+ class Manager;
+}}
+
+namespace Kross {
+
+/**
+ * KrossKexiDB provides access to the KexiDB database functionality.
+ */
+namespace KexiDB {
+
+ /**
+ * \internal
+ * The KexiDBModule is the implementation of a kross-module.
+ */
+ class KexiDBModule : public Kross::Api::Module
+ {
+ public:
+ KexiDBModule(Kross::Api::Manager* manager);
+ virtual ~KexiDBModule();
+ virtual const QString getClassName() const;
+
+ /**
+ * \internal
+ * Variable module-method use to call transparent some functionality
+ * the module provides.
+ *
+ * \param name A name passed to the method. This name is used internaly
+ * to determinate what the caller likes to do. Each implemented
+ * module have to implement what should be done.
+ * \param p A variable pointer passed to the method. It depends on
+ * the module and the name what this pointer is.
+ * \return a \a Kross::Api::Object or NULL.
+ */
+ virtual Kross::Api::Object::Ptr get(const QString& name, void* p = 0);
+
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbparser.cpp b/kexi/plugins/scripting/kexidb/kexidbparser.cpp
new file mode 100644
index 000000000..b022570d3
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbparser.cpp
@@ -0,0 +1,77 @@
+/***************************************************************************
+ * kexidbparser.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+
+#include "kexidbparser.h"
+#include "kexidbschema.h"
+#include "kexidbconnection.h"
+
+#include <api/variant.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBParser::KexiDBParser(KexiDBConnection* connection, ::KexiDB::Parser* parser)
+ : Kross::Api::Class<KexiDBParser>("KexiDBParser")
+ , m_connection(connection)
+ , m_parser(parser)
+{
+ this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("parse", this, &KexiDBParser::parse);
+ this->addFunction0< void >("clear", this, &KexiDBParser::clear);
+
+ this->addFunction0< Kross::Api::Variant >("operation", this, &KexiDBParser::operation);
+
+ this->addFunction0< KexiDBTableSchema >("table", this, &KexiDBParser::table);
+ this->addFunction0< KexiDBQuerySchema >("query", this, &KexiDBParser::query);
+ this->addFunction0< KexiDBConnection >("connection", this, &KexiDBParser::connection);
+ this->addFunction0< Kross::Api::Variant >("statement", this, &KexiDBParser::statement);
+
+ this->addFunction0< Kross::Api::Variant >("errorType", this, &KexiDBParser::errorType);
+ this->addFunction0< Kross::Api::Variant >("errorMsg", this, &KexiDBParser::errorMsg);
+ this->addFunction0< Kross::Api::Variant >("errorAt", this, &KexiDBParser::errorAt);
+}
+
+KexiDBParser::~KexiDBParser()
+{
+}
+
+const QString KexiDBParser::getClassName() const
+{
+ return "Kross::KexiDB::KexiDBParser";
+}
+
+bool KexiDBParser::parse(const QString& sql) { return m_parser->parse(sql); }
+void KexiDBParser::clear() { m_parser->clear(); }
+const QString KexiDBParser::operation() { return m_parser->operationString(); }
+
+KexiDBTableSchema* KexiDBParser::table() {
+ ::KexiDB::TableSchema* t = m_parser->table();
+ return t ? new KexiDBTableSchema(t) : 0;
+}
+
+KexiDBQuerySchema* KexiDBParser::query() {
+ ::KexiDB::QuerySchema* q = m_parser->query();
+ return q ? new KexiDBQuerySchema(q) : 0;
+}
+
+KexiDBConnection* KexiDBParser::connection() { return m_connection; }
+const QString KexiDBParser::statement() { return m_parser->statement(); }
+
+const QString KexiDBParser::errorType() { return m_parser->error().type(); }
+const QString KexiDBParser::errorMsg() { return m_parser->error().error(); }
+int KexiDBParser::errorAt() { return m_parser->error().at(); }
diff --git a/kexi/plugins/scripting/kexidb/kexidbparser.h b/kexi/plugins/scripting/kexidb/kexidbparser.h
new file mode 100644
index 000000000..09ac22daa
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbparser.h
@@ -0,0 +1,95 @@
+/***************************************************************************
+ * kexidbparser.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBPARSER_H
+#define KROSS_KEXIDB_KEXIDBPARSER_H
+
+#include <qstring.h>
+
+#include <api/object.h>
+#include <api/list.h>
+#include <api/class.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/parser/parser.h>
+
+namespace Kross { namespace KexiDB {
+
+ // Forward declaration.
+ class KexiDBConnection;
+ class KexiDBTableSchema;
+ class KexiDBQuerySchema;
+
+ /**
+ * The KexiDBParser could be used to parse SQL-statements.
+ *
+ * Example (in Python) ;
+ * @code
+ * # First we need a parser object.
+ * parser = connection.parser()
+ * # Parse a SQL-statement.
+ * parser.parse("SELECT * from table1")
+ * # The operation could be e.g. SELECT or INSERT.
+ * if parser.operation() == 'Error':
+ * raise parser.errorMsg()
+ * # Print some feedback.
+ * print "Successfully parsed the SQL-statement %s" % parser.statement()
+ * @endcode
+ */
+ class KexiDBParser : public Kross::Api::Class<KexiDBParser>
+ {
+ public:
+ KexiDBParser(KexiDBConnection* connection, ::KexiDB::Parser* parser);
+ virtual ~KexiDBParser();
+ virtual const QString getClassName() const;
+
+ private:
+
+ /** Clears previous results and runs the parser on the SQL statement passed as an argument. */
+ bool parse(const QString& sql);
+ /** Clears parsing results. */
+ void clear();
+ /** Returns the resulting operation. */
+ const QString operation();
+
+ /** Returns the KexiDBTableSchema object on a CREATE TABLE operation. */
+ KexiDBTableSchema* table();
+ /** Returns the KexiDBQuerySchema object on a SELECT operation. */
+ KexiDBQuerySchema* query();
+ /** Returns the KexiDBConnection object pointing to the used database connection. */
+ KexiDBConnection* connection();
+ /** Returns the SQL query statement. */
+ const QString statement();
+
+ /** Returns the type string of the last error. */
+ const QString errorType();
+ /** Returns the message of the last error. */
+ const QString errorMsg();
+ /** Returns the position where the last error occurred. */
+ int errorAt();
+
+ private:
+ KexiDBConnection* m_connection;
+ ::KexiDB::Parser* m_parser;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbschema.cpp b/kexi/plugins/scripting/kexidb/kexidbschema.cpp
new file mode 100644
index 000000000..e07917f3c
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbschema.cpp
@@ -0,0 +1,197 @@
+/***************************************************************************
+ * kexidbschema.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+
+#include "kexidbschema.h"
+#include "kexidbfieldlist.h"
+
+#include <qregexp.h>
+#include <kdebug.h>
+
+#include <api/variant.h>
+
+using namespace Kross::KexiDB;
+
+/***************************************************************************
+ *KexiDBSchema
+ */
+
+template<class T>
+KexiDBSchema<T>::KexiDBSchema(const QString& name, ::KexiDB::SchemaData* schema, ::KexiDB::FieldList* fieldlist)
+ : Kross::Api::Class<T>(name)
+ , m_schema(schema)
+ , m_fieldlist(fieldlist)
+{
+ this->template addFunction0<Kross::Api::Variant>("name", this, &KexiDBSchema<T>::name);
+ this->template addFunction1<void, Kross::Api::Variant>("setName", this, &KexiDBSchema<T>::setName);
+
+ this->template addFunction0<Kross::Api::Variant>("caption", this, &KexiDBSchema<T>::caption);
+ this->template addFunction1<void, Kross::Api::Variant>("setCaption", this, &KexiDBSchema<T>::setCaption);
+
+ this->template addFunction0<Kross::Api::Variant>("description", this, &KexiDBSchema<T>::description);
+ this->template addFunction1<void, Kross::Api::Variant>("setDescription", this, &KexiDBSchema<T>::setDescription);
+
+ this->template addFunction0<KexiDBFieldList>("fieldlist", this, &KexiDBSchema<T>::fieldlist);
+}
+
+template<class T>
+KexiDBSchema<T>::~KexiDBSchema<T>() {
+}
+
+template<class T>
+const QString KexiDBSchema<T>::name() const {
+ return m_schema->name();
+}
+
+template<class T>
+void KexiDBSchema<T>::setName(const QString& name) {
+ m_schema->setName(name);
+}
+
+template<class T>
+const QString KexiDBSchema<T>::caption() const {
+ return m_schema->caption();
+}
+
+template<class T>
+void KexiDBSchema<T>::setCaption(const QString& caption) {
+ m_schema->setCaption(caption);
+}
+
+template<class T>
+const QString KexiDBSchema<T>::description() const {
+ return m_schema->description();
+}
+
+template<class T>
+void KexiDBSchema<T>::setDescription(const QString& description) {
+ m_schema->setDescription(description);
+}
+
+template<class T>
+KexiDBFieldList* KexiDBSchema<T>::fieldlist() const {
+ return new KexiDBFieldList(m_fieldlist);
+}
+
+/***************************************************************************
+ * KexiDBTableSchema
+ */
+
+KexiDBTableSchema::KexiDBTableSchema(::KexiDB::TableSchema* tableschema)
+ : KexiDBSchema<KexiDBTableSchema>("KexiDBTableSchema", tableschema, tableschema)
+{
+ this->addFunction0<KexiDBQuerySchema>("query", this, &KexiDBTableSchema::query);
+}
+
+KexiDBTableSchema::~KexiDBTableSchema() {
+}
+
+const QString KexiDBTableSchema::getClassName() const {
+ return "Kross::KexiDB::KexiDBTableSchema";
+}
+
+::KexiDB::TableSchema* KexiDBTableSchema::tableschema() {
+ return static_cast< ::KexiDB::TableSchema* >(m_schema);
+}
+
+KexiDBQuerySchema* KexiDBTableSchema::query() {
+ return new KexiDBQuerySchema( tableschema()->query() );
+}
+
+/***************************************************************************
+ * KexiDBQuerySchema
+ */
+
+KexiDBQuerySchema::KexiDBQuerySchema(::KexiDB::QuerySchema* queryschema)
+ : KexiDBSchema<KexiDBQuerySchema>("KexiDBQuerySchema", queryschema, queryschema)
+{
+ this->addFunction0<Kross::Api::Variant>("statement", this, &KexiDBQuerySchema::statement);
+ this->addFunction1<void, Kross::Api::Variant>("setStatement", this, &KexiDBQuerySchema::setStatement);
+ this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("setWhereExpression", this, &KexiDBQuerySchema::setWhereExpression);
+}
+
+KexiDBQuerySchema::~KexiDBQuerySchema() {
+}
+
+const QString KexiDBQuerySchema::getClassName() const {
+ return "Kross::KexiDB::KexiDBQuerySchema";
+}
+
+::KexiDB::QuerySchema* KexiDBQuerySchema::queryschema() {
+ return static_cast< ::KexiDB::QuerySchema* >(m_schema);
+}
+
+const QString KexiDBQuerySchema::statement() const {
+ return static_cast< ::KexiDB::QuerySchema* >(m_schema)->statement();
+}
+
+void KexiDBQuerySchema::setStatement(const QString& statement) {
+ static_cast< ::KexiDB::QuerySchema* >(m_schema)->setStatement(statement);
+}
+
+bool KexiDBQuerySchema::setWhereExpression(const QString& whereexpression) {
+ ::KexiDB::BaseExpr* oldexpr = static_cast< ::KexiDB::QuerySchema* >(m_schema)->whereExpression();
+
+ ///@todo use ::KexiDB::Parser for such kind of parser-functionality.
+ QString s = whereexpression;
+ try {
+ QRegExp re("[\"',]{1,1}");
+ while(true) {
+ s.remove(QRegExp("^[\\s,]+"));
+ int pos = s.find('=');
+ if(pos < 0) break;
+ QString key = s.left(pos).stripWhiteSpace();
+ s = s.mid(pos + 1).stripWhiteSpace();
+
+ QString value;
+ int sp = s.find(re);
+ if(sp >= 0) {
+ if(re.cap(0) == ",") {
+ value = s.left(sp).stripWhiteSpace();
+ s = s.mid(sp+1).stripWhiteSpace();
+ }
+ else {
+ int ep = s.find(re.cap(0),sp+1);
+ value = s.mid(sp+1,ep-1);
+ s = s.mid(ep + 1);
+ }
+ }
+ else {
+ value = s;
+ s = QString::null;
+ }
+
+ ::KexiDB::Field* field = static_cast< ::KexiDB::QuerySchema* >(m_schema)->field(key);
+ if(! field)
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Invalid WHERE-expression: Field \"%1\" does not exists in tableschema \"%2\".").arg(key).arg(m_schema->name())) );
+
+ QVariant v(value);
+ if(! v.cast(field->variantType()))
+ throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Invalid WHERE-expression: The for Field \"%1\" defined value is of type \"%2\" rather then the expected type \"%3\"").arg(key).arg(v.typeName()).arg(field->variantType())) );
+
+ static_cast< ::KexiDB::QuerySchema* >(m_schema)->addToWhereExpression(field,v);
+ }
+ }
+ catch(Kross::Api::Exception::Ptr e) {
+ Kross::krosswarning("Exception in Kross::KexiDB::KexiDBQuerySchema::setWhereExpression: ");
+ static_cast< ::KexiDB::QuerySchema* >(m_schema)->setWhereExpression(oldexpr); // fallback
+ return false;
+ }
+ return true;
+}
diff --git a/kexi/plugins/scripting/kexidb/kexidbschema.h b/kexi/plugins/scripting/kexidb/kexidbschema.h
new file mode 100644
index 000000000..61b6bc885
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbschema.h
@@ -0,0 +1,134 @@
+/***************************************************************************
+ * kexidbschema.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBSCHEMA_H
+#define KROSS_KEXIDB_KEXIDBSCHEMA_H
+
+#include <qstring.h>
+
+#include <api/object.h>
+#include <api/class.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/schemadata.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/queryschema.h>
+
+namespace Kross { namespace KexiDB {
+
+ // Forward-declarations.
+ class KexiDBFieldList;
+ class KexiDBQuerySchema;
+
+ /**
+ * The KexiDBSchema object provides common functionality for schemas
+ * like KexiDBTableSchema or KexiDBQuerySchema.
+ *
+ * Example (in Python) ;
+ * @code
+ * # Get the tableschema from a KexiDBConnection object.
+ * tableschema = connection.tableSchema("dept")
+ * # Print some information.
+ * print "table=%s description=%s" % (tableschema.name(), tableschema.description())
+ * # Get the "SELECT * FROM dept;" queryschema for the table.
+ * queryschema = tableschema.query()
+ * # Walk through the fields/columns the queryschema has and print the fieldnames.
+ * for field in queryschema.fieldlist().fields():
+ * print "fieldname=%s" % field.name()
+ * # Execute the query. The returned KexiDBCursor object could be used then to iterate through the result.
+ * cursor = connection.executeQuerySchema(queryschema)
+ * @endcode
+ */
+ template<class T>
+ class KexiDBSchema : public Kross::Api::Class<T>
+ {
+ public:
+ KexiDBSchema(const QString& name, ::KexiDB::SchemaData* schema, ::KexiDB::FieldList* fieldlist);
+ virtual ~KexiDBSchema();
+
+ private:
+
+ /** Returns the name of the schema. */
+ const QString name() const;
+ /** Set the name of the schema. */
+ void setName(const QString& name);
+
+ /** Returns the caption of the schema. */
+ const QString caption() const;
+ /** Set the caption of the schema. */
+ void setCaption(const QString& caption);
+
+ /** Returns a description of the schema. */
+ const QString description() const;
+ /** Set a description of the schema. */
+ void setDescription(const QString& description);
+
+ /** Returns the KexiDBFieldList object this schema has. */
+ KexiDBFieldList* fieldlist() const;
+
+ protected:
+ ::KexiDB::SchemaData* m_schema;
+ ::KexiDB::FieldList* m_fieldlist;
+ };
+
+ /**
+ * The KexiDBTableSchema object implements a KexiDBSchema for tables.
+ */
+ class KexiDBTableSchema : public KexiDBSchema<KexiDBTableSchema>
+ {
+ public:
+ KexiDBTableSchema(::KexiDB::TableSchema* tableschema);
+ virtual ~KexiDBTableSchema();
+ virtual const QString getClassName() const;
+ ::KexiDB::TableSchema* tableschema();
+
+ private:
+
+ /** Return the KexiDBQuerySchema object that represents a
+ "SELECT * FROM this_KexiDBTableSchema_object" SQL-statement. */
+ KexiDBQuerySchema* query();
+
+ };
+
+ /**
+ * The KexiDBTableSchema object implements a KexiDBSchema for queries.
+ */
+ class KexiDBQuerySchema : public KexiDBSchema<KexiDBQuerySchema>
+ {
+ public:
+ KexiDBQuerySchema(::KexiDB::QuerySchema* queryschema);
+ virtual ~KexiDBQuerySchema();
+ virtual const QString getClassName() const;
+ ::KexiDB::QuerySchema* queryschema();
+
+ private:
+
+ /** Returns the SQL-statement of this query schema. */
+ const QString statement() const;
+ /** Set the SQL-statement of this query schema. */
+ void setStatement(const QString& statement);
+ /** Set the where-expression. */
+ bool setWhereExpression(const QString& whereexpression);
+
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/kexidbtransaction.cpp b/kexi/plugins/scripting/kexidb/kexidbtransaction.cpp
new file mode 100644
index 000000000..d4cdff242
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbtransaction.cpp
@@ -0,0 +1,52 @@
+/***************************************************************************
+ * kexidbtransaction.cpp
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+
+#include "kexidbtransaction.h"
+#include "kexidbconnection.h"
+#include <api/variant.h>
+
+//#include <kdebug.h>
+
+using namespace Kross::KexiDB;
+
+KexiDBTransaction::KexiDBTransaction(::KexiDB::Transaction& transaction)
+ : Kross::Api::Class<KexiDBTransaction>("KexiDBTransaction")
+ , m_transaction(transaction)
+{
+ this->addFunction0< Kross::Api::Variant >("isActive", this, &KexiDBTransaction::isActive);
+ this->addFunction0< Kross::Api::Variant >("isNull", this, &KexiDBTransaction::isNull);
+}
+
+KexiDBTransaction::~KexiDBTransaction()
+{
+}
+
+const QString KexiDBTransaction::getClassName() const
+{
+ return "Kross::KexiDB::KexiDBTransaction";
+}
+
+::KexiDB::Transaction& KexiDBTransaction::transaction()
+{
+ return m_transaction;
+}
+
+bool KexiDBTransaction::isActive() const { return m_transaction.active(); }
+bool KexiDBTransaction::isNull() const { return m_transaction.isNull(); }
diff --git a/kexi/plugins/scripting/kexidb/kexidbtransaction.h b/kexi/plugins/scripting/kexidb/kexidbtransaction.h
new file mode 100644
index 000000000..6a6b5785b
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/kexidbtransaction.h
@@ -0,0 +1,62 @@
+/***************************************************************************
+ * kexidbtransaction.h
+ * This file is part of the KDE project
+ * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ ***************************************************************************/
+
+#ifndef KROSS_KEXIDB_KEXIDBTRANSACTION_H
+#define KROSS_KEXIDB_KEXIDBTRANSACTION_H
+
+#include <qstring.h>
+
+#include <api/class.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/transaction.h>
+
+namespace Kross { namespace KexiDB {
+
+ // Forward declaration.
+ class KexiDBConnection;
+
+ /**
+ * Transactions are used to ensure that integrity of a database is
+ * maintained.
+ */
+ class KexiDBTransaction : public Kross::Api::Class<KexiDBTransaction>
+ {
+ public:
+ KexiDBTransaction(::KexiDB::Transaction& transaction);
+ virtual ~KexiDBTransaction();
+ virtual const QString getClassName() const;
+ ::KexiDB::Transaction& transaction();
+
+ private:
+
+ /** Return true if the transaction is active (ie. started). */
+ bool isActive() const;
+
+ /** Return true if the transaction is uninitialized (null). */
+ bool isNull() const;
+
+ private:
+ ::KexiDB::Transaction& m_transaction;
+ };
+
+}}
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexidb/readme.dox b/kexi/plugins/scripting/kexidb/readme.dox
new file mode 100644
index 000000000..c4e33a5b4
--- /dev/null
+++ b/kexi/plugins/scripting/kexidb/readme.dox
@@ -0,0 +1,32 @@
+/** @mainpage KrossKexiDB
+ *
+ * The Kross KexiDB module provides a scripting bridge to the
+ * KexiDB library. KexiDB is the database abstraction layer used
+ * within Kexi to deal with all supported database-backends like
+ * SQLite, MySQL and Postqre.
+ *
+ * The @a KexiDBDriverManager is the manager module which provides
+ * the entry point to access the KexiDB functionality from
+ * scripting languages like Python and Ruby.
+ *
+ * @see http://www.kexi-project.org/scripting/
+ * @see http://kross.dipe.org
+ * @see http://www.kexi-project.org/wiki/wikiview/index.php?Scripting
+ *
+ * @section Legal
+ *
+ * @li copyright (C) 2004-2006 by Sebastian Sauer (mail AT dipe DOT org)
+ *
+ * This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
diff --git a/kexi/plugins/scripting/kexiscripting/Makefile.am b/kexi/plugins/scripting/kexiscripting/Makefile.am
new file mode 100644
index 000000000..ed3e22646
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/Makefile.am
@@ -0,0 +1,37 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_script.la
+
+kexihandler_script_la_SOURCES = \
+ kexiscriptpart.cpp kexiscripteditor.cpp kexiscriptdesignview.cpp
+
+kexihandler_script_la_LDFLAGS = \
+ $(KDE_PLUGIN) -module -no-undefined -Wnounresolved $(all_libraries) $(VER_INFO)
+
+kexihandler_script_la_LIBADD = \
+ $(top_builddir)/lib/kross/main/libkrossmain.la \
+ $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la
+
+INCLUDES = \
+ $(KOFFICE_INCLUDES) \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget \
+ $(all_includes)
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=kexiscripthandler.desktop
+
+rcdir = $(kde_datadir)/kexi
+rc_DATA = kexiscriptpartui.rc kexiscriptpartinstui.rc
+
+METASOURCES = AUTO
+
+SUBDIRS = .
+
+include ../../Makefile.common
+
+noinst_HEADERS = kexiscriptpart.h kexiscripteditor.h kexiscriptdesignview.h
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.cpp b/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.cpp
new file mode 100644
index 000000000..ff2f93d03
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.cpp
@@ -0,0 +1,337 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Sebastian Sauer <mail@dipe.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiscriptdesignview.h"
+#include "kexiscripteditor.h"
+
+#include <kross/main/manager.h>
+#include <kross/main/scriptcontainer.h>
+#include <kross/main/scriptaction.h>
+#include <kross/api/interpreter.h>
+
+#include <qlayout.h>
+#include <qsplitter.h>
+#include <qtimer.h>
+#include <qdatetime.h>
+#include <qdom.h>
+#include <qstylesheet.h>
+#include <ktextbrowser.h>
+#include <kdebug.h>
+
+#include <kexidialogbase.h>
+#include <kexidb/connection.h>
+
+/// @internal
+class KexiScriptDesignViewPrivate
+{
+ public:
+
+ /**
+ * The \a Kross::Api::ScriptAction instance which provides
+ * us access to the scripting framework Kross.
+ */
+ Kross::Api::ScriptAction* scriptaction;
+
+ /// The \a KexiScriptEditor to edit the scripting code.
+ KexiScriptEditor* editor;
+
+ /// The \a KoProperty::Set used in the propertyeditor.
+ KoProperty::Set* properties;
+
+ /// Boolean flag to avoid infinite recursion.
+ bool updatesProperties;
+
+ /// Used to display statusmessages.
+ KTextBrowser* statusbrowser;
+};
+
+KexiScriptDesignView::KexiScriptDesignView(KexiMainWindow *mainWin, QWidget *parent, Kross::Api::ScriptAction* scriptaction)
+ : KexiViewBase(mainWin, parent, "KexiScriptDesignView")
+ , d( new KexiScriptDesignViewPrivate() )
+{
+ d->scriptaction = scriptaction;
+ d->updatesProperties = false;
+
+ QSplitter* splitter = new QSplitter(this);
+ splitter->setOrientation(Vertical);
+ QHBoxLayout* layout = new QHBoxLayout(this);
+ layout->addWidget(splitter);
+
+ d->editor = new KexiScriptEditor(mainWin, splitter, "ScriptEditor");
+ splitter->setFocusProxy(d->editor);
+ addChildView(d->editor);
+ setViewWidget(d->editor);
+
+ d->statusbrowser = new KTextBrowser(splitter, "ScriptStatusBrowser");
+ d->statusbrowser->setReadOnly(true);
+ d->statusbrowser->setTextFormat(QTextBrowser::RichText);
+ //d->browser->setWordWrap(QTextEdit::WidgetWidth);
+ d->statusbrowser->installEventFilter(this);
+ splitter->setResizeMode(d->statusbrowser, QSplitter::KeepSize);
+
+ plugSharedAction( "data_execute", this, SLOT(execute()) );
+ if(KexiEditor::isAdvancedEditor()) // the configeditor is only in advanced mode avaiable.
+ plugSharedAction( "script_config_editor", d->editor, SLOT(slotConfigureEditor()) );
+
+ loadData();
+
+ d->properties = new KoProperty::Set(this, "KexiScripting");
+ connect(d->properties, SIGNAL( propertyChanged(KoProperty::Set&, KoProperty::Property&) ),
+ this, SLOT( slotPropertyChanged(KoProperty::Set&, KoProperty::Property&) ));
+
+ // To schedule the initialize fixes a crasher in Kate.
+ QTimer::singleShot(50, this, SLOT( initialize() ));
+}
+
+KexiScriptDesignView::~KexiScriptDesignView()
+{
+ delete d->properties;
+ delete d;
+}
+
+Kross::Api::ScriptAction* KexiScriptDesignView::scriptAction() const
+{
+ return d->scriptaction;
+}
+
+void KexiScriptDesignView::initialize()
+{
+ updateProperties();
+ d->editor->initialize( d->scriptaction );
+}
+
+void KexiScriptDesignView::updateProperties()
+{
+ if(d->updatesProperties)
+ return;
+ d->updatesProperties = true;
+
+ Kross::Api::Manager* manager = Kross::Api::Manager::scriptManager();
+
+ QString interpretername = d->scriptaction->getInterpreterName();
+ Kross::Api::InterpreterInfo* info = interpretername.isEmpty() ? 0 : manager->getInterpreterInfo(interpretername);
+
+ {
+ // if interpreter isn't defined or invalid, try to fallback.
+ QStringList list;
+ list << "python" << "ruby";
+ QStringList::ConstIterator it( list.constBegin() ), end( list.constEnd() );
+ while( (! info) && (it != end) ) {
+ interpretername = (*it);
+ info = manager->getInterpreterInfo(interpretername);
+ if(info)
+ d->scriptaction->setInterpreterName(interpretername);
+ ++it;
+ }
+ }
+
+ if(info) {
+ d->properties->clear();
+
+ QStringList interpreters = manager->getInterpreters();
+ KoProperty::Property::ListData* proplist = new KoProperty::Property::ListData(interpreters, interpreters);
+ KoProperty::Property* prop = new KoProperty::Property(
+ "language", // name
+ proplist, // ListData
+ d->scriptaction->getInterpreterName(), // value
+ i18n("Interpreter"), // caption
+ i18n("The used scripting interpreter."), // description
+ KoProperty::List // type
+ );
+ d->properties->addProperty(prop);
+
+ Kross::Api::InterpreterInfo::Option::Map options = info->getOptions();
+ Kross::Api::InterpreterInfo::Option::Map::ConstIterator it, end( options.constEnd() );
+ for( it = options.constBegin(); it != end; ++it) {
+ Kross::Api::InterpreterInfo::Option* option = it.data();
+ KoProperty::Property* prop = new KoProperty::Property(
+ it.key().latin1(), // name
+ d->scriptaction->getOption(it.key(), option->value), // value
+ option->name, // caption
+ option->comment, // description
+ KoProperty::Auto // type
+ );
+ d->properties->addProperty(prop);
+ }
+ }
+
+ //propertySetSwitched();
+ propertySetReloaded(true);
+ d->updatesProperties = false;
+}
+
+KoProperty::Set* KexiScriptDesignView::propertySet()
+{
+ return d->properties;
+}
+
+void KexiScriptDesignView::slotPropertyChanged(KoProperty::Set& /*set*/, KoProperty::Property& property)
+{
+ if(property.isNull())
+ return;
+
+ if(property.name() == "language") {
+ QString language = property.value().toString();
+ kdDebug() << QString("KexiScriptDesignView::slotPropertyChanged() language=%1").arg(language) << endl;
+ d->scriptaction->setInterpreterName( language );
+ // We assume Kross and the HighlightingInterface are using same
+ // names for the support languages...
+ d->editor->setHighlightMode( language );
+ updateProperties();
+ }
+ else {
+ bool ok = d->scriptaction->setOption( property.name(), property.value() );
+ if(! ok) {
+ kdWarning() << QString("KexiScriptDesignView::slotPropertyChanged() unknown property '%1'.").arg(property.name()) << endl;
+ return;
+ }
+ }
+
+ setDirty(true);
+}
+
+void KexiScriptDesignView::execute()
+{
+ d->statusbrowser->clear();
+ QTime time;
+ time.start();
+ d->statusbrowser->append( i18n("Execution of the script \"%1\" started.").arg(d->scriptaction->name()) );
+
+ d->scriptaction->activate();
+ if( d->scriptaction->hadException() ) {
+ QString errormessage = d->scriptaction->getException()->getError();
+ d->statusbrowser->append(QString("<b>%2</b><br>").arg(QStyleSheet::escape(errormessage)) );
+
+ QString tracedetails = d->scriptaction->getException()->getTrace();
+ d->statusbrowser->append( QStyleSheet::escape(tracedetails) );
+
+ long lineno = d->scriptaction->getException()->getLineNo();
+ if(lineno >= 0)
+ d->editor->setLineNo(lineno);
+ }
+ else {
+ d->statusbrowser->append( i18n("Successfully executed. Time elapsed: %1ms").arg(time.elapsed()) );
+ }
+}
+
+bool KexiScriptDesignView::loadData()
+{
+ QString data;
+ if(! loadDataBlock(data)) {
+ kexipluginsdbg << "KexiScriptDesignView::loadData(): no DataBlock" << endl;
+ return false;
+ }
+
+ QString errMsg;
+ int errLine;
+ int errCol;
+
+ QDomDocument domdoc;
+ bool parsed = domdoc.setContent(data, false, &errMsg, &errLine, &errCol);
+
+ if(! parsed) {
+ kexipluginsdbg << "KexiScriptDesignView::loadData() XML parsing error line: " << errLine << " col: " << errCol << " message: " << errMsg << endl;
+ return false;
+ }
+
+ QDomElement scriptelem = domdoc.namedItem("script").toElement();
+ if(scriptelem.isNull()) {
+ kexipluginsdbg << "KexiScriptDesignView::loadData(): script domelement is null" << endl;
+ return false;
+ }
+
+ QString interpretername = scriptelem.attribute("language");
+ Kross::Api::Manager* manager = Kross::Api::Manager::scriptManager();
+ Kross::Api::InterpreterInfo* info = interpretername.isEmpty() ? 0 : manager->getInterpreterInfo(interpretername);
+ if(info) {
+ d->scriptaction->setInterpreterName(interpretername);
+
+ Kross::Api::InterpreterInfo::Option::Map options = info->getOptions();
+ Kross::Api::InterpreterInfo::Option::Map::ConstIterator it, end = options.constEnd();
+ for( it = options.constBegin(); it != end; ++it) {
+ QString value = scriptelem.attribute( it.data()->name );
+ if(! value.isNull()) {
+ QVariant v(value);
+ if( v.cast( it.data()->value.type() ) ) // preserve the QVariant's type
+ d->scriptaction->setOption(it.data()->name, v);
+ }
+ }
+ }
+
+ d->scriptaction->setCode( scriptelem.text() );
+
+ return true;
+}
+
+KexiDB::SchemaData* KexiScriptDesignView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ KexiDB::SchemaData *s = KexiViewBase::storeNewData(sdata, cancel);
+ kexipluginsdbg << "KexiScriptDesignView::storeNewData(): new id:" << s->id() << endl;
+
+ if(!s || cancel) {
+ delete s;
+ return 0;
+ }
+
+ if(! storeData()) {
+ kdWarning() << "KexiScriptDesignView::storeNewData Failed to store the data." << endl;
+ //failure: remove object's schema data to avoid garbage
+ KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
+ conn->removeObject( s->id() );
+ delete s;
+ return 0;
+ }
+
+ return s;
+}
+
+tristate KexiScriptDesignView::storeData(bool /*dontAsk*/)
+{
+ kexipluginsdbg << "KexiScriptDesignView::storeData(): " << parentDialog()->partItem()->name() << " [" << parentDialog()->id() << "]" << endl;
+
+ QDomDocument domdoc("script");
+ QDomElement scriptelem = domdoc.createElement("script");
+ domdoc.appendChild(scriptelem);
+
+ QString language = d->scriptaction->getInterpreterName();
+ scriptelem.setAttribute("language", language);
+
+ Kross::Api::InterpreterInfo* info = Kross::Api::Manager::scriptManager()->getInterpreterInfo(language);
+ if(info) {
+ Kross::Api::InterpreterInfo::Option::Map defoptions = info->getOptions();
+ QMap<QString, QVariant>& options = d->scriptaction->getOptions();
+ QMap<QString, QVariant>::ConstIterator it, end( options.constEnd() );
+ for( it = options.constBegin(); it != end; ++it) {
+ if( defoptions.contains(it.key()) ) { // only remember options which the InterpreterInfo knows about...
+ scriptelem.setAttribute(it.key(), it.data().toString());
+ }
+ }
+ }
+
+ QDomText scriptcode = domdoc.createTextNode(d->scriptaction->getCode());
+ scriptelem.appendChild(scriptcode);
+
+ return storeDataBlock( domdoc.toString() );
+}
+
+#include "kexiscriptdesignview.moc"
+
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.h b/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.h
new file mode 100644
index 000000000..cee1ed763
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.h
@@ -0,0 +1,124 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Sebastian Sauer <mail@dipe.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISCRIPTDESIGNVIEW_H
+#define KEXISCRIPTDESIGNVIEW_H
+
+#include <kexiviewbase.h>
+
+#include <koproperty/set.h>
+#include <koproperty/property.h>
+
+// Forward declarations.
+class KexiScriptContainer;
+class KexiScriptEditor;
+class KexiScriptDesignViewPrivate;
+
+namespace Kross { namespace Api {
+ class ScriptAction;
+}}
+
+/**
+ * The KexiScriptDesignView class provides the \a KexiViewBase to
+ * manage script modules in the design-view. The design-view
+ * is used to be able to view and edit the scripting code via
+ * a \a KexiScriptEditor instance.
+ */
+class KexiScriptDesignView : public KexiViewBase
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ */
+ KexiScriptDesignView(KexiMainWindow *mainWin, QWidget *parent, Kross::Api::ScriptAction* scriptaction);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiScriptDesignView();
+
+ /**
+ * \return the \a Kross::Api::ScriptAction this \a KexiScriptDesignView
+ * is responsible for.
+ */
+ Kross::Api::ScriptAction* scriptAction() const;
+
+ /**
+ * \return a property set for this view.
+ */
+ virtual KoProperty::Set* propertySet();
+
+ /**
+ * Try to call \a storeData with new data we like to store. On
+ * success the matching \a KexiDB::SchemaData is returned.
+ *
+ * \param sdata The source \a KexiDB::SchemaData instance.
+ * \param cancel Cancel on failure and don't try to clean
+ * possible temporary created data up.
+ * \return The matching \a KexiDB::SchemaData instance or NULL
+ * if storing failed.
+ */
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+
+ /**
+ * Try to store the modified data in the already opened and
+ * currently used \a KexiDB::SchemaData instance.
+ */
+ virtual tristate storeData(bool dontAsk = false);
+
+ private slots:
+
+ /**
+ * Deferred initialization.
+ */
+ void initialize();
+
+ /**
+ * Handle changes in the property editor.
+ */
+ void slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property);
+
+ /**
+ * Update the \a KoProperty::Property::Dict propertymap of the
+ * interpreter-dependent options.
+ */
+ void updateProperties();
+
+ /**
+ * Execute the scripting code.
+ */
+ void execute();
+
+ private:
+ KexiScriptDesignViewPrivate* d;
+
+ /**
+ * Load the data from XML source and fill the internally
+ * used \a Kross::Api::ScriptContainer instance.
+ */
+ bool loadData();
+};
+
+#endif
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscripteditor.cpp b/kexi/plugins/scripting/kexiscripting/kexiscripteditor.cpp
new file mode 100644
index 000000000..a638af36b
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscripteditor.cpp
@@ -0,0 +1,104 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Sebastian Sauer <mail@dipe.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiscripteditor.h"
+
+#include <kross/main/scriptaction.h>
+
+#include <kdebug.h>
+//#include <kparts/factory.h>
+//#include <klibloader.h>
+//#include <kmdimainfrm.h>
+//#include <kmainwindow.h>
+#include <kpopupmenu.h>
+
+#include <kexidialogbase.h>
+
+/// \internal d-pointer class
+class KexiScriptEditor::Private
+{
+ public:
+ Kross::Api::ScriptAction* scriptaction;
+ Private() : scriptaction(0) {}
+};
+
+KexiScriptEditor::KexiScriptEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : KexiEditor(mainWin, parent, name)
+ , d( new Private() )
+{
+}
+
+KexiScriptEditor::~KexiScriptEditor()
+{
+ delete d;
+}
+
+bool KexiScriptEditor::isInitialized() const
+{
+ return d->scriptaction != 0;
+}
+
+void KexiScriptEditor::initialize(Kross::Api::ScriptAction* scriptaction)
+{
+ d->scriptaction = scriptaction;
+ Q_ASSERT(d->scriptaction);
+
+ disconnect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
+
+ QString code = d->scriptaction->getCode();
+ if(code.isNull()) {
+ // If there is no code we just add some information.
+///@todo remove after release
+ code = "# " + QStringList::split("\n", i18n(
+ "This note will appear for a user in the script's source code "
+ "as a comment. Keep every row not longer than 60 characters and use '\n.'",
+
+ "This is Technology Preview (BETA) version of scripting\n"
+ "support in Kexi. The scripting API may change in details\n"
+ "in the next Kexi version.\n"
+ "For more information and documentation see\n%1"
+ ).arg("http://www.kexi-project.org/scripting/"), true).join("\n# ") + "\n";
+ }
+ KexiEditor::setText(code);
+ // We assume Kross and the HighlightingInterface are using same
+ // names for the support languages...
+ setHighlightMode(d->scriptaction->getInterpreterName());
+
+ clearUndoRedo();
+ KexiEditor::setDirty(false);
+ connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
+}
+
+void KexiScriptEditor::slotTextChanged()
+{
+ KexiScriptEditor::setDirty(true);
+ if(d->scriptaction)
+ d->scriptaction->setCode( KexiEditor::text() );
+}
+
+void KexiScriptEditor::setLineNo(long lineno)
+{
+ setCursorPosition(lineno, 0);
+}
+
+#include "kexiscripteditor.moc"
+
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscripteditor.h b/kexi/plugins/scripting/kexiscripting/kexiscripteditor.h
new file mode 100644
index 000000000..1ef02ff92
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscripteditor.h
@@ -0,0 +1,77 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Sebastian Sauer <mail@dipe.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISCRIPTEDITOR_H
+#define KEXISCRIPTEDITOR_H
+
+#include <kexieditor.h>
+
+namespace Kross { namespace Api {
+ class ScriptAction;
+}}
+
+/**
+ * The KexiEditor class embeds text editor
+ * for editing scripting code.
+ */
+class KexiScriptEditor : public KexiEditor
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ */
+ KexiScriptEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiScriptEditor();
+
+ /**
+ * \returns true if this editor is already initialized (\a initialize was
+ * called) else false is returned.
+ */
+ bool isInitialized() const;
+
+ /**
+ * Initializes the editor. Call this if you like to start
+ * with a clear editor instance. Thinks like the language
+ * highlighter will be reset, undo/redo are cleared and
+ * setDirty(false) is set.
+ */
+ void initialize(Kross::Api::ScriptAction* scriptaction);
+
+ public slots:
+ void slotTextChanged();
+ void setLineNo(long);
+
+ private:
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+};
+
+#endif
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscripthandler.desktop b/kexi/plugins/scripting/kexiscripting/kexiscripthandler.desktop
new file mode 100644
index 000000000..66e5f9fb7
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscripthandler.desktop
@@ -0,0 +1,105 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+GenericName=Scripts
+GenericName[bg]=Скриптове
+GenericName[br]=Urzhiaouegoù
+GenericName[ca]=Seqüències
+GenericName[cs]=Skripty
+GenericName[cy]=Sgriptiau
+GenericName[da]=Scripter
+GenericName[de]=Skripte
+GenericName[el]=Σενάρια
+GenericName[eo]=Skriptoj
+GenericName[es]=Guiones
+GenericName[et]=Skriptid
+GenericName[eu]=Script-ak
+GenericName[fa]=دست‌نوشته‌ها
+GenericName[fi]=Skriptit
+GenericName[fy]=Skripts
+GenericName[ga]=Scripteanna
+GenericName[gl]=Programas
+GenericName[he]=תסריטים
+GenericName[hr]=Skripte
+GenericName[hu]=Szkriptek
+GenericName[is]=Skriftur
+GenericName[it]=Script
+GenericName[ja]=スクリプト
+GenericName[km]=ស្គ្រីប​
+GenericName[lt]=Scenarijai
+GenericName[lv]=Skripti
+GenericName[ms]=Skrip
+GenericName[nb]=Skript
+GenericName[nds]=Skripten
+GenericName[ne]=स्क्रिप्टहरू
+GenericName[nn]=Skript
+GenericName[pl]=Skrypty
+GenericName[pt]=Programas
+GenericName[ru]=Сценарии
+GenericName[se]=Skriptat
+GenericName[sk]=Skripty
+GenericName[sl]=Skripti
+GenericName[sr]=Скрипте
+GenericName[sr@Latn]=Skripte
+GenericName[sv]=Skript
+GenericName[uk]=Скрипти
+GenericName[uz]=Skriptlar
+GenericName[uz@cyrillic]=Скриптлар
+GenericName[zh_CN]=脚本
+GenericName[zh_TW]=命令稿
+Name=Scripts
+Name[bg]=Скриптове
+Name[br]=Urzhiaouegoù
+Name[ca]=Seqüències
+Name[cs]=Skripty
+Name[cy]=Sgriptiau
+Name[da]=Scripter
+Name[de]=Skripte
+Name[el]=Σενάρια
+Name[eo]=Skriptoj
+Name[es]=Guiones
+Name[et]=Skriptid
+Name[eu]=Script-ak
+Name[fa]=دست‌نوشته‌ها
+Name[fi]=Skriptit
+Name[fy]=Skripts
+Name[ga]=Scripteanna
+Name[gl]=Programas
+Name[he]=תסריטים
+Name[hr]=Skripte
+Name[hu]=Szkriptek
+Name[is]=Skriftur
+Name[it]=Script
+Name[ja]=スクリプト
+Name[km]=ស្គ្រីប​
+Name[lt]=Scenarijai
+Name[lv]=Skripti
+Name[ms]=Skrip
+Name[nb]=Skript
+Name[nds]=Skripten
+Name[ne]=स्क्रिप्टहरू
+Name[nn]=Skript
+Name[pl]=Skrypty
+Name[pt]=Programas
+Name[ru]=Сценарии
+Name[se]=Skriptat
+Name[sk]=Skripty
+Name[sl]=Skripti
+Name[sr]=Скрипте
+Name[sr@Latn]=Skripte
+Name[sv]=Skript
+Name[uk]=Скрипти
+Name[uz]=Skriptlar
+Name[uz@cyrillic]=Скриптлар
+Name[zh_CN]=脚本
+Name[zh_TW]=命令稿
+X-KDE-Library=kexihandler_script
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=script
+X-Kexi-TypeMime=kexi/script
+X-Kexi-ItemIcon=script
+X-Kexi-SupportsExecution=true
+X-Kexi-SupportsDataExport=false
+X-Kexi-SupportsPrinting=false
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptpart.cpp b/kexi/plugins/scripting/kexiscripting/kexiscriptpart.cpp
new file mode 100644
index 000000000..d650e9583
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscriptpart.cpp
@@ -0,0 +1,201 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiscriptpart.h"
+#include "kexiscriptdesignview.h"
+
+#include "kexiviewbase.h"
+#include "keximainwindow.h"
+#include "kexiproject.h"
+
+#include <kross/main/manager.h>
+#include <kross/main/scriptaction.h>
+#include <kross/main/scriptguiclient.h>
+
+#include <kgenericfactory.h>
+#include <kexipartitem.h>
+#include <kxmlguiclient.h>
+#include <kexidialogbase.h>
+#include <kconfig.h>
+#include <kdebug.h>
+
+/// \internal
+class KexiScriptPart::Private
+{
+ public:
+ Kross::Api::ScriptGUIClient* scriptguiclient;
+};
+
+KexiScriptPart::KexiScriptPart(QObject *parent, const char *name, const QStringList &l)
+ : KexiPart::Part(parent, name, l)
+ , d( new Private() )
+{
+ d->scriptguiclient = 0;
+
+ // REGISTERED ID:
+ m_registeredPartID = (int)KexiPart::ScriptObjectType;
+
+ m_names["instanceName"]
+ = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). "
+ "Use '_' character instead of spaces. First character should be a..z character. "
+ "If you cannot use latin characters in your language, use english word.",
+ "script");
+ m_names["instanceCaption"] = i18n("Script");
+ m_supportedViewModes = Kexi::DesignViewMode;
+}
+
+KexiScriptPart::~KexiScriptPart()
+{
+ delete d->scriptguiclient;
+ delete d;
+}
+
+bool KexiScriptPart::execute(KexiPart::Item* item, QObject* sender)
+{
+ Q_UNUSED(sender);
+
+ if(! item) {
+ kdWarning() << "KexiScriptPart::execute: Invalid item." << endl;
+ return false;
+ }
+
+ KexiDialogBase* dialog = new KexiDialogBase(m_mainWin);
+ dialog->setId( item->identifier() );
+ KexiScriptDesignView* view = dynamic_cast<KexiScriptDesignView*>( createView(dialog, dialog, *item, Kexi::DesignViewMode) );
+ if(! view) {
+ kdWarning() << "KexiScriptPart::execute: Failed to create a view." << endl;
+ return false;
+ }
+
+ Kross::Api::ScriptAction* scriptaction = view->scriptAction();
+ if(scriptaction) {
+
+ const QString dontAskAgainName = "askExecuteScript";
+ KConfig* config = KGlobal::config();
+ QString dontask = config->readEntry(dontAskAgainName).lower();
+
+ bool exec = (dontask == "yes");
+ if( !exec && dontask != "no" ) {
+ exec = KMessageBox::warningContinueCancel(0,
+ i18n("Do you want to execute the script \"%1\"?\n\nScripts obtained from unknown sources can contain dangerous code.").arg(scriptaction->text()),
+ i18n("Execute Script?"), KGuiItem(i18n("Execute"), "exec"),
+ dontAskAgainName, KMessageBox::Notify | KMessageBox::Dangerous
+ ) == KMessageBox::Continue;
+ }
+
+ if(exec) {
+ //QTimer::singleShot(10, scriptaction, SLOT(activate()));
+ d->scriptguiclient->executeScriptAction( scriptaction );
+ }
+ }
+
+ view->deleteLater(); // not needed any longer.
+ return true;
+}
+
+void KexiScriptPart::initPartActions()
+{
+ if(m_mainWin) {
+ // At this stage the KexiPart::Part::m_mainWin should be defined, so
+ // that we are able to use it's KXMLGUIClient.
+
+ // Initialize the ScriptGUIClient.
+ d->scriptguiclient = new Kross::Api::ScriptGUIClient( m_mainWin );
+
+ // Publish the KexiMainWindow singelton instance. At least the KexiApp
+ // scripting-plugin depends on this instance and loading the plugin will
+ // fail if it's not avaiable.
+ if(! Kross::Api::Manager::scriptManager()->hasChild("KexiMainWindow")) {
+ Kross::Api::Manager::scriptManager()->addQObject(m_mainWin, "KexiMainWindow");
+
+ // Add the KAction's provided by the ScriptGUIClient to the
+ // KexiMainWindow.
+ //FIXME: fix+use createSharedPartAction() whyever it doesn't work as expected right now...
+ QPopupMenu* popup = m_mainWin->findPopupMenu("tools");
+ if(popup) {
+ KAction* execscriptaction = d->scriptguiclient->action("executescriptfile");
+ if(execscriptaction)
+ execscriptaction->plug( popup );
+ KAction* configscriptaction = d->scriptguiclient->action("configurescripts");
+ if(configscriptaction)
+ configscriptaction->plug( popup );
+ KAction* scriptmenuaction = d->scriptguiclient->action("installedscripts");
+ if(scriptmenuaction)
+ scriptmenuaction->plug( popup );
+ /*
+ KAction* execscriptmenuaction = d->scriptguiclient->action("executedscripts");
+ if(execscriptmenuaction)
+ execscriptmenuaction->plug( popup );
+ KAction* loadedscriptmenuaction = d->scriptguiclient->action("loadedscripts");
+ if(loadedscriptmenuaction)
+ loadedscriptmenuaction->plug( popup );
+ */
+ }
+ }
+ }
+}
+
+void KexiScriptPart::initInstanceActions()
+{
+ //createSharedAction(Kexi::DesignViewMode, i18n("Execute Script"), "player_play", 0, "data_execute");
+ createSharedAction(Kexi::DesignViewMode, i18n("Configure Editor..."), "configure", 0, "script_config_editor");
+}
+
+KexiViewBase* KexiScriptPart::createView(QWidget *parent, KexiDialogBase* dialog, KexiPart::Item& item, int viewMode, QMap<QString,QString>*)
+{
+ QString partname = item.name();
+ if( ! partname.isNull() ) {
+ KexiMainWindow *win = dialog->mainWin();
+ if(!win || !win->project() || !win->project()->dbConnection())
+ return 0;
+
+ Kross::Api::ScriptActionCollection* collection = d->scriptguiclient->getActionCollection("projectscripts");
+ if(! collection) {
+ collection = new Kross::Api::ScriptActionCollection( i18n("Scripts"), d->scriptguiclient->actionCollection(), "projectscripts" );
+ d->scriptguiclient->addActionCollection("projectscripts", collection);
+ }
+
+ const char* name = partname.latin1();
+ Kross::Api::ScriptAction::Ptr scriptaction = collection->action(name);
+ if(! scriptaction) {
+ scriptaction = new Kross::Api::ScriptAction(partname);
+ collection->attach(scriptaction); //TODO remove again on unload!
+ }
+
+ if(viewMode == Kexi::DesignViewMode) {
+ return new KexiScriptDesignView(win, parent, scriptaction);
+ }
+ }
+ return 0;
+}
+
+QString KexiScriptPart::i18nMessage(const QCString& englishMessage) const
+{
+ if (englishMessage=="Design of object \"%1\" has been modified.")
+ return i18n("Design of script \"%1\" has been modified.");
+ if (englishMessage=="Object \"%1\" already exists.")
+ return i18n("Script \"%1\" already exists.");
+ return englishMessage;
+}
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_script, KGenericFactory<KexiScriptPart>("kexihandler_script") )
+
+#include "kexiscriptpart.moc"
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptpart.h b/kexi/plugins/scripting/kexiscripting/kexiscriptpart.h
new file mode 100644
index 000000000..ddba0d723
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscriptpart.h
@@ -0,0 +1,100 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2005 Sebastian Sauer <mail@dipe.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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISCRIPTPART_H
+#define KEXISCRIPTPART_H
+
+#include <qdom.h>
+#include <qcstring.h>
+
+#include <kexi.h>
+#include <kexipart.h>
+#include <kexidialogbase.h>
+
+/**
+ * Kexi Scripting Plugin.
+ */
+class KexiScriptPart : public KexiPart::Part
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ *
+ * \param parent The parent QObject this part is child of.
+ * \param name The name this part has.
+ * \param args Optional list of arguments passed to this part.
+ */
+ KexiScriptPart(QObject *parent, const char *name, const QStringList& args);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiScriptPart();
+
+ /**
+ * Implementation of the \a KexiPart::Part::execute method used to
+ * execute the passed \p item instance.
+ */
+ virtual bool execute(KexiPart::Item* item, QObject* sender = 0);
+
+ /**
+ * \return the i18n message for the passed \p englishMessage string.
+ */
+ virtual QString i18nMessage(const QCString& englishMessage) const;
+
+ protected:
+
+ /**
+ * Create a new view.
+ *
+ * \param parent The parent QWidget the new view is displayed in.
+ * \param dialog The \a KexiDialogBase the view is child of.
+ * \param item The \a KexiPart::Item this view is for.
+ * \param viewMode The viewmode we like to have a view for.
+ */
+ virtual KexiViewBase* createView(QWidget *parent,
+ KexiDialogBase* dialog,
+ KexiPart::Item& item,
+ int viewMode = Kexi::DesignViewMode,
+ QMap<QString,QString>* staticObjectArgs = 0);
+
+ /**
+ * Initialize the part's actions.
+ */
+ virtual void initPartActions();
+
+ /**
+ * Initialize the instance actions.
+ */
+ virtual void initInstanceActions();
+
+ private:
+ /// \internal d-pointer class.
+ class Private;
+ /// \internal d-pointer instance.
+ Private* const d;
+};
+
+#endif
+
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptpartinstui.rc b/kexi/plugins/scripting/kexiscripting/kexiscriptpartinstui.rc
new file mode 100644
index 000000000..16124c345
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscriptpartinstui.rc
@@ -0,0 +1,10 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexiscriptpartinst" version="14">
+
+ <MenuBar>
+ <Menu name="settings" noMerge="1">
+ <Action name="script_config_editor"/>
+ </Menu>
+ </MenuBar>
+
+</kpartgui>
diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptpartui.rc b/kexi/plugins/scripting/kexiscripting/kexiscriptpartui.rc
new file mode 100644
index 000000000..171d643f1
--- /dev/null
+++ b/kexi/plugins/scripting/kexiscripting/kexiscriptpartui.rc
@@ -0,0 +1,10 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexiscriptpart" version="10">
+
+ <MenuBar>
+ <Menu name="settings" noMerge="1">
+ <Action name="script_config_editor"/>
+ </Menu>
+ </MenuBar>
+
+</kpartgui>
diff --git a/kexi/plugins/scripting/scripts/Makefile.am b/kexi/plugins/scripting/scripts/Makefile.am
new file mode 100644
index 000000000..b63eedeee
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = exportxhtml importxhtml projectdocumentor copycenter python
diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py
new file mode 100644
index 000000000..3718512fe
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py
@@ -0,0 +1,644 @@
+"""
+Copy Center
+
+Description:
+Python script to copy data between different datastores.
+
+Author:
+Sebastian Sauer <mail@dipe.org>
+
+Copyright:
+Dual-licensed under LGPL v2+higher and the BSD license.
+"""
+
+class CopyCenter:
+
+ class Plugin:
+ def __init__(self, plugin):
+ self.plugin = plugin
+ self.name = plugin.name
+ self.source = self.load("Source")
+ self.destination = self.load("Destination")
+
+ def load(self, plugintype):
+ instance = None
+ try:
+ if hasattr(self.plugin, plugintype):
+ return getattr(self.plugin, plugintype)(self.plugin)
+ except:
+ import traceback
+ print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )
+ return None
+
+ def __init__(self, scriptpath):
+ self.scriptpath = scriptpath
+ self.homepath = self.getHomePath()
+ self.plugins = {}
+
+ import os
+ import sys
+ if not os.path.exists(scriptpath):
+ print "The Path %s does not exist" % scriptpath
+ else:
+ import re
+ regexp = re.compile('^CopyCenterPlugin(.*)\\.py$')
+ for f in os.listdir(scriptpath):
+ file = os.path.join(scriptpath, f)
+ if not os.path.isfile(file): continue
+ m = regexp.match(f)
+ if not m: continue
+ print "Plugin name=%s file=%s" % (m.group(1),file)
+ mylocals = {}
+ try:
+ execfile(file, globals(), mylocals)
+ if mylocals.has_key("CopyCenterPlugin"):
+ plugin = mylocals.get("CopyCenterPlugin")(self)
+ self.plugins[plugin.name] = self.Plugin(plugin)
+ except:
+ print "Failed to import file=%s" % file
+ import traceback
+ print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )
+
+ def getHomePath(self):
+ """ Return the homedirectory. """
+ import os
+ try:
+ home = os.getenv("HOME")
+ if not home:
+ import pwd
+ user = os.getenv("USER") or os.getenv("LOGNAME")
+ if not user:
+ pwent = pwd.getpwuid(os.getuid())
+ else:
+ pwent = pwd.getpwnam(user)
+ home = pwent[6]
+ return home
+ except (KeyError, ImportError):
+ return os.curdir
+
+class Copierer:
+ def __init__(self): pass
+ def appendProgressMessage(self,messagetext): pass
+ def writeSuccess(self,record,rowcount): pass
+ def writeFailed(self,record): pass
+
+def runGuiApp(copycenter, name):
+ import qt
+ import sys
+
+ #--------------------------------------------------------------------
+
+ class ListViewDialog(qt.QDialog):
+ def __init__(self, parent, caption):
+ qt.QDialog.__init__(self, parent, "ProgressDialog", 1)
+ self.parent = parent
+ self.setCaption(caption)
+ layout = qt.QVBoxLayout(self)
+ box = qt.QVBox(self)
+ box.setMargin(2)
+ layout.addWidget(box)
+ self.listview = qt.QListView(box)
+ self.listview.setAllColumnsShowFocus(True)
+ self.listview.header().setStretchEnabled(True,0)
+ btnbox = qt.QHBox(box)
+ btnbox.setMargin(6)
+ btnbox.setSpacing(6)
+ self.okbtn = qt.QPushButton(btnbox)
+ self.okbtn.setText("Ok")
+ #qt.QObject.connect(okbtn, qt.SIGNAL("clicked()"), self.okClicked)
+ self.cancelbtn = qt.QPushButton(btnbox)
+ self.cancelbtn.setText("Cancel")
+ qt.QObject.connect(self.cancelbtn, qt.SIGNAL("clicked()"), self.close)
+ box.setMinimumSize(qt.QSize(460,380))
+ def addItem(self,valuelist,afteritem = None):
+ if afteritem == None:
+ item = qt.QListViewItem(self.listview)
+ else:
+ item = qt.QListViewItem(self.listview,afteritem)
+ i = 0
+ for value in valuelist:
+ item.setText(i,value)
+ i += 1
+ return item
+
+ #--------------------------------------------------------------------
+
+ class CopyJobWidget(qt.QVBox):
+ def __init__(self,dialog,parent):
+ self.dialog = dialog
+ qt.QVBox.__init__(self,parent)
+ self.setSpacing(6)
+ typebox = qt.QHBox(self)
+ typebox.setSpacing(6)
+ label = qt.QLabel("Job File:",typebox)
+ self.jobfilecombobox = qt.QComboBox(typebox)
+ typebox.setStretchFactor(self.jobfilecombobox,1)
+ self.jobfilecombobox.setEditable(True)
+ self.jobfilecombobox.insertItem("")
+ label.setBuddy(self.jobfilecombobox)
+ qt.QObject.connect(self.jobfilecombobox, qt.SIGNAL("textChanged(const QString&)"), self.jobfilecomboboxChanged)
+
+ import os
+ import re
+ for f in os.listdir(self.dialog.copycenter.homepath):
+ file = os.path.join(self.dialog.copycenter.homepath,f)
+ if os.path.isfile(file) and re.search(".+\\.copycenterjob.xml$",f):
+ self.jobfilecombobox.insertItem(file)
+
+ loadbtn = qt.QPushButton(typebox)
+ loadbtn.setText("Open...")
+ qt.QObject.connect(loadbtn, qt.SIGNAL("clicked()"), self.openClicked)
+ savebtn = qt.QPushButton(typebox)
+ savebtn.setText("Save...")
+ qt.QObject.connect(savebtn, qt.SIGNAL("clicked()"), self.saveClicked)
+
+ self.listview = qt.QListView(self)
+ self.listview.setAllColumnsShowFocus(True)
+ self.listview.setSorting(-1)
+ self.listview.setDefaultRenameAction(qt.QListView.Reject)
+ self.listview.header().setClickEnabled(False)
+ self.listview.addColumn("Name")
+ self.listview.addColumn("Value")
+ qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.doubleClicked)
+ #qt.QObject.connect(self.listview, qt.SIGNAL("itemRenamed(QListViewItem*, int, const QString&)"), self.itemRenamed)
+
+ def doubleClicked(self, **args):
+ print "CopyJobWidget.doubleClicked"
+ item = self.listview.selectedItem()
+ if item and item.parent(): item.startRename(1)
+
+ def readOptions(self,domnode,plugininst):
+ print "CopyJobWidget.readOptions plugintype=\"%s\"" % plugininst.plugintype
+ for node in domnode.childNodes:
+ if node.nodeType == node.ELEMENT_NODE:
+ v = node.getAttribute("value")
+ plugininst.options[node.nodeName] = v
+ print "Option \"%s\" has value \"%s\" now." % (node.nodeName, v)
+
+ def jobfilecomboboxChanged(self, **args):
+ print "CopyJobWidget.jobfilecomboboxChanged"
+ import os
+ import xml.dom.minidom
+ filename = str(self.jobfilecombobox.currentText())
+ if not os.path.isfile(filename): return
+ domdoc = xml.dom.minidom.parse(filename)
+ try:
+ elements = domdoc.getElementsByTagName("CopyCenterJob")[0]
+ sourcenode = elements.getElementsByTagName("Source")[0]
+ destinationnode = elements.getElementsByTagName("Destination")[0]
+ except:
+ raise "The XML-file \"%s\" does not contain a valid copy-job." % filename
+
+ sourcepluginname = str(sourcenode.getAttribute('plugin'))
+ if not self.dialog.sourcedata.combobox.listBox().findItem(sourcepluginname,qt.Qt.ExactMatch):
+ raise "There exists no plugin with the name \"%s\"." % sourcepluginname
+ self.dialog.sourcedata.combobox.setCurrentText(sourcepluginname)
+
+ destinationpluginname = str(destinationnode.getAttribute('plugin'))
+ if not self.dialog.destinationdata.combobox.listBox().findItem(destinationpluginname,qt.Qt.ExactMatch):
+ raise "There exists no plugin with the name \"%s\"." % destinationpluginname
+ self.dialog.destinationdata.combobox.setCurrentText(destinationpluginname)
+
+ self.readOptions(sourcenode,self.dialog.getSourcePluginImpl())
+ self.readOptions(destinationnode,self.dialog.getDestinationPluginImpl())
+ self.maybeUpdate()
+
+ def openClicked(self):
+ text = str(self.jobfilecombobox.currentText())
+ if text == "": text = self.dialog.copycenter.homepath
+ filename = str(qt.QFileDialog.getOpenFileName(text,"*.copycenterjob.xml;;*",self.dialog))
+ if filename != "": self.jobfilecombobox.setCurrentText(filename)
+
+ def escape(self,s):
+ return s.replace("&", "&amp;").replace("'", "&apos;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
+
+ def writeOptions(self,writer,pluginname,plugininst):
+ print "CopyJobWidget.writeOptions"
+ writer.write("<%s plugin=\"%s\">\n" % (plugininst.plugintype, pluginname))
+ for optionname in plugininst.options:
+ value = self.escape( unicode(plugininst.options[optionname]).encode("utf-8") )
+ writer.write("\t<%s value=\"%s\" />\n" % (optionname,value))
+ writer.write("</%s>\n" % plugininst.plugintype)
+
+ def saveClicked(self):
+ text = str(self.jobfilecombobox.currentText())
+ if text == "":
+ import os
+ text = os.path.join(self.dialog.copycenter.homepath,"default.copycenterjob.xml")
+ filename = str(qt.QFileDialog.getSaveFileName(text,"*.copycenterjob.xml;;*",self.dialog))
+ if str(filename) == "": return
+ f = open(filename, "w")
+ f.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+ f.write("<CopyCenterJob>\n")
+ sourcepluginname = self.dialog.sourcedata.combobox.currentText()
+ self.writeOptions(f, sourcepluginname, self.dialog.getSourcePluginImpl())
+ destinationpluginname = self.dialog.destinationdata.combobox.currentText()
+ self.writeOptions(f, destinationpluginname, self.dialog.getDestinationPluginImpl())
+ f.write("</CopyCenterJob>\n")
+ f.close()
+ print "File \%s\" successfully written." % filename
+
+ def addItem(self, pluginimpl, afteritem = None, parentitem = None):
+ #print "CopyJobWidget.addItem"
+ class ListViewItem(qt.QListViewItem):
+ def __init__(self, pluginimpl, listview, parentitem = None, afteritem = None):
+ self.pluginimpl = pluginimpl
+ if parentitem == None:
+ qt.QListViewItem.__init__(self,listview)
+ self.setOpen(True)
+ else:
+ if afteritem == None:
+ qt.QListViewItem.__init__(self,parentitem)
+ else:
+ qt.QListViewItem.__init__(self,parentitem,afteritem)
+ self.setRenameEnabled(1,True)
+ def startRename(self, columnindex):
+ qt.QListViewItem.startRename(self,columnindex)
+ #lineedit = self.listView().viewport().child("qt_renamebox")
+ #if lineedit:
+ # regexp = qt.QRegExp("^[_A-Z]+[_A-Z0-9]*$", False)
+ # v = qt.QRegExpValidator(regexp, self.listView());
+ # lineedit.setValidator(v)
+ def okRename(self, columnindex):
+ if columnindex == 1:
+ n = str(self.text(0))
+ if not self.pluginimpl.options.has_key(n):
+ raise "No such option \"%s\"" % n
+ qt.QListViewItem.okRename(self,columnindex)
+ v = str(qt.QListViewItem.text(self,1))
+ print "Option \"%s\" has value \"%s\" now." % (n,v)
+ self.pluginimpl.options[n] = v
+
+ def text(self, columnindex):
+ if columnindex == 1:
+ if qt.QListViewItem.text(self,0).contains("password"):
+ return "*" * len(str(qt.QListViewItem.text(self,1)))
+ return qt.QListViewItem.text(self,columnindex)
+ return ListViewItem(pluginimpl, self.listview, parentitem, afteritem)
+
+ def updateItem(self,pluginname,pluginimpl):
+ #print "CopyJobWidget.updateItem"
+ if pluginimpl == None: return
+ #plugin = self.dialog.plugins[pluginname]
+ item = self.addItem(pluginimpl)
+ item.setText(0,"%s: %s" % (pluginimpl.plugintype, pluginname))
+ afteritem = None
+ for i in pluginimpl.options:
+ afteritem = self.addItem(pluginimpl, afteritem, item)
+ afteritem.setText(0,str(i))
+ afteritem.setText(1,str(pluginimpl.options[i]))
+ print "CopyJobWidget.updateItem Added item with name \"%s\" and value \"%s\"" % (str(i),str(pluginimpl.options[i]))
+ pass
+
+ def maybeUpdate(self):
+ print "CopyJobWidget.maybeUpdate"
+ self.listview.clear()
+ try:
+ self.updateItem(self.dialog.getDestinationPluginName(), self.dialog.getDestinationPluginImpl())
+ self.updateItem(self.dialog.getSourcePluginName(), self.dialog.getSourcePluginImpl())
+ except:
+ import traceback
+ print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )
+ self.listview.clear()
+
+ #--------------------------------------------------------------------
+
+ class ProgressDialog(qt.QDialog):
+ def __init__(self, dialog):
+ self.dialog = dialog
+ self.starttime = None
+ qt.QDialog.__init__(self, dialog, "ProgressDialog", 1)
+ self.setCaption("Copying...")
+ layout = qt.QVBoxLayout(self)
+ box = qt.QVBox(self)
+ box.setSpacing(6)
+ box.setMargin(6)
+ layout.addWidget(box)
+ self.textbrowser = qt.QTextBrowser(box)
+ self.textbrowser.setWordWrap(qt.QTextEdit.WidgetWidth)
+ self.textbrowser.setTextFormat(qt.Qt.RichText)
+ statusbox = qt.QFrame(box)
+ layout = qt.QGridLayout(statusbox,4,2,0,2)
+ layout.addWidget(qt.QLabel("Number of records done:",statusbox),0,0)
+ self.donecounter = 0
+ self.donelabel = qt.QLabel("-",statusbox)
+ layout.addWidget(self.donelabel,0,1)
+ layout.addWidget(qt.QLabel("Successfully copied records:",statusbox),1,0)
+ self.successcounter = 0
+ self.successlabel = qt.QLabel("-",statusbox)
+ layout.addWidget(self.successlabel,1,1)
+ layout.addWidget(qt.QLabel("Failed to copy records:",statusbox),2,0)
+ self.failedcounter = 0
+ self.failedlabel = qt.QLabel("-",statusbox)
+ layout.addWidget(self.failedlabel,2,1)
+ layout.addWidget(qt.QLabel("Elapsed time in seconds:",statusbox),3,0)
+ self.elapsedlabel = qt.QLabel("-",statusbox)
+ layout.addWidget(self.elapsedlabel,3,1)
+ btnbox = qt.QHBox(box)
+ btnbox.setSpacing(6)
+ self.donebtn = qt.QPushButton(btnbox)
+ self.donebtn.setText("Done")
+ self.donebtn.setEnabled(False)
+ qt.QObject.connect(self.donebtn,qt.SIGNAL("clicked()"),self.close)
+ self.cancelbtn = qt.QPushButton(btnbox)
+ self.cancelbtn.setText("Cancel")
+ qt.QObject.connect(self.cancelbtn,qt.SIGNAL("clicked()"),self.close)
+ box.setMinimumSize( qt.QSize(500,380) )
+
+ def updateStates(self):
+ if self.starttime != None:
+ self.donelabel.setText(str(self.donecounter))
+ self.failedlabel.setText(str(self.failedcounter))
+ self.successlabel.setText(str(self.successcounter))
+ self.elapsedlabel.setText( str(self.starttime.elapsed() / 1000) )
+ self.donelabel.update()
+ self.failedlabel.update()
+ self.successlabel.update()
+ self.elapsedlabel.update()
+
+ def writeSuccess(self, record, rowcount):
+ self.donecounter += rowcount
+ self.successcounter += rowcount
+ qt.qApp.processEvents()
+ def writeFailed(self, record):
+ self.donecounter += 1
+ self.failedcounter += 1
+ qt.qApp.processEvents()
+
+ def startCopy(self):
+ try:
+ global Copierer
+ copierer = Copierer()
+ copierer.appendProgressMessage = self.textbrowser.append
+ copierer.writeSuccess = self.writeSuccess
+ copierer.writeFailed = self.writeFailed
+
+ self.starttime = qt.QTime()
+ self.updatetimer = qt.QTimer(self)
+ qt.QObject.connect(self.updatetimer,qt.SIGNAL("timeout()"),self.updateStates)
+
+ # Initialize the source
+ sourcename = self.dialog.getSourcePluginName()
+ sourceimpl = self.dialog.getSourcePluginImpl()
+ self.textbrowser.append("Source: %s" % sourcename)
+ if sourceimpl == None:
+ raise "No such source."
+ try:
+ sourceimpl.init(copierer)
+
+ # Initialize the destination
+ destinationname = self.dialog.getDestinationPluginName()
+ destinationimpl = self.dialog.getDestinationPluginImpl()
+ self.textbrowser.append("<hr>Destination: %s" % destinationname)
+ if destinationimpl == None:
+ raise "No such destination."
+ try:
+ destinationimpl.init(copierer)
+
+ self.starttime.start()
+ self.updatetimer.start(500)
+ qt.qApp.processEvents()
+
+ # Copy the records
+ self.textbrowser.append("<hr><i>Copy the records...</i>")
+ while True:
+ record = sourceimpl.read()
+ if record == None: break
+ destinationimpl.write(record)
+
+ self.updateStates()
+ finally:
+ destinationimpl.finish()
+ finally:
+ sourceimpl.finish()
+
+ self.setCaption("Copy done")
+ self.textbrowser.append("<hr><b>Copy done.</b>")
+ except:
+ self.setCaption("Copy failed")
+ self.textbrowser.append("<b>Error: %s</b>" % sys.exc_info()[0])
+ import traceback
+ print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )
+ #self.progressbar.setEnabled(False)
+ self.donebtn.setEnabled(True)
+ self.cancelbtn.setEnabled(False)
+ self.updatetimer.stop()
+ self.starttime = None
+
+ def show(self):
+ qt.QDialog.show(self)
+ qt.QTimer.singleShot(10,self.startCopy)
+ qt.qApp.processEvents()
+
+ def closeEvent(self, closeevent):
+ if not self.dialog.getSourcePluginImpl().isFinished():
+ if qt.QMessageBox.warning(self,"Abort?","Abort the copy?",qt.QMessageBox.Yes,qt.QMessageBox.No) != qt.QMessageBox.Yes:
+ closeevent.ignore()
+ return
+ self.dialog.getSourcePluginImpl().finish()
+ self.dialog.getDestinationPluginImpl().finish()
+ closeevent.accept()
+
+ #--------------------------------------------------------------------
+
+ class DataSelector(qt.QVGroupBox):
+ def __init__(self, plugintype, title, caption, parent, dialog, items):
+ self.plugintype = plugintype
+ self.pluginimpl = None
+ self.dialog = dialog
+ self.mainbox = None
+
+ qt.QVGroupBox.__init__(self,title,parent)
+ self.setInsideMargin(6)
+ self.setInsideSpacing(0)
+
+ typebox = qt.QHBox(self)
+ label = qt.QLabel(caption,typebox)
+ self.combobox = qt.QComboBox(typebox)
+ for item in items:
+ self.combobox.insertItem(str(item))
+ label.setBuddy(self.combobox)
+ typebox.setStretchFactor(self.combobox,1)
+
+ self.scrollview = qt.QScrollView(self)
+ try:
+ self.scrollview.setResizePolicy(qt.QScrollView.AutoOne)
+ self.scrollview.setFrameStyle(qt.QFrame.NoFrame);
+ self.scrollview.setResizePolicy(qt.QScrollView.AutoOneFit);
+ self.scrollview.viewport().setPaletteBackgroundColor(self.paletteBackgroundColor())
+ except:
+ import traceback
+ print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )
+ qt.QObject.connect(self.combobox, qt.SIGNAL("activated(int)"), self.activated)
+
+ def updatePlugin(self):
+ print "DataSelector.updatePlugin"
+ self.pluginimpl = None
+ text = str(self.combobox.currentText())
+ plugin = self.dialog.copycenter.plugins[text]
+ self.pluginimpl = getattr(plugin, self.plugintype)
+
+ def removeMainBox(self):
+ if self.mainbox == None: return
+ try:
+ self.scrollview.removeChild(self.mainbox)
+ self.mainbox.destroy()
+ except:
+ pass
+ self.mainbox = None
+
+ def updateMainBox(self):
+ print "DataSelector.updateMainBox"
+ self.removeMainBox()
+ self.mainbox = qt.QVBox( self.scrollview.viewport() )
+ self.mainbox.setSpacing(2)
+ if self.pluginimpl != None:
+ try:
+ self.pluginimpl.createWidget(self.dialog, self.mainbox)
+ except:
+ import traceback
+ print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )
+ self.mainbox.setStretchFactor(qt.QWidget(self.mainbox), 1)
+ self.mainbox.show()
+ self.scrollview.addChild(self.mainbox)
+
+ def activated(self, **args):
+ self.updatePlugin()
+ self.updateMainBox()
+
+ def maybeUpdate(self):
+ print "DataSelector.maybeUpdate"
+ self.removeMainBox()
+ qt.QTimer.singleShot(50, self.activated)
+
+ def maybeDone(self):
+ print "DataSelector.maybeDone"
+ if self.pluginimpl.widget == None: return
+ for optionname in self.pluginimpl.options:
+ self.pluginimpl.options[optionname] = self.pluginimpl.widget.getOptionValue(optionname)
+
+ #--------------------------------------------------------------------
+
+ class Dialog(qt.QDialog):
+ def __init__(self, copycenter, parent):
+ self.copycenter = copycenter
+
+ import qt
+ import os
+ import sys
+
+ self.ListViewDialog = ListViewDialog
+ qt.QDialog.__init__(self, parent, "Dialog", 1, qt.Qt.WDestructiveClose)
+ self.setCaption("Copy Center")
+ layout = qt.QVBoxLayout(self)
+ box = qt.QVBox(self)
+ box.setMargin(6)
+ box.setSpacing(6)
+ layout.addWidget(box)
+ self.tab = qt.QTabWidget(box)
+ self.tab.setMargin(6)
+ box.setStretchFactor(self.tab,1)
+
+ self.jobsbox = CopyJobWidget(self,self.tab)
+ self.tab.addTab(self.jobsbox,"Jobs")
+
+ self.splitter = qt.QSplitter(self.tab)
+
+ sourceplugins = []
+ destinationplugins = []
+ for pluginname in self.copycenter.plugins:
+ if self.copycenter.plugins[pluginname].source != None:
+ sourceplugins.append(pluginname)
+ if self.copycenter.plugins[pluginname].destination != None:
+ destinationplugins.append(pluginname)
+ sourceplugins.sort()
+ destinationplugins.sort()
+
+ self.sourcedata = DataSelector(
+ "source", # id
+ "Read Data From", # title
+ "Source:", # caption
+ self.splitter, self, sourceplugins)
+ self.destinationdata = DataSelector(
+ "destination", # id
+ "Write Data to", # title
+ "Destination:", # caption
+ self.splitter, self, destinationplugins)
+
+ btnbox = qt.QHBox(box)
+ btnbox.setSpacing(6)
+ okbtn = qt.QPushButton(btnbox)
+ okbtn.setText("Start Copy")
+ okbtn.setDefault(True)
+ qt.QObject.connect(okbtn,qt.SIGNAL("clicked()"),self.startCopy)
+ cancelbtn = qt.QPushButton(btnbox)
+ cancelbtn.setText("Cancel")
+ qt.QObject.connect(cancelbtn,qt.SIGNAL("clicked()"),self.close)
+
+ self.tab.addTab(self.splitter,"Copy")
+ self.tab.setCurrentPage(1)
+
+ self.helpbrowser = qt.QTextBrowser(self.tab)
+ self.helpbrowser.setLinkUnderline(False)
+ self.helpbrowser.setUndoRedoEnabled(False)
+ self.tab.addTab(self.helpbrowser,"Help")
+ qt.QObject.connect(self.tab,qt.SIGNAL("currentChanged(QWidget*)"),self.currentTabChanged)
+
+ box.setMinimumSize( qt.QSize(760,500) )
+
+ defaultfile = os.path.join(self.copycenter.homepath,"default.copycenterjob.xml")
+ if os.path.isfile(defaultfile):
+ print "Reading default copy job file: %s" % defaultfile
+ self.jobsbox.jobfilecombobox.setCurrentText(defaultfile)
+
+ def getSourcePluginName(self):
+ return str(self.sourcedata.combobox.currentText())
+ def getSourcePluginImpl(self):
+ return self.copycenter.plugins[self.getSourcePluginName()].source
+ def getDestinationPluginName(self):
+ return str(self.destinationdata.combobox.currentText())
+ def getDestinationPluginImpl(self):
+ return self.copycenter.plugins[self.getDestinationPluginName()].destination
+
+ def currentTabChanged(self,widget):
+ if self.tab.currentPage() == self.jobsbox:
+ # The "Copy" page is done
+ self.sourcedata.maybeDone()
+ self.destinationdata.maybeDone()
+ # Update the "Jobs" page
+ self.jobsbox.maybeUpdate()
+ elif self.tab.currentPage() == self.splitter:
+ # Update the "Copy" page
+ self.sourcedata.maybeUpdate()
+ self.destinationdata.maybeUpdate()
+ elif self.tab.currentPage() == self.helpbrowser and self.helpbrowser.lines() <= 1:
+ # Update the "Help" page
+ import os
+ file = os.path.join(self.copycenter.scriptpath, "readme.html")
+ if not os.path.isfile(file): return
+ fh = open(file,'r')
+ self.helpbrowser.setText( fh.read() )
+ fh.close()
+
+ def startCopy(self):
+ dlg = ProgressDialog(self)
+ dlg.show()
+
+ #--------------------------------------------------------------------
+
+ if name == "__main__":
+ qtapp = qt.QApplication(sys.argv)
+ else:
+ qtapp = qt.qApp
+ dialog = Dialog(copycenter, qtapp.mainWidget())
+ dialog.exec_loop()
+
+import os
+
+if __name__ == "__main__":
+ scriptpath = os.getcwd()
+else:
+ scriptpath = os.path.dirname(__name__)
+
+copycenter = CopyCenter(scriptpath)
+runGuiApp(copycenter, __name__)
diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc
new file mode 100644
index 000000000..e3e758b4c
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc
@@ -0,0 +1,10 @@
+<KrossScripting>
+ <ScriptAction
+ name="CopyCenter"
+ version="1"
+ text="Copy Center"
+ description="Copy data from one source to another."
+ icon="editcopy"
+ interpreter="python"
+ file="CopyCenter.py" />
+</KrossScripting>
diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py
new file mode 100644
index 000000000..e82414050
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py
@@ -0,0 +1,646 @@
+"""
+CopyCenterPlugin to provide 'KexiDB'.
+
+Description:
+This python-script is a plugin for the CopyCenter.py.
+
+Author:
+Sebastian Sauer <mail@dipe.org>
+
+Copyright:
+GPL v2 or higher.
+"""
+
+class CopyCenterPlugin:
+ """ The CopyCenterPlugin to provide abstract access to the 'KexiDB'
+ framework to CopyCenter.py """
+
+ name = "Kexi Database"
+ """ The name this plugin has. The name should be unique and
+ will be used for displaying a caption. """
+
+ class Plugin:
+ """ The implementation of a plugin which is published to the
+ CopyCenter.py script. While there exists only one instance of
+ the CopyCenterPlugin class, there will be n instances of this
+ Plugin class (one for 'source' aka read-data-from and one for
+ 'destination' aka write-data-to) created from within the
+ CopyCenter.py. The Source and Destination classes are extending
+ this Plugin class with specialized functionality. """
+ def __init__(self,copycenterplugin):
+ """ Constructor. """
+ self.copycenterplugin = copycenterplugin
+ self.widget = None
+ self.options = {
+ 'autoconnect' : False,
+ 'project' : '', #'~/test.kexi',
+ 'driver' : '', #'MySQL', #'SQLite3','MySQL',...
+ 'file' : '', #'/path/to/mysqlite3dbfile.kexi'
+ 'hostname' : '127.0.0.1',
+ 'port' : '',
+ 'usesocketfile' : False,
+ 'socketfile' : '',
+ 'username' : '',
+ 'password' : '',
+ 'database' : '',
+ 'table' : '',
+ 'fields' : '',
+ 'where' : '',
+ }
+ self.connection = copycenterplugin.Connection(self)
+ def isFinished(self):
+ return self.connection.isFinished()
+ def finish(self):
+ """ Called if reading is finished."""
+ self.connection.finish()
+ def createWidget(self, dialog, parent):
+ """ Create and return a widget to modify the plugin settings. """
+ return self.copycenterplugin.createWidget(dialog, self, parent)
+
+ class Source(Plugin):
+ """ Specialization of the Plugin class to implement the
+ 'source' aka read-data-from functionality. """
+ plugintype = "Source"
+ def init(self,copierer):
+ """ Called if reading should be initialize. """
+ self.connection.init(copierer)
+ self.connection.initRead()
+ # Called if a record should be readed.
+ self.read = self.connection.readRecord
+
+ class Destination(Plugin):
+ """ Specialization of the Plugin class to implement the
+ 'destination' aka write-data-to functionality. """
+ plugintype = "Destination"
+ def init(self,copierer):
+ """ Called if writing should be initialize. """
+ self.connection.init(copierer)
+ self.connection.initWrite()
+ # Called if a record should be written.
+ self.write = self.connection.writeRecord
+
+ class Connection:
+ """ Abstract access to work with KexiDB. """
+ def __init__(self,plugin):
+ self.plugin = plugin
+ self.copierer = None
+ self.kexidbconnection = None
+ self.kexidbcursor = None
+ self.tableschema = None
+ self.fieldlist = None
+ def lastError(self): return self.kexidbconnection.lastError()
+ def connect(self): return self.kexidbconnection.connect()
+ def disconnect(self):
+ if self.kexidbconnection == None or self.kexidbconnection.disconnect():
+ self.kexidbconnection = None
+ return True
+ return False
+ def isConnected(self): return self.kexidbconnection != None and self.kexidbconnection.isConnected()
+ def tableNames(self): return self.kexidbconnection.tableNames()
+ def hasTableName(self,tablename): return tablename in self.kexidbconnection.tableNames()
+ def tableSchema(self,tablename): return self.kexidbconnection.tableSchema(tablename)
+
+ def init(self,copierer):
+ self.copierer = copierer
+ if self.kexidbconnection == None:
+ if self.plugin.widget == None:
+ raise "No connection established."
+ self.copierer.appendProgressMessage("<i>Trying to connect...</i>")
+ if self.plugin.widget.driverbox.driver == None:
+ raise "Invalid driver."
+ if not self.plugin.widget.connectClicked():
+ raise "Failed to connect."
+ connectiondata = self.kexidbconnection.data()
+ self.copierer.appendProgressMessage("Connected: %s %s" % (connectiondata.driverName(),connectiondata.serverInfoString()))
+
+ tablename = str(self.plugin.widget.tablebox.tableedit.text())
+ if tablename == "":
+ raise "No table defined"
+ fields = [ f.strip() for f in str(self.plugin.widget.fieldbox.fieldsedit.text()).split(",") if len(f) > 0 ]
+ if len(fields) < 1:
+ raise "No fields defined"
+
+ self.tableschema = self.kexidbconnection.tableSchema(tablename)
+ if not self.tableschema: raise "No such tableschema \"%s\"" % tablename
+ self.copierer.appendProgressMessage("Table: %s" % self.tableschema.name())
+
+ if len(fields) == 1 and fields[0] == "*":
+ self.fieldlist = self.tableschema.fieldlist()
+ else:
+ self.fieldlist = self.tableschema.fieldlist().subList(fields)
+ if not self.fieldlist: raise "No such fields \"%s\"" % fields
+ fieldlistnames = self.fieldlist.names()
+ if len(fieldlistnames) < 1: raise "No valid fields defined for \"%s\"" % fields
+ self.copierer.appendProgressMessage("Fields: %s" % fieldlistnames)
+ def finish(self):
+ if self.plugin.widget == None:
+ self.disconnect()
+ else:
+ self.plugin.widget.disconnectClicked()
+ self.kexidbcursor = None
+ self.kexidbconnection = None
+ self.tableschema = None
+ self.fieldlist = None
+ self.copierer = None
+ def isFinished(self):
+ return self.copierer == None
+
+ def initRead(self):
+ print "Initialize read"
+ #queryschema = self.plugin.copycenterplugin.drivermanager.querySchema()
+ queryschema = self.tableschema.query()
+ queryschema.fieldlist().setFields(self.fieldlist)
+ print "QuerySchema: %s" % queryschema.fieldlist().names()
+
+ whereexpression = str(self.plugin.widget.whereedit.text())
+ if whereexpression != "":
+ print "WHERE-expression: %s" % whereexpression
+ if not queryschema.setWhereExpression(whereexpression):
+ raise "Invalid WHERE-expression."
+
+ #print "QuerySchema statement=%s" % queryschema.statement()
+ self.kexidbcursor = self.kexidbconnection.executeQuerySchema(queryschema)
+ if not self.kexidbcursor:
+ raise "Failed to create cursor."
+ if not self.kexidbcursor.moveFirst():
+ raise "The cursor has no records to read from."
+
+ def readRecord(self):
+ if self.kexidbcursor == None or self.kexidbcursor.eof():
+ return None
+ record = []
+ for i in range( self.kexidbcursor.fieldCount() ):
+ record.append( self.kexidbcursor.value(i) )
+ self.kexidbcursor.moveNext()
+ #print "read record: %s" % record
+ return record
+
+ def initWrite(self):
+ print "Initialize write"
+
+ def writeRecord(self,record):
+ print "write record: %s" % record
+ if self.kexidbconnection.insertRecord(self.fieldlist,record):
+ print "=> insert successfully"
+ self.copierer.writeSuccess(record, 1)
+ else:
+ print "=> insert failed: %s" % self.kexidbconnection.lastError()
+ self.copierer.writeFailed(record)
+ #import time
+ #time.sleep(1)
+ return True
+
+ def __init__(self, copycenter):
+ """ Constructor. """
+ import krosskexidb
+ self.drivermanager = krosskexidb.DriverManager()
+ self.copycenter = copycenter
+
+ def createWidget(self, dialog, plugin, parent):
+ """ Each plugin may provide a qt.QWidget back to the
+ CopyCenter.py. The widget will be used to configure our
+ plugin settings. """
+
+ import qt
+ import os
+ import re
+
+ self.dialog = dialog
+ self.mainbox = None
+ class ProjectBox(qt.QHBox):
+ def __init__(self,main,copycenterplugin,plugin,parent):
+ self.main = main
+ self.copycenterplugin = copycenterplugin
+ self.plugin = plugin
+
+ qt.QHBox.__init__(self,parent)
+ prjlabel = qt.QLabel("Project File:",self)
+ self.prjcombo = qt.QComboBox(self)
+ self.prjcombo.setEditable(True)
+ self.prjcombo.insertItem("")
+
+ path = copycenterplugin.copycenter.homepath
+ for f in os.listdir(path):
+ file = os.path.join(path,f)
+ if os.path.isfile(file) and re.search(".+\\.(kexi|kexis|kexic)$",f):
+ self.prjcombo.insertItem(os.path.join("~",f))
+
+ prjlabel.setBuddy(self.prjcombo)
+ prjsavebtn = qt.QPushButton("...",self)
+ qt.QObject.connect(prjsavebtn, qt.SIGNAL("clicked()"),self.buttonClicked)
+ qt.QObject.connect(self.prjcombo, qt.SIGNAL("textChanged(const QString&)"), self.main.projectChanged)
+ self.setStretchFactor(self.prjcombo,1)
+ def buttonClicked(self):
+ text = str(self.prjcombo.currentText())
+ if text == "":
+ text = self.copycenterplugin.copycenter.homepath
+ elif re.search("^\\~(\\/|\\\\)",text):
+ import os
+ text = os.path.join(self.copycenterplugin.copycenter.homepath,text[2:])
+ if self.plugin.plugintype == "Source":
+ filename = qt.QFileDialog.getOpenFileName(text,"*.kexi *.kexis *.kexic;;*",self.copycenterplugin.dialog)
+ else: # "Destination":
+ filename = qt.QFileDialog.getSaveFileName(text,"*.kexi *.kexis *.kexic;;*",self.copycenterplugin.dialog)
+ if str(filename) != "": self.prjcombo.setCurrentText(str(filename))
+
+ class DriverBox(qt.QVBox):
+ def __init__(self,main,parent):
+ qt.QVBox.__init__(self,parent)
+ self.main = main
+ self.copycenterplugin = main.copycenterplugin
+ self.plugin = main.plugin
+ self.driver = None
+
+ driverbox = qt.QHBox(self)
+ driverlabel = qt.QLabel("Driver:",driverbox)
+ self.drivercombo = qt.QComboBox(driverbox)
+ self.drivercombo.insertItem("")
+ for driver in self.copycenterplugin.drivermanager.driverNames():
+ self.drivercombo.insertItem(driver)
+
+ qt.QObject.connect(self.drivercombo, qt.SIGNAL("activated(int)"), self.activated)
+ driverlabel.setBuddy(self.drivercombo)
+ driverbox.setStretchFactor(self.drivercombo,1)
+
+ self.box = qt.QVBox(self)
+ self.mainbox = None
+
+ def activated(self,index):
+ drivertext = str(self.drivercombo.currentText())
+
+ self.box.hide()
+ if self.mainbox:
+ self.mainbox.hide()
+ self.mainbox.destroy()
+ self.mainbox = None
+ if index == 0 or drivertext == "":
+ self.driver = None
+ self.box.show()
+ self.main.updateConnectButtons()
+ return False
+
+ self.driver = self.copycenterplugin.drivermanager.driver(drivertext)
+
+ mainbox = qt.QVBox(self.box)
+ mainbox.setSpacing(2)
+
+ if self.driver.isFileDriver():
+ filebox = qt.QHBox(mainbox)
+ filelabel = qt.QLabel("File:",filebox)
+ self.fileedit = qt.QLineEdit(self.plugin.options['file'],filebox)
+ filelabel.setBuddy(self.fileedit)
+ filebox.setStretchFactor(self.fileedit,1)
+ filebtn = qt.QPushButton("...",filebox)
+ qt.QObject.connect(filebtn, qt.SIGNAL("clicked()"), self.fileClicked)
+ else:
+ hostbox = qt.QHBox(mainbox)
+ hostlabel = qt.QLabel("Hostname:",hostbox)
+ self.hostedit = qt.QLineEdit(self.plugin.options['hostname'],hostbox)
+ hostlabel.setBuddy(self.hostedit)
+ hostbox.setStretchFactor(self.hostedit,1)
+
+ portbox = qt.QHBox(mainbox)
+ portlabel = qt.QLabel("Port:",portbox)
+ self.portedit = qt.QLineEdit(self.plugin.options['port'],portbox)
+ portlabel.setBuddy(self.portedit)
+ portbox.setStretchFactor(self.portedit,1)
+
+ sockbox = qt.QHBox(mainbox)
+ self.sockfilecheckbox = qt.QCheckBox("Socket File:",sockbox)
+ qt.QObject.connect(self.sockfilecheckbox, qt.SIGNAL("toggled(bool)"), self.sockfilecheckboxClicked)
+ self.sockfilebox = qt.QHBox(sockbox)
+ self.sockfileedit = qt.QLineEdit(self.plugin.options['socketfile'],self.sockfilebox)
+ self.sockfilebox.setEnabled(False)
+ sockfilebtn = qt.QPushButton("...",self.sockfilebox)
+ self.sockfilecheckbox.setChecked( str(self.plugin.options['usesocketfile']) == str(True) )
+ qt.QObject.connect(sockfilebtn, qt.SIGNAL("clicked()"), self.sockfileClicked)
+ self.sockfilebox.setStretchFactor(self.sockfileedit,1)
+ sockbox.setStretchFactor(self.sockfilebox,1)
+
+ userbox = qt.QHBox(mainbox)
+ userlabel = qt.QLabel("Username:",userbox)
+ self.useredit = qt.QLineEdit(self.plugin.options['username'],userbox)
+ userlabel.setBuddy(self.useredit)
+ userbox.setStretchFactor(self.useredit,1)
+
+ passbox = qt.QHBox(mainbox)
+ passlabel = qt.QLabel("Password:",passbox)
+ self.passedit = qt.QLineEdit(self.plugin.options['password'],passbox)
+ self.passedit.setEchoMode(qt.QLineEdit.Password)
+ passlabel.setBuddy(self.passedit)
+ passbox.setStretchFactor(self.passedit,1)
+
+ dbbox = qt.QHBox(mainbox)
+ dblabel = qt.QLabel("Database:",dbbox)
+ self.dbedit = qt.QLineEdit(self.plugin.options['database'],dbbox)
+ dblabel.setBuddy(self.dbedit)
+ dbbox.setStretchFactor(self.dbedit,1)
+ #self.tablecombo.setText("")
+
+ self.mainbox = mainbox
+ self.mainbox.show()
+ self.box.show()
+ self.main.updateConnectButtons()
+ return True
+
+ def fileClicked(self):
+ text = str(self.fileedit.text())
+ if text == "": text = self.copycenterplugin.copycenter.homepath
+ if self.plugin.plugintype == "Source":
+ filename = qt.QFileDialog.getOpenFileName(text,"*",self.copycenterplugin.dialog)
+ else: # "Destination":
+ filename = qt.QFileDialog.getSaveFileName(text,"*",self.copycenterplugin.dialog)
+ if str(filename) != "": self.fileedit.setText(str(filename))
+ def sockfilecheckboxClicked(self,checked):
+ self.sockfilebox.setEnabled(checked)
+
+ def sockfileClicked(self):
+ text = str(self.sockfileedit.text())
+ if text == "": text = self.copycenterplugin.copycenter.homepath
+ if self.plugin.plugintype == "Source":
+ filename = qt.QFileDialog.getOpenFileName(text,"*",self.copycenterplugin.dialog)
+ else: # "Destination":
+ filename = qt.QFileDialog.getSaveFileName(text,"*",self.copycenterplugin.dialog)
+ if str(filename) != "": self.sockfileedit.setText(str(filename))
+
+ class TableBox(qt.QHBox):
+ def __init__(self,copycenterplugin,plugin,parent):
+ qt.QHBox.__init__(self,parent)
+ self.copycenterplugin = copycenterplugin
+ self.plugin = plugin
+ tablelabel = qt.QLabel("Table:",self)
+ self.tableedit = qt.QLineEdit(self.plugin.options['table'],self)
+ self.tablebtn = qt.QPushButton("...",self)
+ self.tablebtn.setEnabled(False)
+ qt.QObject.connect(self.tablebtn, qt.SIGNAL("clicked()"), self.buttonClicked)
+ tablelabel.setBuddy(self.tableedit)
+ self.setStretchFactor(self.tableedit,1)
+ def buttonClicked(self):
+ ListViewDialog = self.copycenterplugin.dialog.ListViewDialog
+ class TableDialog(ListViewDialog):
+ def __init__(self,tablebox):
+ ListViewDialog.__init__(self,tablebox,"Tables")
+ self.mainwidget = tablebox
+ self.listview.addColumn("Name")
+ text = str(self.mainwidget.tableedit.text())
+ item = None
+ for table in self.mainwidget.plugin.connection.tableNames():
+ if item == None:
+ item = qt.QListViewItem(self.listview,table)
+ else:
+ item = qt.QListViewItem(self.listview,item,table)
+ if table == text:
+ self.listview.setSelected(item,True)
+ self.listview.ensureItemVisible(item)
+ qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.okClicked)
+ qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked)
+ def okClicked(self):
+ item = self.listview.selectedItem()
+ if item == None:
+ self.mainwidget.tableedit.setText("")
+ else:
+ self.mainwidget.tableedit.setText(item.text(0))
+ self.close()
+ dialog = TableDialog(self)
+ dialog.show()
+
+ class FieldBox(qt.QHBox):
+ def __init__(self,copycenterplugin,plugin,parent):
+ qt.QHBox.__init__(self,parent)
+ self.copycenterplugin = copycenterplugin
+ self.plugin = plugin
+ self.tablename = ""
+ fieldslabel = qt.QLabel("Fields:",self)
+ self.fieldsedit = qt.QLineEdit(self.plugin.options['fields'],self)
+ self.setStretchFactor(self.fieldsedit,1)
+ fieldslabel.setBuddy(self.fieldsedit)
+ self.fieldsbtn = qt.QPushButton("...",self)
+ self.fieldsbtn.setEnabled(False)
+ qt.QObject.connect(self.fieldsbtn, qt.SIGNAL("clicked()"), self.fieldsClicked)
+ def fieldsClicked(self):
+ ListViewDialog = self.copycenterplugin.dialog.ListViewDialog
+ class FieldsDialog(ListViewDialog):
+ def __init__(self, fieldbox):
+ ListViewDialog.__init__(self,fieldbox,"Fields")
+ self.fieldbox = fieldbox
+ self.listview.setSelectionMode(qt.QListView.Multi)
+ self.listview.setSorting(-1)
+ self.listview.header().setClickEnabled(False)
+ self.listview.addColumn("Name")
+ self.listview.addColumn("Type")
+ self.listview.addColumn("Options")
+ fieldslist = str(self.fieldbox.fieldsedit.text()).split(",")
+ allfields = ("*" in fieldslist)
+ tableschema = self.fieldbox.plugin.connection.tableSchema(self.fieldbox.tablename)
+ item = None
+ for field in tableschema.fieldlist().fields():
+ opts = []
+ for opt in ("isAutoInc","isNotNull","isNotEmpty"):
+ if getattr(field,opt)():
+ opts.append(opt[2:])
+ item = self.addItem(( field.name(),field.type(),",".join(opts) ),item)
+ if allfields or field.name() in fieldslist:
+ self.listview.setSelected(item,True)
+ qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked)
+ def okClicked(self):
+ selitems = []
+ item = self.listview.firstChild()
+ while item:
+ if item.isSelected():
+ selitems.append(str(item.text(0)))
+ item = item.nextSibling()
+ self.fieldbox.fieldsedit.setText(",".join(selitems))
+ self.close()
+ dialog = FieldsDialog(self)
+ dialog.show()
+ def tableChanged(self, text):
+ self.tablename = str(text)
+ if self.plugin.connection.isConnected():
+ if self.plugin.connection.hasTableName(self.tablename):
+ self.fieldsbtn.setEnabled(True)
+ return
+ self.fieldsbtn.setEnabled(False)
+
+ class MainBox(qt.QHBox):
+ def __init__(self,copycenterplugin,plugin,parent):
+ qt.QHBox.__init__(self,parent)
+ self.copycenterplugin = copycenterplugin
+ self.plugin = plugin
+
+ self.prjbox = ProjectBox(self,copycenterplugin,plugin,parent)
+ self.driverbox = DriverBox(self,parent)
+
+ statusbar = qt.QHBox(parent)
+ statusbar.setSpacing(2)
+ #self.statuslabel = qt.QLabel("Disconnected",statusbar)
+ #statusbar.setStretchFactor(self.statuslabel,1)
+ statusbar.setStretchFactor(qt.QWidget(statusbar),1)
+ self.connectbtn = qt.QPushButton("Connect",statusbar)
+ self.connectbtn.setEnabled(False)
+ qt.QObject.connect(self.connectbtn, qt.SIGNAL("clicked()"),self.connectClicked)
+ self.disconnectbtn = qt.QPushButton("Disconnect",statusbar)
+ self.disconnectbtn.setEnabled(False)
+ qt.QObject.connect(self.disconnectbtn, qt.SIGNAL("clicked()"),self.disconnectClicked)
+
+ #self.connectionbox = ConnectionBox(copycenterplugin,plugin,parent)
+ self.tablebox = TableBox(copycenterplugin,plugin,parent)
+ self.fieldbox = FieldBox(copycenterplugin,plugin,parent)
+ qt.QObject.connect(self.tablebox.tableedit, qt.SIGNAL("textChanged(const QString&)"), self.fieldbox.tableChanged)
+
+ if self.plugin.options['project'] != '':
+ self.prjbox.prjcombo.setCurrentText(self.plugin.options['project'])
+
+ if self.plugin.options['driver'] != '':
+ try:
+ item = str(self.driverbox.drivercombo.listBox().findItem(self.plugin.options['driver'],qt.Qt.ExactMatch).text())
+ self.driverbox.drivercombo.setCurrentText(item)
+ self.driverbox.activated(item)
+ except:
+ pass
+
+ if self.plugin.plugintype == "Destination":
+ #typebox = qt.QHBox(parent)
+ #label = qt.QLabel("Operation:",typebox)
+ #combobox = qt.QComboBox(typebox)
+ #combobox.insertItem("Append")
+ #combobox.insertItem("Replace")
+ #combobox.insertItem("Update")
+ #combobox.insertItem("Update/Insert")
+ #combobox.insertItem("Insert new")
+ #label.setBuddy(combobox)
+ #typebox.setStretchFactor(combobox,1)
+ pass
+ elif self.plugin.plugintype == "Source":
+ wherebox = qt.QHBox(parent)
+ wherelabel = qt.QLabel("Where:",wherebox)
+ self.whereedit = qt.QLineEdit(self.plugin.options['where'],wherebox)
+
+ #orderbox = qt.QHBox(parent)
+ #orderlabel = qt.QLabel("Order By:",orderbox)
+ #orderedit = qt.QLineEdit("",orderbox)
+
+ #errbox = qt.QHBox(parent)
+ #errlabel = qt.QLabel("On Error:",errbox)
+ #errcombo = qt.QComboBox(errbox)
+ #errcombo.insertItem("Ask")
+ #errcombo.insertItem("Skip")
+ #errcombo.insertItem("Abort")
+ #errlabel.setBuddy(errcombo)
+ #errbox.setStretchFactor(errcombo,1)
+
+ if self.plugin.options['autoconnect']:
+ self.connectClicked()
+
+ def projectChanged(self, text):
+ #if self.driverbox.drivercombo.currentItem() != 0:
+ # self.driverbox.drivercombo.setCurrentItem(0)
+
+ file = str(text)
+ import os
+ if re.search("^\\~(\\/|\\\\)",file):
+ file = os.path.join(self.copycenterplugin.copycenter.homepath,file[2:])
+ if file == "" or not os.path.isfile(file):
+ self.driverbox.drivercombo.setCurrentItem(0)
+ self.driverbox.activated(0)
+ return
+
+ connectiondata = self.copycenterplugin.drivermanager.createConnectionDataByFile(file)
+ if connectiondata == None:
+ raise "Unsupported file."
+
+ drivername = connectiondata.driverName().lower()
+ print "driver: %s" % drivername
+ for i in range(1,self.driverbox.drivercombo.count()):
+ if drivername == self.driverbox.drivercombo.text(i).lower():
+ self.driverbox.drivercombo.setCurrentItem(i)
+ self.driverbox.activated(i)
+ break
+
+ if self.driverbox.driver != None:
+ if self.driverbox.driver.isFileDriver():
+ self.driverbox.fileedit.setText(connectiondata.fileName())
+ else: # server
+ self.driverbox.hostedit.setText(connectiondata.hostName())
+ self.driverbox.portedit.setText(str(connectiondata.port()))
+ self.driverbox.sockfilecheckbox.setChecked(connectiondata.localSocketFileUsed())
+ self.driverbox.sockfileedit.setText(connectiondata.localSocketFileName())
+ self.driverbox.useredit.setText(connectiondata.userName())
+ self.driverbox.passedit.setText(connectiondata.password())
+ self.driverbox.dbedit.setText(connectiondata.databaseName())
+
+ def connectClicked(self):
+ if self.driverbox.driver == None:
+ print "No driver selected."
+ return False
+ connectiondata = self.copycenterplugin.drivermanager.createConnectionData()
+ if self.driverbox.driver.isFileDriver():
+ file = str(self.driverbox.fileedit.text())
+ if file == "" or not os.path.isfile(file):
+ qt.QMessageBox.critical(self,"Failed to connect","There exists no such database file \"%s\"" % file)
+ return False
+ connectiondata.setFileName(file)
+ connectiondata.setDatabaseName(file)
+ else:
+ connectiondata.setHostName(str(self.driverbox.hostedit.text()))
+ connectiondata.setPort(str(self.driverbox.portedit.text()))
+ connectiondata.setLocalSocketFileUsed(self.driverbox.sockfilecheckbox.isChecked())
+ connectiondata.setLocalSocketFileName(str(self.driverbox.sockfileedit.text()))
+ connectiondata.setPassword(str(self.driverbox.passedit.text()))
+ connectiondata.setUserName(str(self.driverbox.useredit.text()))
+ connectiondata.setDatabaseName(str(self.driverbox.dbedit.text()))
+ print "Creating connection"
+ connection = self.driverbox.driver.createConnection(connectiondata)
+ print "Trying to connect"
+ if not connection.connect():
+ qt.QMessageBox.critical(self,"Failed to connect",connection.lastError())
+ return False
+ print "Use database \"%s\"" % connectiondata.databaseName()
+ if not connection.useDatabase( connectiondata.databaseName() ):
+ qt.QMessageBox.critical(self,"Failed to connect",connection.lastError())
+ return False
+ print "dbnames = %s" % connection.databaseNames()
+ print "tablenames = %s" % connection.tableNames()
+ #self.useDatabase(connection, filename)
+
+ self.plugin.connection.kexidbconnection = connection
+ self.updateConnectButtons()
+ return True
+
+ def disconnectClicked(self):
+ if not self.plugin.connection.disconnect():
+ qt.QMessageBox.critical(self,"Failed to disconnect",self.plugin.connection.lastError())
+ return
+ self.updateConnectButtons()
+
+ def updateConnectButtons(self):
+ connected = self.plugin.connection.isConnected()
+ self.prjbox.setEnabled(not connected)
+ self.driverbox.setEnabled(not connected)
+ self.connectbtn.setEnabled( (not connected) and (self.driverbox.driver != None) )
+ self.disconnectbtn.setEnabled(connected)
+ self.tablebox.tablebtn.setEnabled(connected)
+ self.fieldbox.tableChanged(self.tablebox.tableedit.text())
+
+ def getOptionValue(self,optionname):
+ try:
+ if optionname == 'project': return str(self.prjbox.prjcombo.currentText())
+ elif optionname == 'driver': return str(self.driverbox.drivercombo.currentText())
+ elif optionname == 'file': return str(self.driverbox.fileedit.text())
+ elif optionname == 'hostname': return str(self.driverbox.hostedit.text())
+ elif optionname == 'port': return str(self.driverbox.portedit.text())
+ elif optionname == 'usesocketfile': return str(self.driverbox.sockfilecheckbox.isChecked())
+ elif optionname == 'socketfile': return str(self.driverbox.sockfileedit.text())
+ elif optionname == 'username': return str(self.driverbox.useredit.text())
+ elif optionname == 'password': return str(self.driverbox.passedit.text())
+ elif optionname == 'database': return str(self.driverbox.dbedit.text())
+ elif optionname == 'table': return str(self.tablebox.tableedit.text())
+ elif optionname == 'fields': return str(self.fieldbox.fieldsedit.text())
+ elif optionname == 'where': return str(self.whereedit.text())
+ except:
+ pass
+ return ""
+
+ mainbox = MainBox(self,plugin,parent)
+ plugin.widget = mainbox
+ return mainbox
+
diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py
new file mode 100644
index 000000000..985d757d8
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py
@@ -0,0 +1,495 @@
+"""
+CopyCenterPlugin to provide 'QtSQL'.
+
+Description:
+This python-script is a plugin for the CopyCenter.py.
+
+Author:
+Sebastian Sauer <mail@dipe.org>
+
+Copyright:
+GPL v2 or higher.
+"""
+
+class CopyCenterPlugin:
+ """ The CopyCenterPlugin to provide 'QtSQL' to CopyCenter.py """
+
+ name = "QtSQL Database"
+ """ The name this plugin has. The name should be unique and
+ will be used for displaying a caption. """
+
+ class Plugin:
+ def _init_(self,copycenterplugin):
+ self.copycenterplugin = copycenterplugin
+ self.widget = None
+ self.database = None
+ self.cursor = None
+ self.isfinished = True
+ def _init(self,copierer):
+ self.copierer = copierer
+ if not self.widget.connectClicked():
+ raise "Failed to connect with database."
+ if self.database == None or not self.database.isOpen():
+ raise "Database is not initialized or not opened."
+ self.copierer.appendProgressMessage("Connected: %s %s@%s:%i %s" %
+ (str(self.database.driverName()),str(self.database.userName()),str(self.database.hostName()),self.database.port(),str(self.database.databaseName())) )
+ self.isfinished = False
+ def isFinished(self):
+ return self.isfinished
+ def finish(self):
+ self.isfinished = True
+ self.widget.disconnectClicked()
+ def createWidget(self,dialog,parent):
+ return self.copycenterplugin.widget(dialog, self, parent)
+
+ class Source(Plugin):
+ plugintype = "Source"
+ def __init__(self,copycenterplugin):
+ self._init_(copycenterplugin)
+ self.options = {
+ 'driver': 'QMYSQL3', #'QMYSQL3','QPSQL7','QODBC3',...
+ 'hostname': '127.0.0.1',
+ 'port': 3306,
+ 'username': 'root', #'MyUsername',
+ 'password': '', #'MySecretPassword',
+ 'database': '', #'MyQtSQLDatabase',
+ 'table': '', #'table1',
+ 'fields': '', #'f1,f2',
+ 'where': '',
+ }
+ def init(self,copierer):
+ self._init(copierer)
+ tablename = str(self.widget.tableedit.text())
+ wherestatement = str(self.widget.whereedit.text())
+ import qt
+ import qtsql
+ self.cursor = qtsql.QSqlCursor(tablename,True,self.database)
+ self.cursor.setFilter(wherestatement)
+ if not self.cursor.select():
+ raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) )
+ self.fieldlist = []
+ for fieldname in str(self.widget.fieldedit.text()).split(","):
+ fn = fieldname.strip()
+ if fn != "":
+ field = self.cursor.field(fn)
+ if not field:
+ raise "There exists no such field \"%s\" in the table \"%s\"." % (fn,tablename)
+ self.fieldlist.append(str(field.name()))
+ if len(self.fieldlist) < 1:
+ raise "No fields for table \"%s\" defined." % tablename
+ copierer.appendProgressMessage("SQL: %s" % str(self.cursor.executedQuery()))
+
+ def read(self):
+ if not self.cursor.next():
+ return None
+ record = []
+ for fieldname in self.fieldlist:
+ record.append( unicode(self.cursor.value(fieldname).toString()).encode("latin-1") )
+ #print "read record: %s" % record
+ return record
+
+ class Destination(Plugin):
+ plugintype = "Destination"
+ def __init__(self,copycenterplugin):
+ self._init_(copycenterplugin)
+ self.options = {
+ 'driver': 'QMYSQL3', #'QMYSQL3','QPSQL7','QODBC3',...
+ 'hostname': '127.0.0.1',
+ 'port': 3306,
+ 'username': 'root', #'MyUsername',
+ 'password': '', #'MySecretPassword',
+ 'database': '', #'MyQtSQLDatabase',
+ 'table': '', #'table2',
+ 'fields': '', #'field1,field2',
+ 'operation': 'Insert', #'Insert','Update'...
+ 'indexfield': '',
+ }
+ def init(self,copierer):
+ self._init(copierer)
+ import qt
+ import qtsql
+
+ self.fieldlist = []
+ for fieldname in str(self.widget.fieldedit.text()).split(","):
+ fn = fieldname.strip()
+ if fn != "": self.fieldlist.append(fn)
+
+ tablename = str(self.widget.tableedit.text())
+ self.cursor = qtsql.QSqlCursor(tablename,True,self.database)
+ {
+ 0: self.initInsert,
+ 1: self.initUpdate
+ }[ self.widget.operationedit.currentItem() ]()
+
+ def initInsert(self):
+ self.write = self.writeInsert
+ if not self.cursor.select():
+ raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) )
+ for fieldname in self.fieldlist: # check fieldlist
+ field = self.cursor.field(fieldname)
+ if not field: raise "There exists no such field \"%s\" in the table \"%s\"." % (fieldname, self.cursor.name())
+ self.copierer.appendProgressMessage("Insert SQL: %s" % str(self.cursor.executedQuery()))
+
+ def writeInsert(self, record):
+ print "insert record: %s" % record
+ import qt
+ cursorrecord = self.cursor.primeInsert()
+ count = len(record)
+ for i in range(len(self.fieldlist)):
+ if i == count: break
+ r = record[i]
+ if r == None:
+ v = qt.QVariant()
+ else:
+ v = qt.QVariant(r)
+ cursorrecord.setValue(self.fieldlist[i], v)
+ rowcount = self.cursor.insert()
+ if rowcount < 1:
+ drv = unicode(self.cursor.lastError().driverText()).encode("latin-1")
+ db = unicode(self.cursor.lastError().databaseText()).encode("latin-1")
+ print "failed: %s %s" % (drv,db)
+ self.copierer.writeFailed(record)
+ else:
+ self.copierer.writeSuccess(record,rowcount)
+ #import time
+ #time.sleep(1)
+ return True
+
+ def initUpdate(self):
+ self.write = self.writeUpdate
+ self.indexfieldname = str(self.widget.indexedit.text()).strip()
+ if self.indexfieldname == "": raise "No index-field defined."
+ pkindex = self.cursor.index(self.indexfieldname)
+ if not pkindex: raise "Invalid index-field defined."
+ self.cursor.setPrimaryIndex(pkindex)
+ #self.cursor.setMode( qtsql.QSqlCursor.Insert | qtsql.QSqlCursor.Update )
+ self.copierer.appendProgressMessage("Update SQL: %s" % str(self.cursor.executedQuery()))
+
+ def writeUpdate(self, record):
+ import qt
+ # determinate the primary-index
+ try:
+ idx = self.fieldlist.index(self.indexfieldname)
+ indexvalue = record[idx]
+ except:
+ import traceback
+ print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )
+ raise "Failed to determinate the value for the primary key."
+ # select cursor and go to matching record.
+ wherestatement = "%s = \"%s\"" % (self.indexfieldname, indexvalue)
+ if not self.cursor.select(wherestatement):
+ raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) )
+ if not self.cursor.next():
+ #print "No such record to update !"
+ return False
+ # Prepare updating the record.
+ cursorrecord = self.cursor.primeUpdate()
+ # Update the fields in the record.
+ count = len(record)
+ for i in range(len(self.fieldlist)):
+ if i == count: break
+ fieldname = self.fieldlist[i]
+ if self.indexfieldname != fieldname: # don't update the indexfield!
+ r = record[i]
+ if r == None:
+ v = qt.QVariant()
+ else:
+ v = qt.QVariant(r)
+ cursorrecord.setValue(fieldname, v)
+ # Write updated record.
+ rowcount = self.cursor.update()
+ if rowcount < 1:
+ self.copierer.writeFailed(record)
+ else:
+ self.copierer.writeSuccess(record,rowcount)
+ print "updated record (rowcount %s): %s" % (rowcount,record)
+ return True
+
+ def __init__(self, copycenter):
+ """ Constructor. """
+ pass
+
+ def widget(self,dialog,plugin,parent):
+ """ Each plugin may provide a qt.QWidget back to the
+ CopyCenter.py. The widget will be used to configure our
+ plugin settings. """
+
+ import qt
+ import os
+
+ self.dialog = dialog
+ ListViewDialog = self.dialog.ListViewDialog
+ class TableDialog(ListViewDialog):
+ def __init__(self, mainwidget):
+ ListViewDialog.__init__(self,mainwidget,"Tables")
+ self.mainwidget = mainwidget
+ self.listview.addColumn("Name")
+ text = str(self.mainwidget.tableedit.text())
+ item = None
+ for table in self.mainwidget.plugin.database.tables():
+ if item == None:
+ item = qt.QListViewItem(self.listview,table)
+ else:
+ item = qt.QListViewItem(self.listview,item,table)
+ if table == text:
+ self.listview.setSelected(item,True)
+ self.listview.ensureItemVisible(item)
+ qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.okClicked)
+ qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked)
+ def okClicked(self):
+ item = self.listview.selectedItem()
+ if item == None:
+ self.mainwidget.tableedit.setText("")
+ else:
+ self.mainwidget.tableedit.setText(item.text(0))
+ self.close()
+
+ class FieldsDialog(ListViewDialog):
+ def __init__(self, mainwidget):
+ ListViewDialog.__init__(self,parent,"Fields")
+ self.mainwidget = mainwidget
+ self.listview.setSelectionMode(qt.QListView.Multi)
+ self.listview.setSorting(-1)
+ self.listview.header().setClickEnabled(False)
+ self.listview.addColumn("Name")
+ self.listview.addColumn("Type")
+ self.listview.addColumn("Options")
+ tablename = str(self.mainwidget.tableedit.text())
+ recinfo = self.mainwidget.plugin.database.recordInfo(tablename)
+ if recinfo != None:
+ fieldslist = str(self.mainwidget.fieldedit.text()).split(",")
+ allfields = ("*" in fieldslist)
+ item = None
+ for fieldinfo in recinfo:
+ opts = ""
+ for s in ('Required','Calculated'): #,'Generated'):
+ if getattr(fieldinfo,"is%s" % s)(): opts += "%s " % s
+ item = self.addItem((fieldinfo.name(), qt.QVariant.typeToName(fieldinfo.type()), opts),item)
+ if allfields or fieldinfo.name() in fieldslist:
+ self.listview.setSelected(item,True)
+ qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked)
+ def okClicked(self):
+ selitems = []
+ item = self.listview.firstChild()
+ while item:
+ if item.isSelected():
+ selitems.append(str(item.text(0)))
+ item = item.nextSibling()
+ self.mainwidget.fieldedit.setText(",".join(selitems))
+ self.close()
+
+
+ class MainWidget(qt.QHBox):
+ def __init__(self,plugin,dialog,parent):
+ import qt
+ import qtsql
+ qt.QHBox.__init__(self,parent)
+ self.dialog = dialog
+ self.plugin = plugin
+
+ self.connectionbox = qt.QVBox(parent)
+ self.connectionbox.setSpacing(2)
+
+ driverbox = qt.QHBox(self.connectionbox)
+ driverlabel = qt.QLabel("Driver:",driverbox)
+ self.driveredit = qt.QComboBox(driverbox)
+ for driver in qtsql.QSqlDatabase.drivers():
+ self.driveredit.insertItem(driver)
+ if self.plugin.options['driver'] == driver:
+ self.driveredit.setCurrentItem(self.driveredit.count() - 1)
+ driverlabel.setBuddy(self.driveredit)
+ driverbox.setStretchFactor(self.driveredit,1)
+
+ hostbox = qt.QHBox(self.connectionbox)
+ hostlabel = qt.QLabel("Hostname:",hostbox)
+ self.hostedit = qt.QLineEdit(self.plugin.options['hostname'],hostbox)
+ hostlabel.setBuddy(self.hostedit)
+ hostbox.setStretchFactor(self.hostedit,1)
+
+ portbox = qt.QHBox(self.connectionbox)
+ portlabel = qt.QLabel("Port:",portbox)
+ self.portedit = qt.QLineEdit(str(self.plugin.options['port']),portbox)
+ portlabel.setBuddy(self.portedit)
+ portbox.setStretchFactor(self.portedit,1)
+
+ userbox = qt.QHBox(self.connectionbox)
+ userlabel = qt.QLabel("Username:",userbox)
+ self.useredit = qt.QLineEdit(self.plugin.options['username'],userbox)
+ userlabel.setBuddy(self.useredit)
+ userbox.setStretchFactor(self.useredit,1)
+
+ passbox = qt.QHBox(self.connectionbox)
+ passlabel = qt.QLabel("Password:",passbox)
+ self.passedit = qt.QLineEdit(self.plugin.options['password'],passbox)
+ self.passedit.setEchoMode(qt.QLineEdit.Password)
+ passlabel.setBuddy(self.passedit)
+ passbox.setStretchFactor(self.passedit,1)
+
+ dbbox = qt.QHBox(self.connectionbox)
+ dblabel = qt.QLabel("Database:",dbbox)
+ self.dbedit = qt.QLineEdit(self.plugin.options['database'],dbbox)
+ dblabel.setBuddy(self.dbedit)
+ dbbox.setStretchFactor(self.dbedit,1)
+
+ statusbar = qt.QHBox(parent)
+ statusbar.setSpacing(2)
+ statusbar.setStretchFactor(qt.QWidget(statusbar),1)
+ self.connectbtn = qt.QPushButton("Connect",statusbar)
+ qt.QObject.connect(self.connectbtn, qt.SIGNAL("clicked()"),self.connectClicked)
+ self.disconnectbtn = qt.QPushButton("Disconnect",statusbar)
+ self.disconnectbtn.setEnabled(False)
+ qt.QObject.connect(self.disconnectbtn, qt.SIGNAL("clicked()"),self.disconnectClicked)
+
+ tablebox = qt.QHBox(parent)
+ tablelabel = qt.QLabel("Table:",tablebox)
+ self.tableedit = qt.QLineEdit(self.plugin.options['table'],tablebox)
+ qt.QObject.connect(self.tableedit, qt.SIGNAL("textChanged(const QString&)"), self.tableEditChanged)
+ self.tablebtn = qt.QPushButton("...",tablebox)
+ self.tablebtn.setEnabled(False)
+ qt.QObject.connect(self.tablebtn, qt.SIGNAL("clicked()"), self.tableBtnClicked)
+ tablelabel.setBuddy(self.tableedit)
+ tablebox.setStretchFactor(self.tableedit,1)
+
+ fieldbox = qt.QHBox(parent)
+ fieldlabel = qt.QLabel("Fields:",fieldbox)
+ self.fieldedit = qt.QLineEdit(self.plugin.options['fields'],fieldbox)
+ self.fieldbtn = qt.QPushButton("...",fieldbox)
+ self.fieldbtn.setEnabled(False)
+ qt.QObject.connect(self.fieldbtn, qt.SIGNAL("clicked()"), self.fieldBtnClicked)
+ fieldlabel.setBuddy(self.fieldedit)
+ fieldbox.setStretchFactor(self.fieldedit,1)
+
+ if self.plugin.plugintype == "Source":
+ box = qt.QHBox(parent)
+ wherelabel = qt.QLabel("Where:",box)
+ self.whereedit = qt.QLineEdit(self.plugin.options['where'],box)
+ wherelabel.setBuddy(self.whereedit)
+ box.setStretchFactor(self.whereedit,1)
+ elif self.plugin.plugintype == "Destination":
+
+ class OperationBox(qt.QVBox):
+ def __init__(self, mainwidget, parent):
+ self.mainwidget = mainwidget
+ qt.QVBox.__init__(self, parent)
+ opbox = qt.QHBox(self)
+ operationlabel = qt.QLabel("Operation:",opbox)
+ self.mainwidget.operationedit = qt.QComboBox(opbox)
+ for op in ('Insert','Update'):
+ self.mainwidget.operationedit.insertItem(op)
+ if self.mainwidget.plugin.options['operation'] == op:
+ self.mainwidget.operationedit.setCurrentItem(self.mainwidget.operationedit.count() - 1)
+ operationlabel.setBuddy(self.mainwidget.operationedit)
+ opbox.setStretchFactor(self.mainwidget.operationedit,1)
+ self.box = None
+ qt.QObject.connect(self.mainwidget.operationedit, qt.SIGNAL("activated(int)"), self.operationActivated)
+ self.operationActivated()
+ def operationActivated(self, **args):
+ if self.box:
+ self.box.hide()
+ self.box.destroy()
+ self.box = None
+ def showInsert(self):
+ pass
+ def showUpdate(self):
+ self.box = qt.QHBox(self)
+ indexlabel = qt.QLabel("Indexfield:", self.box)
+ self.mainwidget.indexedit = qt.QLineEdit(self.mainwidget.plugin.options['indexfield'], self.box)
+ indexlabel.setBuddy(self.mainwidget.indexedit)
+ self.box.setStretchFactor(self.mainwidget.indexedit,1)
+ {
+ 0: showInsert,
+ 1: showUpdate,
+ }[ self.mainwidget.operationedit.currentItem() ](self)
+ if self.box != None: self.box.show()
+ OperationBox(self,parent)
+
+ def tableEditChanged(self,text):
+ if self.plugin.database != None and self.plugin.database.isOpen():
+ if str(text) in self.plugin.database.tables():
+ self.fieldbtn.setEnabled(True)
+ return
+ self.fieldbtn.setEnabled(False)
+
+ def tableBtnClicked(self):
+ dialog = TableDialog(self)
+ dialog.show()
+
+ def fieldBtnClicked(self):
+ dialog = FieldsDialog(self)
+ dialog.show()
+
+ def updateConnectState(self):
+ connected = self.plugin.database != None and self.plugin.database.isOpen()
+ self.connectionbox.setEnabled(not connected)
+ self.connectbtn.setEnabled(not connected)
+ self.disconnectbtn.setEnabled(connected)
+ self.tablebtn.setEnabled(connected)
+ self.tableEditChanged(self.tableedit.text())
+
+ def getOptionValue(self,optionname):
+ try:
+ if optionname == 'driver': return str(self.driveredit.currentText())
+ if optionname == 'hostname': return str(self.hostedit.text())
+ if optionname == 'port': return str(self.portedit.text())
+ if optionname == 'username': return str(self.useredit.text())
+ if optionname == 'password': return str(self.passedit.text())
+ if optionname == 'database': return str(self.dbedit.text())
+ if optionname == 'table': return str(self.tableedit.text())
+ if optionname == 'fields': return str(self.fieldedit.text())
+ if optionname == 'where': return str(self.whereedit.text())
+ if optionname == 'operation': return str(self.operationedit.currentText())
+ if optionname == 'indexfield': return str(self.indexedit.text())
+ except:
+ import traceback
+ print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) )
+ return ""
+
+ def connectClicked(self):
+ if self.plugin.database != None and self.plugin.database.isOpen():
+ print "already connected. not needed to reconnect..."
+ self.updateConnectState()
+ return True
+ print "trying to connect..."
+
+ import qtsql
+ drivername = str(self.driveredit.currentText())
+ print "drivername: %s" % drivername
+ connectionname = "CopyCenter%s" % self.plugin.plugintype
+ print "connectionname: %s" % connectionname
+ self.plugin.database = qtsql.QSqlDatabase.addDatabase(drivername,connectionname)
+ if not self.plugin.database:
+ qt.QMessageBox.critical(self,"Failed to connect","<qt>Failed to create database for driver \"%s\"</qt>" % drivername)
+ return False
+
+ hostname = str(self.hostedit.text())
+ self.plugin.database.setHostName(hostname)
+
+ portnumber = int(str(self.portedit.text()))
+ self.plugin.database.setPort(portnumber)
+
+ username = str(self.useredit.text())
+ self.plugin.database.setUserName(username)
+
+ password = str(self.passedit.text())
+ self.plugin.database.setPassword(password)
+
+ databasename = str(self.dbedit.text())
+ self.plugin.database.setDatabaseName(databasename)
+
+ if not self.plugin.database.open():
+ qt.QMessageBox.critical(self,"Failed to connect","<qt>%s<br><br>%s</qt>" % (self.plugin.database.lastError().driverText(),self.plugin.database.lastError().databaseText()))
+ return False
+ print "database is opened now!"
+ self.updateConnectState()
+ return True
+
+ def disconnectClicked(self):
+ print "trying to disconnect..."
+ if self.plugin.database:
+ self.plugin.database.close()
+ self.plugin.database = None
+ print "database is closed now!"
+ self.updateConnectState()
+
+ plugin.widget = MainWidget(plugin,self.dialog,parent)
+ return plugin.widget
diff --git a/kexi/plugins/scripting/scripts/copycenter/Makefile.am b/kexi/plugins/scripting/scripts/copycenter/Makefile.am
new file mode 100644
index 000000000..d46928ed0
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/copycenter/Makefile.am
@@ -0,0 +1,4 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+scriptsdir = $(kde_datadir)/kexi/scripts/copycenter
+scripts_SCRIPTS = CopyCenter.py CopyCenterPluginQtSQL.py CopyCenterPluginKexiDB.py CopyCenter.rc readme.html
diff --git a/kexi/plugins/scripting/scripts/copycenter/readme.html b/kexi/plugins/scripting/scripts/copycenter/readme.html
new file mode 100644
index 000000000..2aff6152b
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/copycenter/readme.html
@@ -0,0 +1,20 @@
+<html><body>
+<h1>Copy Center</h1>
+
+<b>Version 1.2</b>
+
+<p>Python script to copy data between database backends. The flexible
+plugin-architecture allows transparent copies between different backends.</p>
+
+<ul>
+<li>Read+write Kexi Databases. This includes all database backends supported by Kexi (like SQLite, MySQL or PostgreSQL).</li>
+<li>Read+write QtSQL Databases. MySQL, PostgreSQL and UnixODBC are supported. There might even be more like Oracle in the commercial Qt version or 3rd party backends.</li>
+<li>Runs embedded in Kexi (from the tools=>scripts menu) as well as independent of Kexi (use "krossrunner ~/.kde/share/apps/kexi/scripts/copycenter/CopyCenter.py" or python direct).</li>
+<li>Depends only on PyQt. PyKDE is not used at all and Kross (included in KOffice 1.5) is optional.</li>
+</ul>
+
+Author: (C)2006 Sebastian Sauer (mail at dipe dot org)<br>
+Website: http://www.kde-files.org/content/show.php?content=35251<br>
+License: GPL v2 or higher<br>
+
+</body></html>
diff --git a/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py
new file mode 100644
index 000000000..cace0340c
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py
@@ -0,0 +1,196 @@
+"""
+Export table or query data.
+
+Description:
+This script exports a KexiDB table or query to different fileformats.
+
+Author:
+Sebastian Sauer <mail@dipe.org>
+
+Copyright:
+Dual-licensed under LGPL v2+higher and the BSD license.
+"""
+
+class Datasource:
+ def __init__(self):
+ import kexiapp
+ keximainwindow = kexiapp.get("KexiAppMainWindow")
+
+ try:
+ self.connection = keximainwindow.getConnection()
+ except:
+ raise "No connection established. Please open a project before."
+
+ self.schema = None
+
+ def getSources(self):
+ sources = []
+ for table in self.connection.tableNames():
+ sources.append("Tables/%s" % table)
+ for query in self.connection.queryNames():
+ sources.append("Queries/%s" % query)
+ sources.sort()
+ return sources
+
+ def setSource(self, source):
+ s = source.split("/",1)
+ if s[0] == "Tables":
+ self.schema = self.connection.tableSchema( s[1] )
+ self.queryschema = self.schema.query()
+ elif s[0] == "Queries":
+ self.schema = self.connection.querySchema( s[1] )
+ self.queryschema = self.schema
+ self.cursor = None
+ return self.schema != None
+
+ def name(self):
+ return self.schema.name()
+
+ def caption(self):
+ return self.schema.caption()
+
+ def description(self):
+ return self.schema.description()
+
+ def header(self):
+ h = []
+ for field in self.schema.fieldlist().fields():
+ s = field.caption()
+ if s == None or s == "":
+ s = field.name()
+ h.append(s)
+ return h
+
+ def getNext(self):
+ if not self.cursor:
+ self.cursor = self.connection.executeQuerySchema( self.queryschema )
+ if not self.cursor:
+ raise "Failed to execute queryschema."
+ if not self.cursor.moveFirst():
+ raise "Failed to move cursor to first record."
+ if self.cursor.eof():
+ self.cursor = None
+ return None
+ items = []
+ for i in range( self.cursor.fieldCount() ):
+ items.append( self.cursor.value(i) )
+ self.cursor.moveNext()
+ return items
+
+class HtmlExporter:
+ def __init__(self, datasource):
+ self.datasource = datasource
+
+ def htmlescape(self, text):
+ import string
+ return string.replace(string.replace(string.replace(str(text),'&','&amp;'),'<','&lt;'),'>','&gt;')
+
+ def write(self, output, style):
+ name = self.datasource.name()
+
+ output.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n")
+ output.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n")
+ output.write("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n")
+ output.write("<head><title>%s</title>\n" % name)
+ output.write("<style type=\"text/css\">\n<!--\n")
+ if style == "Paper":
+ output.write("html { background-color:#efefef; }")
+ output.write("body { background-color:#fafafa; color:#303030; margin:1em; padding:1em; border:#606060 1px solid; }")
+ elif style == "Blues":
+ output.write("html { background-color:#0000aa; }")
+ output.write("body { background-color:#000066; color:#efefff; margin:1em; padding:1em; border:#00f 1px solid; }")
+ output.write("h1 { color:#0000ff; }")
+ output.write("th { color:#0000aa; }")
+ else:
+ output.write("html { background-color:#ffffff; color:#000; }")
+ output.write("body { margin:1em; }")
+ output.write("\n//-->\n</style>\n")
+ output.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n")
+ output.write("</head><body><h1>%s</h1>\n" % name)
+
+ caption = self.datasource.caption()
+ if caption and caption != name:
+ output.write("caption: %s<br />\n" % caption)
+
+ description = self.datasource.description()
+ if description:
+ output.write("description: %s<br />\n" % description)
+
+ #import datetime
+ #output.write("date: %s<br />" % datetime.datetime.now())
+
+ output.write("<table border='1'>\n")
+
+ output.write("<tr>")
+ for h in self.datasource.header():
+ output.write("<th>%s</th>" % h)
+ output.write("</tr>")
+
+ while 1 == 1:
+ items = self.datasource.getNext()
+ if items == None: break
+ output.write("<tr>")
+ for item in items:
+ u = unicode(str(self.htmlescape(item)),"latin-1")
+ output.write("<td>%s</td>" % u.encode("utf-8"))
+ output.write("</tr>\n")
+ output.write("</table>\n")
+ output.write("</body></html>\n")
+
+class GuiApp:
+ def __init__(self, datasource):
+ self.datasource = datasource
+
+ try:
+ import gui
+ except:
+ raise "Import of the Kross GUI module failed."
+
+ self.dialog = gui.Dialog("Export XHTML")
+ self.dialog.addLabel(self.dialog, "Export a table- or query-datasource to a XHTML-file.")
+
+ datasourceitems = self.datasource.getSources()
+ self.datasourcelist = self.dialog.addList(self.dialog, "Datasource:", datasourceitems)
+
+ styleitems = ["Plain", "Paper", "Blues"]
+ self.stylelist = self.dialog.addList(self.dialog, "Style:", styleitems)
+
+ #queryframe = Tkinter.Frame(frame)
+ #queryframe.pack()
+ #Tkinter.Label(queryframe, text="Table or query to export:").pack(side=Tkinter.LEFT)
+ #self.querycontent = Tkinter.StringVar()
+ #self.query = apply(Tkinter.OptionMenu, (queryframe, self.querycontent) + tuple( self.datasource.getSources() ))
+ #self.query.pack(side=Tkinter.LEFT)
+
+ self.file = self.dialog.addFileChooser(self.dialog,
+ "File:",
+ gui.getHome() + "/kexidata.xhtml",
+ (('XHTML files', '*.xhtml'),('All files', '*')))
+
+ btnframe = self.dialog.addFrame(self.dialog)
+ self.dialog.addButton(btnframe, "Export", self.doExport)
+ self.dialog.addButton(btnframe, "Cancel", self.dialog.close)
+
+ self.dialog.show()
+
+ def doExport(self):
+ file = str( self.file.get() )
+ query = str( self.datasourcelist.get() )
+ print "Exporting '%s' to file '%s' ..." % (query,file)
+
+ if not self.datasource.setSource(query):
+ raise "Invalid datasource selected."
+ #return
+
+ style = str( self.stylelist.get() )
+
+ f = open(file, "w")
+ global HtmlExporter
+ exporter = HtmlExporter(self.datasource)
+ exporter.write(f, style)
+ f.close()
+
+ print "Successfully exported '%s' to file %s" % (query,file)
+ self.dialog.close()
+
+GuiApp( Datasource() )
diff --git a/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc
new file mode 100644
index 000000000..11c1dcdf6
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc
@@ -0,0 +1,8 @@
+<KrossScripting>
+ <ScriptAction
+ name="exportxhtml"
+ text="Export Data to XHTML File"
+ icon="fileexport"
+ interpreter="python"
+ file="ExportXHTML.py" />
+</KrossScripting>
diff --git a/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am b/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am
new file mode 100644
index 000000000..1c7b9ca6b
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am
@@ -0,0 +1,4 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+scriptsdir = $(kde_datadir)/kexi/scripts/exportxhtml
+scripts_SCRIPTS = ExportXHTML.py ExportXHTML.rc
diff --git a/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py
new file mode 100755
index 000000000..200b3dee6
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py
@@ -0,0 +1,434 @@
+"""
+Import data from a XHTML file to a KexiDB table.
+
+Description:
+This script implements import of data from a XHTML file to a KexiDB table. The
+table needs to be an already existing table the data should be added to.
+
+Author:
+Sebastian Sauer <mail@dipe.org>
+
+Copyright:
+Dual-licensed under LGPL v2+higher and the BSD license.
+"""
+
+class SaxInput:
+ """ The inputsource we like to import the data from. This class
+ provides us abstract access to the SAX XML parser we use internaly
+ to import data from the XML-file. """
+
+ xmlfile = None
+ """ The XML file we should read the content from. """
+
+ def __init__(self):
+ """ Constructor. """
+
+ # try to import the xml.sax python module.
+ try:
+ import xml.sax.saxlib
+ import xml.sax.saxexts
+ except:
+ raise "Import of the python xml.sax.saxlib module failed. This module is needed by the ImportXHTML python script."
+
+ def read(self, outputwriter):
+ """ Start reading and parsing the XML-file. """
+
+ import xml.sax.saxlib
+ import xml.sax.saxexts
+
+ class SaxHandler(xml.sax.saxlib.HandlerBase):
+ """ The SaxHandler is our event-handler SAX calls on
+ parsing the XML-file. """
+
+ tablebase = ["html","body","table"]
+ """ The table-base defines where we will find our table-tag
+ that holds all the data we are interessted at. The default
+ is to look at <html><body><table></table></body></html>. """
+
+ def __init__(self, inputreader, outputwriter):
+ """ Constructor. """
+
+ # The to a SaxInput instance pointing inputreader.
+ self.inputreader = inputreader
+ # The to a KexiDBOutput instance pointing outputwriter.
+ self.outputwriter = outputwriter
+ # The hierachy-level in the DOM-tree we are in.
+ self.level = 0
+ # Defines if we are in the with tablebase defined DOM-element.
+ self.intable = False
+
+ # Points to a KexiDBOutput.Record instance if we are in a DOM-element that defines a record.
+ self.record = None
+ # Points to a KexiDBOutput.Field instance if we are in a record's field.
+ self.field = None
+
+ def startDocument(self):
+ sys.stdout.write('=> Starting parsing\n')
+
+ def endDocument(self):
+ sys.stdout.write('=> Fineshed parsing\n')
+
+ def startElement(self, name, attrs):
+ """ This method is called by SAX if a DOM-element starts. """
+
+ if self.level < len(self.tablebase):
+ if self.tablebase[self.level] != name:
+ self.intable = False
+ else:
+ self.intable = True
+ self.level += 1
+ if not self.intable:
+ return
+
+ # Print some debugging-output to stdout.
+ for idx in range(self.level): sys.stdout.write(' ')
+ sys.stdout.write('Element: %s' % name)
+ for attrName in attrs.keys():
+ sys.stdout.write(' %s="%s"' % (attrName,attrs.get(attrName)))
+ sys.stdout.write('\n')
+
+ # handle tr-, th- and td-tags inside the table.
+ if name == "tr" and (self.level == len(self.tablebase) + 1):
+ self.record = self.outputwriter.Record()
+ elif name == "td" and (self.level == len(self.tablebase) + 2):
+ self.field = self.outputwriter.Field()
+ elif name == "th" and (self.level == len(self.tablebase) + 2):
+ self.field = self.outputwriter.Field()
+
+ def endElement(self, name):
+ """ This method is called by SAX if a DOM-Element ends. """
+
+ self.level -= 1
+ #sys.stdout.write('EndElement:%s level:%s len(self.tablebase):%s\n' % (name,self.level,len(self.tablebase)))
+
+ if self.record != None:
+ # a record is defined. so, we are looking for the matching
+ # end-tags to close a record or a field.
+ if name == "tr" and (self.level == len(self.tablebase)):
+ self.outputwriter.write(self.record)
+ self.record = None
+ self.field = None
+ elif name == "td" and (self.level == len(self.tablebase) + 1):
+ #if self.field == None:
+ # raise "Unexpected closing </td>"
+ self.record.setField( self.field )
+ self.field = None
+ elif name == "th" and (self.level == len(self.tablebase) + 1):
+ #if self.field == None:
+ # raise "Unexpected closing </td>"
+ self.record.setHeader( self.field )
+ self.field = None
+
+ def characters(self, chars, offset, length):
+ """ This method is called by SAX if the text-content of a DOM-Element
+ was parsed. """
+
+ if self.field != None:
+ # the xml-data is unicode and we need to encode it
+ # to latin-1 cause KexiDB deals only with latin-1.
+ u = unicode(chars[offset:offset+length])
+ self.field.append(u.encode("latin-1"))
+
+ # start the job
+ outputwriter.begin()
+ # create saxhandler to handle parsing events.
+ handler = SaxHandler(self, outputwriter)
+ # we need a sax-parser and connect it with the handler.
+ parser = xml.sax.saxexts.make_parser()
+ parser.setDocumentHandler(handler)
+ # open the XML-file, parse the content and close the file again.
+ f = file(self.xmlfile, 'r')
+ parser.parseFile(f)
+ f.close()
+ # job is done
+ outputwriter.end()
+
+class KexiDBOutput:
+ """ The destination target we like to import the data to. This class
+ provides abstract access to the KexiDB module. """
+
+ class Result:
+ """ Holds some informations about the import-result. """
+ def __init__(self, outputwriter):
+ self.outputwriter = outputwriter
+ # number of records successfully imported.
+ self.successcount = 0
+ # number of records where import failed.
+ self.failedcount = 0
+
+ def addLog(self, record, state):
+ import datetime
+ date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M.%S")
+ self.outputwriter.logfile.write("%s (%s) %s\n" % (date,state,str(record)))
+
+ def success(self, record):
+ """ Called if a record was written successfully. """
+ print "SUCCESS: %s" % str(record)
+ self.successcount += 1
+ if hasattr(self.outputwriter,"logfile"):
+ self.addLog(record, "Success")
+
+ def failed(self, record):
+ """ Called if we failed to write a record. """
+ print "FAILED: %s" % str(record)
+ self.failedcount += 1
+ if hasattr(self.outputwriter,"logfile"):
+ self.addLog(record, "Failed")
+
+ class Record:
+ """ A Record in the dataset. """
+ def __init__(self):
+ self.fields = []
+ def setHeader(self, headerfield):
+ self.fields.append( headerfield )
+ self.isHeader = True
+ def setField(self, field):
+ self.fields.append( field )
+ def __str__(self):
+ s = "["
+ for f in self.fields:
+ s += "%s, " % str(f)
+ return s + "]"
+
+ class Field:
+ """ A field in a record. """
+ def __init__(self):
+ self.content = []
+ def append(self, content):
+ self.content.append( content )
+ def __str__(self):
+ return "".join(self.content)
+
+ def __init__(self):
+ """ Constructor. """
+ import kexiapp
+ keximainwindow = kexiapp.get("KexiAppMainWindow")
+
+ try:
+ self.connection = keximainwindow.getConnection()
+ except:
+ raise "No connection established. Please open a project before."
+
+ self.fieldlist = None
+ self.headerrecord = None
+ self.mapping = {}
+
+ def begin(self):
+ """ Called before parsing starts. """
+ print "START JOB"
+ if self.fieldlist == None:
+ raise "Invalid tableschema or fieldlist!"
+ global KexiDBOutput
+ self.result = KexiDBOutput.Result(self)
+ if hasattr(self,"logfilename") and self.logfilename != None and self.logfilename != "":
+ self.logfile = open(self.logfilename,'w')
+
+ def end(self):
+ """ Called if parsing is fineshed. """
+ print "END JOB"
+ self.logfile = None
+ self.mapping = {}
+ #self.headerrecord = None
+
+ def getTables(self):
+ """ return a list of avaiable tablenames. """
+ tables = self.connection.tableNames()
+ tables.sort()
+ return tables
+
+ def setTable(self, tablename):
+ """ Set the tablename we like to import the data to. """
+ tableschema = self.connection.tableSchema(tablename)
+ if tableschema == None:
+ raise "There exists no table with the name '%s'!" % tablename
+ self.fieldlist = tableschema.fieldlist()
+ fields = self.fieldlist.fields()
+ for field in fields:
+ print "KexiDBOutput.setTable(%s): %s(%s)" % (tablename,field.name(),field.type())
+ print "names=%s" % self.fieldlist.names()
+
+ def setMapping(self, mapping):
+ """ Set the tablefieldname=xmlcolnr dictonary we should map the data to. """
+ self.mapping = mapping
+
+ def setLogFile(self, logfilename):
+ """ Set the name of the logfile. """
+ self.logfilename = logfilename
+
+ def write(self, record):
+ """ Write the record to the KexiDB table. """
+
+ if hasattr(record, "isHeader"):
+ self.headerrecord = record
+ return
+
+ sys.stdout.write('KexiDBOutput.write:')
+ for f in record.fields:
+ sys.stdout.write(' "%s"' % f)
+ sys.stdout.write('\n')
+
+ if hasattr(self,"onWrite"):
+ if not self.onWrite(record):
+ raise RuntimeError()
+ delattr(self,"onWrite")
+ self.fieldlist = self.fieldlist.subList( list( self.mapping ) )
+
+ # Translate a KexiDBOutput.Record into a list of values.
+ values = []
+ for k in self.fieldlist.names():
+ values.append( str(record.fields[ int(self.mapping[k]) ]) )
+ print "Import values: %s" % values
+
+ try:
+ if self.connection.insertRecord(self.fieldlist, values):
+ self.result.success(record)
+ else:
+ self.result.failed(record)
+ except:
+ err = self.connection.lastError()
+ raise Exception( "Failed to insert the record:\n%s\n\n%s" % (values,err) )
+ #raise Exception( "Failed to insert into table \"%s\" the record:\n%s\n%s" % (self.tableschema.name(),values,self.connection.lastError()) )
+
+class GuiApp:
+ """ The GUI-dialog displayed to let the user define the source
+ XML-file and the destination KexiDB table. """
+
+ class InitialDialog:
+ def __init__(self, guiapp):
+ self.guiapp = guiapp
+ self.ok = False
+
+ import gui
+ self.dialog = gui.Dialog("Import XHTML")
+ self.dialog.addLabel(self.dialog, "Import data from a XHTML-file to a KexiDB table.\n"
+ "The destination table needs to be an existing table the data should be added to.")
+ self.importfile = self.dialog.addFileChooser(self.dialog,
+ "Source File:",
+ gui.getHome() + "/kexidata.xhtml",
+ (('XHTML files', '*.xhtml'),('All files', '*')))
+
+ self.desttable = self.dialog.addList(self.dialog, "Destination Table:", self.guiapp.outputwriter.getTables())
+
+ #self.operation = self.dialog.addList(self.dialog, "Operation:", ("Insert","Update","Insert/Update"))
+ #self.error = self.dialog.addList(self.dialog, "On error:", ("Ask","Skip","Abort"))
+
+ self.logfile = self.dialog.addFileChooser(self.dialog,
+ "Log File:",
+ "",
+ (('Logfiles', '*.log'),('All files', '*')))
+
+ btnframe = self.dialog.addFrame(self.dialog)
+ self.dialog.addButton(btnframe, "Next", self.doNext)
+ self.dialog.addButton(btnframe, "Cancel", self.doCancel)
+ self.dialog.show()
+
+ def doCancel(self):
+ """ Called if the Cancel-button was pressed. """
+ self.dialog.close()
+ self.dialog = None
+ #self.guiapp.InitialDialog
+
+ def doNext(self):
+ """ Start to import the XML-file into the KexiDB table. """
+
+ self.guiapp.inputreader.xmlfile = str(self.importfile.get())
+ self.guiapp.outputwriter.setTable( str(self.desttable.get()) )
+ self.guiapp.outputwriter.setLogFile( str(self.logfile.get()) )
+
+ try:
+ self.guiapp.inputreader.read( self.guiapp.outputwriter )
+
+ msgbox = self.dialog.showMessageBox("info","Import done",
+ "Successfully imported records: %s\nFailed to import records: %s" % (self.guiapp.outputwriter.result.successcount, self.guiapp.outputwriter.result.failedcount) )
+ msgbox.show()
+
+ self.doCancel()
+ except RuntimeError, e:
+ pass
+ #except Exception, e:
+ # import traceback
+ # traceback.print_exc()
+ # msgbox = self.dialog.showMessageBox("error", "Error", e)
+ # msgbox.show()
+
+ class MapperDialog:
+ """ The dialog that provides us a way to map
+ XHTML columns to the destination table. """
+
+ def __init__(self, outputwriter, record):
+ self.outputwriter = outputwriter
+ self.ok = False
+ fieldlist = outputwriter.fieldlist
+
+ import gui
+ self.dlg = gui.Dialog("Import XHTML")
+ self.dlg.addLabel(self.dlg, "Define how the destination table should be mapped to the data from the XHTML file.")
+ values = ["",]
+ for i in range(len(record.fields)):
+ try:
+ values.append( "%s: %s" % (i,str(outputwriter.headerrecord.fields[i])) )
+ except:
+ values.append( "%s: (%s)" % (i,str(record.fields[i])) )
+
+ self.items = []
+ i = 0
+ for field in fieldlist.fields():
+ f = self.dlg.addFrame(self.dlg)
+
+ l = self.dlg.addList(f, "%s:" % field.name(), values)
+ self.items.append( (field,l) )
+
+ details = "%s:" % str( field.type() )
+ if field.isAutoInc(): details += "autoinc,"
+ if field.isUniqueKey(): details += "unique,"
+ if field.isNotNull(): details += "notnull,"
+ if field.isNotEmpty(): details += "notempty,"
+ self.dlg.addLabel(f, "(%s)" % details[:-1])
+
+ try:
+ variable = str( record.fields[i] )
+ try:
+ int(variable)
+ i += 1
+ if not field.isAutoInc():
+ l.set(i)
+ except ValueError, e:
+ if not field.type() in ("Integer","BigInteger","ShortInteger","Float","Double"):
+ i += 1
+ l.set(i)
+ except:
+ pass
+
+ btnframe = self.dlg.addFrame(self.dlg)
+ self.dlg.addButton(btnframe, "Next", self.doNext)
+ self.dlg.addButton(btnframe, "Cancel", self.dlg.close)
+ self.dlg.show()
+
+ def doNext(self):
+ mapping = {}
+ for item in self.items:
+ (field,l) = item
+ fieldname = field.name()
+ colnr = str( l.get() ).split(":",1)[0]
+ if colnr.isdigit():
+ print "Table field '%s' is mapped to XML column '%s'" % (fieldname,colnr)
+ mapping[ fieldname ] = colnr
+ self.outputwriter.setMapping(mapping)
+ self.ok = True
+ self.dlg.close()
+
+ def __init__(self, inputreader, outputwriter):
+ """ Constructor. """
+
+ self.inputreader = inputreader
+ self.outputwriter = outputwriter
+ self.outputwriter.onWrite = self.onWrite
+
+ self.InitialDialog(self)
+
+ def onWrite(self, record):
+ """ This method got called after the first record got
+ readed and before we start to import. """
+ return self.MapperDialog(self.outputwriter, record).ok
+
+GuiApp( SaxInput(), KexiDBOutput() )
diff --git a/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc
new file mode 100644
index 000000000..0cfe7718b
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc
@@ -0,0 +1,8 @@
+<KrossScripting>
+ <ScriptAction
+ name="importxhtml"
+ text="Import Data From XHTML File"
+ icon="fileimport"
+ interpreter="python"
+ file="ImportXHTML.py" />
+</KrossScripting>
diff --git a/kexi/plugins/scripting/scripts/importxhtml/Makefile.am b/kexi/plugins/scripting/scripts/importxhtml/Makefile.am
new file mode 100644
index 000000000..a0a424fa8
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/importxhtml/Makefile.am
@@ -0,0 +1,4 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+scriptsdir = $(kde_datadir)/kexi/scripts/importxhtml
+scripts_SCRIPTS = ImportXHTML.py ImportXHTML.rc
diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am b/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am
new file mode 100644
index 000000000..9d32e165f
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am
@@ -0,0 +1,4 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+scriptsdir = $(kde_datadir)/kexi/scripts/projectdocumentor
+scripts_SCRIPTS = ProjectDocumentor.py ProjectDocumentor.rc
diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py
new file mode 100755
index 000000000..89a60301b
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py
@@ -0,0 +1,186 @@
+"""
+Project Documentor
+
+Description:
+This script collects various informations about a Kexi project
+and exports them to a HTML file.
+
+Author:
+Sebastian Sauer <mail@dipe.org>
+
+Copyright:
+Dual-licensed under LGPL v2+higher and the BSD license.
+"""
+
+class DataProvider:
+ def __init__(self):
+ import kexiapp
+ keximainwindow = kexiapp.get("KexiAppMainWindow")
+
+ try:
+ self.connection = keximainwindow.getConnection()
+ except:
+ raise "No connection established. Please open the project to be documented first."
+
+ def printConnection(self):
+ condata = self.connection.data()
+ infos = []
+ for item in ("caption", "description", "driverName", "hostName", "port", "userName", "fileName", "dbPath", "localSocketFileName", "serverInfoString"):
+ result = getattr(condata, item)()
+ if result != None and result != "" and (item != "port" or result != 0):
+ infos.append( (item, result) )
+ return infos
+
+ def printDriver(self):
+ driver = self.connection.driver()
+ result = [ ("Version", "%s.%s" % (driver.versionMajor(),driver.versionMinor())) ]
+ conlist = driver.connectionsList()
+ if len(conlist) > 0:
+ result.append( ("Connections",str(conlist)) )
+ return result
+
+ def printDatabases(self):
+ result = [ ("Current database", self.connection.currentDatabase()) ]
+ dbnames = self.connection.databaseNames()
+ if len(dbnames) > 0:
+ result.append( ("Databases",str(dbnames)) )
+ return result
+
+ def printTables(self):
+ result = []
+ for t in self.connection.tableNames():
+ tableschema = self.connection.tableSchema(t)
+ ti = []
+ for i in ("name", "caption", "description"):
+ v = getattr(tableschema,i)()
+ if v != None and v != "":
+ ti.append( (i,v) )
+ tf = []
+ for field in tableschema.fieldlist().fields():
+ tfi = []
+ for n in ("caption","description","type","subType","typeGroup","length","defaultValue"):
+ v = getattr(field,n)()
+ if v != None and v != "":
+ tfi.append( (n,v) )
+ props = []
+ for n in ("PrimaryKey","ForeignKey","AutoInc","UniqueKey","NotNull", "NotEmpty","Indexed","Unsigned"):
+ v = getattr(field,"is%s" % n)()
+ if v != None and v != "" and v != False and v != 0:
+ props.append( "%s " % n )
+ if len(props) > 0:
+ tfi.append( ("properties",props) )
+
+ tf.append( (field.name(), tfi) )
+ ti.append( ("fields", tf) )
+ if len(ti) > 0:
+ result.append( (t, ti) )
+ return result
+
+ def printQueries(self):
+ result = []
+ for q in self.connection.queryNames():
+ queryschema = self.connection.querySchema(q)
+ qi = []
+ for i in ("name", "caption", "description", "statement"):
+ v = getattr(queryschema,i)()
+ if v != None and v != "":
+ qi.append( (i,v) )
+ if len(qi) > 0:
+ result.append( (q, qi) )
+ return result
+
+class GuiApp:
+ def __init__(self, dataprovider):
+ self.dataprovider = dataprovider
+
+ try:
+ import gui
+ except:
+ raise "Import of the Kross GUI module failed."
+
+ self.dialog = gui.Dialog("Project Documentor")
+
+ self.dialog.addLabel(self.dialog, "Save information about the project to an HTML file.")
+
+ self.file = self.dialog.addFileChooser(self.dialog,
+ "File:",
+ gui.getHome() + "/projectdoc.html",
+ (('HTML files', '*.html'),('All files', '*')))
+
+ self.printCheckBoxes = {}
+ for d in dir(self.dataprovider):
+ if d.startswith("print"):
+ self.printCheckBoxes[d] = self.dialog.addCheckBox(self.dialog, d[5:], True)
+
+ #value = getattr(self.dataprovider,d)()
+ #if value != None and len(value) > 0:
+ # f.write("<h2>%s</h2>" % d[5:])
+ # f.write( self.toHTML(value) )
+
+ #self.exportProjectdetails =
+ #self.exportTableschemas = self.dialog.addCheckBox(self.dialog, "Table schemas", True)
+ #self.exportQueryschemas = self.dialog.addCheckBox(self.dialog, "Query schemas", True)
+
+ btnframe = self.dialog.addFrame(self.dialog)
+ self.dialog.addButton(btnframe, "Save", self.doSave)
+ self.dialog.addButton(btnframe, "Cancel", self.dialog.close)
+
+ self.dialog.show()
+
+ def toHTML(self, value):
+ import types
+ result = ""
+ if isinstance(value, types.TupleType):
+ result += "<ul>"
+ if len(value) == 1:
+ result += "<li>%s</li>" % value
+ elif len(value) == 2:
+ result += "<li>%s: %s</li>" % (value[0], self.toHTML(value[1]))
+ elif len(value) > 2:
+ for item in value:
+ i = self.toHTML(item)
+ if i != "":
+ result += "<li>%s</li>" % i
+ result += "</ul>"
+ elif isinstance(value, types.ListType):
+ for item in value:
+ result += "%s" % self.toHTML(item)
+ else:
+ result += "%s" % value
+ return result
+
+ def doSave(self):
+ file = str( self.file.get() )
+ print "Attempting to save project documentation to file: %s" % file
+
+ f = open(file, "w")
+
+ f.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>")
+ f.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 4.01 Strict//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd\">")
+ f.write("<html><head><title>Project information</title>")
+ f.write("<style type=\"text/css\">")
+ f.write(" html { background-color:#fafafa; }")
+ f.write(" body { background-color:#ffffff; margin:1em; padding:1em; border:#99a 1px solid; color:#003; }")
+ f.write("</style>")
+ f.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />")
+ f.write("</head><body><h1>Project information</h1>")
+
+ for d in dir(self.dataprovider):
+ if d.startswith("print"):
+ print "GuiApp.doSave() CHECK %s" % d
+ a = self.printCheckBoxes[d]
+ if a and a.isChecked():
+ print "GuiApp.doSave() BEGIN %s" % d
+ value = getattr(self.dataprovider,d)()
+ if value != None and len(value) > 0:
+ f.write("<h2>%s</h2>" % d[5:])
+ f.write( self.toHTML(value) )
+ print "GuiApp.doSave() END %s" % d
+
+ f.close()
+
+ print "Successfully saved project documentation to file: %s" % file
+ self.dialog.close()
+
+GuiApp( DataProvider() )
+
diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc
new file mode 100644
index 000000000..bb0f6c691
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc
@@ -0,0 +1,8 @@
+<KrossScripting>
+ <ScriptAction
+ name="projectdocumentor"
+ text="Project Documentation Generator"
+ icon="contents"
+ interpreter="python"
+ file="ProjectDocumentor.py" />
+</KrossScripting>
diff --git a/kexi/plugins/scripting/scripts/python/Makefile.am b/kexi/plugins/scripting/scripts/python/Makefile.am
new file mode 100644
index 000000000..4b31c35a1
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/python/Makefile.am
@@ -0,0 +1,2 @@
+include $(top_srcdir)/kexi/Makefile.global
+SUBDIRS = kexiapp
diff --git a/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am b/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am
new file mode 100644
index 000000000..f0f0492d1
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am
@@ -0,0 +1,2 @@
+kexiapppythondir = $(kde_datadir)/kexi/kross/python/kexiapp
+kexiapppython_SCRIPTS = __init__.py
diff --git a/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py b/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py
new file mode 100755
index 000000000..b52243048
--- /dev/null
+++ b/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py
@@ -0,0 +1,25 @@
+"""
+Initializer for the krosskexiapp-module.
+
+Description:
+This module provides the entry-point for python scripts
+to work with a running Kexi application instance.
+
+Author:
+Sebastian Sauer <mail@dipe.org>
+
+Copyright:
+Dual-licensed under LGPL v2+higher and the BSD license.
+"""
+
+try:
+ import krosskexiapp
+except ImportError, e:
+ raise "Import of the Kross KexiApp module failed.\n%s" % e
+
+def get(modulename):
+ return krosskexiapp.get(modulename)
+
+def currentConnection():
+ mainwindow = krosskexiapp.get("KexiAppMainWindow")
+ return mainwindow.getConnection()
diff --git a/kexi/plugins/tables/Makefile.am b/kexi/plugins/tables/Makefile.am
new file mode 100644
index 000000000..0971a64e2
--- /dev/null
+++ b/kexi/plugins/tables/Makefile.am
@@ -0,0 +1,28 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+kde_module_LTLIBRARIES = kexihandler_table.la
+
+kexihandler_table_la_SOURCES = kexitablepart.cpp kexitabledesignerview.cpp kexitabledesignerview_p.cpp \
+ kexitabledesigner_dataview.cpp kexitabledesignercommands.cpp kexilookupcolumnpage.cpp
+
+kexihandler_table_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module
+kexihandler_table_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/widget/tableview/libkexidatatable.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la
+
+INCLUDES= $(KOFFICE_INCLUDES) \
+ -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/widget/tableview \
+ -I$(top_srcdir)/kexi/kexidb -I$(top_srcdir)/lib $(all_includes)
+
+servicesdir=$(kde_servicesdir)/kexi
+services_DATA=kexitablehandler.desktop
+
+rcdir = $(kde_datadir)/kexi
+rc_DATA = kexitablepartui.rc kexitablepartinstui.rc
+
+METASOURCES = AUTO
+
+include ../Makefile.common
diff --git a/kexi/plugins/tables/kexilookupcolumnpage.cpp b/kexi/plugins/tables/kexilookupcolumnpage.cpp
new file mode 100644
index 000000000..9df927945
--- /dev/null
+++ b/kexi/plugins/tables/kexilookupcolumnpage.cpp
@@ -0,0 +1,419 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexilookupcolumnpage.h"
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qheader.h>
+
+#include <kiconloader.h>
+#include <klocale.h>
+#include <ktoolbarbutton.h>
+#include <kdebug.h>
+#include <kpopupmenu.h>
+
+#include <widget/kexipropertyeditorview.h>
+#include <widget/kexidatasourcecombobox.h>
+#include <widget/kexifieldlistview.h>
+#include <widget/kexifieldcombobox.h>
+#include <widget/kexismalltoolbutton.h>
+#include <kexidb/connection.h>
+#include <kexiproject.h>
+
+#include <koproperty/property.h>
+#include <koproperty/utils.h>
+
+QString mimeTypeToType(const QString& mimeType)
+{
+ if (mimeType=="kexi/table")
+ return "table";
+ else if (mimeType=="kexi/query")
+ return "query";
+//! @todo more types
+ return mimeType;
+}
+
+QString typeToMimeType(const QString& type)
+{
+ if (type=="table")
+ return "kexi/table";
+ else if (type=="query")
+ return "kexi/query";
+//! @todo more types
+ return type;
+}
+
+//----------------------------------------------
+
+//! @internal
+class KexiLookupColumnPage::Private
+{
+ public:
+ Private()
+ : currentFieldUid(-1)
+ , insideClearRowSourceSelection(false)
+ , propertySetEnabled(true)
+ {
+ }
+ ~Private()
+ {
+ }
+
+ bool hasPropertySet() const {
+ return propertySet;
+ }
+
+ void setPropertySet(KoProperty::Set* aPropertySet) {
+ propertySet = aPropertySet;
+ }
+
+ QVariant propertyValue(const QCString& propertyName) const {
+ return propertySet ? propertySet->property(propertyName).value() : QVariant();
+ }
+
+ void changeProperty(const QCString &property, const QVariant &value)
+ {
+ if (!propertySetEnabled)
+ return;
+ propertySet->changeProperty(property, value);
+ }
+
+ void updateInfoLabelForPropertySet(const QString& textToDisplayForNullSet) {
+ KexiPropertyEditorView::updateInfoLabelForPropertySet(
+ objectInfoLabel, propertySet, textToDisplayForNullSet);
+ }
+
+ KexiDataSourceComboBox *rowSourceCombo;
+ KexiFieldComboBox *boundColumnCombo, *visibleColumnCombo;
+ KexiObjectInfoLabel *objectInfoLabel;
+ QLabel *rowSourceLabel, *boundColumnLabel, *visibleColumnLabel;
+ QToolButton *clearRowSourceButton, *gotoRowSourceButton, *clearBoundColumnButton,
+ *clearVisibleColumnButton;
+ //! Used only in assignPropertySet() to check whether we already have the set assigned
+ int currentFieldUid;
+
+ bool insideClearRowSourceSelection : 1;
+ //! True is changeProperty() works. Used to block updating properties when within assignPropertySet().
+ bool propertySetEnabled : 1;
+
+ private:
+ //! A property set that is displayed on the page.
+ //! The set is also updated after any change in this page's data.
+ QGuardedPtr<KoProperty::Set> propertySet;
+};
+
+//----------------------------------------------
+
+KexiLookupColumnPage::KexiLookupColumnPage(QWidget *parent)
+ : QWidget(parent)
+ , d(new Private())
+{
+ setName("KexiLookupColumnPage");
+
+ QVBoxLayout *vlyr = new QVBoxLayout(this);
+ d->objectInfoLabel = new KexiObjectInfoLabel(this, "KexiObjectInfoLabel");
+ vlyr->addWidget(d->objectInfoLabel);
+
+//todo d->noDataSourceAvailableSingleText = i18n("No data source could be assigned for this widget.");
+//todo d->noDataSourceAvailableMultiText = i18n("No data source could be assigned for multiple widgets.");
+
+ //-Row Source
+ QWidget *contents = new QWidget(this);
+ vlyr->addWidget(contents);
+ QVBoxLayout *contentsVlyr = new QVBoxLayout(contents);
+
+ QHBoxLayout *hlyr = new QHBoxLayout(contentsVlyr);
+ d->rowSourceLabel = new QLabel(i18n("Row source:"), contents);
+ d->rowSourceLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ d->rowSourceLabel->setMargin(2);
+ d->rowSourceLabel->setMinimumHeight(IconSize(KIcon::Small)+4);
+ d->rowSourceLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom);
+ hlyr->addWidget(d->rowSourceLabel);
+
+ d->gotoRowSourceButton = new KexiSmallToolButton(contents, QString::null, "goto", "gotoRowSourceButton");
+ d->gotoRowSourceButton->setMinimumHeight(d->rowSourceLabel->minimumHeight());
+ QToolTip::add(d->gotoRowSourceButton, i18n("Go to selected row source"));
+ hlyr->addWidget(d->gotoRowSourceButton);
+ connect(d->gotoRowSourceButton, SIGNAL(clicked()), this, SLOT(slotGotoSelectedRowSource()));
+
+ d->clearRowSourceButton = new KexiSmallToolButton(contents, QString::null,
+ "clear_left", "clearRowSourceButton");
+ d->clearRowSourceButton->setMinimumHeight(d->rowSourceLabel->minimumHeight());
+ QToolTip::add(d->clearRowSourceButton, i18n("Clear row source"));
+ hlyr->addWidget(d->clearRowSourceButton);
+ connect(d->clearRowSourceButton, SIGNAL(clicked()), this, SLOT(clearRowSourceSelection()));
+
+ d->rowSourceCombo = new KexiDataSourceComboBox(contents, "rowSourceCombo");
+ d->rowSourceLabel->setBuddy(d->rowSourceCombo);
+ contentsVlyr->addWidget(d->rowSourceCombo);
+
+ contentsVlyr->addSpacing(8);
+
+ //- Bound Column
+ hlyr = new QHBoxLayout(contentsVlyr);
+ d->boundColumnLabel = new QLabel(i18n("Bound column:"), contents);
+ d->boundColumnLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ d->boundColumnLabel->setMargin(2);
+ d->boundColumnLabel->setMinimumHeight(IconSize(KIcon::Small)+4);
+ d->boundColumnLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom);
+ hlyr->addWidget(d->boundColumnLabel);
+
+ d->clearBoundColumnButton = new KexiSmallToolButton(contents, QString::null,
+ "clear_left", "clearBoundColumnButton");
+ d->clearBoundColumnButton->setMinimumHeight(d->boundColumnLabel->minimumHeight());
+ QToolTip::add(d->clearBoundColumnButton, i18n("Clear bound column"));
+ hlyr->addWidget(d->clearBoundColumnButton);
+ connect(d->clearBoundColumnButton, SIGNAL(clicked()), this, SLOT(clearBoundColumnSelection()));
+
+ d->boundColumnCombo = new KexiFieldComboBox(contents, "boundColumnCombo");
+ d->boundColumnLabel->setBuddy(d->boundColumnCombo);
+ contentsVlyr->addWidget(d->boundColumnCombo);
+
+ contentsVlyr->addSpacing(8);
+
+ //- Visible Column
+ hlyr = new QHBoxLayout(contentsVlyr);
+ d->visibleColumnLabel = new QLabel(i18n("Visible column:"), contents);
+ d->visibleColumnLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
+ d->visibleColumnLabel->setMargin(2);
+ d->visibleColumnLabel->setMinimumHeight(IconSize(KIcon::Small)+4);
+ d->visibleColumnLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom);
+ hlyr->addWidget(d->visibleColumnLabel);
+
+ d->clearVisibleColumnButton = new KexiSmallToolButton(contents, QString::null,
+ "clear_left", "clearVisibleColumnButton");
+ d->clearVisibleColumnButton->setMinimumHeight(d->visibleColumnLabel->minimumHeight());
+ QToolTip::add(d->clearVisibleColumnButton, i18n("Clear visible column"));
+ hlyr->addWidget(d->clearVisibleColumnButton);
+ connect(d->clearVisibleColumnButton, SIGNAL(clicked()), this, SLOT(clearVisibleColumnSelection()));
+
+ d->visibleColumnCombo = new KexiFieldComboBox(contents, "visibleColumnCombo");
+ d->visibleColumnLabel->setBuddy(d->visibleColumnCombo);
+ contentsVlyr->addWidget(d->visibleColumnCombo);
+
+ vlyr->addStretch(1);
+
+ connect(d->rowSourceCombo, SIGNAL(textChanged(const QString &)),
+ this, SLOT(slotRowSourceTextChanged(const QString &)));
+ connect(d->rowSourceCombo, SIGNAL(dataSourceChanged()), this, SLOT(slotRowSourceChanged()));
+ connect(d->boundColumnCombo, SIGNAL(selected()), this, SLOT(slotBoundColumnSelected()));
+ connect(d->visibleColumnCombo, SIGNAL(selected()), this, SLOT(slotVisibleColumnSelected()));
+
+ clearBoundColumnSelection();
+ clearVisibleColumnSelection();
+}
+
+KexiLookupColumnPage::~KexiLookupColumnPage()
+{
+ delete d;
+}
+
+void KexiLookupColumnPage::setProject(KexiProject *prj)
+{
+ d->rowSourceCombo->setProject(prj,
+ true/*showTables*/, true/*showQueries*/
+ );
+ d->boundColumnCombo->setProject(prj);
+ d->visibleColumnCombo->setProject(prj);
+}
+
+void KexiLookupColumnPage::assignPropertySet(KoProperty::Set* propertySet)
+{
+ if (!d->hasPropertySet() && !propertySet)
+ return;
+ if (propertySet && d->currentFieldUid == (*propertySet)["uid"].value().toInt())
+ return; //already assigned
+
+ d->propertySetEnabled = false;
+ d->setPropertySet( propertySet );
+ d->updateInfoLabelForPropertySet( i18n("No field selected") );
+
+ const bool hasRowSource = d->hasPropertySet() && !d->propertyValue("rowSourceType").isNull()
+ && !d->propertyValue("rowSource").isNull();
+
+ QString rowSource, rowSourceType;
+ if (hasRowSource) {
+ rowSourceType = typeToMimeType( d->propertyValue("rowSourceType").toString() );
+ rowSource = d->propertyValue("rowSource").toString();
+ }
+ d->rowSourceCombo->setDataSource( rowSourceType, rowSource );
+ d->rowSourceLabel->setEnabled( d->hasPropertySet() );
+ d->rowSourceCombo->setEnabled( d->hasPropertySet() );
+ if (!d->hasPropertySet())
+ d->clearRowSourceButton->setEnabled( false );
+
+ int boundColumn = -1, visibleColumn = -1;
+ if (d->rowSourceCombo->isSelectionValid()) {
+ boundColumn = d->propertyValue("boundColumn").toInt();
+ visibleColumn = d->propertyValue("visibleColumn").toInt();
+ }
+ d->boundColumnCombo->setFieldOrExpression(boundColumn);
+ d->visibleColumnCombo->setFieldOrExpression(visibleColumn);
+ updateBoundColumnWidgetsAvailability();
+ d->propertySetEnabled = true;
+}
+
+void KexiLookupColumnPage::clearBoundColumnSelection()
+{
+ d->boundColumnCombo->setCurrentText("");
+ d->boundColumnCombo->setFieldOrExpression(QString::null);
+ slotBoundColumnSelected();
+ d->clearBoundColumnButton->setEnabled(false);
+}
+
+void KexiLookupColumnPage::slotBoundColumnSelected()
+{
+// KexiDB::Field::Type dataType = KexiDB::Field::InvalidType;
+//! @todo this should also work for expressions
+/*disabled KexiDB::Field *field = d->fieldListView->schema()->field( d->boundColumnCombo->fieldOrExpression() );
+ if (field)
+ dataType = field->type();
+*/
+ d->clearBoundColumnButton->setEnabled( !d->boundColumnCombo->fieldOrExpression().isEmpty() );
+ if (!d->boundColumnCombo->fieldOrExpression().isEmpty()) {
+ kdDebug() << endl;
+ }
+
+ // update property set
+ if (d->hasPropertySet()) {
+ d->changeProperty("boundColumn", d->boundColumnCombo->indexOfField());
+ }
+/*
+ emit boundColumnChanged(
+ d->boundColumnCombo->fieldOrExpression(),
+ d->boundColumnCombo->fieldOrExpressionCaption(),
+ dataType
+ );*/
+}
+
+void KexiLookupColumnPage::clearVisibleColumnSelection()
+{
+ d->visibleColumnCombo->setCurrentText("");
+ d->visibleColumnCombo->setFieldOrExpression(QString::null);
+ slotVisibleColumnSelected();
+ d->clearVisibleColumnButton->setEnabled(false);
+}
+
+void KexiLookupColumnPage::slotVisibleColumnSelected()
+{
+// KexiDB::Field::Type dataType = KexiDB::Field::InvalidType;
+//! @todo this should also work for expressions
+ d->clearVisibleColumnButton->setEnabled( !d->visibleColumnCombo->fieldOrExpression().isEmpty() );
+
+ // update property set
+ if (d->hasPropertySet()) {
+//! @todo support expression in special "visibleExpression"
+ d->changeProperty("visibleColumn", d->visibleColumnCombo->indexOfField());
+ }
+}
+
+void KexiLookupColumnPage::slotRowSourceChanged()
+{
+ if (!d->rowSourceCombo->project())
+ return;
+ QString mime = d->rowSourceCombo->selectedMimeType();
+ bool rowSourceFound = false;
+ QString name = d->rowSourceCombo->selectedName();
+ if ((mime=="kexi/table" || mime=="kexi/query") && d->rowSourceCombo->isSelectionValid()) {
+ KexiDB::TableOrQuerySchema *tableOrQuery = new KexiDB::TableOrQuerySchema(
+ d->rowSourceCombo->project()->dbConnection(), name.latin1(), mime=="kexi/table");
+ if (tableOrQuery->table() || tableOrQuery->query()) {
+//disabled d->fieldListView->setSchema( tableOrQuery );
+/*tmp*/ delete tableOrQuery;
+ rowSourceFound = true;
+ d->boundColumnCombo->setTableOrQuery(name, mime=="kexi/table");
+ d->visibleColumnCombo->setTableOrQuery(name, mime=="kexi/table");
+ }
+ else {
+ delete tableOrQuery;
+ }
+ }
+ if (!rowSourceFound) {
+ d->boundColumnCombo->setTableOrQuery("", true);
+ d->visibleColumnCombo->setTableOrQuery("", true);
+ }
+ clearBoundColumnSelection();
+ clearVisibleColumnSelection();
+ d->clearRowSourceButton->setEnabled(rowSourceFound);
+ d->gotoRowSourceButton->setEnabled(rowSourceFound);
+/* disabled
+ if (dataSourceFound) {
+ slotFieldListViewSelectionChanged();
+ } else {
+ d->addField->setEnabled(false);
+ }*/
+ updateBoundColumnWidgetsAvailability();
+
+ //update property set
+ if (d->hasPropertySet()) {
+ d->changeProperty("rowSourceType", mimeTypeToType(mime));
+ d->changeProperty("rowSource", name);
+ }
+
+//disabled emit formDataSourceChanged(mime, name);
+//! @todo update d->propertySet ^^
+}
+
+void KexiLookupColumnPage::slotRowSourceTextChanged(const QString & string)
+{
+ Q_UNUSED(string);
+ const bool enable = d->rowSourceCombo->isSelectionValid();
+ if (enable) {
+ updateBoundColumnWidgetsAvailability();
+ }
+ else {
+ clearRowSourceSelection( d->rowSourceCombo->selectedName().isEmpty()/*alsoClearComboBox*/ );
+ }
+}
+
+void KexiLookupColumnPage::clearRowSourceSelection(bool alsoClearComboBox)
+{
+ if (d->insideClearRowSourceSelection)
+ return;
+ d->insideClearRowSourceSelection = true;
+ if (alsoClearComboBox && !d->rowSourceCombo->selectedName().isEmpty())
+ d->rowSourceCombo->setDataSource("", "");
+ d->clearRowSourceButton->setEnabled(false);
+ d->gotoRowSourceButton->setEnabled(false);
+ d->insideClearRowSourceSelection = false;
+}
+
+void KexiLookupColumnPage::slotGotoSelectedRowSource()
+{
+ QString mime = d->rowSourceCombo->selectedMimeType();
+ if (mime=="kexi/table" || mime=="kexi/query") {
+ if (d->rowSourceCombo->isSelectionValid())
+ emit jumpToObjectRequested(mime.latin1(), d->rowSourceCombo->selectedName().latin1());
+ }
+}
+
+void KexiLookupColumnPage::updateBoundColumnWidgetsAvailability()
+{
+ const bool hasRowSource = d->rowSourceCombo->isSelectionValid();
+ d->boundColumnCombo->setEnabled( hasRowSource );
+ d->boundColumnLabel->setEnabled( hasRowSource );
+ d->clearBoundColumnButton->setEnabled( hasRowSource && !d->boundColumnCombo->fieldOrExpression().isEmpty() );
+ d->visibleColumnCombo->setEnabled( hasRowSource );
+ d->visibleColumnLabel->setEnabled( hasRowSource );
+ d->clearVisibleColumnButton->setEnabled( hasRowSource && !d->visibleColumnCombo->fieldOrExpression().isEmpty() );
+}
+
+#include "kexilookupcolumnpage.moc"
diff --git a/kexi/plugins/tables/kexilookupcolumnpage.h b/kexi/plugins/tables/kexilookupcolumnpage.h
new file mode 100644
index 000000000..457b2e3d1
--- /dev/null
+++ b/kexi/plugins/tables/kexilookupcolumnpage.h
@@ -0,0 +1,88 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef KEXILOOKUPCOLUMNPAGE_H
+#define KEXILOOKUPCOLUMNPAGE_H
+
+#include <qwidget.h>
+#include <kexidb/field.h>
+#include <kexidb/utils.h>
+#include <koproperty/set.h>
+
+class KCommand;
+class KexiObjectInfoLabel;
+class KexiDataSourceComboBox;
+class KexiFieldComboBox;
+class KexiFieldListView;
+class KexiProject;
+class KexiSmallToolButton;
+class QToolButton;
+class QLabel;
+class QFrame;
+
+//! @short A page within table designer's property pane, providing lookup column editor.
+/*! It's data model is basically KexiDB::LookupFieldSchema class, but the page does
+ not create it directly but instead updates a property set that defines
+ the field currently selected in the designer.
+
+ @todo not all features of KexiDB::LookupFieldSchema class are displayed on this page yet
+ */
+class KexiLookupColumnPage : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiLookupColumnPage(QWidget *parent);
+ virtual ~KexiLookupColumnPage();
+
+ public slots:
+ void setProject(KexiProject *prj);
+ void clearRowSourceSelection(bool alsoClearComboBox = true);
+ void clearBoundColumnSelection();
+ void clearVisibleColumnSelection();
+
+ //! Receives a pointer to a new property \a set (from KexiFormView::managerPropertyChanged())
+ void assignPropertySet(KoProperty::Set* propertySet);
+
+ signals:
+ //! Signal emitted when helper button 'Go to selected row sourcesource' is clicked.
+ void jumpToObjectRequested(const QCString& mime, const QCString& name);
+
+// /*! Signal emitted when current bound column has been changed. */
+// void boundColumnChanged(const QString& string, const QString& caption,
+ // KexiDB::Field::Type type);
+
+ protected slots:
+ void slotRowSourceTextChanged(const QString & string);
+ void slotRowSourceChanged();
+ void slotGotoSelectedRowSource();
+ void slotBoundColumnSelected();
+ void slotVisibleColumnSelected();
+
+ protected:
+ void updateBoundColumnWidgetsAvailability();
+
+ //! Used instead of m_propertySet->changeProperty() to honor m_propertySetEnabled
+ void changeProperty(const QCString &property, const QVariant &value);
+
+ private:
+ class Private;
+ Private* d;
+};
+
+#endif
diff --git a/kexi/plugins/tables/kexitabledesigner_dataview.cpp b/kexi/plugins/tables/kexitabledesigner_dataview.cpp
new file mode 100644
index 000000000..bea2d9f51
--- /dev/null
+++ b/kexi/plugins/tables/kexitabledesigner_dataview.cpp
@@ -0,0 +1,79 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitabledesigner_dataview.h"
+
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+#include <kexiutils/utils.h>
+#include "kexitableview.h"
+#include "kexidatatableview.h"
+#include "keximainwindow.h"
+
+KexiTableDesigner_DataView::KexiTableDesigner_DataView(KexiMainWindow *win, QWidget *parent)
+ : KexiDataTable(win, parent, "KexiTableDesigner_DataView", true/*db-aware*/)
+{
+}
+
+KexiTableDesigner_DataView::~KexiTableDesigner_DataView()
+{
+ if (dynamic_cast<KexiDataTableView*>(tableView())
+ && dynamic_cast<KexiDataTableView*>(tableView())->cursor())
+ {
+ mainWin()->project()->dbConnection()->deleteCursor(
+ dynamic_cast<KexiDataTableView*>(tableView())->cursor() );
+ }
+}
+
+tristate KexiTableDesigner_DataView::beforeSwitchTo(int mode, bool &dontStore)
+{
+ Q_UNUSED( dontStore );
+
+ if (mode != Kexi::DataViewMode) {
+ //accept editing before switching
+// if (!m_view->acceptRowEdit()) {
+ if (!acceptRowEdit()) {
+ return cancelled;
+ }
+ }
+
+ return true;
+}
+
+tristate KexiTableDesigner_DataView::afterSwitchFrom(int mode)
+{
+ Q_UNUSED( mode );
+
+ if (tempData()->tableSchemaChangedInPreviousView) {
+ KexiUtils::WaitCursor wait;
+ KexiDB::Cursor *c = mainWin()->project()->dbConnection()->prepareQuery(*tempData()->table);
+ if (!c)
+ return false;
+ setData(c);
+ tempData()->tableSchemaChangedInPreviousView = false;
+ }
+ return true;
+}
+
+KexiTablePart::TempData* KexiTableDesigner_DataView::tempData() const
+{
+ return static_cast<KexiTablePart::TempData*>(parentDialog()->tempData());
+}
+
+#include "kexitabledesigner_dataview.moc"
diff --git a/kexi/plugins/tables/kexitabledesigner_dataview.h b/kexi/plugins/tables/kexitabledesigner_dataview.h
new file mode 100644
index 000000000..59e84ab1d
--- /dev/null
+++ b/kexi/plugins/tables/kexitabledesigner_dataview.h
@@ -0,0 +1,49 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXITABLEDESIGNERDATAVIEW_H
+#define KEXITABLEDESIGNERDATAVIEW_H
+
+#include <kexidatatable.h>
+#include "kexitablepart.h"
+
+class KexiTableDesigner_DataView : public KexiDataTable
+{
+ Q_OBJECT
+
+ public:
+ KexiTableDesigner_DataView(KexiMainWindow *win, QWidget *parent);
+
+ virtual ~KexiTableDesigner_DataView();
+
+ KexiTablePart::TempData* tempData() const;
+
+ protected:
+// //! called just once from ctor
+// void init();
+// void initActions();
+// //! called whenever data should be reloaded (on switching to this view mode)
+// void initData();
+
+ virtual tristate beforeSwitchTo(int mode, bool &dontStore);
+ virtual tristate afterSwitchFrom(int mode);
+
+};
+
+#endif
diff --git a/kexi/plugins/tables/kexitabledesignercommands.cpp b/kexi/plugins/tables/kexitabledesignercommands.cpp
new file mode 100644
index 000000000..ccbb181ad
--- /dev/null
+++ b/kexi/plugins/tables/kexitabledesignercommands.cpp
@@ -0,0 +1,281 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <qdom.h>
+#include <qwidget.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qsplitter.h>
+#include <qmetaobject.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kmessagebox.h>
+#include <kaccelmanager.h>
+
+#include <koproperty/property.h>
+
+#include "kexitabledesignercommands.h"
+
+using namespace KexiTableDesignerCommands;
+
+
+Command::Command(KexiTableDesignerView* view)
+ : KCommand()
+ , m_view(view)
+{
+}
+
+Command::~Command()
+{
+}
+
+//--------------------------------------------------------
+
+ChangeFieldPropertyCommand::ChangeFieldPropertyCommand( KexiTableDesignerView* view,
+ const KoProperty::Set& set, const QCString& propertyName, const QVariant& oldValue, const QVariant& newValue,
+ KoProperty::Property::ListData* const oldListData, KoProperty::Property::ListData* const newListData)
+ : Command(view)
+ , m_alterTableAction(
+ propertyName=="name" ? oldValue.toString() : set.property("name").value().toString(),
+ propertyName, newValue, set["uid"].value().toInt())
+ , m_oldValue(oldValue)
+// , m_fieldUID(set["uid"].value().toInt())
+ , m_oldListData( oldListData ? new KoProperty::Property::ListData(*oldListData) : 0 )
+ , m_listData( newListData ? new KoProperty::Property::ListData(*newListData) : 0 )
+{
+ kexipluginsdbg << "ChangeFieldPropertyCommand: " << debugString() << endl;
+}
+
+ChangeFieldPropertyCommand::~ChangeFieldPropertyCommand()
+{
+ delete m_oldListData;
+ delete m_listData;
+}
+
+QString ChangeFieldPropertyCommand::name() const
+{
+ return i18n("Change \"%1\" property for table field from \"%2\" to \"%3\"")
+ .arg(m_alterTableAction.propertyName()).arg(m_oldValue.toString())
+ .arg(m_alterTableAction.newValue().toString());
+}
+
+QString ChangeFieldPropertyCommand::debugString()
+{
+ QString s( name() );
+ if (m_oldListData || m_listData)
+ s += QString("\nAnd list data from [%1]\n to [%2]")
+ .arg( m_oldListData ?
+ QString("%1 -> %2")
+ .arg(m_oldListData->keysAsStringList().join(",")).arg(m_oldListData->names.join(","))
+ : QString("<NONE>"))
+ .arg( m_listData ?
+ QString("%1 -> %2")
+ .arg(m_listData->keysAsStringList().join(",")).arg(m_listData->names.join(","))
+ : QString("<NONE>"));
+ return s + QString(" (UID=%1)").arg(m_alterTableAction.uid());
+}
+
+void ChangeFieldPropertyCommand::execute()
+{
+ m_view->changeFieldProperty(
+ m_alterTableAction.uid(),
+ m_alterTableAction.propertyName().latin1(),
+ m_alterTableAction.newValue(), m_listData );
+}
+
+void ChangeFieldPropertyCommand::unexecute()
+{
+ m_view->changeFieldProperty(
+ m_alterTableAction.uid(),
+ m_alterTableAction.propertyName().latin1(),
+ m_oldValue, m_oldListData );
+}
+
+KexiDB::AlterTableHandler::ActionBase* ChangeFieldPropertyCommand::createAction()
+{
+ if (m_alterTableAction.propertyName()=="subType") {//skip these properties
+ return 0;
+ }
+ return new KexiDB::AlterTableHandler::ChangeFieldPropertyAction( m_alterTableAction );
+}
+
+//--------------------------------------------------------
+
+RemoveFieldCommand::RemoveFieldCommand( KexiTableDesignerView* view, int fieldIndex,
+ const KoProperty::Set* set)
+ : Command(view)
+ , m_alterTableAction( set ? (*set)["name"].value().toString() : QString::null,
+ set ? (*set)["uid"].value().toInt() : -1 )
+ , m_set( set ? new KoProperty::Set(*set /*deep copy*/) : 0 )
+ , m_fieldIndex(fieldIndex)
+{
+}
+
+RemoveFieldCommand::~RemoveFieldCommand()
+{
+ delete m_set;
+}
+
+QString RemoveFieldCommand::name() const
+{
+ if (m_set)
+ return i18n("Remove table field \"%1\"").arg(m_alterTableAction.fieldName());
+
+ return QString("Remove empty row at position %1").arg(m_fieldIndex);
+}
+
+void RemoveFieldCommand::execute()
+{
+// m_view->deleteField( m_fieldIndex );
+ m_view->deleteRow( m_fieldIndex );
+}
+
+void RemoveFieldCommand::unexecute()
+{
+ m_view->insertEmptyRow(m_fieldIndex);
+ if (m_set)
+ m_view->insertField( m_fieldIndex, *m_set );
+}
+
+QString RemoveFieldCommand::debugString()
+{
+ if (!m_set)
+ return name();
+
+ return name() + "\nAT ROW " + QString::number(m_fieldIndex)
+ + ", FIELD: " + (*m_set)["caption"].value().toString()
+ + QString(" (UID=%1)").arg(m_alterTableAction.uid());
+}
+
+KexiDB::AlterTableHandler::ActionBase* RemoveFieldCommand::createAction()
+{
+ return new KexiDB::AlterTableHandler::RemoveFieldAction( m_alterTableAction );
+}
+
+//--------------------------------------------------------
+
+InsertFieldCommand::InsertFieldCommand( KexiTableDesignerView* view,
+ int fieldIndex/*, const KexiDB::Field& field*/, const KoProperty::Set& set )
+ : Command(view)
+ , m_alterTableAction(0) //fieldIndex, new KexiDB::Field(field) /*deep copy*/)
+ , m_set( set ) //? new KoProperty::Set(*set) : 0 )
+{
+ KexiDB::Field *f = view->buildField( m_set );
+ if (f)
+ m_alterTableAction = new KexiDB::AlterTableHandler::InsertFieldAction(
+ fieldIndex, f, set["uid"].value().toInt());
+ else //null action
+ m_alterTableAction = new KexiDB::AlterTableHandler::InsertFieldAction(true);
+}
+
+InsertFieldCommand::~InsertFieldCommand()
+{
+ delete m_alterTableAction;
+}
+
+QString InsertFieldCommand::name() const
+{
+ return i18n("Insert table field \"%1\"").arg(m_set["caption"].value().toString());
+}
+
+void InsertFieldCommand::execute()
+{
+ m_view->insertField( m_alterTableAction->index(), /*m_alterTableAction.field(),*/ m_set );
+}
+
+void InsertFieldCommand::unexecute()
+{
+ m_view->clearRow( m_alterTableAction->index() );//m_alterTableAction.index() );
+}
+
+KexiDB::AlterTableHandler::ActionBase* InsertFieldCommand::createAction()
+{
+ return new KexiDB::AlterTableHandler::InsertFieldAction(*m_alterTableAction);
+}
+
+//--------------------------------------------------------
+
+ChangePropertyVisibilityCommand::ChangePropertyVisibilityCommand( KexiTableDesignerView* view,
+ const KoProperty::Set& set, const QCString& propertyName, bool visible)
+ : Command(view)
+ , m_alterTableAction(set.property("name").value().toString(), propertyName, visible, set["uid"].value().toInt())
+// , m_fieldUID(set["uid"].value().toInt())
+ , m_oldVisibility( set.property(propertyName).isVisible() )
+{
+ kexipluginsdbg << "ChangePropertyVisibilityCommand: " << debugString() << endl;
+}
+
+ChangePropertyVisibilityCommand::~ChangePropertyVisibilityCommand()
+{
+}
+
+QString ChangePropertyVisibilityCommand::name() const
+{
+ return QString("[internal] Change \"%1\" visibility from \"%2\" to \"%3\"")
+ .arg(m_alterTableAction.propertyName())
+ .arg(m_oldVisibility ? "true" : "false")
+ .arg(m_alterTableAction.newValue().toBool() ? "true" : "false");
+}
+
+void ChangePropertyVisibilityCommand::execute()
+{
+ m_view->changePropertyVisibility(
+ m_alterTableAction.uid(),
+ m_alterTableAction.propertyName().latin1(),
+ m_alterTableAction.newValue().toBool() );
+}
+
+void ChangePropertyVisibilityCommand::unexecute()
+{
+ m_view->changePropertyVisibility(
+ m_alterTableAction.uid(),
+ m_alterTableAction.propertyName().latin1(),
+ m_oldVisibility );
+}
+
+//--------------------------------------------------------
+
+InsertEmptyRowCommand::InsertEmptyRowCommand( KexiTableDesignerView* view, int row )
+ : Command(view)
+ , m_alterTableAction(true) //unused, null action
+ , m_row(row)
+{
+}
+
+InsertEmptyRowCommand::~InsertEmptyRowCommand()
+{
+}
+
+QString InsertEmptyRowCommand::name() const
+{
+ return QString("Insert empty row at position %1").arg(m_row);
+}
+
+void InsertEmptyRowCommand::execute()
+{
+ m_view->insertEmptyRow( m_row );
+}
+
+void InsertEmptyRowCommand::unexecute()
+{
+ // let's assume the row is empty...
+ m_view->deleteRow( m_row );
+}
+
diff --git a/kexi/plugins/tables/kexitabledesignercommands.h b/kexi/plugins/tables/kexitabledesignercommands.h
new file mode 100644
index 000000000..355aabe21
--- /dev/null
+++ b/kexi/plugins/tables/kexitabledesignercommands.h
@@ -0,0 +1,188 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXITABLEDESIGNER_COMMANDS_H
+#define KEXITABLEDESIGNER_COMMANDS_H
+
+#include <qmap.h>
+#include <qdict.h>
+#include <qptrlist.h>
+#include <qptrdict.h>
+#include <qvariant.h>
+#include <qguardedptr.h>
+
+#include <kcommand.h>
+#include <kexidb/alter.h>
+#include <koproperty/set.h>
+
+#include "kexitabledesignerview.h"
+
+class QWidget;
+class QRect;
+class QPoint;
+class QStringList;
+class QCString;
+
+namespace KexiTableDesignerCommands {
+
+//! @short Base class for all Table Designer's commands
+class Command : public KCommand
+{
+ public:
+ Command(KexiTableDesignerView* view);
+ virtual ~Command();
+
+ //! Used to collect actions data for AlterTableHandler
+ //! Can return 0 if the action should not be passed to AlterTableHandler
+ virtual KexiDB::AlterTableHandler::ActionBase* createAction() { return 0; }
+
+ virtual QString debugString() { return name(); }
+
+ protected:
+ QGuardedPtr<KexiTableDesignerView> m_view;
+};
+
+//! @short Undo/redo command used for when changing a property for a table field
+class ChangeFieldPropertyCommand : public Command
+{
+ public:
+ /*! Creates the ChangeFieldPropertyCommand object.
+ Note: we use internal "uid" property of a field (set["uid"]) to avoid problems with looking
+ for field by name when more than one field exists with the same name
+ (it's invalid but allowed in design time).
+ \a oldlistData and and \a newListData can be specified so Property::setListData() will be called
+ on execute() and unexecute().
+ */
+ ChangeFieldPropertyCommand( KexiTableDesignerView* view,
+ const KoProperty::Set& set, const QCString& propertyName,
+ const QVariant& oldValue, const QVariant& newValue,
+ KoProperty::Property::ListData* const oldListData = 0, KoProperty::Property::ListData* const newListData = 0);
+
+ virtual ~ChangeFieldPropertyCommand();
+
+ virtual QString name() const;
+ virtual void execute();
+ virtual void unexecute();
+ virtual KexiDB::AlterTableHandler::ActionBase* createAction();
+ virtual QString debugString();
+
+ protected:
+ KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction;
+ QVariant m_oldValue;
+// int m_fieldUID;
+ KoProperty::Property::ListData* m_oldListData, *m_listData;
+};
+
+//! @short Undo/redo command used when a field is removed from a table
+class RemoveFieldCommand : public Command
+{
+ public:
+ /*! Constructs RemoveFieldCommand object.
+ If \a set is 0, the action only means removing empty row (internal). */
+ RemoveFieldCommand( KexiTableDesignerView* view, int fieldIndex,
+ const KoProperty::Set* set);
+
+ virtual ~RemoveFieldCommand();
+
+ virtual QString name() const;
+ virtual void execute();
+ virtual void unexecute();
+ virtual KexiDB::AlterTableHandler::ActionBase* createAction();
+
+ virtual QString debugString();
+
+ protected:
+ KexiDB::AlterTableHandler::RemoveFieldAction m_alterTableAction;
+ KoProperty::Set* m_set;
+ int m_fieldIndex;
+};
+
+//! @short Undo/redo command used when a new field is inserted into a table
+class InsertFieldCommand : public Command
+{
+ public:
+ InsertFieldCommand( KexiTableDesignerView* view,
+ int fieldIndex/*, const KexiDB::Field& field*/, const KoProperty::Set& set );
+ virtual ~InsertFieldCommand();
+
+ virtual QString name() const;
+ virtual void execute();
+ virtual void unexecute();
+ virtual KexiDB::AlterTableHandler::ActionBase* createAction();
+
+ virtual QString debugString() {
+ return name() + "\nAT ROW " + QString::number(m_alterTableAction->index()) //m_alterTableAction.index())
+ + ", FIELD: " + m_set["caption"].value().toString(); //m_alterTableAction.field().debugString();
+ }
+
+ protected:
+ KexiDB::AlterTableHandler::InsertFieldAction *m_alterTableAction;
+ KoProperty::Set m_set;
+};
+
+
+/* ---- Internal commands follow (not used for building performing ALTER TABLE ---- */
+
+//! @short Undo/redo command used when property visibility is changed
+/*! Internal, only used in addition to property change. */
+class ChangePropertyVisibilityCommand : public Command
+{
+ public:
+ /*! Creates the ChangePropertyVisibilityCommand object.
+ Note: we use internal "uid" property of a field (set["uid"]) to avoid problems with looking
+ for field by name when more than one field exists with the same name
+ (it's invalid but allowed in design time).
+ */
+ ChangePropertyVisibilityCommand( KexiTableDesignerView* view,
+ const KoProperty::Set& set, const QCString& propertyName,
+ bool visible);
+
+ virtual ~ChangePropertyVisibilityCommand();
+
+ virtual QString name() const;
+ virtual void execute();
+ virtual void unexecute();
+
+ protected:
+ KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction;
+// int m_fieldUID;
+ bool m_oldVisibility;
+};
+
+//! @short Undo/redo command used when property visibility is changed
+/*! Internal, only used in addition to property change. */
+class InsertEmptyRowCommand : public Command
+{
+ public:
+ /*! Creates the InsertEmptyRowCommand object. */
+ InsertEmptyRowCommand( KexiTableDesignerView* view, int row );
+ virtual ~InsertEmptyRowCommand();
+
+ virtual QString name() const;
+ virtual void execute();
+ virtual void unexecute();
+
+ protected:
+ KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction;
+ int m_row;
+};
+
+}
+
+#endif
diff --git a/kexi/plugins/tables/kexitabledesignerview.cpp b/kexi/plugins/tables/kexitabledesignerview.cpp
new file mode 100644
index 000000000..7e3478ed4
--- /dev/null
+++ b/kexi/plugins/tables/kexitabledesignerview.cpp
@@ -0,0 +1,1943 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitabledesignerview.h"
+#include "kexitabledesignerview_p.h"
+#include "kexilookupcolumnpage.h"
+#include "kexitabledesignercommands.h"
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qsplitter.h>
+
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kaction.h>
+#include <kpopupmenu.h>
+#include <kmessagebox.h>
+#include <kiconeffect.h>
+
+#include <koproperty/set.h>
+#include <koproperty/utils.h>
+
+#include <kexidb/cursor.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+#include <kexidb/roweditbuffer.h>
+#include <kexidb/error.h>
+#include <kexidb/lookupfieldschema.h>
+#include <kexiutils/identifier.h>
+#include <kexiproject.h>
+#include <keximainwindow.h>
+#include <widget/tableview/kexidataawarepropertyset.h>
+#include <widget/kexicustompropertyfactory.h>
+#include <kexiutils/utils.h>
+#include <kexidialogbase.h>
+#include <kexitableview.h>
+
+//#define MAX_FIELDS 101 //nice prime number
+
+//! used only for BLOBs
+#define DEFAULT_OBJECT_TYPE_VALUE "image"
+
+//#define KexiTableDesignerView_DEBUG
+
+//! @todo remove this when BLOBs are implemented
+//#define KEXI_NO_BLOB_FIELDS
+
+using namespace KexiTableDesignerCommands;
+
+//! @internal Used in tryCastQVariant() anf canCastQVariant()
+static bool isIntegerQVariant(QVariant::Type t)
+{
+ return t==QVariant::LongLong
+ || t==QVariant::ULongLong
+ || t==QVariant::Int
+ || t==QVariant::UInt;
+}
+
+//! @internal Used in tryCastQVariant()
+static bool canCastQVariant(QVariant::Type fromType, QVariant::Type toType)
+{
+ return (fromType==QVariant::Int && toType==QVariant::UInt)
+ || (fromType==QVariant::CString && toType==QVariant::String)
+ || (fromType==QVariant::LongLong && toType==QVariant::ULongLong)
+ || ((fromType==QVariant::String || fromType==QVariant::CString)
+ && (isIntegerQVariant(toType) || toType==QVariant::Double));
+}
+
+/*! @internal
+ \return a variant value converted from \a fromVal to \a toType type.
+ Null QVariant is returned if \a fromVal's type and \a toType type
+ are incompatible. */
+static QVariant tryCastQVariant( const QVariant& fromVal, QVariant::Type toType )
+{
+ const QVariant::Type fromType = fromVal.type();
+ if (fromType == toType)
+ return fromVal;
+ if (canCastQVariant(fromType, toType) || canCastQVariant(toType, fromType)
+ || (isIntegerQVariant(fromType) && toType==QVariant::Double))
+ {
+ QVariant res( fromVal );
+ if (res.cast(toType))
+ return res;
+ }
+ return QVariant();
+}
+
+
+KexiTableDesignerView::KexiTableDesignerView(KexiMainWindow *win, QWidget *parent)
+ : KexiDataTable(win, parent, "KexiTableDesignerView", false/*not db-aware*/)
+ , KexiTableDesignerInterface()
+ , d( new KexiTableDesignerViewPrivate(this) )
+{
+ //needed for custom "identifier" property editor widget
+ KexiCustomPropertyFactory::init();
+
+ KexiDB::Connection *conn = mainWin()->project()->dbConnection();
+ d->view = dynamic_cast<KexiTableView*>(mainWidget());
+
+ d->data = new KexiTableViewData();
+ if (conn->isReadOnly())
+ d->data->setReadOnly(true);
+ d->data->setInsertingEnabled( false );
+
+ KexiTableViewColumn *col = new KexiTableViewColumn("pk", KexiDB::Field::Text, QString::null,
+ i18n("Additional information about the field"));
+ col->setIcon( KexiUtils::colorizeIconToTextColor( SmallIcon("info"), d->view->palette() ) );
+ col->setHeaderTextVisible(false);
+ col->field()->setSubType("KIcon");
+ col->setReadOnly(true);
+ d->data->addColumn( col );
+
+// col = new KexiTableViewColumn("name", KexiDB::Field::Text, i18n("Field Name"),
+ col = new KexiTableViewColumn("caption", KexiDB::Field::Text, i18n("Field Caption"),
+ i18n("Describes caption for the field"));
+// KexiUtils::Validator *vd = new KexiUtils::IdentifierValidator();
+// vd->setAcceptsEmptyValue(true);
+// col->setValidator( vd );
+ d->data->addColumn( col );
+
+ col = new KexiTableViewColumn("type", KexiDB::Field::Enum, i18n("Data Type"),
+ i18n("Describes data type for the field"));
+ d->data->addColumn( col );
+
+#ifdef KEXI_NO_BLOB_FIELDS
+//! @todo remove this later
+ QValueVector<QString> types(KexiDB::Field::LastTypeGroup-1); //don't show last type (BLOB)
+#else
+ QValueVector<QString> types(KexiDB::Field::LastTypeGroup);
+#endif
+ d->maxTypeNameTextWidth = 0;
+ QFontMetrics fm(font());
+ for (uint i=1; i<=types.count(); i++) {
+ types[i-1] = KexiDB::Field::typeGroupName(i);
+ d->maxTypeNameTextWidth = QMAX(d->maxTypeNameTextWidth, fm.width(types[i-1]));
+ }
+ col->field()->setEnumHints(types);
+
+ d->data->addColumn( col = new KexiTableViewColumn("comments", KexiDB::Field::Text, i18n("Comments"),
+ i18n("Describes additional comments for the field")) );
+
+ d->view->setSpreadSheetMode();
+
+ connect(d->data, SIGNAL(aboutToChangeCell(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)),
+ this, SLOT(slotBeforeCellChanged(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)));
+ connect(d->data, SIGNAL(rowUpdated(KexiTableItem*)),
+ this, SLOT(slotRowUpdated(KexiTableItem*)));
+ //connect(d->data, SIGNAL(aboutToInsertRow(KexiTableItem*,KexiDB::ResultInfo*,bool)),
+ // this, SLOT(slotAboutToInsertRow(KexiTableItem*,KexiDB::ResultInfo*,bool)));
+ connect(d->data, SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)),
+ this, SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)));
+
+ setMinimumSize(d->view->minimumSizeHint().width(), d->view->minimumSizeHint().height());
+ d->view->setFocus();
+
+ d->sets = new KexiDataAwarePropertySet( this, d->view );
+ connect(d->sets, SIGNAL(rowDeleted()), this, SLOT(updateActions()));
+ connect(d->sets, SIGNAL(rowInserted()), this, SLOT(slotRowInserted()));
+
+ d->contextMenuTitle = new KPopupTitle(d->view->contextMenu());
+ d->view->contextMenu()->insertItem(d->contextMenuTitle, -1, 0);
+ connect(d->view->contextMenu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowContextMenu()));
+
+ plugSharedAction("tablepart_toggle_pkey", this, SLOT(slotTogglePrimaryKey()));
+ d->action_toggle_pkey = static_cast<KToggleAction*>( sharedAction("tablepart_toggle_pkey") );
+ d->action_toggle_pkey->plug(d->view->contextMenu(), 1); //add at the beginning
+ d->view->contextMenu()->insertSeparator(2);
+ setAvailable("tablepart_toggle_pkey", !conn->isReadOnly());
+
+#ifndef KEXI_NO_UNDOREDO_ALTERTABLE
+ plugSharedAction("edit_undo", this, SLOT(slotUndo()));
+ plugSharedAction("edit_redo", this, SLOT(slotRedo()));
+ setAvailable("edit_undo", false);
+ setAvailable("edit_redo", false);
+ connect(d->history, SIGNAL(commandExecuted(KCommand*)), this, SLOT(slotCommandExecuted(KCommand*)));
+#endif
+
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(QString::null); //to create the tab
+ KexiUtils::connectPushButtonActionForDebugWindow(
+ "simulateAlterTableExecution", this, SLOT(slotSimulateAlterTableExecution()));
+ KexiUtils::connectPushButtonActionForDebugWindow(
+ "executeRealAlterTable", this, SLOT(executeRealAlterTable()));
+#endif
+}
+
+KexiTableDesignerView::~KexiTableDesignerView()
+{
+// removeCurrentPropertySet();
+ delete d;
+}
+
+void KexiTableDesignerView::initData()
+{
+ //add column data
+// d->data->clear();
+ d->data->deleteAllRows();
+ int tableFieldCount = 0;
+ d->primaryKeyExists = false;
+
+ if (tempData()->table) {
+ tableFieldCount = tempData()->table->fieldCount();
+//not needed d->sets->clear(tableFieldCount);
+
+ //recreate table data rows
+ for(int i=0; i < tableFieldCount; i++) {
+ KexiDB::Field *field = tempData()->table->field(i);
+ KexiTableItem *item = d->data->createItem(); //new KexiTableItem(0);
+ if (field->isPrimaryKey()) {
+ (*item)[COLUMN_ID_ICON] = "key";
+ d->primaryKeyExists = true;
+ }
+ else {
+ KexiDB::LookupFieldSchema *lookupFieldSchema
+ = field->table() ? field->table()->lookupFieldSchema(*field) : 0;
+ if (lookupFieldSchema && lookupFieldSchema->rowSource().type()!=KexiDB::LookupFieldSchema::RowSource::NoType
+ && !lookupFieldSchema->rowSource().name().isEmpty())
+ {
+ (*item)[COLUMN_ID_ICON] = "combo";
+ }
+ }
+ (*item)[COLUMN_ID_CAPTION] = field->captionOrName();
+ (*item)[COLUMN_ID_TYPE] = field->typeGroup()-1; //-1 because type groups are counted from 1
+ (*item)[COLUMN_ID_DESC] = field->description();
+ d->data->append(item);
+
+//later! createPropertySet( i, field );
+ }
+ }
+// else {
+// d->sets->clear();//default size
+// }
+
+ //add empty space
+// const int columnsCount = d->data->columnsCount();
+ for (int i=tableFieldCount; i<(int)d->sets->size(); i++) {
+// KexiTableItem *item = new KexiTableItem(columnsCount);//3 empty fields
+ d->data->append(d->data->createItem());
+ }
+
+ //set data for our spreadsheet: this will clear our sets
+ d->view->setData(d->data);
+
+ //now recreate property sets
+ if (tempData()->table) {
+ for(int i=0; i < tableFieldCount; i++) {
+ KexiDB::Field *field = tempData()->table->field(i);
+ createPropertySet( i, *field );
+ }
+ }
+
+ //column widths
+ d->view->setColumnWidth(COLUMN_ID_ICON, IconSize( KIcon::Small ) + 10);
+ d->view->adjustColumnWidthToContents(COLUMN_ID_CAPTION); //adjust column width
+ d->view->setColumnWidth(COLUMN_ID_TYPE, d->maxTypeNameTextWidth + 2 * d->view->rowHeight());
+ d->view->setColumnStretchEnabled( true, COLUMN_ID_DESC ); //last column occupies the rest of the area
+ const int minCaptionColumnWidth = d->view->fontMetrics().width("wwwwwwwwwww");
+ if (minCaptionColumnWidth > d->view->columnWidth(COLUMN_ID_CAPTION))
+ d->view->setColumnWidth(COLUMN_ID_CAPTION, minCaptionColumnWidth);
+
+ setDirty(false);
+ d->view->setCursorPosition(0, COLUMN_ID_CAPTION); //set @ name column
+ propertySetSwitched();
+}
+
+//! Gets subtype strings and names for type \a fieldType
+void
+KexiTableDesignerView::getSubTypeListData(KexiDB::Field::TypeGroup fieldTypeGroup,
+ QStringList& stringsList, QStringList& namesList)
+{
+/* disabled - "mime" is moved from subType to "objectType" custom property
+ if (fieldTypeGroup==KexiDB::Field::BLOBGroup) {
+ // special case: BLOB type uses "mime-based" subtypes
+//! @todo hardcoded!
+ stringsList << "image";
+ namesList << i18n("Image object type", "Image");
+ }
+ else {*/
+ stringsList = KexiDB::typeStringsForGroup(fieldTypeGroup);
+ namesList = KexiDB::typeNamesForGroup(fieldTypeGroup);
+// }
+ kexipluginsdbg << "KexiTableDesignerView::getSubTypeListData(): subType strings: " <<
+ stringsList.join("|") << "\nnames: " << namesList.join("|") << endl;
+}
+
+KoProperty::Set *
+KexiTableDesignerView::createPropertySet( int row, const KexiDB::Field& field, bool newOne )
+{
+ QString typeName = "KexiDB::Field::" + field.typeGroupString();
+ KoProperty::Set *set = new KoProperty::Set(d->sets, typeName);
+ if (mainWin()->project()->dbConnection()->isReadOnly())
+ set->setReadOnly( true );
+// connect(buff,SIGNAL(propertyChanged(KexiPropertyBuffer&,KexiProperty&)),
+// this, SLOT(slotPropertyChanged(KexiPropertyBuffer&,KexiProperty&)));
+
+ KoProperty::Property *prop;
+
+ set->addProperty(prop = new KoProperty::Property("uid", d->generateUniqueId(), ""));
+ prop->setVisible(false);
+
+ //meta-info for property editor
+ set->addProperty(prop = new KoProperty::Property("this:classString", i18n("Table field")) );
+ prop->setVisible(false);
+ set->addProperty(prop = new KoProperty::Property("this:iconName",
+//! \todo add table_field icon
+ "lineedit" //"table_field"
+ ));
+ prop->setVisible(false);
+ set->addProperty(prop = new KoProperty::Property("this:useCaptionAsObjectName",
+ QVariant(true, 1), QString::null)); //we want "caption" to be displayed in the header, not name
+ prop->setVisible(false);
+
+ //name
+ set->addProperty(prop
+ = new KoProperty::Property("name", QVariant(field.name()), i18n("Name"),
+ QString::null, KexiCustomPropertyFactory::Identifier) );
+
+ //type
+ set->addProperty( prop
+ = new KoProperty::Property("type", QVariant(field.type()), i18n("Type")) );
+#ifndef KexiTableDesignerView_DEBUG
+ prop->setVisible(false);//always hidden
+#endif
+
+ //subtype
+ QStringList typeStringList, typeNameList;
+ getSubTypeListData(field.typeGroup(), typeStringList, typeNameList);
+/* disabled - "mime" is moved from subType to "objectType" custom property
+ QString subTypeValue;
+ if (field.typeGroup()==KexiDB::Field::BLOBGroup) {
+// special case: BLOB type uses "mime-based" subtypes
+//! @todo this should be retrieved from KexiDB::Field when BLOB supports many different mimetypes
+ subTypeValue = slist.first();
+ }
+ else {*/
+ QString subTypeValue = field.typeString();
+ //}
+ set->addProperty(prop = new KoProperty::Property("subType",
+ typeStringList, typeNameList, subTypeValue, i18n("Subtype")));
+
+ // objectType
+ QStringList objectTypeStringList, objectTypeNameList;
+//! @todo this should be retrieved from KexiDB::Field when BLOB supports many different mimetypes
+ objectTypeStringList << "image";
+ objectTypeNameList << i18n("Image object type", "Image");
+ QString objectTypeValue( field.customProperty("objectType").toString() );
+ if (objectTypeValue.isEmpty())
+ objectTypeValue = DEFAULT_OBJECT_TYPE_VALUE;
+ set->addProperty(prop = new KoProperty::Property("objectType",
+ objectTypeStringList, objectTypeNameList, objectTypeValue, i18n("Subtype")/*todo other i18n string?*/));
+
+ set->addProperty( prop
+ = new KoProperty::Property("caption", QVariant(field.caption()), i18n("Caption") ) );
+ prop->setVisible(false);//always hidden
+
+ set->addProperty( prop
+ = new KoProperty::Property("description", QVariant(field.description())) );
+ prop->setVisible(false);//always hidden
+
+ set->addProperty(prop
+ = new KoProperty::Property("unsigned", QVariant(field.isUnsigned(), 4), i18n("Unsigned Number")));
+
+ set->addProperty( prop
+ = new KoProperty::Property("length", (int)field.length()/*200?*/, i18n("Length")));
+
+ set->addProperty( prop
+ = new KoProperty::Property("precision", (int)field.precision()/*200?*/, i18n("Precision")));
+#ifdef KEXI_NO_UNFINISHED
+ prop->setVisible(false);
+#endif
+ set->addProperty( prop
+ = new KoProperty::Property("visibleDecimalPlaces", field.visibleDecimalPlaces(), i18n("Visible Decimal Places")));
+ prop->setOption("min", -1);
+ prop->setOption("minValueText", i18n("Auto Decimal Places","Auto"));
+
+//! @todo set reasonable default for column width
+ set->addProperty( prop
+ = new KoProperty::Property("width", (int)field.width()/*200?*/, i18n("Column Width")));
+#ifdef KEXI_NO_UNFINISHED
+ prop->setVisible(false);
+#endif
+
+ set->addProperty( prop
+ = new KoProperty::Property("defaultValue", field.defaultValue(), i18n("Default Value"),
+ QString::null,
+//! @todo use "Variant" type here when supported by KoProperty
+ (KoProperty::PropertyType)field.variantType()) );
+ prop->setOption("3rdState", i18n("None"));
+// prop->setVisible(false);
+
+ set->addProperty( prop
+ = new KoProperty::Property("primaryKey", QVariant(field.isPrimaryKey(), 4), i18n("Primary Key")));
+ prop->setIcon("key");
+
+ set->addProperty( prop
+ = new KoProperty::Property("unique", QVariant(field.isUniqueKey(), 4), i18n("Unique")));
+
+ set->addProperty( prop
+ = new KoProperty::Property("notNull", QVariant(field.isNotNull(), 4), i18n("Required")));
+
+ set->addProperty( prop
+ = new KoProperty::Property("allowEmpty", QVariant(!field.isNotEmpty(), 4), i18n("Allow Zero\nSize")));
+
+ set->addProperty( prop
+ = new KoProperty::Property("autoIncrement", QVariant(field.isAutoIncrement(), 4), i18n("Autonumber")));
+ prop->setIcon("autonumber");
+
+ set->addProperty( prop
+ = new KoProperty::Property("indexed", QVariant(field.isIndexed(), 4), i18n("Indexed")));
+
+ //- properties related to lookup columns (used and set by the "lookup column" tab in the property pane)
+ KexiDB::LookupFieldSchema *lookupFieldSchema = field.table() ? field.table()->lookupFieldSchema(field) : 0;
+ set->addProperty( prop = new KoProperty::Property("rowSource",
+ lookupFieldSchema ? lookupFieldSchema->rowSource().name() : QString::null, i18n("Row Source")));
+ prop->setVisible(false);
+
+ set->addProperty( prop = new KoProperty::Property("rowSourceType",
+ lookupFieldSchema ? lookupFieldSchema->rowSource().typeName() : QString::null, i18n("Row Source\nType")));
+ prop->setVisible(false);
+
+ set->addProperty( prop
+ = new KoProperty::Property("boundColumn",
+ lookupFieldSchema ? lookupFieldSchema->boundColumn() : -1, i18n("Bound Column")));
+ prop->setVisible(false);
+
+//! @todo this is backward-compatible code for "single visible column" implementation
+//! for multiple columns, only the first is displayed, so there is a data loss is GUI is used
+//! -- special koproperty editor needed
+ int visibleColumn = -1;
+ if (lookupFieldSchema && !lookupFieldSchema->visibleColumns().isEmpty())
+ visibleColumn = lookupFieldSchema->visibleColumns().first();
+ set->addProperty( prop
+ = new KoProperty::Property("visibleColumn", visibleColumn, i18n("Visible Column")));
+ prop->setVisible(false);
+
+//! @todo support columnWidths(), columnHeadersVisible(), maximumListRows(), limitToList(), displayWidget()
+
+ //----
+ d->updatePropertiesVisibility(field.type(), *set);
+
+ connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)),
+ this, SLOT(slotPropertyChanged(KoProperty::Set&, KoProperty::Property&)));
+
+ d->sets->insert(row, set, newOne);
+ return set;
+}
+
+void KexiTableDesignerView::updateActions(bool activated)
+{
+ Q_UNUSED(activated);
+/*! \todo check if we can set pkey for this column type (eg. BLOB?) */
+ setAvailable("tablepart_toggle_pkey", propertySet()!=0 && !mainWin()->project()->dbConnection()->isReadOnly());
+ if (!propertySet())
+ return;
+ KoProperty::Set &set = *propertySet();
+ d->slotTogglePrimaryKeyCalled = true;
+ d->action_toggle_pkey->setChecked(set["primaryKey"].value().toBool());
+ d->slotTogglePrimaryKeyCalled = false;
+}
+
+void KexiTableDesignerView::slotUpdateRowActions(int row)
+{
+ KexiDataTable::slotUpdateRowActions(row);
+ updateActions();
+}
+
+void KexiTableDesignerView::slotTogglePrimaryKey()
+{
+ if (d->slotTogglePrimaryKeyCalled)
+ return;
+ d->slotTogglePrimaryKeyCalled = true;
+ if (!propertySet())
+ return;
+ KoProperty::Set &set = *propertySet();
+ bool isSet = !set["primaryKey"].value().toBool();
+ set.changeProperty("primaryKey", QVariant(isSet,1)); //this will update all related properties as well
+/* CommandGroup *setPrimaryKeyCommand;
+ if (isSet) {
+ setPrimaryKeyCommand = new CommandGroup(i18n("Set primary key for field \"%1\"")
+ .arg(set["name"].value().toString()) );
+ }
+ else {
+ setPrimaryKeyCommand = new CommandGroup(i18n("Unset primary key for field \"%1\"")
+ .arg(set["name"].value().toString()) );
+ }
+ switchPrimaryKey(set, isSet, false, setPrimaryKeyCommand);*/
+ //addHistoryCommand( setPrimaryKeyCommand, false /* !execute */ );
+ d->slotTogglePrimaryKeyCalled = false;
+}
+
+void KexiTableDesignerView::switchPrimaryKey(KoProperty::Set &propertySet,
+ bool set, bool aWasPKey, CommandGroup* commandGroup)
+{
+ const bool was_pkey = aWasPKey || propertySet["primaryKey"].value().toBool();
+// propertySet["primaryKey"] = QVariant(set, 1);
+ d->setPropertyValueIfNeeded( propertySet, "primaryKey", QVariant(set,1), commandGroup );
+ if (&propertySet==this->propertySet()) {
+ //update action and icon @ column 0 (only if we're changing current property set)
+ d->action_toggle_pkey->setChecked(set);
+ if (d->view->selectedItem()) {
+ //show key in the table
+ d->view->data()->clearRowEditBuffer();
+ d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_ICON,
+ QVariant(set ? "key" : ""));
+ d->view->data()->saveRowChanges(*d->view->selectedItem(), true);
+ }
+ if (was_pkey || set) //change flag only if we're setting pk or really clearing it
+ d->primaryKeyExists = set;
+ }
+
+ if (set) {
+ //primary key is set, remove old pkey if exists
+ KoProperty::Set *s = 0;
+ int i;
+ const int count = (int)d->sets->size();
+ for (i=0; i<count; i++) {
+ s = d->sets->at(i);
+ if (s && s!=&propertySet && (*s)["primaryKey"].value().toBool() && i!=d->view->currentRow())
+ break;
+ }
+ if (i<count) {//remove
+ //(*s)["autoIncrement"] = QVariant(false, 0);
+ d->setPropertyValueIfNeeded( *s, "autoIncrement", QVariant(false,0), commandGroup );
+ //(*s)["primaryKey"] = QVariant(false, 0);
+ d->setPropertyValueIfNeeded( *s, "primaryKey", QVariant(false,0), commandGroup );
+ //remove key from table
+ d->view->data()->clearRowEditBuffer();
+ KexiTableItem *item = d->view->itemAt(i);
+ if (item) {
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant());
+ d->view->data()->saveRowChanges(*item, true);
+ }
+ }
+ //set unsigned big-integer type
+// d->view->data()->saveRowChanges(*d->view->selectedItem());
+ d->slotBeforeCellChanged_enabled = false;
+ d->view->data()->clearRowEditBuffer();
+ d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE,
+ QVariant(KexiDB::Field::IntegerGroup-1/*counting from 0*/));
+// QVariant(KexiDB::Field::typeGroupName(KexiDB::Field::IntegerGroup)));
+ d->view->data()->saveRowChanges(*d->view->selectedItem(), true);
+ //propertySet["subType"] = KexiDB::Field::typeString(KexiDB::Field::BigInteger);
+ d->setPropertyValueIfNeeded( propertySet, "subType", KexiDB::Field::typeString(KexiDB::Field::BigInteger),
+ commandGroup );
+ //propertySet["unsigned"] = QVariant(true,4);
+ d->setPropertyValueIfNeeded( propertySet, "unsigned", QVariant(true,4), commandGroup );
+/*todo*/
+ d->slotBeforeCellChanged_enabled = true;
+ }
+ updateActions();
+}
+
+/*void KexiTableDesignerView::slotCellSelected(int, int row)
+{
+ kdDebug() << "KexiTableDesignerView::slotCellSelected()" << endl;
+ if(row == m_row)
+ return;
+ m_row = row;
+ propertyBufferSwitched();
+}*/
+
+tristate KexiTableDesignerView::beforeSwitchTo(int mode, bool &dontStore)
+{
+ if (!d->view->acceptRowEdit())
+ return false;
+/* if (mode==Kexi::DesignViewMode) {
+ initData();
+ return true;
+ }
+ else */
+ tristate res = true;
+ if (mode==Kexi::DataViewMode) {
+ if (!dirty() && parentDialog()->neverSaved()) {
+ KMessageBox::sorry(this, i18n("Cannot switch to data view, because table design is empty.\n"
+ "First, please create your design.") );
+ return cancelled;
+ }
+//<temporary>
+ else if (dirty() && !parentDialog()->neverSaved()) {
+// cancelled = (KMessageBox::No == KMessageBox::questionYesNo(this, i18n("Saving changes for existing table design is not yet supported.\nDo you want to discard your changes now?")));
+
+// KexiDB::Connection *conn = mainWin()->project()->dbConnection();
+ bool emptyTable;
+ int r = KMessageBox::warningYesNoCancel(this,
+ i18n("Saving changes for existing table design is now required.")
+ + "\n" + d->messageForSavingChanges(emptyTable, /* skip warning? */!isPhysicalAlteringNeeded()),
+ QString::null,
+ KStdGuiItem::save(), KStdGuiItem::discard(), QString::null,
+ KMessageBox::Notify|KMessageBox::Dangerous);
+ if (r == KMessageBox::Cancel)
+ res = cancelled;
+ else
+ res = true;
+ dontStore = (r!=KMessageBox::Yes);
+ if (!dontStore)
+ d->dontAskOnStoreData = true;
+// if (dontStore)
+// setDirty(false);
+ }
+//</temporary>
+ //todo
+ return res;
+ }
+ else if (mode==Kexi::TextViewMode) {
+ //todo
+ }
+ return res;
+}
+
+tristate KexiTableDesignerView::afterSwitchFrom(int mode)
+{
+ if (mode==Kexi::NoViewMode || mode==Kexi::DataViewMode) {
+ initData();
+ }
+ return true;
+}
+
+KoProperty::Set *KexiTableDesignerView::propertySet()
+{
+ return d->sets ? d->sets->currentPropertySet() : 0;
+}
+
+/*
+void KexiTableDesignerView::removeCurrentPropertySet()
+{
+ const int r = d->view->currentRow();
+ KoProperty::Set *buf = d->sets.at(r);
+ if (!buf)
+ return;
+ buf->debug();
+// m_currentBufferCleared = true;
+ d->sets.remove(r);
+ propertysetswitched();
+// delete buf;
+// m_currentBufferCleared = false;
+}
+*/
+
+void KexiTableDesignerView::slotBeforeCellChanged(
+ KexiTableItem *item, int colnum, QVariant& newValue, KexiDB::ResultInfo* /*result*/)
+{
+ if (!d->slotBeforeCellChanged_enabled)
+ return;
+// kdDebug() << d->view->selectedItem() << " " << item
+ //<< " " << d->sets->at( d->view->currentRow() ) << " " << propertySet() << endl;
+ if (colnum==COLUMN_ID_CAPTION) {//'caption'
+// if (!item->at(1).toString().isEmpty() && item->at(1).isNull()) {
+ //if 'type' is not filled yet
+ if (item->at(COLUMN_ID_TYPE).isNull()) {
+ //auto select 1st row of 'type' column
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant((int)0));
+ }
+
+ KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item);
+ if (propertySetForItem) {
+ d->addHistoryCommand_in_slotPropertyChanged_enabled = false; //because we'll add the two changes as one KMacroCommand
+ QString oldName( propertySetForItem->property("name").value().toString() );
+ QString oldCaption( propertySetForItem->property("caption").value().toString() );
+
+ //we need to create the action now as set["name"] will be changed soon..
+ ChangeFieldPropertyCommand *changeCaptionCommand
+ = new ChangeFieldPropertyCommand( this, *propertySetForItem, "caption", oldCaption, newValue);
+
+ //update field caption and name
+ propertySetForItem->changeProperty("caption", newValue);
+ propertySetForItem->changeProperty("name", newValue); // "name" prop. is of custom type Identifier, so this assignment
+ // will automatically convert newValue to an valid identifier
+
+ //remember this action containing 2 subactions
+ CommandGroup *changeCaptionAndNameCommand = new CommandGroup(
+ i18n("Change \"%1\" field's name to \"%2\" and caption from \"%3\" to \"%4\"")
+ .arg(oldName).arg(propertySetForItem->property("name").value().toString())
+ .arg(oldCaption).arg(newValue.toString() ));
+ changeCaptionAndNameCommand->addCommand( changeCaptionCommand );
+// new ChangeFieldPropertyCommand( this, *propertySetForItem,
+ // "caption", oldCaption, newValue)
+ // );
+ changeCaptionAndNameCommand->addCommand(
+ new ChangeFieldPropertyCommand( this, *propertySetForItem,
+ "name", oldName, propertySetForItem->property("name").value().toString())
+ );
+ addHistoryCommand( changeCaptionAndNameCommand, false /* !execute */ );
+
+ d->addHistoryCommand_in_slotPropertyChanged_enabled = true;
+ }
+ }
+ else if (colnum==COLUMN_ID_TYPE) {//'type'
+ if (newValue.isNull()) {
+ //'type' col will be cleared: clear all other columns as well
+ d->slotBeforeCellChanged_enabled = false;
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant());
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, QVariant(QString::null));
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC, QVariant());
+ d->slotBeforeCellChanged_enabled = true;
+ return;
+ }
+
+ KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item);
+ if (!propertySetForItem)
+ return;
+
+ KoProperty::Set &set = *propertySetForItem; //propertySet();
+
+ //'type' col is changed (existed before)
+ //-get type group number
+ KexiDB::Field::TypeGroup fieldTypeGroup;
+ int i_fieldTypeGroup = newValue.toInt()+1/*counting from 1*/;
+ if (i_fieldTypeGroup < 1 || i_fieldTypeGroup >
+#ifdef KEXI_NO_BLOB_FIELDS
+//! @todo remove this later
+ (int)KexiDB::Field::LastTypeGroup-1) //don't show last (BLOB) type
+#else
+ (int)KexiDB::Field::LastTypeGroup)
+#endif
+ return;
+ fieldTypeGroup = static_cast<KexiDB::Field::TypeGroup>(i_fieldTypeGroup);
+
+ //-get 1st type from this group, and update 'type' property
+ KexiDB::Field::Type fieldType = KexiDB::defaultTypeForGroup( fieldTypeGroup );
+ if (fieldType==KexiDB::Field::InvalidType)
+ fieldType = KexiDB::Field::Text;
+//moved down set["type"] = (int)fieldType;
+// set["subType"] = KexiDB::Field::typeName(fieldType);
+
+ //-get subtypes for this type: keys (slist) and names (nlist)
+ QStringList slist, nlist;
+ getSubTypeListData(fieldTypeGroup, slist, nlist);
+
+ QString subTypeValue;
+/* disabled - "mime" is moved from subType to "objectType" custom property
+ if (fieldType==KexiDB::Field::BLOB) {
+ // special case: BLOB type uses "mime-based" subtypes
+ subTypeValue = slist.first();
+ }
+ else {*/
+ subTypeValue = KexiDB::Field::typeString(fieldType);
+ //}
+ KoProperty::Property *subTypeProperty = &set["subType"];
+ kexipluginsdbg << subTypeProperty->value() << endl;
+
+ // *** this action contains subactions ***
+ CommandGroup *changeDataTypeCommand = new CommandGroup(
+ i18n("Change data type for field \"%1\" to \"%2\"")
+ .arg(set["name"].value().toString()).arg( KexiDB::Field::typeName( fieldType ) ) );
+
+//kexipluginsdbg << "++++++++++" << slist << nlist << endl;
+
+ //update subtype list and value
+ const bool forcePropertySetReload
+ = KexiDB::Field::typeGroup( KexiDB::Field::typeForString(subTypeProperty->value().toString()) )
+ != fieldTypeGroup; //<-- ?????
+// const bool forcePropertySetReload = set["type"].value().toInt() != (int)fieldTypeGroup;
+ const bool useListData = slist.count() > 1; //disabled-> || fieldType==KexiDB::Field::BLOB;
+
+ if (!useListData) {
+ slist.clear(); //empty list will be passed
+ nlist.clear();
+ }
+ d->setPropertyValueIfNeeded( set, "type", (int)fieldType, changeDataTypeCommand,
+ false /*!forceAddCommand*/, true /*rememberOldValue*/);
+
+ // notNull and defaultValue=false is reasonable for boolean type
+ if (fieldType == KexiDB::Field::Boolean) {
+//! @todo maybe this is good for other data types as well?
+ d->setPropertyValueIfNeeded( set, "notNull", QVariant(true, 1), changeDataTypeCommand,
+ false /*!forceAddCommand*/, false /*!rememberOldValue*/);
+ d->setPropertyValueIfNeeded( set, "defaultValue", QVariant(false, 1), changeDataTypeCommand,
+ false /*!forceAddCommand*/, false /*!rememberOldValue*/);
+ }
+
+/* if (useListData) {
+ {
+ subTypeProperty->setListData( slist, nlist );
+ }
+ else {
+ subTypeProperty->setListData( 0 );
+ }*/
+ if (set["primaryKey"].value().toBool()==true) {
+ //primary keys require big int, so if selected type is not integer- remove PK
+ if (fieldTypeGroup != KexiDB::Field::IntegerGroup) {
+ /*not needed, line below will do the work
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant());
+ d->view->data()->saveRowChanges(*item); */
+ //set["primaryKey"] = QVariant(false, 1);
+ d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(false, 1), changeDataTypeCommand );
+//! @todo should we display (passive?) dialog informing about cleared pkey?
+ }
+ }
+// if (useListData)
+// subTypeProperty->setValue( subTypeValue, false/*!rememberOldValue*/ );
+ d->setPropertyValueIfNeeded( set, "subType", subTypeValue,
+ changeDataTypeCommand, false, false /*!rememberOldValue*/,
+ &slist, &nlist );
+
+ if (d->updatePropertiesVisibility(fieldType, set, changeDataTypeCommand) || forcePropertySetReload) {
+ //properties' visiblility changed: refresh prop. set
+ propertySetReloaded(true);
+ }
+
+ addHistoryCommand( changeDataTypeCommand, false /* !execute */ );
+ }
+ else if (colnum==COLUMN_ID_DESC) {//'description'
+ KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item);
+ if (!propertySetForItem)
+ return;
+ //update field desc.
+ QVariant oldValue((*propertySetForItem)["description"].value());
+ kexipluginsdbg << oldValue << endl;
+ propertySetForItem->changeProperty("description", newValue);
+ /*moved addHistoryCommand(
+ new ChangeFieldPropertyCommand( this, *propertySetForItem,
+ "description", oldValue, newValue ), false);*/
+ }
+}
+
+void KexiTableDesignerView::slotRowUpdated(KexiTableItem *item)
+{
+ const int row = d->view->data()->findRef(item);
+ if (row < 0)
+ return;
+
+ setDirty();
+
+ //-check if the row was empty before updating
+ //if yes: we want to add a property set for this new row (field)
+ QString fieldCaption( item->at(COLUMN_ID_CAPTION).toString() );
+ const bool prop_set_allowed = !item->at(COLUMN_ID_TYPE).isNull();
+
+ if (!prop_set_allowed && d->sets->at(row)/*propertySet()*/) {
+ //there is a property set, but it's not allowed - remove it:
+ d->sets->remove( row ); //d->sets->removeCurrentPropertySet();
+
+ //clear 'type' column:
+ d->view->data()->clearRowEditBuffer();
+// d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, QVariant());
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant());
+ d->view->data()->saveRowChanges(*item);
+
+ } else if (prop_set_allowed && !d->sets->at(row)/*propertySet()*/) {
+ //-- create a new field:
+ KexiDB::Field::TypeGroup fieldTypeGroup = static_cast<KexiDB::Field::TypeGroup>(
+ item->at(COLUMN_ID_TYPE).toInt()+1/*counting from 1*/ );
+ int intFieldType = KexiDB::defaultTypeForGroup( fieldTypeGroup );
+ if (intFieldType==0)
+ return;
+
+ QString description( item->at(COLUMN_ID_DESC).toString() );
+
+//todo: check uniqueness:
+ QString fieldName( KexiUtils::string2Identifier(fieldCaption) );
+
+ KexiDB::Field::Type fieldType = KexiDB::intToFieldType( intFieldType );
+ KexiDB::Field field( //tmp
+ fieldName,
+ fieldType,
+ KexiDB::Field::NoConstraints,
+ KexiDB::Field::NoOptions,
+ /*length*/0,
+ /*precision*/0,
+ /*defaultValue*/QVariant(),
+ fieldCaption,
+ description,
+ /*width*/0);
+// m_newTable->addField( field );
+
+ // reasonable case for boolean type: set notNull flag and "false" as default value
+ if (fieldType == KexiDB::Field::Boolean) {
+ field.setNotNull( true );
+ field.setDefaultValue( QVariant(false, 0) );
+ }
+
+ kexipluginsdbg << "KexiTableDesignerView::slotRowUpdated(): " << field.debugString() << endl;
+
+ //create a new property set:
+ KoProperty::Set *newSet = createPropertySet( row, field, true );
+//moved
+ //add a special property indicating that this is brand new buffer,
+ //not just changed
+// KoProperty::Property* prop = new KoProperty::Property("newrow", QVariant());
+// prop->setVisible(false);
+// newbuff->addProperty( prop );
+
+ //refresh property editor:
+ propertySetSwitched();
+
+ if (row>=0) {
+ if (d->addHistoryCommand_in_slotRowUpdated_enabled) {
+ addHistoryCommand( new InsertFieldCommand( this, row, *newSet /*propertySet()*/ ), //, field /*will be copied*/
+ false /* !execute */ );
+ }
+ }
+ else {
+ kexipluginswarn << "KexiTableDesignerView::slotRowUpdated() row # not found !" << endl;
+ }
+ }
+}
+
+void KexiTableDesignerView::updateActions()
+{
+ updateActions(false);
+}
+
+void KexiTableDesignerView::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property)
+{
+// if (!d->slotPropertyChanged_enabled)
+// return;
+ const QCString pname = property.name();
+ kexipluginsdbg << "KexiTableDesignerView::slotPropertyChanged(): " << pname << " = " << property.value()
+ << " (oldvalue = " << property.oldValue() << ")" << endl;
+
+ // true is PK should be altered
+ bool changePrimaryKey = false;
+ // true is PK should be set to true, otherwise unset
+ bool setPrimaryKey = false;
+
+ if (pname=="primaryKey" && d->slotPropertyChanged_primaryKey_enabled) {
+ changePrimaryKey = true;
+ setPrimaryKey = property.value().toBool();
+ }
+
+ // update "lookup column" icon
+ if (pname=="rowSource" || pname=="rowSourceType") {
+//! @todo indicate invalid definitions of lookup columns as well using a special icon
+//! (e.g. due to missing data source)
+ const int row = d->sets->findRowForPropertyValue("uid", set["uid"].value().toInt());
+ KexiTableItem *item = d->view->itemAt(row);
+ if (item)
+ d->updateIconForItem(*item, set);
+ }
+
+ //setting autonumber requires setting PK as well
+ CommandGroup *setAutonumberCommand = 0;
+ CommandGroup *toplevelCommand = 0;
+ if (pname=="autoIncrement" && property.value().toBool()==true) {
+ if (set["primaryKey"].value().toBool()==false) {//we need PKEY here!
+ QString msg = QString("<p>")
+ +i18n("Setting autonumber requires primary key to be set for current field.")+"</p>";
+ if (d->primaryKeyExists)
+ msg += (QString("<p>")+ i18n("Previous primary key will be removed.")+"</p>");
+ msg += (QString("<p>")
+ +i18n("Do you want to create primary key for current field? "
+ "Click \"Cancel\" to cancel setting autonumber.")+"</p>");
+
+ if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg,
+ i18n("Setting Autonumber Field"),
+ KGuiItem(i18n("Create &Primary Key"), "key"), KStdGuiItem::cancel() ))
+ {
+ changePrimaryKey = true;
+ setPrimaryKey = true;
+ //switchPrimaryKey(set, true);
+ // this will be toplevel command
+ setAutonumberCommand = new CommandGroup(
+ i18n("Assign autonumber for field \"%1\"").arg(set["name"].value().toString()) );
+ toplevelCommand = setAutonumberCommand;
+ d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(true,1), setAutonumberCommand );
+ }
+ else {
+ setAutonumberCommand = new CommandGroup(
+ i18n("Remove autonumber from field \"%1\"").arg(set["name"].value().toString()) );
+ //d->slotPropertyChanged_enabled = false;
+// set["autoIncrement"].setValue( QVariant(false,1), false/*don't save old*/);
+// d->slotPropertyChanged_enabled = true;
+ d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(false,1), setAutonumberCommand,
+ true /*forceAddCommand*/, false/*rememberOldValue*/ );
+ addHistoryCommand( setAutonumberCommand, false /* !execute */ );
+ return;
+ }
+ }
+ }
+
+ //clear PK when these properties were set to false:
+ if ((pname=="indexed" || pname=="unique" || pname=="notNull")
+ && set["primaryKey"].value().toBool() && property.value().toBool()==false)
+ {
+//! @todo perhaps show a hint in help panel telling what happens?
+ changePrimaryKey = true;
+ setPrimaryKey = false;
+ // this will be toplevel command
+ CommandGroup *unsetIndexedOrUniquOrNotNullCommand = new CommandGroup(
+ i18n("Set \"%1\" property for field \"%2\"").arg(property.caption()).arg(set["name"].value().toString()) );
+ toplevelCommand = unsetIndexedOrUniquOrNotNullCommand;
+ d->setPropertyValueIfNeeded( set, pname, QVariant(false,1), unsetIndexedOrUniquOrNotNullCommand );
+ if (pname=="notNull") {
+//? d->setPropertyValueIfNeeded( set, "notNull", QVariant(true,1), unsetIndexedOrUniquOrNotNullCommand );
+ d->setPropertyValueIfNeeded( set, "unique", QVariant(false,1), unsetIndexedOrUniquOrNotNullCommand );
+ }
+ }
+
+ if (pname=="defaultValue") {
+ KexiDB::Field::Type type = KexiDB::intToFieldType( set["type"].value().toInt() );
+ set["defaultValue"].setType((KoProperty::PropertyType)KexiDB::Field::variantType(type));
+ }
+
+ if (pname=="subType" && d->slotPropertyChanged_subType_enabled) {
+ d->slotPropertyChanged_subType_enabled = false;
+ if (set["primaryKey"].value().toBool()==true
+ && property.value().toString()!=KexiDB::Field::typeString(KexiDB::Field::BigInteger))
+ {
+ kexipluginsdbg << "INVALID " << property.value().toString() << endl;
+// if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg,
+// i18n("This field has promary key assigned. Setting autonumber field"),
+// KGuiItem(i18n("Create &Primary Key"), "key"), KStdGuiItem::cancel() ))
+
+ }
+ KexiDB::Field::Type type = KexiDB::intToFieldType( set["type"].value().toInt() );
+ QString typeName;
+/* disabled - "mime" is moved from subType to "objectType" custom property
+ if (type==KexiDB::Field::BLOB) { //special case
+ //find i18n'd text
+ QStringList stringsList, namesList;
+ getSubTypeListData(KexiDB::Field::BLOBGroup, stringsList, namesList);
+ const int stringIndex = stringsList.findIndex( property.value().toString() );
+ if (-1 == stringIndex || stringIndex>=(int)namesList.count())
+ typeName = property.value().toString(); //for sanity
+ else
+ typeName = namesList[stringIndex];
+ }
+ else {*/
+ typeName = KexiDB::Field::typeName( KexiDB::Field::typeForString(property.value().toString()) );
+// }
+// kdDebug() << property.value().toString() << endl;
+// kdDebug() << set["type"].value() << endl;
+// if (KexiDB::Field::typeGroup( set["type"].value().toInt() ) == (int)KexiDB::Field::TextGroup) {
+ CommandGroup* changeFieldTypeCommand = new CommandGroup(
+ i18n("Change type for field \"%1\" to \"%2\"").arg(set["name"].value().toString())
+ .arg(typeName) );
+ d->setPropertyValueIfNeeded( set, "subType", property.value(), property.oldValue(),
+ changeFieldTypeCommand );
+
+ kexipluginsdbg << set["type"].value() << endl;
+ const KexiDB::Field::Type newType = KexiDB::Field::typeForString(property.value().toString());
+ set["type"].setValue( newType );
+
+ // cast "defaultValue" property value to a new type
+ QVariant oldDefVal( set["defaultValue"].value() );
+ QVariant newDefVal( tryCastQVariant(oldDefVal, KexiDB::Field::variantType(type)) );
+ if (oldDefVal.type()!=newDefVal.type())
+ set["defaultValue"].setType( newDefVal.type() );
+ d->setPropertyValueIfNeeded( set, "defaultValue", newDefVal, newDefVal,
+ changeFieldTypeCommand );
+
+ d->updatePropertiesVisibility(newType, set);
+ //properties' visiblility changed: refresh prop. set
+ propertySetReloaded(true);
+ d->slotPropertyChanged_subType_enabled = true;
+
+ addHistoryCommand( changeFieldTypeCommand, false /* !execute */ );
+ return;
+// }
+// d->slotPropertyChanged_subType_enabled = true;
+// return;
+ }
+
+ if (d->addHistoryCommand_in_slotPropertyChanged_enabled && !changePrimaryKey/*we'll add multiple commands for PK*/) {
+ addHistoryCommand( new ChangeFieldPropertyCommand(this, set,
+ property.name(), property.oldValue() /* ??? */, property.value()),
+ false /* !execute */ );
+ }
+
+ if (changePrimaryKey) {
+ d->slotPropertyChanged_primaryKey_enabled = false;
+ if (setPrimaryKey) {
+ //primary key implies some rules
+ //const bool prev_addHistoryCommand_in_slotPropertyChanged_enabled = d->addHistoryCommand_in_slotPropertyChanged_enabled;
+// d->addHistoryCommand_in_slotPropertyChanged_enabled = false;
+
+ //this action contains subactions
+ CommandGroup *setPrimaryKeyCommand = new CommandGroup(
+ i18n("Set primary key for field \"%1\"")
+ .arg(set["name"].value().toString()) );
+ if (toplevelCommand)
+ toplevelCommand->addCommand( setPrimaryKeyCommand );
+ else
+ toplevelCommand = setPrimaryKeyCommand;
+
+ d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(true,1), setPrimaryKeyCommand, true /*forceAddCommand*/ );
+ d->setPropertyValueIfNeeded( set, "unique", QVariant(true,1), setPrimaryKeyCommand );
+ d->setPropertyValueIfNeeded( set, "notNull", QVariant(true,1), setPrimaryKeyCommand );
+ d->setPropertyValueIfNeeded( set, "allowEmpty", QVariant(false,1), setPrimaryKeyCommand );
+ d->setPropertyValueIfNeeded( set, "indexed", QVariant(true,1), setPrimaryKeyCommand );
+//! \todo: add setting for this: "Integer PKeys have autonumber set by default"
+ d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(true,1), setPrimaryKeyCommand );
+
+/* set["unique"] = QVariant(true,1);
+ set["notNull"] = QVariant(true,1);
+ set["allowEmpty"] = QVariant(false,1);
+ set["indexed"] = QVariant(true,1);
+ set["autoIncrement"] = QVariant(true,1);*/
+// d->addHistoryCommand_in_slotPropertyChanged_enabled = prev_addHistoryCommand_in_slotPropertyChanged_enabled;
+//down addHistoryCommand( toplevelCommand, false /* !execute */ );
+ }
+ else {//! set PK to false
+ //remember this action containing 2 subactions
+ CommandGroup *setPrimaryKeyCommand = new CommandGroup(
+ i18n("Unset primary key for field \"%1\"")
+ .arg(set["name"].value().toString()) );
+ if (toplevelCommand)
+ toplevelCommand->addCommand( setPrimaryKeyCommand );
+ else
+ toplevelCommand = setPrimaryKeyCommand;
+
+ d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(false,1), setPrimaryKeyCommand, true /*forceAddCommand*/ );
+ d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(false,1), setPrimaryKeyCommand );
+// set["autoIncrement"] = QVariant(false,1);
+
+//down addHistoryCommand( toplevelCommand, false /* !execute */ );
+ }
+ switchPrimaryKey(set, setPrimaryKey, true/*wasPKey*/, toplevelCommand);
+ d->updatePropertiesVisibility(
+ KexiDB::Field::typeForString( set["subType"].value().toString() ), set, toplevelCommand);
+ addHistoryCommand( toplevelCommand, false /* !execute */ );
+ //properties' visiblility changed: refresh prop. set
+ propertySetReloaded(true/*preservePrevSelection*/);
+ d->slotPropertyChanged_primaryKey_enabled = true;
+ }
+}
+
+void KexiTableDesignerView::slotRowInserted()
+{
+ updateActions();
+
+ if (d->addHistoryCommand_in_slotRowInserted_enabled) {
+ const int row = d->view->currentRow();
+ if (row>=0) {
+ addHistoryCommand( new InsertEmptyRowCommand( this, row ), false /* !execute */ );
+ }
+ }
+ //TODO?
+}
+
+void KexiTableDesignerView::slotAboutToDeleteRow(
+ KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint)
+{
+ Q_UNUSED(result)
+ Q_UNUSED(repaint)
+ if (item[COLUMN_ID_ICON].toString()=="key")
+ d->primaryKeyExists = false;
+
+ if (d->addHistoryCommand_in_slotAboutToDeleteRow_enabled) {
+ const int row = d->view->data()->findRef(&item);
+ KoProperty::Set *set = row >=0 ? d->sets->at(row) : 0;
+ //set can be 0 here, what means "removing empty row"
+ addHistoryCommand(
+ new RemoveFieldCommand( this, row, set ),
+ false /* !execute */
+ );
+ }
+}
+
+KexiDB::Field * KexiTableDesignerView::buildField( const KoProperty::Set &set ) const
+{
+ //create a map of property values
+ kexipluginsdbg << set["type"].value() << endl;
+ QMap<QCString, QVariant> values = KoProperty::propertyValues(set);
+ //remove internal values, to avoid creating custom field's properties
+ QMap<QCString, QVariant>::Iterator it = values.begin();
+ KexiDB::Field *field = new KexiDB::Field();
+
+ while (it!=values.end()) {
+ const QString propName( it.key() );
+ if (d->internalPropertyNames.find(propName.latin1()) || propName.startsWith("this:")
+ || (/*sanity*/propName=="objectType" && KexiDB::Field::BLOB != KexiDB::intToFieldType( set["type"].value().toInt() )))
+ {
+ QMap<QCString, QVariant>::Iterator it_tmp = it;
+ ++it;
+ values.remove(it_tmp);
+ }
+ else
+ ++it;
+ }
+ //assign properties to the field
+ // (note that "objectType" property will be saved as custom property)
+ if (!KexiDB::setFieldProperties( *field, values )) {
+ delete field;
+ return 0;
+ }
+ return field;
+}
+
+tristate KexiTableDesignerView::buildSchema(KexiDB::TableSchema &schema, bool beSilent)
+{
+ if (!d->view->acceptRowEdit())
+ return cancelled;
+
+ tristate res = true;
+ //check for pkey; automatically add a pkey if user wanted
+ if (!d->primaryKeyExists) {
+ if (beSilent) {
+ kexipluginsdbg << "KexiTableDesignerView::buildSchema(): no primay key defined..." << endl;
+ }
+ else {
+ const int questionRes = KMessageBox::questionYesNoCancel(this,
+ i18n("<p>Table \"%1\" has no <b>primary key</b> defined.</p>"
+ "<p>Although a primary key is not required, it is needed "
+ "for creating relations between database tables. "
+ "Do you want to add primary key automatically now?</p>"
+ "<p>If you want to add a primary key by hand, press \"Cancel\" "
+ "to cancel saving table design.</p>").arg(schema.name()),
+ QString::null, KGuiItem(i18n("&Add Primary Key"), "key"), KStdGuiItem::no(),
+ "autogeneratePrimaryKeysOnTableDesignSaving");
+ if (questionRes==KMessageBox::Cancel) {
+ return cancelled;
+ }
+ else if (questionRes==KMessageBox::Yes) {
+ //-find unique name, starting with, "id", "id2", ....
+ int i=0;
+ int idIndex = 1; //means "id"
+ QString pkFieldName("id%1");
+ QString pkFieldCaption(i18n("Identifier%1", "Id%1"));
+ while (i<(int)d->sets->size()) {
+ KoProperty::Set *set = d->sets->at(i);
+ if (set) {
+ if ((*set)["name"].value().toString()
+ == pkFieldName.arg(idIndex==1?QString::null : QString::number(idIndex))
+ || (*set)["caption"].value().toString()
+ == pkFieldCaption.arg(idIndex==1?QString::null : QString::number(idIndex)))
+ {
+ //try next id index
+ i = 0;
+ idIndex++;
+ continue;
+ }
+ }
+ i++;
+ }
+ pkFieldName = pkFieldName.arg(idIndex==1?QString::null : QString::number(idIndex));
+ pkFieldCaption = pkFieldCaption.arg(idIndex==1?QString::null : QString::number(idIndex));
+ //ok, add PK with such unique name
+ d->view->insertEmptyRow(0);
+ d->view->setCursorPosition(0, COLUMN_ID_CAPTION);
+ d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_CAPTION,
+ QVariant(pkFieldCaption));
+ d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE,
+ QVariant(KexiDB::Field::IntegerGroup-1/*counting from 0*/));
+ if (!d->view->data()->saveRowChanges(*d->view->selectedItem(), true)) {
+ return cancelled;
+ }
+ slotTogglePrimaryKey();
+ }
+ }
+ }
+
+ //check for duplicates
+ KoProperty::Set *b = 0;
+ bool no_fields = true;
+ int i;
+ QDict<char> names(101, false);
+ char dummy;
+ for (i=0;i<(int)d->sets->size();i++) {
+ b = d->sets->at(i);
+ if (b) {
+ no_fields = false;
+ const QString name = (*b)["name"].value().toString();
+ if (name.isEmpty()) {
+ if (beSilent) {
+ kexipluginswarn <<
+ QString("KexiTableDesignerView::buildSchema(): no field caption entered at row %1...")
+ .arg(i+1) << endl;
+ }
+ else {
+ d->view->setCursorPosition(i, COLUMN_ID_CAPTION);
+ d->view->startEditCurrentCell();
+ KMessageBox::information(this, i18n("You should enter field caption.") );
+ }
+ res = cancelled;
+ break;
+ }
+ if (names[name]) {
+ break;
+ }
+ names.insert( name, &dummy ); //remember
+ }
+ }
+ if (res == true && no_fields) {//no fields added
+ if (beSilent) {
+ kexipluginswarn <<
+ "KexiTableDesignerView::buildSchema(): no field defined..." << endl;
+ }
+ else {
+ KMessageBox::sorry(this,
+ i18n("You have added no fields.\nEvery table should have at least one field.") );
+ }
+ res = cancelled;
+ }
+ if (res == true && b && i<(int)d->sets->size()) {//found a duplicate
+ if (beSilent) {
+ kexipluginswarn <<
+ QString("KexiTableDesignerView::buildSchema(): duplicated field name '%1'")
+ .arg((*b)["name"].value().toString()) << endl;
+ }
+ else {
+ d->view->setCursorPosition(i, COLUMN_ID_CAPTION);
+ d->view->startEditCurrentCell();
+//! @todo for "names hidden" mode we won't get this error because user is unable to change names
+ KMessageBox::sorry(this,
+ i18n("You have added \"%1\" field name twice.\nField names cannot be repeated. "
+ "Correct name of the field.")
+ .arg((*b)["name"].value().toString()) );
+ }
+ res = cancelled;
+ }
+ if (res == true) {
+ //for every field, create KexiDB::Field definition
+ for (i=0;i<(int)d->sets->size();i++) {
+ KoProperty::Set *s = d->sets->at(i);
+ if (!s)
+ continue;
+ KexiDB::Field * f = buildField( *s );
+ if (!f)
+ continue; //hmm?
+ schema.addField(f);
+ if (!(*s)["rowSource"].value().toString().isEmpty() && !(*s)["rowSourceType"].value().toString().isEmpty()) {
+ //add lookup column
+ KexiDB::LookupFieldSchema *lookupFieldSchema = new KexiDB::LookupFieldSchema();
+ lookupFieldSchema->rowSource().setTypeByName( (*s)["rowSourceType"].value().toString() );
+ lookupFieldSchema->rowSource().setName( (*s)["rowSource"].value().toString() );
+ lookupFieldSchema->setBoundColumn( (*s)["boundColumn"].value().toInt() );
+//! @todo this is backward-compatible code for "single visible column" implementation
+//! for multiple columns, only the first is displayed, so there is a data loss is GUI is used
+//! -- special koproperty editor needed
+ QValueList<uint> visibleColumns;
+ const int visibleColumn = (*s)["visibleColumn"].value().toInt();
+ if (visibleColumn >= 0)
+ visibleColumns.append( (uint)visibleColumn );
+ lookupFieldSchema->setVisibleColumns( visibleColumns );
+//! @todo support columnWidths(), columnHeadersVisible(), maximumListRows(), limitToList(), displayWidget()
+ if (!schema.setLookupFieldSchema(f->name(), lookupFieldSchema)) {
+ kexipluginswarn <<
+ "KexiTableDesignerView::buildSchema(): !schema.setLookupFieldSchema()" << endl;
+ delete lookupFieldSchema;
+ return false;
+ }
+ }
+ }
+ }
+ return res;
+}
+
+//! @internal
+//! A recursive function for copying alter table actions from undo/redo commands.
+static void copyAlterTableActions(KCommand* command, KexiDB::AlterTableHandler::ActionList &actions)
+{
+ CommandGroup* cmdGroup = dynamic_cast<CommandGroup*>( command );
+ if (cmdGroup) {//command group: flatten it
+ for (QPtrListIterator<KCommand> it(cmdGroup->commands()); it.current(); ++it)
+ copyAlterTableActions(it.current(), actions);
+ return;
+ }
+ Command* cmd = dynamic_cast<Command*>( command );
+ if (!cmd) {
+ kexipluginswarn << "KexiTableDesignerView::copyAlterTableActions(): cmd is not of type 'Command'!" << endl;
+ return;
+ }
+ KexiDB::AlterTableHandler::ActionBase* action = cmd->createAction();
+ //some commands can contain null actions, e.g. "set visibility" command
+ if (action)
+ actions.append( action );
+}
+
+tristate KexiTableDesignerView::buildAlterTableActions(KexiDB::AlterTableHandler::ActionList &actions)
+{
+ actions.clear();
+ kexipluginsdbg << "KexiTableDesignerView::buildAlterTableActions(): " << d->history->commands().count()
+ << " top-level command(s) to process..." << endl;
+ for (QPtrListIterator<KCommand> it(d->history->commands()); it.current(); ++it) {
+ copyAlterTableActions(it.current(), actions);
+ }
+ return true;
+}
+
+KexiDB::SchemaData* KexiTableDesignerView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ if (tempData()->table || m_dialog->schemaData()) //must not be
+ return 0;
+
+ //create table schema definition
+ tempData()->table = new KexiDB::TableSchema(sdata.name());
+ tempData()->table->setName( sdata.name() );
+ tempData()->table->setCaption( sdata.caption() );
+ tempData()->table->setDescription( sdata.description() );
+
+ tristate res = buildSchema(*tempData()->table);
+ cancel = ~res;
+
+ //FINALLY: create table:
+ if (res == true) {
+ //todo
+ KexiDB::Connection *conn = mainWin()->project()->dbConnection();
+ res = conn->createTable(tempData()->table);
+ if (res!=true)
+ parentDialog()->setStatus(conn, "");
+ }
+
+ if (res == true) {
+ //we've current schema
+ tempData()->tableSchemaChangedInPreviousView = true;
+//not needed; KexiProject emits newItemStored signal //let project know the table is created
+// mainWin()->project()->emitTableCreated(*tempData()->table);
+ }
+ else {
+ delete tempData()->table;
+ tempData()->table = 0;
+ }
+ return tempData()->table;
+}
+
+tristate KexiTableDesignerView::storeData(bool dontAsk)
+{
+ if (!tempData()->table || !m_dialog->schemaData()) {
+ d->recentResultOfStoreData = false;
+ return false;
+ }
+
+ KexiDB::Connection *conn = mainWin()->project()->dbConnection();
+ KexiDB::AlterTableHandler *alterTableHandler = 0;
+ KexiDB::TableSchema *newTable = 0;
+
+ //- create action list for the alter table handler
+ KexiDB::AlterTableHandler::ActionList actions;
+ tristate res = buildAlterTableActions( actions );
+ bool realAlterTableCanBeUsed = false; //!< @todo this is temporary flag before we switch entirely to real alter table
+ if (res == true) {
+ alterTableHandler = new KexiDB::AlterTableHandler( *conn );
+ alterTableHandler->setActions(actions);
+
+ if (!d->tempStoreDataUsingRealAlterTable) {
+ //only compute requirements
+ KexiDB::AlterTableHandler::ExecutionArguments args;
+ args.onlyComputeRequirements = true;
+ (void)alterTableHandler->execute(tempData()->table->name(), args);
+ res = args.result;
+ if (res == true && 0 == (args.requirements & (0xffff ^ KexiDB::AlterTableHandler::SchemaAlteringRequired)))
+ realAlterTableCanBeUsed = true;
+ }
+ }
+
+ if (res == true) {
+ res = KexiTablePart::askForClosingObjectsUsingTableSchema(
+ this, *conn, *tempData()->table,
+ i18n("You are about to change the design of table \"%1\" "
+ "but following objects using this table are opened:")
+ .arg(tempData()->table->name()));
+ }
+
+ if (res == true) {
+ if (!d->tempStoreDataUsingRealAlterTable && !realAlterTableCanBeUsed) {
+//! @todo temp; remove this case:
+ delete alterTableHandler;
+ alterTableHandler = 0;
+ // - inform about removing the current table and ask for confirmation
+ if (!d->dontAskOnStoreData && !dontAsk) {
+ bool emptyTable;
+ const QString msg = d->messageForSavingChanges(emptyTable);
+ if (!emptyTable) {
+ if (KMessageBox::No == KMessageBox::questionYesNo(this, msg))
+ res = cancelled;
+ }
+ }
+ d->dontAskOnStoreData = false; //one-time use
+ if (~res) {
+ d->recentResultOfStoreData = res;
+ return res;
+ }
+ // keep old behaviour:
+ newTable = new KexiDB::TableSchema();
+ // copy the schema data
+ static_cast<KexiDB::SchemaData&>(*newTable) = static_cast<KexiDB::SchemaData&>(*tempData()->table);
+ res = buildSchema(*newTable);
+ kexipluginsdbg << "KexiTableDesignerView::storeData() : BUILD SCHEMA:" << endl;
+ newTable->debug();
+
+ res = conn->alterTable(*tempData()->table, *newTable);
+ if (res != true)
+ parentDialog()->setStatus(conn, "");
+ }
+ else {
+ KexiDB::AlterTableHandler::ExecutionArguments args;
+ newTable = alterTableHandler->execute(tempData()->table->name(), args);
+ res = args.result;
+ kexipluginsdbg << "KexiTableDesignerView::storeData() : ALTER TABLE EXECUTE: "
+ << res.toString() << endl;
+ if (true != res) {
+ alterTableHandler->debugError();
+ parentDialog()->setStatus(alterTableHandler, "");
+ }
+ }
+ }
+ if (res == true) {
+ //change current schema
+ tempData()->table = newTable;
+ tempData()->tableSchemaChangedInPreviousView = true;
+ d->history->clear();
+ }
+ else {
+ delete newTable;
+ }
+ delete alterTableHandler;
+ d->recentResultOfStoreData = res;
+ return res;
+}
+
+tristate KexiTableDesignerView::simulateAlterTableExecution(QString *debugTarget)
+{
+#ifndef KEXI_NO_UNDOREDO_ALTERTABLE
+# ifdef KEXI_DEBUG_GUI
+ if (mainWin()->activeWindow() != parentDialog()) //to avoid executing for multiple alter table views
+ return false;
+ if (!tempData()->table || !m_dialog->schemaData())
+ return false;
+ KexiDB::Connection *conn = mainWin()->project()->dbConnection();
+ KexiDB::AlterTableHandler::ActionList actions;
+ tristate res = buildAlterTableActions( actions );
+//todo: result?
+ KexiDB::AlterTableHandler alterTableHandler( *conn );
+ alterTableHandler.setActions(actions);
+ KexiDB::AlterTableHandler::ExecutionArguments args;
+ if (debugTarget) {
+ args.debugString = debugTarget;
+ }
+ else {
+ args.simulate = true;
+ }
+ (void)alterTableHandler.execute(tempData()->table->name(), args);
+ return args.result;
+# else
+ return false;
+# endif
+#else
+ return false;
+#endif
+}
+
+void KexiTableDesignerView::slotSimulateAlterTableExecution()
+{
+ (void)simulateAlterTableExecution(0);
+}
+
+tristate KexiTableDesignerView::executeRealAlterTable()
+{
+ QSignal signal;
+ signal.connect( mainWin(), SLOT(slotProjectSave()) );
+ d->tempStoreDataUsingRealAlterTable = true;
+ d->recentResultOfStoreData = false;
+ signal.activate(); //will call KexiMainWindowImpl::slotProjectSaveAs() and thus storeData()
+ d->tempStoreDataUsingRealAlterTable = false;
+ return d->recentResultOfStoreData;
+}
+
+KexiTablePart::TempData* KexiTableDesignerView::tempData() const
+{
+ return static_cast<KexiTablePart::TempData*>(parentDialog()->tempData());
+}
+
+/*void KexiTableDesignerView::slotAboutToUpdateRow(
+ KexiTableItem* item, KexiDB::RowEditBuffer* buffer, KexiDB::ResultInfo* result)
+{
+ KexiDB::RowEditBuffer::SimpleMap map = buffer->simpleBuffer();
+ buffer->debug();
+
+ QVariant old_type = item->at(1);
+ QVariant *buf_type = buffer->at( d->view->field(1)->name() );
+
+ //check if there is a type specified
+// if ((old_type.isNull() && !buf_type) || (buf_type && buf_type->isNull())) {
+ //kdDebug() << "err" << endl;
+ //}
+// allow = true;
+// m_dirty = m_dirty | result->success;
+}*/
+
+#ifdef KEXI_DEBUG_GUI
+void KexiTableDesignerView::debugCommand( KCommand* command, int nestingLevel )
+{
+ if (dynamic_cast<Command*>(command))
+ KexiUtils::addAlterTableActionDebug(dynamic_cast<Command*>(command)->debugString(), nestingLevel);
+ else
+ KexiUtils::addAlterTableActionDebug(command->name(), nestingLevel);
+ //show subcommands
+ if (dynamic_cast<CommandGroup*>(command)) {
+ for (QPtrListIterator<KCommand> it(dynamic_cast<CommandGroup*>(command)->commands()); it.current(); ++it) {
+ debugCommand(it.current(), nestingLevel + 1);
+ }
+ }
+}
+#endif
+
+void KexiTableDesignerView::addHistoryCommand( KCommand* command, bool execute )
+{
+#ifndef KEXI_NO_UNDOREDO_ALTERTABLE
+# ifdef KEXI_DEBUG_GUI
+ debugCommand( command, 0 );
+# endif
+ d->history->addCommand( command, execute );
+ updateUndoRedoActions();
+#endif
+}
+
+void KexiTableDesignerView::updateUndoRedoActions()
+{
+#ifndef KEXI_NO_UNDOREDO_ALTERTABLE
+ setAvailable("edit_undo", d->historyActionCollection->action("edit_undo")->isEnabled());
+ setAvailable("edit_redo", d->historyActionCollection->action("edit_redo")->isEnabled());
+#endif
+}
+
+void KexiTableDesignerView::slotUndo()
+{
+#ifndef KEXI_NO_UNDOREDO_ALTERTABLE
+# ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(QString("UNDO:"));
+# endif
+ d->history->undo();
+ updateUndoRedoActions();
+#endif
+}
+
+void KexiTableDesignerView::slotRedo()
+{
+#ifndef KEXI_NO_UNDOREDO_ALTERTABLE
+# ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(QString("REDO:"));
+# endif
+ d->history->redo();
+ updateUndoRedoActions();
+#endif
+}
+
+void KexiTableDesignerView::slotCommandExecuted(KCommand *command)
+{
+#ifdef KEXI_DEBUG_GUI
+ debugCommand( command, 1 );
+#endif
+}
+
+void KexiTableDesignerView::slotAboutToShowContextMenu()
+{
+ //update title
+ if (propertySet()) {
+ const KoProperty::Set &set = *propertySet();
+ QString captionOrName(set["caption"].value().toString());
+ if (captionOrName.isEmpty())
+ captionOrName = set["name"].value().toString();
+//! @todo show "field" icon
+ d->contextMenuTitle->setTitle( i18n("Table field \"%1\"").arg(captionOrName) );
+ }
+ else {
+ d->contextMenuTitle->setTitle( i18n("Empty table row", "Empty Row") );
+ }
+}
+
+QString KexiTableDesignerView::debugStringForCurrentTableSchema(tristate& result)
+{
+ KexiDB::TableSchema tempTable;
+ //copy schema data
+ static_cast<KexiDB::SchemaData&>(tempTable) = static_cast<KexiDB::SchemaData&>(*tempData()->table);
+ result = buildSchema(tempTable, true /*beSilent*/);
+ if (true!=result)
+ return QString::null;
+ return tempTable.debugString(false /*without name*/);
+}
+
+// -- low-level actions used by undo/redo framework
+
+void KexiTableDesignerView::clearRow(int row, bool addCommand)
+{
+ if (!d->view->acceptRowEdit())
+ return;
+ KexiTableItem *item = d->view->itemAt(row);
+ if (!item)
+ return;
+ //remove from prop. set
+ d->sets->remove( row );
+ //clear row in table view (just clear value in COLUMN_ID_TYPE column)
+// for (int i=0; i < (int)d->view->data()->columnsCount(); i++) {
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotRowUpdated_enabled = false;
+ d->addHistoryCommand_in_slotPropertyChanged_enabled = false;
+ d->slotBeforeCellChanged_enabled = false;
+ }
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant());
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotRowUpdated_enabled = true;
+ d->addHistoryCommand_in_slotPropertyChanged_enabled = true;
+ d->slotBeforeCellChanged_enabled = true;
+ }
+ d->view->data()->saveRowChanges(*item, true);
+}
+
+void KexiTableDesignerView::insertField(int row, const QString& caption, bool addCommand)
+{
+ insertFieldInternal(row, 0, caption, addCommand);
+}
+
+void KexiTableDesignerView::insertField(int row, KoProperty::Set& set, bool addCommand)
+{
+ insertFieldInternal(row, &set, QString::null, addCommand);
+}
+
+void KexiTableDesignerView::insertFieldInternal(int row, KoProperty::Set* set, //const KexiDB::Field& field,
+ const QString& caption, bool addCommand)
+{
+ if (set && (!set->contains("type") || !set->contains("caption"))) {
+ kexipluginswarn << "KexiTableDesignerView::insertField(): no 'type' or 'caption' property in set!" << endl;
+ return;
+ }
+ if (!d->view->acceptRowEdit())
+ return;
+ KexiTableItem *item = d->view->itemAt(row);
+ if (!item)
+ return;
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotRowUpdated_enabled = false;
+ d->addHistoryCommand_in_slotPropertyChanged_enabled = false;
+ d->slotBeforeCellChanged_enabled = false;
+ }
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION,
+ set ? (*set)["caption"].value() : QVariant(caption));//field.caption());
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE,
+ set ? (int)KexiDB::Field::typeGroup( (*set)["type"].value().toInt() )-1/*counting from 0*/
+ : (((int)KexiDB::Field::TextGroup)-1)/*default type, counting from 0*/
+ );
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC,
+ set ? (*set)["description"].value() : QVariant());//field.description());
+ if (!addCommand) {
+ d->slotBeforeCellChanged_enabled = true;
+ }
+ //this will create a new property set:
+ d->view->data()->saveRowChanges(*item);
+ if (set) {
+ KoProperty::Set *newSet = d->sets->at(row);
+ if (newSet) {
+ *newSet = *set; //deep copy
+ }
+ else {
+ kexipluginswarn << "KexiTableDesignerView::insertField() !newSet, row==" << row << endl;
+ }
+ }
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotPropertyChanged_enabled = true;
+ d->addHistoryCommand_in_slotRowUpdated_enabled = true;
+ }
+ d->view->updateRow( row );
+ propertySetReloaded(true);
+}
+
+void KexiTableDesignerView::insertEmptyRow( int row, bool addCommand )
+{
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotRowInserted_enabled = false;
+ }
+ d->view->insertEmptyRow( row );
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotRowInserted_enabled = true;
+ }
+}
+
+/*void KexiTableDesignerView::deleteRow( int row )
+{
+ d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = false;
+ d->view->deleteItem( d->view->data()->at(row) );
+ d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = true;
+}*/
+
+void KexiTableDesignerView::deleteRow( int row, bool addCommand )
+{
+ KexiTableItem *item = d->view->itemAt( row );
+ if (!item)
+ return;
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = false;
+ }
+ const bool res = d->view->deleteItem(item);
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = true;
+ }
+ if (!res)
+ return;
+}
+
+void KexiTableDesignerView::changeFieldPropertyForRow( int row,
+ const QCString& propertyName, const QVariant& newValue,
+ KoProperty::Property::ListData* const listData, bool addCommand )
+{
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(QString("** changeFieldProperty: \"")
+ + QString(propertyName) + "\" to \"" + newValue.toString() + "\"", 2/*nestingLevel*/);
+#endif
+ if (!d->view->acceptRowEdit())
+ return;
+
+ KoProperty::Set* set = d->sets->at( row );
+ if (!set || !set->contains(propertyName))
+ return;
+ KoProperty::Property &property = set->property(propertyName);
+ if (listData) {
+ if (listData->keys.isEmpty())
+ property.setListData( 0 );
+ else
+ property.setListData( new KoProperty::Property::ListData(*listData) );
+ }
+ if (propertyName != "type") //delayed type update (we need to have subtype set properly)
+ property.setValue(newValue);
+ KexiTableItem *item = d->view->itemAt(row);
+ Q_ASSERT(item);
+
+ if (propertyName == "type") {
+ // d->addHistoryCommand_in_slotRowUpdated_enabled = false;
+// d->addHistoryCommand_in_slotPropertyChanged_enabled = false;
+ d->slotPropertyChanged_subType_enabled = false;
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE,
+ int( KexiDB::Field::typeGroup( newValue.toInt() ) )-1);
+ d->view->data()->saveRowChanges(*item);
+ d->addHistoryCommand_in_slotRowUpdated_enabled = true;
+// d->addHistoryCommand_in_slotPropertyChanged_enabled = true;
+ // d->slotPropertyChanged_subType_enabled = true;
+ property.setValue(newValue); //delayed type update (we needed to have subtype set properly)
+ }
+
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotRowUpdated_enabled = false;
+ d->addHistoryCommand_in_slotPropertyChanged_enabled = false;
+ d->slotPropertyChanged_subType_enabled = false;
+ }
+ //special cases: properties displayed within the data grid:
+ if (propertyName == "caption") {
+ if (!addCommand) {
+ d->slotBeforeCellChanged_enabled = false;
+ }
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, newValue);
+ d->view->data()->saveRowChanges(*item);
+ if (!addCommand) {
+ d->slotBeforeCellChanged_enabled = true;
+ }
+ }
+ else if (propertyName == "description") {
+ if (!addCommand) {
+ d->slotBeforeCellChanged_enabled = false;
+ }
+ d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC, newValue);
+ if (!addCommand) {
+ d->slotBeforeCellChanged_enabled = true;
+ }
+ d->view->data()->saveRowChanges(*item);
+ }
+ if (!addCommand) {
+ d->addHistoryCommand_in_slotPropertyChanged_enabled = true;
+ d->addHistoryCommand_in_slotRowUpdated_enabled = true;
+ d->slotPropertyChanged_subType_enabled = true;
+ }
+ d->view->updateRow( row );
+}
+
+void KexiTableDesignerView::changeFieldProperty( int fieldUID,
+ const QCString& propertyName, const QVariant& newValue,
+ KoProperty::Property::ListData* const listData, bool addCommand )
+{
+ //find a property by UID
+ const int row = d->sets->findRowForPropertyValue("uid", fieldUID);
+ if (row<0) {
+ kexipluginswarn << "KexiTableDesignerView::changeFieldProperty(): field with uid="<<fieldUID<<" not found!"<<endl;
+ return;
+ }
+ changeFieldPropertyForRow(row, propertyName, newValue, listData, addCommand);
+}
+
+void KexiTableDesignerView::changePropertyVisibility(
+ int fieldUID, const QCString& propertyName, bool visible )
+{
+#ifdef KEXI_DEBUG_GUI
+ KexiUtils::addAlterTableActionDebug(QString("** changePropertyVisibility: \"")
+ + QString(propertyName) + "\" to \"" + (visible ? "true" : "false") + "\"", 2/*nestingLevel*/);
+#endif
+ if (!d->view->acceptRowEdit())
+ return;
+
+ //find a property by name
+ const int row = d->sets->findRowForPropertyValue("uid", fieldUID);
+ if (row<0)
+ return;
+ KoProperty::Set* set = d->sets->at( row );
+ if (!set || !set->contains(propertyName))
+ return;
+
+ KoProperty::Property &property = set->property(propertyName);
+ if (property.isVisible() != visible) {
+ property.setVisible(visible);
+ propertySetReloaded(true);
+ }
+}
+
+void KexiTableDesignerView::propertySetSwitched()
+{
+ KexiDataTable::propertySetSwitched();
+
+ //if (parentDialog()!=parentDialog()->mainWin()->currentDialog())
+ // return; //this is not the current dialog's view
+
+ static_cast<KexiTablePart*>(parentDialog()->part())->lookupColumnPage()
+ ->assignPropertySet(propertySet());
+}
+
+bool KexiTableDesignerView::isPhysicalAlteringNeeded()
+{
+ //- create action list for the alter table handler
+ KexiDB::AlterTableHandler::ActionList actions;
+ tristate res = buildAlterTableActions( actions );
+ if (res != true)
+ return true;
+
+ KexiDB::Connection *conn = mainWin()->project()->dbConnection();
+ KexiDB::AlterTableHandler *alterTableHandler = new KexiDB::AlterTableHandler( *conn );
+ alterTableHandler->setActions(actions);
+
+ //only compute requirements
+ KexiDB::AlterTableHandler::ExecutionArguments args;
+ args.onlyComputeRequirements = true;
+ (void)alterTableHandler->execute(tempData()->table->name(), args);
+ res = args.result;
+ delete alterTableHandler;
+ if (res == true && 0 == (args.requirements & (0xffff ^ KexiDB::AlterTableHandler::SchemaAlteringRequired)))
+ return false;
+ return true;
+}
+
+#include "kexitabledesignerview.moc"
diff --git a/kexi/plugins/tables/kexitabledesignerview.h b/kexi/plugins/tables/kexitabledesignerview.h
new file mode 100644
index 000000000..773163b61
--- /dev/null
+++ b/kexi/plugins/tables/kexitabledesignerview.h
@@ -0,0 +1,258 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXITABLEDESIGNERINTERVIEW_H
+#define KEXITABLEDESIGNERINTERVIEW_H
+
+#include <koproperty/property.h>
+#include <kexidb/alter.h>
+#include <core/kexitabledesignerinterface.h>
+
+#include <kexidatatable.h>
+#include "kexitablepart.h"
+
+class KexiTableItem;
+class KexiTableDesignerViewPrivate;
+class KCommand;
+class CommandGroup;
+
+namespace KoProperty {
+ class Set;
+}
+
+//! Design view of the Table Designer
+/*! Contains a spreadsheet-like space for entering field definitions.
+ Property editor is provided for altering field definitions.
+
+ The view also supports Undo and Redo operations.
+ These are connected to a factility creating a list of actions used
+ by AlterTableHandler to perform required operation of altering the table.
+
+ Altering itself is performed upon design saving (storeData()).
+ Saving unstored designs just creates a new table.
+ Saving changes made to empty (not filled with data) table is performed
+ by physically deleting the previous table schema and recreating it
+ TODO: this will be not quite when we have db relationships supported.
+
+ Saving changes made to table containing data requires use of the AlterTableHandler
+ functionality.
+*/
+class KexiTableDesignerView : public KexiDataTable, public KexiTableDesignerInterface
+{
+ Q_OBJECT
+
+ public:
+ /*! Creates a new alter table dialog. */
+ KexiTableDesignerView(KexiMainWindow *win, QWidget *parent);
+
+ virtual ~KexiTableDesignerView();
+
+ KexiTablePart::TempData* tempData() const;
+
+ /*! Clears field information entered for row.
+ This is performed by removing values from caption and data type columns.
+ Used by InsertFieldCommand to undo inserting a new field. */
+ virtual void clearRow(int row, bool addCommand = false);
+
+ /*! Inserts a new field with \a caption for \a row.
+ Property set is also created. */
+ virtual void insertField(int row, const QString& caption, bool addCommand = false);
+
+ /*! Inserts a new \a field for \a row.
+ Property set is also created. \a set will be deeply-copied into the new set.
+ Used by InsertFieldCommand to insert a new field. */
+ virtual void insertField(int row, KoProperty::Set& set, bool addCommand = false);
+
+ /*! Inserts a new empty row at position \a row.
+ Used by RemoveFieldCommand as a part of undo inserting a new field;
+ also used by InsertEmptyRowCommand. */
+ virtual void insertEmptyRow( int row, bool addCommand = false );
+
+ /*! Deletes \a row from the table view. Property set is also deleted.
+ All the subsequent fields are moved up. Used for undoing InsertEmptyRowCommand
+ and by RemoveFieldCommand to remove a field. */
+ virtual void deleteRow( int row, bool addCommand = false );
+
+ /*! Deletes a field for \a row. Property set is also deleted.
+ Used by RemoveFieldCommand to remove a field. */
+// virtual void deleteField( int row );
+
+ /*! Changes property \a propertyName to \a newValue for a field at row \a row.
+ If \a listData is not NULL and not empty, a deep copy of it is passed to Property::setListData().
+ If \a listData \a nlist if not NULL but empty, Property::setListData(0) is called. */
+ virtual void changeFieldPropertyForRow( int row,
+ const QCString& propertyName, const QVariant& newValue,
+ KoProperty::Property::ListData* const listData, bool addCommand );
+
+ /*! Changes property \a propertyName to \a newValue.
+ Works exactly like changeFieldPropertyForRow() except the field is pointed by \a fieldUID.
+ Used by ChangeFieldPropertyCommand to change field's property. */
+ void changeFieldProperty( int fieldUID, const QCString& propertyName,
+ const QVariant& newValue, KoProperty::Property::ListData* const listData = 0,
+ bool addCommand = false );
+
+ /*! Changes visibility of property \a propertyName to \a visible for a field pointed by \a fieldUID.
+ Used by ChangePropertyVisibilityCommand. */
+ void changePropertyVisibility( int fieldUID, const QCString& propertyName, bool visible );
+
+ /*! Builds table field's schema by looking at the \a set. */
+ KexiDB::Field * buildField( const KoProperty::Set &set ) const;
+
+ /*! Creates temporary table for the current design and returns debug string for it. */
+ virtual QString debugStringForCurrentTableSchema(tristate& result);
+
+ /*! Simulates execution of alter table, and puts debug into \a debugTarget.
+ A case when debugTarget is not 0 is true for the alter table test suite. */
+ virtual tristate simulateAlterTableExecution(QString *debugTarget);
+
+ public slots:
+ /*! Real execution of the Alter Table. For debugging of the real alter table.
+ \return true on success, false on failure and cancelled if user has cancelled
+ execution. */
+ virtual tristate executeRealAlterTable();
+
+ protected slots:
+ /*! Equivalent to updateActions(false). Called on row insert/delete
+ in a KexiDataAwarePropertySet. */
+ void updateActions();
+
+ virtual void slotUpdateRowActions(int row);
+
+ void slotAboutToShowContextMenu();
+
+ //! Called before cell change in tableview.
+ void slotBeforeCellChanged(KexiTableItem *item, int colnum,
+ QVariant& newValue, KexiDB::ResultInfo* result);
+
+ //! Called on row change in a tableview.
+ void slotRowUpdated(KexiTableItem *item);
+
+ //! Called before row inserting in tableview.
+ void slotRowInserted();
+// void slotAboutToInsertRow(KexiTableItem* item, KexiDB::ResultInfo* result, bool repaint);
+
+ //! Called before row deleting in tableview.
+ void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint);
+
+ /*! Called after any property has been changed in the current property set,
+ to perform some actions (like updating other dependent properties) */
+ void slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property);
+
+ /*! Toggles primary key for currently selected field.
+ Does nothing for empty row. */
+ void slotTogglePrimaryKey();
+
+ /*! Undoes the recently performed action. */
+ void slotUndo();
+
+ /*! Redoes the recently undoed action. */
+ void slotRedo();
+
+ /*! Reaction on command execution from the command history */
+ void slotCommandExecuted(KCommand *command);
+
+ /*! Simulates real execution of the Alter Table. For debugging. */
+ void slotSimulateAlterTableExecution();
+
+ protected:
+ virtual void updateActions(bool activated);
+
+ //! called whenever data should be reloaded (on switching to this view mode)
+ void initData();
+
+ /*! Creates a new property set for \a field.
+ The property set will be asigned to \a row, and owned by this dialog.
+ If \a newOne is true, the property set will be marked as newly created.
+ \return newly created property set. */
+ KoProperty::Set* createPropertySet( int row, const KexiDB::Field& field, bool newOne = false );
+
+ virtual tristate beforeSwitchTo(int mode, bool &dontStore);
+
+ virtual tristate afterSwitchFrom(int mode);
+
+ /*! \return property set associated with currently selected row (i.e. field)
+ or 0 if current row is empty. */
+ virtual KoProperty::Set *propertySet();
+
+// void removeCurrentPropertySet();
+
+ /*! Reimplemented from KexiViewBase, because tables creation is more complex.
+ No table schema altering is required, so just buildSchema() is used to create a new schema.
+ */
+ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel);
+
+ /*! Reimplemented from KexiViewBase, because table storage is more complex.
+ Table schema altering may be required, so just buildSchema() is used to create a new schema.
+ */
+ virtual tristate storeData(bool dontAsk = false);
+
+ /*! Builds table schema by looking at the current design. Used in storeNewData()
+ and storeData().
+ If \a beSilent is true, no message boxes are used to show questions or warnings.
+ This is used in the altertable test suite (kexi/tests/altertable).
+ \return true on successful schema creating, false on failure and cancelled when there
+ was a problem with user's design (and user has been informed about it). */
+ tristate buildSchema(KexiDB::TableSchema &schema, bool beSilent = false);
+
+ /*! Builds action list usable for KexiDB::AlterTableHandler by looking at undo buffer
+ of commands' history. Used in storeData() */
+ tristate buildAlterTableActions(KexiDB::AlterTableHandler::ActionList &actions);
+
+ /*! Helper, used for slotTogglePrimaryKey() and slotPropertyChanged().
+ Assigns primary key icon and value for property set \a propertySet,
+ and deselects it from previous pkey's row.
+ \a aWasPKey is internal.
+ If \a commandGroup is not 0, it is used as parent group for storing actions' history. */
+ void switchPrimaryKey(KoProperty::Set &propertySet, bool set, bool aWasPKey = false,
+ CommandGroup* commandGroup = 0);
+
+ //! Gets subtype strings and names for type \a fieldType.
+ void getSubTypeListData(KexiDB::Field::TypeGroup fieldTypeGroup,
+ QStringList& stringsList, QStringList& namesList);
+
+ /*! Adds history command \a command to the undo/redo buffer.
+ If \a execute is true, the command is executed afterwards. */
+ void addHistoryCommand( KCommand* command, bool execute );
+
+ //! Updates undo/redo shared actions availability by looking at command history's action
+ void updateUndoRedoActions();
+
+#ifdef KEXI_DEBUG_GUI
+ void debugCommand( KCommand* command, int nestingLevel );
+#endif
+
+ /*! Inserts a new \a field for \a row.
+ Property set is also created. If \a set is not 0 (the default),
+ it will be copied into the new set. Used by insertField(). */
+ void insertFieldInternal(int row, KoProperty::Set* set, const QString& caption, bool addCommand);
+
+ //! Reimplemented to pass the information also to the "Lookup" tab
+ virtual void propertySetSwitched();
+
+ /*! \return true if physical altering is needed for the current list of actions.
+ Used in KexiTableDesignerView::beforeSwitchTo() to avoid warning about removinf
+ table data if table recreating is not needed.
+ True is also returned if there is any trouble with getting the answer. */
+ bool isPhysicalAlteringNeeded();
+
+ private:
+ KexiTableDesignerViewPrivate *d;
+};
+
+#endif
diff --git a/kexi/plugins/tables/kexitabledesignerview_p.cpp b/kexi/plugins/tables/kexitabledesignerview_p.cpp
new file mode 100644
index 000000000..56ef997d4
--- /dev/null
+++ b/kexi/plugins/tables/kexitabledesignerview_p.cpp
@@ -0,0 +1,294 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitabledesignerview_p.h"
+#include "kexitabledesignerview.h"
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qsplitter.h>
+
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kaction.h>
+#include <kpopupmenu.h>
+#include <kmessagebox.h>
+
+#include <koproperty/set.h>
+
+#include <kexidb/cursor.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+#include <kexidb/roweditbuffer.h>
+#include <kexidb/error.h>
+#include <kexiutils/identifier.h>
+#include <kexiproject.h>
+#include <keximainwindow.h>
+#include <widget/tableview/kexidataawarepropertyset.h>
+#include <widget/kexicustompropertyfactory.h>
+#include <kexiutils/utils.h>
+#include <kexidialogbase.h>
+#include <kexitableview.h>
+#include "kexitabledesignercommands.h"
+
+using namespace KexiTableDesignerCommands;
+
+//----------------------------------------------
+
+CommandHistory::CommandHistory(KActionCollection *actionCollection, bool withMenus)
+ : KCommandHistory(actionCollection, withMenus)
+{
+ // We need ALL the commands because we'll collect reuse their
+ // data before performing alter table, so set that to the maximum,
+ // as KCommandHistory has default = 50.
+ setUndoLimit(INT_MAX);
+ setRedoLimit(INT_MAX);
+}
+
+void CommandHistory::addCommand(KCommand *command, bool execute)
+{
+ KCommandHistory::addCommand(command, execute);
+ m_commandsToUndo.append(command);
+}
+
+void CommandHistory::undo()
+{
+ if (!m_commandsToUndo.isEmpty()) {
+ KCommand * cmd = m_commandsToUndo.take( m_commandsToUndo.count()-1 );
+ m_commandsToRedo.append( cmd );
+ }
+ KCommandHistory::undo();
+}
+
+void CommandHistory::redo()
+{
+ if (!m_commandsToRedo.isEmpty()) {
+ KCommand * cmd = m_commandsToRedo.take( m_commandsToRedo.count()-1 );
+ m_commandsToUndo.append( cmd );
+ }
+ KCommandHistory::redo();
+}
+
+void CommandHistory::clear() {
+ KCommandHistory::clear(); m_commandsToUndo.clear();
+}
+
+//----------------------------------------------
+
+KexiTableDesignerViewPrivate::KexiTableDesignerViewPrivate(KexiTableDesignerView* aDesignerView)
+ : designerView(aDesignerView)
+ , sets(0)
+ , uniqueIdCounter(0)
+ , dontAskOnStoreData(false)
+ , slotTogglePrimaryKeyCalled(false)
+ , primaryKeyExists(false)
+ , slotPropertyChanged_primaryKey_enabled(true)
+ , slotPropertyChanged_subType_enabled(true)
+ , addHistoryCommand_in_slotPropertyChanged_enabled(true)
+ , addHistoryCommand_in_slotRowUpdated_enabled(true)
+ , addHistoryCommand_in_slotAboutToDeleteRow_enabled(true)
+ , addHistoryCommand_in_slotRowInserted_enabled(true)
+ , slotBeforeCellChanged_enabled(true)
+ , tempStoreDataUsingRealAlterTable(false)
+{
+ historyActionCollection = new KActionCollection((QWidget*)0,"");
+ history = new CommandHistory(historyActionCollection, true);
+
+ internalPropertyNames.insert("subType",(char*)1);
+ internalPropertyNames.insert("uid",(char*)1);
+ internalPropertyNames.insert("newrow",(char*)1);
+ internalPropertyNames.insert("rowSource",(char*)1);
+ internalPropertyNames.insert("rowSourceType",(char*)1);
+ internalPropertyNames.insert("boundColumn",(char*)1);
+ internalPropertyNames.insert("visibleColumn",(char*)1);
+}
+
+KexiTableDesignerViewPrivate::~KexiTableDesignerViewPrivate() {
+ delete sets;
+ delete historyActionCollection;
+ delete history;
+}
+
+int KexiTableDesignerViewPrivate::generateUniqueId()
+{
+ return ++uniqueIdCounter;
+}
+
+void KexiTableDesignerViewPrivate::setPropertyValueIfNeeded(
+ const KoProperty::Set& set, const QCString& propertyName,
+ const QVariant& newValue, const QVariant& oldValue, CommandGroup* commandGroup,
+ bool forceAddCommand, bool rememberOldValue,
+ QStringList* const slist, QStringList* const nlist)
+{
+ KoProperty::Property& property = set[propertyName];
+
+ KoProperty::Property::ListData *oldListData = property.listData() ?
+ new KoProperty::Property::ListData(*property.listData()) : 0; //remember because we'll change list data soon
+ if (slist && nlist) {
+ if (slist->isEmpty() || nlist->isEmpty()) {
+ property.setListData(0);
+ }
+ else {
+ property.setListData(*slist, *nlist);
+ }
+ }
+ if (oldValue.type() == newValue.type()
+ && (oldValue == newValue || (!oldValue.isValid() && !newValue.isValid()))
+ && !forceAddCommand)
+ {
+ return;
+ }
+
+ const bool prev_addHistoryCommand_in_slotPropertyChanged_enabled
+ = addHistoryCommand_in_slotPropertyChanged_enabled; //remember
+ addHistoryCommand_in_slotPropertyChanged_enabled = false;
+ if (property.value() != newValue)
+ property.setValue( newValue, rememberOldValue );
+ if (commandGroup) {
+ commandGroup->addCommand(
+ new ChangeFieldPropertyCommand( designerView, set, propertyName, oldValue, newValue,
+ oldListData, property.listData()) );
+ }
+ delete oldListData;
+ addHistoryCommand_in_slotPropertyChanged_enabled
+ = prev_addHistoryCommand_in_slotPropertyChanged_enabled; //restore
+}
+
+void KexiTableDesignerViewPrivate::setPropertyValueIfNeeded(
+ const KoProperty::Set& set, const QCString& propertyName,
+ const QVariant& newValue, CommandGroup* commandGroup,
+ bool forceAddCommand, bool rememberOldValue,
+ QStringList* const slist, QStringList* const nlist)
+{
+ KoProperty::Property& property = set[propertyName];
+ QVariant oldValue( property.value() );
+ setPropertyValueIfNeeded( set, propertyName, newValue, property.value(),
+ commandGroup, forceAddCommand, rememberOldValue, slist, nlist);
+}
+
+void KexiTableDesignerViewPrivate::setVisibilityIfNeeded( const KoProperty::Set& set, KoProperty::Property* prop,
+ bool visible, bool &changed, CommandGroup *commandGroup )
+{
+ if (prop->isVisible() != visible) {
+ if (commandGroup) {
+ commandGroup->addCommand(
+ new ChangePropertyVisibilityCommand( designerView, set, prop->name(), visible ) );
+ }
+ prop->setVisible( visible );
+ changed = true;
+ }
+}
+
+bool KexiTableDesignerViewPrivate::updatePropertiesVisibility(KexiDB::Field::Type fieldType, KoProperty::Set &set,
+ CommandGroup *commandGroup)
+{
+ bool changed = false;
+ KoProperty::Property *prop;
+ bool visible;
+
+ prop = &set["subType"];
+ kexipluginsdbg << "subType=" << prop->value().toInt() << " type=" << set["type"].value().toInt()<< endl;
+
+ //if there is no more than 1 subType name or it's a PK: hide the property
+ visible = (prop->listData() && prop->listData()->keys.count() > 1 /*disabled || isObjectTypeGroup*/)
+ && set["primaryKey"].value().toBool()==false;
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+
+ prop = &set["objectType"];
+ const bool isObjectTypeGroup = set["type"].value().toInt() == (int)KexiDB::Field::BLOB; // used only for BLOBs
+ visible = isObjectTypeGroup;
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+
+ prop = &set["unsigned"];
+ visible = KexiDB::Field::isNumericType(fieldType);
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+
+ prop = &set["length"];
+ visible = (fieldType == KexiDB::Field::Text);
+ if (prop->isVisible()!=visible) {
+// prop->setVisible( visible );
+ //update the length when it makes sense
+ const int lengthToSet = visible ? KexiDB::Field::defaultTextLength() : 0;
+ setPropertyValueIfNeeded( set, "length", lengthToSet,
+ commandGroup, false, false /*!rememberOldValue*/ );
+// if (lengthToSet != prop->value().toInt())
+// prop->setValue( lengthToSet, false );
+// changed = true;
+ }
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+#ifndef KEXI_NO_UNFINISHED
+ prop = &set["precision"];
+ visible = KexiDB::Field::isFPNumericType(fieldType);
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+#endif
+ prop = &set["visibleDecimalPlaces"];
+ visible = KexiDB::supportsVisibleDecimalPlacesProperty(fieldType);
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+
+ prop = &set["unique"];
+ visible = fieldType != KexiDB::Field::BLOB;
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+
+ prop = &set["indexed"];
+ visible = fieldType != KexiDB::Field::BLOB;
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+
+ prop = &set["allowEmpty"];
+ visible = KexiDB::Field::hasEmptyProperty(fieldType);
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+
+ prop = &set["autoIncrement"];
+ visible = KexiDB::Field::isAutoIncrementAllowed(fieldType);
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+
+//! @todo remove this when BLOB supports default value
+#ifdef KEXI_NO_UNFINISHED
+ prop = &set["defaultValue"];
+ visible = !isObjectTypeGroup;
+ setVisibilityIfNeeded( set, prop, visible, changed, commandGroup );
+#endif
+
+ return changed;
+}
+
+QString KexiTableDesignerViewPrivate::messageForSavingChanges(bool &emptyTable, bool skipWarning)
+{
+ KexiDB::Connection *conn = designerView->mainWin()->project()->dbConnection();
+ bool ok;
+ emptyTable = conn->isEmpty( *designerView->tempData()->table, ok ) && ok;
+ return i18n("Do you want to save the design now?")
+ + ( (emptyTable || skipWarning) ? QString::null :
+ (QString("\n\n") + designerView->part()->i18nMessage(":additional message before saving design",
+ designerView->parentDialog())) );
+}
+
+void KexiTableDesignerViewPrivate::updateIconForItem(KexiTableItem &item, KoProperty::Set& set)
+{
+ QVariant icon;
+ if (!set["rowSource"].value().toString().isEmpty() && !set["rowSourceType"].value().toString().isEmpty())
+ icon = "combo";
+ //show/hide icon in the table
+ view->data()->clearRowEditBuffer();
+ view->data()->updateRowEditBuffer(&item, COLUMN_ID_ICON, icon);
+ view->data()->saveRowChanges(item, true);
+}
+
+#include "kexitabledesignerview_p.moc"
diff --git a/kexi/plugins/tables/kexitabledesignerview_p.h b/kexi/plugins/tables/kexitabledesignerview_p.h
new file mode 100644
index 000000000..f5650e74c
--- /dev/null
+++ b/kexi/plugins/tables/kexitabledesignerview_p.h
@@ -0,0 +1,191 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIALTERTABLEDIALOG_P_H
+#define KEXIALTERTABLEDIALOG_P_H
+
+#include "kexitabledesignerview.h"
+#include <kcommand.h>
+
+class KexiDataAwarePropertySet;
+
+//! @internal indices for table columns
+#define COLUMN_ID_ICON 0
+#define COLUMN_ID_CAPTION 1
+#define COLUMN_ID_TYPE 2
+#define COLUMN_ID_DESC 3
+
+/*! @internal
+ Command group, reimplemented to get access to commands().
+ We need it to iterate through commands so we can perform a set of ALTER TABLE atomic actions. */
+class CommandGroup : public KMacroCommand
+{
+ public:
+ CommandGroup( const QString & name )
+ : KMacroCommand(name)
+ {}
+ virtual ~CommandGroup() {}
+ const QPtrList<KCommand>& commands() const { return m_commands; }
+};
+
+/*! @internal
+ Command history, reimplemented to get access to commands().
+ We need it to iterate through commands so we can perform a set of ALTER TABLE atomic actions. */
+class CommandHistory : public KCommandHistory
+{
+ Q_OBJECT
+ public:
+ CommandHistory(KActionCollection *actionCollection, bool withMenus = true);
+
+ const QPtrList<KCommand>& commands() const { return m_commandsToUndo; }
+
+ void addCommand(KCommand *command, bool execute = true);
+
+ void clear();
+
+ public slots:
+ virtual void undo();
+ virtual void redo();
+
+ protected:
+ QPtrList<KCommand> m_commandsToUndo, m_commandsToRedo;
+};
+
+//----------------------------------------------
+
+//! @internal
+class KexiTableDesignerViewPrivate
+{
+ public:
+ KexiTableDesignerViewPrivate(KexiTableDesignerView* aDesignerView);
+ ~KexiTableDesignerViewPrivate();
+
+ int generateUniqueId();
+
+ /*! @internal
+ Sets property \a propertyName in property set \a set to \a newValue.
+ If \a commandGroup is not 0, a new ChangeFieldPropertyCommand object is added there as well.
+ While setting the new value, addHistoryCommand_in_slotPropertyChanged_enabled is set to false,
+ so addHistoryCommand() wont be executed in slotPropertyChanged() as an answer to setting
+ the property.
+
+ If \a forceAddCommand is false (the default) and \a newValue does not differ from curent property value
+ (set[propertyName].value()), ChangeFieldPropertyCommand command is not added to the \a commandGroup.
+ Otherwise, command is always added.
+
+ \a rememberOldValue argument is passed to Property::setValue()
+
+ If \a slist and \a nlist if not NULL and not empty, these are passed to Property::setListData().
+ If \a slist and \a nlist if not NULL but empty, Property::setListData(0) is called.
+
+ addHistoryCommand_in_slotPropertyChanged_enabled is then set back to the original state.
+ */
+ void setPropertyValueIfNeeded( const KoProperty::Set& set, const QCString& propertyName,
+ const QVariant& newValue, CommandGroup* commandGroup,
+ bool forceAddCommand = false, bool rememberOldValue = true,
+ QStringList* const slist = 0, QStringList* const nlist = 0);
+
+ /*! Like above but allows to specify \a oldValue. */
+ void setPropertyValueIfNeeded(
+ const KoProperty::Set& set, const QCString& propertyName,
+ const QVariant& newValue, const QVariant& oldValue, CommandGroup* commandGroup,
+ bool forceAddCommand = false, bool rememberOldValue = true,
+ QStringList* const slist = 0, QStringList* const nlist = 0);
+
+ /*! @internal
+ Used in updatePropertiesVisibility().
+ Does nothing if visibility should not be changed, i.e. when prop->isVisible()==visible,
+ otherwise sets changed to true and sets visibility of property \a prop to \a visible.
+ */
+ void setVisibilityIfNeeded( const KoProperty::Set& set, KoProperty::Property* prop,
+ bool visible, bool &changed, CommandGroup *commandGroup );
+
+ bool updatePropertiesVisibility(KexiDB::Field::Type fieldType, KoProperty::Set &set,
+ CommandGroup *commandGroup = 0);
+
+ /*! \return message used to ask user for accepting saving the design.
+ \a emptyTable is set to true if the table designed contains no rows.
+ If \a skipWarning is true, no warning about data loss is appended (useful when
+ only non-physical altering actions will be performed). */
+ QString messageForSavingChanges(bool &emptyTable, bool skipWarning = false);
+
+ /*! Updates icon in the first column, depending on property set \a set.
+ For example, when "rowSource" and "rowSourceType" propertiesa are not empty,
+ "combo" icon appears. */
+ void updateIconForItem(KexiTableItem &item, KoProperty::Set& set);
+
+ KexiTableDesignerView* designerView;
+
+ KexiTableView *view; //!< helper
+
+ KexiTableViewData *data;
+
+ KexiDataAwarePropertySet *sets;
+
+ int row; //!< used to know if a new row is selected in slotCellSelected()
+
+ KToggleAction *action_toggle_pkey;
+
+ KPopupTitle *contextMenuTitle;
+
+ int uniqueIdCounter;
+
+ //! internal
+ int maxTypeNameTextWidth;
+ //! Set to true in beforeSwitchTo() to avoid asking again in storeData()
+ bool dontAskOnStoreData : 1;
+
+ bool slotTogglePrimaryKeyCalled : 1;
+
+ bool primaryKeyExists : 1;
+ //! Used in slotPropertyChanged() to avoid infinite recursion
+ bool slotPropertyChanged_primaryKey_enabled : 1;
+ //! Used in slotPropertyChanged() to avoid infinite recursion
+ bool slotPropertyChanged_subType_enabled : 1;
+ //! used in slotPropertyChanged() to disable addHistoryCommand()
+ bool addHistoryCommand_in_slotPropertyChanged_enabled : 1;
+ //! used in slotRowUpdated() to disable addHistoryCommand()
+ bool addHistoryCommand_in_slotRowUpdated_enabled : 1;
+ //! used in slotAboutToDeleteRow() to disable addHistoryCommand()
+ bool addHistoryCommand_in_slotAboutToDeleteRow_enabled : 1;
+ //! used in slotRowInserted() to disable addHistoryCommand()
+ bool addHistoryCommand_in_slotRowInserted_enabled : 1;
+
+ //! used to disable slotBeforeCellChanged()
+ bool slotBeforeCellChanged_enabled : 1;
+
+//! @tood temp; remove this:
+ //! Temporary flag, used for testingu the Alter Table machinery. Affects storeData()
+ //! Used in slotExecuteRealAlterTable() to switch on real alter table for a while.
+ bool tempStoreDataUsingRealAlterTable : 1;
+
+ /*! Set to a recent result of calling \ref tristate KexiTableDesignerView::storeData(bool dontAsk).
+ Then, it is used in \ref void KexiTableDesignerView::executeRealAlterTable()
+ to know what return value should be. */
+ tristate recentResultOfStoreData;
+
+ KActionCollection* historyActionCollection;
+ CommandHistory* history;
+
+ //! A cache used in KexiTableDesignerView::buildField() to quickly identify
+ //! properties internal to the designer
+ QAsciiDict<char> internalPropertyNames;
+};
+
+#endif
diff --git a/kexi/plugins/tables/kexitablehandler.desktop b/kexi/plugins/tables/kexitablehandler.desktop
new file mode 100644
index 000000000..8491b7a34
--- /dev/null
+++ b/kexi/plugins/tables/kexitablehandler.desktop
@@ -0,0 +1,118 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Kexi/Handler
+
+GenericName=Tables
+GenericName[bg]=Таблици
+GenericName[br]=Taolennoù
+GenericName[ca]=Taules
+GenericName[cs]=Tabulky
+GenericName[cy]=Tablau
+GenericName[da]=Tabeller
+GenericName[de]=Tabellen
+GenericName[el]=Πίνακες
+GenericName[eo]=Tabeloj
+GenericName[es]=Tablas
+GenericName[et]=Tabelid
+GenericName[eu]=Taulak
+GenericName[fa]=جدولها
+GenericName[fi]=Taulukot
+GenericName[fr]=Tableaux
+GenericName[fy]=Tabellen
+GenericName[ga]=Táblaí
+GenericName[gl]=Táboas
+GenericName[he]=טבלאות
+GenericName[hi]=तालिका
+GenericName[hr]=Tablice
+GenericName[hu]=Táblák
+GenericName[is]=Töflur
+GenericName[it]=Tabelle
+GenericName[ja]=テーブル
+GenericName[km]=តារាង​
+GenericName[lt]=Lentelės
+GenericName[lv]=Tabulas
+GenericName[ms]=Jadual
+GenericName[nb]=Tabeller
+GenericName[nds]=Tabellen
+GenericName[ne]=तालिका
+GenericName[nl]=Tabellen
+GenericName[nn]=Tabellar
+GenericName[pl]=Tabele
+GenericName[pt]=Tabelas
+GenericName[pt_BR]=Tabelas
+GenericName[ru]=Таблицы
+GenericName[se]=Tabeallat
+GenericName[sk]=Tabuľky
+GenericName[sl]=Tabele
+GenericName[sr]=Табеле
+GenericName[sr@Latn]=Tabele
+GenericName[sv]=Tabeller
+GenericName[ta]=அட்டவணைகள்
+GenericName[tr]=Tablolar
+GenericName[uk]=Таблиці
+GenericName[uz]=Jadvallar
+GenericName[uz@cyrillic]=Жадваллар
+GenericName[zh_CN]=表
+GenericName[zh_TW]=表格
+Name=Tables
+Name[bg]=Таблици
+Name[br]=Taolennoù
+Name[ca]=Taules
+Name[cs]=Tabulky
+Name[cy]=Tablau
+Name[da]=Tabeller
+Name[de]=Tabellen
+Name[el]=Πίνακες
+Name[eo]=Tabeloj
+Name[es]=Tablas
+Name[et]=Tabelid
+Name[eu]=Taulak
+Name[fa]=جدولها
+Name[fi]=Taulukot
+Name[fr]=Tableaux
+Name[fy]=Tabellen
+Name[ga]=Táblaí
+Name[gl]=Táboas
+Name[he]=טבלאות
+Name[hi]=टेबल्स
+Name[hr]=Tablice
+Name[hu]=Táblák
+Name[is]=Töflur
+Name[it]=Tabelle
+Name[ja]=テーブル
+Name[km]=តារាង​
+Name[lt]=Lentelės
+Name[lv]=Tabulas
+Name[ms]=Jadual
+Name[nb]=Tabeller
+Name[nds]=Tabellen
+Name[ne]=तालिकाहरू
+Name[nl]=Tabellen
+Name[nn]=Tabellar
+Name[pl]=Tabele
+Name[pt]=Tabelas
+Name[pt_BR]=Tabelas
+Name[ru]=Таблицы
+Name[se]=Tabeallat
+Name[sk]=Tabuľky
+Name[sl]=Tabele
+Name[sr]=Табеле
+Name[sr@Latn]=Tabele
+Name[sv]=Tabeller
+Name[ta]=அட்டவணை
+Name[tg]=Ҷадвалҳо
+Name[tr]=Tablolar
+Name[uk]=Таблиці
+Name[uz]=Jadvallar
+Name[uz@cyrillic]=Жадваллар
+Name[wa]=Tåvleas
+Name[zh_CN]=表
+Name[zh_TW]=表格
+X-KDE-Library=kexihandler_table
+X-KDE-ParentApp=kexi
+X-Kexi-PartVersion=2
+X-Kexi-TypeName=table
+X-Kexi-TypeMime=kexi/table
+X-Kexi-ItemIcon=table
+X-Kexi-SupportsDataExport=true
+X-Kexi-SupportsPrinting=true
diff --git a/kexi/plugins/tables/kexitablepart.cpp b/kexi/plugins/tables/kexitablepart.cpp
new file mode 100644
index 000000000..3d09a81ed
--- /dev/null
+++ b/kexi/plugins/tables/kexitablepart.cpp
@@ -0,0 +1,313 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitablepart.h"
+
+#include <kdebug.h>
+#include <kgenericfactory.h>
+#include <kmessagebox.h>
+#include <ktabwidget.h>
+#include <kiconloader.h>
+
+#include "keximainwindow.h"
+#include "kexiproject.h"
+#include "kexipartinfo.h"
+#include "widget/kexidatatable.h"
+#include "widget/tableview/kexidatatableview.h"
+#include "kexitabledesignerview.h"
+#include "kexitabledesigner_dataview.h"
+#include "kexilookupcolumnpage.h"
+
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+#include <kexidialogbase.h>
+
+//! @internal
+class KexiTablePart::Private
+{
+ public:
+ Private()
+ {
+ }
+ ~Private()
+ {
+ delete static_cast<KexiLookupColumnPage*>(lookupColumnPage);
+ }
+ QGuardedPtr<KexiLookupColumnPage> lookupColumnPage;
+};
+
+KexiTablePart::KexiTablePart(QObject *parent, const char *name, const QStringList &l)
+ : KexiPart::Part(parent, name, l)
+ , d(new Private())
+{
+ // REGISTERED ID:
+ m_registeredPartID = (int)KexiPart::TableObjectType;
+
+ kdDebug() << "KexiTablePart::KexiTablePart()" << endl;
+ m_names["instanceName"]
+ = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). "
+ "Use '_' character instead of spaces. First character should be a..z character. "
+ "If you cannot use latin characters in your language, use english word.",
+ "table");
+ m_names["instanceCaption"] = i18n("Table");
+ m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode;
+//js TODO: also add Kexi::TextViewMode when we'll have SQL ALTER TABLE EDITOR!!!
+}
+
+KexiTablePart::~KexiTablePart()
+{
+ delete d;
+}
+
+void KexiTablePart::initPartActions()
+{
+}
+
+void KexiTablePart::initInstanceActions()
+{
+//moved to main window createSharedAction(Kexi::DataViewMode, i18n("Filter"), "filter", 0, "tablepart_filter");
+
+ KAction *a = createSharedToggleAction(
+ Kexi::DesignViewMode, i18n("Primary Key"), "key", 0, "tablepart_toggle_pkey");
+// Kexi::DesignViewMode, i18n("Toggle Primary Key"), "key", 0, "tablepart_toggle_pkey");
+ a->setWhatsThis(i18n("Sets or removes primary key for currently selected field."));
+}
+
+KexiDialogTempData* KexiTablePart::createTempData(KexiDialogBase* dialog)
+{
+ return new KexiTablePart::TempData(dialog);
+}
+
+KexiViewBase* KexiTablePart::createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode, QMap<QString,QString>*)
+{
+ KexiMainWindow *win = dialog->mainWin();
+ if (!win || !win->project() || !win->project()->dbConnection())
+ return 0;
+
+
+ KexiTablePart::TempData *temp = static_cast<KexiTablePart::TempData*>(dialog->tempData());
+ if (!temp->table) {
+ temp->table = win->project()->dbConnection()->tableSchema(item.name());
+ kdDebug() << "KexiTablePart::execute(): schema is " << temp->table << endl;
+ }
+
+ if (viewMode == Kexi::DesignViewMode) {
+ KexiTableDesignerView *t = new KexiTableDesignerView(win, parent);
+ return t;
+ }
+ else if (viewMode == Kexi::DataViewMode) {
+ if(!temp->table)
+ return 0; //todo: message
+ //we're not setting table schema here -it will be forced to set
+ // in KexiTableDesigner_DataView::afterSwitchFrom()
+ KexiTableDesigner_DataView *t = new KexiTableDesigner_DataView(win, parent);
+ return t;
+ }
+ return 0;
+}
+
+bool KexiTablePart::remove(KexiMainWindow *win, KexiPart::Item &item)
+{
+ if (!win || !win->project() || !win->project()->dbConnection())
+ return false;
+
+ KexiDB::Connection *conn = win->project()->dbConnection();
+ KexiDB::TableSchema *sch = conn->tableSchema(item.identifier());
+
+ if (sch) {
+ tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema(
+ win, *conn, *sch,
+ i18n("You are about to remove table \"%1\" but following objects using this table are opened:")
+ .arg(sch->name()));
+ return true == conn->dropTable( sch );
+ }
+ //last chance: just remove item
+ return conn->removeObject( item.identifier() );
+}
+
+tristate KexiTablePart::rename(KexiMainWindow *win, KexiPart::Item & item,
+ const QString& newName)
+{
+//TODO: what about objects (queries/forms) that use old name?
+ KexiDB::Connection *conn = win->project()->dbConnection();
+ KexiDB::TableSchema *sch = conn->tableSchema(item.identifier());
+ if (!sch)
+ return false;
+ return conn->alterTableName(*sch, newName);
+}
+
+KexiDB::SchemaData*
+KexiTablePart::loadSchemaData(KexiDialogBase *dlg, const KexiDB::SchemaData& sdata, int viewMode)
+{
+ Q_UNUSED( viewMode );
+
+ return dlg->mainWin()->project()->dbConnection()->tableSchema( sdata.name() );
+}
+
+#if 0
+KexiPart::DataSource *
+KexiTablePart::dataSource()
+{
+ return new KexiTableDataSource(this);
+}
+#endif
+
+tristate KexiTablePart::askForClosingObjectsUsingTableSchema(QWidget *parent, KexiDB::Connection& conn,
+ KexiDB::TableSchema& table, const QString& msg)
+{
+ QPtrList<KexiDB::Connection::TableSchemaChangeListenerInterface>* listeners
+ = conn.tableSchemaChangeListeners(table);
+ if (!listeners || listeners->isEmpty())
+ return true;
+
+ QString openedObjectsStr = "<ul>";
+ for (QPtrListIterator<KexiDB::Connection::TableSchemaChangeListenerInterface> it(*listeners);
+ it.current(); ++it) {
+ openedObjectsStr += QString("<li>%1</li>").arg(it.current()->listenerInfoString);
+ }
+ openedObjectsStr += "</ul>";
+ int r = KMessageBox::questionYesNo(parent,
+ "<p>"+msg+"</p><p>"+openedObjectsStr+"</p><p>"
+ +i18n("Do you want to close all windows for these objects?"),
+ QString::null, KGuiItem(i18n("Close windows"),"fileclose"), KStdGuiItem::cancel());
+ tristate res;
+ if (r == KMessageBox::Yes) {
+ //try to close every window
+ res = conn.closeAllTableSchemaChangeListeners(table);
+ if (res!=true) //do not expose closing errors twice; just cancel
+ res = cancelled;
+ }
+ else
+ res = cancelled;
+
+ return res;
+}
+
+QString
+KexiTablePart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const
+{
+ if (englishMessage=="Design of object \"%1\" has been modified.")
+ return i18n("Design of table \"%1\" has been modified.");
+
+ if (englishMessage=="Object \"%1\" already exists.")
+ return i18n("Table \"%1\" already exists.");
+
+ if (dlg->currentViewMode()==Kexi::DesignViewMode && !dlg->neverSaved()
+ && englishMessage==":additional message before saving design")
+ return i18n("Warning! Any data in this table will be removed upon design's saving!");
+
+ return englishMessage;
+}
+
+void KexiTablePart::setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin)
+{
+ if (!d->lookupColumnPage) {
+ d->lookupColumnPage = new KexiLookupColumnPage(0);
+ connect(d->lookupColumnPage, SIGNAL(jumpToObjectRequested(const QCString&, const QCString&)),
+ mainWin, SLOT(highlightObject(const QCString&, const QCString&)));
+
+//! @todo add "Table" tab
+
+ /*
+ connect(d->dataSourcePage, SIGNAL(formDataSourceChanged(const QCString&, const QCString&)),
+ KFormDesigner::FormManager::self(), SLOT(setFormDataSource(const QCString&, const QCString&)));
+ connect(d->dataSourcePage, SIGNAL(dataSourceFieldOrExpressionChanged(const QString&, const QString&, KexiDB::Field::Type)),
+ KFormDesigner::FormManager::self(), SLOT(setDataSourceFieldOrExpression(const QString&, const QString&, KexiDB::Field::Type)));
+ connect(d->dataSourcePage, SIGNAL(insertAutoFields(const QString&, const QString&, const QStringList&)),
+ KFormDesigner::FormManager::self(), SLOT(insertAutoFields(const QString&, const QString&, const QStringList&)));*/
+ }
+
+ KexiProject *prj = mainWin->project();
+ d->lookupColumnPage->setProject(prj);
+
+//! @todo add lookup field icon
+ tab->addTab( d->lookupColumnPage, SmallIconSet("combo"), "");
+ tab->setTabToolTip( d->lookupColumnPage, i18n("Lookup column"));
+}
+
+KexiLookupColumnPage* KexiTablePart::lookupColumnPage() const
+{
+ return d->lookupColumnPage;
+}
+
+//----------------
+
+#if 0
+KexiTableDataSource::KexiTableDataSource(KexiPart::Part *part)
+ : KexiPart::DataSource(part)
+{
+}
+
+KexiTableDataSource::~KexiTableDataSource()
+{
+}
+
+KexiDB::FieldList *
+KexiTableDataSource::fields(KexiProject *project, const KexiPart::Item &it)
+{
+ kdDebug() << "KexiTableDataSource::fields(): " << it.name() << endl;
+ return project->dbConnection()->tableSchema(it.name());
+}
+
+KexiDB::Cursor *
+KexiTableDataSource::cursor(KexiProject * /*project*/,
+ const KexiPart::Item &/*it*/, bool /*buffer*/)
+{
+ return 0;
+}
+#endif
+
+//----------------
+
+KexiTablePart::TempData::TempData(QObject* parent)
+ : KexiDialogTempData(parent)
+ , table(0)
+ , tableSchemaChangedInPreviousView(true /*to force reloading on startup*/ )
+{
+}
+
+//----------------
+
+/**
+TODO
+*/
+/*
+AboutData( const char *programName,
+ const char *version,
+ const char *i18nShortDescription = 0,
+ int licenseType = License_Unknown,
+ const char *i18nCopyrightStatement = 0,
+ const char *i18nText = 0,
+ const char *homePageAddress = 0,
+ const char *bugsEmailAddress = "submit@bugs.kde.org"
+);
+
+#define KEXIPART_EXPORT_FACTORY( libname, partClass, aboutData ) \
+ static KexiPart::AboutData * libname ## updateAD(KexiPart::AboutData *ad) \
+ { ad->setAppName( #libname ); return ad; } \
+ K_EXPORT_COMPONENT_FACTORY( libname, KGenericFactory<partClass>(libname ## updateAD(#libname)) )
+*/
+
+K_EXPORT_COMPONENT_FACTORY( kexihandler_table, KGenericFactory<KexiTablePart>("kexihandler_table") )
+
+#include "kexitablepart.moc"
+
diff --git a/kexi/plugins/tables/kexitablepart.h b/kexi/plugins/tables/kexitablepart.h
new file mode 100644
index 000000000..e4b060ad1
--- /dev/null
+++ b/kexi/plugins/tables/kexitablepart.h
@@ -0,0 +1,100 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXITABLEPART_H
+#define KEXITABLEPART_H
+
+#include <kexi.h>
+#include <kexipart.h>
+#include <kexidialogbase.h>
+//#include <kexipartdatasource.h>
+#include <kexipartitem.h>
+#include <kexidb/fieldlist.h>
+
+class KexiMainWin;
+class KexiLookupColumnPage;
+
+class KexiTablePart : public KexiPart::Part
+{
+ Q_OBJECT
+
+ public:
+ KexiTablePart(QObject *parent, const char *name, const QStringList &);
+ virtual ~KexiTablePart();
+
+ virtual bool remove(KexiMainWindow *win, KexiPart::Item &item);
+
+ virtual tristate rename(KexiMainWindow *win, KexiPart::Item &item,
+ const QString& newName);
+
+// virtual KexiPart::DataSource *dataSource();
+
+ class TempData : public KexiDialogTempData
+ {
+ public:
+ TempData(QObject* parent);
+ KexiDB::TableSchema *table;
+ /*! true, if \a table member has changed in previous view. Used on view switching.
+ We're checking this flag to see if we should refresh data for DataViewMode. */
+ bool tableSchemaChangedInPreviousView : 1;
+ };
+
+ static tristate askForClosingObjectsUsingTableSchema(
+ QWidget *parent, KexiDB::Connection& conn,
+ KexiDB::TableSchema& table, const QString& msg);
+
+ virtual QString i18nMessage(const QCString& englishMessage,
+ KexiDialogBase* dlg) const;
+
+ KexiLookupColumnPage* lookupColumnPage() const;
+
+ protected:
+ virtual KexiDialogTempData* createTempData(KexiDialogBase* dialog);
+
+ virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog,
+ KexiPart::Item &item, int viewMode = Kexi::DataViewMode, QMap<QString,QString>* staticObjectArgs = 0);
+
+ virtual void initPartActions();
+ virtual void initInstanceActions();
+
+ virtual void setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin);
+
+ virtual KexiDB::SchemaData* loadSchemaData(KexiDialogBase *dlg, const KexiDB::SchemaData& sdata, int viewMode);
+
+ private:
+ class Private;
+ Private* d;
+};
+
+#if 0
+class KexiTableDataSource : public KexiPart::DataSource
+{
+ public:
+ KexiTableDataSource(KexiPart::Part *part);
+ ~KexiTableDataSource();
+
+ virtual KexiDB::FieldList *fields(KexiProject *project, const KexiPart::Item &item);
+ virtual KexiDB::Cursor *cursor(KexiProject *project, const KexiPart::Item &item, bool buffer);
+};
+#endif
+
+#endif
+
diff --git a/kexi/plugins/tables/kexitablepartinstui.rc b/kexi/plugins/tables/kexitablepartinstui.rc
new file mode 100644
index 000000000..e96a59767
--- /dev/null
+++ b/kexi/plugins/tables/kexitablepartinstui.rc
@@ -0,0 +1,18 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexitablepartinst" version="6">
+
+<MenuBar>
+ <Menu name="edit" noMerge="0">
+ <text>&amp;Edit</text>
+ <Separator/>
+ <Action name="tablepart_toggle_pkey"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="designToolBar" fullWidth="false" noMerge="0">
+ <text>Design</text>
+ <!-- Design View -->
+ <!-- TODO: reenable after shared toggle actions fix: Action name="tablepart_toggle_pkey"/ -->
+</ToolBar>
+
+</kpartgui>
diff --git a/kexi/plugins/tables/kexitablepartui.rc b/kexi/plugins/tables/kexitablepartui.rc
new file mode 100644
index 000000000..c78b2587f
--- /dev/null
+++ b/kexi/plugins/tables/kexitablepartui.rc
@@ -0,0 +1,7 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kexitablepart" version="5">
+
+<MenuBar>
+</MenuBar>
+
+</kpartgui>
diff --git a/kexi/tests/Makefile.am b/kexi/tests/Makefile.am
new file mode 100644
index 000000000..5eefec2aa
--- /dev/null
+++ b/kexi/tests/Makefile.am
@@ -0,0 +1,6 @@
+
+SUBDIRS = newapi widgets
+
+# unused: parser tableview
+# startup
+
diff --git a/kexi/tests/README b/kexi/tests/README
new file mode 100644
index 000000000..e5c61abfa
--- /dev/null
+++ b/kexi/tests/README
@@ -0,0 +1,14 @@
+Kexi tests
+----------
+
+newapi/ New KexiDB API test in few aspects
+parser/ Interactive test of KEXISQL parser
+tableview/ Test for not-data-aware KexiTableView widget
+
+See README file in selected directory for details.
+
+In these files:
+- <driver_name> means selected name of a driver from kexidb/drivers,
+ eg. sqlite, mysql (case insensitive)
+- <new_db_name> mean any valid database name
+- <db_name> means any existing database name
diff --git a/kexi/tests/altertable/1.kexi b/kexi/tests/altertable/1.kexi
new file mode 100644
index 000000000..1165d90ec
--- /dev/null
+++ b/kexi/tests/altertable/1.kexi
Binary files differ
diff --git a/kexi/tests/altertable/Makefile.am b/kexi/tests/altertable/Makefile.am
new file mode 100644
index 000000000..a23890076
--- /dev/null
+++ b/kexi/tests/altertable/Makefile.am
@@ -0,0 +1,22 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_PROGRAMS = kexialtertabletest
+
+INCLUDES = -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget \
+ -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/lib \
+ $(all_includes)
+
+SUBDIRS = .
+
+METASOURCES = AUTO
+
+kexialtertabletest_SOURCES = altertable.cpp
+kexialtertabletest_LDADD = $(LIB_QT) $(LIB_KDECORE) $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/kexiutils/libkexiutils.la \
+ $(top_builddir)/kexi/main/libkeximain.la \
+ $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la
+kexialtertabletest_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
diff --git a/kexi/tests/altertable/README b/kexi/tests/altertable/README
new file mode 100644
index 000000000..1278a54dc
--- /dev/null
+++ b/kexi/tests/altertable/README
@@ -0,0 +1,200 @@
+===================================================
+ README for the "altertable" test
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+===================================================
+
+
+Invoking
+--------
+"altertable" test requires <db_name>, <driver_name> and <alterscript> arguments
+
+The purpose of .altertable files
+--------------------------------
+.altertable files are provoded to test a given use case of table altering.
+It contains a set of commands mapped to a sequence of ALTER TABLE and other
+SQL statements. The commands are mapped to AlterTableHandler::***Action objects,
+what is equat to actions performed by the user during the table designing.
+
+Second purpose of the test is testing the Table Designer's GUI itself.
+Whenever there is a bug in a the GUI, e.g. in the property editor,
+the resulting schema can differ from expected, or there can be even a crash.
+The suite already helped to find a few bugs in the GUI code.
+
+
+How the test is performed, .alterscript file contents
+-----------------------------------------------------
+
+The file can be consisted of many sections described below. The test can be built by:
+a. requesting a table design to be opened in the Table designer,
+b. specifying commands affecting the design,
+c. then checking the actions sequence genrated by the "alter table machinery"
+ (it's a method that allocates AlterTableHandler::***Action objects and add them
+ using AlterTableHandler::addAction() to the alte table machinery.
+ The result is the same as user's actions);
+d. then saving the design,
+e. and finally checking the table data with the expected table contents.
+Every comparison is performed line by line: obtained result is compared with expected one.
+
+2. Expected result of altering the table.
+ It's a full human-redable dump of table schema and its contents.
+
+Each section has a strictly defined format, so the test suite can combine commands into more complex sets.
+
+
+Available commands of the test suite
+------------------------------------
+
+1. Top-level commands
+
+* openDatabase <filename>
+ Opens kexi database for tests. In fact the file is copied to a temporary file (with .tmp suffix)
+ and we're dealing with the copy, so the original could not be broken. Thus, tests can be reproduced.
+#TODO: support server databases
+ Example use: openDatabase 1.kexi
+
+* designTable <tablename> \n <block> \n endDesign
+ Opens table in design mode. <block> contains one or more schema altering
+ commands described in 2.
+
+2. Commands for altering table fields (during the design mode, within "designTable" command):
+
+* insertField <rownumber(int)> <fieldname(string)>
+ Inserts a new table field with default properties (text type) and 'fieldname' name.
+ Note that the inserted field can *replace* an existing field. To avoid this, use
+ insertEmptyRow command before insertField to add an empty row.
+ Example use: insertField 2 abc
+
+* insertEmptyRow <rownumber(int)>
+ Inserts empty row before 'rownumber'. Rows below are moved down.
+ Example use: insertEmptyRow 2
+
+* removeField <rownumber(int)>
+ Removes a table field at a specified row.
+ Example use: removeField 1
+
+* changeFieldProperty <rownumber(int)> <propertyname(string)> <valuetype(string)> <value(string)>
+ Changes property of table field at a specified row.
+ 'valuetype' can be int, string, bool, double/float, bool/boolean, data, dateTime, time,
+ bytearray, longlong.
+ <value(string)> should be a string representation of the value. Special cases are:
+ byteArray: hexadecimal string like 'fd1a5c', dateTime: yyyy-mm-ddThh:mm:ss string
+ like '2005-11-05T12:34:56'.
+ Null values should be specified as <null> string. Empty string values should be specified as "".
+ 'type' property means a field datatype, where value can be any of the names
+ in KexiDB::Field::Type enum, written as string, e.g. "integer","text", "date", etc.
+ Example use: changeFieldProperty 1 type string date
+
+* i++
+ Increases "i" variable by 1. This integer variable is initialized to 1 before test is executed.
+ Can be used as an argument for <rownumber(int)> for above field-related commands.
+
+* i=<number(int)>
+ Sets "i" variable to <number(int)>.
+ Example use: shows that using the variable instead of constants allows to insert
+ a command without a need for managing subsequent arguments.
+ i=3
+ removeField i
+ insertField i textField
+ changeFieldProperty i type string text
+ i++ #i is now 4
+ insertField i longTextField
+ changeFieldProperty i type string longText
+
+3. Commands related to altered (not saved) table schema:
+
+* showSchema [clipboard]
+ Shows schema dump as returned by KexiTableDesignerInterface::debugStringForCurrentTableSchema().
+ Useful for creating "checkSchema" checks: Just paste the output to the test file.
+ You can use "clipboard" word to copy prepare the schema dump to clipboard.
+
+* checkSchema \n <block> \n endSchema
+ Checks validity of the not yet saved schema altered in the Table Designer using the
+ actions listed in p. 1. The <block> should end with "endSchema" line.
+ Between these lines there should be pasted a <block> - exact textual schema dump as returned
+ by KexiTableDesignerInterface::debugStringForCurrentTableSchema().
+ The check compares lines returned from the Designer with the lines you provided, line by line.
+ You can use "showSchema" command to display the expected schema to the stderr and copy the text.
+ Every line contains up to three main sections <fieldname> <type> [<constraints>].
+ The lines can be indented - trailing and leading whitespaces are ignored in comparison.
+ Example use:
+ checkSchema
+ textfield Text(200)
+ owner UNSIGNED Integer
+ booleanfield Boolean NOTNULL
+ endSchema
+
+4. Commands related to simplified list of Alter Table actions (simulated, before real saving):
+
+* showActions [clipboard]
+ Shows the list of simplified Alter Table actions that are result of commands related to table fields,
+ mentioned in 1.
+ You can use "clipboard" word to copy prepare the expected actions dump to clipboard.
+
+* checkActions \n <block> \n endActions
+ Checks validity of the list of simplified Alter Table actions.
+ The <block> should end with "endActions" line.
+ The check compares lines returned from the Designer with the lines you provided as <block>, line by line.
+ Textual dump of actions is obtained from KexiTableDesignerInterface::simulateAlterTableExecution().
+ Every line contains section(s): <actionname> [(<fielddebugstring>)].
+ Example use:
+ checkActions
+ Insert table field "textfield" at position 1 (textfield Text(200))
+ Remove table field "model"
+ Insert table field "longtextfield" at position 3 (longtextfield Text(200))
+ endActions
+
+5. Commands related to physical schema saving (altering) and checking its result
+
+* saveTableDesign
+ Executes the final Alter Table function. Table design will be altered and data should
+ be preserved. After this command it is usable to run "checkTableData" test to see
+ whether the data looks as expected.
+
+* showTableData [clipboard]
+ Shows current table contents in tab-separated CSV format (one row per record)
+ on the stderr; text is encoded in utf-8. The data is printed to the stderr.
+ If optional "clipboard" word is present, the data is copied to clipboard instead.
+ Table dumps can be sometimes large and hard to prepare by hand, so you can use
+ "clipboard" word to prepare the expected table dump by pasting the text to
+ a .altertable file.
+ For details about the output format in the description "checkTableData".
+
+* checkTableData \n <block> \n endTableData
+ Compares the current contents of table with expected contents, line by line.
+ The data has to be in tab-separated CSV format (one row per record);
+ text has to be encoded in utf-8 and enclosed in " quotes.
+ Column names should be included as a first row.
+ You can use showTableData command first and then copy the results to your test file for later.
+ Example use:
+ checkTableData
+ ID Name Surname
+ 1 John Wayne
+ 2 Al Pacino
+ endTableData
+
+6. Other commands.
+
+* closeWindow
+ Closes the currently opened table designer window without asking for saving changes.
+
+* stop
+ Stops processing immediately. For example, this can be inserted temporarily to stop testing
+ (with success result). This command is available in any place.
+
+* quit
+ Executes "closeWindow" command and quits the application (with success result).
+
+6. Comments
+
+Comments can be inserted by adding # on the left hand as in bash shell
+or using /* and */ for multiple rows. Empty rows are ignored.
+
+
+The result of executing the "altertable" test
+---------------------------------------------
+
+On errors, kexialtertabletest program will show an appropriate error message with line number
+where the error encountered and stop executing the tests.
+
+A given "checkSchema" command should result in "Schema check for table 'foo': OK" message.
+Entire test from a give .altertable file 'foo' should end with "Tests from file 'foo': OK" message.
diff --git a/kexi/tests/altertable/TODO b/kexi/tests/altertable/TODO
new file mode 100644
index 000000000..f54a4b50d
--- /dev/null
+++ b/kexi/tests/altertable/TODO
@@ -0,0 +1,3 @@
+TODOs for the "altertable" test
+
+- support server databases
diff --git a/kexi/tests/altertable/alltypes.altertable b/kexi/tests/altertable/alltypes.altertable
new file mode 100644
index 000000000..70435a606
--- /dev/null
+++ b/kexi/tests/altertable/alltypes.altertable
@@ -0,0 +1,109 @@
+openDatabase 1.kexi
+
+/*
+ This test checks:
+ - creating table fields of all possible types
+ - adding new fields to the table with preserving the original content
+ Additionally:
+ - as "booleanField" field is type of bool, and by default
+ it is declared as NOT NULL, values for it are filled with "false".
+ - 3rd (original) field is removed before adding new fields
+
+ Used tables: cars
+*/
+designTable cars
+ i=3
+ removeField i
+ insertField i textField
+ changeFieldProperty i type string text
+ i++
+ insertField i longTextField
+ changeFieldProperty i type string longText
+ i++
+ insertField i byteField
+ changeFieldProperty i type string byte
+ i++
+ insertField i shortIntField
+ changeFieldProperty i type string shortInteger
+ i++
+ insertField i intField
+ changeFieldProperty i type string integer
+ i++
+ insertField i bigIntField
+ changeFieldProperty i type string bigInteger
+ i++
+ insertField i booleanField
+ changeFieldProperty i type string boolean
+ i++
+ insertField i dateField
+ changeFieldProperty i type string date
+ i++
+ insertField i dateTimeField
+ changeFieldProperty i type string dateTime
+ i++
+ insertField i timeField
+ changeFieldProperty i type string time
+ i++
+ insertField i floatField
+ changeFieldProperty i type string float
+ i++
+ insertField i doubleField
+ changeFieldProperty i type string double
+ i++
+ insertField i blobField
+ changeFieldProperty i type string blob
+endDesign
+
+ showSchema
+
+ checkSchema
+ id UNSIGNED Integer AUTOINC UNIQUE PKEY NOTNULL NOTEMPTY
+ owner UNSIGNED Integer
+ textfield Text(200)
+ longtextfield LongText
+ bytefield Byte
+ shortintfield ShortInteger
+ intfield Integer
+ bigintfield BigInteger
+ booleanfield Boolean NOTNULL DEFAULT=[bool]false
+ datefield Date
+ datetimefield DateTime
+ timefield Time
+ floatfield Float
+ doublefield Double
+ blobfield BLOB
+ endSchema
+
+ showActions
+
+ checkActions
+ Remove table field "model"
+ Insert table field "textfield" at position 2 (textfield Text(200))
+ Insert table field "longtextfield" at position 3 (longtextfield LongText)
+ Insert table field "bytefield" at position 4 (bytefield Byte)
+ Insert table field "shortintfield" at position 5 (shortintfield ShortInteger)
+ Insert table field "intfield" at position 6 (intfield Integer)
+ Insert table field "bigintfield" at position 7 (bigintfield BigInteger)
+ Insert table field "booleanfield" at position 8 (booleanfield Boolean NOTNULL DEFAULT=[bool]false)
+ Insert table field "datefield" at position 9 (datefield Date)
+ Insert table field "datetimefield" at position 10 (datetimefield DateTime)
+ Insert table field "timefield" at position 11 (timefield Time)
+ Insert table field "floatfield" at position 12 (floatfield Float)
+ Insert table field "doublefield" at position 13 (doublefield Double)
+ Insert table field "blobfield" at position 14 (blobfield BLOB)
+ endActions
+
+ saveTableDesign #executes Alter Table
+
+ showTableData clipboard
+ stop
+
+ checkTableData
+"ID" "Car owner" "textField" "longTextField" "byteField" "shortIntField" "intField" "bigIntField" "booleanField" "dateField" "dateTimeField" "timeField" "floatField" "doubleField" "blobField"
+1 2 false
+2 2 false
+3 3 false
+5 4 false
+6 3 false
+ endTableData
+
diff --git a/kexi/tests/altertable/altertable.cpp b/kexi/tests/altertable/altertable.cpp
new file mode 100644
index 000000000..bf14bc009
--- /dev/null
+++ b/kexi/tests/altertable/altertable.cpp
@@ -0,0 +1,716 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "altertable.h"
+
+#include <unistd.h>
+
+#include <qapplication.h>
+#include <qfile.h>
+#include <qdir.h>
+#include <qregexp.h>
+#include <qclipboard.h>
+
+#include <kdebug.h>
+
+#include <main/keximainwindowimpl.h>
+#include <core/kexiaboutdata.h>
+#include <core/kexidialogbase.h>
+#include <core/kexiviewbase.h>
+#include <core/kexipartitem.h>
+#include <core/kexitabledesignerinterface.h>
+#include <core/kexiinternalpart.h>
+#include <kexiutils/utils.h>
+#include <koproperty/set.h>
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+
+QString testFilename;
+QFile testFile;
+QTextStream testFileStream;
+QStringList testFileLine;
+uint testLineNumber = 0;
+QString origDbFilename, dbFilename;
+int variableI = 1; // simple variable 'i' support
+int newArgc;
+char** newArgv;
+KexiMainWindowImpl* win = 0;
+KexiProject* prj = 0;
+
+void showError(const QString& msg)
+{
+ QString msg_(msg);
+ msg_.prepend(QString("Error at line %1: ").arg(testLineNumber));
+ kdDebug() << msg_ << endl;
+}
+
+/* Reads a single line from testFileStream, fills testFileLine, updates testLineNumber
+ text in quotes is extracted, e.g. \"ab c\" is treat as one item "ab c"
+ Returns flas on failure (e.g. end of file).
+ Empty lines and lines or parts of lines with # (comments) are omitted. */
+tristate readLineFromTestFile(const QString& expectedCommandName = QString::null)
+{
+ QString s;
+ bool blockComment = false;
+ while (true) {
+ if (testFileStream.atEnd())
+ return cancelled;
+ testLineNumber++;
+ s = testFileStream.readLine().stripWhiteSpace();
+ if (blockComment) {
+ if (s.endsWith("*/"))
+ blockComment = false;
+ continue;
+ }
+ if (!blockComment && s.startsWith("/*")) {
+ blockComment = true;
+ continue;
+ }
+ if (s.startsWith("#"))
+ continue; //skip commented line
+ if (!s.isEmpty())
+ break;
+ }
+ s.append(" "); //sentinel
+ QString item;
+ testFileLine.clear();
+ const int len = s.length();
+ bool skipWhiteSpace = true, quoted = false;
+ for (int i=0; i<len; i++) {
+ const QChar ch( s.ref(i) );
+ if (skipWhiteSpace) {
+ if (ch=='#')
+ break; //eoln
+ if (ch==' ' || ch=='\t')
+ continue;
+ skipWhiteSpace = false;
+ if (ch=='\"') {
+ quoted = true;
+ continue;
+ }
+ item.append(ch);
+ }
+ else {
+ if ((quoted && ch=='\"') || (!quoted && (ch==' ' || ch=='\t'))) { //end of item
+ skipWhiteSpace = true;
+ quoted = false;
+ testFileLine.append( item );
+ item = QString::null;
+ continue;
+ }
+ item.append(ch);
+ }
+ }
+ if (!expectedCommandName.isEmpty() && testFileLine[0]!=expectedCommandName) {
+ showError( QString("Invalid command '%1', expected '%2'")
+ .arg(testFileLine[0]).arg(expectedCommandName));
+ return false;
+ }
+ if (quoted) {
+ showError( "Invalid contents" );
+ return false;
+ }
+ return true;
+}
+
+bool checkItemsNumber(int expectedNumberOfItems, int optionalNumberOfItems = -1)
+{
+ bool ok = expectedNumberOfItems==(int)testFileLine.count();
+ if (optionalNumberOfItems>0)
+ ok = ok || optionalNumberOfItems==(int)testFileLine.count();
+ if (!ok) {
+ QString msg = QString("Invalid number of args (%1) for command '%2', expected: %3")
+ .arg(testFileLine.count()).arg(testFileLine[0]).arg(expectedNumberOfItems);
+ if (optionalNumberOfItems>0)
+ msg.append( QString(" or %1").arg(optionalNumberOfItems) );
+ showError( msg );
+ return false;
+ }
+ return true;
+}
+
+QVariant::Type typeNameToQVariantType(const QCString& name_)
+{
+ QCString name( name_.lower() );
+ if (name=="string")
+ return QVariant::String;
+ if (name=="int")
+ return QVariant::Int;
+ if (name=="bool" || name=="boolean")
+ return QVariant::Bool;
+ if (name=="double" || name=="float")
+ return QVariant::Double;
+ if (name=="date")
+ return QVariant::Date;
+ if (name=="datetime")
+ return QVariant::DateTime;
+ if (name=="time")
+ return QVariant::Time;
+ if (name=="bytearray")
+ return QVariant::ByteArray;
+ if (name=="longlong")
+ return QVariant::LongLong;
+//todo more types
+ showError(QString("Invalid type '%1'").arg(name_));
+ return QVariant::Invalid;
+}
+
+// casts string to QVariant
+bool castStringToQVariant( const QString& string, const QCString& type, QVariant& result )
+{
+ if (string.lower()=="<null>") {
+ result = QVariant();
+ return true;
+ }
+ if (string=="\"\"") {
+ result = QString("");
+ return true;
+ }
+ const QVariant::Type vtype = typeNameToQVariantType( type );
+ bool ok;
+ result = KexiDB::stringToVariant( string, vtype, ok );
+ return ok;
+}
+
+// returns a number parsed from argument; if argument is i or i++, variableI is used
+// 'ok' is set to false on failure
+static int getNumber(const QString& argument, bool& ok)
+{
+ int result;
+ ok = true;
+ if (argument=="i" || argument=="i++") {
+ result = variableI;
+ if (argument=="i++")
+ variableI++;
+ }
+ else {
+ result = argument.toInt(&ok);
+ if (!ok) {
+ showError(QString("Invalid value '%1'").arg(argument));
+ return -1;
+ }
+ }
+ return result;
+}
+
+//---------------------------------------
+
+AlterTableTester::AlterTableTester()
+ : QObject()
+ , m_finishedCopying(false)
+{
+ //copy the db file to a temp file
+ qInitNetworkProtocols();
+ QPtrList<QNetworkOperation> list = m_copyOperator.copy(
+ "file://" + QDir::current().path() + "/" + origDbFilename,
+ "file://" + QDir::current().path() + "/" + dbFilename, false, false );
+ connect(&m_copyOperator, SIGNAL(finished(QNetworkOperation*)),
+ this, SLOT(slotFinishedCopying(QNetworkOperation*)));
+}
+
+AlterTableTester::~AlterTableTester()
+{
+ QFile(dbFilename).remove();
+}
+
+void AlterTableTester::slotFinishedCopying(QNetworkOperation* oper)
+{
+ if (oper->operation()==QNetworkProtocol::OpPut)
+ m_finishedCopying = true;
+}
+
+bool AlterTableTester::changeFieldProperty(KexiTableDesignerInterface* designerIface)
+{
+ if (!checkItemsNumber(5))
+ return false;
+ QVariant newValue;
+ QCString propertyName( testFileLine[2].latin1() );
+ QCString propertyType( testFileLine[3].latin1() );
+ QString propertyValueString(testFileLine[4]);
+ if (propertyName=="type")
+ newValue = (int)KexiDB::Field::typeForString(testFileLine[4]);
+ else {
+ if (!castStringToQVariant(propertyValueString, propertyType, newValue)) {
+ showError( QString("Could not set property '%1' value '%2' of type '%3'")
+ .arg(propertyName).arg(propertyValueString).arg(propertyType) );
+ return false;
+ }
+ }
+ bool ok;
+ int row = getNumber(testFileLine[1], ok)-1;
+ if (!ok)
+ return false;
+ designerIface->changeFieldPropertyForRow( row, propertyName, newValue, 0, true );
+ if (propertyName=="type") {
+ //clean subtype name, e.g. from "longText" to "LongText", because dropdown list is case-sensitive
+ QString realSubTypeName;
+ if (KexiDB::Field::BLOB == KexiDB::Field::typeForString(testFileLine[4]))
+//! @todo hardcoded!
+ realSubTypeName = "image";
+ else
+ realSubTypeName = KexiDB::Field::typeString( KexiDB::Field::typeForString(testFileLine[4]) );
+ designerIface->changeFieldPropertyForRow( row, "subType", realSubTypeName, 0, true );
+ }
+ return true;
+}
+
+//helper
+bool AlterTableTester::getSchemaDump(KexiDialogBase* dlg, QString& schemaDebugString)
+{
+ KexiTableDesignerInterface* designerIface
+ = dynamic_cast<KexiTableDesignerInterface*>( dlg->selectedView() );
+ if (!designerIface)
+ return false;
+
+ // Get the result
+ tristate result;
+ schemaDebugString = designerIface->debugStringForCurrentTableSchema(result);
+ if (true!=result) {
+ showError( QString("Loading modified schema failed. Result: %1")
+ .arg(~result ? "cancelled" : "false") );
+ return false;
+ }
+ schemaDebugString.remove(QRegExp(",$")); //no need to have "," at the end of lines
+ return true;
+}
+
+bool AlterTableTester::showSchema(KexiDialogBase* dlg, bool copyToClipboard)
+{
+ QString schemaDebugString;
+ if (!getSchemaDump(dlg, schemaDebugString))
+ return false;
+ if (copyToClipboard)
+ QApplication::clipboard()->setText( schemaDebugString );
+ else
+ kdDebug() << QString("Schema for '%1' table:\n").arg(dlg->partItem()->name())
+ + schemaDebugString + "\nendSchema" << endl;
+ return true;
+}
+
+bool AlterTableTester::checkInternal(KexiDialogBase* dlg,
+ QString& debugString, const QString& endCommand, bool skipColonsAndStripWhiteSpace)
+{
+ Q_UNUSED(dlg);
+ QTextStream resultStream(&debugString, IO_ReadOnly);
+ // Load expected result, compare
+ QString expectedLine, resultLine;
+ while (true) {
+ const bool testFileStreamAtEnd = testFileStream.atEnd();
+ if (!testFileStreamAtEnd) {
+ testLineNumber++;
+ expectedLine = testFileStream.readLine();
+ if (skipColonsAndStripWhiteSpace) {
+ expectedLine = expectedLine.stripWhiteSpace();
+ expectedLine.remove(QRegExp(",$")); //no need to have "," at the end of lines
+ }
+ }
+ if (testFileStreamAtEnd || endCommand==expectedLine.stripWhiteSpace()) {
+ if (!resultStream.atEnd()) {
+ showError( "Test file ends unexpectedly." );
+ return false;
+ }
+ break;
+ }
+ //test line loaded, load result
+ if (resultStream.atEnd()) {
+ showError( QString("Result ends unexpectedly. There is at least one additinal test line: '")
+ + expectedLine +"'" );
+ return false;
+ }
+ resultLine = resultStream.readLine();
+ if (skipColonsAndStripWhiteSpace) {
+ resultLine = resultLine.stripWhiteSpace();
+ resultLine.remove(QRegExp(",$")); //no need to have "," at the end of lines
+ }
+ if (resultLine!=expectedLine) {
+ showError(
+ QString("Result differs from the expected:\nExpected: ")
+ +expectedLine+"\n????????: "+resultLine+"\n");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool AlterTableTester::checkSchema(KexiDialogBase* dlg)
+{
+ QString schemaDebugString;
+ if (!getSchemaDump(dlg, schemaDebugString))
+ return false;
+ bool result = checkInternal(dlg, schemaDebugString, "endSchema", true /*skipColonsAndStripWhiteSpace*/);
+ kdDebug() << QString("Schema check for table '%1': %2").arg(dlg->partItem()->name())
+ .arg(result ? "OK" : "Failed") << endl;
+ return result;
+}
+
+bool AlterTableTester::getActionsDump(KexiDialogBase* dlg, QString& actionsDebugString)
+{
+ KexiTableDesignerInterface* designerIface
+ = dynamic_cast<KexiTableDesignerInterface*>( dlg->selectedView() );
+ if (!designerIface)
+ return false;
+ tristate result = designerIface->simulateAlterTableExecution(&actionsDebugString);
+ if (true!=result) {
+ showError( QString("Computing simplified actions for table '%1' failed.").arg(dlg->partItem()->name()) );
+ return false;
+ }
+ return true;
+}
+
+bool AlterTableTester::showActions(KexiDialogBase* dlg, bool copyToClipboard)
+{
+ QString actionsDebugString;
+ if (!getActionsDump(dlg, actionsDebugString))
+ return false;
+ if (copyToClipboard)
+ QApplication::clipboard()->setText( actionsDebugString );
+ else
+ kdDebug() << QString("Simplified actions for altering table '%1':\n").arg(dlg->partItem()->name())
+ + actionsDebugString+"\n" << endl;
+ return true;
+}
+
+bool AlterTableTester::checkActions(KexiDialogBase* dlg)
+{
+ QString actionsDebugString;
+ if (!getActionsDump(dlg, actionsDebugString))
+ return false;
+ bool result = checkInternal(dlg, actionsDebugString, "endActions", true /*skipColonsAndStripWhiteSpace*/);
+ kdDebug() << QString("Actions check for table '%1': %2").arg(dlg->partItem()->name())
+ .arg(result ? "OK" : "Failed") << endl;
+ return result;
+}
+
+bool AlterTableTester::saveTableDesign(KexiDialogBase* dlg)
+{
+ KexiTableDesignerInterface* designerIface
+ = dynamic_cast<KexiTableDesignerInterface*>( dlg->selectedView() );
+ if (!designerIface)
+ return false;
+ tristate result = designerIface->executeRealAlterTable();
+ if (true!=result) {
+ showError( QString("Saving design of table '%1' failed.").arg(dlg->partItem()->name()) );
+ return false;
+ }
+ return true;
+}
+
+bool AlterTableTester::getTableDataDump(KexiDialogBase* dlg, QString& dataString)
+{
+ KexiTableDesignerInterface* designerIface
+ = dynamic_cast<KexiTableDesignerInterface*>( dlg->selectedView() );
+ if (!designerIface)
+ return false;
+
+ QMap<QString,QString> args;
+ QTextStream ts( &dataString, IO_WriteOnly );
+ args["textStream"] = KexiUtils::ptrToString<QTextStream>( &ts );
+ args["destinationType"]="file";
+ args["delimiter"]="\t";
+ args["textQuote"]="\"";
+ args["itemId"] = QString::number(
+ prj->dbConnection()->tableSchema( dlg->partItem()->name() )->id() );
+ if (!KexiInternalPart::executeCommand("csv_importexport", win, "KexiCSVExport", &args)) {
+ showError( "Error exporting table contents." );
+ return false;
+ }
+ return true;
+}
+
+bool AlterTableTester::showTableData(KexiDialogBase* dlg, bool copyToClipboard)
+{
+ QString dataString;
+ if (!getTableDataDump(dlg, dataString))
+ return false;
+ if (copyToClipboard)
+ QApplication::clipboard()->setText( dataString );
+ else
+ kdDebug() << QString("Contents of table '%1':\n").arg(dlg->partItem()->name())+dataString+"\n" << endl;
+ return true;
+}
+
+bool AlterTableTester::checkTableData(KexiDialogBase* dlg)
+{
+ QString dataString;
+ if (!getTableDataDump(dlg, dataString))
+ return false;
+ bool result = checkInternal(dlg, dataString, "endTableData", false /*!skipColonsAndStripWhiteSpace*/);
+ kdDebug() << QString("Table '%1' contents: %2").arg(dlg->partItem()->name())
+ .arg(result ? "OK" : "Failed") << endl;
+ return result;
+}
+
+bool AlterTableTester::closeWindow(KexiDialogBase* dlg)
+{
+ if (!dlg)
+ return true;
+ QString name = dlg->partItem()->name();
+ tristate result = true == win->closeDialog(dlg, true/*layoutTaskBar*/, true/*doNotSaveChanges*/);
+ kdDebug() << QString("Closing window for table '%1': %2").arg(name)
+ .arg(result==true ? "OK" : (result==false ? "Failed" : "Cancelled")) << endl;
+ return result == true;
+}
+
+//! Processes test file
+tristate AlterTableTester::run(bool &closeAppRequested)
+{
+ closeAppRequested = false;
+ while (!m_finishedCopying)
+ qApp->processEvents(300);
+
+ kdDebug() << "Database copied to temporary: " << dbFilename << endl;
+
+ if (!checkItemsNumber(2))
+ return false;
+
+ tristate res = win->openProject( dbFilename, 0 );
+ if (true != res)
+ return res;
+ prj = win->project();
+
+ //open table in design mode
+ res = readLineFromTestFile("designTable");
+ if (true != res)
+ return ~res;
+
+ QString tableName(testFileLine[1]);
+ KexiPart::Item *item = prj->itemForMimeType("kexi/table", tableName);
+ if (!item) {
+ showError(QString("No such table '%1'").arg(tableName));
+ return false;
+ }
+ bool openingCancelled;
+ KexiDialogBase* dlg = win->openObject(item, Kexi::DesignViewMode, openingCancelled);
+ if (!dlg) {
+ showError(QString("Could not open table '%1'").arg(item->name()));
+ return false;
+ }
+ KexiTableDesignerInterface* designerIface
+ = dynamic_cast<KexiTableDesignerInterface*>( dlg->selectedView() );
+ if (!designerIface)
+ return false;
+
+ //dramatic speedup: temporary hide the window and propeditor
+ QWidget * propeditor
+ = KexiUtils::findFirstChild<QWidget>(qApp->mainWidget(), "KexiPropertyEditorView");
+ if (propeditor)
+ propeditor->hide();
+ dlg->hide();
+
+ bool designTable = true;
+ while (!testFileStream.atEnd()) {
+ res = readLineFromTestFile();
+ if (true != res)
+ return ~res;
+ QString command( testFileLine[0] );
+ if (designTable) {
+ //subcommands available within "designTable" commands
+ if (command=="endDesign") {
+ if (!checkItemsNumber(1))
+ return false;
+ //end of the design session: unhide the window and propeditor
+ dlg->show();
+ if (propeditor)
+ propeditor->show();
+ designTable = false;
+ continue;
+ }
+ else if (command=="removeField") {
+ if (!checkItemsNumber(2))
+ return false;
+ bool ok;
+ int row = getNumber(testFileLine[1], ok)-1;
+ if (!ok)
+ return false;
+ designerIface->deleteRow( row, true );
+ continue;
+ }
+ else if (command=="insertField") {
+ if (!checkItemsNumber(3))
+ return false;
+ bool ok;
+ int row = getNumber(testFileLine[1], ok)-1;
+ if (!ok)
+ return false;
+ designerIface->insertField( row, testFileLine[2], true );
+ continue;
+ }
+ else if (command=="insertEmptyRow") {
+ if (!checkItemsNumber(2))
+ return false;
+ bool ok;
+ int row = getNumber(testFileLine[1], ok)-1;
+ if (!ok)
+ return false;
+ designerIface->insertEmptyRow( row, true );
+ continue;
+ }
+ else if (command=="changeFieldProperty") {
+ if (!checkItemsNumber(5) || !changeFieldProperty(designerIface))
+ return false;
+ continue;
+ }
+ else if (command.startsWith("i=")) {
+ bool ok;
+ variableI = command.mid(2).toInt(&ok);
+ if (!ok) {
+ showError(QString("Invalid variable initialization '%1'").arg(command));
+ return false;
+ }
+ continue;
+ }
+ else if (command.startsWith("i++")) {
+ variableI++;
+ continue;
+ }
+ }
+ else {
+ //top-level commands available outside of "designTable"
+ if (command=="showSchema") {
+ if (!checkItemsNumber(1, 2) || !showSchema(dlg, testFileLine[1]=="clipboard"))
+ return false;
+ continue;
+ }
+ else if (command=="checkSchema") {
+ if (!checkItemsNumber(1) || !checkSchema(dlg))
+ return false;
+ continue;
+ }
+ else if (command=="showActions") {
+ if (!checkItemsNumber(1, 2) || !showActions(dlg, testFileLine[1]=="clipboard"))
+ return false;
+ continue;
+ }
+ else if (command=="checkActions") {
+ if (!checkItemsNumber(1) || !checkActions(dlg))
+ return false;
+ continue;
+ }
+ else if (command=="saveTableDesign") {
+ if (!checkItemsNumber(1) || !saveTableDesign(dlg))
+ return false;
+ continue;
+ }
+ else if (command=="showTableData") {
+ if (!checkItemsNumber(1, 2) || !showTableData(dlg, testFileLine[1]=="clipboard"))
+ return false;
+ continue;
+ }
+ else if (command=="checkTableData") {
+ if (!checkItemsNumber(1) || !checkTableData(dlg))
+ return false;
+ continue;
+ }
+ }
+ //common commands
+ if (command=="stop") {
+ if (!checkItemsNumber(1))
+ return false;
+ kdDebug() << QString("Test STOPPED at line %1.").arg(testLineNumber) << endl;
+ break;
+ }
+ else if (command=="closeWindow") {
+ if (!checkItemsNumber(1) || !closeWindow(dlg))
+ return false;
+ else
+ dlg = 0;
+ continue;
+ }
+ else if (command=="quit") {
+ if (!checkItemsNumber(1) || !closeWindow(dlg))
+ return false;
+ closeAppRequested = true;
+ kdDebug() << QString("Quitting the application...") << endl;
+ break;
+ }
+ else {
+ showError( QString("No such command '%1'").arg(command) );
+ return false;
+ }
+ }
+ return true;
+}
+
+//---------------------------------------
+
+int quit(int result)
+{
+ testFile.close();
+ delete qApp;
+ if (newArgv)
+ delete [] newArgv;
+ return result;
+}
+
+int main(int argc, char *argv[])
+{
+ // args: <.altertable test filename>
+ if (argc < 2) {
+ kdWarning() << "Please specify test filename.\nOptions: \n"
+ "\t-close - closes the main window when test finishes" << endl;
+ return quit(1);
+ }
+
+ // options:
+ const bool closeOnFinish = argc > 2 && 0==qstrcmp(argv[1], "-close");
+
+ // open test file
+ testFilename = argv[argc-1];
+ testFile.setName(testFilename);
+ if (!testFile.open(IO_ReadOnly)) {
+ kdWarning() << QString("Opening test file %1 failed.").arg(testFilename) << endl;
+ return quit(1);
+ }
+ //load db name
+ testFileStream.setDevice( &testFile );
+ tristate res = readLineFromTestFile("openDatabase");
+ if (true != res)
+ return quit( ~res ? 0 : 1 );
+ origDbFilename = testFileLine[1];
+ dbFilename = origDbFilename + ".tmp";
+
+ newArgc = 2;
+ newArgv = new char*[newArgc];
+ newArgv[0] = qstrdup(argv[0]);
+ newArgv[1] = qstrdup( "--skip-startup-dialog" );
+
+ KAboutData* aboutdata = Kexi::createAboutData();
+ aboutdata->setProgramName( "Kexi Alter Table Test" );
+ int result = KexiMainWindowImpl::create(newArgc, newArgv, aboutdata);
+ if (!qApp)
+ return quit(result);
+
+ win = KexiMainWindowImpl::self();
+ AlterTableTester tester;
+ //QObject::connect(win, SIGNAL(projectOpened()), &tester, SLOT(run()));
+
+ bool closeAppRequested;
+ res = tester.run(closeAppRequested);
+ if (true != res) {
+ if (false == res)
+ kdWarning() << QString("Running test for file '%1' failed.").arg(testFilename) << endl;
+ return quit(res==false ? 1 : 0);
+ }
+ kdDebug() << QString("Tests from file '%1': OK").arg(testFilename) << endl;
+ result = (closeOnFinish || closeAppRequested) ? 0 : qApp->exec();
+ quit(result);
+ return result;
+}
+
+#include "altertable.moc"
diff --git a/kexi/tests/altertable/altertable.h b/kexi/tests/altertable/altertable.h
new file mode 100644
index 000000000..455c2bf5c
--- /dev/null
+++ b/kexi/tests/altertable/altertable.h
@@ -0,0 +1,63 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef AlterTableTester_H
+#define AlterTableTester_H
+
+#include <qurloperator.h>
+#include <qnetwork.h>
+#include <qnetworkprotocol.h>
+#include <kexiutils/tristate.h>
+
+class KexiTableDesignerInterface;
+class KexiDialogBase;
+
+class AlterTableTester : public QObject
+{
+ Q_OBJECT
+ public:
+ AlterTableTester();
+ ~AlterTableTester();
+
+ tristate run(bool &closeAppRequested);
+
+ protected slots:
+ void slotFinishedCopying(QNetworkOperation*);
+
+ private:
+ bool changeFieldProperty(KexiTableDesignerInterface* designerIface);
+ bool getSchemaDump(KexiDialogBase* dlg, QString& schemaDebugString);
+ bool showSchema(KexiDialogBase* dlg, bool copyToClipboard);
+ bool checkSchema(KexiDialogBase* dlg);
+ bool getActionsDump(KexiDialogBase* dlg, QString& actionsDebugString);
+ bool showActions(KexiDialogBase* dlg, bool copyToClipboard);
+ bool checkActions(KexiDialogBase* dlg);
+ bool checkInternal(KexiDialogBase* dlg, QString& debugString,
+ const QString& endCommand, bool skipColons);
+ bool saveTableDesign(KexiDialogBase* dlg);
+ bool getTableDataDump(KexiDialogBase* dlg, QString& dataString);
+ bool showTableData(KexiDialogBase* dlg, bool copyToClipboard);
+ bool checkTableData(KexiDialogBase* dlg);
+ bool closeWindow(KexiDialogBase* dlg);
+
+ QUrlOperator m_copyOperator;
+ bool m_finishedCopying;
+};
+
+#endif
diff --git a/kexi/tests/altertable/defaultvalues.altertable b/kexi/tests/altertable/defaultvalues.altertable
new file mode 100644
index 000000000..1ed274404
--- /dev/null
+++ b/kexi/tests/altertable/defaultvalues.altertable
@@ -0,0 +1,129 @@
+openDatabase 1.kexi
+
+/*
+ This test checks:
+ - creating table fields of all possible types with specific default values
+ - adding new fields to the table with preserving the original content
+
+ All the existing columns are removed
+
+ Used tables: cars
+*/
+
+designTable cars #initially there are 3rows
+ removeField 2
+ removeField 2
+ i=2
+ insertField i textField
+ changeFieldProperty i type string text
+ changeFieldProperty i defaultValue string abc
+ i++
+ insertField i longTextField
+ changeFieldProperty i type string longText
+ changeFieldProperty i defaultValue string def
+ i++
+ insertField i byteField
+ changeFieldProperty i type string byte
+ changeFieldProperty i defaultValue int 11
+ i++
+ insertField i shortIntField
+ changeFieldProperty i type string shortInteger
+ changeFieldProperty i defaultValue int 22
+ i++
+ insertField i intField
+ changeFieldProperty i type string integer
+ changeFieldProperty i defaultValue int 333
+ i++
+ insertField i bigIntField
+ changeFieldProperty i type string bigInteger
+ changeFieldProperty i defaultValue longlong 1234567891011
+ i++
+ insertField i booleanField
+ changeFieldProperty i type string boolean
+ changeFieldProperty i defaultValue bool true
+ i++
+ insertField i dateField
+ changeFieldProperty i type string date
+ changeFieldProperty i defaultValue date 2006-08-09
+ i++
+ insertField i dateTimeField
+ changeFieldProperty i type string dateTime
+ changeFieldProperty i defaultValue dateTime 2006-08-09T10:36:01
+ i++
+ insertField i timeField
+ changeFieldProperty i type string time
+ changeFieldProperty i defaultValue time 10:36:02
+ i++
+ insertField i floatField
+ changeFieldProperty i type string float
+ changeFieldProperty i defaultValue float 1.98
+ i++
+ insertField i doubleField
+ changeFieldProperty i type string double
+ changeFieldProperty i defaultValue double 3.1415926
+ i++
+ insertField i blobField
+ changeFieldProperty i type string blob
+ changeFieldProperty i defaultValue byteArray fdfeff
+endDesign
+
+ showSchema
+
+ checkSchema
+ id UNSIGNED Integer AUTOINC UNIQUE PKEY NOTNULL NOTEMPTY
+ textfield Text(200) DEFAULT=[QString]abc,
+ longtextfield LongText DEFAULT=[QString]def,
+ bytefield Byte DEFAULT=[int]11,
+ shortintfield ShortInteger DEFAULT=[int]22,
+ intfield Integer DEFAULT=[int]333,
+ bigintfield BigInteger DEFAULT=[Q_LLONG]1234567891011,
+ booleanfield Boolean NOTNULL DEFAULT=[bool]true,
+ datefield Date DEFAULT=[QDate]2006-08-09,
+ datetimefield DateTime DEFAULT=[QDateTime]2006-08-09T10:36:01,
+ timefield Time DEFAULT=[QTime]10:36:02,
+ floatfield Float DEFAULT=[double]1.98,
+ doublefield Double DEFAULT=[double]3.1415926,
+ blobfield BLOB DEFAULT=[QByteArray]FDFEFF
+ endSchema
+
+# showActions clipboard
+
+ checkActions
+Remove table field "owner"
+Remove table field "model"
+Insert table field "textfield" at position 1 (textfield Text(200) DEFAULT=[QString]abc)
+Insert table field "longtextfield" at position 2 (longtextfield LongText DEFAULT=[QString]def)
+Insert table field "bytefield" at position 3 (bytefield Byte DEFAULT=[int]11)
+Insert table field "shortintfield" at position 4 (shortintfield ShortInteger DEFAULT=[int]22)
+Insert table field "intfield" at position 5 (intfield Integer DEFAULT=[int]333)
+Insert table field "bigintfield" at position 6 (bigintfield BigInteger DEFAULT=[Q_LLONG]1234567891011)
+Insert table field "booleanfield" at position 7 (booleanfield Boolean NOTNULL DEFAULT=[bool]true)
+Insert table field "datefield" at position 8 (datefield Date DEFAULT=[QDate]2006-08-09)
+Insert table field "datetimefield" at position 9 (datetimefield DateTime DEFAULT=[QDateTime]2006-08-09T10:36:01)
+Insert table field "timefield" at position 10 (timefield Time DEFAULT=[QTime]10:36:02)
+Insert table field "floatfield" at position 11 (floatfield Float DEFAULT=[double]1.98)
+Insert table field "doublefield" at position 12 (doublefield Double DEFAULT=[double]3.1415926)
+Insert table field "blobfield" at position 13 (blobfield BLOB DEFAULT=[QByteArray]FDFEFF)
+ endActions
+
+saveTableDesign #executes Alter Table
+
+#closeWindow
+
+#stop
+#quit
+
+# copyTableDataToClipboard
+ showTableData clipboard
+# stop
+
+ checkTableData
+"ID" "textField" "longTextField" "byteField" "shortIntField" "intField" "bigIntField" "booleanField" "dateField" "dateTimeField" "timeField" "floatField" "doubleField" "blobField"
+1 "abc" "def" 11 22 333 1234567891011 true 2006-08-09 2006-08-09 10:36:01 10:36:02 1.98 3.1415926 "FDFEFF"
+2 "abc" "def" 11 22 333 1234567891011 true 2006-08-09 2006-08-09 10:36:01 10:36:02 1.98 3.1415926 "FDFEFF"
+3 "abc" "def" 11 22 333 1234567891011 true 2006-08-09 2006-08-09 10:36:01 10:36:02 1.98 3.1415926 "FDFEFF"
+5 "abc" "def" 11 22 333 1234567891011 true 2006-08-09 2006-08-09 10:36:01 10:36:02 1.98 3.1415926 "FDFEFF"
+6 "abc" "def" 11 22 333 1234567891011 true 2006-08-09 2006-08-09 10:36:01 10:36:02 1.98 3.1415926 "FDFEFF"
+ endTableData
+
+
diff --git a/kexi/tests/gui/finddialog/finddialog.pro b/kexi/tests/gui/finddialog/finddialog.pro
new file mode 100644
index 000000000..f76b53725
--- /dev/null
+++ b/kexi/tests/gui/finddialog/finddialog.pro
@@ -0,0 +1,18 @@
+TEMPLATE = app
+
+include( $(KEXI)/common.pro )
+
+CONFIG += qt warn_on release
+DEPENDPATH = ../../include
+
+system( bash kmoc )
+
+TARGET = finddialogtest
+DESTDIR=.
+
+system( bash kmoc )
+system( bash kdcopidl )
+
+SOURCES = kexifinddialog.cpp main.cpp
+
+FORMS = kexifinddialogbase.ui
diff --git a/kexi/tests/gui/finddialog/kexifinddialog.cpp b/kexi/tests/gui/finddialog/kexifinddialog.cpp
new file mode 100644
index 000000000..64f54d51c
--- /dev/null
+++ b/kexi/tests/gui/finddialog/kexifinddialog.cpp
@@ -0,0 +1,67 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexifinddialog.h"
+
+#include <kstdguiitem.h>
+#include <kpushbutton.h>
+#include <kcombobox.h>
+#include <klocale.h>
+
+#include <qcheckbox.h>
+#include <qlabel.h>
+
+KexiFindDialog::KexiFindDialog( bool replaceMode, QWidget* parent, const char* name, bool modal )
+ : KexiFindDialogBase(parent, name, modal)
+ , m_replaceMode(true)
+{
+ m_btnFind->setIconSet(KStdGuiItem::find().iconSet());
+ m_btnClose->setIconSet(KStdGuiItem::close().iconSet());
+ setReplaceMode(replaceMode);
+ m_lookIn->insertItem(i18n("(All columns)"));
+}
+
+void KexiFindDialog::setReplaceMode(bool set)
+{
+ if (m_replaceMode == set)
+ return;
+ m_replaceMode = set;
+ if (m_replaceMode) {
+ m_promptOnReplace->show();
+ m_replaceLbl->show();
+ m_textToReplace->show();
+ m_btnReplace->show();
+ m_btnReplaceAll->show();
+ }
+ else {
+ m_promptOnReplace->hide();
+ m_replaceLbl->hide();
+ m_textToReplace->hide();
+ m_btnReplace->hide();
+ m_btnReplaceAll->hide();
+ resize(width(),height()-30);
+ }
+ updateGeometry();
+}
+
+KexiFindDialog::~KexiFindDialog()
+{
+}
+
+#include "kexifinddialog.moc"
diff --git a/kexi/tests/gui/finddialog/kexifinddialog.h b/kexi/tests/gui/finddialog/kexifinddialog.h
new file mode 100644
index 000000000..9025d1523
--- /dev/null
+++ b/kexi/tests/gui/finddialog/kexifinddialog.h
@@ -0,0 +1,66 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFINDDIALOG_H
+#define KEXIFINDDIALOG_H
+
+#include "kexifinddialogbase.h"
+
+/*! @brief A Kexi-specific "Find text" dialog.
+
+ Also used for replace.
+*/
+class KexiFindDialog : public KexiFindDialogBase
+{
+ Q_OBJECT
+ public:
+ KexiFindDialog( bool replaceMode, QWidget* parent = 0, const char* name = 0, bool modal = FALSE );
+ virtual ~KexiFindDialog();
+
+#if 0
+TODO TODO TODO TODO TODO TODO
+ /*! Sets \a columnNames list for 'look in column' combo box.
+ "(All columns)" item is also prepended. */
+ void setLookInColumnList(const QStringList& columnNames);
+
+ /*! \return a list for 'look in column' combo box.
+ "(All columns)" item is also prepended. */
+ QStringList* lookInColumnList() const;
+
+ /*! \return column name selected in 'look in column' combo box.
+ If "(All columns)" item is selected, "*" is returned. */
+ QString lookInColumn() const;
+
+ /*! Selects \a columnName to be selected 'look in column'.
+ By default "(All columns)" item is selected. To select this item, pass "*". */
+ void setLookInColumn(const QString& columnName);
+
+#endif
+
+ public slots:
+ /*! Sets or clears replace mode.
+ For replace mode 'prompt or replace' option is visible.
+ */
+ void setReplaceMode(bool set);
+
+ protected:
+ bool m_replaceMode : 1;
+};
+
+#endif
diff --git a/kexi/tests/gui/finddialog/kexifinddialogbase.ui b/kexi/tests/gui/finddialog/kexifinddialogbase.ui
new file mode 100644
index 000000000..f4684bffa
--- /dev/null
+++ b/kexi/tests/gui/finddialog/kexifinddialogbase.ui
@@ -0,0 +1,326 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiFindDialogBase</class>
+<widget class="QDialog">
+ <property name="name">
+ <cstring>KexiFindDialogBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>476</width>
+ <height>224</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Find Text</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="KHistoryCombo" row="1" column="1" rowspan="1" colspan="3">
+ <property name="name">
+ <cstring>m_textToReplace</cstring>
+ </property>
+ <property name="insertionPolicy">
+ <enum>AtTop</enum>
+ </property>
+ <property name="autoCompletion">
+ <bool>true</bool>
+ </property>
+ <property name="duplicatesEnabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="4" column="2">
+ <property name="name">
+ <cstring>m_caseSensitive</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="text">
+ <string>C&amp;ase sensitive</string>
+ </property>
+ </widget>
+ <widget class="KHistoryCombo" row="0" column="1" rowspan="1" colspan="3">
+ <property name="name">
+ <cstring>m_textToFind</cstring>
+ </property>
+ <property name="insertionPolicy">
+ <enum>AtTop</enum>
+ </property>
+ <property name="autoCompletion">
+ <bool>true</bool>
+ </property>
+ <property name="duplicatesEnabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="4" column="1">
+ <item>
+ <property name="text">
+ <string>Any Part of Field</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Whole Field</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Start of Field</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>m_match</cstring>
+ </property>
+ <property name="insertionPolicy">
+ <enum>NoInsertion</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Fi&amp;nd:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_textToFind</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>m_replaceLbl</cstring>
+ </property>
+ <property name="text">
+ <string>Re&amp;place with:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_textToReplace</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="3" column="0">
+ <property name="name">
+ <cstring>textLabel2_2_2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Search:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_search</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="4" column="0">
+ <property name="name">
+ <cstring>textLabel2_2_3</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Match:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_match</cstring>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="3" column="1">
+ <item>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Down</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>All Rows</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>m_search</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="insertionPolicy">
+ <enum>NoInsertion</enum>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="2" column="1">
+ <property name="name">
+ <cstring>m_lookIn</cstring>
+ </property>
+ <property name="insertionPolicy">
+ <enum>NoInsertion</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="0">
+ <property name="name">
+ <cstring>textLabel2_2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Look in column:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_lookIn</cstring>
+ </property>
+ </widget>
+ <spacer row="4" column="3">
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QCheckBox" row="5" column="2">
+ <property name="name">
+ <cstring>m_wholeWords</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="text">
+ <string>&amp;Whole words only</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="6" column="2">
+ <property name="name">
+ <cstring>m_promptOnReplace</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>WheelFocus</enum>
+ </property>
+ <property name="text">
+ <string>Prompt on replace</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <spacer row="7" column="2">
+ <property name="name">
+ <cstring>spacer3</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLayoutWidget" row="0" column="4" rowspan="8" colspan="1">
+ <property name="name">
+ <cstring>layout2</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>m_btnFind</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Find Next</string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>m_btnClose</cstring>
+ </property>
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>m_btnReplace</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Replace</string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>m_btnReplaceAll</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Replace All</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer8</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </vbox>
+ </widget>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<connections>
+ <connection>
+ <sender>m_btnClose</sender>
+ <signal>clicked()</signal>
+ <receiver>KexiFindDialogBase</receiver>
+ <slot>reject()</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>m_textToFind</tabstop>
+ <tabstop>m_textToReplace</tabstop>
+ <tabstop>m_lookIn</tabstop>
+ <tabstop>m_search</tabstop>
+ <tabstop>m_match</tabstop>
+ <tabstop>m_caseSensitive</tabstop>
+ <tabstop>m_wholeWords</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>kcombobox.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/tests/gui/finddialog/main.cpp b/kexi/tests/gui/finddialog/main.cpp
new file mode 100644
index 000000000..7da98bf48
--- /dev/null
+++ b/kexi/tests/gui/finddialog/main.cpp
@@ -0,0 +1,36 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+
+#include "kexifinddialog.h"
+
+int main( int argc, char ** argv )
+{
+ KAboutData aboutData( "test", I18N_NOOP("KFind"), "0", "", KAboutData::License_LGPL );
+ KCmdLineArgs::init( argc, argv, &aboutData );
+ KApplication app;
+
+ KexiFindDialog dlg(true, 0, "dialog");
+
+ return dlg.exec();
+}
diff --git a/kexi/tests/newapi/Makefile.am b/kexi/tests/newapi/Makefile.am
new file mode 100644
index 000000000..3694806dc
--- /dev/null
+++ b/kexi/tests/newapi/Makefile.am
@@ -0,0 +1,32 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+# unused: kexidbmysqlcursor kexidbfirebirdcursor
+
+noinst_PROGRAMS = kexidbtest
+
+# unused: kexidbmysqlcursor
+
+INCLUDES = -I$(top_srcdir)/kexi \
+ -I$(top_srcdir)/kexi/widget \
+ -I$(top_srcdir)/kexi/core \
+ $(all_includes)
+
+SUBDIRS = .
+
+METASOURCES = AUTO
+
+kexidbtest_SOURCES = main.cpp
+kexidbtest_LDADD = $(LIB_QT) $(LIB_KDECORE) $(top_builddir)/kexi/kexidb/libkexidb.la \
+ ../../kexidb/parser/libkexidbparser.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la
+kexidbtest_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+#kexidbmysqlcursor_SOURCES = mysqlcursor.cpp
+#kexidbmysqlcursor_LDADD = $(LIB_QT) $(LIB_KDECORE) $(top_builddir)/kexi/kexidb/libkexidb.la \
+# ../../kexidb/parser/libkexidbparser.la
+#kexidbmysqlcursor_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+#kexidbfirebirdcursor_SOURCES = firebirdcursor.cpp
+#kexidbfirebirdcursor_LDADD = $(LIB_QT) $(LIB_KDECORE) $(top_builddir)/kexi/kexidb/libkexidb.la
+#kexidbfirebirdcursor_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
diff --git a/kexi/tests/newapi/README b/kexi/tests/newapi/README
new file mode 100644
index 000000000..a9be56210
--- /dev/null
+++ b/kexi/tests/newapi/README
@@ -0,0 +1,59 @@
+1. kexidbtest
+-------------
+
+This is a set of tests for the new, common KexiDB API.
+Every test is driver-independent.
+
+Usage: run 'kexidbtest --help' for usage details.
+
+
+2. sqltest
+----------
+
+Script for easier executing 'parser' subtest within kexidbtest.
+Usage: run './sqltest' without arguments for usage details.
+
+There is also sqltest_int script accepting interactive mode.
+Usage: run './sqltest_int' without arguments for usage details.
+
+
+3. Important documents
+----------------------
+-Kexi API Documentation in html
+http://koffice.org/developer/apidocs/kexi/html
+
+-KexiDB Drivers section of KexiWiki Web Page
+http://www.kexi-project.org/wiki/wikiview/index.php?KexiDBDrivers
+
+
+
+4. Information for KexiDB drivers developers
+--------------------------------------------
+
+While you're developing new driver or improving existing one,
+you may want to test a number of aspects to see if the behaviour
+looks like expected.
+
+Following tests should be passed (the order is from most simple
+test to more complicated):
+
+-dbcreation
+-schema
+-tables
+-cursors
+-tableview
+-parser
+-dr_prop
+
+If the given driver does not pass one of these tests, and you have found:
+- that the problem is at the KexiDB library side (e.g. crash, or improper
+behaviour), or
+- that the problem can be solved by extending KexiDB API, or
+- that the documentation is not correct or not enough detailed, or
+- whatever like that,
+
+..please contact:
+
+KexiDB maintainer
+Jaroslaw Staniek, js @ iidea . pl, irc://irc.freenode.net #kexi
+
diff --git a/kexi/tests/newapi/cursors_test.h b/kexi/tests/newapi/cursors_test.h
new file mode 100644
index 000000000..36bd3dd86
--- /dev/null
+++ b/kexi/tests/newapi/cursors_test.h
@@ -0,0 +1,60 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef CURSORS_TEST_H
+#define CURSORS_TEST_H
+
+int tablesTest();
+
+int cursorsTest()
+{
+ if (!conn->databaseExists( db_name )) {
+ if (tablesTest()!=0)
+ return 1;
+ kdDebug() << "DB created & filled"<< endl;
+ }
+
+ if (!conn->useDatabase( db_name )) {
+ conn->debugError();
+ return 1;
+ }
+
+ KexiDB::Cursor *cursor = conn->executeQuery( "select * from persons", cursor_options );//KexiDB::Cursor::Buffered );
+ kdDebug()<<"executeQuery() = "<<!!cursor<<endl;
+ if (!cursor)
+ return 1;
+
+ kdDebug()<<"Cursor::moveLast() ---------------------" << endl;
+ kdDebug()<<"-- Cursor::moveLast() == " << cursor->moveLast() << endl;
+ cursor->moveLast();
+ kdDebug()<<"Cursor::moveFirst() ---------------------" << endl;
+ kdDebug()<<"-- Cursor::moveFirst() == " << cursor->moveFirst() << endl;
+
+/* kdDebug()<<"Cursor::moveNext() == "<<cursor->moveNext()<<endl;
+ kdDebug()<<"Cursor::moveNext() == "<<cursor->moveNext()<<endl;
+ kdDebug()<<"Cursor::moveNext() == "<<cursor->moveNext()<<endl;
+ kdDebug()<<"Cursor::moveNext() == "<<cursor->moveNext()<<endl;
+ kdDebug()<<"Cursor::eof() == "<<cursor->eof()<<endl;*/
+ conn->deleteCursor(cursor);
+
+ return 0;
+}
+
+#endif
+
diff --git a/kexi/tests/newapi/dbcreation_test.h b/kexi/tests/newapi/dbcreation_test.h
new file mode 100644
index 000000000..741859c17
--- /dev/null
+++ b/kexi/tests/newapi/dbcreation_test.h
@@ -0,0 +1,61 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef DBCREATION_TEST_H
+#define DBCREATION_TEST_H
+
+int dbCreationTest()
+{
+ if (conn->databaseExists( db_name )) {
+ if (!conn->dropDatabase( db_name )) {
+ conn->debugError();
+ return 1;
+ }
+ kdDebug() << "DB '" << db_name << "' dropped"<< endl;
+ }
+ if (!conn->createDatabase( db_name )) {
+ conn->debugError();
+ return 1;
+ }
+ kdDebug() << "DB '" << db_name << "' created"<< endl;
+ if (!conn->useDatabase( db_name )) {
+ conn->debugError();
+ return 1;
+ }
+/* KexiDB::Cursor *cursor = conn->executeQuery( "select * from osoby", KexiDB::Cursor::Buffered );
+ kdDebug()<<"executeQuery() = "<<!!cursor<<endl;
+ if (cursor) {
+ kdDebug()<<"Cursor::moveLast() ---------------------" << endl;
+ kdDebug()<<"-- Cursor::moveLast() == " << cursor->moveLast() << endl;
+ cursor->moveLast();
+ kdDebug()<<"Cursor::moveFirst() ---------------------" << endl;
+ kdDebug()<<"-- Cursor::moveFirst() == " << cursor->moveFirst() << endl;
+*/
+/* kdDebug()<<"Cursor::moveNext() == "<<cursor->moveNext()<<endl;
+ kdDebug()<<"Cursor::moveNext() == "<<cursor->moveNext()<<endl;
+ kdDebug()<<"Cursor::moveNext() == "<<cursor->moveNext()<<endl;
+ kdDebug()<<"Cursor::moveNext() == "<<cursor->moveNext()<<endl;
+ kdDebug()<<"Cursor::eof() == "<<cursor->eof()<<endl;*/
+// conn->deleteCursor(cursor);
+// }
+ return 0;
+}
+
+#endif
+
diff --git a/kexi/tests/newapi/dr_prop_test.h b/kexi/tests/newapi/dr_prop_test.h
new file mode 100644
index 000000000..71e882e3e
--- /dev/null
+++ b/kexi/tests/newapi/dr_prop_test.h
@@ -0,0 +1,41 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef DR_PROP_TEST_H
+#define DR_PROP_TEST_H
+
+int drPropTest()
+{
+ QValueList<QCString> names = driver->propertyNames();
+ kdDebug() << QString("%1 properties found:").arg(names.count()) << endl;
+ for (QValueList<QCString>::ConstIterator it = names.constBegin(); it!=names.constEnd(); ++it) {
+ kdDebug() << " - " << (*it) << ":"
+ << " caption=\"" << driver->propertyCaption(*it) << "\""
+ << " type=" << driver->propertyValue(*it).typeName()
+ << " value=\""<<driver->propertyValue(*it).toString()<<"\"" << endl;
+ }
+// QVariant propertyValue( const QCString& propName ) const;
+
+// QVariant propertyCaption( const QCString& propName ) const;
+
+ return 0;
+}
+
+#endif
+
diff --git a/kexi/tests/newapi/main.cpp b/kexi/tests/newapi/main.cpp
new file mode 100644
index 000000000..514538f3a
--- /dev/null
+++ b/kexi/tests/newapi/main.cpp
@@ -0,0 +1,254 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qfileinfo.h>
+#include <qguardedptr.h>
+
+#include <kdebug.h>
+#include <kcmdlineargs.h>
+#include <kapplication.h>
+#include <kinstance.h>
+#include <kiconloader.h>
+#include <kaboutdata.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+#include <kexidb/field.h>
+#include <kexidb/tableschema.h>
+#include <kexidb/queryschema.h>
+#include <kexidb/indexschema.h>
+#include <kexidb/parser/parser.h>
+
+#include <iostream>
+
+using namespace std;
+
+QCString prgname;
+QCString db_name;
+QCString drv_name;
+QCString test_name;
+int cursor_options = 0;
+bool db_name_required = true;
+
+KexiDB::ConnectionData conn_data;
+QGuardedPtr<KexiDB::Connection> conn;
+QGuardedPtr<KexiDB::Driver> driver;
+KApplication *app = 0;
+KInstance *instance = 0;
+
+static KCmdLineOptions options[] =
+{
+ { "test <test_name>",
+ "Available tests:\n"
+ "- cursors: test for cursors behaviour\n"
+ "- schema: test for db schema retrieving\n"
+ "- dbcreation: test for new db creation\n"
+ "- tables: test for tables creation and data\n"
+ " inserting\n"
+ "- tableview: test for KexiDataTableView data-aware\n"
+ " widget\n"
+ "- parser: test for parsing sql statements,\n"
+ " returns debug string for a given\n"
+ " sql statement or error message\n"
+ "- dr_prop: shows properties of selected driver"
+ , 0},
+ { "buffered-cursors",
+ "Optional switch :turns cursors used in any tests\n"
+ " to be buffered", 0},
+ { "query-params <params>", "Query parameters separated\n"
+ "by '|' character that will be passed to query\n"
+ "statement to replace [...] placeholders.", 0 },
+ { "", " Notes:\n"
+ "1. 'dr_prop' requires <db_name> argument.\n"
+ "2. 'parser' test requires <db_name>,\n"
+ " <driver_name> and <sql_statement> arguments\n"
+ "3. All other tests require <db_name>\n"
+ " and <driver_name> arguments.\n"
+ "4. 'tables' test automatically runs 'dbcreation'\n"
+ " test. (<new_db_name> is removed if already exists.\n"
+ "5. <db_name> must be a valid kexi database\n"
+ " e.g. created with 'tables' test.", 0},
+ { "+driver_name", "Driver name", 0},
+ { "+[db_name]", "Database name", 0},
+ { "+[sql_statement]", "Optional SQL statement (for parser test)", 0},
+ KCmdLineLastOption
+};
+
+#include "dbcreation_test.h"
+#include "cursors_test.h"
+#include "schema_test.h"
+#include "tables_test.h"
+#include "tableview_test.h"
+#include "parser_test.h"
+#include "dr_prop_test.h"
+
+#define RETURN(code) \
+ kdDebug()<< test_name << " TEST: " << (code==0?"PASSED":"ERROR") << endl; \
+ return code
+
+int main(int argc, char** argv)
+{
+ int minargs = 2;
+ bool gui = false;
+/* if (argc < minargs) {
+ usage();
+ RETURN(0);
+ }*/
+ QFileInfo info=QFileInfo(argv[0]);
+ prgname = info.baseName().latin1();
+
+ KCmdLineArgs::init(argc, argv,
+ new KAboutData( prgname, "KexiDBTest",
+ "0.1.2", "", KAboutData::License_GPL,
+ "(c) 2003-2006, Kexi Team\n"
+ "(c) 2003-2006, OpenOffice Polska Ltd.\n",
+ "",
+ "http://www.koffice.org/kexi",
+ "submit@bugs.kde.org"
+ )
+ );
+ KCmdLineArgs::addCmdLineOptions( options );
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ QCStringList tests;
+ tests << "cursors" << "schema" << "dbcreation" << "tables"
+ << "tableview" << "parser" << "dr_prop";
+ if (!args->isSet("test")) {
+ kdDebug() << "No test specified. Use --help." << endl;
+ RETURN(1);
+ }
+ test_name = args->getOption("test");
+ if (!tests.contains(test_name)) {
+ kdDebug() << QString("No such test \"%1\". Use --help.").arg(test_name) << endl;
+ RETURN(1);
+ }
+
+ if (test_name=="tableview") {
+ gui = true;
+ }
+ else if (test_name=="parser") {
+ minargs = 3;
+ }
+ else if (test_name=="dr_prop") {
+ minargs = 1;
+ db_name_required = false;
+ }
+ if ((int)args->count()<minargs) {
+ kdDebug() << QString("Not enough args (%1 required). Use --help.").arg(minargs) << endl;
+ RETURN(1);
+ }
+
+ if (gui) {
+ app = new KApplication(true, true);
+ instance = app;
+ KGlobal::iconLoader()->addAppDir("kexi");
+ }
+ else {
+ instance = new KInstance(prgname);
+ }
+
+ drv_name = args->arg(0);
+
+ KexiDB::DriverManager manager;
+ QStringList names = manager.driverNames();
+ kdDebug() << "DRIVERS: " << endl;
+ for (QStringList::ConstIterator it = names.constBegin(); it != names.constEnd() ; ++it)
+ kdDebug() << *it << endl;
+ if (manager.error() || names.isEmpty()) {
+ manager.debugError();
+ RETURN(1);
+ }
+
+ //get driver
+ driver = manager.driver(drv_name);
+ if (!driver || manager.error()) {
+ manager.debugError();
+ RETURN(1);
+ }
+ kdDebug() << "MIME type for '" << driver->name() << "': " << driver->fileDBDriverMimeType() << endl;
+
+ //open connection
+ if (args->count() >= 2)
+ db_name = args->arg(1);
+ if (db_name_required && db_name.isEmpty()) {
+ kdDebug() << prgname << ": database name?" << endl;
+ RETURN(1);
+ }
+ if (!db_name.isEmpty()) {
+ //additional switches:
+ if (args->isSet("buffered-cursors")) {
+ cursor_options |= KexiDB::Cursor::Buffered;
+ }
+ conn_data.setFileName( db_name );
+ conn = driver->createConnection(conn_data);
+
+ if (!conn || driver->error()) {
+ driver->debugError();
+ RETURN(1);
+ }
+ if (!conn->connect()) {
+ conn->debugError();
+ RETURN(1);
+ }
+ }
+
+ //start test:
+ int r=0;
+ if (test_name == "cursors")
+ r=cursorsTest();
+ else if (test_name == "schema")
+ r=schemaTest();
+ else if (test_name == "dbcreation")
+ r=dbCreationTest();
+ else if (test_name == "tables")
+ r=tablesTest();
+ else if (test_name == "tableview")
+ r=tableViewTest();
+ else if (test_name == "parser") {
+ QStringList params;
+ if (args->isSet("query-params"))
+ params = QStringList::split("|", args->getOption("query-params"));
+ r=parserTest(args->arg(2), params);
+ }
+ else if (test_name == "dr_prop")
+ r=drPropTest();
+ else {
+ kdWarning() << "No such test: " << test_name << endl;
+// usage();
+ RETURN(1);
+ }
+
+ if (app && r==0)
+ app->exec();
+
+ if (r)
+ kdDebug() << "RECENT SQL STATEMENT: " << conn->recentSQLString() << endl;
+
+ if (conn && !conn->disconnect())
+ r = 1;
+
+// kdDebug() << "!!! KexiDB::Transaction::globalcount == " << KexiDB::Transaction::globalCount() << endl;
+// kdDebug() << "!!! KexiDB::TransactionData::globalcount == " << KexiDB::TransactionData::globalCount() << endl;
+
+ delete app;
+
+ RETURN(r);
+}
diff --git a/kexi/tests/newapi/mysqlcursor.cpp b/kexi/tests/newapi/mysqlcursor.cpp
new file mode 100644
index 000000000..7f0e92231
--- /dev/null
+++ b/kexi/tests/newapi/mysqlcursor.cpp
@@ -0,0 +1,121 @@
+
+#include <kdebug.h>
+#include <kinstance.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+
+int main(int argc, char * argv[])
+{
+ KInstance instance("newapi");
+ KexiDB::DriverManager manager;
+ QStringList names = manager.driverNames();
+ kdDebug() << "DRIVERS: " << endl;
+ for (QStringList::ConstIterator it = names.constBegin(); it != names.constEnd() ; ++it)
+ kdDebug() << *it << endl;
+ if (manager.error()) {
+ kdDebug() << manager.errorMsg() << endl;
+ return 1;
+ }
+
+ //get driver
+ KexiDB::Driver *driver = manager.driver("mySQL");
+ if (manager.error()) {
+ kdDebug() << manager.errorMsg() << endl;
+ return 1;
+ }
+
+ //connection data that can be later reused
+ KexiDB::ConnectionData conn_data;
+
+ conn_data.userName="root";
+ if (argc>1)
+ conn_data.password=argv[1];
+ else
+ conn_data.password="mysql";
+ conn_data.hostName="localhost";
+
+ KexiDB::Connection *conn = driver->createConnection(conn_data);
+ if (driver->error()) {
+ kdDebug() << driver->errorMsg() << endl;
+ return 1;
+ }
+ if (!conn->connect()) {
+ kdDebug() << conn->errorMsg() << endl;
+ return 1;
+ }
+ if (!conn->useDatabase( "test" )) {
+ kdDebug() <<"use db:"<< conn->errorMsg() << endl;
+ return 1;
+ }
+
+ kdDebug()<<"Creating first cursor"<<endl;
+ KexiDB::Cursor *c=conn->executeQuery("select * from Applications");
+ if (!c) kdDebug()<<conn->errorMsg()<<endl;
+ kdDebug()<<"Creating second cursor"<<endl;
+ KexiDB::Cursor *c2=conn->executeQuery("select * from Applications");
+ if (!c2) kdDebug()<<conn->errorMsg()<<endl;
+
+ QStringList l=conn->databaseNames();
+ if (l.isEmpty()) kdDebug()<<conn->errorMsg()<<endl;
+ kdDebug()<<"Databases:"<<endl;
+ for (QStringList::ConstIterator it = l.constBegin(); it != l.constEnd() ; ++it)
+ kdDebug() << *it << endl;
+
+ if (c) {
+ while (c->moveNext()) {
+ kdDebug()<<"Cursor: Value(0)"<<c->value(0).asString()<<endl;
+ kdDebug()<<"Cursor: Value(1)"<<c->value(1).asString()<<endl;
+ }
+ kdDebug()<<"Cursor error:"<<c->errorMsg()<<endl;
+ }
+ if (c2) {
+ while (c2->moveNext()) {
+ kdDebug()<<"Cursor2: Value(0)"<<c2->value(0).asString()<<endl;
+ kdDebug()<<"Cursor2: Value(1)"<<c2->value(1).asString()<<endl;
+ }
+ }
+ if (c) {
+ kdDebug()<<"Cursor::prev"<<endl;
+ while (c->movePrev()) {
+ kdDebug()<<"Cursor: Value(0)"<<c->value(0).asString()<<endl;
+ kdDebug()<<"Cursor: Value(1)"<<c->value(1).asString()<<endl;
+
+ }
+ kdDebug()<<"up/down"<<endl;
+ c->moveNext();
+ kdDebug()<<"Cursor: Value(0)"<<c->value(0).asString()<<endl;
+ kdDebug()<<"Cursor: Value(1)"<<c->value(1).asString()<<endl;
+ c->moveNext();
+ kdDebug()<<"Cursor: Value(0)"<<c->value(0).asString()<<endl;
+ kdDebug()<<"Cursor: Value(1)"<<c->value(1).asString()<<endl;
+ c->movePrev();
+ kdDebug()<<"Cursor: Value(0)"<<c->value(0).asString()<<endl;
+ kdDebug()<<"Cursor: Value(1)"<<c->value(1).asString()<<endl;
+ c->movePrev();
+ kdDebug()<<"Cursor: Value(0)"<<c->value(0).asString()<<endl;
+ kdDebug()<<"Cursor: Value(1)"<<c->value(1).asString()<<endl;
+
+ }
+#if 0
+ KexiDB::Table *t = conn->tableSchema( "persons" );
+ if (t)
+ t->debug();
+ t = conn->tableSchema( "cars" );
+ if (t)
+ t->debug();
+
+// conn->tableNames();
+
+ if (!conn->disconnect()) {
+ kdDebug() << conn->errorMsg() << endl;
+ return 1;
+ }
+ debug("before del");
+ delete conn;
+ debug("after del");
+#endif
+ return 0;
+}
diff --git a/kexi/tests/newapi/mysqlcursortest_create.sql b/kexi/tests/newapi/mysqlcursortest_create.sql
new file mode 100644
index 000000000..750603c12
--- /dev/null
+++ b/kexi/tests/newapi/mysqlcursortest_create.sql
@@ -0,0 +1,41 @@
+-- MySQL dump 9.08
+--
+-- Host: localhost Database: test
+---------------------------------------------------------
+-- Server version 4.0.14
+
+--
+-- Table structure for table 'Applications'
+--
+
+CREATE TABLE Applications (
+ id int(11) NOT NULL default '0',
+ value varchar(20) default NULL,
+ PRIMARY KEY (id)
+) TYPE=MyISAM;
+
+--
+-- Dumping data for table 'Applications'
+--
+
+INSERT INTO Applications VALUES (0,'A');
+INSERT INTO Applications VALUES (1,'B');
+INSERT INTO Applications VALUES (2,'C');
+INSERT INTO Applications VALUES (3,'D');
+INSERT INTO Applications VALUES (4,'E');
+
+--
+-- Table structure for table 'jw1'
+--
+
+CREATE TABLE jw1 (
+ id int(11) NOT NULL default '0',
+ txt varchar(20) default NULL,
+ PRIMARY KEY (id)
+) TYPE=MyISAM;
+
+--
+-- Dumping data for table 'jw1'
+--
+
+
diff --git a/kexi/tests/newapi/mysqlcursortest_expectedoutput b/kexi/tests/newapi/mysqlcursortest_expectedoutput
new file mode 100644
index 000000000..58ce8aa3e
--- /dev/null
+++ b/kexi/tests/newapi/mysqlcursortest_expectedoutput
@@ -0,0 +1,128 @@
+KexiDB: DriverManagerInternal::incRefCount(): 1
+kio (KTrader): KServiceTypeProfile::offers( Kexi/DBDriver, )
+kio (KSycoca): Trying to open ksycoca from /var/tmp/kdecache-jowenn/ksycoca
+kio (KTrader): Returning 2 offers
+KexiDB: KexiDB::DriverManager::lookupDrivers(): registered driver: mySQL(kexidb_mysqldriver)
+KexiDB: KexiDB::DriverManager::lookupDrivers(): registered driver: SQLite(kexidb_sqlitedriver)
+newapi: DRIVERS:
+newapi: SQLite
+newapi: mySQL
+KexiDB: DriverManager::driver(): loading mySQL
+KexiDB: KexiDBInterfaceManager::load(): library: kexidb_mysqldriver
+KexiDB (driver impl): MySqlDriver::MySqlDriver()
+KexiDB: KexiDBInterfaceManager::load(): loading succeed: mySQL
+KexiDB: drv=134779696
+KexiDB (driver impl): MySqlConnection::connect()
+KexiDB: Connection::databaseExists(test,true)
+KexiDB: Connection::databaseNames(true)
+KexiDB (driver impl): MySqlConnection::drv_getDatabasesList()
+newapi: Creating first cursor
+newapi: Creating second cursor
+KexiDB: Connection::databaseNames(false)
+KexiDB (driver impl): MySqlConnection::drv_getDatabasesList()
+KexiDB: Connection::databaseNames(): mysql
+KexiDB: Connection::databaseNames(): test
+KexiDB: add
+newapi: Databases:
+newapi: test
+KexiDB: m_at < m_records_in_buf :: 0 < 0
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 1
+newapi: Cursor: Value(0)0
+newapi: Cursor: Value(1)A
+KexiDB: m_at < m_records_in_buf :: 1 < 1
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 2
+newapi: Cursor: Value(0)1
+newapi: Cursor: Value(1)B
+KexiDB: m_at < m_records_in_buf :: 2 < 2
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 3
+newapi: Cursor: Value(0)2
+newapi: Cursor: Value(1)C
+KexiDB: m_at < m_records_in_buf :: 3 < 3
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 4
+newapi: Cursor: Value(0)3
+newapi: Cursor: Value(1)D
+KexiDB: m_at < m_records_in_buf :: 4 < 4
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 5
+newapi: Cursor: Value(0)4
+newapi: Cursor: Value(1)E
+KexiDB: m_at < m_records_in_buf :: 5 < 5
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_result != FetchOK ********
+newapi: Cursor error:
+KexiDB: m_at < m_records_in_buf :: 0 < 0
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 1
+newapi: Cursor2: Value(0)0
+newapi: Cursor2: Value(1)A
+KexiDB: m_at < m_records_in_buf :: 1 < 1
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 2
+newapi: Cursor2: Value(0)1
+newapi: Cursor2: Value(1)B
+KexiDB: m_at < m_records_in_buf :: 2 < 2
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 3
+newapi: Cursor2: Value(0)2
+newapi: Cursor2: Value(1)C
+KexiDB: m_at < m_records_in_buf :: 3 < 3
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 4
+newapi: Cursor2: Value(0)3
+newapi: Cursor2: Value(1)D
+KexiDB: m_at < m_records_in_buf :: 4 < 4
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_at == 5
+newapi: Cursor2: Value(0)4
+newapi: Cursor2: Value(1)E
+KexiDB: m_at < m_records_in_buf :: 5 < 5
+KexiDB (driver impl): ==== (buffered) sqlite_step ====
+KexiDB (driver impl): m_result != FetchOK ********
+newapi: Cursor::prev
+newapi: Cursor: Value(0)4
+newapi: Cursor: Value(1)E
+newapi: Cursor: Value(0)3
+newapi: Cursor: Value(1)D
+newapi: Cursor: Value(0)2
+newapi: Cursor: Value(1)C
+newapi: Cursor: Value(0)1
+newapi: Cursor: Value(1)B
+newapi: Cursor: Value(0)0
+newapi: Cursor: Value(1)A
+newapi: up/down
+KexiDB: m_at < m_records_in_buf :: -1 < 5
+KexiDB (driver impl): m_at == 1
+newapi: Cursor: Value(0)0
+newapi: Cursor: Value(1)A
+KexiDB: m_at < m_records_in_buf :: 1 < 5
+KexiDB (driver impl): m_at == 2
+newapi: Cursor: Value(0)1
+newapi: Cursor: Value(1)B
+newapi: Cursor: Value(0)1
+newapi: Cursor: Value(1)B
+newapi: Cursor: Value(0)0
+newapi: Cursor: Value(1)A
+KexiDB: DriverManager::~DriverManager()
+KexiDB: DriverManagerInternal::decRefCount(): 0
+KexiDB: DriverManagerInternal::~DriverManagerInternal()
+KexiDB: Driver::~Driver()
+KexiDB: Cursor::close() == true
+KexiDB: Cursor::~Cursor() 'select * from Applications'
+KexiDB: Cursor::close() == true
+KexiDB: Cursor::~Cursor() 'select * from Applications'
+KexiDB: Connection::closeDatabase(): true
+KexiDB (driver impl): MySqlConnection::disconnect()
+KexiDB: Connection::~Connection()
+KexiDB: ~Transaction(): null
+KexiDB: -- Transaction::globalcount == 0
+KexiDB: ~Transaction(): null
+KexiDB: -- Transaction::globalcount == 0
+KexiDB: Driver::~Driver() ok
+KexiDB: DriverManagerInternal::~DriverManagerInternal() ok
+KexiDB: DriverManager::~DriverManager() ok
+KexiDB: ~Transaction(): null
+KexiDB: -- Transaction::globalcount == 0
diff --git a/kexi/tests/newapi/newapi.pro b/kexi/tests/newapi/newapi.pro
new file mode 100644
index 000000000..9caf6926e
--- /dev/null
+++ b/kexi/tests/newapi/newapi.pro
@@ -0,0 +1,33 @@
+TEMPLATE = app
+
+include( $(KEXI)/common.pro )
+
+unix {
+ LIBS += -lkexidb
+}
+
+win32 {
+
+ LIBS += \
+ $$KDELIBDESTDIR/kexicore$$KEXILIB_SUFFIX \
+ $$KDELIBDESTDIR/kexidatatable$$KEXILIB_SUFFIX \
+ $$KDELIBDESTDIR/kexiextendedwidgets$$KEXILIB_SUFFIX
+
+QMAKE_CXXFLAGS += $(KEXI_OPTIONS)
+
+# test specific:
+ LIBS += \
+ $$KDELIBDESTDIR/kexidb$$KEXILIB_SUFFIX
+
+#allow to select target independently from debug information
+ CONFIG += console
+ CONFIG -= windows
+}
+
+DESTDIR = . # no # $KDEDIR/bin
+TARGET = kexidbtest
+
+SOURCES = \
+main.cpp
+
+HEADERS =
diff --git a/kexi/tests/newapi/parser_test.h b/kexi/tests/newapi/parser_test.h
new file mode 100644
index 000000000..144099614
--- /dev/null
+++ b/kexi/tests/newapi/parser_test.h
@@ -0,0 +1,62 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef PARSER_TEST_H
+#define PARSER_TEST_H
+
+int parserTest(const char *st, const QStringList& params)
+{
+ int r = 0;
+
+ if (!conn->useDatabase( db_name )) {
+ conn->debugError();
+ return 1;
+ }
+
+ KexiDB::Parser parser(conn);
+
+ const bool ok = parser.parse(QString::fromLocal8Bit( st ));
+ KexiDB::QuerySchema *q = parser.query();
+ QValueList<QVariant> variantParams;
+ foreach( QStringList::ConstIterator, it, params )
+ variantParams.append(*it);
+ if (ok && q) {
+ cout << q->debugString().latin1() << '\n';
+ cout << "-STATEMENT:\n" << conn->selectStatement( *q, variantParams ).latin1() << '\n';
+ }
+ else {
+ KexiDB::ParserError err = parser.error();
+ kdDebug() << QString("Error = %1\ntype = %2\nat = %3").arg(err.error())
+ .arg(err.type()).arg(err.at()) << endl;
+ r = 1;
+ }
+ delete q;
+ q=0;
+
+
+ if (!conn->closeDatabase()) {
+ conn->debugError();
+ return 1;
+ }
+
+ return r;
+}
+
+#endif
+
diff --git a/kexi/tests/newapi/schema.sql b/kexi/tests/newapi/schema.sql
new file mode 100644
index 000000000..f918c6396
--- /dev/null
+++ b/kexi/tests/newapi/schema.sql
@@ -0,0 +1,38 @@
+
+begin;
+
+drop table kexi__objects;
+drop table kexi__fields;
+drop table kexi__querydata;
+drop table kexi__db;
+
+CREATE TABLE kexi__objects (o_id Integer, o_type Byte, o_name Text(200),
+o_caption Text, o_help LongText);
+
+CREATE TABLE kexi__fields (t_id Integer, f_type Byte, f_name Text(200),
+f_length Integer, f_precision Integer,
+f_constraints Integer, f_options Integer, f_order Integer,
+f_caption Text(200), f_help Text);
+
+CREATE TABLE kexi__querydata (q_id Integer, q_sql LongText, q_valid Boolean );
+
+CREATE TABLE kexi__parts (p_id Integer, p_name Text, p_mime Text, p_url Text);
+
+CREATE TABLE kexi__db (db_property Text(32), db_value LongText );
+
+insert into kexi__objects values (1, 1, 'persons', 'Persons', 'Persons group in our factory');
+insert into kexi__fields values (1, 3, 'id', 0, 0, 16, 0, 0, null, null);
+insert into kexi__fields values (1, 1, 'age', 0, 0, 16, 0, 1, null, null);
+insert into kexi__fields values (1, 11, 'name', 30, 0, 16, 0, 2, null, null);
+insert into kexi__fields values (1, 11, 'surname', 30, 0, 16, 0, 3, null, null);
+
+insert into kexi__objects values (2, 1, 'cars', 'Cars', 'Cars owned by persons');
+insert into kexi__fields values (2, 3, 'id', 0, 0, 16, 0, 0, null, null);
+insert into kexi__fields values (2, 3, 'owner', 0, 0, 16, 0, 1, null, null);
+insert into kexi__fields values (2, 11, 'model', 30, 0, 16, 0, 2, null, null);
+
+insert into kexi__db values ('kexidb_major_ver', '1');
+insert into kexi__db values ('kexidb_minor_ver', '2');
+
+commit;
+
diff --git a/kexi/tests/newapi/schema_test.h b/kexi/tests/newapi/schema_test.h
new file mode 100644
index 000000000..6eec76d18
--- /dev/null
+++ b/kexi/tests/newapi/schema_test.h
@@ -0,0 +1,55 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef SCHEMA_TEST_H
+#define SCHEMA_TEST_H
+
+int schemaTest()
+{
+ if (!conn->useDatabase( db_name )) {
+ kdDebug() << conn->errorMsg() << endl;
+ return 1;
+ }
+
+ KexiDB::TableSchema *t = conn->tableSchema( "persons" );
+ if (t)
+ t->debug();
+ else
+ kdDebug() << "!persons" << endl;
+ t = conn->tableSchema( "cars" );
+ if (t)
+ t->debug();
+ else
+ kdDebug() << "!cars" << endl;
+/*
+// some tests
+ {
+ KexiDB::Field::ListIterator iter = t->fieldsIterator();
+ KexiDB::Field::List *lst = t->fields();
+ lst->clear();
+ for (;iter.current();++iter) {
+ kdDebug() << "FIELD=" << iter.current()->name() << endl;
+// iter.current()->setName(" ");
+ }
+ }*/
+ return 0;
+}
+
+#endif
+
diff --git a/kexi/tests/newapi/sqltest b/kexi/tests/newapi/sqltest
new file mode 100755
index 000000000..cd63134b6
--- /dev/null
+++ b/kexi/tests/newapi/sqltest
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# sqltest - fast parser testing using sqlite database.
+# Type sqltest --help for usage info.
+# Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+[ $# -lt 1 ] && echo "Usage: $0 <sqlite-database-name> <sql-statement> [-v] [other_options]" && \
+ echo " -v Verbose mode" && exit 1
+
+dbname=$1
+shift
+
+sql=$1
+shift
+
+verbose=0
+if [ "$1" = "-v" ] ; then
+ verbose=1
+ shift
+fi
+
+temp=`mktemp /tmp/$0.XXXXXX`
+
+./kexidbtest -test parser sqlite3 "$dbname" "$sql" $* 2> $temp || \
+ cat $temp && test "$verbose" -eq 1 && cat $temp
+
+rm -f $temp
diff --git a/kexi/tests/newapi/sqltest_int b/kexi/tests/newapi/sqltest_int
new file mode 100755
index 000000000..6cbe47ac1
--- /dev/null
+++ b/kexi/tests/newapi/sqltest_int
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# sqltest_int - fast parser testing using sqlite database (interactive)
+# Type sqltest_int for usage info.
+
+[ $# -lt 1 ] && echo "Usage: $0 <sqlite-database-name> [-v] [other_options]" && \
+ echo " -v Verbose mode" && exit 1
+
+dbname=$1
+shift
+
+echo "Enter SQL Statement:"
+read
+
+./sqltest "$dbname" "$REPLY" $*
diff --git a/kexi/tests/newapi/statements.txt b/kexi/tests/newapi/statements.txt
new file mode 100644
index 000000000..3d108ce76
--- /dev/null
+++ b/kexi/tests/newapi/statements.txt
@@ -0,0 +1,89 @@
+---------- TOPIC000: GENERAL ISSUES --------------
+-- OK000: the same field used in two columns
+select id, id from persons;
+-- OK001: whitespace between table-identifier, dot field-identifier/asterisk
+select persons . id from persons;
+select persons . * from persons;
+-- OK002: multiple asterisks
+select *, * from persons;
+-- ER003: identifier cannot start with a number
+select 1id from persons;
+-- ER004: asterisk not allowed: no tables specified
+select *;
+-- OK005: empty tables set
+select 1, 2;
+-- OK006: empty column set (KEXISQL EXTENSION!)
+select from cars;
+-- OK007: totally empty statement (KEXISQL EXTENSION!)
+select;
+
+---------- TOPIC100: ALIASES IN SELECT STATEMENT --------------
+-- OK100: aliases for columns
+select id myid from persons;
+-- OK101: aliases for tables
+select id from persons p;
+-- ER102: there's no "persons" table in this query (alias "p" covers it)
+select persons.id from persons p;
+-- OK103: alias "p" for table "persons" is used
+select p.id from persons p;
+-- OK104: multiple aliases for the same table
+select persons.id from persons, persons p, persons p2
+-- ER105: column "id" is defined in both tables (so "id" column is ambiguous)
+select id from persons p, cars p;
+-- ER106: alias "p" is used twice for tables and both have "id" column (so "p" column is ambiguous)
+select p.id from persons p, cars p;
+select p.* from persons p, cars p;
+select persons.* from persons, cars persons;
+-- ER107: alias not allowed for asterisk
+select * as c from cars;
+select cars.* as c from cars;
+
+---------- TOPIC200: EXPRESSIONS IN COLUMNS OF SELECT STATEMENT --------------
+-- OK200: like ER106, but it's ok, because we're not using fields from "p" tables
+select 1 from persons p, cars p;
+-- OK201: complex expressions support, operators precedence, and brackets
+select NULL IS NOT NULL from cars;
+select 2+3*4 from cars;
+select (2+3)*4 from cars;
+-- OK202: support for aliases for complex-expression columns
+select (2+3)*4 from cars;
+-- ER203: column names are invalidated inside a complex expressions
+select one*two from persons;
+-- ER204: like ER106, but ambiguous column is inside a complex expression
+select id*2 from persons p, cars p;
+
+---------- TOPIC300: EXPRESSIONS IN 'WHERE' SECTION OF SELECT STATEMENT --------------
+-- OK300: complex expressions in WHERE section
+select id from cars where (id > 2 OR cars.owner IS NULL) AND NOT 2 * id < 5;
+
+---------- TOPIC400: 'ORDER BY' SECTION OF SELECT STATEMENT --------------
+-- OK400: simple ORDER BY
+select id from cars order by id;
+-- OK401: simple ORDER BY with DESC
+select id from cars order by id DESC;
+-- OK402: simple ORDER BY with ASC
+select id from cars order by id ASC;
+-- OK403: simple ORDER BY with WHERE
+select id from cars order by id WHERE id < 5;
+-- OK404: simple ORDER BY with WHERE; opposite direction
+select id from cars WHERE id < 5 order by id;
+-- OK405: simple ORDER BY, sorting field 'owner' is not in the list of displayed fields
+select id from cars order by owner;
+-- OK406: ORDER BY with many arguments
+select id from cars order by owner, model, id;
+-- OK407: ORDER BY where column numbers are used instead of names
+select id, model from cars order by 2, 1;
+-- ER408: ORDER BY column number 2 out of range - should be between 1 and 1
+-- (there's only one visible field)
+select id from cars order by 2, 1;
+
+---------- TOPIC500: EXPRESSIONS --------------
+-- OK500: operators precedence: arithmetic before relational
+select 1 + 2 < 3;
+-- OK501: *,/ before +,-
+select 1+2*3;
+-- OK501: unary expressions before binary expressions
+select 1+-2;
+
+-- TODO
+-'--' comments
diff --git a/kexi/tests/newapi/tables_test.h b/kexi/tests/newapi/tables_test.h
new file mode 100644
index 000000000..67516db04
--- /dev/null
+++ b/kexi/tests/newapi/tables_test.h
@@ -0,0 +1,117 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef TABLETEST_H
+#define TABLETEST_H
+
+int tablesTest()
+{
+ if (dbCreationTest()!=0)
+ return 1;
+
+ if (!conn->useDatabase( db_name )) {
+ conn->debugError();
+ return 1;
+ }
+
+ conn->setAutoCommit(false);
+ KexiDB::Transaction t = conn->beginTransaction();
+ if (conn->error()) {
+ conn->debugError();
+ return 1;
+ }
+
+ //now: lets create tables:
+ KexiDB::Field *f;
+ KexiDB::TableSchema *t_persons = new KexiDB::TableSchema("persons");
+ t_persons->setCaption("Persons in our factory");
+ t_persons->addField( f=new KexiDB::Field("id", KexiDB::Field::Integer, KexiDB::Field::PrimaryKey | KexiDB::Field::AutoInc, KexiDB::Field::Unsigned) );
+ f->setCaption("ID");
+ t_persons->addField( f=new KexiDB::Field("age", KexiDB::Field::Integer, 0, KexiDB::Field::Unsigned) );
+ f->setCaption("Age");
+ t_persons->addField( f=new KexiDB::Field("name", KexiDB::Field::Text) );
+ f->setCaption("Name");
+ t_persons->addField( f=new KexiDB::Field("surname", KexiDB::Field::Text) );
+ f->setCaption("Surname");
+ if (!conn->createTable( t_persons )) {
+ conn->debugError();
+ return 1;
+ }
+ kdDebug() << "-- PERSONS created --" << endl;
+ t_persons->debug();
+
+ if (!conn->insertRecord(*t_persons, QVariant(1), QVariant(27), QVariant("Jaroslaw"), QVariant("Staniek"))
+ ||!conn->insertRecord(*t_persons, QVariant(2), QVariant(60), QVariant("Lech"), QVariant("Walesa"))
+ ||!conn->insertRecord(*t_persons, QVariant(3), QVariant(45), QVariant("Bill"), QVariant("Gates"))
+ ||!conn->insertRecord(*t_persons, QVariant(4), QVariant(35), QVariant("John"), QVariant("Smith"))
+ )
+ {
+ kdDebug() << "-- PERSONS data err. --" << endl;
+ return 1;
+ }
+ kdDebug() << "-- PERSONS data created --" << endl;
+
+
+ KexiDB::TableSchema *t_cars = new KexiDB::TableSchema("cars");
+ t_cars->setCaption("Cars owned by persons");
+ t_cars->addField( f=new KexiDB::Field("id", KexiDB::Field::Integer, KexiDB::Field::PrimaryKey | KexiDB::Field::AutoInc, KexiDB::Field::Unsigned) );
+ f->setCaption("ID");
+ t_cars->addField( f=new KexiDB::Field("owner", KexiDB::Field::Integer, 0, KexiDB::Field::Unsigned) );
+ f->setCaption("Car owner");
+ t_cars->addField( f=new KexiDB::Field("model", KexiDB::Field::Text) );
+ f->setCaption("Car model");
+ if (!conn->createTable( t_cars )) {
+ conn->debugError();
+ return 1;
+ }
+ kdDebug() << "-- CARS created --" << endl;
+ if (!conn->insertRecord(*t_cars, QVariant(1), QVariant(1), QVariant("Fiat"))
+ ||!conn->insertRecord(*t_cars, QVariant(2), QVariant(2), QVariant("Syrena"))
+ ||!conn->insertRecord(*t_cars, QVariant(3), QVariant(3), QVariant("Chrysler"))
+ ||!conn->insertRecord(*t_cars, QVariant(4), QVariant(3), QVariant("BMW"))
+ ||!conn->insertRecord(*t_cars, QVariant(5), QVariant(4), QVariant("Volvo"))
+ )
+ {
+ kdDebug() << "-- CARS data err. --" << endl;
+ return 1;
+ }
+ kdDebug() << "-- CARS data created --" << endl;
+
+ if (!conn->commitTransaction(t)) {
+ conn->debugError();
+ return 1;
+ }
+
+ kdDebug() << "NOW, TABLE LIST: " << endl;
+ QStringList tnames = conn->tableNames();
+ for (QStringList::iterator it = tnames.begin(); it!=tnames.end(); ++it) {
+ kdDebug() << " - " << (*it) << endl;
+ }
+
+
+ if (!conn->closeDatabase()) {
+ conn->debugError();
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
+
diff --git a/kexi/tests/newapi/tableview_test.h b/kexi/tests/newapi/tableview_test.h
new file mode 100644
index 000000000..2b5ef6c8e
--- /dev/null
+++ b/kexi/tests/newapi/tableview_test.h
@@ -0,0 +1,60 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef TABLEVIEW_TEST_H
+#define TABLEVIEW_TEST_H
+
+#include <widget/tableview/kexidatatableview.h>
+#include <kexidb/cursor.h>
+
+int tableViewTest()
+{
+ if (!conn->useDatabase( db_name )) {
+ conn->debugError();
+ return 1;
+ }
+
+ KexiDB::TableSchema *persons = conn->tableSchema( "persons" );
+ if (!persons) {
+ conn->debugError();
+ kdDebug() << "tableViewTest(): !persons" <<endl;
+ return 1;
+ }
+
+// KexiTableView *tv = new KexiTableView(0, "tv", /*KexiTableList *contents=*/0);
+// KexiDB::Cursor *cursor = conn->executeQuery( "select * from persons", KexiDB::Cursor::Buffered );
+ KexiDB::Cursor *cursor = conn->prepareQuery( *persons , cursor_options );//KexiDB::Cursor::Buffered );
+ if (!cursor) {
+ conn->debugError();
+ kdDebug() << "tableViewTest(): !cursor" <<endl;
+ return 1;
+ }
+
+ KexiDataTableView *tv = new KexiDataTableView(0, "tv", cursor);
+
+ app->setMainWidget(tv);
+ tv->move((qApp->desktop()->width() - tv->width())/2, (qApp->desktop()->height() - tv->height())/2);
+ tv->show();
+ tv->setFocus();
+
+ return 0;
+}
+
+#endif
+
diff --git a/kexi/tests/parser/Makefile.am b/kexi/tests/parser/Makefile.am
new file mode 100644
index 000000000..ec1a34415
--- /dev/null
+++ b/kexi/tests/parser/Makefile.am
@@ -0,0 +1,13 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+bin_PROGRAMS = kexidbparser
+
+noinst_PROGRAMS = kexidbparser
+
+INCLUDES = -I$(top_srcdir)/kexi $(all_includes)
+
+kexidbparser_SOURCES = main.cpp
+kexidbparser_LDADD = $(LIB_QT) $(LIB_KDECORE) $(top_builddir)/kexi/kexidb/libkexidb.la $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la $(all_libraries)
+kexidbparser_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO) --no-undefined
+
+
diff --git a/kexi/tests/parser/README b/kexi/tests/parser/README
new file mode 100644
index 000000000..d6f2ed449
--- /dev/null
+++ b/kexi/tests/parser/README
@@ -0,0 +1,9 @@
+Parser - Interactive test of KEXISQL parser
+
+Usage:
+<program_name> <driver_name> <db_name>
+
+Notes:
+<driver_name> can be just any existing - no database is changed, but only opened
+because of parser requirements.
+Predefined "db" database file for sqlite is available in this dir.
diff --git a/kexi/tests/parser/db b/kexi/tests/parser/db
new file mode 100644
index 000000000..d27cda227
--- /dev/null
+++ b/kexi/tests/parser/db
Binary files differ
diff --git a/kexi/tests/parser/main.cpp b/kexi/tests/parser/main.cpp
new file mode 100644
index 000000000..f6ee57421
--- /dev/null
+++ b/kexi/tests/parser/main.cpp
@@ -0,0 +1,103 @@
+#include <iostream>
+#include <string>
+
+#include <qfileinfo.h>
+
+#include <kdebug.h>
+#include <kinstance.h>
+
+#include <kexidb/drivermanager.h>
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+#include <kexidb/parser/parser.h>
+
+using namespace std;
+QCString prgname;
+
+int main(int argc, char **argv)
+{
+ kdDebug() << "main()" << endl;
+ QFileInfo info=QFileInfo(argv[0]);
+ prgname = info.baseName().latin1();
+ KInstance instance( prgname );
+ if (argc<2) {
+ return 1;
+ }
+ QCString drv_name(argv[1]);
+ QCString db_name = QString(argv[2]).lower().latin1();
+
+ KexiDB::DriverManager manager; // = KexiDB::DriverManager::self();
+ QStringList names = manager.driverNames();
+ kdDebug() << "DRIVERS: " << endl;
+ for (QStringList::ConstIterator it = names.constBegin(); it != names.constEnd() ; ++it)
+ kdDebug() << *it << endl;
+ if (manager.error()) {
+ kdDebug() << manager.errorMsg() << endl;
+ return 1;
+ }
+
+ //get driver
+ KexiDB::Driver *driver = manager.driver(drv_name);
+ if (!driver || manager.error()) {
+ kdDebug() << manager.errorMsg() << endl;
+ return 1;
+ }
+
+ //connection data that can be later reused
+ KexiDB::ConnectionData conn_data;
+ conn_data.setFileName(db_name);
+
+ KexiDB::Connection *conn = driver->createConnection(conn_data);
+ if (!conn || driver->error()) {
+ kdDebug() << "error: " << driver->errorMsg() << endl;
+ return 1;
+ }
+ if (!conn->connect()) {
+ kdDebug() << "error: " << conn->errorMsg() << endl;
+ return 1;
+ }
+ if (!conn->useDatabase( db_name )) {
+ kdDebug() << "error: " << conn->errorMsg() << endl;
+ return 1;
+ }
+
+ KexiDB::Parser *parser = new KexiDB::Parser(conn);
+
+ std::string cmd;
+ while(cmd != "quit")
+ {
+ std::cout << "SQL> ";
+ getline(std::cin, cmd);
+ parser->parse(cmd.c_str());
+ switch(parser->operation())
+ {
+ case KexiDB::Parser::OP_Error:
+ kdDebug() << "***********************" << endl;
+ kdDebug() << "* error *" << endl;
+ kdDebug() << "***********************" << endl;
+ break;
+ case KexiDB::Parser::OP_CreateTable:
+ {
+ kdDebug() << "Schema of table: " << parser->table()->name() << endl;
+ parser->table()->debug();
+ break;
+ }
+ case KexiDB::Parser::OP_Select:
+ {
+ kdDebug() << "Select statement: " << endl;
+ KexiDB::QuerySchema *q = parser->query();
+ q->debug();
+ delete q;
+ break;
+ }
+ default:
+ kdDebug() << "main(): not implemented in main.cpp" << endl;
+
+
+ }
+ parser->clear();
+ }
+ return 0;
+}
+
diff --git a/kexi/tests/parser/parser.pro b/kexi/tests/parser/parser.pro
new file mode 100644
index 000000000..4629417a4
--- /dev/null
+++ b/kexi/tests/parser/parser.pro
@@ -0,0 +1,24 @@
+TEMPLATE = app
+
+include( $(KEXI)/common.pro )
+
+win32 {
+
+QMAKE_CXXFLAGS += $(KEXI_OPTIONS)
+
+# test specific:
+ LIBS += \
+ $$KDELIBDESTDIR/kexidb$$KEXILIB_SUFFIX
+
+#allow to select target independently from debug information
+ CONFIG += console
+ CONFIG -= windows
+}
+
+DESTDIR = . # no # $KDEDIR/bin
+TARGET = parser
+
+SOURCES = \
+main.cpp
+
+HEADERS =
diff --git a/kexi/tests/startup/Makefile.am b/kexi/tests/startup/Makefile.am
new file mode 100644
index 000000000..ab6cc9c55
--- /dev/null
+++ b/kexi/tests/startup/Makefile.am
@@ -0,0 +1,19 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+bin_PROGRAMS = kexistartuptest
+
+noinst_PROGRAMS = kexistartuptest
+
+INCLUDES = -I$(top_srcdir)/kexi $(all_includes)
+
+SUBDIRS = .
+
+METASOURCES = AUTO
+
+kexistartuptest_SOURCES = main.cpp
+kexistartuptest_LDADD = $(LIB_QT) $(LIB_KDECORE) $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/core/libkexicore.la \
+ $(all_libraries)
+
+kexistartuptest_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO) --no-undefined
+
diff --git a/kexi/tests/startup/main.cpp b/kexi/tests/startup/main.cpp
new file mode 100644
index 000000000..dd2e86ce0
--- /dev/null
+++ b/kexi/tests/startup/main.cpp
@@ -0,0 +1,119 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+#include <kapplication.h>
+
+//#include <tableview/kexitableview.h>
+/*#include <kexidb/drivermanager.h>
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>*/
+
+#include "main/startup/KexiStartupDialog.h"
+#include "main/startup/KexiConnSelector.h"
+#include "core/kexiprojectset.h"
+#include "core/kexiprojectdata.h"
+
+int main(int argc, char* argv[])
+{
+ KApplication app(argc, argv, "startup");
+
+// Widget w;
+// app.setMainWidget(&w);
+
+/* KexiTableView tv;
+ app.setMainWidget(&tv);
+
+ KexiTableViewData data;
+ KexiTableViewColumn col;
+ col.type = QVariant::Int; col.caption = "Id"; data.addColumn( col );
+ col.type = QVariant::String; col.caption = "Name"; data.addColumn( col );
+ col.type = QVariant::Int; col.caption = "Age"; data.addColumn( col );
+ tv.setData(&data, false);
+ tv.show();*/
+
+ //some connection data
+ KexiDBConnectionSet connset;
+ KexiDB::ConnectionData *conndata;
+ conndata = new KexiDB::ConnectionData();
+ conndata->name = "My connection 1";
+ conndata->driverName = "mysql";
+ conndata->hostName = "host.net";
+ conndata->userName = "user";
+ connset.addConnectionData(conndata);
+ conndata = new KexiDB::ConnectionData();
+ conndata->name = "My connection 2";
+ conndata->driverName = "mysql";
+ conndata->hostName = "myhost.org";
+ conndata->userName = "otheruser";
+ conndata->port = 53121;
+ connset.addConnectionData(conndata);
+
+ //some recent projects data
+ KexiProjectData *prjdata;
+ prjdata = new KexiProjectData( *conndata, "bigdb", "Big DB" );
+ prjdata->setCaption("My Big Project");
+ prjdata->setDescription("This is my first biger project started yesterday. Have fun!");
+ KexiProjectSet prj_set;
+ prj_set.addProjectData(prjdata);
+
+ KexiStartupDialog startup(KexiStartupDialog::Everything, 0, connset, prj_set, 0, "dlg");
+ int e=startup.exec();
+ kdDebug() << (e==QDialog::Accepted ? "Accepted" : "Rejected") << endl;
+
+ if (e==QDialog::Accepted) {
+ int r = startup.result();
+ if (r==KexiStartupDialog::TemplateResult) {
+ kdDebug() << "Template key == " << startup.selectedTemplateKey() << endl;
+ if (startup.selectedTemplateKey()=="blank") {
+#if 0
+ KexiConnSelectorDialog sel(connset, 0,"sel");
+ e = sel.exec();
+ kdDebug() << (e==QDialog::Accepted ? "Accepted" : "Rejected") << endl;
+ if (e==QDialog::Accepted) {
+ kdDebug() << "Selected conn. type: " << (sel.selectedConnectionType()==KexiConnSelectorWidget::FileBased ? "File based" : "Server based") << endl;
+ if (sel.selectedConnectionType()==KexiConnSelectorWidget::ServerBased) {
+ kdDebug() << "SERVER: " << sel.selectedConnectionData()->serverInfoString() << endl;
+ }
+ }
+#endif
+ }
+ }
+ else if (r==KexiStartupDialog::OpenExistingResult) {
+ kdDebug() << "Existing project --------" << endl;
+ QString selFile = startup.selectedExistingFile();
+ if (!selFile.isEmpty())
+ kdDebug() << "Project File: " << selFile << endl;
+ else if (startup.selectedExistingConnection()) {
+ kdDebug() << "Existing connection: " << startup.selectedExistingConnection()->serverInfoString() << endl;
+ //ok, now we are trying to show daabases for this conenction to this user
+ //todo
+ }
+ }
+ else if (r==KexiStartupDialog::OpenRecentResult) {
+ kdDebug() << "Recent project --------" << endl;
+ const KexiProjectData *data = startup.selectedProjectData();
+ if (data) {
+ kdDebug() << "Selected project: database=" << data->databaseName()
+ << " connection=" << data->constConnectionData()->serverInfoString() << endl;
+ }
+ }
+ }
+}
diff --git a/kexi/tests/startup/testdb.kexis b/kexi/tests/startup/testdb.kexis
new file mode 100644
index 000000000..06a558aae
--- /dev/null
+++ b/kexi/tests/startup/testdb.kexis
@@ -0,0 +1,63 @@
+#
+# This file contains information for accessing Kexi project(s)
+# stored on a database server
+#
+# Description: The file can contain one special "File Information"
+# section and one or more sections for defining database connections.
+#
+
+[File Information]
+# Version information for this file format, "1" is by default.
+# You can forget about adding this.
+version=1
+
+# begin of section for a shortcut to a single database
+[Test Database]
+
+# The type of this section. Can be:
+# - database: a single database project together with
+# it's connection parameters
+# - connection: connection arameters only, without database name
+# user will be able to see a list of databases provided
+# for the connection and open one of them. (not yet implemented)
+type=database
+
+# Database driver (engine) name like: mysql, postgresql.
+engine=mysql
+
+# Additional human-readable caption for informational purposes.
+caption=Test Project
+
+# Database name, or connection name, depending on type of this section.
+name=testdb
+
+# Server name, for local connection. Can be empty or equal to "localhost".
+server=localhost
+
+# TCP/IP port, leave it empty if default port for a given
+# engine should be used.
+port=3333
+
+# Username, leave empty if you want to reuse your login name.
+user=testuser
+
+# User password (DANGEROUS: currently plain text!).
+# Leave it empty, and you will be asked for password everytime
+# you're opening your database.
+# KDE Wallet will be supported in the future.
+password=testy
+
+# Additional (probably multiline) human-readable caption
+# for informational purposes. Line endings should be netered as \n.
+comment=My favourite test project
+
+# Set this to 1, if you want to use socket file (named pipe)
+# instead of TCP/IP for your local connection here.
+# Ignored for remote connections.
+useLocalSocketFile=0
+
+# Enter local socket file name (named pipe) for your local connection here.
+# Leave it empty if you want to use default socket file.
+# Ignored for remote connections.
+localSocketFile=
+
diff --git a/kexi/tests/tableview/Makefile.am b/kexi/tests/tableview/Makefile.am
new file mode 100644
index 000000000..714b5a62e
--- /dev/null
+++ b/kexi/tests/tableview/Makefile.am
@@ -0,0 +1,16 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+bin_PROGRAMS = kexitableviewtest #kexidbfirebirdcursor
+
+noinst_PROGRAMS = kexitableviewtest
+
+INCLUDES = -I$(top_srcdir)/kexi $(all_includes)
+
+SUBDIRS = .
+
+METASOURCES = AUTO
+
+kexitableviewtest_SOURCES = main.cpp
+kexitableviewtest_LDADD = $(LIB_QT) $(LIB_KDECORE) $(top_builddir)/kexi/kexidb/libkexidb.la $(top_builddir)/kexi/widget/libkexiextendedwidgets.la $(all_libraries)
+kexitableviewtest_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(VER_INFO) --no-undefined
+
diff --git a/kexi/tests/tableview/README b/kexi/tests/tableview/README
new file mode 100644
index 000000000..e389d8e94
--- /dev/null
+++ b/kexi/tests/tableview/README
@@ -0,0 +1,3 @@
+Parser - Test for not-data-aware KexiTableView widget
+
+Usage: just run the program.
diff --git a/kexi/tests/tableview/main.cpp b/kexi/tests/tableview/main.cpp
new file mode 100644
index 000000000..90a51b5fa
--- /dev/null
+++ b/kexi/tests/tableview/main.cpp
@@ -0,0 +1,53 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+
+#include <tableview/kexitableview.h>
+
+/*#include <kexidb/drivermanager.h>
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>*/
+
+int main(int argc, char* argv[])
+{
+ KApplication app(argc, argv, "tv_test");
+ KGlobal::iconLoader()->addAppDir("kexi");
+
+ KexiTableView tv;
+
+ KexiTableViewData data;
+ KexiDB::Field f1("id",KexiDB::Field::Integer),
+ f2("name",KexiDB::Field::Text),
+ f3("age",KexiDB::Field::Integer);
+ data.addColumn( new KexiTableViewColumn(f1) );
+ data.addColumn( new KexiTableViewColumn(f2) );
+ data.addColumn( new KexiTableViewColumn(f3) );
+
+ tv.setData(&data, false);
+
+ app.setMainWidget(&tv);
+ tv.show();
+
+ return app.exec();
+}
diff --git a/kexi/tests/tableview/tableview.pro b/kexi/tests/tableview/tableview.pro
new file mode 100644
index 000000000..bd851f804
--- /dev/null
+++ b/kexi/tests/tableview/tableview.pro
@@ -0,0 +1,32 @@
+TEMPLATE = app
+
+include( $(KEXI)/common.pro )
+
+unix {
+ LIBS += -lkexidb
+}
+
+win32 {
+
+ LIBS += \
+ $$KDELIBDESTDIR/kexicore$$KEXILIB_SUFFIX \
+ $$KDELIBDESTDIR/kexidatatable$$KEXILIB_SUFFIX \
+ $$KDELIBDESTDIR/kexiextendedwidgets$$KEXILIB_SUFFIX
+
+# test specific:
+ LIBS += \
+ $$KDELIBDESTDIR/kexidb$$KEXILIB_SUFFIX \
+ $$KDELIBDESTDIR/kexiwidgets$$KEXILIB_SUFFIX
+
+#allow to select target independently from debug information
+ CONFIG += console
+ CONFIG -= windows
+}
+
+DESTDIR = .
+TARGET = tableview
+
+SOURCES = \
+main.cpp
+
+HEADERS =
diff --git a/kexi/tests/tests.pro b/kexi/tests/tests.pro
new file mode 100644
index 000000000..65a90fd1f
--- /dev/null
+++ b/kexi/tests/tests.pro
@@ -0,0 +1,6 @@
+TEMPLATE = subdirs
+
+SUBDIRS += newapi
+
+#parser
+
diff --git a/kexi/tests/widgets/Makefile.am b/kexi/tests/widgets/Makefile.am
new file mode 100644
index 000000000..83fe29f8f
--- /dev/null
+++ b/kexi/tests/widgets/Makefile.am
@@ -0,0 +1,14 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+noinst_PROGRAMS = kexidbdrivercombotest
+
+INCLUDES = -I$(top_srcdir)/kexi $(all_includes)
+
+kexidbdrivercombotest_SOURCES = kexidbdrivercombotest.cpp
+kexidbdrivercombotest_LDADD = $(LIB_QT) $(LIB_KDECORE) \
+ $(top_builddir)/kexi/kexidb/libkexidb.la \
+ $(top_builddir)/kexi/widget/libkexiextendedwidgets.la
+kexidbdrivercombotest_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+METASOURCES = AUTO
+
diff --git a/kexi/tests/widgets/kexidbdrivercombotest.cpp b/kexi/tests/widgets/kexidbdrivercombotest.cpp
new file mode 100644
index 000000000..8feb56fa3
--- /dev/null
+++ b/kexi/tests/widgets/kexidbdrivercombotest.cpp
@@ -0,0 +1,76 @@
+/***************************************************************************
+ * This file is part of the KDE project *
+ * Copyright (C) 2005 Martin Ellis <martin.ellis@kdemail.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 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. *
+ ***************************************************************************/
+
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <kdebug.h>
+#include <kcmdlineargs.h>
+#include <kapplication.h>
+#include <kinstance.h>
+
+#include <kexidb/drivermanager.h>
+#include <widget/kexidbdrivercombobox.h>
+
+/*
+ This is an example of the KexiDBDriverComboBox class, used to
+ allow the user to pick a database driver.
+
+ When run it shows two comboboxes. The top one allows the user to
+ pick any database driver. The second allows the user to pick
+ any of the drivers for database servers (i.e. it does not include
+ file based drivers).
+*/
+
+int main(int argc, char** argv)
+{
+ // Initialise the program
+ KCmdLineArgs::init(argc, argv, "kexidbcomboboxtest", "", "", "", true);
+ KApplication* app = new KApplication(true, true);
+
+ // Look for installed database drivers
+ KexiDB::DriverManager manager;
+ KexiDB::Driver::InfoMap drvs = manager.driversInfo();
+
+ // Set up a combo box and a quit widget in a new container
+ QWidget* vbox = new QWidget();
+ QVBoxLayout* vbLayout = new QVBoxLayout(vbox);
+
+ KexiDBDriverComboBox* all = new KexiDBDriverComboBox(vbox, drvs);
+ KexiDBDriverComboBox* srvOnly = new KexiDBDriverComboBox(vbox, drvs,
+ KexiDBDriverComboBox::ShowServerDrivers);
+
+ QPushButton* quit = new QPushButton("Quit", vbox);
+
+ vbLayout->addWidget(all); // Combobox listing all drivers
+ vbLayout->addWidget(srvOnly); // Combobox only drivers for DB servers
+ vbLayout->addWidget(quit);
+
+ // Show the whole lot
+ QObject::connect(quit, SIGNAL(clicked()), app, SLOT(quit()));
+ vbox->show();
+ app->exec();
+
+ delete app;
+}
+
diff --git a/kexi/tools/Makefile.am b/kexi/tools/Makefile.am
new file mode 100644
index 000000000..7c35ef5a4
--- /dev/null
+++ b/kexi/tools/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS=add_column delete_column
diff --git a/kexi/tools/add_column/Makefile.am b/kexi/tools/add_column/Makefile.am
new file mode 100644
index 000000000..74e150d12
--- /dev/null
+++ b/kexi/tools/add_column/Makefile.am
@@ -0,0 +1,5 @@
+
+bin_SCRIPTS = kexi_add_column kexi_add_column_gui
+
+message_pldir = $(kde_locale)/pl/LC_MESSAGES
+message_pl_DATA = kexi_add_column_gui_transl_pl.sh
diff --git a/kexi/tools/add_column/kexi_add_column b/kexi/tools/add_column/kexi_add_column
new file mode 100755
index 000000000..02d03de6d
--- /dev/null
+++ b/kexi/tools/add_column/kexi_add_column
@@ -0,0 +1,114 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Jaroslaw Staniek <js@iidea.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 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; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+
+usage {
+ echo "This script adds a new empty column to a table in a .kexi (SQLite 3)
+ database file without removing data from the table.
+
+Usage:
+ $0 database_name table_name new_column_name new_column_type
+ [new_column_caption]
+
+- {database_name}.old backup file is created before proceeding
+- database_name and table_name must exist
+- new_column_name must not exist and should be valid idetifier
+- new_column_type must be one of:
+ Byte, ShortInteger, Integer, BigInteger, Boolean, Date, DateTime, Time,
+ Float, Double, Text, LongText, BLOB (for images)
+- new_column_caption can be any text; enclose it in \" \" if you want to use
+ spaces there
+
+Example: to append a column 'photo' of type BLOB to the table 'cars', type
+ $0 db.kexi cars photo BLOB Photo"
+}
+
+exit_with_error {
+ rm -f "$temp_db"
+ echo $*
+ echo "Error."
+ exit 1
+}
+
+check {
+ [ -n "$*" ] && exit_with_error "$*"
+}
+
+ksqlite="ksqlite -noheader"
+
+if [ $# -lt 4 ] ; then
+ usage
+ exit 0
+fi
+database_name=$1
+table_name=$2
+new_column_name=$3
+new_column_type=$4
+new_column_caption=$5
+
+# get numeric value for the data type
+case $new_column_type in
+ Byte) typenum=1;;
+ ShortInteger) typenum=2;;
+ Integer) typenum=3;;
+ BigInteger) typenum=4;;
+ Boolean) typenum=5;;
+ Date) typenum=6;;
+ DateTime) typenum=7;;
+ Time) typenum=8;;
+ Float) typenum=9;;
+ Double) typenum=10;;
+ Text) typenum=11;;
+ LongText) typenum=12;;
+ BLOB) typenum=13;;
+ *) echo "Unknown type name '$new_column_type'"; exit 1;;
+esac
+
+temp_db=`mktemp "$database_name"XXXXXXXX` || exit_with_error
+cp "$database_name" "$temp_db" || exit_with_error
+msg=`echo "DROP TABLE '$table_name';" | $ksqlite "$temp_db"`
+check "$msg"
+
+# 1. Recreate table with new field appended
+msg=`echo ".schema '$table_name';" | $ksqlite "$database_name" | grep "^CREATE TABLE $table_name " | \
+ sed -e "s/);/, $new_column_name $new_column_type);/g" | $ksqlite "$temp_db"`
+check "$msg"
+
+# 2.1. Get table's ID
+table_id=`echo "SELECT o_id FROM kexi__objects WHERE o_type=1 AND o_name='$table_name';" | \
+ $ksqlite "$temp_db" || exit_with_error`
+
+# 2.2. Get the new field's order
+order=`echo "SELECT MAX(f_order)+1 FROM kexi__fields WHERE t_id=$table_id;" | $ksqlite "$temp_db" || exit_with_error`
+
+# 2.3. Add the new column information to kexi__fields metadata table
+msg=`echo "INSERT INTO kexi__fields (t_id, f_type, f_name, f_length, f_precision, f_constraints, \
+ f_options, f_default, f_order, f_caption, f_help) \
+ VALUES ($table_id, $typenum, '$new_column_name', \
+ 0, 0, 0, 0, NULL, $order, '$new_column_caption', NULL);" | $ksqlite "$temp_db"`
+check "$msg"
+
+# 3. Copy the old data
+msg=`echo ".dump '$table_name';" | $ksqlite "$database_name" | grep -v "^CREATE TABLE " | \
+ sed -e "s/\(^INSERT.*\));$/\\1, NULL);/g" | $ksqlite "$temp_db"`
+check "$msg"
+
+# 4. Copy the original database file to .old file and replace the original with the new one
+cp "$database_name" "$database_name.old" || exit_with_error
+mv "$temp_db" "$database_name" || exit_with_error
diff --git a/kexi/tools/add_column/kexi_add_column_gui b/kexi/tools/add_column/kexi_add_column_gui
new file mode 100644
index 000000000..29731d696
--- /dev/null
+++ b/kexi/tools/add_column/kexi_add_column_gui
@@ -0,0 +1,99 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Jaroslaw Staniek <js@iidea.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 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; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+
+basedir=`dirname "$0"`
+
+setup_messages {
+ lang=`grep Language= ~/.kde/share/config/kdeglobals | head -n 1 | \
+ sed -e 's/Language=\(.*\):.*/\1/'`
+ if [ -z "$lang" ] ; then lang="en" ; fi
+
+ IFS=:
+ for dir in `kde-config --expandvars --path locale` ; do
+ transl_file="$dir"$lang"/LC_MESSAGES/kexi_add_column_gui_transl_$lang.sh";
+ if [ -f "$transl_file" ] ; then
+ source "$transl_file"
+ break
+ else
+ transl_file=;
+ fi
+ done
+ IFS=" "
+ if [ -z "$transl_file" ] ; then
+ transl_file="$basedir/kexi_add_column_gui_transl_$lang.sh";
+ if [ ! -f "$transl_file" ] ; then source "$transl_file"; else transl_file=; fi
+ fi
+echo $transl_file
+ if [ -z "$transl_file" ] ; then
+ # default: english messages:
+ msg_filters="*.kexi|Kexi Project stored in a file
+*.*|All files"
+ msg_select_db_file="Select database file"
+ msg_enter_table_name="Table name (without spaces):"
+ msg_enter_new_column_name="New column's name (without spaces):"
+ msg_enter_new_column_type="New column's type:"
+ msg_byte="Byte"
+ msg_short_integer="Short integer"
+ msg_integer="Integer"
+ msg_big_integer="Big integer"
+ msg_yes_no="Yes/No"
+ msg_date="Date"
+ msg_date_time="Date/Time"
+ msg_time="Time"
+ msg_float="Single precision number"
+ msg_double="Double precision number"
+ msg_text="Text"
+ msg_long_text="Long text"
+ msg_object="Object (image)"
+ msg_enter_new_column_caption="New column's caption (optional):"
+ fi
+} # /setup_messages
+
+setup_messages
+
+database_name=`kdialog --title "$msg_select_db_file" --getopenfilename . "$msg_filters"` || exit 1
+
+table_name=`kdialog --inputbox "$msg_enter_table_name"` || exit 1
+
+new_column_name=`kdialog --inputbox "$msg_enter_new_column_name"` || exit 1
+
+new_column_type=`kdialog --radiolist "$msg_enter_new_column_type " \
+Byte "$msg_byte" off \
+ShortInteger "$msg_short_integer" off \
+Integer "$msg_integer" off \
+BigInteger "$msg_big_integer" off \
+Boolean "$msg_yes_no" off \
+Date "$msg_date" off \
+DateTime "$msg_date_time" off \
+Time "$msg_time" off \
+Float "$msg_float" off \
+Double "$msg_double" off \
+Text "$msg_text" off \
+LongText "$msg_long_text" off \
+BLOB "$msg_object" off ` || exit 1
+new_column_caption=`kdialog --inputbox "$msg_enter_new_column_caption"`
+
+msg=`sh kexi_add_column "$database_name" "$table_name" "$new_column_name" \
+ "$new_column_type" "$new_column_caption" 2>&1`
+
+[ -z "$msg" ] && exit 0
+
+kdialog --error "$msg"
+exit 1
diff --git a/kexi/tools/add_column/kexi_add_column_gui_transl_pl.sh b/kexi/tools/add_column/kexi_add_column_gui_transl_pl.sh
new file mode 100644
index 000000000..974b79cfa
--- /dev/null
+++ b/kexi/tools/add_column/kexi_add_column_gui_transl_pl.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Translation file for kexi_add_column_gui
+
+msg_filters="*.kexi|Projekt Kexi zapisany w pliku
+*.*|Wszystkie pliki"
+msg_select_db_file="Wybierz plik bazy danych"
+msg_enter_table_name="Nazwa tabeli (bez spacji):"
+msg_enter_new_column_name="Nazwa nowej kolumny (bez spacji):"
+msg_enter_new_column_type="Typ nowej kolumny:"
+msg_byte="Bajt"
+msg_short_integer="Liczba calkowita krotka"
+msg_integer="Liczba calkowita"
+msg_big_integer="Liczba calkowita wielka"
+msg_yes_no="Tak/Nie"
+msg_date="Data"
+msg_date_time="Data/Czas"
+msg_time="Czas"
+msg_float="Liczba zmiennoprzecinkowa krotka"
+msg_double="Liczba zmiennoprzecinkowa dluga"
+msg_text="Tekst"
+msg_long_text="Dlugi tekst"
+msg_object="Obiekt (obraz)"
+msg_enter_new_column_caption="Tytul nowej kolumny (opcjonalnie):"
diff --git a/kexi/tools/build_tarball/build_kexi_tarball.sh b/kexi/tools/build_tarball/build_kexi_tarball.sh
new file mode 100755
index 000000000..6e5a6f342
--- /dev/null
+++ b/kexi/tools/build_tarball/build_kexi_tarball.sh
@@ -0,0 +1,232 @@
+#!/bin/sh
+#
+# This script prepares & uploads tarballs
+# Usage within KDE svn tree.
+# Based on cvs2pack
+# Copyright 2002 Nikolas Zimmermann <wildfox@kde.org>
+# Copyright 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+# Copyright 2005 Martin Ellis <martin.ellis@kdemail.net>
+# License: GPL (http://www.gnu.org/)
+
+DIRNAME=`pwd`/`dirname $0`
+cd $DIRNAME
+
+# General
+MODULE=koffice
+PROJECT=kexi
+PROJECT_TITLE=Kexi
+MODULE_PATH=trunk/$MODULE
+
+# Admin from branch as trunk in for KDE4.
+# Bump to 3.5 when that branch is stable?
+KDESOURCEDIR=branches/KDE/3.5
+KDEADMIN_PATH=$KDESOURCEDIR/kde-common/admin
+
+PROJECT_PATH=koffice/kexi
+#For versions in a branch:
+PROJECT_VER=1.0
+#PROJECT_PATH=branches/kexi/$PROJECT_VER
+SVN2DIST_OPTIONS=--no-i18n #set --no-i18n for version being outside the trunk
+
+# Make sure these are all directories - see Makefile creation below
+EXC="3rdparty/uuid plugins/importwizard scriptingcore scriptingplugins"
+
+# Uploading
+UPLOAD_USER=user # CHANGE
+UPLOAD_PASS=pass # CHANGE
+UPLOAD_HOST=host # CHANGE
+DO_UPLOAD=0 # 1 = Yes; 0 = No
+
+# SVN'ing
+SVN_USER=staniek # CHANGE
+SVN_PASS= # CHANGE
+SVN_HOST=https://svn.kde.org/home/kde
+
+# Dist-settings
+# set empty if this is snapshot
+DIST_VER=`grep "# define KEXI_VERSION_STRING" ../../kexi_version.h | \
+ sed -e 's/.*\"\(.*\)\"/\1/;s/ //g;y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/;'`
+
+# Pick kexi_stable.lsm or kexi.lsm as a LSM file template
+echo $DIST_VER | grep -e "beta" -e "rc" \
+ > /dev/null && lsmsrcfile=kexi.lsm || lsmsrcfile=kexi_stable.lsm
+
+fixAppSpecific()
+{
+ rm -f ../changes-* #remove koffice-specific changelog
+ mv CHANGES ../
+ echo "For complete list of authors see kexi/main/kexiaboutdata.h file or use \"Help->About Kexi\" menu command." > ../AUTHORS
+}
+
+# Paths
+mkdir -p /tmp/kexi-dist
+DESTINATION=/tmp/kexi-dist/$DIST_VER # CHANGE TO A NON-EXISTING DIR, WHICH WILL BE CREATED LATER!!!
+CONFIGURE_PREFIX=`kde-config --prefix` # CHANGE
+EXPECT_PROGRAM=`which expect` # CHANGE
+ED_LOCATION=/bin # CHANGE
+
+# Work around for stupid Subversion bug on Martin's machine
+SVN_PROGRAM="strace -o /dev/null "`which svn`
+
+DATE_PROGRAM=`which date` # CHANGE
+PERL_PROGRAM=`which perl` # CHANGE
+SVN2DIST_PROGRAM=`which svn2dist` # CHANGE
+test -z $SVN2DIST_PROGRAM && exit 1
+NCFTPPUT_PROGRAM=`which ncftpput` # CHANGE
+CONF= # CANGE OR LEAVE EMPTY IF NO SPECIAL configure.in.in should be copied
+
+# --- end of configuration area ---
+
+# Main program
+if [ -z "$DIST_VER" ]; then
+ today=`$DATE_PROGRAM +%d-%m-%y`
+ snapshot="-snapshot"
+else
+ today=$DIST_VER
+ snapshot=
+fi
+
+echo "*** Creating $PROJECT-$today$snaphshot ***"
+
+if ! [ -e $DESTINATION ]; then
+ echo "*** Setup-Mode: Creating..."
+ echo " -> $DESTINATION..."
+ mkdir $DESTINATION
+ echo " -> $DESTINATION/source..."
+ mkdir $DESTINATION/source
+ echo " -> $DESTINATION/archive..."
+ mkdir $DESTINATION/archive
+fi
+
+if [ -e $DESTINATION ]; then
+# rm -f $DESTINATION/expect_script
+
+# touch $DESTINATION/expect_script
+# chmod +x $DESTINATION/expect_script
+
+# echo "#!$EXPECT_PROGRAM
+# spawn $SVN_PROGRAM -d :pserver:$SVN_USER@$CVS_HOST:/home/kde login
+# expect -re \"SVN password: \"
+# send \"$SVN_PASS\\r\"
+# expect eof" >> $DESTINATION/expect_script
+
+ rm -f $DESTINATION/LOG
+fi
+
+echo $DESTINATION/source/$MODULE
+if ! [ -e $DESTINATION/source/$MODULE ]; then
+ echo "*** Setup-Mode: Checking $MODULE/$PROJECT out from SVN..."
+ cd $DESTINATION/source
+
+ # Get admin dir
+ echo "$SVN_PROGRAM co $SVN_HOST/$KDEADMIN_PATH" >> $DESTINATION/LOG
+ $SVN_PROGRAM co $SVN_HOST/$KDEADMIN_PATH >> $DESTINATION/LOG 2>&1 || exit 1
+
+ # Get KOffice top-level dir
+ echo "$SVN_PROGRAM co -N $SVN_HOST/$MODULE_PATH" >> $DESTINATION/LOG
+ $SVN_PROGRAM co -N $SVN_HOST/$MODULE_PATH >> $DESTINATION/LOG 2>&1 || exit 1
+
+ # Get KoProperty, KOffice{Core|UI} and KROSS from lib
+ echo "cd $MODULE && $SVN_PROGRAM up -N lib ; cd .. " >> $DESTINATION/LOG
+ (cd $MODULE && $SVN_PROGRAM up -N lib ; cd .. ) >> $DESTINATION/LOG 2>&1 || exit 1
+ echo "cd $MODULE/lib && $SVN_PROGRAM up koproperty ; cd .." >> $DESTINATION/LOG
+ (cd $MODULE/lib && $SVN_PROGRAM up koproperty ; cd ..) >> $DESTINATION/LOG 2>&1 || exit 1
+ echo "cd $MODULE/lib && $SVN_PROGRAM up kofficecore ; cd .." >> $DESTINATION/LOG
+ (cd $MODULE/lib && $SVN_PROGRAM up kofficecore ; cd ..) >> $DESTINATION/LOG 2>&1 || exit 1
+ echo "cd $MODULE/lib && $SVN_PROGRAM up kofficeui ; cd .." >> $DESTINATION/LOG
+ (cd $MODULE/lib && $SVN_PROGRAM up kofficeui ; cd ..) >> $DESTINATION/LOG 2>&1 || exit 1
+ echo "cd $MODULE/lib && $SVN_PROGRAM up kross ; cd .." >> $DESTINATION/LOG
+ (cd $MODULE/lib && $SVN_PROGRAM up kross ; cd ..) >> $DESTINATION/LOG 2>&1 || exit 1
+
+ # Get Kexi
+ echo "cd $MODULE && $SVN_PROGRAM up $PROJECT ; cd .." >> $DESTINATION/LOG
+ (cd $MODULE && $SVN_PROGRAM up $PROJECT ; cd ..) >> $DESTINATION/LOG 2>&1 || exit 1
+
+ #mv $PROJECT_VER/$MODULE/$PROJECT $MODULE/ || exit 1
+ #rm -rf $PROJECT_VER || exit 1
+
+ cd $MODULE || exit 1
+
+ ln -s ../admin admin
+ cd ..
+fi
+
+
+echo "1. Cleaning up..."
+cd $DESTINATION/source/$MODULE || exit 1
+rm -fr acinclude.m4 aclocal.m4 Makefile.in Makefile libtool $PROJECT/config.h \
+config.h.in stamp-h.in subdirs configure configure.in configure.files stamp-h \
+inst-apps autom4te-2.5x.cache autom4te.cache .autoconf_trace MakeVars.in \
+Makefile.am Makefile.rules.in
+
+find . -name \*~ | xargs rm -f
+
+
+echo "2. Updating from SVN..."
+#$DESTINATION/expect_script >> $DESTINATION/LOG 2>&1
+cd admin || exit 1
+$SVN_PROGRAM up >> $DESTINATION/LOG 2>&1 || exit 1
+cd ../kexi || exit 1
+$SVN_PROGRAM up >> $DESTINATION/LOG 2>&1 || exit 1
+
+
+cd $DESTINATION/archive || exit 1
+rm -f *
+#rm -f $DESTINATION/expect_script
+
+echo "3. Makefile creation..."
+export UNSERMAKE="no"
+if [ -n "$CONF" ]; then
+ cp $CONF $DESTINATION/source/$MODULE
+fi
+cd $DESTINATION/source/$MODULE/$PROJECT
+
+for dir in $EXC; do
+ rm -rf $dir
+ # Avoid Makefile.cvs complaining about missing dirs
+ mkdir $dir
+done
+#fix exectutable bits for sources:
+find . -name \*.h -o -name \*.cpp -o -name \*.c -o -name \*.cc -o -name \*.1 | xargs chmod a-x
+
+#other app-specific fixes:
+fixAppSpecific
+
+#--svn2dist will do this
+#cd $DESTINATION/source/$MODULE
+#make -f Makefile.cvs >> $DESTINATION/LOG 2>&1
+#cd ..
+cd $DESTINATION/source
+echo "4. Building tarballs..."
+$SVN2DIST_PROGRAM $MODULE $PROJECT -v $today \
+ --svn-root "$SVN_HOST/trunk" \
+ $SVN2DIST_OPTIONS \
+ --log="$DESTINATION/LOG.SVN2DIST" >> $DESTINATION/LOG 2>&1 || exit 1
+cat "svn2dist-$PROJECT.log" >> $DESTINATION/LOG
+rm -Rf $PROJECT-$today
+rm -f $PROJECT-$today.tar
+mv $PROJECT-$today.* ../archive
+cd ../archive
+
+#create .lsm file
+lsmfile=$PROJECT"-"$DIST_VER".lsm"
+echo "Begin4
+Title: $PROJECT_TITLE
+Version: "$DIST_VER"
+Entered-date: "`date +%Y-%m-%d` > $lsmfile
+cat $DIRNAME/$lsmsrcfile >> $lsmfile
+
+if [ -n "$snapshot" ] ; then
+ mv $PROJECT-$today.tar.bz2 $PROJECT-$today$snapshot.tar.bz2
+ mv $PROJECT-$today.tar.gz $PROJECT-$today$snapshot.tar.gz
+fi
+
+if [ $DO_UPLOAD -eq 1 ]; then
+ echo "5. Uploading tarballs..."
+ $NCFTPPUT_PROGRAM -u $UPLOAD_USER -p $UPLOAD_PASS $UPLOAD_HOST $PROJECT $DESTINATION/archive/* >> $DESTINATION/LOG 2>&1
+ cd ..
+ echo "6. Done!"
+ exit
+fi
+
+echo "5. Won't upload. Done!"
diff --git a/kexi/tools/build_tarball/kexi.lsm b/kexi/tools/build_tarball/kexi.lsm
new file mode 100644
index 000000000..98399347f
--- /dev/null
+++ b/kexi/tools/build_tarball/kexi.lsm
@@ -0,0 +1,10 @@
+Description: Integrated data management application
+Keywords: database KDE KOffice desktop Qt
+Author: http://www.kexi-project.org (Kexi Team)
+Maintained-by: js@iidea.pl (Jaroslaw Staniek)
+Primary-site: http://download.kde.org/download.php?url=unstable/apps/KDE3.x/database/
+Home-Page: http://www.kexi-project.org
+Original-site: None
+Platforms: Unix, Qt
+Copying-policy: LGPL
+End \ No newline at end of file
diff --git a/kexi/tools/delete_column/Makefile.am b/kexi/tools/delete_column/Makefile.am
new file mode 100644
index 000000000..1f019ae88
--- /dev/null
+++ b/kexi/tools/delete_column/Makefile.am
@@ -0,0 +1,5 @@
+
+bin_SCRIPTS = kexi_delete_column kexi_delete_column_gui
+
+message_pldir = $(kde_locale)/pl/LC_MESSAGES
+message_pl_DATA = kexi_delete_column_gui_transl_pl.sh
diff --git a/kexi/tools/delete_column/README b/kexi/tools/delete_column/README
new file mode 100644
index 000000000..3e263591b
--- /dev/null
+++ b/kexi/tools/delete_column/README
@@ -0,0 +1,17 @@
+== "Delete column" utility for Kexi ==
+
+This script deletes a single table column from a .kexi (SQLite 3)
+database file without removing data from the table.
+
+Rationale:
+Kexi 1.x does not offer deleting columns from a table
+without removing its data. The kexi_delete_column script does exactly this.
+
+Usage:
+Type kexi_delete_column for list of arguments.
+
+GUI tool:
+There is also a GUI tool 'kexi_delete_column_gui' that is basically
+a wrapper running on top of kexi_delete_column
+The tool works without arguments - it uses KDE dialogs instead ('kdialog').
+
diff --git a/kexi/tools/delete_column/kexi_delete_column b/kexi/tools/delete_column/kexi_delete_column
new file mode 100755
index 000000000..22566c618
--- /dev/null
+++ b/kexi/tools/delete_column/kexi_delete_column
@@ -0,0 +1,136 @@
+#!/bin/sh
+#
+# Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.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 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; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+
+usage {
+ echo "This script deletes a single table column from a .kexi (SQLite 3)
+database file without removing data from the table.
+
+Usage:
+ $0 database_name table_name column_name
+
+- {database_name}.old backup file is created before proceeding
+- database_name, table_name, column_name must exist
+- note that queries, forms and other objects referencing
+ to the altered table can become invalid and have to be
+ fixed by hand
+
+Example: to delete 'price' column from table 'products', type
+ $0 db.kexi products price"
+}
+
+exit_with_error {
+ rm -f "$temp_db"
+ echo $*
+ echo "Error."
+ exit 1
+}
+
+check {
+ [ -n "$*" ] && exit_with_error "$*"
+}
+
+ksqlite="ksqlite -noheader"
+ksqlite_header="ksqlite -header"
+
+if [ $# -lt 3 ] ; then
+ usage
+ exit 0
+fi
+database_name=$1
+table_name=$2
+column_name=$3
+
+temp_db=`mktemp "$database_name"XXXXXXXX` || exit_with_error
+cp "$database_name" "$temp_db" || exit_with_error
+
+# 1. alter the table physically
+
+prepare_new_create_table_statement {
+ # possible problems: typename ( number , number ) may contain ","
+
+ schema=`echo ".schema '$table_name';" | $ksqlite "$database_name" | \
+ grep "^CREATE TABLE $table_name " | \
+ sed -e "s/[^(]*(\(.*\));/\1/" || exit_with_error`
+
+ IFS=","
+ for coldef in $schema ; do
+ col=`echo $coldef | sed "s/^[ ]*\([^ ]*\) .*$/\1/"`
+ if [ "$col" != "$column_name" ] ; then
+ echo -n ,$coldef
+ fi
+ done | cut -c2-
+ IFS=" "
+}
+
+get_sql_column_names {
+ names=`$ksqlite_header "$temp_db" "SELECT * FROM '$temp_table_name' LIMIT 1;" | \
+ head -n 1 || exit_with_error`
+ IFS="|"
+ for col in $names ; do
+ if [ "$col" != "$column_name" ] ; then
+ echo -n ", $col"
+ fi
+ done | cut -c3-
+ IFS=" "
+}
+
+# 1.1. rename the original table to a temp name
+temp_table_name=`mktemp "$table_name"XXXXXXXX`
+msg=`$ksqlite "$temp_db" "ALTER TABLE '$table_name' RENAME TO '$temp_table_name';"`
+check "$msg"
+
+# 1.2. create a new table without the removed column and copy the data
+new_create_table_statement=`prepare_new_create_table_statement`
+msg=`$ksqlite "$temp_db" "CREATE TABLE '$table_name' ($new_create_table_statement);"`
+check "$msg"
+
+sql_column_names=`get_sql_column_names`
+msg=`$ksqlite "$temp_db" "INSERT INTO '$table_name' SELECT $sql_column_names FROM '$temp_table_name';"`
+check "$msg"
+
+# 1.3. drop the temporary table
+msg=`$ksqlite "$temp_db" "DROP TABLE '$temp_table_name';"`
+check "$msg"
+
+
+# 2. alter information in the kexi__fields system table (schema)
+
+# 2.1. Get table's ID
+table_id=`$ksqlite "$temp_db" "SELECT o_id FROM kexi__objects WHERE o_type=1 AND o_name='$table_name';" || exit_with_error`
+
+# 2.1. Get column's number
+column_order=`$ksqlite "$temp_db" "SELECT f_order FROM kexi__fields WHERE t_id=$table_id AND f_name='$column_name';" || exit_with_error`
+
+$ksqlite "$temp_db" "DELETE FROM kexi__fields WHERE t_id=$table_id AND f_name='$column_name';"
+
+for fname in `$ksqlite "$temp_db" \
+ "SELECT f_name FROM kexi__fields WHERE t_id=$table_id AND f_order>=$column_order ORDER BY f_order DESC;"` ; do
+ msg=`$ksqlite "$temp_db" "UPDATE kexi__fields SET f_order=$column_order WHERE t_id=$table_id AND f_name='$fname';"`
+ check "$msg"
+ column_order=`expr $column_order + 1`
+done
+
+# 3. Copy the original database file to .old file and replace
+# the original with the new one
+cp "$database_name" "$database_name.old" || exit_with_error
+mv "$temp_db" "$database_name" || exit_with_error
+
+exit 1
+
diff --git a/kexi/tools/delete_column/kexi_delete_column_gui b/kexi/tools/delete_column/kexi_delete_column_gui
new file mode 100755
index 000000000..0a87091c8
--- /dev/null
+++ b/kexi/tools/delete_column/kexi_delete_column_gui
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.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 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; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+
+basedir=`dirname "$0"`
+
+setup_messages {
+ lang=`grep Language= ~/.kde/share/config/kdeglobals | head -n 1 | \
+ sed -e 's/Language=\(.*\):.*/\1/'`
+ if [ -z "$lang" ] ; then lang="en" ; fi
+
+ IFS=:
+ for dir in `kde-config --expandvars --path locale` ; do
+ transl_file="$dir"$lang"/LC_MESSAGES/kexi_delete_column_gui_transl_$lang.sh";
+ if [ -f "$transl_file" ] ; then
+ source "$transl_file"
+ break
+ else
+ transl_file=;
+ fi
+ done
+ IFS=" "
+ if [ -z "$transl_file" ] ; then
+ transl_file="$basedir/kexi_delete_column_gui_transl_$lang.sh";
+ if [ -f "$transl_file" ] ; then source "$transl_file"; else transl_file=; fi
+ fi
+echo $transl_file
+ if [ -z "$transl_file" ] ; then
+ # default: english messages:
+ msg_filters="*.kexi|Kexi Project stored in a file
+*.*|All files"
+ msg_select_db_file="Select database file"
+ msg_enter_table_name="Table name (not caption):"
+ msg_enter_column_name="Column name (not caption):"
+ fi
+} # /setup_messages
+
+setup_messages
+
+ksqlite="ksqlite -noheader"
+
+database_name=`kdialog --title "$msg_select_db_file" --getopenfilename . "$msg_filters"` || exit 1
+
+table_name=`kdialog --inputbox "$msg_enter_table_name"` || exit 1
+
+# show list of columns and ask for one
+
+msg=`$ksqlite "$database_name" "SELECT f_name FROM kexi__objects o, kexi__fields f WHERE o.o_id=f.t_id AND o.o_name='$table_name' AND o_type=1 ORDER BY f_order;" || exit 1`
+
+command_file=`mktemp "$database_name"XXXXXXXX`".sh"
+echo -n "kdialog --radiolist \"$msg_enter_column_name\"" > $command_file
+echo $msg | while read f ; do
+ echo -n " $f $f off" >> $command_file
+done
+
+column_name=`. $command_file || exit 1`
+rm -f $command_file
+
+# call the command line tool
+
+msg=`sh kexi_delete_column "$database_name" "$table_name" "$column_name" 2>&1`
+
+[ -z "$msg" ] && exit 0
+
+kdialog --error "$msg"
+exit 1
diff --git a/kexi/tools/delete_column/kexi_delete_column_gui_transl_pl.sh b/kexi/tools/delete_column/kexi_delete_column_gui_transl_pl.sh
new file mode 100644
index 000000000..ba46b9492
--- /dev/null
+++ b/kexi/tools/delete_column/kexi_delete_column_gui_transl_pl.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Translation file for kexi_delete_column_gui
+
+msg_filters="*.kexi|Projekt Kexi zapisany w pliku
+*.*|Wszystkie pliki"
+
+msg_select_db_file="Wybierz plik bazy danych"
+msg_enter_table_name="Nazwa tabeli (nie tytuł):"
+msg_enter_column_name="Nazwa kolumny (nie tytuł):"
diff --git a/kexi/tools/feedback/create_kexifeedback.sh b/kexi/tools/feedback/create_kexifeedback.sh
new file mode 100644
index 000000000..074b5e3f5
--- /dev/null
+++ b/kexi/tools/feedback/create_kexifeedback.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+# Small, simple and stupid script.
+# It grabs a local copy of kfeedbackwizard from anonsvn
+# and refactors KFeedbackWizard to KexiFeedbackWizard.
+# The files will be placed in the correct directory
+# so one can immediately use them.
+#
+# Copyright(C) 2005 by Christian Nitschkowski <segfault_ii@web.de>
+
+cd ../../3rdparty
+[ -d kexifeedbackwizard ] && echo "kexifeedbackwizard/ already exists: giving up" && exit 0
+
+echo Fetching kfeedbackwizard from anonsvn...
+
+svn checkout svn://anonsvn.kde.org/home/kde/trunk/playground/utils/kfeedbackwizard
+
+echo Refactoring KFeedbackWizard to KexiFeedbackWizard...
+
+cd kfeedbackwizard
+rm -rf .svn
+rm -rf po/.svn
+rm -rf lib/.svn
+cat configure.in.in | sed -e s/kfeedbackwizard/kexifeedbackwizard/ >configure.in.in.tmp
+mv configure.in.in.tmp configure.in.in
+cat Makefile.am | sed -e "s/SUBDIRS = po lib src/SUBDIRS = po lib/" >Makefile.am.tmp
+mv Makefile.am.tmp Makefile.am
+rm po/kfeedbackwizard.pot
+rm -rf src
+rm -rf templates
+rm kfeedbackwizard.kdevelop
+rm Doxyfile
+rm INSTALL
+rm NEWS
+cd lib
+for i in `ls -1 kfeedback*`; do
+ cat ${i} | sed -e "s/KFeedback/KexiFeedback/g;s/KFEEDBACK/KEXIFEEDBACK/g;s/kfeedback/kexifeedback/g" \
+ >$(echo ${i} | sed -e s/kfeedback/kexifeedback/)
+ rm ${i}
+done
+cat Makefile.am | sed -e s/kfeedback/kexifeedback/g >Makefile.am.tmp
+mv Makefile.am.tmp Makefile.am
+cd ../..
+mv kfeedbackwizard kexifeedbackwizard
+
+# Test if Makefile.am already contains a reference to the feedbackdir
+# If it's already there, stop here
+cat Makefile.am | grep FEEDBACKDIR && echo Done && cd ../tools/feedback && exit 0
+echo Adding kexifeedbackwizard to Makefile.am...
+echo -e "if use_kexifeedback\nFEEDBACKDIR = kexifeedbackwizard\nendif\n" >Makefile.am.tmp
+cat Makefile.am | sed -e "s/SUBDIRS = */SUBDIRS = \$(FEEDBACKDIR) /" >>Makefile.am.tmp
+mv Makefile.am Makefile.am.nofw
+mv Makefile.am.tmp Makefile.am
+cd ../tools/feedback
+echo Done
diff --git a/kexi/tools/sql_keywords/Makefile b/kexi/tools/sql_keywords/Makefile
new file mode 100644
index 000000000..353b5b1d0
--- /dev/null
+++ b/kexi/tools/sql_keywords/Makefile
@@ -0,0 +1,30 @@
+KEXI_SQL_KEYWORDS=../../kexidb/keywords.cpp
+MYSQL_KEYWORDS=../../kexidb/drivers/mySQL/mysqlkeywords.cpp
+SQLITE3_KEYWORDS=../../kexidb/drivers/sqlite/sqlitekeywords.cpp
+PQXX_KEYWORDS=../../kexidb/drivers/pqxx/pqxxkeywords.cpp
+
+all: build
+
+build:
+ ./sql_keywords.sh
+
+install: ${KEXI_SQL_KEYWORDS} ${SQLITE3_KEYWORDS} \
+ ${MYSQL_KEYWORDS} ${PQXX_KEYWORDS}
+
+${MYSQL_KEYWORDS}: mysqlkeywords.cpp
+ cp mysqlkeywords.cpp ${MYSQL_KEYWORDS}
+
+
+${KEXI_SQL_KEYWORDS}: keywords.cpp
+ cp keywords.cpp ${KEXI_SQL_KEYWORDS}
+
+
+${SQLITE3_KEYWORDS}: sqlitekeywords.cpp
+ cp sqlitekeywords.cpp ${SQLITE3_KEYWORDS}
+
+${PQXX_KEYWORDS}: pqxxkeywords.cpp
+ cp pqxxkeywords.cpp ${PQXX_KEYWORDS}
+
+clean:
+ rm -f *.new *.all *.cpp *~
+.PHONY: clean kexi
diff --git a/kexi/tools/sql_keywords/kexi_reserved b/kexi/tools/sql_keywords/kexi_reserved
new file mode 100644
index 000000000..7b6a9d2e6
--- /dev/null
+++ b/kexi/tools/sql_keywords/kexi_reserved
@@ -0,0 +1,58 @@
+# This file contains keywords that are resevered for use in Kexi SQL
+# They should be escaped when used as identifiers in databases.
+AFTER
+ALL
+ASC
+BEFORE
+BEGIN
+BETWEEN
+BY
+CASCADE
+CASE
+CHECK
+COLLATE
+COMMIT
+CONSTRAINT
+CROSS
+DATABASE
+DEFAULT
+DELETE
+DESC
+DISTINCT
+DROP
+END
+ELSE
+EXPLAIN
+FOR
+FOREIGN
+FULL
+GROUP
+HAVING
+IGNORE
+INDEX
+INNER
+INSERT
+INTO
+KEY
+LIMIT
+MATCH
+NATURAL
+OFFSET
+ORDER
+OUTER
+PRIMARY
+REFERENCES
+REPLACE
+RESTRICT
+ROLLBACK
+ROW
+SET
+TEMPORARY
+THEN
+TRANSACTION
+UNION
+UNIQUE
+UPDATE
+USING
+VALUES
+WHEN
diff --git a/kexi/tools/sql_keywords/sql_keywords.sh b/kexi/tools/sql_keywords/sql_keywords.sh
new file mode 100755
index 000000000..6a6cb003b
--- /dev/null
+++ b/kexi/tools/sql_keywords/sql_keywords.sh
@@ -0,0 +1,299 @@
+#!/bin/bash
+################################################################################
+# sql_keywords.sh
+#
+# Generate sets of driver-specific keywords.
+# This program generates files that can be used as part of KexiDB drivers
+# that list keywords specific to that driver, i.e. words that have to be
+# escaped if they are to be used as identifiers in the database.
+#
+# It extracts keywords from the lexer of the DB sources, deletes keywords that
+# are already going to be escaped because they are part of Kexi's SQL dialect,
+# and writes the resulting keywords to a "char *keywords[]" construct in a .cpp
+# file that can then be used in the driver.
+#
+# To use:
+# Put the DB source tarballs (e.g. mysql-4.1.7.tar.gz,
+# postgresql-base-7.4.6.tar.gz) in the current directory
+# then run. (Makefile provided for installs)
+#
+# Sed, awk, grep have been used without much thought -
+# CHECK THE OUTPUT BEFORE INCLUDING IT IN A DRIVER!
+#
+# Martin Ellis <martin.ellis@kdemail.net>
+# 11/2004
+
+set -e
+progname="sql_keywords.sh"
+
+################################################################################
+# C++ file generator
+# params : array - scoped name of the array to generate
+# include - a file to include (or "" if none)
+# inFile - file containing raw keywords
+# outfile - file to write
+header () {
+ local array="$1"
+ local include="$2"
+ local inFile="$3"
+ local outFile="$4"
+ echo "Writing keywords in $inFile to $outFile"
+ cat <<EOF1 > "$outFile";
+ /*
+ * This file has been automatically generated from
+ * koffice/kexi/tools/sql_keywords/$progname and
+ * $inFile.
+ *
+ * Please edit the $progname, not this file!
+ */
+EOF1
+ if [ "$include" != "" ] ; then
+ echo "#include <$include>" >> "$outFile"
+ fi
+ cat <<EOF2 >> "$outFile";
+
+namespace KexiDB {
+ const char* ${array}[] = {
+EOF2
+}
+
+body() {
+ local inFile="$1"
+ local outFile="$2"
+ awk '/^[a-zA-Z_0-9]*/ { print "\t\t\""$$1"\","; } ' "$inFile" >> "$outFile"
+}
+
+footer() {
+ local outFile="$1"
+ cat <<EOF >> "$outFile";
+ 0
+ };
+}
+EOF
+
+}
+
+################################################################################
+# Keyword comparison functions
+# Globals: keywords
+
+# readKeywords
+# params: filename - file of keywords to read
+# sets: keywords - array of keywords in the file
+readKeywords () {
+ local filename="$1"
+ local kexiSQL="$2"
+ i=0
+ while read keyword ; do
+ keywords[$i]="$keyword"
+ (( i++ ))
+ done < "$filename"
+}
+
+# compareKeywords
+# reads: kexiSQL -
+# driverSQL
+# sets: keywords - driver keywords that are not keywords in Kexi
+compareKeywords () {
+ numFound=0
+ for(( i=0; i < ${#driverSQL[@]}; i++ )) ; do
+ found="no"
+ for(( j=0; j < ${#kexiSQL[@]}; j++ )) ; do
+ if [ "${driverSQL[$i]}" == "${kexiSQL[$j]}" ] ; then
+ found="yes"
+ fi
+ done
+ if [ "$found" == "no" ] ; then
+ keywords[$numFound]="${driverSQL[$i]}"
+ (( numFound++ ))
+ fi
+ done
+}
+
+
+# getDriverKeywords
+# params : kexi -
+# driver -
+# outFile -
+getDriverKeywords () {
+ local kexi="$1"
+ local driver="$2"
+ local outFile="$3"
+
+ declare -a kexiSQL
+ declare -a driverSQL
+
+ echo "Looking for driver-specific keywords in \"$driver\""
+ readKeywords $kexi
+ for(( i=0; i < ${#keywords[@]}; i++ )) ; do
+ kexiSQL[$i]=${keywords[$i]}
+ done
+ unset keywords
+
+ readKeywords $driver
+ for(( i=0; i < ${#keywords[@]}; i++ )) ; do
+ driverSQL[$i]=${keywords[$i]}
+ done
+ unset keywords
+
+ compareKeywords
+ echo "Writing driver-specific keywords for \"$driver\" to \"$outFile\""
+ rm -f $outFile
+ for(( i=0; i < ${#keywords[@]}; i++ )) ; do
+ echo ${keywords[$i]} >> $outFile
+ done
+ unset keywords
+}
+################################################################################
+
+
+################################################################################
+# Kexi lexer
+
+checkKexiKeywords () {
+ local scanner="../../kexidb/parser/sqlscanner.l"
+ if [ ! -r kexi.all -o "$scanner" -nt "kexi.all" ] ; then
+ echo "Getting Kexi keywords"
+ grep '^(\?"[a-zA-Z_0-9]' "$scanner" | \
+ sed 's/(\?"\([^"]*\)"[^"]*/\1\n/g' | \
+ awk '/^[a-zA-Z_0-9]+$/ {print $1;}' |
+ sort | uniq > "kexi.all"
+ awk '/^[a-zA-Z_0-9]+$/ {print $1;}' kexi_reserved >> "kexi.all"
+ fi
+}
+
+################################################################################
+# DB lexer functions
+# These functions munge the extracted lexers from DBs and write the collected
+# keywords to file
+
+# getSQLiteKeywords
+# params : inFile - SQLite3 lexer file
+# outFile - all SQLite3 keywords
+getSQLiteKeywords () {
+ local inFile="$1"
+ local outFile="$2"
+
+ echo "Getting SQLite keywords ($inFile -> $outFile)"
+ sed -n '/^static Keyword aKeywordTable/,/};/p' $inFile | \
+ awk '/ { "[a-zA-Z_0-9]*"/ { print $2;}' | \
+ sed 's/"\(.*\)".*/\1/g' > $outFile
+}
+
+getPostgreSQLKeywords () {
+ local inFile="$1"
+ local outFile="$2"
+
+ echo "Getting PostgreSQL keywords ($inFile -> $outFile)"
+ sed -n '/^static const ScanKeyword ScanKeywords/,/};/p' $inFile | \
+ awk '/\t{"[a-zA-Z_0-9]*"/ { print $1;}' | \
+ sed 's/.*"\(.*\)".*/\1/g' | tr 'a-z' 'A-Z' > $outFile
+}
+
+# getMySQLKeywords
+# params : inFile - MySQL lexer file
+# outFile - all MySQL keywords
+getMySQLKeywords () {
+ local inFile="$1"
+ local outFile="$2"
+
+ echo "Getting MySQL keywords ($inFile -> $outFile)"
+ sed -n '/^static SYMBOL symbols/,/};/p' $inFile | \
+ awk '/ { "[a-zA-Z_0-9]*"/ { print $2;}' | \
+ sed 's/"\(.*\)".*/\1/g' > $outFile
+}
+
+################################################################################
+# DB tarball functions
+# These functions extract the lexer files from the DB source tarballs
+
+# checkExtracted
+# params : tarball - tarball containing backend DB source
+# file - file in tarball containing DB's lexer
+checkExtracted () {
+ local tarball="$1"
+ local file="$2"
+
+ if [ ! -r "$file" ] ; then
+ echo "Getting file \"$file\" from \"$tarball\""
+ tar -zxf "$tarball" "$file"
+ fi
+}
+
+# checkTarballs
+checkTarballs () {
+ local pathInTar
+ local appName
+ local appVer
+
+ # SQLite (native DB backend) keywords
+ appName="SQLite"
+ appVer=sqlite
+ inFile="../../3rdparty/kexisql3/src/tokenize.c"
+ filePrefix="sqlite"
+ if [ ! -r "$appVer.all" ] || [ ! -r "$appVer.new" ] ; then
+ getSQLiteKeywords "$inFile" "$appVer.all"
+ fi
+ if [ "$appVer.all" -nt "$appVer.new" ] ; then
+ getDriverKeywords "kexi.all" "$appVer.all" "$appVer.new"
+ header "${appName}Driver::keywords" "${filePrefix}driver.h" "$inFile" "${filePrefix}keywords.cpp"
+ body "$appVer.new" "${filePrefix}keywords.cpp"
+ footer "${filePrefix}keywords.cpp"
+ fi
+
+ ls mysql-*.tar.gz postgresql-*.tar.gz 2>/dev/null | while read tarball ; do
+ case "$tarball" in
+ mysql-4.1.[0-9\.]*.tar.gz)
+ pathInTar="sql/lex.h"
+ appName="MySql"
+ filePrefix="mysql"
+ appVer="${tarball%.tar.gz}"
+ if [ ! -r "$appVer.all" ] || [ ! -r "$appVer.new" ] ; then
+ checkExtracted "$tarball" "$appVer/$pathInTar"
+ getMySQLKeywords "$appVer/$pathInTar" "$appVer.all"
+ rm -rf "$appVer"
+ fi
+
+ if [ "$appVer.all" -nt "$appVer.new" ] ; then
+ getDriverKeywords "kexi.all" "$appVer.all" "$appVer.new"
+ header "${appName}Driver::keywords" "${filePrefix}driver.h" "$appVer/$pathInTar" "${filePrefix}keywords.cpp"
+ body "$appVer.new" "${filePrefix}keywords.cpp"
+ footer "${filePrefix}keywords.cpp"
+ fi
+ ;;
+
+ postgresql-base-7.4.[0-9\.]*.tar.gz)
+ pathInTar="src/backend/parser/keywords.c"
+ appName="pqxxSql"
+ filePrefix="pqxx"
+ appVer=`echo "${tarball%.tar.gz}" | sed 's/-base//'`
+ if [ ! -r "$appVer.all" ] || [ ! -r "$appVer.new" ] ; then
+ checkExtracted "$tarball" "$appVer/$pathInTar"
+ getPostgreSQLKeywords "$appVer/$pathInTar" "$appVer.all"
+ rm -rf "$appVer"
+ fi
+
+ if [ "$appVer.all" -nt "$appVer.new" ] ; then
+ getDriverKeywords "kexi.all" "$appVer.all" "$appVer.new"
+ header "${appName}Driver::keywords" "${filePrefix}driver.h" "$appVer/$pathInTar" "${filePrefix}keywords.cpp"
+ body "$appVer.new" "${filePrefix}keywords.cpp"
+ footer "${filePrefix}keywords.cpp"
+ fi
+ ;;
+
+ *)
+ echo "Don't know how to deal with $tarball - ignoring"
+ ;;
+ esac
+ done
+}
+
+checkKexiKeywords
+src=`printf "koffice/kexi/kexidb/parser/sqlscanner.l\n"\
+" * and koffice/kexi/tools/sql_keywords/kexi__reserved"`
+header "DriverPrivate::kexiSQLKeywords" "driver_p.h" "$src" "keywords.cpp"
+body "kexi.all" "keywords.cpp"
+footer "keywords.cpp"
+
+checkTarballs
+wc -l *.all *.new | awk '{print $2" "$1}' |sort|awk '{print $1"\t"$2}'
diff --git a/kexi/widget/Makefile.am b/kexi/widget/Makefile.am
new file mode 100644
index 000000000..95dc30dd7
--- /dev/null
+++ b/kexi/widget/Makefile.am
@@ -0,0 +1,51 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexiextendedwidgets.la
+libkexiextendedwidgets_la_SOURCES = kexidataawareview.cpp \
+ kexibrowser.cpp kexibrowseritem.cpp \
+ kexidatatable.cpp kexiquerydesignersqleditor.cpp kexiqueryparameters.cpp \
+ kexisectionheader.cpp pixmapcollection.cpp \
+ kexiscrollview.cpp kexidbconnectionwidgetbase.ui kexidbconnectionwidget.cpp \
+ kexidbconnectionwidgetdetailsbase.ui kexidbdrivercombobox.cpp \
+ kexieditor.cpp \
+ kexifieldlistview.cpp kexifieldcombobox.cpp kexidatasourcecombobox.cpp \
+ kexipropertyeditorview.cpp kexismalltoolbutton.cpp \
+ kexicustompropertyfactory.cpp kexicustompropertyfactory_p.cpp \
+ kexicharencodingcombobox.cpp \
+ kexiprjtypeselectorbase.ui kexiprjtypeselector.cpp
+
+libkexiextendedwidgets_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved
+libkexiextendedwidgets_la_LIBADD = $(LIB_KDEUI) ./utils/libkexiguiutils.la tableview/libkexidatatable.la ../core/libkexicore.la -lktexteditor
+
+SUBDIRS = utils tableview . relations
+
+# kde_appsdir Where your application's menu entry (.desktop) should go to.
+# kde_icondir Where your icon should go to - better use KDE_ICON.
+# kde_sounddir Where your sounds should go to.
+# kde_htmldir Where your docs should go to. (contains lang subdirs)
+# kde_datadir Where you install application data. (Use a subdir)
+# kde_locale Where translation files should go to. (contains lang subdirs)
+# kde_cgidir Where cgi-bin executables should go to.
+# kde_confdir Where config files should go to (system-wide ones with default values).
+# kde_mimedir Where mimetypes .desktop files should go to.
+# kde_servicesdir Where services .desktop files should go to.
+# kde_servicetypesdir Where servicetypes .desktop files should go to.
+# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON).
+# kde_wallpaperdir Where general wallpapers should go to.
+# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to.
+# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS.
+# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES.
+# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES.
+# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3).
+# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3).
+
+# set the include path for X, qt and KDE - all_includes must remain last!
+INCLUDES = -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/widget/tableview \
+ $(LIB_KEXI_KMDI_INCLUDES) \
+ -I$(top_srcdir)/kexi/core \
+ -I$(top_srcdir)/lib/kofficecore -I$(top_srcdir)/lib $(all_includes)
+
+noinst_HEADERS = kexibrowseritem.h kexibrowser_p.h
+
+METASOURCES = AUTO
+
diff --git a/kexi/widget/kexibrowser.cpp b/kexi/widget/kexibrowser.cpp
new file mode 100644
index 000000000..ca637cd2c
--- /dev/null
+++ b/kexi/widget/kexibrowser.cpp
@@ -0,0 +1,890 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexibrowser.h"
+#include "kexibrowser_p.h"
+#include "kexibrowseritem.h"
+
+#include <qheader.h>
+#include <qpoint.h>
+#include <qpixmapcache.h>
+#include <qtoolbutton.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <klistview.h>
+#include <kmessagebox.h>
+#include <klineedit.h>
+#include <kimageeffect.h>
+#include <kconfig.h>
+
+#include <kexi.h>
+#include <kexipart.h>
+#include <kexipartinfo.h>
+#include <kexipartitem.h>
+#include <kexiproject.h>
+#include <kexidialogbase.h>
+#include <keximainwindow.h>
+#include <kexiutils/identifier.h>
+#include <widget/utils/kexiflowlayout.h>
+#include <widget/kexismalltoolbutton.h>
+#include <kexidb/utils.h>
+
+/*
+class KexiBrowserView : public QWidget
+{
+ KexiBrowserView*(
+};
+
+KexiBrowserView::KexiBrowserView(KexiMainWindow *mainWin)
+ : QWidget(mainWin, "KexiBrowserView")
+{
+ QVBoxLayout *lyr = new QVBoxLayout(this);
+ KexiFlowLayout *buttons_flyr = new KexiFlowLayout(lyr);
+ m_browser = KexiBrowser(this, mainWin);
+ lyr->addWidget(m_browser);
+ setFocusProxy(m_browser);
+}*/
+
+KexiBrowser::KexiBrowser(QWidget* parent, KexiMainWindow *mainWin, int features)
+ : QWidget(parent, "KexiBrowser")
+ , m_mainWin(mainWin)
+ , m_features(features)
+ , m_actions( new KActionCollection(this) )
+ , m_baseItems(199, false)
+ , m_normalItems(199)
+ , m_prevSelectedPart(0)
+ , m_singleClick(false)
+// , m_nameEndsWithAsterisk(false)
+ , m_readOnly(false)
+// , m_enableExecuteArea(true)
+{
+ setCaption(i18n("Project Navigator"));
+ setIcon(*m_mainWin->icon());
+
+ QVBoxLayout *lyr = new QVBoxLayout(this);
+ KexiFlowLayout *buttons_flyr = new KexiFlowLayout(lyr);
+
+ m_list = new KexiBrowserListView(this);
+ lyr->addWidget(m_list);
+ m_list->installEventFilter(this);
+ m_list->renameLineEdit()->installEventFilter(this);
+ connect( kapp, SIGNAL( settingsChanged(int) ), SLOT( slotSettingsChanged(int) ) );
+ slotSettingsChanged(0);
+
+ m_list->header()->hide();
+ m_list->addColumn("");
+ m_list->setShowToolTips(true);
+ m_list->setSorting(0);
+ m_list->sort();
+ m_list->setAllColumnsShowFocus(true);
+ m_list->setTooltipColumn(0);
+ m_list->renameLineEdit()->setValidator( new KexiUtils::IdentifierValidator(this) );
+ m_list->setResizeMode(QListView::LastColumn);
+ connect(m_list, SIGNAL(contextMenu(KListView *, QListViewItem *, const QPoint &)),
+ this, SLOT(slotContextMenu(KListView*, QListViewItem *, const QPoint&)));
+ connect(m_list, SIGNAL(selectionChanged(QListViewItem*)), this,
+ SLOT(slotSelectionChanged(QListViewItem*)));
+
+ KConfig *config = kapp->config();
+ config->setGroup("MainWindow");
+ if ((m_features & SingleClickOpensItemOptionEnabled)
+ && config->readBoolEntry("SingleClickOpensItem", false))
+ {
+ connect(m_list, SIGNAL(executed(QListViewItem*)), this,
+ SLOT(slotExecuteItem(QListViewItem*)));
+ }
+ else {
+ connect(m_list, SIGNAL(doubleClicked(QListViewItem*)), this,
+ SLOT(slotExecuteItem(QListViewItem*)));
+ m_list->enableExecuteArea = false;
+ }
+
+ // actions
+ m_openAction = new KAction(i18n("&Open"), "fileopen", 0/*Qt::Key_Enter conflict!*/, this,
+ SLOT(slotOpenObject()), this, "open_object");
+ m_openAction->setToolTip(i18n("Open object"));
+ m_openAction->setWhatsThis(i18n("Opens object selected in the list"));
+
+// m_openAction->plug(m_toolbar);
+ KexiSmallToolButton *btn;
+ if (m_features & Toolbar) {
+ btn = new KexiSmallToolButton(this, m_openAction);
+ buttons_flyr->add(btn);
+ }
+
+ if (m_mainWin->userMode()) {
+//! @todo some of these actions can be supported once we deliver ACLs...
+ m_deleteAction = 0;
+ m_renameAction = 0;
+ m_designAction = 0;
+ m_editTextAction = 0;
+ m_newObjectAction = 0;
+ m_newObjectPopup = 0;
+ }
+ else {
+ m_deleteAction = new KAction(i18n("&Delete"), "editdelete", 0/*Qt::Key_Delete*/,
+ this, SLOT(slotRemove()), m_actions, "edit_delete");
+ //! @todo 1.1: just add "Delete" tooltip and what's this
+ m_deleteAction->setToolTip(i18n("&Delete").replace("&",""));
+
+ m_renameAction = new KAction(i18n("&Rename"), "", 0,
+ this, SLOT(slotRename()), m_actions, "edit_rename");
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+ //todo plugSharedAction("edit_cut",SLOT(slotCut()));
+ //todo plugSharedAction("edit_copy",SLOT(slotCopy()));
+ //todo plugSharedAction("edit_paste",SLOT(slotPaste()));
+#endif
+
+ m_designAction = new KAction(i18n("&Design"), "edit", 0/*Qt::CTRL + Qt::Key_Enter conflict!*/, this,
+ SLOT(slotDesignObject()), this, "design_object");
+ m_designAction->setToolTip(i18n("Design object"));
+ m_designAction->setWhatsThis(i18n("Starts designing of the object selected in the list"));
+ if (m_features & Toolbar) {
+ btn = new KexiSmallToolButton(this, m_designAction);
+ buttons_flyr->add(btn);
+ }
+
+ m_editTextAction = new KAction(i18n("Open in &Text View"), "", 0, this,
+ SLOT(slotEditTextObject()), this, "editText_object");
+ m_editTextAction->setToolTip(i18n("Open object in text view"));
+ m_editTextAction->setWhatsThis(i18n("Opens selected object in the list in text view"));
+
+ m_newObjectAction = new KAction("", "filenew", 0, this, SLOT(slotNewObject()), this, "new_object");
+ if (m_features & Toolbar) {
+ m_newObjectToolButton = new KexiSmallToolButton(this, "", QIconSet(), "new_object");
+ m_newObjectPopup = new KPopupMenu(this, "newObjectPopup");
+ connect(m_newObjectPopup, SIGNAL(aboutToShow()), this, SLOT(slotNewObjectPopupAboutToShow()));
+ // KexiPart::Part* part = Kexi::partManager().part("kexi/table");
+ // m_newObjectPopup->insertItem( SmallIconSet(part->info()->createItemIcon()), part->instanceName() );
+ m_newObjectToolButton->setPopup(m_newObjectPopup);
+ m_newObjectToolButton->setPopupDelay(QApplication::startDragTime());
+ connect(m_newObjectToolButton, SIGNAL(clicked()), this, SLOT(slotNewObject()));
+ buttons_flyr->add(m_newObjectToolButton);
+
+ m_deleteObjectToolButton = new KexiSmallToolButton(this, m_deleteAction);
+ m_deleteObjectToolButton->setTextLabel("");
+ buttons_flyr->add(m_deleteObjectToolButton);
+ }
+ }
+
+ m_executeAction = new KAction(i18n("Execute"), "player_play", 0, this,
+ SLOT(slotExecuteObject()), this, "data_execute");
+
+ m_exportActionMenu = new KActionMenu(i18n("Export"));
+ m_dataExportAction = new KAction(i18n("Export->To File as Data &Table... ", "To &File as Data Table..."),
+ "table", 0, this, SLOT(slotExportAsDataTable()), this, "exportAsDataTable");
+ m_dataExportAction->setWhatsThis(
+ i18n("Exports data from the currently selected table or query data to a file."));
+ m_exportActionMenu->insert( m_dataExportAction );
+
+ m_printAction = new KAction(i18n("&Print..."), "fileprint", 0, this,
+ SLOT(slotPrintItem()), this, "printItem");
+ m_printAction->setWhatsThis(
+ i18n("Prints data from the currently selected table or query."));
+ m_pageSetupAction = new KAction(i18n("Page Setup..."), "", 0, this,
+ SLOT(slotPageSetupForItem()), this, "pageSetupForItem");
+ m_pageSetupAction->setWhatsThis(
+ i18n("Shows page setup for printing the active table or query."));
+
+ if (m_mainWin->userMode()) {
+//! @todo some of these actions can be supported once we deliver ACLs...
+ m_partPopup = 0;
+ }
+ else {
+ m_partPopup = new KPopupMenu(this, "partPopup");
+ m_partPopupTitle_id = m_partPopup->insertTitle("");
+ m_newObjectAction->plug(m_partPopup);
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+ m_partPopup->insertSeparator();
+ plugSharedAction("edit_paste", m_partPopup);
+#endif
+ }
+
+ if (m_features & ContextMenus) {
+ //init popups
+ m_itemPopup = new KPopupMenu(this, "itemPopup");
+ m_itemPopupTitle_id = m_itemPopup->insertTitle("");
+ m_openAction->plug(m_itemPopup);
+ m_openAction_id = m_itemPopup->idAt(m_itemPopup->count()-1);
+
+ if (m_designAction) {
+ m_designAction->plug(m_itemPopup);
+ m_designAction_id = m_itemPopup->idAt(m_itemPopup->count()-1);
+ }
+
+ if (m_editTextAction) {
+ m_editTextAction->plug(m_itemPopup);
+ m_editTextAction_id = m_itemPopup->idAt(m_itemPopup->count()-1);
+ }
+
+ if (m_newObjectAction) {
+ m_newObjectAction->plug(m_itemPopup);
+ m_itemPopup->insertSeparator();
+ }
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+ //todo plugSharedAction("edit_cut", m_itemPopup);
+ //todo plugSharedAction("edit_copy", m_itemPopup);
+ //todo m_itemPopup->insertSeparator();
+#endif
+ m_executeAction->plug(m_itemPopup);
+ m_executeAction_id = m_itemPopup->idAt(m_itemPopup->count()-1);
+
+ m_exportActionMenu->plug(m_itemPopup);
+ m_exportActionMenu_id = m_exportActionMenu->menuId(0);
+ m_itemPopup->insertSeparator();
+ m_exportActionMenu_id_sep = m_itemPopup->idAt(m_itemPopup->count()-1);
+
+ m_printAction->plug(m_itemPopup);
+ m_printAction_id = m_itemPopup->idAt(m_itemPopup->count()-1);
+
+ m_pageSetupAction->plug(m_itemPopup);
+ m_pageSetupAction_id = m_itemPopup->idAt(m_itemPopup->count()-1);
+ if (m_renameAction || m_deleteAction) {
+ m_itemPopup->insertSeparator();
+ m_pageSetupAction_id_sep = m_itemPopup->idAt(m_itemPopup->count()-1);
+ }
+ else {
+ m_pageSetupAction_id_sep = -1;
+ }
+
+ if (m_renameAction)
+ m_renameAction->plug(m_itemPopup);
+ if (m_deleteAction)
+ m_deleteAction->plug(m_itemPopup);
+ }
+ else {
+ m_itemPopup = 0;
+ }
+
+ if (!(m_features & Writable)) {
+ setReadOnly(true);
+ }
+}
+
+KexiBrowser::~KexiBrowser()
+{
+}
+
+void KexiBrowser::setProject(KexiProject* prj, const QString& itemsMimeType,
+ QString* partManagerErrorMessages)
+{
+ clear();
+ m_itemsMimeType = itemsMimeType;
+ m_list->setRootIsDecorated(m_itemsMimeType.isEmpty());
+
+ KexiPart::PartInfoList *pl = Kexi::partManager().partInfoList();
+ for (KexiPart::Info *info = pl->first(); info; info = pl->next()) {
+ if (!info->isVisibleInNavigator())
+ continue;
+ if (!m_itemsMimeType.isEmpty() && info->mimeType()!=m_itemsMimeType.latin1())
+ continue;
+
+// kdDebug() << "KexiMainWindowImpl::initNavigator(): adding " << it->groupName() << endl;
+
+/* KexiPart::Part *p=Kexi::partManager().part(it);
+ if (!p) {
+ //TODO: js - OPTIONALLY: show error
+ continue;
+ }
+ p->createGUIClient(this);*/
+
+ //load part - we need this to have GUI merged with part's actions
+//! @todo FUTURE - don't do that when DESIGN MODE is OFF
+ KexiPart::Part *p=Kexi::partManager().part(info);
+ if (p) {
+ KexiBrowserItem *groupItem = 0;
+ if (m_itemsMimeType.isEmpty()) {
+ groupItem = addGroup(*info);
+ if (!groupItem)
+ continue;
+ }
+ //lookup project's objects (part items)
+//! @todo FUTURE - don't do that when DESIGN MODE is OFF
+ KexiPart::ItemDict *item_dict = prj->items(info);
+ if (!item_dict)
+ continue;
+ for (KexiPart::ItemDictIterator item_it( *item_dict ); item_it.current(); ++item_it)
+ addItem(*item_it.current(), groupItem, info);
+ if (!m_itemsMimeType.isEmpty())
+ break; //the only group added, so our work is completed
+ }
+ else {
+ //add this error to the list that will be displayed later
+ QString msg, details;
+ KexiDB::getHTMLErrorMesage(&Kexi::partManager(), msg, details);
+ if (!msg.isEmpty() && partManagerErrorMessages) {
+ if (partManagerErrorMessages->isEmpty()) {
+ *partManagerErrorMessages = QString("<qt><p>")
+ +i18n("Errors encountered during loading plugins:")+"<ul>";
+ }
+ partManagerErrorMessages->append( QString("<li>") + msg );
+ if (!details.isEmpty())
+ partManagerErrorMessages->append(QString("<br>")+details);
+ partManagerErrorMessages->append("</li>");
+ }
+ }
+ }
+ if (partManagerErrorMessages && !partManagerErrorMessages->isEmpty())
+ partManagerErrorMessages->append("</ul></p>");
+}
+
+QString KexiBrowser::itemsMimeType() const
+{
+ return m_itemsMimeType;
+}
+
+KexiBrowserItem *KexiBrowser::addGroup(KexiPart::Info& info)
+{
+ if(!info.isVisibleInNavigator())
+ return 0;
+
+ KexiBrowserItem *item = new KexiBrowserItem(m_list, &info);
+ m_baseItems.insert(info.mimeType().lower(), item);
+ return item;
+// kdDebug() << "KexiBrowser::addGroup()" << endl;
+}
+
+KexiBrowserItem* KexiBrowser::addItem(KexiPart::Item& item)
+{
+ //part object for this item
+ KexiBrowserItem *parent = item.mimeType().isEmpty() ? 0 : m_baseItems.find(item.mimeType().lower());
+ return addItem(item, parent, parent->info());
+}
+
+KexiBrowserItem* KexiBrowser::addItem(KexiPart::Item& item, KexiBrowserItem *parent, KexiPart::Info* info)
+{
+// if (!parent) //TODO: add "Other" part group for that
+ // return 0;
+// kdDebug() << "KexiBrowser::addItem() found parent:" << parent << endl;
+ KexiBrowserItem *bitem;
+ if (parent)
+ bitem = new KexiBrowserItem(parent, info, &item);
+ else
+ bitem = new KexiBrowserItem(m_list, info, &item);
+ m_normalItems.insert(item.identifier(), bitem);
+ return bitem;
+}
+
+void
+KexiBrowser::slotRemoveItem(const KexiPart::Item &item)
+{
+ KexiBrowserItem *to_remove=m_normalItems.take(item.identifier());
+ if (!to_remove)
+ return;
+
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+
+ QListViewItem *new_item_to_select = 0;
+ if (it==to_remove) {//compute item to select if current one will be removed
+ new_item_to_select = it->itemBelow();//nearest item to select
+ if (!new_item_to_select || new_item_to_select->parent()!=it->parent()) {
+ new_item_to_select = it->itemAbove();
+ }
+ }
+ delete to_remove;
+
+ if (new_item_to_select)
+ m_list->setSelected(new_item_to_select, true);
+}
+
+void
+KexiBrowser::slotContextMenu(KListView* /*list*/, QListViewItem *item, const QPoint &pos)
+{
+ if(!item || !(m_features & ContextMenus))
+ return;
+ KexiBrowserItem *bit = static_cast<KexiBrowserItem*>(item);
+ KPopupMenu *pm = 0;
+ if (bit->item()) {
+ pm = m_itemPopup;
+ //update popup title
+ QString title_text = bit->text(0).stripWhiteSpace();
+ KexiBrowserItem *par_it = static_cast<KexiBrowserItem*>(bit->parent());
+ KexiPart::Part* par_part = 0;
+ if (par_it && par_it->info() && ((par_part = Kexi::partManager().part(par_it->info()))) && !par_part->instanceCaption().isEmpty()) {
+ //add part type name
+ title_text += (" : " + par_part->instanceCaption());
+ }
+ pm->changeTitle(m_itemPopupTitle_id, *bit->pixmap(0), title_text);
+ }
+ else if (m_partPopup) {
+ pm = m_partPopup;
+ QString title_text = bit->text(0).stripWhiteSpace();
+ pm->changeTitle(m_partPopupTitle_id, *bit->pixmap(0), title_text);
+/* KexiPart::Part* part = Kexi::partManager().part(bit->info());
+ if (part)
+ m_newObjectAction->setText(i18n("&Create Object: %1...").arg( part->instanceName() ));
+ else
+ m_newObjectAction->setText(i18n("&Create Object..."));
+ m_newObjectAction->setIconSet( SmallIconSet(bit->info()->itemIcon()) );*/
+ m_list->setCurrentItem(item);
+ m_list->repaintItem(item);
+ }
+ if (pm)
+ pm->exec(pos);
+}
+
+void
+KexiBrowser::slotExecuteItem(QListViewItem *vitem)
+{
+// kdDebug() << "KexiBrowser::slotExecuteItem()" << endl;
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(vitem);
+ if (!it)
+ return;
+ if (!it->item() && !m_singleClick /*annoying when in single click mode*/) {
+ m_list->setOpen( vitem, !vitem->isOpen() );
+ return;
+ }
+ if (it->info()->isExecuteSupported())
+ emit executeItem( it->item() );
+ else
+ emit openOrActivateItem( it->item(), Kexi::DataViewMode );
+}
+
+void
+KexiBrowser::slotSelectionChanged(QListViewItem* i)
+{
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(i);
+ if (!it)
+ return;
+ KexiPart::Part* part = Kexi::partManager().part(it->info());
+ if (!part) {
+ it = static_cast<KexiBrowserItem*>(it->parent());
+ if (it) {
+ part = Kexi::partManager().part(it->info());
+ }
+ }
+
+ const bool gotitem = it && it->item();
+ //bool gotgroup = it && !it->item();
+//TODO: also check if the item is not read only
+ if (m_deleteAction) {
+ m_deleteAction->setEnabled(gotitem && !m_readOnly);
+ if (m_features & Toolbar) {
+ m_deleteObjectToolButton->setEnabled(gotitem && !m_readOnly);
+ }
+ }
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+//todo setAvailable("edit_cut",gotitem);
+//todo setAvailable("edit_copy",gotitem);
+//todo setAvailable("edit_edititem",gotitem);
+#endif
+
+ m_openAction->setEnabled(gotitem && part && (part->supportedViewModes() & Kexi::DataViewMode));
+ if (m_designAction)
+ m_designAction->setEnabled(gotitem && part && (part->supportedViewModes() & Kexi::DesignViewMode));
+ if (m_editTextAction)
+ m_editTextAction->setEnabled(gotitem && part && (part->supportedViewModes() & Kexi::TextViewMode));
+
+ if (m_features & ContextMenus) {
+ m_itemPopup->setItemVisible(m_openAction_id, m_openAction->isEnabled());
+ if (m_designAction)
+ m_itemPopup->setItemVisible(m_designAction_id, m_designAction->isEnabled());
+ if (m_editTextAction)
+ m_itemPopup->setItemVisible(m_editTextAction_id, part && m_editTextAction->isEnabled());
+ if (m_executeAction)
+ m_itemPopup->setItemVisible(m_executeAction_id, gotitem && it->info()->isExecuteSupported());
+ if (m_exportActionMenu) {
+ m_itemPopup->setItemVisible(m_exportActionMenu_id, gotitem && it->info()->isDataExportSupported());
+ m_itemPopup->setItemVisible(m_exportActionMenu_id_sep, gotitem && it->info()->isDataExportSupported());
+ }
+ if (m_printAction)
+ m_itemPopup->setItemVisible(m_printAction_id, gotitem && it->info()->isPrintingSupported());
+ if (m_pageSetupAction) {
+ m_itemPopup->setItemVisible(m_pageSetupAction_id, gotitem && it->info()->isPrintingSupported());
+ if (m_pageSetupAction_id_sep!=-1)
+ m_itemPopup->setItemVisible(m_pageSetupAction_id_sep, gotitem && it->info()->isPrintingSupported());
+ }
+ }
+
+ if (m_prevSelectedPart != part) {
+ m_prevSelectedPart = part;
+ if (part) {
+ if (m_newObjectAction) {
+ m_newObjectAction->setText(i18n("&Create Object: %1...").arg( part->instanceCaption() ));
+ m_newObjectAction->setIcon( part->info()->createItemIcon() );
+ if (m_features & Toolbar) {
+ m_newObjectToolButton->setIconSet( part->info()->createItemIcon() );
+ QToolTip::add(m_newObjectToolButton,
+ i18n("Create object: %1").arg( part->instanceCaption().lower() ));
+ QWhatsThis::add(m_newObjectToolButton,
+ i18n("Creates a new object: %1").arg( part->instanceCaption().lower() ));
+ }
+ }
+ } else {
+ if (m_newObjectAction) {
+ m_newObjectAction->setText(i18n("&Create Object..."));
+ // m_newObjectToolbarAction->setIconSet( SmallIconSet("filenew") );
+ // m_newObjectToolbarAction->setText(m_newObjectAction->text());
+ if (m_features & Toolbar) {
+ m_newObjectToolButton->setIconSet( "filenew" );
+ QToolTip::add(m_newObjectToolButton, i18n("Create object"));
+ QWhatsThis::add(m_newObjectToolButton, i18n("Creates a new object"));
+ }
+ }
+ }
+ }
+ emit selectionChanged(it ? it->item() : 0);
+}
+
+void KexiBrowser::installEventFilter ( const QObject * filterObj )
+{
+ if (!filterObj)
+ return;
+ m_list->installEventFilter ( filterObj );
+ QWidget::installEventFilter ( filterObj );
+}
+
+bool KexiBrowser::eventFilter ( QObject *o, QEvent * e )
+{
+ if (o==m_list && e->type()==QEvent::Resize) {
+ kdDebug() << "resize!" << endl;
+ }
+ if (o==m_list->renameLineEdit()) {
+ if (e->type()==QEvent::Hide)
+ itemRenameDone();
+ }
+ else if (e->type()==QEvent::KeyPress) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) {
+ if (0==(ke->state() & (Qt::ShiftButton|Qt::ControlButton|Qt::AltButton))) {
+ QListViewItem *it = m_list->selectedItem();
+ if (it)
+ slotExecuteItem(it);
+ }
+ else if (Qt::ControlButton==(ke->state() & (Qt::ShiftButton|Qt::ControlButton|Qt::AltButton))) {
+ slotDesignObject();
+ }
+ }
+ }
+ else if (e->type()==QEvent::AccelOverride) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ //override delete action
+ if (ke->key()==Qt::Key_Delete && ke->state()==Qt::NoButton) {
+ slotRemove();
+ ke->accept();
+ return true;
+ }
+ //override rename action
+ if (ke->key()==Qt::Key_F2 && ke->state()==Qt::NoButton) {
+ slotRename();
+ ke->accept();
+ return true;
+ }
+/* else if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) {
+ if (ke->state()==ControlButton) {
+ slotDesignObject();
+ }
+ else if (ke->state()==0 && !m_list->renameLineEdit()->isVisible()) {
+ QListViewItem *it = m_list->selectedItem();
+ if (it)
+ slotExecuteItem(it);
+ }
+ ke->accept();
+ return true;
+ }*/
+ }
+ return false;
+}
+
+void KexiBrowser::slotRemove()
+{
+ if (!m_deleteAction || !m_deleteAction->isEnabled() || !(m_features & Writable))
+ return;
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+ if (!it || !it->item())
+ return;
+ emit removeItem( it->item() );
+}
+
+void KexiBrowser::slotNewObject()
+{
+ if (!m_newObjectAction || !(m_features & Writable))
+ return;
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+ if (!it || !it->info())
+ return;
+ emit newItem( it->info() );
+}
+
+void KexiBrowser::slotOpenObject()
+{
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+ if (!it || !it->item())
+ return;
+ emit openItem( it->item(), Kexi::DataViewMode );
+}
+
+void KexiBrowser::slotDesignObject()
+{
+ if (!m_designAction)
+ return;
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+ if (!it || !it->item())
+ return;
+ emit openItem( it->item(), Kexi::DesignViewMode );
+}
+
+void KexiBrowser::slotEditTextObject()
+{
+ if (!m_editTextAction)
+ return;
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+ if (!it || !it->item())
+ return;
+ emit openItem( it->item(), Kexi::TextViewMode );
+}
+
+void KexiBrowser::slotCut()
+{
+ if (!(m_features & Writable))
+ return;
+// KEXI_UNFINISHED_SHARED_ACTION("edit_cut");
+ //TODO
+}
+
+void KexiBrowser::slotCopy()
+{
+// KEXI_UNFINISHED_SHARED_ACTION("edit_copy");
+ //TODO
+}
+
+void KexiBrowser::slotPaste()
+{
+ if (!(m_features & Writable))
+ return;
+// KEXI_UNFINISHED_SHARED_ACTION("edit_paste");
+ //TODO
+}
+
+void KexiBrowser::slotRename()
+{
+ if (!m_renameAction || !(m_features & Writable))
+ return;
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+ if (it)
+ m_list->rename(it, 0);
+}
+
+void KexiBrowser::itemRenameDone()
+{
+ if (!(m_features & Writable))
+ return;
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+ if (!it)
+ return;
+ QString txt = it->text(0).stripWhiteSpace();
+ bool ok = it->item()->name().lower()!=txt.lower(); //the new name must be different
+ if (ok) {
+ /* TODO */
+ emit renameItem(it->item(), txt, ok);
+ }
+ if (!ok) {
+ txt = it->item()->name(); //revert
+ }
+ //"modified" flag has been removed before editing: readd it
+ if (m_list->nameEndsWithAsterisk) {
+ txt += "*";
+ m_list->nameEndsWithAsterisk = false;
+ }
+ it->setText(0, txt);
+ it->parent()->sort();
+ setFocus();
+}
+
+void KexiBrowser::setFocus()
+{
+ if (!m_list->selectedItem() && m_list->firstChild())//select first
+ m_list->setSelected(m_list->firstChild(), true);
+ m_list->setFocus();
+}
+
+void KexiBrowser::updateItemName( KexiPart::Item& item, bool dirty )
+{
+ if (!(m_features & Writable))
+ return;
+ KexiBrowserItem *bitem = m_normalItems[item.identifier()];
+ if (!bitem)
+ return;
+ bitem->setText( 0, item.name() + (dirty ? "*" : "") );
+}
+
+void KexiBrowser::slotSettingsChanged(int)
+{
+ m_singleClick = KGlobalSettings::singleClick();
+}
+
+void KexiBrowser::selectItem(KexiPart::Item& item)
+{
+ KexiBrowserItem *bitem = m_normalItems[item.identifier()];
+ if (!bitem)
+ return;
+ m_list->setSelected(bitem, true);
+ m_list->ensureItemVisible(bitem);
+ m_list->setCurrentItem(bitem);
+}
+
+void KexiBrowser::clearSelection()
+{
+ m_list->clearSelection();
+ QListViewItem *item = m_list->firstChild();
+ if (item) {
+ m_list->ensureItemVisible(item);
+ }
+}
+
+void KexiBrowser::slotNewObjectPopupAboutToShow()
+{
+ if ((m_features & Toolbar) && m_newObjectPopup && m_newObjectPopup->count()==0) {
+ //preload items
+ KexiPart::PartInfoList *list = Kexi::partManager().partInfoList(); //this list is properly sorted
+ for (KexiPart::PartInfoListIterator it(*list); it.current(); ++it) {
+ //add an item to "New object" toolbar popup
+ KAction *action = m_mainWin->actionCollection()->action(
+ KexiPart::nameForCreateAction(*it.current()) );
+ if (action) {
+ action->plug(m_newObjectPopup);
+ }
+ else {
+ //! @todo err
+ }
+ }
+ }
+}
+
+void KexiBrowser::slotExecuteObject()
+{
+ if (!m_executeAction)
+ return;
+ KexiPart::Item* item = selectedPartItem();
+ if (item)
+ emit executeItem( item );
+}
+
+void KexiBrowser::slotExportAsDataTable()
+{
+ if (!m_dataExportAction)
+ return;
+ KexiPart::Item* item = selectedPartItem();
+ if (item)
+ emit exportItemAsDataTable( item );
+}
+
+KexiPart::Item* KexiBrowser::selectedPartItem() const
+{
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem());
+ return it ? it->item() : 0;
+}
+
+bool KexiBrowser::actionEnabled(const QCString& actionName) const
+{
+ if (actionName=="project_export_data_table" && (m_features & ContextMenus))
+ return m_itemPopup->isItemVisible(m_exportActionMenu_id);
+ kdWarning() << "KexiBrowser::actionEnabled() no such action: " << actionName << endl;
+ return false;
+}
+
+void KexiBrowser::slotPrintItem()
+{
+ if (!m_printAction)
+ return;
+ KexiPart::Item* item = selectedPartItem();
+ if (item)
+ emit printItem( item );
+}
+
+void KexiBrowser::slotPageSetupForItem()
+{
+ if (!m_pageSetupAction)
+ return;
+ KexiPart::Item* item = selectedPartItem();
+ if (item)
+ emit pageSetupForItem( item );
+}
+
+
+void KexiBrowser::setReadOnly(bool set)
+{
+ m_readOnly = set;
+ if (m_deleteAction)
+ m_deleteAction->setEnabled(!m_readOnly);
+ if (m_renameAction)
+ m_renameAction->setEnabled(!m_readOnly);
+ if (m_newObjectAction) {
+ m_newObjectAction->setEnabled(!m_readOnly);
+ if (m_features & Toolbar) {
+ m_newObjectPopup->setEnabled(!m_readOnly);
+ m_newObjectToolButton->setEnabled(!m_readOnly);
+ }
+ }
+}
+
+bool KexiBrowser::isReadOnly() const
+{
+ return m_readOnly;
+}
+
+void KexiBrowser::clear()
+{
+ m_list->clear();
+}
+
+//--------------------------------------------
+KexiBrowserListView::KexiBrowserListView(QWidget *parent)
+ : KListView(parent, "KexiBrowserListView")
+ , nameEndsWithAsterisk(false)
+ , enableExecuteArea(true)
+{
+}
+
+KexiBrowserListView::~KexiBrowserListView()
+{
+}
+
+void KexiBrowserListView::rename(QListViewItem *item, int c)
+{
+ if (renameLineEdit()->isVisible())
+ return;
+ KexiBrowserItem *it = static_cast<KexiBrowserItem*>(item);
+ if (it->item() && c==0) {
+ //only edit 1st column for items, not item groups
+//TODO: also check it this item is not read-only
+// item->setText(0, item->text(0).mid(1,item->text(0).length()-2));
+ //remove "modified" flag for editing
+ nameEndsWithAsterisk = item->text(0).endsWith("*");
+ if (nameEndsWithAsterisk)
+ item->setText(0, item->text(0).left(item->text(0).length()-1));
+ KListView::rename(item, c);
+ adjustColumn(0);
+ }
+}
+
+bool KexiBrowserListView::isExecuteArea( const QPoint& point )
+{
+ return enableExecuteArea && KListView::isExecuteArea(point);
+}
+
+#include "kexibrowser.moc"
+#include "kexibrowser_p.moc"
diff --git a/kexi/widget/kexibrowser.h b/kexi/widget/kexibrowser.h
new file mode 100644
index 000000000..1bdc5d25f
--- /dev/null
+++ b/kexi/widget/kexibrowser.h
@@ -0,0 +1,183 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIBROWSER_H
+#define KEXIBROWSER_H
+
+#include <klistview.h>
+#include <qasciidict.h>
+#include <qintdict.h>
+
+class QListViewItem;
+class KIconLoader;
+class KPopupMenu;
+class KAction;
+class KActionMenu;
+class KActionCollection;
+class KListView;
+class KToolBar;
+class KexiBrowserItem;
+class KexiView;
+class KexiMainWindow;
+class KexiSmallToolButton;
+class KexiBrowserListView;
+
+namespace KexiPart
+{
+ class Info;
+ class Item;
+ class Part;
+}
+class KexiProject;
+
+//! @short The Main Kexi navigator widget
+class KEXIEXTWIDGETS_EXPORT KexiBrowser : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ enum Features {
+ Writable = 1, //!< the browser supports actions that modify the project (e.g. delete, rename)
+ ContextMenus = 2, //!< the browser supports context menu
+ Toolbar = 4, //!< the browser displays
+ SingleClickOpensItemOptionEnabled = 8, //!< enables "SingleClickOpensItem" option
+ DefaultFeatures = Writable | ContextMenus | Toolbar
+ | SingleClickOpensItemOptionEnabled //!< the default
+ };
+
+ KexiBrowser(QWidget* parent, KexiMainWindow *mainWin, int features = DefaultFeatures);
+ virtual ~KexiBrowser();
+
+ /*! Sets project \a prj for this browser. If \a partManagerErrorMessages is not NULL
+ it will be set to error message if there's a problem with loading any KexiPart.
+ If \a itemsMimeType is empty (the default), items of all mime types are displayed,
+ items for only one mime type are displayed. In the latter case, no group (parent)
+ items are displayed.
+ Previous items are removed. */
+ void setProject(KexiProject* prj, const QString& itemsMimeType = QString::null,
+ QString* partManagerErrorMessages = 0);
+
+ /*! \return items' mime type previously set by setProject. Returns empty string
+ if setProject() was not executed yet or itemsMimeType argument of setProject() was
+ empty (i.e. all mime types are displayed). */
+ QString itemsMimeType() const;
+
+ KexiPart::Item* selectedPartItem() const;
+
+ void installEventFilter ( const QObject * filterObj );
+ virtual bool eventFilter ( QObject *o, QEvent * e );
+
+ bool actionEnabled(const QCString& actionName) const;
+
+ public slots:
+ KexiBrowserItem* addGroup(KexiPart::Info& info);
+ KexiBrowserItem* addItem(KexiPart::Item& item);
+ void slotRemoveItem(const KexiPart::Item &item);
+ virtual void setFocus();
+ void updateItemName(KexiPart::Item& item, bool dirty);
+ void selectItem(KexiPart::Item& item);
+ void clearSelection();
+ void clear();
+
+ //! Sets by main window to disable actions that may try to modify the project.
+ //! Does not disable actions like opening objects.
+ void setReadOnly(bool set);
+
+ bool isReadOnly() const;
+
+ signals:
+ void openItem( KexiPart::Item*, int viewMode );
+
+ /*! this signal is emmited when user double clicked (or single -depending on settings)
+ or pressed return ky on the part item.
+ This signal differs from openItem() signal in that if the object is already opened
+ in view mode other than \a viewMode, the mode is not changed. */
+ void openOrActivateItem( KexiPart::Item*, int viewMode );
+
+ void newItem( KexiPart::Info* );
+
+ void removeItem( KexiPart::Item* );
+
+ void renameItem( KexiPart::Item *item, const QString& _newName, bool &succes );
+
+ void selectionChanged( KexiPart::Item* item );
+
+ void executeItem( KexiPart::Item* );
+
+ void exportItemAsDataTable( KexiPart::Item* );
+
+ void printItem( KexiPart::Item* );
+
+ void pageSetupForItem( KexiPart::Item* );
+
+ protected slots:
+ void slotContextMenu(KListView*, QListViewItem *i, const QPoint &point);
+ void slotExecuteItem(QListViewItem *item);
+ void slotSelectionChanged(QListViewItem* i);
+ void slotSettingsChanged(int);
+ void slotNewObjectPopupAboutToShow();
+
+ void slotNewObject();
+ void slotOpenObject();
+ void slotDesignObject();
+ void slotEditTextObject();
+ void slotRemove();
+ void slotCut();
+ void slotCopy();
+ void slotPaste();
+ void slotRename();
+ void slotExecuteObject();
+ void slotExportAsDataTable();
+ void slotPrintItem();
+ void slotPageSetupForItem();
+
+ protected:
+ void itemRenameDone();
+ KexiBrowserItem* addItem(KexiPart::Item& item, KexiBrowserItem *parent, KexiPart::Info* info);
+
+ KexiMainWindow *m_mainWin;
+ int m_features;
+ KexiBrowserListView *m_list;
+ KActionCollection *m_actions;
+ QAsciiDict<KexiBrowserItem> m_baseItems;
+ QIntDict<KexiBrowserItem> m_normalItems;
+ KPopupMenu *m_itemPopup, *m_partPopup;
+ KAction *m_deleteAction, *m_renameAction,
+ *m_newObjectAction, // *m_newObjectToolbarAction,
+ *m_openAction, *m_designAction, *m_editTextAction,
+ *m_executeAction,
+ *m_dataExportAction, *m_printAction, *m_pageSetupAction;
+ KActionMenu* m_exportActionMenu;
+ KPopupMenu* m_newObjectPopup;
+ int m_itemPopupTitle_id, m_partPopupTitle_id,
+ m_openAction_id, m_designAction_id, m_editTextAction_id,
+ m_executeAction_id,
+ m_exportActionMenu_id, m_exportActionMenu_id_sep,
+ m_printAction_id, m_pageSetupAction_id, m_pageSetupAction_id_sep;
+
+ KexiPart::Part *m_prevSelectedPart;
+ KToolBar *m_toolbar;
+ KexiSmallToolButton *m_newObjectToolButton, *m_deleteObjectToolButton;
+ QString m_itemsMimeType;
+ bool m_singleClick : 1;
+ bool m_readOnly : 1;
+};
+
+#endif
diff --git a/kexi/widget/kexibrowser_p.h b/kexi/widget/kexibrowser_p.h
new file mode 100644
index 000000000..b5610d24c
--- /dev/null
+++ b/kexi/widget/kexibrowser_p.h
@@ -0,0 +1,43 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIBROWSER_P_H
+#define KEXIBROWSER_P_H
+
+#include <klistview.h>
+
+/*! @internal */
+class KexiBrowserListView : public KListView
+{
+ Q_OBJECT
+ public:
+ KexiBrowserListView(QWidget *parent);
+ ~KexiBrowserListView();
+
+ virtual bool isExecuteArea( const QPoint& point );
+
+ bool nameEndsWithAsterisk : 1;
+ bool enableExecuteArea : 1; //!< used in isExecuteArea()
+ public slots:
+ virtual void rename(QListViewItem *item, int c);
+ protected:
+};
+
+#endif
diff --git a/kexi/widget/kexibrowseritem.cpp b/kexi/widget/kexibrowseritem.cpp
new file mode 100644
index 000000000..087039f06
--- /dev/null
+++ b/kexi/widget/kexibrowseritem.cpp
@@ -0,0 +1,91 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002-2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexibrowseritem.h"
+
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <core/kexipartinfo.h>
+
+KexiBrowserItem::KexiBrowserItem(KListView *parent, KexiPart::Info *i)
+ : KListViewItem(parent, i->groupName())
+ , m_info(i)
+ , m_item(0)
+{
+ setPixmap(0, SmallIcon(i->itemIcon()));
+ setOpen(true);
+//ugly setSelectable(false);
+ initItem();
+ m_fifoSorting = 1; //because this is top level item
+}
+
+KexiBrowserItem::KexiBrowserItem(KListViewItem *parent, KexiPart::Info *i, KexiPart::Item *item)
+ : KListViewItem(parent, item->name())
+ , m_info(i)
+ , m_item(item)
+{
+ setPixmap(0, SmallIcon(i->itemIcon()));
+ initItem();
+}
+
+KexiBrowserItem::KexiBrowserItem(KListView *parent, KexiPart::Info *i, KexiPart::Item *item)
+ : KListViewItem(parent, item->name())
+ , m_info(i)
+ , m_item(item)
+{
+ setPixmap(0, SmallIcon(i->itemIcon()));
+ initItem();
+}
+
+KexiBrowserItem::~KexiBrowserItem()
+{
+}
+
+void KexiBrowserItem::initItem()
+{
+ m_fifoSorting = 0;
+ int sortKey = 0;
+ // set sorting key with FIFO order
+ if (parent()) {
+ sortKey = parent()->childCount();
+ } else if (listView()) {
+ sortKey = listView()->childCount();
+ }
+ m_sortKey.sprintf("%2.2d",sortKey);
+// kdDebug() << "m_sortKey=" << m_sortKey << endl;
+}
+
+void
+KexiBrowserItem::clearChildren()
+{
+ KexiBrowserItem* child;
+
+ while((child = static_cast<KexiBrowserItem*>(firstChild())))
+ {
+ delete child;
+ }
+}
+
+QString KexiBrowserItem::key( int column, bool ascending ) const
+{
+// kdDebug() << "KexiBrowserItem::key() : " << (m_fifoSorting ? m_sortKey : KListViewItem::key(column,ascending)) << endl;
+ return m_fifoSorting ? m_sortKey : KListViewItem::key(column,ascending);
+}
+
diff --git a/kexi/widget/kexibrowseritem.h b/kexi/widget/kexibrowseritem.h
new file mode 100644
index 000000000..12c2fef64
--- /dev/null
+++ b/kexi/widget/kexibrowseritem.h
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002-2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIBROWSERITEM_H
+#define KEXIBROWSERITEM_H
+
+#include <klistview.h>
+#include <qstring.h>
+
+#include <core/kexipartitem.h>
+
+namespace KexiPart
+{
+ class Info;
+}
+
+//! @short List view item for the navigator widget (KexiBrowser)
+//! Used for creating group items as well as object items
+class KEXIEXTWIDGETS_EXPORT KexiBrowserItem : public KListViewItem
+{
+ public:
+ //! Creates group item for part \a i
+ KexiBrowserItem(KListView *parent, KexiPart::Info *i);
+
+ //! Creates item for object \a item defined by part \a i for \a parent
+ KexiBrowserItem(KListViewItem *parent, KexiPart::Info *i, KexiPart::Item *item);
+
+ //! Creates item for object \a item defined by part \a i, without parent
+ //! (used in a case when KexiBrowser::itemsMimeType() is not empty)
+ KexiBrowserItem(KListView *parent, KexiPart::Info *i, KexiPart::Item *item);
+
+ virtual ~KexiBrowserItem();
+
+ void clearChildren();
+
+ //! \return part info; should not be null.
+ KexiPart::Info *info() const { return m_info; }
+
+ //! \return part item. Can be null if the browser item is a "folder", i.e. a parent node.
+ KexiPart::Item* item() const { return m_item; }
+
+ protected:
+ void initItem();
+ virtual QString key( int column, bool ascending ) const;
+
+ KexiPart::Info *m_info;
+ KexiPart::Item *m_item;
+
+ QString m_sortKey;
+ bool m_fifoSorting : 1;
+};
+
+#endif
diff --git a/kexi/widget/kexicharencodingcombobox.cpp b/kexi/widget/kexicharencodingcombobox.cpp
new file mode 100644
index 000000000..c1f505429
--- /dev/null
+++ b/kexi/widget/kexicharencodingcombobox.cpp
@@ -0,0 +1,114 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexicharencodingcombobox.h"
+
+#include <qtextcodec.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kcharsets.h>
+
+KexiCharacterEncodingComboBox::KexiCharacterEncodingComboBox(
+ QWidget* parent, const QString& selectedEncoding )
+ : KComboBox( parent, "KexiCharacterEncodingComboBox" )
+ , m_defaultEncodingAdded(false)
+{
+ QString defaultEncoding(QString::fromLatin1(KGlobal::locale()->encoding()));
+ QString defaultEncodingDescriptiveName;
+
+ QString _selectedEncoding = selectedEncoding;
+ if (_selectedEncoding.isEmpty())
+ _selectedEncoding = QString::fromLatin1(KGlobal::locale()->encoding());
+
+ QStringList descEncodings(KGlobal::charsets()->descriptiveEncodingNames());
+ QStringList::ConstIterator it = descEncodings.constBegin();
+
+ for (uint id = 0; it!=descEncodings.constEnd(); ++it)
+ {
+ bool found = false;
+ QString name( KGlobal::charsets()->encodingForName( *it ) );
+ QTextCodec *codecForEnc = KGlobal::charsets()->codecForName(name, found);
+ if (found) {
+ insertItem(*it);
+ if (codecForEnc->name() == defaultEncoding || name == defaultEncoding) {
+ defaultEncodingDescriptiveName = *it;
+ //remember, do not add, will be prepended later
+ }
+ else {
+ m_encodingDescriptionForName.insert(name, *it);
+ }
+ if (codecForEnc->name() == _selectedEncoding || name == _selectedEncoding) {
+ setCurrentItem(id);
+ }
+ id++;
+ }
+ }
+
+ //prepend default encoding, if present
+ if (!defaultEncodingDescriptiveName.isEmpty()) {
+ m_defaultEncodingAdded = true;
+ QString desc = i18n("Text encoding: Default", "Default: %1")
+ .arg(defaultEncodingDescriptiveName);
+ insertItem( desc, 0 );
+ if (_selectedEncoding==defaultEncoding) {
+ setCurrentItem(0);
+ }
+ else
+ setCurrentItem(currentItem()+1);
+ m_encodingDescriptionForName.insert(defaultEncoding, desc);
+ }
+}
+
+KexiCharacterEncodingComboBox::~KexiCharacterEncodingComboBox()
+{
+}
+
+QString KexiCharacterEncodingComboBox::selectedEncoding() const
+{
+ if (defaultEncodingSelected()) {
+ return QString::fromLatin1(KGlobal::locale()->encoding());
+ }
+ else {
+ return KGlobal::charsets()->encodingForName( currentText() );
+ }
+}
+
+void KexiCharacterEncodingComboBox::setSelectedEncoding(const QString& encodingName)
+{
+ QString desc = m_encodingDescriptionForName[encodingName];
+ if (desc.isEmpty()) {
+ kdWarning() << "KexiCharacterEncodingComboBox::setSelectedEncoding(): "
+ "no such encoding \"" << encodingName << "\"" << endl;
+ return;
+ }
+ setCurrentText(desc);
+}
+
+bool KexiCharacterEncodingComboBox::defaultEncodingSelected() const
+{
+ return m_defaultEncodingAdded && 0==currentItem();
+}
+
+void KexiCharacterEncodingComboBox::selectDefaultEncoding()
+{
+ if (m_defaultEncodingAdded)
+ setCurrentItem(0);
+}
diff --git a/kexi/widget/kexicharencodingcombobox.h b/kexi/widget/kexicharencodingcombobox.h
new file mode 100644
index 000000000..9af5a69f5
--- /dev/null
+++ b/kexi/widget/kexicharencodingcombobox.h
@@ -0,0 +1,48 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXICHARENCODINGCOMBO_H
+#define KEXICHARENCODINGCOMBO_H
+
+#include <qmap.h>
+#include <kcombobox.h>
+
+/*! @short Combobox widget providing a list of possible character encodings.
+*/
+class KEXIEXTWIDGETS_EXPORT KexiCharacterEncodingComboBox : public KComboBox
+{
+ public:
+ //! Constructs a new combobox. \a selectedEncoding can be provided to preselect encoding.
+ //! If it is not provided, default encoding is selected for current system settings.
+ KexiCharacterEncodingComboBox( QWidget* parent = 0,
+ const QString& selectedEncoding = QString::null );
+ ~KexiCharacterEncodingComboBox();
+
+ QString selectedEncoding() const;
+ void setSelectedEncoding(const QString& encodingName);
+ //! Selects default encoding, if present
+ void selectDefaultEncoding();
+ bool defaultEncodingSelected() const;
+
+ protected:
+ QMap<QString,QString> m_encodingDescriptionForName;
+ bool m_defaultEncodingAdded : 1;
+};
+
+#endif
diff --git a/kexi/widget/kexicustompropertyfactory.cpp b/kexi/widget/kexicustompropertyfactory.cpp
new file mode 100644
index 000000000..3df9e2339
--- /dev/null
+++ b/kexi/widget/kexicustompropertyfactory.cpp
@@ -0,0 +1,110 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicustompropertyfactory.h"
+#include "kexicustompropertyfactory_p.h"
+#include <kexiutils/identifier.h>
+
+#include <koproperty/customproperty.h>
+
+using namespace KoProperty;
+
+//! @internal
+class PixmapIdCustomProperty : public CustomProperty
+{
+ public:
+ PixmapIdCustomProperty(Property *parent)
+ : CustomProperty(parent) {
+ }
+ virtual ~PixmapIdCustomProperty() {};
+ virtual void setValue(const QVariant &value, bool rememberOldValue) {
+ Q_UNUSED( value );
+ Q_UNUSED( rememberOldValue);
+ }
+ virtual QVariant value() const { return m_property->value(); }
+ virtual bool handleValue() const {
+ return false; //m_property->type()==KexiCustomPropertyFactory::PixmapData;
+ }
+};
+
+//! @internal
+class IdentifierCustomProperty : public CustomProperty
+{
+ public:
+ IdentifierCustomProperty(Property *parent)
+ : CustomProperty(parent) {
+ }
+ virtual ~IdentifierCustomProperty() {};
+ virtual void setValue(const QVariant &value, bool rememberOldValue)
+ {
+ Q_UNUSED(rememberOldValue);
+ if (!value.toString().isEmpty())
+ m_value = KexiUtils::string2Identifier(value.toString()).lower();
+ }
+ virtual QVariant value() const { return m_value; }
+ virtual bool handleValue() const {
+ return true;
+ }
+ QString m_value;
+};
+
+//---------------
+
+KexiCustomPropertyFactory::KexiCustomPropertyFactory(QObject* parent)
+: CustomPropertyFactory(parent)
+{
+}
+
+KexiCustomPropertyFactory::~KexiCustomPropertyFactory()
+{
+}
+
+CustomProperty* KexiCustomPropertyFactory::createCustomProperty(Property *parent)
+{
+ const int type = parent->type();
+ if (type==(int)KexiCustomPropertyFactory::PixmapId)
+ return new PixmapIdCustomProperty(parent);
+ else if (type==(int)KexiCustomPropertyFactory::Identifier)
+ return new IdentifierCustomProperty(parent);
+ return 0;
+}
+
+Widget* KexiCustomPropertyFactory::createCustomWidget(Property *prop)
+{
+ const int type = prop->type();
+ if (type==(int)KexiCustomPropertyFactory::PixmapId)
+ return new KexiImagePropertyEdit(prop);
+ else if (type==(int)KexiCustomPropertyFactory::Identifier)
+ return new KexiIdentifierPropertyEdit(prop);
+
+ return 0;
+}
+
+void KexiCustomPropertyFactory::init()
+{
+ if (KoProperty::FactoryManager::self()->factoryForEditorType(KexiCustomPropertyFactory::PixmapId))
+ return; //already registered
+
+ // register custom editors and properties
+ KexiCustomPropertyFactory *factory = new KexiCustomPropertyFactory(KoProperty::FactoryManager::self());
+ QValueList<int> types;
+ types << KexiCustomPropertyFactory::PixmapId << KexiCustomPropertyFactory::Identifier;
+ KoProperty::FactoryManager::self()->registerFactoryForProperties(types, factory);
+ KoProperty::FactoryManager::self()->registerFactoryForEditors(types, factory);
+}
diff --git a/kexi/widget/kexicustompropertyfactory.h b/kexi/widget/kexicustompropertyfactory.h
new file mode 100644
index 000000000..c6e6e9732
--- /dev/null
+++ b/kexi/widget/kexicustompropertyfactory.h
@@ -0,0 +1,45 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXICUSTOMPROPFACTORY_H
+#define KEXICUSTOMPROPFACTORY_H
+
+#include <koproperty/property.h>
+#include <koproperty/factory.h>
+
+//! Kexi-specific custom property factory for KoProperty library
+class KEXIEXTWIDGETS_EXPORT KexiCustomPropertyFactory : public KoProperty::CustomPropertyFactory
+{
+ public:
+ enum PropertyType {
+ PixmapId = KoProperty::UserDefined+0, //!< Shared Kexi pixmap
+ Identifier = KoProperty::UserDefined+1 //!< string allowing nonempty identifiers
+ };
+
+ //! Called once to register all propert and editor types provided by this factory.
+ static void init();
+
+ KexiCustomPropertyFactory(QObject* parent);
+ virtual ~KexiCustomPropertyFactory();
+
+ virtual KoProperty::CustomProperty* createCustomProperty(KoProperty::Property *parent);
+ virtual KoProperty::Widget* createCustomWidget(KoProperty::Property *prop);
+};
+
+#endif
diff --git a/kexi/widget/kexicustompropertyfactory_p.cpp b/kexi/widget/kexicustompropertyfactory_p.cpp
new file mode 100644
index 000000000..0e0a054d7
--- /dev/null
+++ b/kexi/widget/kexicustompropertyfactory_p.cpp
@@ -0,0 +1,108 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicustompropertyfactory_p.h"
+
+#include <qlineedit.h>
+#include <kdebug.h>
+#include <koproperty/property.h>
+#include <kexiutils/identifier.h>
+
+using namespace KoProperty;
+
+KexiImagePropertyEdit::KexiImagePropertyEdit(
+ Property *property, QWidget *parent, const char *name)
+ : PixmapEdit(property, parent, name)
+ , m_id(0)
+{
+}
+
+KexiImagePropertyEdit::~KexiImagePropertyEdit()
+{
+}
+
+void KexiImagePropertyEdit::selectPixmap()
+{
+ QString fileName( PixmapEdit::selectPixmapFileName() );
+ if (fileName.isEmpty())
+ return;
+ KexiBLOBBuffer::Handle h(KexiBLOBBuffer::self()->insertPixmap( KURL(fileName) ));
+ setValue((uint)/*! @todo unsafe*/h.id());
+#if 0 //will be reenabled for new image collection
+ if(!m_manager->activeForm() || !property())
+ return;
+
+ ObjectTreeItem *item = m_manager->activeForm()->objectTree()->lookup(m_manager->activeForm()->selectedWidget()->name());
+ QString name = item ? item->pixmapName(property()->name()) : "";
+ PixmapCollectionChooser dialog( m_manager->activeForm()->pixmapCollection(), name, topLevelWidget() );
+ if(dialog.exec() == QDialog::Accepted) {
+ setValue(dialog.pixmap(), true);
+ item->setPixmapName(property()->name(), dialog.pixmapName());
+ }
+#endif
+}
+
+QVariant KexiImagePropertyEdit::value() const
+{
+ return (uint)/*! @todo unsafe*/m_id;
+}
+
+void KexiImagePropertyEdit::setValue(const QVariant &value, bool emitChange)
+{
+ m_id = value.toInt();
+ PixmapEdit::setValue(KexiBLOBBuffer::self()->objectForId(m_id).pixmap(), emitChange);
+}
+
+void KexiImagePropertyEdit::drawViewer(QPainter *p, const QColorGroup &cg, const QRect &r,
+ const QVariant &value)
+{
+ KexiBLOBBuffer::Handle h( KexiBLOBBuffer::self()->objectForId(value.toInt()) );
+ PixmapEdit::drawViewer(p, cg, r, h.pixmap());
+}
+
+//----------------------------------------------------------------
+
+KexiIdentifierPropertyEdit::KexiIdentifierPropertyEdit(
+ Property *property, QWidget *parent, const char *name)
+ : StringEdit(property, parent, name)
+{
+ m_edit->setValidator(
+ new KexiUtils::IdentifierValidator(m_edit, "KexiIdentifierPropertyEdit Validator") );
+}
+
+KexiIdentifierPropertyEdit::~KexiIdentifierPropertyEdit()
+{
+}
+
+void KexiIdentifierPropertyEdit::setValue(const QVariant &value, bool emitChange)
+{
+ QString string(value.toString());
+ if (string.isEmpty()) {
+ kdWarning() << "KexiIdentifierPropertyEdit::setValue(): "
+ "Value cannot be empty. This call has no effect." << endl;
+ return;
+ }
+ QString identifier( KexiUtils::string2Identifier(string) );
+ if (identifier!=string)
+ kdDebug() << QString("KexiIdentifierPropertyEdit::setValue(): "
+ "String \"%1\" converted to identifier \"%2\".").arg(string).arg(identifier) << endl;
+ StringEdit::setValue( identifier, emitChange );
+}
+
+#include "kexicustompropertyfactory_p.moc"
diff --git a/kexi/widget/kexicustompropertyfactory_p.h b/kexi/widget/kexicustompropertyfactory_p.h
new file mode 100644
index 000000000..f1ec4b0f2
--- /dev/null
+++ b/kexi/widget/kexicustompropertyfactory_p.h
@@ -0,0 +1,71 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXICUSTOMPROPFACTORY_P_H
+#define KEXICUSTOMPROPFACTORY_P_H
+
+#include <koproperty/editors/pixmapedit.h>
+#include <koproperty/editors/stringedit.h>
+#include <kexiblobbuffer.h>
+
+//! Kexi-specific image editor for property editor's item
+class KexiImagePropertyEdit : public KoProperty::PixmapEdit
+{
+ Q_OBJECT
+
+ public:
+ KexiImagePropertyEdit(KoProperty::Property *property,
+ QWidget *parent=0, const char *name=0);
+ virtual ~KexiImagePropertyEdit();
+
+ virtual QVariant value() const;
+ virtual void setValue(const QVariant &value, bool emitChange=true);
+ virtual void drawViewer(QPainter *p, const QColorGroup &cg, const QRect &r,
+ const QVariant &value);
+
+ public slots:
+ virtual void selectPixmap();
+
+ protected:
+ KexiBLOBBuffer::Id_t m_id;
+};
+
+/*! Identifier editor based on ordinary string editor but always keeps a valid identifier
+ or empty value. It's line edit has IdentifierValidator::IdentifierValidator set, so user
+ is unable to enter invalid characters. Any chages to a null value or empty string,
+ have no effect.
+
+ @todo move this to koproperty library (when KexiUtils is moves to kofficecore)
+ */
+class KexiIdentifierPropertyEdit : public KoProperty::StringEdit
+{
+ Q_OBJECT
+
+ public:
+ KexiIdentifierPropertyEdit(KoProperty::Property *property,
+ QWidget *parent=0, const char *name=0);
+ virtual ~KexiIdentifierPropertyEdit();
+
+ /*! Reimplemented: sets \a value but it is converted to identifier
+ using KexiUtils::string2Identifier().
+ If \a value is null or empty string, this method has no effect. */
+ virtual void setValue(const QVariant &value, bool emitChange=true);
+};
+
+#endif
diff --git a/kexi/widget/kexidataawareview.cpp b/kexi/widget/kexidataawareview.cpp
new file mode 100644
index 000000000..418f0aa14
--- /dev/null
+++ b/kexi/widget/kexidataawareview.cpp
@@ -0,0 +1,383 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidataawareview.h"
+
+#include <kexidataawareobjectiface.h>
+#include <utils/kexisharedactionclient.h>
+#include <core/keximainwindow.h>
+
+#include <qlayout.h>
+
+#include <kpopupmenu.h>
+
+KexiDataAwareView::KexiDataAwareView(KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : KexiViewBase(mainWin, parent, name)
+ , KexiSearchAndReplaceViewInterface()
+ , m_internalView(0)
+ , m_actionClient(0)
+ , m_dataAwareObject(0)
+{
+}
+
+void KexiDataAwareView::init( QWidget* viewWidget, KexiSharedActionClient* actionClient,
+ KexiDataAwareObjectInterface* dataAwareObject, bool noDataAware )
+{
+ m_internalView = viewWidget;
+ m_actionClient = actionClient;
+ m_dataAwareObject = dataAwareObject;
+ setViewWidget(m_internalView, true);
+
+ if (!noDataAware) {
+ m_dataAwareObject->connectCellSelectedSignal(this, SLOT(slotCellSelected(int,int)));
+
+ //! before closing - we'are accepting editing
+ connect(this, SIGNAL(closing(bool&)), this, SLOT(slotClosing(bool&)));
+
+ //! updating actions on start/stop editing
+ m_dataAwareObject->connectRowEditStartedSignal(this, SLOT(slotUpdateRowActions(int)));
+ m_dataAwareObject->connectRowEditTerminatedSignal(this, SLOT(slotUpdateRowActions(int)));
+ m_dataAwareObject->connectReloadActionsSignal(this, SLOT(reloadActions()));
+ }
+
+ QVBoxLayout *box = new QVBoxLayout(this);
+ box->addWidget(m_internalView);
+
+ setMinimumSize(m_internalView->minimumSizeHint().width(),
+ m_internalView->minimumSizeHint().height());
+ resize( preferredSizeHint( m_internalView->sizeHint() ) );
+ setFocusProxy(m_internalView);
+
+ if (!noDataAware) {
+ initActions();
+ reloadActions();
+ }
+}
+
+void KexiDataAwareView::initActions()
+{
+ plugSharedAction("edit_delete_row", this, SLOT(deleteCurrentRow()));
+ m_actionClient->plugSharedAction(sharedAction("edit_delete_row")); //for proper shortcut
+
+ plugSharedAction("edit_delete", this, SLOT(deleteAndStartEditCurrentCell()));
+ m_actionClient->plugSharedAction(sharedAction("edit_delete")); //for proper shortcut
+
+ plugSharedAction("edit_edititem", this, SLOT(startEditOrToggleValue()));
+ m_actionClient->plugSharedAction(sharedAction("edit_edititem")); //for proper shortcut
+
+ plugSharedAction("data_save_row", this, SLOT(acceptRowEdit()));
+ m_actionClient->plugSharedAction(sharedAction("data_save_row")); //for proper shortcut
+
+ plugSharedAction("data_cancel_row_changes", this, SLOT(cancelRowEdit()));
+ m_actionClient->plugSharedAction(sharedAction("data_cancel_row_changes")); //for proper shortcut
+
+ if (m_dataAwareObject->isSortingEnabled()) {
+ plugSharedAction("data_sort_az", this, SLOT(sortAscending()));
+ plugSharedAction("data_sort_za", this, SLOT(sortDescending()));
+ }
+
+ m_actionClient->plugSharedAction(sharedAction("edit_insert_empty_row")); //for proper shortcut
+
+ setAvailable("data_sort_az", m_dataAwareObject->isSortingEnabled());
+ setAvailable("data_sort_za", m_dataAwareObject->isSortingEnabled());
+//! \todo plugSharedAction("data_filter", this, SLOT(???()));
+
+ plugSharedAction("data_go_to_first_record", this, SLOT(slotGoToFirstRow()));
+ plugSharedAction("data_go_to_previous_record", this, SLOT(slotGoToPreviusRow()));
+ plugSharedAction("data_go_to_next_record", this, SLOT(slotGoToNextRow()));
+ plugSharedAction("data_go_to_last_record", this, SLOT(slotGoToLastRow()));
+ plugSharedAction("data_go_to_new_record", this, SLOT(slotGoToNewRow()));
+
+//! \todo update availability
+ setAvailable("data_go_to_first_record", true);
+ setAvailable("data_go_to_previous_record", true);
+ setAvailable("data_go_to_next_record", true);
+ setAvailable("data_go_to_last_record", true);
+ setAvailable("data_go_to_new_record", true);
+
+ plugSharedAction("edit_copy", this, SLOT(copySelection()));
+ m_actionClient->plugSharedAction(sharedAction("edit_copy")); //for proper shortcut
+
+ plugSharedAction("edit_cut", this, SLOT(cutSelection()));
+ m_actionClient->plugSharedAction(sharedAction("edit_cut")); //for proper shortcut
+
+ plugSharedAction("edit_paste", this, SLOT(paste()));
+ m_actionClient->plugSharedAction(sharedAction("edit_paste")); //for proper shortcut
+
+// plugSharedAction("edit_find", this, SLOT(editFind()));
+// m_actionClient->plugSharedAction(sharedAction("edit_find")); //for proper shortcut
+
+// plugSharedAction("edit_findnext", this, SLOT(editFindNext()));
+// m_actionClient->plugSharedAction(sharedAction("edit_findnext")); //for proper shortcut
+
+// plugSharedAction("edit_findprevious", this, SLOT(editFindPrevious()));
+// m_actionClient->plugSharedAction(sharedAction("edit_findprev")); //for proper shortcut
+
+//! @todo plugSharedAction("edit_replace", this, SLOT(editReplace()));
+//! @todo m_actionClient->plugSharedAction(sharedAction("edit_replace")); //for proper shortcut
+
+// setAvailable("edit_find", true);
+// setAvailable("edit_findnext", true);
+// setAvailable("edit_findprevious", true);
+//! @todo setAvailable("edit_replace", true);
+}
+
+void KexiDataAwareView::slotUpdateRowActions(int row)
+{
+ const bool ro = m_dataAwareObject->isReadOnly();
+// const bool inserting = m_dataAwareObject->isInsertingEnabled();
+ const bool deleting = m_dataAwareObject->isDeleteEnabled();
+ const bool emptyInserting = m_dataAwareObject->isEmptyRowInsertingEnabled();
+ const bool editing = m_dataAwareObject->rowEditing();
+ const bool sorting = m_dataAwareObject->isSortingEnabled();
+ const int rows = m_dataAwareObject->rows();
+
+ setAvailable("edit_cut", !ro);
+ setAvailable("edit_paste", !ro);
+ setAvailable("edit_delete", !ro); // && !(inserting && row==rows));
+ setAvailable("edit_delete_row", !ro && !(deleting && row==rows));
+ setAvailable("edit_insert_empty_row", !ro && emptyInserting);
+ setAvailable("edit_clear_table", !ro && deleting && rows>0);
+ setAvailable("data_save_row", editing);
+ setAvailable("data_cancel_row_changes", editing);
+ setAvailable("data_sort_az", sorting);
+ setAvailable("data_sort_za", sorting);
+}
+
+QWidget* KexiDataAwareView::mainWidget()
+{
+ return m_internalView;
+}
+
+QSize KexiDataAwareView::minimumSizeHint() const
+{
+ return m_internalView ? m_internalView->minimumSizeHint() : QSize(0,0);//KexiViewBase::minimumSizeHint();
+}
+
+QSize KexiDataAwareView::sizeHint() const
+{
+ return m_internalView ? m_internalView->sizeHint() : QSize(0,0);//KexiViewBase::sizeHint();
+}
+
+void KexiDataAwareView::updateActions(bool activated)
+{
+ setAvailable("data_sort_az", m_dataAwareObject->isSortingEnabled());
+ setAvailable("data_sort_za", m_dataAwareObject->isSortingEnabled());
+ KexiViewBase::updateActions(activated);
+}
+
+void KexiDataAwareView::reloadActions()
+{
+// m_view->initActions(guiClient()->actionCollection());
+//warning FIXME Move this to the table part
+/*
+ kdDebug()<<"INIT ACTIONS***********************************************************************"<<endl;
+ new KAction(i18n("Filter"), "filter", 0, this, SLOT(filter()), actionCollection(), "tablepart_filter");
+ setXMLFile("kexidatatableui.rc");
+*/
+ m_dataAwareObject->contextMenu()->clear();
+
+ plugSharedAction("edit_cut", m_dataAwareObject->contextMenu());
+ plugSharedAction("edit_copy", m_dataAwareObject->contextMenu());
+ plugSharedAction("edit_paste", m_dataAwareObject->contextMenu());
+
+ bool separatorNeeded = true;
+
+ unplugSharedAction("edit_clear_table");
+ plugSharedAction("edit_clear_table", this, SLOT(deleteAllRows()));
+
+ if (m_dataAwareObject->isEmptyRowInsertingEnabled()) {
+ unplugSharedAction("edit_insert_empty_row");
+ plugSharedAction("edit_insert_empty_row", m_internalView, SLOT(insertEmptyRow()));
+ if (separatorNeeded)
+ m_dataAwareObject->contextMenu()->insertSeparator();
+ plugSharedAction("edit_insert_empty_row", m_dataAwareObject->contextMenu());
+ }
+ else {
+ unplugSharedAction("edit_insert_empty_row");
+ unplugSharedAction("edit_insert_empty_row", m_dataAwareObject->contextMenu());
+ }
+
+ if (m_dataAwareObject->isDeleteEnabled()) {
+ if (separatorNeeded)
+ m_dataAwareObject->contextMenu()->insertSeparator();
+ plugSharedAction("edit_delete", m_dataAwareObject->contextMenu());
+ plugSharedAction("edit_delete_row", m_dataAwareObject->contextMenu());
+ }
+ else {
+ unplugSharedAction("edit_delete_row", m_dataAwareObject->contextMenu());
+ unplugSharedAction("edit_delete_row", m_dataAwareObject->contextMenu());
+ }
+ //if (!m_view->isSortingEnabled()) {
+// unplugSharedAction("data_sort_az");
+// unplugSharedAction("data_sort_za");
+ //}
+ setAvailable("data_sort_az", m_dataAwareObject->isSortingEnabled());
+ setAvailable("data_sort_za", m_dataAwareObject->isSortingEnabled());
+
+ slotCellSelected( m_dataAwareObject->currentColumn(), m_dataAwareObject->currentRow() );
+}
+
+void KexiDataAwareView::slotCellSelected(int /*col*/, int row)
+{
+ slotUpdateRowActions(row);
+}
+
+void KexiDataAwareView::deleteAllRows()
+{
+ m_dataAwareObject->deleteAllRows(true/*ask*/, true/*repaint*/);
+}
+
+void KexiDataAwareView::deleteCurrentRow()
+{
+ m_dataAwareObject->deleteCurrentRow();
+}
+
+void KexiDataAwareView::deleteAndStartEditCurrentCell()
+{
+ m_dataAwareObject->deleteAndStartEditCurrentCell();
+}
+
+void KexiDataAwareView::startEditOrToggleValue()
+{
+ m_dataAwareObject->startEditOrToggleValue();
+}
+
+bool KexiDataAwareView::acceptRowEdit()
+{
+ return m_dataAwareObject->acceptRowEdit();
+}
+
+void KexiDataAwareView::slotClosing(bool& cancel)
+{
+ if (!acceptRowEdit())
+ cancel = true;
+}
+
+void KexiDataAwareView::cancelRowEdit()
+{
+ m_dataAwareObject->cancelRowEdit();
+}
+
+void KexiDataAwareView::sortAscending()
+{
+ m_dataAwareObject->sortAscending();
+}
+
+void KexiDataAwareView::sortDescending()
+{
+ m_dataAwareObject->sortDescending();
+}
+
+void KexiDataAwareView::copySelection()
+{
+ m_dataAwareObject->copySelection();
+}
+
+void KexiDataAwareView::cutSelection()
+{
+ m_dataAwareObject->cutSelection();
+}
+
+void KexiDataAwareView::paste()
+{
+ m_dataAwareObject->paste();
+}
+
+void KexiDataAwareView::slotGoToFirstRow() { m_dataAwareObject->selectFirstRow(); }
+void KexiDataAwareView::slotGoToPreviusRow() { m_dataAwareObject->selectPrevRow(); }
+void KexiDataAwareView::slotGoToNextRow() { m_dataAwareObject->selectNextRow(); }
+void KexiDataAwareView::slotGoToLastRow() { m_dataAwareObject->selectLastRow(); }
+void KexiDataAwareView::slotGoToNewRow() { m_dataAwareObject->addNewRecordRequested(); }
+
+bool KexiDataAwareView::setupFindAndReplace(QStringList& columnNames, QStringList& columnCaptions,
+ QString& currentColumnName)
+{
+ if (!dataAwareObject() || !dataAwareObject()->data())
+ return false;
+ KexiTableViewColumn::List columns( dataAwareObject()->data()->columns );
+ for (KexiTableViewColumn::ListIterator it(columns); it.current(); ++it) {
+ if (!it.current()->visible())
+ continue;
+ columnNames.append( it.current()->field()->name() );
+ columnCaptions.append( it.current()->captionAliasOrName() );
+ }
+
+ //update "look in" selection if there was any
+ const int currentColumnNumber = dataAwareObject()->currentColumn();
+ if (currentColumnNumber!=-1) {
+ KexiTableViewColumn *col = columns.at( currentColumnNumber );
+ if (col && col->field())
+ currentColumnName = col->field()->name();
+ }
+ return true;
+}
+
+tristate KexiDataAwareView::find(const QVariant& valueToFind,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool next)
+{
+ if (!dataAwareObject() || !dataAwareObject()->data())
+ return cancelled;
+
+// const KexiDataAwareObjectInterface::FindAndReplaceOptions options(dlg->options());
+/* if (res == KexiFindDialog::Find) {*/
+// QVariant valueToFind(dlg->valueToFind());
+ return dataAwareObject()->find( valueToFind, options, next );
+/*
+//! @todo result...
+
+ }
+ else if (res == KexiFindDialog::Replace) {
+//! @todo
+ }
+ else if (res == KexiFindDialog::ReplaceAll) {
+//! @todo
+ }
+ */
+}
+
+tristate KexiDataAwareView::findNextAndReplace(const QVariant& valueToFind,
+ const QVariant& replacement,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll)
+{
+ if (!dataAwareObject() || !dataAwareObject()->data())
+ return cancelled;
+
+ return dataAwareObject()->findNextAndReplace(valueToFind, replacement, options, replaceAll);
+}
+
+/*
+void KexiDataAwareView::editFindNext()
+{
+ //! @todo reuse code from editFind()
+}
+
+void KexiDataAwareView::editFindPrevious()
+{
+ //! @todo reuse code from editFind()
+}
+
+void KexiDataAwareView::editReplace()
+{
+ //! @todo editReplace()
+ //! @todo reuse code from editFind()
+ // When ready, update KexiDataAwareView::initActions() and KexiMainWindowImpl
+}*/
+
+#include "kexidataawareview.moc"
diff --git a/kexi/widget/kexidataawareview.h b/kexi/widget/kexidataawareview.h
new file mode 100644
index 000000000..8f1d369f7
--- /dev/null
+++ b/kexi/widget/kexidataawareview.h
@@ -0,0 +1,117 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDATAAWAREVIEW_H
+#define KEXIDATAAWAREVIEW_H
+
+#include <kexiviewbase.h>
+#include <kexisearchandreplaceiface.h>
+
+class KexiDataAwareObjectInterface;
+class KexiSharedActionClient;
+
+/*! @short Provides a view displaying record-based data.
+
+ The KexiDataAwareView is used to implement differently-looking views
+ for displaying record-based data in a consistent way:
+ - tabular data views
+ - form data view
+
+ Action implementations like data editing and deleting are shared for different
+ view types to keep even better consistency.
+ The view also implements KexiSearchAndReplaceViewInterface to support search/replace features
+ used by shared KexiFindDialog.
+*/
+class KEXIEXTWIDGETS_EXPORT KexiDataAwareView : public KexiViewBase,
+ public KexiSearchAndReplaceViewInterface
+{
+ Q_OBJECT
+
+ public:
+ KexiDataAwareView(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0);
+
+ QWidget* mainWidget();
+
+ virtual QSize minimumSizeHint() const;
+
+ virtual QSize sizeHint() const;
+
+ KexiDataAwareObjectInterface* dataAwareObject() const { return m_dataAwareObject; }
+
+ /*! Sets up data for find/replace dialog, based on view's data model.
+ Implemented for KexiSearchAndReplaceViewInterface. */
+ virtual bool setupFindAndReplace(QStringList& columnNames, QStringList& columnCaptions,
+ QString& currentColumnName);
+
+ /*! Finds \a valueToFind within the view.
+ Implemented for KexiSearchAndReplaceViewInterface. */
+ virtual tristate find(const QVariant& valueToFind,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool next);
+
+ /*! Finds \a valueToFind within the view and replaces with \a replacement.
+ Implemented for KexiSearchAndReplaceViewInterface. */
+ virtual tristate findNextAndReplace(const QVariant& valueToFind,
+ const QVariant& replacement,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll);
+
+ public slots:
+ void deleteAllRows();
+ void deleteCurrentRow();
+ void deleteAndStartEditCurrentCell();
+ void startEditOrToggleValue();
+ bool acceptRowEdit();
+ void cancelRowEdit();
+ void sortAscending();
+ void sortDescending();
+ void copySelection();
+ void cutSelection();
+ void paste();
+ void slotGoToFirstRow();
+ void slotGoToPreviusRow();
+ void slotGoToNextRow();
+ void slotGoToLastRow();
+ void slotGoToNewRow();
+/* void editFind();
+ void slotFind();
+ void editFindNext();
+ void editFindPrevious();
+ void editReplace();*/
+
+ protected slots:
+// void slotCellSelected(const QVariant& v); //!< @internal
+ void slotCellSelected(int col, int row);
+ void reloadActions();
+ void slotUpdateRowActions(int row);
+ void slotClosing(bool& cancel);
+
+ protected:
+ void init( QWidget* viewWidget, KexiSharedActionClient* actionClient,
+ KexiDataAwareObjectInterface* dataAwareObject,
+ // temporary, for KexiFormView in design mode
+ bool noDataAware = false
+ );
+ void initActions();
+ virtual void updateActions(bool activated);
+
+ QWidget* m_internalView;
+ KexiSharedActionClient* m_actionClient;
+ KexiDataAwareObjectInterface* m_dataAwareObject;
+};
+
+#endif
diff --git a/kexi/widget/kexidatasourcecombobox.cpp b/kexi/widget/kexidatasourcecombobox.cpp
new file mode 100644
index 000000000..77dc771fc
--- /dev/null
+++ b/kexi/widget/kexidatasourcecombobox.cpp
@@ -0,0 +1,333 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidatasourcecombobox.h"
+
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <klistbox.h>
+
+#include <kexi.h>
+#include <kexiproject.h>
+#include <keximainwindow.h>
+#include <kexipart.h>
+#include <kexipartmanager.h>
+#include <kexipartinfo.h>
+#include <kexipartitem.h>
+
+#include <kexidb/connection.h>
+
+#ifdef KEXI_SHOW_UNIMPLEMENTED
+#define ADD_DEFINEQUERY_ROW
+#endif
+
+//! @internal
+class KexiDataSourceComboBox::Private
+{
+ public:
+ Private()
+ : tablesCount(0)
+ , prevIndex(-1)
+ , showTables(true)
+ , showQueries(true)
+ {
+ }
+ int firstTableIndex() const {
+ int index = 1; //skip empty row
+#ifdef ADD_DEFINEQUERY_ROW
+ index++; /*skip 'define query' row*/
+#endif
+ return index;
+ }
+ int firstQueryIndex() const {
+ return firstTableIndex() + tablesCount;
+ }
+
+ QGuardedPtr<KexiProject> prj;
+ QPixmap tableIcon, queryIcon;
+ int tablesCount;
+ int prevIndex; //!< Used in slotActivated()
+ bool showTables : 1;
+ bool showQueries : 1;
+};
+
+//------------------------
+
+KexiDataSourceComboBox::KexiDataSourceComboBox(QWidget *parent, const char *name)
+ : KComboBox(true/*rw*/, parent, name)
+ , d(new Private())
+{
+ setInsertionPolicy(NoInsertion);
+ setCompletionMode(KGlobalSettings::CompletionPopupAuto);
+ setSizeLimit( 16 );
+ connect(this, SIGNAL(activated(int)), this, SLOT(slotActivated(int)));
+ connect(this, SIGNAL(returnPressed(const QString &)), this, SLOT(slotReturnPressed(const QString &)));
+
+ d->tableIcon = SmallIcon("table");
+ d->queryIcon = SmallIcon("query");
+}
+
+KexiDataSourceComboBox::~KexiDataSourceComboBox()
+{
+ delete d;
+}
+
+KexiProject* KexiDataSourceComboBox::project() const
+{
+ return d->prj;
+}
+
+void KexiDataSourceComboBox::setProject(KexiProject *prj, bool showTables, bool showQueries)
+{
+ if ((KexiProject*)d->prj == prj)
+ return;
+
+ if (d->prj) {
+ disconnect(d->prj, 0, this, 0);
+ }
+ d->prj = prj;
+ d->showTables = showTables;
+ d->showQueries = showQueries;
+ clear();
+ d->tablesCount = 0;
+ if (!d->prj)
+ return;
+
+ //needed for updating contents of the combo box
+ connect(d->prj, SIGNAL(newItemStored(KexiPart::Item&)),
+ this, SLOT(slotNewItemStored(KexiPart::Item&)));
+ connect(d->prj, SIGNAL(itemRemoved(const KexiPart::Item&)),
+ this, SLOT(slotItemRemoved(const KexiPart::Item&)));
+ connect(d->prj, SIGNAL(itemRenamed(const KexiPart::Item&, const QCString&)),
+ this, SLOT(slotItemRenamed(const KexiPart::Item&, const QCString&)));
+
+ KexiDB::Connection *conn = d->prj->dbConnection();
+ if (!conn)
+ return;
+
+ //special item: empty
+ insertItem("");
+#ifdef ADD_DEFINEQUERY_ROW
+ //special item: define query
+ insertItem(i18n("Define Query..."));
+#endif
+
+ KCompletion *comp = completionObject();
+
+ if (d->showTables) {
+ //tables
+ KexiPart::Info* partInfo = Kexi::partManager().infoForMimeType("kexi/table");
+ if (!partInfo)
+ return;
+ KexiPart::ItemList list;
+ prj->getSortedItems(list, partInfo);
+ list.sort();
+ d->tablesCount = 0;
+ for (KexiPart::ItemListIterator it(list); it.current(); ++it, d->tablesCount++) {
+ insertItem(d->tableIcon, it.current()->name()); //or caption()?
+ comp->addItem(it.current()->name());
+ }
+ }
+
+ if (d->showQueries) {
+ //queries
+ KexiPart::Info* partInfo = Kexi::partManager().infoForMimeType("kexi/query");
+ if (!partInfo)
+ return;
+ KexiPart::ItemList list;
+ prj->getSortedItems(list, partInfo);
+ list.sort();
+ for (KexiPart::ItemListIterator it(list); it.current(); ++it) {
+ insertItem(d->queryIcon, it.current()->name()); //or caption()?
+ comp->addItem(it.current()->name());
+ }
+ }
+// setCurrentText("");
+ setCurrentItem(0);
+}
+
+void KexiDataSourceComboBox::setDataSource(const QString& mimeType, const QString& name)
+{
+ if (name.isEmpty()) {
+ clearEdit();
+ setCurrentItem(0);
+ d->prevIndex = -1;
+ emit dataSourceChanged();
+ return;
+ }
+
+ QString mt(mimeType);
+ if (mimeType.isEmpty())
+ mt="kexi/table";
+ int i = findItem(mt, name);
+ if (i==-1) {
+ if (mimeType.isEmpty())
+ i = findItem("kexi/query", name);
+ if (i==-1) {
+ setCurrentItem(0);
+ return;
+ }
+ }
+ setCurrentItem(i);
+ slotActivated(i);
+}
+
+void KexiDataSourceComboBox::slotNewItemStored(KexiPart::Item& item)
+{
+ QString name(item.name());
+ //insert a new item, maintaining sort order and splitting to tables and queries
+ if (item.mimeType()=="kexi/table") {
+ int i = 1; /*skip empty row*/
+#ifdef ADD_DEFINEQUERY_ROW
+ i++; /*skip 'define query' row*/
+#endif
+ for (; i < d->firstQueryIndex() && name>=text(i); i++)
+ ;
+ insertItem(d->tableIcon, name, i);
+ completionObject()->addItem(name);
+ d->tablesCount++;
+ }
+ else if (item.mimeType()=="kexi/query") {
+ int i;
+ for (i=d->firstQueryIndex(); i<count() && name>=text(i); i++)
+ ;
+ insertItem(d->queryIcon, name, i);
+ completionObject()->addItem(name);
+ }
+}
+
+int KexiDataSourceComboBox::findItem(const QString& mimeType, const QString& name)
+{
+ int i, end;
+ if (mimeType=="kexi/table") {
+ i = 0;
+#ifdef ADD_DEFINEQUERY_ROW
+ i++; //skip 'define query'
+#endif
+ end = d->firstQueryIndex();
+ }
+ else if (mimeType=="kexi/query") {
+ i = d->firstQueryIndex();
+ end = count();
+ }
+ else
+ return -1;
+
+ QString nameString(name);
+
+ for (; i<end; i++)
+ if (text(i)==nameString)
+ return i;
+
+ return -1;
+}
+
+void KexiDataSourceComboBox::slotItemRemoved(const KexiPart::Item& item)
+{
+ const int i = findItem(item.mimeType(), item.name());
+ if (i==-1)
+ return;
+ removeItem(i);
+ completionObject()->removeItem(item.name());
+ if (item.mimeType()=="kexi/table")
+ d->tablesCount--;
+#if 0 //disabled because even invalid data source can be set
+ if (currentItem()==i) {
+ if (i==(count()-1))
+ setCurrentItem(i-1);
+ else
+ setCurrentItem(i);
+ }
+#endif
+}
+
+void KexiDataSourceComboBox::slotItemRenamed(const KexiPart::Item& item, const QCString& oldName)
+{
+ const int i = findItem(item.mimeType(), QString(oldName));
+ if (i==-1)
+ return;
+ changeItem(item.name(), i);
+ completionObject()->removeItem(QString(oldName));
+ completionObject()->addItem(item.name());
+ setCurrentText(oldName); //still keep old name
+}
+
+void KexiDataSourceComboBox::slotActivated( int index )
+{
+ if (index >= d->firstTableIndex() && index < count() && d->prevIndex!=currentItem()) {
+ d->prevIndex = currentItem();
+ emit dataSourceChanged();
+ }
+}
+
+QString KexiDataSourceComboBox::selectedMimeType() const
+{
+ if (selectedName().isEmpty())
+ return "";
+ const int index = currentItem();
+ if (index >= d->firstTableIndex() && index < (int)d->firstQueryIndex())
+ return "kexi/table";
+ else if (index >= (int)d->firstQueryIndex() && index < count())
+ return "kexi/query";
+ return "";
+}
+
+QString KexiDataSourceComboBox::selectedName() const
+{
+ if (isSelectionValid())
+ return text(currentItem());
+ return currentText();
+}
+
+bool KexiDataSourceComboBox::isSelectionValid() const
+{
+ const int index = currentItem();
+ return index >= d->firstTableIndex() && index < count() && text(index)==currentText();
+}
+
+void KexiDataSourceComboBox::slotReturnPressed(const QString & text)
+{
+ //text is available: select item for this text:
+ bool changed = false;
+ if (text.isEmpty() && 0!=currentItem()) {
+ setCurrentItem(0);
+ changed = true;
+ }
+ else {
+ QListBoxItem *item = listBox()->findItem( text, Qt::ExactMatch );
+ if (item) {
+ int index = listBox()->index( item );
+ //if (index < d->firstTableIndex())
+ if (index>=0 && index!=currentItem()) {
+ setCurrentItem( index );
+ changed = true;
+ }
+ }
+ }
+ if (changed)
+ emit dataSourceChanged();
+}
+
+void KexiDataSourceComboBox::focusOutEvent( QFocusEvent *e )
+{
+ KComboBox::focusOutEvent( e );
+ slotReturnPressed(currentText());
+}
+
+#include "kexidatasourcecombobox.moc"
diff --git a/kexi/widget/kexidatasourcecombobox.h b/kexi/widget/kexidatasourcecombobox.h
new file mode 100644
index 000000000..01a02d039
--- /dev/null
+++ b/kexi/widget/kexidatasourcecombobox.h
@@ -0,0 +1,88 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDATASOURCECOMBOBOX_H
+#define KEXIDATASOURCECOMBOBOX_H
+
+#include <kcombobox.h>
+
+class KexiProject;
+namespace KexiPart {
+ class Item;
+}
+
+/**
+ * A combo box listing availabe data sources (tables and queries)
+ * with icons. "Define query..." item can be also prepended.
+ */
+class KEXIEXTWIDGETS_EXPORT KexiDataSourceComboBox : public KComboBox
+{
+ Q_OBJECT
+
+ public:
+ KexiDataSourceComboBox(QWidget *parent, const char *name=0);
+ ~KexiDataSourceComboBox();
+
+ //! \return global project that is used to retrieve schema informationm for this combo box.
+ KexiProject* project() const;
+
+ //! \return name of selected table or query. Can return null string.
+ //! You should use isSelectionValid() to check validity of the input.
+ QString selectedMimeType() const;
+
+ //! \return name of selected table or query. Can return null string or nonexisting name,
+ //! so you should use isSelectionValid() to check validity of the input.
+ QString selectedName() const;
+
+ //! \return true if current selection is valid
+ bool isSelectionValid() const;
+
+ /*! \return index of item of mime type \a mimeType and name \a name.
+ Returs -1 of no such item exists. */
+ int findItem(const QString& mimeType, const QString& name);
+
+ public slots:
+ //! Sets global project that is used to retrieve schema informationm for this combo box.
+ //! Tables visibility can be set using \a showTables queries visibility using \a showQueries.
+ void setProject(KexiProject *prj, bool showTables = true, bool showQueries = true);
+
+ /*! Sets item for data source described by \a mimeType and \a name.
+ If \a mimeType is empty, either "kexi/table" and "kexi/query" are tried. */
+ void setDataSource(const QString& mimeType, const QString& name);
+
+ signals:
+ //! Emitted whenever data source changes.
+ //! Even setting invalid data source or clearing it will emit this signal.
+ void dataSourceChanged();
+
+ protected slots:
+ void slotNewItemStored(KexiPart::Item& item);
+ void slotItemRemoved(const KexiPart::Item& item);
+ void slotItemRenamed(const KexiPart::Item& item, const QCString& oldName);
+ void slotActivated( int index );
+ void slotReturnPressed(const QString & text);
+
+ protected:
+ virtual void focusOutEvent( QFocusEvent *e );
+
+ class Private;
+ Private *d;
+};
+
+#endif
diff --git a/kexi/widget/kexidatatable.cpp b/kexi/widget/kexidatatable.cpp
new file mode 100644
index 000000000..b9a0aeb2d
--- /dev/null
+++ b/kexi/widget/kexidatatable.cpp
@@ -0,0 +1,82 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+
+#include <qlayout.h>
+#include <qlabel.h>
+
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kaction.h>
+#include <kpopupmenu.h>
+
+#include <kexidb/cursor.h>
+
+#include "kexidatatableview.h"
+#include "kexidatatable.h"
+#include "kexidialogbase.h"
+
+KexiDataTable::KexiDataTable(KexiMainWindow *mainWin, QWidget *parent,
+ const char *name, bool dbAware)
+// : KexiViewBase(mainWin, parent, name)
+ : KexiDataAwareView( mainWin, parent, name )
+{
+ KexiTableView *view;
+ if (dbAware)
+ view = new KexiDataTableView(this,
+ QString("%1_datatableview").arg(name ? name : "KexiDataTableView").latin1());
+ else
+ view = new KexiTableView(0, this,
+ QString("%1_tableview").arg(name ? name : "KexiTableView").latin1());
+
+ KexiDataAwareView::init( view, view, view );
+}
+
+KexiDataTable::KexiDataTable(KexiMainWindow *mainWin, QWidget *parent,
+ KexiDB::Cursor *cursor, const char *name)
+ : KexiDataAwareView( mainWin, parent, name )
+{
+ KexiTableView *view = new KexiDataTableView(this, "view", cursor);
+ KexiDataAwareView::init( view, view, view );
+}
+
+KexiDataTable::~KexiDataTable()
+{
+}
+
+void
+KexiDataTable::setData(KexiDB::Cursor *c)
+{
+ if (!dynamic_cast<KexiDataTableView*>(mainWidget()))
+ return;
+ dynamic_cast<KexiDataTableView*>(mainWidget())->setData(c);
+}
+
+void KexiDataTable::filter()
+{
+}
+
+KexiTableView* KexiDataTable::tableView() const
+{
+ return dynamic_cast<KexiTableView*>(m_internalView);
+}
+
+#include "kexidatatable.moc"
diff --git a/kexi/widget/kexidatatable.h b/kexi/widget/kexidatatable.h
new file mode 100644
index 000000000..9fb73225e
--- /dev/null
+++ b/kexi/widget/kexidatatable.h
@@ -0,0 +1,80 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDATATABLE_H
+#define KEXIDATATABLE_H
+
+#include "kexidataawareview.h"
+
+class KexiMainWindow;
+class KexiDataTableView;
+class KexiTableView;
+class KexiTableViewData;
+class KPopupMenu;
+
+namespace KexiDB
+{
+ class Cursor;
+}
+
+/*! @short Provides a data-driven (record-based) tabular view.
+
+ The KexiDataTable can display data provided "by hand"
+ or from KexiDB-compatible database source.
+ @see KexiFormView
+*/
+class KEXIEXTWIDGETS_EXPORT KexiDataTable : public KexiDataAwareView
+{
+ Q_OBJECT
+
+ public:
+ /*! CTOR1: Creates, empty table view that can be initialized later
+ with setData().
+ If \a dbAware is true, table will be db-aware,
+ and KexiDataTableView is used internally.
+ Otherwise, table will be not-db-aware,
+ and KexiTableView is used internally. In the latter case,
+ data can be set by calling tableView()->setData(KexiTableViewData* data). */
+ KexiDataTable(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0,
+ bool dbAware = true);
+
+ /*! CTOR2: Creates db-aware, table view initialized with \a cursor.
+ KexiDataTableView is used internally. */
+ KexiDataTable(KexiMainWindow *mainWin, QWidget *parent,
+ KexiDB::Cursor *cursor, const char *name = 0);
+
+ virtual ~KexiDataTable();
+
+ KexiTableView* tableView() const;
+
+ public slots:
+ /*! Sets data. Only works for db-aware table. */
+ void setData(KexiDB::Cursor *cursor);
+
+ protected slots:
+//! @todo
+ void filter();
+
+ protected:
+ void init();
+};
+
+#endif
diff --git a/kexi/widget/kexidbconnectionwidget.cpp b/kexi/widget/kexidbconnectionwidget.cpp
new file mode 100644
index 000000000..5b1b45384
--- /dev/null
+++ b/kexi/widget/kexidbconnectionwidget.cpp
@@ -0,0 +1,407 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexidbconnectionwidget.h"
+#include "kexidbconnectionwidgetdetailsbase.h"
+
+#include <kexi.h>
+#include <kexiguimsghandler.h>
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+#include "kexidbdrivercombobox.h"
+
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <klineedit.h>
+#include <knuminput.h>
+#include <kpassdlg.h>
+#include <kurlrequester.h>
+#include <ktextedit.h>
+#include <kprogress.h>
+
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qbuttongroup.h>
+#include <qwidgetstack.h>
+#include <qlayout.h>
+#include <qvbox.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qthread.h>
+#include <qradiobutton.h>
+
+//! Templorary hides db list
+//! @todo reenable this when implemented
+#define NO_LOAD_DB_LIST
+
+// @internal
+class KexiDBConnectionWidget::Private
+{
+ public:
+ Private()
+ : connectionOnly(false)
+ {
+ }
+
+ KPushButton *btnSaveChanges, *btnTestConnection;
+ bool connectionOnly : 1;
+};
+
+//---------
+
+KexiDBConnectionWidget::KexiDBConnectionWidget( QWidget* parent, const char* name )
+ : KexiDBConnectionWidgetBase( parent, name )
+ , d(new Private())
+{
+ iconLabel->setPixmap(DesktopIcon("network"));
+
+ QVBoxLayout *driversComboLyr = new QVBoxLayout(frmEngine);
+ m_driversCombo = new KexiDBDriverComboBox(frmEngine, Kexi::driverManager().driversInfo(),
+ KexiDBDriverComboBox::ShowServerDrivers);
+ lblEngine->setBuddy( m_driversCombo );
+ lblEngine->setFocusProxy( m_driversCombo );
+ driversComboLyr->addWidget( m_driversCombo );
+
+#ifdef NO_LOAD_DB_LIST
+ btnLoadDBList->hide();
+#endif
+ btnLoadDBList->setIconSet(SmallIconSet("reload"));
+ QToolTip::add(btnLoadDBList, i18n("Load database list from the server"));
+ QWhatsThis::add(btnLoadDBList,
+ i18n("Loads database list from the server, so you can select one using the \"Name\" combo box."));
+
+ QHBoxLayout *hbox = new QHBoxLayout(frmBottom);
+ hbox->addStretch(2);
+ d->btnSaveChanges = new KPushButton(KGuiItem(i18n("Save Changes"), "filesave",
+ i18n("Save all changes made to this connection information"),
+ i18n("Save all changes made to this connection information. You can later reuse this information.")),
+ frmBottom, "savechanges");
+ hbox->addWidget( d->btnSaveChanges );
+ hbox->addSpacing( KDialogBase::spacingHint() );
+ QWidget::setTabOrder(titleEdit, d->btnSaveChanges);
+ d->btnSaveChanges->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+
+ d->btnTestConnection = new KPushButton(KGuiItem(i18n("&Test Connection"), "",
+ i18n("Test database connection"),
+ i18n("Tests database connection. You can ensure that valid connection information is provided.")),
+ frmBottom, "testConnection");
+ hbox->addWidget( d->btnTestConnection );
+ QWidget::setTabOrder(d->btnSaveChanges, d->btnTestConnection);
+ d->btnTestConnection->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+
+ connect( locationBGrp, SIGNAL(clicked(int)), this, SLOT(slotLocationBGrpClicked(int)) );
+ connect( chkPortDefault, SIGNAL(toggled(bool)), this , SLOT(slotCBToggled(bool)) );
+ connect( btnLoadDBList, SIGNAL(clicked()), this, SIGNAL(loadDBList()) );
+ connect( d->btnSaveChanges, SIGNAL(clicked()), this, SIGNAL(saveChanges()) );
+}
+
+KexiDBConnectionWidget::~KexiDBConnectionWidget()
+{
+ delete d;
+}
+
+bool KexiDBConnectionWidget::connectionOnly() const
+{ return d->connectionOnly; }
+
+void KexiDBConnectionWidget::setDataInternal(const KexiProjectData& data, bool connectionOnly,
+ const QString& shortcutFileName)
+{
+ m_data = data;
+ d->connectionOnly = connectionOnly;
+
+ if (d->connectionOnly) {
+ nameLabel->hide();
+ nameCombo->hide();
+ btnLoadDBList->hide();
+ dbGroupBox->setTitle(i18n("Database Connection"));
+ }
+ else {
+ nameLabel->show();
+ nameCombo->show();
+#ifndef NO_LOAD_DB_LIST
+ btnLoadDBList->show();
+#endif
+ nameCombo->setCurrentText(m_data.databaseName());
+ dbGroupBox->setTitle(i18n("Database"));
+ }
+//! @todo what if there's no such driver name?
+ m_driversCombo->setDriverName(m_data.connectionData()->driverName);
+ hostEdit->setText(m_data.connectionData()->hostName);
+ locationBGrp->setButton( m_data.connectionData()->hostName.isEmpty() ? 0 : 1 );
+ slotLocationBGrpClicked( locationBGrp->selectedId() );
+ if (m_data.connectionData()->port!=0) {
+ chkPortDefault->setChecked(false);
+ customPortEdit->setValue(m_data.connectionData()->port);
+ }
+ else {
+ chkPortDefault->setChecked(true);
+/* @todo default port # instead of 0 */
+ customPortEdit->setValue(0);
+ }
+ userEdit->setText(m_data.connectionData()->userName);
+ passwordEdit->setText(m_data.connectionData()->password);
+ if (d->connectionOnly)
+ titleEdit->setText(m_data.connectionData()->caption);
+ else
+ titleEdit->setText(m_data.caption());
+
+ if (shortcutFileName.isEmpty()) {
+ d->btnSaveChanges->hide();
+// chkSavePassword->hide();
+ }
+ else {
+ if (!QFileInfo(shortcutFileName).isWritable()) {
+ d->btnSaveChanges->setEnabled(false);
+ }
+ }
+// chkSavePassword->setChecked(!m_data.connectionData()->password.isEmpty());
+ chkSavePassword->setChecked(m_data.connectionData()->savePassword);
+ adjustSize();
+}
+
+void KexiDBConnectionWidget::setData(const KexiProjectData& data, const QString& shortcutFileName)
+{
+ setDataInternal(data, false /*!connectionOnly*/, shortcutFileName);
+}
+
+void KexiDBConnectionWidget::setData(const KexiDB::ConnectionData& data, const QString& shortcutFileName)
+{
+ KexiProjectData pdata(data);
+ setDataInternal(pdata, true /*connectionOnly*/, shortcutFileName);
+}
+
+KPushButton* KexiDBConnectionWidget::saveChangesButton() const
+{
+ return d->btnSaveChanges;
+}
+
+KPushButton* KexiDBConnectionWidget::testConnectionButton() const
+{
+ return d->btnTestConnection;
+}
+
+KexiProjectData KexiDBConnectionWidget::data()
+{
+ return m_data;
+}
+
+void KexiDBConnectionWidget::slotLocationBGrpClicked(int id)
+{
+ if (id != 0 && id != 1) //only support local/remove radio buttons
+ return;
+ hostLbl->setEnabled(id==1);
+ hostEdit->setEnabled(id==1);
+}
+
+void KexiDBConnectionWidget::slotCBToggled(bool on)
+{
+ if (sender()==chkPortDefault) {
+ customPortEdit->setEnabled(!on);
+ }
+// else if (sender()==chkSocketDefault) {
+// customSocketEdit->setEnabled(!on);
+// }
+}
+
+//-----------
+
+KexiDBConnectionTabWidget::KexiDBConnectionTabWidget( QWidget* parent, const char* name )
+ : KTabWidget( parent, name )
+{
+ mainWidget = new KexiDBConnectionWidget( this, "mainWidget" );
+ mainWidget->layout()->setMargin(KDialog::marginHint());
+ addTab( mainWidget, i18n("Parameters") );
+
+// QVBox *page2 = new QVBox(this);
+// page2->setMargin(KDialog::marginHint());
+// page2->setSpacing(KDialog::spacingHint());
+// QLabel *lbl = new QLabel(i18n("&Description:"), page2);
+// m_descriptionEdit = new KTextEdit(page2);
+// lbl->setBuddy(m_descriptionEdit);
+ detailsWidget = new KexiDBConnectionWidgetDetailsBase(this, "detailsWidget");
+ addTab( detailsWidget, i18n("Details") );
+
+ connect( mainWidget->testConnectionButton(), SIGNAL(clicked()), this, SLOT(slotTestConnection()) );
+}
+
+KexiDBConnectionTabWidget::~KexiDBConnectionTabWidget()
+{
+}
+
+void KexiDBConnectionTabWidget::setData(const KexiProjectData& data, const QString& shortcutFileName)
+{
+ mainWidget->setData( data, shortcutFileName );
+ detailsWidget->chkUseSocket->setChecked( data.constConnectionData()->useLocalSocketFile );
+ detailsWidget->customSocketEdit->setURL( data.constConnectionData()->localSocketFileName );
+ detailsWidget->customSocketEdit->setEnabled( detailsWidget->chkUseSocket->isChecked() );
+ detailsWidget->chkSocketDefault->setChecked( data.constConnectionData()->localSocketFileName.isEmpty() );
+ detailsWidget->chkSocketDefault->setEnabled( detailsWidget->chkUseSocket->isChecked() );
+ detailsWidget->descriptionEdit->setText( data.description() );
+}
+
+void KexiDBConnectionTabWidget::setData(const KexiDB::ConnectionData& data,
+ const QString& shortcutFileName)
+{
+ mainWidget->setData( data, shortcutFileName );
+ detailsWidget->chkUseSocket->setChecked( data.useLocalSocketFile );
+ detailsWidget->customSocketEdit->setURL( data.localSocketFileName );
+ detailsWidget->customSocketEdit->setEnabled( detailsWidget->chkUseSocket->isChecked() );
+ detailsWidget->chkSocketDefault->setChecked( data.localSocketFileName.isEmpty() );
+ detailsWidget->chkSocketDefault->setEnabled( detailsWidget->chkUseSocket->isChecked() );
+ detailsWidget->descriptionEdit->setText( data.description );
+}
+
+KexiProjectData KexiDBConnectionTabWidget::currentProjectData()
+{
+ KexiProjectData data;
+
+//! @todo check if that's database of connection shortcut. Now we're assuming db shortcut only!
+
+ // collect data from the form's fields
+// if (d->isDatabaseShortcut) {
+ if (mainWidget->connectionOnly()) {
+ data.connectionData()->caption = mainWidget->titleEdit->text();
+ data.setCaption( QString::null );
+ data.connectionData()->description = detailsWidget->descriptionEdit->text();
+ data.setDatabaseName( QString::null );
+ }
+ else {
+ data.connectionData()->caption = QString::null; /* connection name is not specified... */
+ data.setCaption( mainWidget->titleEdit->text() );
+ data.setDescription( detailsWidget->descriptionEdit->text() );
+ data.setDatabaseName( mainWidget->nameCombo->currentText() );
+ }
+// }
+/* else {
+ data.setCaption( QString::null );
+ data.connectionData()->connName = config.readEntry("caption");
+ data.setDescription( QString::null );
+ data.connectionData()->description = config.readEntry("comment");
+ data.setDatabaseName( QString::null );
+ }*/
+ data.connectionData()->driverName = mainWidget->driversCombo()->selectedDriverName();
+
+/* if (data.connectionData()->driverName.isEmpty()) {
+ //ERR: "No valid "engine" field specified for %1 section" group
+ return false;
+ }*/
+ data.connectionData()->hostName =
+ (mainWidget->remotehostRBtn->isChecked()/*remote*/) ? mainWidget->hostEdit->text()
+ : QString::null;
+ data.connectionData()->port = mainWidget->chkPortDefault->isChecked()
+ ? 0 : mainWidget->customPortEdit->value();
+ data.connectionData()->localSocketFileName = detailsWidget->chkSocketDefault->isChecked()
+ ? QString::null : detailsWidget->customSocketEdit->url();
+ data.connectionData()->useLocalSocketFile = detailsWidget->chkUseSocket->isChecked();
+//UNSAFE!!!!
+ data.connectionData()->userName = mainWidget->userEdit->text();
+ data.connectionData()->password = mainWidget->passwordEdit->text();
+ data.connectionData()->savePassword = mainWidget->chkSavePassword->isChecked();
+/* @todo add "options=", eg. as string list? */
+ return data;
+}
+
+bool KexiDBConnectionTabWidget::savePasswordOptionSelected() const
+{
+ return mainWidget->chkSavePassword->isChecked();
+}
+
+
+
+
+void KexiDBConnectionTabWidget::slotTestConnection()
+{
+ KexiGUIMessageHandler msgHandler;
+ KexiDB::connectionTestDialog(this, *currentProjectData().connectionData(),
+ msgHandler);
+}
+
+//--------
+
+//! @todo set proper help ctxt ID
+
+KexiDBConnectionDialog::KexiDBConnectionDialog(const KexiProjectData& data,
+ const QString& shortcutFileName, const KGuiItem& acceptButtonGuiItem)
+ : KDialogBase(0, "dlg", true, i18n("Open Database"),
+ KDialogBase::User1|KDialogBase::Cancel|KDialogBase::Help,
+ KDialogBase::User1, false,
+ acceptButtonGuiItem.text().isEmpty()
+ ? KGuiItem(i18n("&Open"), "fileopen", i18n("Open Database Connection"))
+ : acceptButtonGuiItem
+ )
+{
+ tabWidget = new KexiDBConnectionTabWidget(this, "tabWidget");
+ tabWidget->setData(data, shortcutFileName);
+ init();
+}
+
+KexiDBConnectionDialog::KexiDBConnectionDialog(const KexiDB::ConnectionData& data,
+ const QString& shortcutFileName, const KGuiItem& acceptButtonGuiItem)
+ : KDialogBase(0, "dlg", true, i18n("Connect to a Database Server"),
+ KDialogBase::User1|KDialogBase::Cancel|KDialogBase::Help,
+ KDialogBase::User1, false,
+ acceptButtonGuiItem.text().isEmpty()
+ ? KGuiItem(i18n("&Open"), "fileopen", i18n("Open Database Connection"))
+ : acceptButtonGuiItem
+ )
+{
+ tabWidget = new KexiDBConnectionTabWidget(this, "tabWidget");
+ tabWidget->setData(data, shortcutFileName);
+ init();
+}
+
+KexiDBConnectionDialog::~KexiDBConnectionDialog()
+{
+}
+
+void KexiDBConnectionDialog::init()
+{
+ connect( this, SIGNAL(user1Clicked()), this, SLOT(accept()));
+ setMainWidget(tabWidget);
+ connect(tabWidget->mainWidget, SIGNAL(saveChanges()), this, SIGNAL(saveChanges()));
+ connect(tabWidget, SIGNAL(testConnection()), this, SIGNAL(testConnection()));
+
+ adjustSize();
+ resize(width(), tabWidget->height());
+ if (tabWidget->mainWidget->connectionOnly())
+ tabWidget->mainWidget->driversCombo()->setFocus();
+ else if (tabWidget->mainWidget->nameCombo->currentText().isEmpty())
+ tabWidget->mainWidget->nameCombo->setFocus();
+ else if (tabWidget->mainWidget->userEdit->text().isEmpty())
+ tabWidget->mainWidget->userEdit->setFocus();
+ else if (tabWidget->mainWidget->passwordEdit->text().isEmpty())
+ tabWidget->mainWidget->passwordEdit->setFocus();
+ else //back
+ tabWidget->mainWidget->nameCombo->setFocus();
+}
+
+KexiProjectData KexiDBConnectionDialog::currentProjectData()
+{ return tabWidget->currentProjectData(); }
+
+bool KexiDBConnectionDialog::savePasswordOptionSelected() const
+{ return tabWidget->savePasswordOptionSelected(); }
+
+KexiDBConnectionWidget* KexiDBConnectionDialog::mainWidget() const
+{ return tabWidget->mainWidget; }
+
+KexiDBConnectionWidgetDetailsBase* KexiDBConnectionDialog::detailsWidget() const
+{ return tabWidget->detailsWidget; }
+
+#include "kexidbconnectionwidget.moc"
+
diff --git a/kexi/widget/kexidbconnectionwidget.h b/kexi/widget/kexidbconnectionwidget.h
new file mode 100644
index 000000000..dd8559e5a
--- /dev/null
+++ b/kexi/widget/kexidbconnectionwidget.h
@@ -0,0 +1,179 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDBCONNECTIONWIDGET_H
+#define KEXIDBCONNECTIONWIDGET_H
+
+#include "kexidbconnectionwidgetbase.h"
+
+#include <kexiprojectdata.h>
+
+#include <ktabwidget.h>
+#include <kdialogbase.h>
+
+class KTextEdit;
+class KPushButton;
+class KexiDBDriverComboBox;
+class KexiDBConnectionWidgetDetailsBase;
+class KexiDBConnectionTabWidget;
+
+class KEXIEXTWIDGETS_EXPORT KexiDBConnectionWidget : public KexiDBConnectionWidgetBase
+{
+ Q_OBJECT
+
+ public:
+ KexiDBConnectionWidget( QWidget* parent = 0, const char* name = 0 );
+ virtual ~KexiDBConnectionWidget();
+
+ /*! Sets project data \a data.
+ \a shortcutFileName is only used to check if the file is writable
+ (if no, "save changes" button will be disabled). */
+ void setData(const KexiProjectData& data, const QString& shortcutFileName = QString::null);
+
+ /*! Sets connection data \a data.
+ \a shortcutFileName is only used to check if the file is writable
+ (if no, "save changes" button will be disabled). */
+ void setData(const KexiDB::ConnectionData& data, const QString& shortcutFileName = QString::null);
+
+ KexiProjectData data();
+
+ //! \return a pointer to 'save changes' button. You can call hide() for this to hide it.
+ KPushButton* saveChangesButton() const;
+
+ //! \return a pointer to 'test connection' button. You can call hide() for this to hide it.
+ KPushButton* testConnectionButton() const;
+
+ KexiDBDriverComboBox *driversCombo() const { return m_driversCombo; }
+
+ //! \return true if only connection data is managed by this widget
+ bool connectionOnly() const;
+
+ signals:
+ //! emitted when data saving is needed
+ void saveChanges();
+
+ void loadDBList();
+
+ protected slots:
+ void slotLocationBGrpClicked(int id);
+ void slotCBToggled(bool on);
+ virtual void languageChange() { KexiDBConnectionWidgetBase::languageChange(); }
+
+ protected:
+ void setDataInternal(const KexiProjectData& data, bool connectionOnly,
+ const QString& shortcutFileName);
+
+ KexiProjectData m_data;
+ KexiDBDriverComboBox *m_driversCombo;
+
+ class Private;
+ Private *d;
+
+// friend class KexiDBConnectionTabWidget;
+};
+
+class KEXIEXTWIDGETS_EXPORT KexiDBConnectionTabWidget : public KTabWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiDBConnectionTabWidget( QWidget* parent = 0, const char* name = 0 );
+ virtual ~KexiDBConnectionTabWidget();
+
+ /*! Sets connection data \a data.
+ \a shortcutFileName is only used to check if the file is writable
+ (if no, "save changes" button will be disabled). */
+ void setData(const KexiProjectData& data, const QString& shortcutFileName = QString::null);
+ void setData(const KexiDB::ConnectionData& data, const QString& shortcutFileName = QString::null);
+ KexiProjectData currentProjectData();
+
+ //! \return true if 'save password' option is selected
+ bool savePasswordOptionSelected() const;
+
+ signals:
+ //! emitted when test connection is needed
+ void testConnection();
+
+ protected slots:
+ void slotTestConnection();
+
+ protected:
+ KexiDBConnectionWidget *mainWidget;
+ KexiDBConnectionWidgetDetailsBase* detailsWidget;
+
+ friend class KexiDBConnectionDialog;
+};
+
+class KEXIEXTWIDGETS_EXPORT KexiDBConnectionDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ /*! Creates a new connection dialog for project data \a data.
+ Not only connection data is visible but also database name and and title.
+ \a shortcutFileName is only used to check if the shortcut file is writable
+ (if no, "save changes" button will be disabled).
+ The shortcut file is in .KEXIS format.
+ Connect to saveChanges() signal to react on saving changes.
+ If \a shortcutFileName is empty, the button will be hidden.
+ \a acceptButtonGuiItem allows to override default "Open" button's appearance. */
+ KexiDBConnectionDialog(const KexiProjectData& data,
+ const QString& shortcutFileName = QString::null,
+ const KGuiItem& acceptButtonGuiItem = KGuiItem(""));
+
+ /*! Creates a new connection dialog for connection data \a data.
+ Only connection data is visible: database name and and title fields are hidden.
+ \a shortcutFileName is only used to check if the shortcut file is writable
+ (if no, "save changes" button will be disabled).
+ The shortcut file is in .KEXIC format.
+ See above constructor for more details. */
+ KexiDBConnectionDialog(const KexiDB::ConnectionData& data,
+ const QString& shortcutFileName = QString::null,
+ const KGuiItem& acceptButtonGuiItem = KGuiItem(""));
+
+ ~KexiDBConnectionDialog();
+
+ /*! \return project data displayed within the dialog.
+ Information about database name and title can be empty if the dialog
+ contain only a connection data (if second constructor was used). */
+ KexiProjectData currentProjectData();
+
+ //! \return true if 'save password' option is selected
+ bool savePasswordOptionSelected() const;
+
+ KexiDBConnectionWidget *mainWidget() const;
+ KexiDBConnectionWidgetDetailsBase* detailsWidget() const;
+
+ signals:
+ //! emitted when data saving is needed
+ void saveChanges();
+
+ //! emitted when test connection is needed
+ void testConnection();
+
+ void loadDBList();
+
+ protected:
+ KexiDBConnectionTabWidget *tabWidget;
+
+ private:
+ void init();
+};
+
+#endif // KEXIDBCONNECTIONWIDGET_H
diff --git a/kexi/widget/kexidbconnectionwidgetbase.ui b/kexi/widget/kexidbconnectionwidgetbase.ui
new file mode 100644
index 000000000..70fcddcac
--- /dev/null
+++ b/kexi/widget/kexidbconnectionwidgetbase.ui
@@ -0,0 +1,464 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiDBConnectionWidgetBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiDBConnectionWidgetBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>381</width>
+ <height>395</height>
+ </rect>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLayoutWidget" row="0" column="0" rowspan="3" colspan="1">
+ <property name="name">
+ <cstring>layout7</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>iconLabel</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer7</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>331</height>
+ </size>
+ </property>
+ </spacer>
+ </vbox>
+ </widget>
+ <spacer row="4" column="1">
+ <property name="name">
+ <cstring>spacer6</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>4</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QButtonGroup" row="0" column="1">
+ <property name="name">
+ <cstring>locationBGrp</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>4</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Database Server</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer row="1" column="5" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>spacer9</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>41</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KIntNumInput" row="3" column="4" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>customPortEdit</cstring>
+ </property>
+ <property name="minValue">
+ <number>0</number>
+ </property>
+ </widget>
+ <widget class="QLabel" row="3" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>portLbl</cstring>
+ </property>
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ </widget>
+ <widget class="KLineEdit" row="2" column="2" rowspan="1" colspan="5">
+ <property name="name">
+ <cstring>hostEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QRadioButton" row="1" column="0" rowspan="1" colspan="3">
+ <property name="name">
+ <cstring>localhostRBtn</cstring>
+ </property>
+ <property name="text">
+ <string>Local server</string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <spacer row="3" column="6">
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>90</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel" row="2" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>hostLbl</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Hostname:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>hostEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="3" column="2" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>chkPortDefault</cstring>
+ </property>
+ <property name="text">
+ <string>Default</string>
+ <comment>port: default</comment>
+ </property>
+ <property name="buttonGroupId">
+ <number>2</number>
+ </property>
+ </widget>
+ <widget class="QRadioButton" row="1" column="3" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>remotehostRBtn</cstring>
+ </property>
+ <property name="text">
+ <string>Remote server</string>
+ </property>
+ <property name="buttonGroupId">
+ <number>1</number>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>lblEngine</cstring>
+ </property>
+ <property name="focusPolicy">
+ <enum>TabFocus</enum>
+ </property>
+ <property name="text">
+ <string>&amp;Engine:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="QFrame" row="0" column="1" rowspan="1" colspan="6">
+ <property name="name">
+ <cstring>frmEngine</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QGroupBox" row="1" column="1">
+ <property name="name">
+ <cstring>authenticationGBox</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>4</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Authentication</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>userLbl</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Username:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>userEdit</cstring>
+ </property>
+ </widget>
+ <widget class="KLineEdit" row="0" column="1">
+ <property name="name">
+ <cstring>userEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>passwordLbl</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Password:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>passwordEdit</cstring>
+ </property>
+ </widget>
+ <widget class="KLineEdit" row="1" column="1">
+ <property name="name">
+ <cstring>passwordEdit</cstring>
+ </property>
+ <property name="echoMode">
+ <enum>Password</enum>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="2" column="1">
+ <property name="name">
+ <cstring>chkSavePassword</cstring>
+ </property>
+ <property name="text">
+ <string>Save password in the shortcut file</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QGroupBox" row="2" column="1">
+ <property name="name">
+ <cstring>dbGroupBox</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>4</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Database</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>nameLabel</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Name:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>nameCombo</cstring>
+ </property>
+ </widget>
+ <widget class="KLineEdit" row="1" column="1" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>titleEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>titleLabel</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Title (optional):</string>
+ </property>
+ <property name="alignment">
+ <set>AlignTop</set>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>titleEdit</cstring>
+ </property>
+ </widget>
+ <widget class="KPushButton" row="0" column="2">
+ <property name="name">
+ <cstring>btnLoadDBList</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>4</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="0" column="1">
+ <property name="name">
+ <cstring>nameCombo</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ <property name="insertionPolicy">
+ <enum>NoInsertion</enum>
+ </property>
+ <property name="autoCompletion">
+ <bool>true</bool>
+ </property>
+ <property name="trapReturnKey">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QLayoutWidget" row="3" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer13</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Minimum</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>80</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>frmBottom</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<tabstops>
+ <tabstop>lblEngine</tabstop>
+ <tabstop>localhostRBtn</tabstop>
+ <tabstop>hostEdit</tabstop>
+ <tabstop>chkPortDefault</tabstop>
+ <tabstop>customPortEdit</tabstop>
+ <tabstop>userEdit</tabstop>
+ <tabstop>passwordEdit</tabstop>
+ <tabstop>chkSavePassword</tabstop>
+ <tabstop>nameCombo</tabstop>
+ <tabstop>btnLoadDBList</tabstop>
+ <tabstop>titleEdit</tabstop>
+ <tabstop>frmEngine</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>knuminput.h</includehint>
+ <includehint>knuminput.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>klineedit.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/widget/kexidbconnectionwidgetdetailsbase.ui b/kexi/widget/kexidbconnectionwidgetdetailsbase.ui
new file mode 100644
index 000000000..b729c9bc7
--- /dev/null
+++ b/kexi/widget/kexidbconnectionwidgetdetailsbase.ui
@@ -0,0 +1,194 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>KexiDBConnectionWidgetDetailsBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiDBConnectionWidgetDetailsBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>297</width>
+ <height>171</height>
+ </rect>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLayoutWidget" row="1" column="0">
+ <property name="name">
+ <cstring>layout8</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <widget class="QCheckBox" row="0" column="0">
+ <property name="name">
+ <cstring>chkUseSocket</cstring>
+ </property>
+ <property name="text">
+ <string>Use socket &amp;file instead of TCP/IP port:</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget" row="1" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>layout6</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer7</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>chkSocketDefault</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Default</string>
+ <comment>socket: default</comment>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="KURLRequester">
+ <property name="name">
+ <cstring>customSocketEdit</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <spacer row="0" column="1">
+ <property name="name">
+ <cstring>spacer6</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>129</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </grid>
+ </widget>
+ <widget class="QLayoutWidget" row="0" column="0">
+ <property name="name">
+ <cstring>layout9</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <widget class="KTextEdit" row="1" column="0">
+ <property name="name">
+ <cstring>descriptionEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Description:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>descriptionEdit</cstring>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<connections>
+ <connection>
+ <sender>chkSocketDefault</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>KexiDBConnectionWidgetDetailsBase</receiver>
+ <slot>slotCBToggled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>chkUseSocket</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>KexiDBConnectionWidgetDetailsBase</receiver>
+ <slot>slotCBToggled(bool)</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>customSocketEdit</tabstop>
+ <tabstop>descriptionEdit</tabstop>
+ <tabstop>chkUseSocket</tabstop>
+ <tabstop>chkSocketDefault</tabstop>
+</tabstops>
+<includes>
+ <include location="local" impldecl="in implementation">kexidbconnectionwidgetdetailsbase.ui.h</include>
+</includes>
+<slots>
+ <slot access="protected">slotCBToggled( bool on )</slot>
+</slots>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>kurlrequester.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+</includehints>
+</UI>
diff --git a/kexi/widget/kexidbconnectionwidgetdetailsbase.ui.h b/kexi/widget/kexidbconnectionwidgetdetailsbase.ui.h
new file mode 100644
index 000000000..90d6a4b82
--- /dev/null
+++ b/kexi/widget/kexidbconnectionwidgetdetailsbase.ui.h
@@ -0,0 +1,29 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+void KexiDBConnectionWidgetDetailsBase::slotCBToggled( bool on )
+{
+ if (sender()==chkSocketDefault) {
+ customSocketEdit->setEnabled( !on );
+ }
+ else if (sender()==chkUseSocket) {
+ customSocketEdit->setEnabled( on && !chkSocketDefault->isChecked() );
+ chkSocketDefault->setEnabled( on );
+ }
+}
diff --git a/kexi/widget/kexidbdrivercombobox.cpp b/kexi/widget/kexidbdrivercombobox.cpp
new file mode 100644
index 000000000..e3431531e
--- /dev/null
+++ b/kexi/widget/kexidbdrivercombobox.cpp
@@ -0,0 +1,92 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidbdrivercombobox.h"
+
+#include <qlistbox.h>
+
+#include <kiconloader.h>
+
+KexiDBDriverComboBox::KexiDBDriverComboBox(QWidget* parent, const KexiDB::Driver::InfoMap& driversInfo,
+ Options options)
+ : KComboBox(parent, "KexiDBDriverComboBox")
+{
+ //retrieve list of drivers and sort it: file-based first, then server-based
+ QStringList captionsForFileBasedDrivers, captionsForServerBasedDrivers;
+ QMap<QString,QString> fileBasedDriversDict, serverBasedDriversDict; //a map from caption to name
+ foreach(KexiDB::Driver::InfoMap::ConstIterator, it, driversInfo) {
+ if (it.data().fileBased) {
+ captionsForFileBasedDrivers += it.data().caption;
+ fileBasedDriversDict[it.data().caption] = it.data().name.lower();
+ }
+ else {
+ captionsForServerBasedDrivers += it.data().caption;
+ serverBasedDriversDict[it.data().caption] = it.data().name.lower();
+ }
+ }
+ captionsForFileBasedDrivers.sort();
+ captionsForServerBasedDrivers.sort();
+ //insert file-based
+ if (options & ShowFileDrivers) {
+ foreach(QStringList::ConstIterator, it, captionsForFileBasedDrivers) {
+ const KexiDB::Driver::Info& info = driversInfo[ fileBasedDriversDict[ *it ] ];
+ //! @todo change this if better icon is available
+ insertItem( SmallIcon("gear"), info.caption );
+ m_driversMap.insert(info.caption, info.name.lower());
+ }
+ }
+ //insert server-based
+ if (options & ShowServerDrivers) {
+ foreach(QStringList::ConstIterator, it, captionsForServerBasedDrivers) {
+ const KexiDB::Driver::Info& info = driversInfo[ serverBasedDriversDict[ *it ] ];
+ //! @todo change this if better icon is available
+ insertItem( SmallIcon("gear"), info.caption );
+ m_driversMap.insert(info.caption, info.name.lower());
+ }
+ }
+// if (listBox())
+// listBox()->sort();
+
+ // Build the names list after sorting
+ for (int i=0; i<count(); i++)
+ m_driverNames += m_driversMap[ text(i) ];
+}
+
+KexiDBDriverComboBox::~KexiDBDriverComboBox()
+{
+}
+
+QString KexiDBDriverComboBox::selectedDriverName() const
+{
+ QMapConstIterator<QString,QString> it( m_driversMap.find( text( currentItem() ) ) );
+ if (it==m_driversMap.constEnd())
+ return QString::null;
+ return it.data();
+}
+
+void KexiDBDriverComboBox::setDriverName(const QString& driverName)
+{
+ int index = m_driverNames.findIndex( driverName.lower() );
+ if (index==-1) {
+ return;
+ }
+ setCurrentItem(index);
+}
+
+#include "kexidbdrivercombobox.moc"
diff --git a/kexi/widget/kexidbdrivercombobox.h b/kexi/widget/kexidbdrivercombobox.h
new file mode 100644
index 000000000..981b67c35
--- /dev/null
+++ b/kexi/widget/kexidbdrivercombobox.h
@@ -0,0 +1,102 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDBDRIVERCOMBOBOX_H
+#define KEXIDBDRIVERCOMBOBOX_H
+
+#include <qwidget.h>
+#include <qmap.h>
+
+#include <kcombobox.h>
+
+#include <kexidb/driver.h>
+
+//! \brief Combo box widget for selecting a database driver
+/*! This widget provides a combobox for selecting a database driver.
+
+
+\b Usage: \n
+\code
+ KexiDB::DriverManager manager;
+ KexiDB::Driver::InfoMap drvs = manager.driversInfo();
+
+ KexiDBDriverComboBox* combo = new KexiDBDriverComboBox(drvs, true, 0);
+\endcode
+
+A more complete example can be found in
+<a href="http://websvn.kde.org/trunk/koffice/kexi/tests/widgets/kexidbdrivercombotest.cpp?&view=auto">koffice/kexi/tests/widgets/</a>.
+
+*/
+
+class KEXIEXTWIDGETS_EXPORT KexiDBDriverComboBox : public KComboBox
+{
+ Q_OBJECT
+
+ public:
+ enum Options {
+ ShowFileDrivers = 1,
+ ShowServerDrivers = 2,
+ ShowAll = ShowFileDrivers|ShowServerDrivers
+ };
+
+ /*! Constructs a KexiDBDriverComboBox object.
+
+ The combobox is populated with the names of the drivers in
+ \a driversInfo. A suitable value for \a driversInfo can be obtained
+ from KexiDB::DriverManager::driversInfo().
+
+ If \a includeFileBasedDrivers is set to false, then only those drivers
+ that are for database servers (those which have X-Kexi-DriverType=Network
+ in their .desktop file) are shown. */
+ KexiDBDriverComboBox(QWidget* parent, const KexiDB::Driver::InfoMap& driversInfo,
+ Options options = ShowAll );
+
+ ~KexiDBDriverComboBox();
+
+ /*! Gets a list of the names of all drivers.
+
+ Note that this returns just the names of those drivers that are in the
+ combobox: if the includeFileBasedDrivers argument to the constructor
+ was false, this won't include the file based drivers either.
+
+ \return a list of names of drivers that were found */
+ QStringList driverNames() const { return m_driverNames; }
+
+ /*! Get the name of the currrently selected driver. If the combobox is empty,
+ QString::null will be returned.
+
+ \return the name of the currently selected driver */
+ QString selectedDriverName() const;
+
+ /*! Set the currrently selected driver.
+
+ The combobox entry for \a driverName is selected. If \a driverName
+ is not listed in the combobox then there is no change. The search
+ is case insensitive.
+
+ */
+ void setDriverName(const QString& driverName);
+
+ protected:
+ QMap<QString,QString> m_driversMap;
+ QStringList m_driverNames;
+};
+
+#endif
+
diff --git a/kexi/widget/kexidswelcome.cpp b/kexi/widget/kexidswelcome.cpp
new file mode 100644
index 000000000..957132a18
--- /dev/null
+++ b/kexi/widget/kexidswelcome.cpp
@@ -0,0 +1,90 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qpushbutton.h>
+
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kglobalsettings.h>
+#include <kstdguiitem.h>
+#include <kdeversion.h>
+
+#include "kexidatasourcewizard.h"
+#include "kexidswelcome.h"
+
+KexiDSWelcome::KexiDSWelcome(KexiDataSourceWizard *parent)
+ : QWidget(parent)
+{
+ m_wiz = parent;
+ KexiDSPixmap *pic = new KexiDSPixmap(this);
+
+ QLabel *lText = new QLabel(i18n("Kexi can help you with creation of %2 using data sources in almost no time with the \"%1 Wizard\""), this);
+ lText->setAlignment(AlignTop | AlignLeft | WordBreak);
+ QCheckBox *useWizard = new QCheckBox(i18n("Create %1 using the \"%1 Wizard\""), this);
+ connect(useWizard, SIGNAL(toggled(bool)), this, SLOT(setUseWizard(bool)));
+ useWizard->setChecked(true);
+
+ QSpacerItem *spacer = new QSpacerItem(320, 220);
+ QCheckBox *dontShow = new QCheckBox(i18n("Do not show this wizard again"), this);
+
+ QGridLayout *g = new QGridLayout(this);
+
+ g->addMultiCellWidget(pic, 0, 4, 0, 0);
+ g->addWidget(lText, 0, 1);
+ g->addWidget(useWizard, 2, 1);
+ g->addItem(spacer, 3, 1);
+ g->addWidget(dontShow, 4, 1);
+}
+
+void
+KexiDSWelcome::setUseWizard(bool use)
+{
+#if KDE_IS_VERSION(3,1,9) && !defined(Q_WS_WIN)
+ bool useIcons = KGlobalSettings::showIconsOnPushButtons();
+#else
+ bool useIcons = true;
+#endif
+ if(use)
+ {
+ KGuiItem forward = KStdGuiItem::forward(KStdGuiItem::UseRTL);
+
+ if(useIcons)
+ m_wiz->nextButton()->setIconSet( forward.iconSet() );
+
+ m_wiz->nextButton()->setText(i18n("&Next"));
+ }
+ else
+ {
+ if(useIcons)
+ m_wiz->nextButton()->setIconSet(SmallIconSet("apply"));
+
+ m_wiz->nextButton()->setText(i18n("&Finish"));
+ }
+
+ m_wiz->finishNext(!use);
+}
+
+KexiDSWelcome::~KexiDSWelcome()
+{
+}
+
+#include "kexidswelcome.moc"
+
diff --git a/kexi/widget/kexidswelcome.h b/kexi/widget/kexidswelcome.h
new file mode 100644
index 000000000..23cb64feb
--- /dev/null
+++ b/kexi/widget/kexidswelcome.h
@@ -0,0 +1,48 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDSWELCOME_H
+#define KEXIDSWELCOME_H
+
+#include <qwidget.h>
+
+class KexiDataSourceWizard;
+
+/**
+ * This page is part of the KexiDataSourceWizard
+ * it is the greeting page per default, where people
+ * can choose whether they want to use the wizard or not.
+ */
+class KEXIEXTWIDGETS_EXPORT KexiDSWelcome : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiDSWelcome(KexiDataSourceWizard *parent);
+ ~KexiDSWelcome();
+
+ protected slots:
+ void setUseWizard(bool use);
+
+ private:
+ KexiDataSourceWizard *m_wiz;
+};
+
+#endif
+
diff --git a/kexi/widget/kexieditor.cpp b/kexi/widget/kexieditor.cpp
new file mode 100644
index 000000000..f482584e2
--- /dev/null
+++ b/kexi/widget/kexieditor.cpp
@@ -0,0 +1,261 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexieditor.h"
+
+#include <keximainwindow.h>
+
+#include <qlayout.h>
+#include <qframe.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+//uncomment this to enable KTextEdit-based editor
+//#define KTEXTEDIT_BASED_SQL_EDITOR
+
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+# include <ktextedit.h>
+#else
+# include <ktexteditor/document.h>
+# include <ktexteditor/view.h>
+# include <ktexteditor/editorchooser.h>
+# include <ktexteditor/editinterface.h>
+# include <ktexteditor/viewcursorinterface.h>
+# include <ktexteditor/popupmenuinterface.h>
+# include <ktexteditor/undointerface.h>
+# include <ktexteditor/configinterface.h>
+# include <ktexteditor/highlightinginterface.h>
+#endif
+
+/** Used for the shared action framework to redirect shared actions like
+copy and paste to the editor. */
+class KexiEditorSharedActionConnector : public KexiSharedActionConnector
+{
+public:
+ KexiEditorSharedActionConnector( KexiActionProxy* proxy, QObject* obj )
+ : KexiSharedActionConnector( proxy, obj )
+ {
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ plugSharedAction("edit_cut", SLOT(cut()));
+ plugSharedAction("edit_copy", SLOT(copy()));
+ plugSharedAction("edit_paste", SLOT(paste()));
+ plugSharedAction("edit_clear", SLOT(clear()));
+ plugSharedAction("edit_undo", SLOT(undo()));
+ plugSharedAction("edit_redo", SLOT(redo()));
+ plugSharedAction("edit_select_all", SLOT(selectAll()));
+#else
+ QValueList<QCString> actions;
+ actions << "edit_cut" << "edit_copy" << "edit_paste" << "edit_clear"
+ << "edit_undo" << "edit_redo" << "edit_select_all";
+ plugSharedActionsToExternalGUI(actions, dynamic_cast<KXMLGUIClient*>(obj));
+#endif
+ }
+};
+
+//! @internal
+class KexiEditorPrivate {
+ public:
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ KTextEdit *view;
+#else
+ KTextEditor::Document *doc;
+ KTextEditor::View *view;
+#endif
+};
+
+KexiEditor::KexiEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : KexiViewBase(mainWin, parent, name)
+ , d(new KexiEditorPrivate())
+{
+ QVBoxLayout *layout = new QVBoxLayout(this);
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ d->view = new KTextEdit( "", QString::null, this, "kexi_editor" );
+ //adjust font
+ connect(d->view, SIGNAL(textChanged()), this, SIGNAL(textChanged()));
+ QFont f("Courier");
+ f.setStyleStrategy(QFont::PreferAntialias);
+ f.setPointSize(d->view->font().pointSize());
+ d->view->setFont( f );
+ d->view->setCheckSpellingEnabled(false);
+#else
+ QFrame *fr = new QFrame(this);
+ fr->setFrameStyle(QFrame::Sunken|QFrame::WinPanel);
+ layout->addWidget(fr);
+ layout = new QVBoxLayout(fr);
+ layout->setMargin( 2 );
+
+ d->doc = KTextEditor::EditorChooser::createDocument(fr);
+ if (!d->doc)
+ return;
+ d->view = d->doc->createView(fr, 0L);
+
+ KTextEditor::PopupMenuInterface *popupInt = dynamic_cast<KTextEditor::PopupMenuInterface*>( d->view );
+ if(popupInt) {
+ QPopupMenu *pop = (QPopupMenu*) mainWin->factory()->container("edit", mainWin);
+ if(pop) {
+ //plugSharedAction("edit_undo", pop);
+ popupInt->installPopup(pop);
+ }
+ }
+
+ connect(d->doc, SIGNAL(textChanged()), this, SIGNAL(textChanged()));
+#endif
+ KexiEditorSharedActionConnector c(this, d->view);
+ d->view->installEventFilter(this);
+
+ layout->addWidget(d->view);
+ setViewWidget(d->view, true/*focus*/);
+ d->view->show();
+}
+
+KexiEditor::~KexiEditor()
+{
+ delete d;
+}
+
+void KexiEditor::updateActions(bool activated)
+{
+ KexiViewBase::updateActions(activated);
+}
+
+bool KexiEditor::isAdvancedEditor()
+{
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ return false;
+#else
+ return true;
+#endif
+}
+
+QString KexiEditor::text()
+{
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ return d->view->text();
+#else
+ if (!d->doc)
+ return QString::null;
+ KTextEditor::EditInterface *eIface = KTextEditor::editInterface(d->doc);
+ return eIface->text();
+#endif
+}
+
+void KexiEditor::setText(const QString &text)
+{
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ const bool was_dirty = m_parentView ? m_parentView->dirty() : dirty();
+ d->view->setText(text);
+ setDirty(was_dirty);
+#else
+ if (!d->doc)
+ return;
+ const bool was_dirty = dirty();
+ KTextEditor::EditInterface *eIface = KTextEditor::editInterface(d->doc);
+ eIface->setText(text);
+ KTextEditor::UndoInterface *undoIface = KTextEditor::undoInterface(d->doc);
+ undoIface->clearUndo();
+ undoIface->clearRedo();
+ setDirty(was_dirty);
+#endif
+}
+
+void KexiEditor::setHighlightMode(const QString& highlightmodename)
+{
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+#else
+ KTextEditor::HighlightingInterface *hl = KTextEditor::highlightingInterface( d->doc );
+ for(uint i = 0; i < hl->hlModeCount(); i++) {
+ //kdDebug() << "hlmode("<<i<<"): " << hl->hlModeName(i) << endl;
+ if (hl->hlModeName(i).contains(highlightmodename, false)) {
+ hl->setHlMode(i);
+ return;
+ }
+ }
+ hl->setHlMode(0); // 0=None, don't highlight anything.
+#endif
+}
+
+void KexiEditor::slotConfigureEditor()
+{
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ //TODO show errormessage?
+#else
+ KTextEditor::ConfigInterface *config = KTextEditor::configInterface( d->doc );
+ if (config)
+ config->configDialog();
+#endif
+}
+
+void KexiEditor::jump(int character)
+{
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ const int numRows = d->view->paragraphs();
+ int row = 0, col = 0;
+ for (int ch = 0; row < numRows; row++) {
+ const int rowLen = d->view->paragraphLength(row)+1;
+ if ((ch + rowLen) > character) {
+ col = character-ch;
+ break;
+ }
+ ch += rowLen;
+ }
+ d->view->setCursorPosition(row, col);
+#else
+ if (!d->doc)
+ return;
+ KTextEditor::EditInterface *ei = KTextEditor::editInterface(d->doc);
+ const int numRows = ei->numLines();
+ int row = 0, col = 0;
+ for (int ch = 0; row < numRows; row++) {
+ const int rowLen = ei->lineLength(row)+1;
+ if ((ch + rowLen) > character) {
+ col = character-ch;
+ break;
+ }
+ ch += rowLen;
+ }
+ KTextEditor::ViewCursorInterface *ci = KTextEditor::viewCursorInterface(d->view);
+ ci->setCursorPositionReal(row, col);
+#endif
+}
+
+void KexiEditor::setCursorPosition(int line, int col)
+{
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ d->view->setCursorPosition(line, col);
+#else
+ KTextEditor::ViewCursorInterface *ci = KTextEditor::viewCursorInterface( d->view );
+ ci->setCursorPosition(line, col);
+#endif
+}
+
+void KexiEditor::clearUndoRedo()
+{
+#ifdef KTEXTEDIT_BASED_SQL_EDITOR
+ //TODO how to remove undo/redo from a KTextEdit?
+#else
+ KTextEditor::UndoInterface* u = KTextEditor::undoInterface( d->doc );
+ u->clearUndo();
+ u->clearRedo();
+#endif
+}
+
+#include "kexieditor.moc"
+
diff --git a/kexi/widget/kexieditor.h b/kexi/widget/kexieditor.h
new file mode 100644
index 000000000..fce5f45b1
--- /dev/null
+++ b/kexi/widget/kexieditor.h
@@ -0,0 +1,120 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIEDITOR_H
+#define KEXIEDITOR_H
+
+#include <qwidget.h>
+#include "kexiviewbase.h"
+
+class KTextEdit;
+class KexiEditorPrivate;
+
+namespace KTextEditor
+{
+ class Document;
+ class View;
+}
+
+//! An text editor view that uses both KTextEditor and KTextEdit
+/*! It is used for SQL and script editor. */
+class KEXIEXTWIDGETS_EXPORT KexiEditor : public KexiViewBase
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ *
+ * \param mainWin The \a KexiMainWindow instance this KexiEditor
+ * belongs too.
+ * \param parent The parent \a QWidget this KexiEditor is child
+ * of. You don't need to free the KexiEditor cause Qt
+ * will handle that for us.
+ * \param name The name this KexiEditor has. Used only for debugging.
+ */
+ KexiEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0);
+
+ /**
+ * Destructor.
+ */
+ virtual ~KexiEditor();
+
+ /**
+ * \return true if internally the KTextEditor::EditorChooser got
+ * used else, if a simple KTextEdit is used, false is returned.
+ */
+ static bool isAdvancedEditor();
+
+ /**
+ * \return the text displayed in the editor-widget.
+ */
+ QString text();
+
+ /**
+ * Set the highlight-mode to \p highlightmodename . If
+ * \a isAdvancedEditor returns false (KTextEdit is used
+ * rather then KTextEditor), then the method just does
+ * nothing. The \p highlightmodename could be any kind
+ * of string like e.g. "python", "kjs" or "sql"
+ * KTextEditor supports.
+ */
+ void setHighlightMode(const QString& highlightmodename);
+
+ /**
+ * Find row and column for this \p character and jump to the
+ * position.
+ */
+ void jump(int character);
+
+ /**
+ * Set the cursor position to \p line and \p col .
+ */
+ void setCursorPosition(int line, int col);
+
+ /**
+ * Clear all remembered undo/redo-actions. Only
+ * avaiable if \a isAdvancedEditor returns true.
+ */
+ void clearUndoRedo();
+
+ public slots:
+ /*! Sets editor's text to \a text. 'Dirty' flag remains unchanged.
+ Undo/redo buffer is cleared.*/
+ void setText(const QString &text);
+ /*! Display the configuration-dialog. Only avaiable if isAdvancedEditor() returns true. */
+ void slotConfigureEditor();
+
+ protected:
+ /*! Update the actions. This call is redirected to \a KexiViewBase::updateActions */
+ virtual void updateActions(bool activated);
+
+ signals:
+ /*! Emitted if the text displayed in the editor changed. */
+ void textChanged();
+
+ private:
+ /*! Private d-pointer class. */
+ KexiEditorPrivate *d;
+};
+
+#endif
diff --git a/kexi/widget/kexifieldcombobox.cpp b/kexi/widget/kexifieldcombobox.cpp
new file mode 100644
index 000000000..c0355e9c2
--- /dev/null
+++ b/kexi/widget/kexifieldcombobox.cpp
@@ -0,0 +1,250 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexifieldcombobox.h"
+
+#include <qheader.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qapplication.h>
+#include <qbitmap.h>
+#include <qstyle.h>
+#include <qlistbox.h>
+
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <kdeversion.h>
+#include <kconfig.h>
+#include <kglobalsettings.h>
+#include <klocale.h>
+
+#include <kexidb/tableschema.h>
+#include <kexidb/queryschema.h>
+#include <kexidb/utils.h>
+#include <kexiutils/utils.h>
+#include <kexidragobjects.h>
+#include <kexiproject.h>
+
+//! @internal
+class KexiFieldComboBox::Private
+{
+ public:
+ Private()
+// : schema(0)
+ : keyIcon( SmallIcon("key") )
+ , noIcon( KexiUtils::emptyIcon(KIcon::Small) )
+ , table(true)
+ {
+ }
+ ~Private()
+ {
+// delete schema;
+ }
+ QGuardedPtr<KexiProject> prj;
+// KexiDB::TableOrQuerySchema* schema;
+ QPixmap keyIcon, noIcon;
+ QString tableOrQueryName;
+ QString fieldOrExpression;
+ QMap<QString, QString> captions;
+ bool table : 1;
+};
+
+//------------------------
+
+KexiFieldComboBox::KexiFieldComboBox(QWidget *parent, const char *name)
+ : KComboBox(true/*rw*/, parent, name)
+ , d(new Private())
+{
+ setInsertionPolicy(NoInsertion);
+ setCompletionMode(KGlobalSettings::CompletionPopupAuto);
+ setSizeLimit( 16 );
+ connect(this, SIGNAL(activated(int)), this, SLOT(slotActivated(int)));
+ connect(this, SIGNAL(returnPressed(const QString &)), this, SLOT(slotReturnPressed(const QString &)));
+
+// setAcceptDrops(true);
+// viewport()->setAcceptDrops(true);
+}
+
+KexiFieldComboBox::~KexiFieldComboBox()
+{
+ delete d;
+}
+
+void KexiFieldComboBox::setProject(KexiProject *prj)
+{
+ if ((KexiProject*)d->prj==prj)
+ return;
+ d->prj = prj;
+ setTableOrQuery("", true);
+}
+
+KexiProject* KexiFieldComboBox::project() const
+{
+ return d->prj;
+}
+
+void KexiFieldComboBox::setTableOrQuery(const QString& name, bool table)
+{
+ d->tableOrQueryName = name;
+ d->table = table;
+ clear();
+ d->captions.clear();
+ insertItem("");
+// delete d->schema;
+ if (d->tableOrQueryName.isEmpty() || !d->prj)
+ return;
+
+ KexiDB::TableOrQuerySchema tableOrQuery(d->prj->dbConnection(), d->tableOrQueryName.latin1(), d->table);
+ if (!tableOrQuery.table() && !tableOrQuery.query())
+ return;
+
+// bool hasPKeys = true; //t->hasPrimaryKeys();
+ KexiDB::QueryColumnInfo::Vector columns = tableOrQuery.columns();
+ const int count = columns.count();
+ for(int i=0; i < count; i++)
+ {
+ KexiDB::QueryColumnInfo *colinfo = columns[i];
+ insertItem(
+ (colinfo && (colinfo->field->isPrimaryKey() || colinfo->field->isUniqueKey()))
+ ? d->keyIcon
+ : d->noIcon
+ , colinfo->aliasOrName());
+ completionObject()->addItem(colinfo->aliasOrName());
+ //store user-friendly caption (used by fieldOrExpressionCaption())
+ d->captions.insert( colinfo->aliasOrName(), colinfo->captionOrAliasOrName() );
+ }
+
+ //update selection
+ setFieldOrExpression(d->fieldOrExpression);
+}
+
+QString KexiFieldComboBox::tableOrQueryName() const
+{
+ return d->tableOrQueryName;
+}
+
+bool KexiFieldComboBox::isTableAssigned() const
+{
+ return d->table;
+}
+
+void KexiFieldComboBox::setFieldOrExpression(const QString& string)
+{
+ const QString name(string); //string.stripWhiteSpace().lower());
+ const int pos = name.find('.');
+ if (pos==-1) {
+ d->fieldOrExpression = name;
+ }
+ else {
+ QString objectName = name.left(pos);
+ if (d->tableOrQueryName!=objectName) {
+ d->fieldOrExpression = name;
+ setCurrentItem(0);
+ setCurrentText(name);
+//! @todo show error
+ kexiwarn << "KexiFieldComboBox::setField(): invalid table/query name in '" << name << "'" << endl;
+ return;
+ }
+ d->fieldOrExpression = name.mid(pos+1);
+ }
+
+ QListBoxItem *item = listBox()->findItem(d->fieldOrExpression);
+ if (!item) {
+ setCurrentItem(0);
+ setCurrentText(d->fieldOrExpression);
+ //todo: show 'the item doesn't match' info?
+ return;
+ }
+ setCurrentItem( listBox()->index(item) );
+}
+
+void KexiFieldComboBox::setFieldOrExpression(int index)
+{
+ index++; //skip 1st empty item
+ if (index>=count()) {
+ kexiwarn << QString("KexiFieldComboBox::setFieldOrExpression(int index): index %1 "
+ "out of range (0..%2)").arg(index).arg(count()-1) << endl;
+ index = -1;
+ }
+ if (index<=0) {
+ setCurrentItem(0);
+ d->fieldOrExpression = QString::null;
+ }
+ else {
+ setCurrentItem(index);
+ d->fieldOrExpression = currentText();
+ }
+}
+
+QString KexiFieldComboBox::fieldOrExpression() const
+{
+ return d->fieldOrExpression;
+}
+
+int KexiFieldComboBox::indexOfField() const
+{
+ KexiDB::TableOrQuerySchema tableOrQuery(d->prj->dbConnection(), d->tableOrQueryName.latin1(), d->table);
+ if (!tableOrQuery.table() && !tableOrQuery.query())
+ return -1;
+
+ return currentItem()>0 ? (currentItem()-1) : -1;
+}
+
+QString KexiFieldComboBox::fieldOrExpressionCaption() const
+{
+ return d->captions[ d->fieldOrExpression ];
+}
+
+void KexiFieldComboBox::slotActivated(int i)
+{
+ d->fieldOrExpression = text(i);
+ emit selected();
+}
+
+void KexiFieldComboBox::slotReturnPressed(const QString & text)
+{
+ //text is available: select item for this text:
+ int index;
+ if (text.isEmpty()) {
+ index = 0;
+ }
+ else {
+ QListBoxItem *item = listBox()->findItem( text, Qt::ExactMatch );
+ if (!item)
+ return;
+ index = listBox()->index( item );
+ if (index < 1)
+ return;
+ }
+ setCurrentItem( index );
+ slotActivated( index );
+}
+
+void KexiFieldComboBox::focusOutEvent( QFocusEvent *e )
+{
+ KComboBox::focusOutEvent( e );
+ // accept changes if the focus is moved
+ if (!KexiUtils::hasParent(this, focusWidget())) //(a check needed because drop-down listbox also causes a focusout)
+ slotReturnPressed(currentText());
+}
+
+#include "kexifieldcombobox.moc"
diff --git a/kexi/widget/kexifieldcombobox.h b/kexi/widget/kexifieldcombobox.h
new file mode 100644
index 000000000..238168d21
--- /dev/null
+++ b/kexi/widget/kexifieldcombobox.h
@@ -0,0 +1,82 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFIELDCOMBOBOX_H
+#define KEXIFIELDCOMBOBOX_H
+
+#include <qpixmap.h>
+#include <kcombobox.h>
+
+namespace KexiDB {
+ class TableOrQuerySchema;
+}
+class KexiProject;
+
+/*! This widget provides a list of fields from a table or query
+ within a combobox, so user can pick one of them.
+*/
+class KEXIEXTWIDGETS_EXPORT KexiFieldComboBox : public KComboBox
+{
+ Q_OBJECT
+
+ public:
+ KexiFieldComboBox(QWidget *parent, const char *name = 0);
+ virtual ~KexiFieldComboBox();
+
+// /*! Sets table or query schema \a schema.
+// The schema object will be owned by the KexiFieldComboBox object. */
+// void setSchema(KexiDB::TableOrQuerySchema* schema);
+
+// KexiDB::TableOrQuerySchema* schema() const { return m_schema; }
+
+ public slots:
+ //! \return global project that is used to retrieve schema informationm for this combo box.
+ KexiProject* project() const;
+
+ //! Sets global project that is used to retrieve schema informationm for this combo box.
+ void setProject(KexiProject *prj);
+
+ void setTableOrQuery(const QString& name, bool table);
+ QString tableOrQueryName() const;
+ bool isTableAssigned() const;
+ void setFieldOrExpression(const QString& string);
+ void setFieldOrExpression(int index);
+ QString fieldOrExpression() const;
+ QString fieldOrExpressionCaption() const;
+
+ /*! \return index of selected table or query field.
+ -1 is returned if there is nothing selected or expression is selected
+ of project is not assigned or table or query is not assigned. */
+ int indexOfField() const;
+
+ signals:
+ void selected();
+
+ protected slots:
+ void slotActivated(int);
+ void slotReturnPressed(const QString & text);
+
+ protected:
+ virtual void focusOutEvent( QFocusEvent *e );
+
+ class Private;
+ Private *d;
+};
+
+#endif
diff --git a/kexi/widget/kexifieldlistview.cpp b/kexi/widget/kexifieldlistview.cpp
new file mode 100644
index 000000000..84b577a65
--- /dev/null
+++ b/kexi/widget/kexifieldlistview.cpp
@@ -0,0 +1,180 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexifieldlistview.h"
+
+#include <qheader.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qapplication.h>
+#include <qbitmap.h>
+#include <qstyle.h>
+
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <kdeversion.h>
+#include <kconfig.h>
+#include <kglobalsettings.h>
+#include <klocale.h>
+
+#include <kexidb/tableschema.h>
+#include <kexidb/queryschema.h>
+#include <kexidb/utils.h>
+#include <kexidragobjects.h>
+#include <kexiutils/utils.h>
+
+KexiFieldListView::KexiFieldListView(QWidget *parent, const char *name, int options)
+ : KListView(parent, name)
+ , m_schema(0)
+ , m_keyIcon(SmallIcon("key"))
+ , m_noIcon(KexiUtils::emptyIcon(KIcon::Small))
+ , m_options(options)
+ , m_allColumnsItem(0)
+{
+ setAcceptDrops(true);
+ viewport()->setAcceptDrops(true);
+ setDropVisualizer(false);
+ setDropHighlighter(true);
+ setAllColumnsShowFocus(true);
+ addColumn(i18n("Field Name"));
+ if (m_options & ShowDataTypes)
+ addColumn(i18n("Data Type"));
+ if (m_options & AllowMultiSelection)
+ setSelectionMode(QListView::Extended);
+ setResizeMode(QListView::LastColumn);
+// header()->hide();
+ setSorting(-1, true); // disable sorting
+ setDragEnabled(true);
+
+ connect(this, SIGNAL(doubleClicked(QListViewItem*, const QPoint &, int)),
+ this, SLOT(slotDoubleClicked(QListViewItem*)));
+}
+
+KexiFieldListView::~KexiFieldListView()
+{
+ delete m_schema;
+}
+
+void KexiFieldListView::setSchema(KexiDB::TableOrQuerySchema* schema)
+{
+ if (schema && m_schema == schema)
+ return;
+ m_allColumnsItem = 0;
+ clear();
+ delete m_schema;
+ m_schema = schema;
+ if (!m_schema)
+ return;
+
+ int order=0;
+ bool hasPKeys = true; //t->hasPrimaryKeys();
+ KListViewItem *item = 0;
+ KexiDB::QueryColumnInfo::Vector columns = m_schema->columns(true /*unique*/);
+ const int count = columns.count();
+ for(int i=-1; i < count; i++)
+ {
+ KexiDB::QueryColumnInfo *colinfo = 0;
+ if (i==-1) {
+ if (! (m_options & ShowAsterisk))
+ continue;
+ item = new KListViewItem(this, item, i18n("* (All Columns)"));
+ m_allColumnsItem = item;
+ }
+ else {
+ colinfo = columns[i];
+ item = new KListViewItem(this, item, colinfo->aliasOrName());
+ if (m_options & ShowDataTypes)
+ item->setText(1, colinfo->field->typeName());
+ }
+ if(colinfo && (colinfo->field->isPrimaryKey() || colinfo->field->isUniqueKey()))
+ item->setPixmap(0, m_keyIcon);
+ else if (hasPKeys) {
+ item->setPixmap(0, m_noIcon);
+ }
+ order++;
+ }
+
+ setCurrentItem(firstChild());
+}
+
+#if 0
+QSize KexiFieldListView::sizeHint()
+{
+ QFontMetrics fm(font());
+
+ kdDebug() << m_table->name() << " cw=" << columnWidth(1) + fm.width("i") << ", " << fm.width(m_table->name()+" ") << endl;
+
+ QSize s(
+ QMAX( columnWidth(1) + fm.width("i"), fm.width(m_table->name()+" ")),
+ childCount()*firstChild()->totalHeight() + 4 );
+// QSize s( columnWidth(1), childCount()*firstChild()->totalHeight() + 3*firstChild()->totalHeight()/10);
+ return s;
+}
+
+void KexiFieldListView::setReadOnly(bool b)
+{
+ setAcceptDrops(!b);
+ viewport()->setAcceptDrops(!b);
+}
+#endif
+
+QDragObject* KexiFieldListView::dragObject()
+{
+ if (!schema())
+ return 0;
+ const QStringList selectedFields( selectedFieldNames() );
+ return new KexiFieldDrag(m_schema->table() ? "kexi/table" : "kexi/query",
+ m_schema->name(), selectedFields, this, "KexiFieldDrag");
+/* if (selectedItem()) {
+ KexiFieldDrag *drag = new KexiFieldDrag("kexi/table", m_schema->name(),
+ selectedItem()->text(1), this, "KexiFieldDrag");
+ return drag;
+ }*/
+}
+
+QStringList KexiFieldListView::selectedFieldNames() const
+{
+ if (!schema())
+ return QStringList();
+ QStringList selectedFields;
+ for (QListViewItem *item = firstChild(); item; item = item->nextSibling()) {
+ if (item->isSelected()) {
+//! @todo what about query fields/aliases? it.current()->text(0) can be not enough
+ if (item == m_allColumnsItem && m_allColumnsItem)
+ selectedFields.append("*");
+ else
+ selectedFields.append(item->text(0));
+ }
+ }
+ return selectedFields;
+}
+
+void KexiFieldListView::slotDoubleClicked(QListViewItem* item)
+{
+ if (schema() && item) {
+ //! @todo what about query fields/aliases? it.current()->text(0) can be not enough
+ emit fieldDoubleClicked(schema()->table() ? "kexi/table" : "kexi/query",
+ schema()->name(), item->text(0));
+ }
+}
+
+#include "kexifieldlistview.moc"
diff --git a/kexi/widget/kexifieldlistview.h b/kexi/widget/kexifieldlistview.h
new file mode 100644
index 000000000..fcbe1c5f1
--- /dev/null
+++ b/kexi/widget/kexifieldlistview.h
@@ -0,0 +1,82 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFIELDLISTVIEW_H
+#define KEXIFIELDLISTVIEW_H
+
+#include <qframe.h>
+#include <qpixmap.h>
+#include <klistview.h>
+
+class KListViewItem;
+
+namespace KexiDB {
+ class TableOrQuerySchema;
+}
+
+/*! This widget provides a list of fields from a table or query.
+*/
+class KEXIEXTWIDGETS_EXPORT KexiFieldListView : public KListView
+{
+ Q_OBJECT
+
+ public:
+ //! Flags used to alter list's behaviour and appearance
+ enum Options {
+ ShowDataTypes = 1, //!< if set, 'data type' column is added
+ ShowAsterisk = 2, //!< if set, asterisk ('*') item is prepended to the list
+ AllowMultiSelection = 4 //!< if set, multiple selection is allowed
+ };
+
+ KexiFieldListView(QWidget *parent, const char *name = 0,
+ int options = ShowDataTypes | AllowMultiSelection );
+ virtual ~KexiFieldListView();
+
+ /*! Sets table or query schema \a schema.
+ The schema object will be owned by the KexiFieldListView object. */
+ void setSchema(KexiDB::TableOrQuerySchema* schema);
+
+ /*! \return table or query schema schema set for this widget. */
+ KexiDB::TableOrQuerySchema* schema() const { return m_schema; }
+
+ /*! \return list of selected field names. */
+ QStringList selectedFieldNames() const;
+
+// void setReadOnly(bool);
+// virtual QSize sizeHint();
+
+ signals:
+ /*! Emitted when a field is double clicked */
+ void fieldDoubleClicked(const QString& sourceMimeType, const QString& sourceName,
+ const QString& fieldName);
+
+ protected slots:
+ void slotDoubleClicked(QListViewItem* item);
+
+ protected:
+ virtual QDragObject *dragObject();
+
+ KexiDB::TableOrQuerySchema* m_schema;
+ QPixmap m_keyIcon; //!< a small "primary key" icon for 0-th column
+ QPixmap m_noIcon; //!< blank icon of the same size as m_keyIcon
+ int m_options;
+ KListViewItem *m_allColumnsItem;
+};
+
+#endif
diff --git a/kexi/widget/kexifilterdlg.cpp b/kexi/widget/kexifilterdlg.cpp
new file mode 100644
index 000000000..6f847e61f
--- /dev/null
+++ b/kexi/widget/kexifilterdlg.cpp
@@ -0,0 +1,149 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qlistview.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qheader.h>
+#include <qstringlist.h>
+
+#include "kexiproject.h"
+#include "kexiprojecthandler.h"
+#include "kexiprojecthandleritem.h"
+#include "kexidataprovider.h"
+#include "kexifilterdlg.h"
+#include "kexiquerydesignersqleditor.h"
+
+KexiFilterDlg::KexiFilterDlg(KexiProject *project, QWidget *parent, const char *name)
+ : QDialog(parent, name)
+{
+ m_project = project;
+
+ QHBoxLayout *lbraces = new QHBoxLayout(0, 0, 4);
+
+ QPushButton *bsBO = createMiniButton("[");
+ QPushButton *bBO = createMiniButton("(");
+ QPushButton *bBC = createMiniButton(")");
+ QPushButton *bsBC = createMiniButton("]");
+ lbraces->addWidget(bsBO);
+ lbraces->addWidget(bBO);
+ lbraces->addWidget(bBC);
+ lbraces->addWidget(bsBC);
+
+ QHBoxLayout *lcond = new QHBoxLayout(0, 0, 4);
+ QPushButton *blt = createMiniButton("<");
+ QPushButton *beq = createMiniButton("=");
+ QPushButton *bgt = createMiniButton(">");
+ QPushButton *bp = createMiniButton("%");
+ lcond->addWidget(blt);
+ lcond->addWidget(beq);
+ lcond->addWidget(bgt);
+ lcond->addWidget(bp);
+
+ QHBoxLayout *lbool = new QHBoxLayout(0, 0, 4);
+ QPushButton *bAnd = new QPushButton("AND", this);
+ bAnd->setFlat(true);
+ QPushButton *bOr = new QPushButton("OR", this);
+ bOr->setFlat(true);
+ QPushButton *bLike = new QPushButton("LIKE", this);
+ bLike->setFlat(true);
+ lbool->addWidget(bLike);
+ lbool->addWidget(bAnd);
+ lbool->addWidget(bOr);
+
+ m_catalog = new QListView(this);
+ m_catalog->addColumn("a");
+ m_catalog->header()->hide();
+
+ KexiQueryDesignerSQLEditor *e = new KexiQueryDesignerSQLEditor(this);
+
+ setupCatalog(QString("kexi/table"));
+
+ QGridLayout *g = new QGridLayout(this);
+ g->setSpacing(6);
+ g->addMultiCellWidget(e, 0, 0, 0, 2);
+ g->addItem(lbraces, 1, 0);
+ g->addItem(lcond, 1, 1);
+ g->addItem(lbool, 1, 2);
+ g->addMultiCellWidget(m_catalog, 2, 2, 0, 2);
+}
+
+QPushButton*
+KexiFilterDlg::createMiniButton(const QString &text)
+{
+ QPushButton *p = new QPushButton(text, this);
+ p->setFlat(true);
+ p->setMaximumSize(QSize(20, 300));
+
+ return p;
+}
+
+void
+KexiFilterDlg::setupCatalog(const QStringList &mimes)
+{
+ m_catalog->clear();
+ m_catalog->setRootIsDecorated(true);
+ QStringList::ConstIterator it, end( mimes.constEnd() );
+ for( it = mimes.constBegin(); it != end; ++it)
+ {
+ KexiProjectHandler *h = m_project->handlerForMime(*it);
+ if(h)
+ {
+ QListViewItem *base = new QListViewItem(m_catalog, h->name());
+ base->setPixmap(0, h->groupPixmap());
+
+ QDictIterator<KexiProjectHandlerItem> iit(*h->items()); // See QDictIterator
+ for(; iit.current(); ++iit )
+ {
+ QListViewItem *bi = new QListViewItem(base, iit.current()->name());
+ bi->setPixmap(0, h->itemPixmap());
+
+ KexiDataProvider *prov=KEXIDATAPROVIDER(h);
+ if(prov)
+ {
+ QStringList fields = prov->fields(0, iit.current()->identifier());
+ QStringList::ConstIterator fit, end( fields.constEnd() );
+ for( fit = fields.constBegin(); fit != end; ++fit)
+ {
+ QListViewItem *bif = new QListViewItem(bi, (*fit));
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+KexiFilterDlg::setupCatalog(const QString &mime)
+{
+ QStringList l;
+ l.append(mime);
+ setupCatalog(l);
+}
+
+void
+KexiFilterDlg::insert(QListViewItem *)
+{
+}
+
+KexiFilterDlg::~KexiFilterDlg()
+{
+}
+
+#include "kexifilterdlg.moc"
diff --git a/kexi/widget/kexifilterdlg.h b/kexi/widget/kexifilterdlg.h
new file mode 100644
index 000000000..e5544888e
--- /dev/null
+++ b/kexi/widget/kexifilterdlg.h
@@ -0,0 +1,50 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFILTERDLG_H
+#define KEXIFILTERDLG_H
+
+#include <qdialog.h>
+
+class QPushButton;
+class QListView;
+class QListViewItem;
+class KexiProject;
+
+class KEXIEXTWIDGETS_EXPORT KexiFilterDlg : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ KexiFilterDlg(KexiProject *p, QWidget *parent=0, const char *name=0);
+ ~KexiFilterDlg();
+
+ QPushButton *createMiniButton(const QString &text);
+ void setupCatalog(const QStringList &mimes);
+ void setupCatalog(const QString &mime);
+
+ protected slots:
+ void insert(QListViewItem *);
+
+ protected:
+ QListView *m_catalog;
+ KexiProject *m_project;
+};
+
+#endif
diff --git a/kexi/widget/kexiprjtypeselector.cpp b/kexi/widget/kexiprjtypeselector.cpp
new file mode 100644
index 000000000..71dafc6c1
--- /dev/null
+++ b/kexi/widget/kexiprjtypeselector.cpp
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexiprjtypeselector.h"
+#include <qlabel.h>
+#include <kiconloader.h>
+#include <kmimetype.h>
+#include <kexidb/driver.h>
+
+KexiPrjTypeSelector::KexiPrjTypeSelector( QWidget* parent )
+ : KexiPrjTypeSelectorBase( parent, "KexiPrjTypeSelector" )
+{
+ QString none;
+ icon_file->setPixmap(
+ KGlobal::iconLoader()->loadIcon( KMimeType::mimeType(
+ KexiDB::Driver::defaultFileBasedDriverMimeType() )->icon(none,0), KIcon::Desktop, 48
+ )
+ );
+ icon_file->setFixedSize(icon_file->pixmap()->size()/2);
+ icon_server->setPixmap( DesktopIcon("network", 48) );
+ icon_server->setFixedSize(icon_server->pixmap()->size()/2);
+}
+
+KexiPrjTypeSelector::~KexiPrjTypeSelector()
+{
+}
+
+#include "kexiprjtypeselector.moc"
diff --git a/kexi/widget/kexiprjtypeselector.h b/kexi/widget/kexiprjtypeselector.h
new file mode 100644
index 000000000..efb8a294c
--- /dev/null
+++ b/kexi/widget/kexiprjtypeselector.h
@@ -0,0 +1,38 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIPRJTYPESELECTOR_H
+#define KEXIPRJTYPESELECTOR_H
+
+#include "kexiprjtypeselectorbase.h"
+
+//! @short A simple widget with radio buttons with "show file/server-based projects" options
+class KEXIEXTWIDGETS_EXPORT KexiPrjTypeSelector : public KexiPrjTypeSelectorBase
+{
+ Q_OBJECT
+
+ public:
+ KexiPrjTypeSelector( QWidget* parent = 0 );
+ ~KexiPrjTypeSelector();
+
+ protected slots:
+ virtual void languageChange() { KexiPrjTypeSelectorBase::languageChange(); }
+};
+
+#endif // KEXIPRJTYPESELECTOR_H
diff --git a/kexi/widget/kexiprjtypeselectorbase.ui b/kexi/widget/kexiprjtypeselectorbase.ui
new file mode 100644
index 000000000..a8031909c
--- /dev/null
+++ b/kexi/widget/kexiprjtypeselectorbase.ui
@@ -0,0 +1,162 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>KexiPrjTypeSelectorBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KexiPrjTypeSelectorBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>254</width>
+ <height>61</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QButtonGroup">
+ <property name="name">
+ <cstring>buttonGroup</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="title">
+ <string></string>
+ </property>
+ <property name="selectedId" stdset="0">
+ <number>1</number>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QRadioButton" row="1" column="1">
+ <property name="name">
+ <cstring>option_server</cstring>
+ </property>
+ <property name="text">
+ <string>Projects stored on a database server</string>
+ </property>
+ <property name="buttonGroupId">
+ <number>2</number>
+ </property>
+ </widget>
+ <widget class="QRadioButton" row="0" column="1">
+ <property name="name">
+ <cstring>option_file</cstring>
+ </property>
+ <property name="text">
+ <string>Projects stored in a file</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="buttonGroupId">
+ <number>1</number>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>icon_file</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignLeft</set>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>icon_server</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignLeft</set>
+ </property>
+ </widget>
+ <widget class="QFrame" row="2" column="1">
+ <property name="name">
+ <cstring>frame_server</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>1</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Plain</enum>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ </vbox>
+</widget>
+<connections>
+ <connection>
+ <sender>buttonGroup</sender>
+ <signal>clicked(int)</signal>
+ <receiver>KexiPrjTypeSelectorBase</receiver>
+ <slot>slotSelectionChanged(int)</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>option_file</tabstop>
+ <tabstop>option_server</tabstop>
+</tabstops>
+<includes>
+ <include location="local" impldecl="in implementation">kexiprjtypeselectorbase.ui.h</include>
+</includes>
+<slots>
+ <slot access="protected" specifier="non virtual">slotSelectionChanged( int id )</slot>
+</slots>
+<functions>
+ <function access="private" specifier="non virtual">init()</function>
+</functions>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kexi/widget/kexiprjtypeselectorbase.ui.h b/kexi/widget/kexiprjtypeselectorbase.ui.h
new file mode 100644
index 000000000..03f1301fd
--- /dev/null
+++ b/kexi/widget/kexiprjtypeselectorbase.ui.h
@@ -0,0 +1,23 @@
+/****************************************************************************
+** ui.h extension file, included from the uic-generated form implementation.
+**
+** If you want to add, delete, or rename functions or slots, use
+** Qt Designer to update this file, preserving your code.
+**
+** You should not define a constructor or destructor in this file.
+** Instead, write your code in functions called init() and destroy().
+** These will automatically be called by the form's constructor and
+** destructor.
+*****************************************************************************/
+
+
+void KexiPrjTypeSelectorBase::init()
+{
+ slotSelectionChanged( 1 );
+}
+
+
+void KexiPrjTypeSelectorBase::slotSelectionChanged( int id )
+{
+ frame_server->setEnabled(id==2);
+}
diff --git a/kexi/widget/kexipropertyeditorview.cpp b/kexi/widget/kexipropertyeditorview.cpp
new file mode 100644
index 000000000..5225a7afd
--- /dev/null
+++ b/kexi/widget/kexipropertyeditorview.cpp
@@ -0,0 +1,223 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexipropertyeditorview.h"
+#include "keximainwindow.h"
+#include <koproperty/set.h>
+#include <koproperty/editor.h>
+#include <koproperty/property.h>
+
+#include <klocale.h>
+#include <kiconloader.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+
+KexiObjectInfoLabel::KexiObjectInfoLabel(QWidget* parent, const char* name)
+ : QWidget(parent, name)
+{
+ QHBoxLayout *hlyr = new QHBoxLayout(this);
+ m_objectIconLabel = new QLabel(this);
+ m_objectIconLabel->setMargin(2);
+ setFixedHeight( IconSize(KIcon::Small) + 2 + 2 );
+ hlyr->addWidget(m_objectIconLabel);
+ m_objectNameLabel = new QLabel(this);
+ m_objectNameLabel->setMargin(2);
+ m_objectNameLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
+ hlyr->addWidget(m_objectNameLabel);
+}
+
+KexiObjectInfoLabel::~KexiObjectInfoLabel()
+{
+}
+
+void KexiObjectInfoLabel::setObjectClassIcon(const QString& name)
+{
+ m_classIcon = name;
+ if (m_classIcon.isEmpty())
+ m_objectIconLabel->setFixedWidth( 0 );
+ else
+ m_objectIconLabel->setFixedWidth( IconSize(KIcon::Small) + 2 + 2 );
+ m_objectIconLabel->setPixmap( SmallIcon(name) );
+}
+
+void KexiObjectInfoLabel::setObjectClassName(const QString& name)
+{
+ m_className = name;
+ updateName();
+}
+
+void KexiObjectInfoLabel::setObjectName(const QString& name)
+{
+ m_objectName = name;
+ updateName();
+}
+
+void KexiObjectInfoLabel::updateName()
+{
+ QString txt( m_className );
+ if (txt.isEmpty())
+ txt = m_objectName;
+ else if (!m_objectName.isEmpty())
+ txt += QString(" \"%1\"").arg(m_objectName);
+ m_objectNameLabel->setText(txt);
+}
+
+void KexiObjectInfoLabel::setBuddy( QWidget * buddy )
+{
+ m_objectNameLabel->setBuddy(buddy);
+}
+
+//------------------------------
+
+//! @internal
+class KexiPropertyEditorView::Private
+{
+ public:
+ Private()
+ {
+ }
+ KoProperty::Editor *editor;
+// QLabel *objectIcon;
+// QString iconName;
+// QLabel *objectClassName;
+ KexiObjectInfoLabel *objectInfoLabel;
+};
+
+//------------------------------
+
+KexiPropertyEditorView::KexiPropertyEditorView(KexiMainWindow *mainWin, QWidget* parent)
+ : QWidget(parent, "KexiPropertyEditorView")
+ , d(new Private())
+{
+ setCaption(i18n("Properties"));
+ //TODO: set a nice icon
+ setIcon(*mainWin->icon());
+
+ QVBoxLayout *lyr = new QVBoxLayout(this);
+
+ //add object class info
+ d->objectInfoLabel = new KexiObjectInfoLabel(this, "KexiObjectInfoLabel");
+ lyr->addWidget(d->objectInfoLabel);
+
+ /*
+ QHBoxLayout *vlyr = new QHBoxLayout(lyr);
+ d->objectIcon = new QLabel(this);
+ d->objectIcon->setMargin(2);
+ d->objectIcon->setFixedHeight( IconSize(KIcon::Small) + 2 + 2 );
+ vlyr->addWidget(d->objectIcon);
+ d->objectClassName = new QLabel(this);
+ d->objectClassName->setMargin(2);
+ d->objectClassName->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
+ vlyr->addWidget(d->objectClassName);*/
+
+ d->editor = new KoProperty::Editor(this, true /*AutoSync*/, "propeditor");
+ lyr->addWidget(d->editor);
+ setFocusProxy(d->editor);
+ d->objectInfoLabel->setBuddy(d->editor);
+ setFocusPolicy(WheelFocus);
+
+ connect(d->editor, SIGNAL(propertySetChanged(KoProperty::Set*)),
+ this, SLOT(slotPropertySetChanged(KoProperty::Set*)));
+
+// d->iconName = "dummy";
+ slotPropertySetChanged(0);
+}
+
+KexiPropertyEditorView::~KexiPropertyEditorView()
+{
+ delete d;
+}
+
+QSize KexiPropertyEditorView::sizeHint() const
+{
+ return QSize(200,200);//m_editor->sizeHint();
+}
+
+QSize KexiPropertyEditorView::minimumSizeHint() const
+{
+ return QSize(200,200);//m_editor->sizeHint();
+}
+
+/*void KexiPropertyEditorView::setGeometry ( const QRect &r )
+{
+ QWidget::setGeometry(r);
+}
+
+void KexiPropertyEditorView::resize ( int w, int h )
+{
+ QWidget::resize( w, h );
+}*/
+
+KoProperty::Editor *KexiPropertyEditorView::editor() const
+{
+ return d->editor;
+}
+
+/*! Updates \a infoLabel widget by reusing properties provided by property set \a set.
+ Read documentation of KexiPropertyEditorView class for information about accepted properties.
+ If \a set is 0 and \a textToDisplayForNullSet string is not empty, this string is displayed
+ (without icon or any other additional part).
+ If \a set is 0 and \a textToDisplayForNullSet string is empty, the \a infoLabel widget becomes
+ hidden.
+*/
+void KexiPropertyEditorView::updateInfoLabelForPropertySet(KexiObjectInfoLabel *infoLabel,
+ KoProperty::Set* set, const QString& textToDisplayForNullSet)
+{
+ QString className, iconName, objectName;
+ if (set) {
+ if (set->contains("this:classString"))
+ className = (*set)["this:classString"].value().toString();
+ if (set->contains("this:iconName"))
+ iconName = (*set)["this:iconName"].value().toString();
+ const bool useCaptionAsObjectName = set->contains("this:useCaptionAsObjectName")
+ && (*set)["this:useCaptionAsObjectName"].value().toBool();
+ if (set->contains(useCaptionAsObjectName ? "caption" : "name"))
+ objectName = (*set)[useCaptionAsObjectName ? "caption" : "name"].value().toString();
+ }
+ if (!set || objectName.isEmpty()) {
+ objectName = textToDisplayForNullSet;
+ className = QString::null;
+ iconName = QString::null;
+ }
+
+ if (className.isEmpty() && objectName.isEmpty())
+ infoLabel->hide();
+ else
+ infoLabel->show();
+
+ if (infoLabel->objectClassName() == className
+ && infoLabel->objectClassIcon() == iconName
+ && infoLabel->objectName() == objectName)
+ return;
+
+ infoLabel->setObjectClassIcon(iconName);
+ infoLabel->setObjectClassName(className);
+ infoLabel->setObjectName(objectName);
+}
+
+void KexiPropertyEditorView::slotPropertySetChanged(KoProperty::Set* set)
+{
+ //update information about selected object
+ updateInfoLabelForPropertySet(d->objectInfoLabel, set);
+ d->editor->setEnabled(set);
+}
+
+#include "kexipropertyeditorview.moc"
diff --git a/kexi/widget/kexipropertyeditorview.h b/kexi/widget/kexipropertyeditorview.h
new file mode 100644
index 000000000..77dab6c80
--- /dev/null
+++ b/kexi/widget/kexipropertyeditorview.h
@@ -0,0 +1,117 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIPROPERTYEDITORVIEW_H
+#define KEXIPROPERTYEDITORVIEW_H
+
+//#include "kexiviewbase.h"
+#include <qwidget.h>
+
+class QLabel;
+class KexiMainWindow;
+
+namespace KoProperty {
+ class Editor;
+ class Set;
+}
+
+//! @short Helper class displaying small icon with class name and object name
+/*! The info label is displayed in a form:
+ <i>[ObjectClassIcon] ClassName "ObjectName"</i>
+
+ The <i>ObjectClassIcon</i> is optional. If "ClassName" is empty, the information
+ is displayed as:
+ <i>[ObjectClassIcon] ObjectName</i>
+
+ Example uses:
+ - [button_icon] Button "quit"
+ - [label_icon] Label "welcome"
+*/
+class KEXIEXTWIDGETS_EXPORT KexiObjectInfoLabel : public QWidget
+{
+ public:
+ KexiObjectInfoLabel(QWidget* parent, const char* name = 0);
+ ~KexiObjectInfoLabel();
+
+ void setObjectClassIcon(const QString& name);
+ QString objectClassIcon() const { return m_classIcon; }
+ void setObjectClassName(const QString& name);
+ QString objectClassName() const { return m_className; }
+ void setObjectName(const QString& name);
+ QString objectName() const { return m_objectName; }
+ void setBuddy( QWidget * buddy );
+ protected:
+ void updateName();
+
+ QString m_className;
+ QString m_classIcon, m_objectName;
+ QLabel *m_objectIconLabel, *m_objectNameLabel;
+};
+
+//! @short The container (acts as a dock window) for KexiPropertyEditor.
+/*! The widget displays KexiObjectInfoLabel on its top, to show user what
+ object the properties belong to. Read KexiObjectInfoLabel documentation for
+ the description what information is displayed.
+
+ There are properties obtained from KexiMainWindow's current property set
+ that help to customize displaying this information:
+ - "this:classString property" of type string describes object's class name
+ - "this:iconName" property of type string describes class name
+ - "name" or "caption" property of type string describes object's name
+ - "this:useCaptionAsObjectName" property of type boolean forces displaying "caption"
+ property instead of "name" - this can be usable when we know that "caption" properties
+ are available for a given type of objects (this is the case for Table Designer fields)
+*/
+class KEXIEXTWIDGETS_EXPORT KexiPropertyEditorView : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiPropertyEditorView(KexiMainWindow *mainWin, QWidget* parent);
+ virtual ~KexiPropertyEditorView();
+
+ /*! Helper function. Updates \a infoLabel widget by reusing properties provided
+ by property set \a set.
+ Read documentation of KexiPropertyEditorView class for information about accepted properties.
+ If \a set is 0 and \a textToDisplayForNullSet string is not empty, this string is displayed
+ (without icon or any other additional part).
+ If \a set is 0 and \a textToDisplayForNullSet string is empty, the \a infoLabel widget becomes
+ hidden. */
+ static void updateInfoLabelForPropertySet(
+ KexiObjectInfoLabel *infoLabel, KoProperty::Set* set,
+ const QString& textToDisplayForNullSet = QString::null);
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+ KoProperty::Editor *editor() const;
+
+// public slots:
+// virtual void setGeometry( const QRect &r );
+// virtual void resize( int w, int h );
+
+ protected slots:
+ void slotPropertySetChanged(KoProperty::Set* );
+
+ protected:
+ class Private;
+ Private *d;
+};
+
+#endif
diff --git a/kexi/widget/kexiquerydesignersqleditor.cpp b/kexi/widget/kexiquerydesignersqleditor.cpp
new file mode 100644
index 000000000..3ff579894
--- /dev/null
+++ b/kexi/widget/kexiquerydesignersqleditor.cpp
@@ -0,0 +1,35 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiquerydesignersqleditor.h"
+
+KexiQueryDesignerSQLEditor::KexiQueryDesignerSQLEditor(
+ KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : KexiEditor(mainWin, parent, name)
+{
+ setHighlightMode("sql");
+}
+
+KexiQueryDesignerSQLEditor::~KexiQueryDesignerSQLEditor()
+{
+}
+
+#include "kexiquerydesignersqleditor.moc"
diff --git a/kexi/widget/kexiquerydesignersqleditor.h b/kexi/widget/kexiquerydesignersqleditor.h
new file mode 100644
index 000000000..a34dbc7a9
--- /dev/null
+++ b/kexi/widget/kexiquerydesignersqleditor.h
@@ -0,0 +1,39 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIQUERYDESIGNERSQLEDITOR_H
+#define KEXIQUERYDESIGNERSQLEDITOR_H
+
+#include "kexieditor.h"
+
+//! Text editor for entering query statements.
+/*! The KexiQueryDesignerSQLEditor class embeds text editor
+ for entering query statements. */
+class KEXIEXTWIDGETS_EXPORT KexiQueryDesignerSQLEditor : public KexiEditor
+{
+ Q_OBJECT
+
+ public:
+ KexiQueryDesignerSQLEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0);
+ virtual ~KexiQueryDesignerSQLEditor();
+};
+
+#endif
diff --git a/kexi/widget/kexiqueryparameters.cpp b/kexi/widget/kexiqueryparameters.cpp
new file mode 100644
index 000000000..449c265c5
--- /dev/null
+++ b/kexi/widget/kexiqueryparameters.cpp
@@ -0,0 +1,139 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiqueryparameters.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kinputdialog.h>
+#include <knumvalidator.h>
+
+#include <kexidb/queryschemaparameter.h>
+#include <kexidb/utils.h>
+#include "utils/kexidatetimeformatter.h"
+
+//static
+QValueList<QVariant> KexiQueryParameters::getParameters(QWidget *parent,
+ const KexiDB::Driver &driver, KexiDB::QuerySchema& querySchema, bool &ok)
+{
+ Q_UNUSED(driver);
+ ok = false;
+ const KexiDB::QuerySchemaParameterList params( querySchema.parameters() );
+ QValueList<QVariant> values;
+ const QString caption( i18n("Enter Query Parameter Value", "Enter Parameter Value") );
+ foreach(KexiDB::QuerySchemaParameterListConstIterator, it, params) {
+ switch ((*it).type) {
+ case KexiDB::Field::Byte:
+ case KexiDB::Field::ShortInteger:
+ case KexiDB::Field::Integer:
+ case KexiDB::Field::BigInteger: {
+//! @todo problem for ranges in case of BigInteger - will disappear when we remove use of KInputDialog
+ int minValue, maxValue;
+//! @todo add support for unsigned parameter here
+ KexiDB::getLimitsForType((*it).type, minValue, maxValue);
+ const int result = KInputDialog::getInteger(
+ caption, (*it).message, 0, minValue, maxValue, 1/*step*/, 10/*base*/, &ok, parent);
+ if (!ok)
+ return QValueList<QVariant>(); //cancelled
+ values.append(result);
+ break;
+ }
+ case KexiDB::Field::Boolean: {
+ QStringList list;
+ list << i18n("Boolean True - Yes", "Yes") << i18n("Boolean False - No", "No");
+ const QString result = KInputDialog::getItem(
+ caption, (*it).message, list, 0/*current*/, false /*!editable*/, &ok, parent);
+ if (!ok || result.isEmpty())
+ return QValueList<QVariant>(); //cancelled
+ values.append( QVariant( result==list.first(), 1 ) );
+ break;
+ }
+ case KexiDB::Field::Date: {
+ KexiDateFormatter df;
+ const QString result = KInputDialog::getText(
+ caption, (*it).message, QString::null, &ok, parent, 0/*name*/,
+//! @todo add validator
+ 0/*validator*/, df.inputMask() );
+ if (!ok)
+ return QValueList<QVariant>(); //cancelled
+ values.append( df.stringToDate(result) );
+ break;
+ }
+ case KexiDB::Field::DateTime: {
+ KexiDateFormatter df;
+ KexiTimeFormatter tf;
+ const QString result = KInputDialog::getText(
+ caption, (*it).message, QString::null, &ok, parent, 0/*name*/,
+//! @todo add validator
+ 0/*validator*/, dateTimeInputMask(df, tf) );
+ if (!ok)
+ return QValueList<QVariant>(); //cancelled
+ values.append( stringToDateTime(df, tf, result) );
+ break;
+ }
+ case KexiDB::Field::Time: {
+ KexiTimeFormatter tf;
+ const QString result = KInputDialog::getText(
+ caption, (*it).message, QString::null, &ok, parent, 0/*name*/,
+//! @todo add validator
+ 0/*validator*/, tf.inputMask() );
+ if (!ok)
+ return QValueList<QVariant>(); //cancelled
+ values.append( tf.stringToTime(result) );
+ break;
+ }
+ case KexiDB::Field::Float:
+ case KexiDB::Field::Double: {
+ // KInputDialog::getDouble() does not work well, use getText and double validator
+ KDoubleValidator validator(0);
+ const QString textResult = KInputDialog::getText( caption, (*it).message, QString::null, &ok,
+ parent, 0, &validator);
+ if (!ok || textResult.isEmpty())
+ return QValueList<QVariant>(); //cancelled
+//! @todo this value will be still rounded: consider storing them as a decimal type
+//! (e.g. using a special Q_LLONG+decimalplace class)
+ const double result = textResult.toDouble(&ok); //this is also good for float (to avoid rounding)
+ if (!ok)
+ return QValueList<QVariant>();
+ values.append( result );
+ break;
+ }
+ case KexiDB::Field::Text:
+ case KexiDB::Field::LongText: {
+ const QString result = KInputDialog::getText(
+ caption, (*it).message, QString::null, &ok, parent);
+ if (!ok)
+ return QValueList<QVariant>(); //cancelled
+ values.append( result );
+ break;
+ }
+ case KexiDB::Field::BLOB: {
+//! @todo BLOB input unsupported
+ values.append( QByteArray() );
+ }
+ default:
+ kexiwarn << "KexiQueryParameters::getParameters() unsupported type " << KexiDB::Field::typeName((*it).type)
+ << " for parameter \"" << (*it).message << "\" - aborting query execution!" << endl;
+ return QValueList<QVariant>();
+ }
+ }
+ ok = true;
+ return values;
+}
+
diff --git a/kexi/widget/kexiqueryparameters.h b/kexi/widget/kexiqueryparameters.h
new file mode 100644
index 000000000..40251b715
--- /dev/null
+++ b/kexi/widget/kexiqueryparameters.h
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIQUERYPARAMETERS_H
+#define KEXIQUERYPARAMETERS_H
+
+#include <kexidb/queryschema.h>
+
+//! @short Utilities providing GUI for getting query parameters
+class KEXIEXTWIDGETS_EXPORT KexiQueryParameters
+{
+ public:
+ /*! Asks for query parameters using KInputDialog, one dialog per query parameter
+ (see @ref KexiDB::QuerySchema::parameters()). The type of each dialog depends
+ on the type of query parameter.
+ \return list of values obtained from the user
+ \a ok is set to true on success and to false on failure. */
+ //! @todo do not use KInputDialog - switch to more powerful custom dialog
+ //! @todo offer option to display one dialog (form) with all the parameters
+ //! @todo support more types (using validators)
+ //! @todo support defaults
+ //! @todo support validation rules, e.g. min/max value, unsigned
+ //! @todo support Enum type (list of strings, need support for keys and user-visible strings)
+ static QValueList<QVariant> getParameters(QWidget *parent, const KexiDB::Driver &driver,
+ KexiDB::QuerySchema& querySchema, bool &ok);
+};
+
+#endif // KEXIDBCONNECTIONWIDGET_H
diff --git a/kexi/widget/kexiscrollview.cpp b/kexi/widget/kexiscrollview.cpp
new file mode 100644
index 000000000..1cf473781
--- /dev/null
+++ b/kexi/widget/kexiscrollview.cpp
@@ -0,0 +1,407 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include "kexiscrollview.h"
+
+#include <qcursor.h>
+#include <qobjectlist.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+
+#include <kdebug.h>
+#include <kstaticdeleter.h>
+#include <klocale.h>
+
+#include <utils/kexirecordnavigator.h>
+#include <core/kexi.h>
+#include <kexiutils/utils.h>
+
+//! @internal
+class KexiScrollViewData
+{
+ public:
+ QPixmap horizontalOuterAreaPixmapBuffer;
+ QPixmap verticalOuterAreaPixmapBuffer;
+};
+
+// @todo warning: not reentrant!
+static KStaticDeleter<KexiScrollViewData> KexiScrollView_data_deleter;
+KexiScrollViewData* KexiScrollView_data = 0;
+
+KexiScrollView::KexiScrollView(QWidget *parent, bool preview)
+ : QScrollView(parent, "kexiscrollview", WStaticContents)
+ , m_widget(0)
+ , m_helpFont(font())
+ , m_preview(preview)
+ , m_scrollViewNavPanel(0)
+{
+ setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
+ viewport()->setPaletteBackgroundColor(colorGroup().mid());
+ QColor fc = palette().active().foreground(),
+ bc = viewport()->paletteBackgroundColor();
+ m_helpColor = KexiUtils::blendedColors(fc, bc, 1, 2);
+// m_helpColor = QColor((fc.red()+bc.red()*2)/3, (fc.green()+bc.green()*2)/3,
+// (fc.blue()+bc.blue()*2)/3);
+ m_helpFont.setPointSize( m_helpFont.pointSize() * 3 );
+
+ setFocusPolicy(WheelFocus);
+
+ //initial resize mode is always manual;
+ //will be changed on show(), if needed
+ setResizePolicy(Manual);
+
+ viewport()->setMouseTracking(true);
+ m_resizing = false;
+ m_enableResizing = true;
+ m_snapToGrid = false;
+ m_gridSize = 0;
+ m_outerAreaVisible = true;
+
+ connect(&m_delayedResize, SIGNAL(timeout()), this, SLOT(refreshContentsSize()));
+ m_smodeSet = false;
+ if (m_preview) {
+ refreshContentsSizeLater(true, true);
+//! @todo allow to hide navigator
+ updateScrollBars();
+ m_scrollViewNavPanel = new KexiRecordNavigator(this, leftMargin(), "nav");
+ m_scrollViewNavPanel->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Preferred);
+ }
+}
+
+KexiScrollView::~KexiScrollView()
+{
+}
+
+void
+KexiScrollView::setWidget(QWidget *w)
+{
+ addChild(w);
+ m_widget = w;
+}
+
+void
+KexiScrollView::setRecordNavigatorVisible(bool visible)
+{
+ if(/*m_scrollViewNavPanel->isVisible() &&*/ !visible)
+ m_scrollViewNavPanel->hide();
+ else if(visible) {
+ m_scrollViewNavPanel->show();
+ updateNavPanelGeometry();
+ }
+}
+
+void
+KexiScrollView::setSnapToGrid(bool enable, int gridSize)
+{
+ m_snapToGrid = enable;
+ if(enable) {
+ m_gridSize = gridSize;
+ }
+}
+
+void
+KexiScrollView::refreshContentsSizeLater(bool horizontal, bool vertical)
+{
+ Q_UNUSED( horizontal );
+ Q_UNUSED( vertical );
+
+ if (!m_smodeSet) {
+ m_smodeSet = true;
+ m_vsmode = vScrollBarMode();
+ m_hsmode = hScrollBarMode();
+ }
+// if (vertical)
+ setVScrollBarMode(QScrollView::AlwaysOff);
+ //if (horizontal)
+ setHScrollBarMode(QScrollView::AlwaysOff);
+ updateScrollBars();
+ m_delayedResize.start( 100, true );
+}
+
+void
+KexiScrollView::refreshContentsSize()
+{
+ if(!m_widget)
+ return;
+ if (m_preview) {
+ resizeContents(m_widget->width(), m_widget->height());
+// kdDebug() << "KexiScrollView::refreshContentsSize(): ( "
+ // << m_widget->width() <<", "<< m_widget->height() << endl;
+ setVScrollBarMode(m_vsmode);
+ setHScrollBarMode(m_hsmode);
+ m_smodeSet = false;
+ updateScrollBars();
+ }
+ else {
+ // Ensure there is always space to resize Form
+ int w = contentsWidth(), h = contentsHeight();
+ bool change = false;
+ const int delta_x = QMAX( (KexiScrollView_data ?
+ KexiScrollView_data->verticalOuterAreaPixmapBuffer.width() : 0), 300);
+ const int delta_y = QMAX( (KexiScrollView_data ?
+ KexiScrollView_data->horizontalOuterAreaPixmapBuffer.height() : 0), 300);
+ if((m_widget->width() + delta_x * 2 / 3) > w) {
+ w = m_widget->width() + delta_x;
+ change = true;
+ }
+ else if((w - m_widget->width()) > delta_x) {
+ w = m_widget->width() + delta_x;
+ change = true;
+ }
+ if((m_widget->height() + delta_y * 2 / 3) > h) {
+ h = m_widget->height() + delta_y;
+ change = true;
+ }
+ else if((h - m_widget->height()) > delta_y) {
+ h = m_widget->height() + delta_y;
+ change = true;
+ }
+ if (change) {
+ repaint();
+ viewport()->repaint();
+ repaintContents();
+ updateContents(0, 0, 2000,2000);
+ clipper()->repaint();
+
+ resizeContents(w, h);
+ }
+// kdDebug() << "KexiScrollView::refreshContentsSize(): ( "
+ // << contentsWidth() <<", "<< contentsHeight() << endl;
+ updateScrollBars();
+ setVScrollBarMode(Auto);
+ setHScrollBarMode(Auto);
+ }
+ updateContents();
+ updateScrollBars();
+}
+
+void
+KexiScrollView::updateNavPanelGeometry()
+{
+ if (m_scrollViewNavPanel)
+ m_scrollViewNavPanel->updateGeometry(leftMargin());
+}
+
+void
+KexiScrollView::contentsMousePressEvent(QMouseEvent *ev)
+{
+ if(!m_widget)
+ return;
+
+ QRect r3(0, 0, m_widget->width() + 4, m_widget->height() + 4);
+ if(!r3.contains(ev->pos())) // clicked outside form
+ //m_form->resetSelection();
+ emit outerAreaClicked();
+
+ if(!m_enableResizing)
+ return;
+
+ QRect r(m_widget->width(), 0, 4, m_widget->height() + 4); // right limit
+ QRect r2(0, m_widget->height(), m_widget->width() + 4, 4); // bottom limit
+ if(r.contains(ev->pos()) || r2.contains(ev->pos()))
+ {
+ m_resizing = true;
+ emit resizingStarted();
+ }
+}
+
+void
+KexiScrollView::contentsMouseReleaseEvent(QMouseEvent *)
+{
+ if(m_resizing) {
+ m_resizing = false;
+ emit resizingEnded();
+ }
+
+ unsetCursor();
+}
+
+void
+KexiScrollView::contentsMouseMoveEvent(QMouseEvent *ev)
+{
+ if(!m_widget || !m_enableResizing)
+ return;
+
+ if(m_resizing) // resize widget
+ {
+ int tmpx = ev->x(), tmpy = ev->y();
+ const int exceeds_x = (tmpx - contentsX() + 5) - clipper()->width();
+ const int exceeds_y = (tmpy - contentsY() + 5) - clipper()->height();
+ if (exceeds_x > 0)
+ tmpx -= exceeds_x;
+ if (exceeds_y > 0)
+ tmpy -= exceeds_y;
+ if ((tmpx - contentsX()) < 0)
+ tmpx = contentsX();
+ if ((tmpy - contentsY()) < 0)
+ tmpy = contentsY();
+
+ // we look for the max widget right() (or bottom()), which would be the limit for form resizing (not to hide widgets)
+ QObjectList *list = m_widget->queryList("QWidget", 0, true, false /* not recursive*/);
+ for(QObject *o = list->first(); o; o = list->next())
+ {
+ QWidget *w = (QWidget*)o;
+ tmpx = QMAX(tmpx, (w->geometry().right() + 10));
+ tmpy = QMAX(tmpy, (w->geometry().bottom() + 10));
+ }
+ delete list;
+
+ int neww = -1, newh;
+ if(cursor().shape() == QCursor::SizeHorCursor)
+ {
+ if(m_snapToGrid)
+ neww = int( float(tmpx) / float(m_gridSize) + 0.5 ) * m_gridSize;
+ else
+ neww = tmpx;
+ newh = m_widget->height();
+ }
+ else if(cursor().shape() == QCursor::SizeVerCursor)
+ {
+ neww = m_widget->width();
+ if(m_snapToGrid)
+ newh = int( float(tmpy) / float(m_gridSize) + 0.5 ) * m_gridSize;
+ else
+ newh = tmpy;
+ }
+ else if(cursor().shape() == QCursor::SizeFDiagCursor)
+ {
+ if(m_snapToGrid) {
+ neww = int( float(tmpx) / float(m_gridSize) + 0.5 ) * m_gridSize;
+ newh = int( float(tmpy) / float(m_gridSize) + 0.5 ) * m_gridSize;
+ } else {
+ neww = tmpx;
+ newh = tmpy;
+ }
+ }
+ //needs update?
+ if (neww!=-1 && m_widget->size() != QSize(neww, newh)) {
+ m_widget->resize( neww, newh );
+ refreshContentsSize();
+ updateContents();
+ }
+ }
+ else // update mouse cursor
+ {
+ QPoint p = ev->pos();
+ QRect r(m_widget->width(), 0, 4, m_widget->height()); // right
+ QRect r2(0, m_widget->height(), m_widget->width(), 4); // bottom
+ QRect r3(m_widget->width(), m_widget->height(), 4, 4); // bottom-right corner
+
+ if(r.contains(p))
+ setCursor(QCursor::SizeHorCursor);
+ else if(r2.contains(p))
+ setCursor(QCursor::SizeVerCursor);
+ else if(r3.contains(p))
+ setCursor(QCursor::SizeFDiagCursor);
+ else
+ unsetCursor();
+ }
+}
+
+void
+KexiScrollView::setupPixmapBuffer(QPixmap& pixmap, const QString& text, int lines)
+{
+ Q_UNUSED( lines );
+
+ QFontMetrics fm(m_helpFont);
+ const int flags = Qt::AlignCenter|Qt::AlignTop;
+ QRect rect(fm.boundingRect(0,0,1000,1000,flags,text));
+ const int txtw = rect.width(), txth = rect.height();//fm.width(text), txth = fm.height()*lines;
+ pixmap = QPixmap(txtw, txth);
+ if (!pixmap.isNull()) {
+ //create pixmap once
+ pixmap.fill( viewport()->paletteBackgroundColor() );
+ QPainter pb(&pixmap, this);
+ pb.setPen(m_helpColor);
+ pb.setFont(m_helpFont);
+ pb.drawText(0, 0, txtw, txth, Qt::AlignCenter|Qt::AlignTop, text);
+ }
+}
+
+void
+KexiScrollView::drawContents( QPainter * p, int clipx, int clipy, int clipw, int cliph )
+{
+ QScrollView::drawContents(p, clipx, clipy, clipw, cliph);
+ if (m_widget) {
+ if(m_preview || !m_outerAreaVisible)
+ return;
+
+ //draw right and bottom borders
+ const int wx = childX(m_widget);
+ const int wy = childY(m_widget);
+ p->setPen(palette().active().foreground());
+ p->drawLine(wx+m_widget->width(), wy, wx+m_widget->width(), wy+m_widget->height());
+ p->drawLine(wx, wy+m_widget->height(), wx+m_widget->width(), wy+m_widget->height());
+//kdDebug() << "KexiScrollView::drawContents() " << wy+m_widget->height() << endl;
+
+ if (!KexiScrollView_data) {
+ KexiScrollView_data_deleter.setObject( KexiScrollView_data, new KexiScrollViewData() );
+
+ //create flicker-less buffer
+ setupPixmapBuffer( KexiScrollView_data->horizontalOuterAreaPixmapBuffer, i18n("Outer Area"), 1 );
+ setupPixmapBuffer( KexiScrollView_data->verticalOuterAreaPixmapBuffer, i18n("Outer\nArea"), 2 );
+ }
+ if (!KexiScrollView_data->horizontalOuterAreaPixmapBuffer.isNull()
+ && !KexiScrollView_data->verticalOuterAreaPixmapBuffer.isNull()
+ && !m_delayedResize.isActive() /* only draw text if there's not pending delayed resize*/)
+ {
+ if (m_widget->height()>(KexiScrollView_data->verticalOuterAreaPixmapBuffer.height()+20)) {
+ p->drawPixmap(
+ QMAX( m_widget->width(), KexiScrollView_data->verticalOuterAreaPixmapBuffer.width() + 20 ) + 20,
+ QMAX( (m_widget->height() - KexiScrollView_data->verticalOuterAreaPixmapBuffer.height())/2, 20 ),
+ KexiScrollView_data->verticalOuterAreaPixmapBuffer
+ );
+ }
+ p->drawPixmap(
+ QMAX( (m_widget->width() - KexiScrollView_data->horizontalOuterAreaPixmapBuffer.width())/2, 20 ),
+ QMAX( m_widget->height(), KexiScrollView_data->horizontalOuterAreaPixmapBuffer.height() + 20 ) + 20,
+ KexiScrollView_data->horizontalOuterAreaPixmapBuffer
+ );
+ }
+ }
+}
+
+void
+KexiScrollView::leaveEvent( QEvent *e )
+{
+ QWidget::leaveEvent(e);
+ m_widget->update(); //update form elements on too fast mouse move
+}
+
+void
+KexiScrollView::setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h )
+{
+/*todo*/
+// kdDebug(44021)<<"KexiScrollView::setHBarGeometry"<<endl;
+ if (m_scrollViewNavPanel && m_scrollViewNavPanel->isVisible()) {
+ m_scrollViewNavPanel->setHBarGeometry( hbar, x, y, w, h );
+ }
+ else {
+ hbar.setGeometry( x, y, w, h );
+ }
+}
+
+KexiRecordNavigator*
+KexiScrollView::recordNavigator() const
+{
+ return m_scrollViewNavPanel;
+}
+
+#include "kexiscrollview.moc"
+
diff --git a/kexi/widget/kexiscrollview.h b/kexi/widget/kexiscrollview.h
new file mode 100644
index 000000000..c313f2d7a
--- /dev/null
+++ b/kexi/widget/kexiscrollview.h
@@ -0,0 +1,93 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISCROLLVIEW_H
+#define KEXISCROLLVIEW_H
+
+#include <qscrollview.h>
+#include <qtimer.h>
+
+class QColor;
+class QFont;
+class KexiRecordNavigator;
+
+//! The scrollview which includes KexiDBForm
+/*! It allows to resize its m_widget, following snapToGrid setting.
+ Its contents is resized so the widget can always be resized. */
+class KEXIEXTWIDGETS_EXPORT KexiScrollView : public QScrollView
+{
+ Q_OBJECT
+
+ public:
+ KexiScrollView(QWidget *parent, bool preview);
+ virtual ~KexiScrollView();
+
+ void setWidget(QWidget *w);
+ void setSnapToGrid(bool enable, int gridSize=10);
+
+ void setResizingEnabled(bool enabled) { m_enableResizing = enabled; }
+ void setRecordNavigatorVisible(bool visible);
+
+ void setOuterAreaIndicatorVisible(bool visible) { m_outerAreaVisible = visible; }
+
+ void refreshContentsSizeLater(bool horizontal, bool vertical);
+ void updateNavPanelGeometry();
+
+ KexiRecordNavigator* recordNavigator() const;
+
+ inline bool preview() const { return m_preview; }
+
+ public slots:
+ /*! Make sure there is a 300px margin around the form contents to allow resizing. */
+ virtual void refreshContentsSize();
+
+ signals:
+ void outerAreaClicked();
+ void resizingStarted();
+ void resizingEnded();
+
+ protected:
+ virtual void contentsMousePressEvent(QMouseEvent * ev);
+ virtual void contentsMouseReleaseEvent(QMouseEvent * ev);
+ virtual void contentsMouseMoveEvent(QMouseEvent * ev);
+ virtual void drawContents( QPainter * p, int clipx, int clipy, int clipw, int cliph );
+ virtual void leaveEvent( QEvent *e );
+ virtual void setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h );
+ void setupPixmapBuffer(QPixmap& pixmap, const QString& text, int lines);
+
+ bool m_resizing;
+ bool m_enableResizing;
+ QWidget *m_widget;
+
+ int m_gridSize;
+ QFont m_helpFont;
+ QColor m_helpColor;
+ QTimer m_delayedResize;
+ //! for refreshContentsSizeLater()
+ QScrollView::ScrollBarMode m_vsmode, m_hsmode;
+ bool m_snapToGrid : 1;
+ bool m_preview : 1;
+ bool m_smodeSet : 1;
+ bool m_outerAreaVisible : 1;
+ KexiRecordNavigator* m_scrollViewNavPanel;
+};
+
+#endif
+
diff --git a/kexi/widget/kexisectionheader.cpp b/kexi/widget/kexisectionheader.cpp
new file mode 100644
index 000000000..42c5031e9
--- /dev/null
+++ b/kexi/widget/kexisectionheader.cpp
@@ -0,0 +1,162 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexisectionheader.h"
+#include "kexiviewbase.h"
+#include <kexiutils/utils.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qtooltip.h>
+
+#include <kiconloader.h>
+#include <kpushbutton.h>
+
+class KexiSectionHeader::BoxLayout : public QBoxLayout
+{
+ public:
+ BoxLayout( KexiSectionHeader* parent, Direction d, int margin = 0,
+ int spacing = -1, const char * name = 0 );
+ virtual void addItem( QLayoutItem * item );
+ QGuardedPtr<KexiViewBase> view;
+};
+
+//==========================
+
+//! @internal
+class KexiSectionHeaderPrivate
+{
+ public:
+ KexiSectionHeaderPrivate()
+ {
+ }
+
+ Qt::Orientation orientation;
+ QLabel *lbl;
+ KexiSectionHeader::BoxLayout *lyr;
+ QHBox *lbl_b;
+};
+
+//==========================
+
+KexiSectionHeader::KexiSectionHeader(const QString &caption, Orientation o, QWidget* parent )
+ : QWidget(parent, "KexiSectionHeader")
+ , d( new KexiSectionHeaderPrivate() )
+{
+ d->orientation = o;
+ d->lyr = new BoxLayout( this, d->orientation==Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight );
+ d->lyr->setAutoAdd(true);
+ d->lbl_b = new QHBox(this);
+ d->lbl = new QLabel(QString(" ")+caption, d->lbl_b);
+ d->lbl->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
+ d->lbl->setFocusPolicy(StrongFocus);
+ d->lbl->installEventFilter(this);
+ installEventFilter(this);
+ setCaption(caption);
+}
+
+KexiSectionHeader::~KexiSectionHeader()
+{
+ delete d;
+}
+
+void KexiSectionHeader::addButton(const QString& icon, const QString& toolTip,
+ const QObject * receiver, const char * member)
+{
+ KPushButton *btn = new KPushButton(d->lbl_b);
+ btn->setFlat(true);
+ btn->setFocusPolicy(NoFocus);
+ btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ if (receiver && member) {
+ connect(btn, SIGNAL(clicked()), receiver, member);
+ }
+
+ if (!icon.isEmpty()) {
+ QIconSet iset = SmallIconSet(icon);
+ btn->setIconSet( iset );
+ QFontMetrics fm(d->lbl->font());
+ btn->setMaximumHeight( QMAX(fm.height(), 16) );
+ }
+ if (!toolTip.isEmpty()) {
+ QToolTip::add(btn, toolTip);
+ }
+}
+
+bool KexiSectionHeader::eventFilter( QObject *o, QEvent *e )
+{
+ if (o == d->lbl && e->type()==QEvent::MouseButtonRelease) {//|| e->type()==QEvent::FocusOut) {// && o->inherits("QWidget")) {
+ if (d->lyr->view)
+ d->lyr->view->setFocus();
+// if (KexiUtils::hasParent( this, static_cast<QWidget*>(o))) {
+// d->lbl->setPaletteBackgroundColor( e->type()==QEvent::FocusIn ? red : blue);
+// }
+ }
+ return QWidget::eventFilter(o,e);
+}
+
+void KexiSectionHeader::slotFocus(bool in)
+{
+ in = in || focusWidget()==this;
+ d->lbl->setPaletteBackgroundColor(
+ in ? palette().active().color(QColorGroup::Highlight) : palette().active().color(QColorGroup::Background) );
+ d->lbl->setPaletteForegroundColor(
+ in ? palette().active().color(QColorGroup::HighlightedText) : palette().active().color(QColorGroup::Foreground) );
+}
+
+QSize KexiSectionHeader::sizeHint() const
+{
+ if (!d->lyr->view)
+ return QWidget::sizeHint();
+ QSize s = d->lyr->view->sizeHint();
+ return QSize(s.width(), d->lbl->sizeHint().height() + s.height());
+}
+
+/*void KexiSectionHeader::setFocus()
+{
+ if (d->lyr->view)
+ d->lyr->view->setFocus();
+ else
+ QWidget::setFocus();
+}*/
+
+//======================
+
+KexiSectionHeader::BoxLayout::BoxLayout( KexiSectionHeader* parent, Direction d, int margin, int spacing, const char * name )
+ : QBoxLayout(parent, d, margin, spacing, name )
+{
+}
+
+void KexiSectionHeader::BoxLayout::addItem( QLayoutItem * item )
+{
+ QBoxLayout::addItem( item );
+ if (item->widget()) {
+ item->widget()->installEventFilter( mainWidget() );
+ if (item->widget()->inherits("KexiViewBase")) {
+ view = static_cast<KexiViewBase*>(item->widget());
+ KexiSectionHeader *sh = static_cast<KexiSectionHeader*>(mainWidget());
+ connect(view,SIGNAL(focus(bool)),sh,SLOT(slotFocus(bool)));
+ sh->d->lbl->setBuddy(item->widget());
+ }
+ }
+}
+
+
+#include "kexisectionheader.moc"
+
diff --git a/kexi/widget/kexisectionheader.h b/kexi/widget/kexisectionheader.h
new file mode 100644
index 000000000..b842f6ffa
--- /dev/null
+++ b/kexi/widget/kexisectionheader.h
@@ -0,0 +1,54 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISECTIONHEADER_H
+#define KEXISECTIONHEADER_H
+
+#include <qwidget.h>
+
+class KexiSectionHeaderPrivate;
+
+class KEXIEXTWIDGETS_EXPORT KexiSectionHeader : public QWidget
+{
+ Q_OBJECT
+ public:
+ class BoxLayout;
+
+ KexiSectionHeader(const QString &caption, Orientation o,
+ QWidget* parent = 0 );
+
+ virtual ~KexiSectionHeader();
+
+ void addButton(const QString& icon, const QString& toolTip,
+ const QObject * receiver, const char * member);
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ virtual QSize sizeHint() const;
+
+ public slots:
+ void slotFocus(bool in);
+
+ protected:
+ KexiSectionHeaderPrivate *d;
+ friend class BoxLayout;
+};
+
+#endif
+
diff --git a/kexi/widget/kexismalltoolbutton.cpp b/kexi/widget/kexismalltoolbutton.cpp
new file mode 100644
index 000000000..085d48470
--- /dev/null
+++ b/kexi/widget/kexismalltoolbutton.cpp
@@ -0,0 +1,133 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexismalltoolbutton.h"
+
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qstyle.h>
+
+#include <kiconloader.h>
+#include <kglobalsettings.h>
+
+#include <core/kexi.h>
+
+KexiSmallToolButton::KexiSmallToolButton(QWidget* parent, const QString& text,
+ const QString& icon, const char* name)
+ : QToolButton(parent, name)
+{
+ init();
+ update(text, SmallIconSet(icon));
+}
+
+KexiSmallToolButton::KexiSmallToolButton(QWidget* parent, const QString& text,
+ const QIconSet& iconSet, const char* name)
+ : QToolButton(parent, name)
+{
+ init();
+ update(text, iconSet);
+}
+
+KexiSmallToolButton::KexiSmallToolButton(QWidget* parent, KAction* action)
+ : QToolButton(parent, action->name())
+ , m_action(action)
+{
+ init();
+ connect(this, SIGNAL(clicked()), action, SLOT(activate()));
+ connect(action, SIGNAL(enabled(bool)), this, SLOT(setEnabled(bool)));
+ updateAction();
+}
+
+KexiSmallToolButton::~KexiSmallToolButton()
+{
+}
+
+void KexiSmallToolButton::updateAction()
+{
+ if (!m_action)
+ return;
+ update(m_action->text(), m_action->iconSet(KIcon::Small));
+ setAccel(m_action->shortcut());
+ QToolTip::add(this, m_action->toolTip());
+ QWhatsThis::add(this, m_action->whatsThis());
+}
+
+void KexiSmallToolButton::init()
+{
+ setPaletteBackgroundColor(palette().active().background());
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ QFont f(KGlobalSettings::toolBarFont());
+ f.setPixelSize(Kexi::smallFont().pixelSize());
+ setFont(f);
+ setAutoRaise(true);
+}
+
+void KexiSmallToolButton::update(const QString& text, const QIconSet& iconSet, bool tipToo)
+{
+ int width = 0;
+ if (text.isEmpty()) {
+ width = 10;
+ setUsesTextLabel(false);
+ }
+ else {
+ width += QFontMetrics(font()).width(text+" ");
+ setUsesTextLabel(true);
+ setTextPosition(QToolButton::Right);
+ QToolButton::setTextLabel(text, tipToo);
+ }
+ if (!iconSet.isNull()) {
+ width += IconSize(KIcon::Small);
+ QToolButton::setIconSet(iconSet);
+ }
+ setFixedWidth( width );
+}
+
+void KexiSmallToolButton::setIconSet( const QIconSet& iconSet )
+{
+ update(textLabel(), iconSet);
+}
+
+void KexiSmallToolButton::setIconSet( const QString& icon )
+{
+ setIconSet( SmallIconSet(icon) );
+}
+
+void KexiSmallToolButton::setTextLabel( const QString & newLabel, bool tipToo )
+{
+ Q_UNUSED( tipToo );
+
+ update(newLabel, iconSet());
+}
+
+void KexiSmallToolButton::drawButton( QPainter *_painter )
+{
+ QToolButton::drawButton(_painter);
+ if (QToolButton::popup()) {
+ QStyle::SFlags arrowFlags = QStyle::Style_Default;
+ if (isDown())
+ arrowFlags |= QStyle::Style_Down;
+ if (isEnabled())
+ arrowFlags |= QStyle::Style_Enabled;
+ style().drawPrimitive(QStyle::PE_ArrowDown, _painter,
+ QRect(width()-7, height()-7, 5, 5), colorGroup(),
+ arrowFlags, QStyleOption() );
+ }
+}
+
+#include "kexismalltoolbutton.moc"
diff --git a/kexi/widget/kexismalltoolbutton.h b/kexi/widget/kexismalltoolbutton.h
new file mode 100644
index 000000000..59af7cfa3
--- /dev/null
+++ b/kexi/widget/kexismalltoolbutton.h
@@ -0,0 +1,59 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISMALLTOOLBUTTON_H
+#define KEXISMALLTOOLBUTTON_H
+
+#include <qtoolbutton.h>
+#include <kaction.h>
+
+class QIconSet;
+
+//! A small tool button with icon and optional text
+class KEXIEXTWIDGETS_EXPORT KexiSmallToolButton : public QToolButton
+{
+ Q_OBJECT
+
+ public:
+ KexiSmallToolButton(QWidget* parent, const QString& text,
+ const QString& icon = QString::null, const char* name = 0);
+
+ KexiSmallToolButton(QWidget* parent, const QString& text,
+ const QIconSet& iconSet, const char* name = 0);
+
+ KexiSmallToolButton(QWidget* parent, KAction *action);
+
+ virtual ~KexiSmallToolButton();
+
+ void updateAction();
+
+ virtual void setIconSet( const QIconSet& iconSet );
+ virtual void setIconSet( const QString& icon );
+ virtual void setTextLabel( const QString & newLabel, bool tipToo );
+ virtual void setTextLabel( const QString & newLabel ) { setTextLabel(newLabel, false); }
+
+ protected:
+ void update(const QString& text, const QIconSet& iconSet, bool tipToo = false);
+ void init();
+ virtual void drawButton( QPainter *_painter );
+
+ QGuardedPtr<KAction> m_action;
+};
+
+#endif
diff --git a/kexi/widget/pixmapcollection.cpp b/kexi/widget/pixmapcollection.cpp
new file mode 100644
index 000000000..7f9718d6b
--- /dev/null
+++ b/kexi/widget/pixmapcollection.cpp
@@ -0,0 +1,440 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qpixmap.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qstringlist.h>
+#include <qtoolbutton.h>
+#include <qdom.h>
+
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kfiledialog.h>
+#include <kcombobox.h>
+#include <kicondialog.h>
+#include <klineedit.h>
+#include <kicontheme.h>
+#include <kpixmapio.h>
+#include <kpopupmenu.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include "pixmapcollection.h"
+
+/// Pixmap Collection
+PixmapCollection::PixmapCollection(const QString &collectionName, QObject *parent, const char *name)
+ : QObject(parent, name)
+{
+ m_name = collectionName;
+}
+
+QString
+PixmapCollection::addPixmapPath(const KURL &url)
+{
+ QString name = url.filename();
+ while(m_pixmaps.contains(name))
+ {
+ bool ok;
+ int num = name.right(1).toInt(&ok, 10);
+ if(ok)
+ name = name.left(name.length()-1) + QString::number(num+1);
+ else
+ name += "2";
+ }
+
+ m_pixmaps.insert(name, qMakePair(url.path(), 0));
+ return name;
+}
+
+QString
+PixmapCollection::addPixmapName(const QString &icon, int size)
+{
+ QString name = icon;
+ while(m_pixmaps.contains(name))
+ {
+ bool ok;
+ int num = name.right(1).toInt(&ok, 10);
+ if(ok)
+ name = name.left(name.length()-1) + QString::number(num+1);
+ else
+ name += "2";
+ }
+
+ m_pixmaps.insert(name, qMakePair(icon, size));
+ return name;
+}
+
+void
+PixmapCollection::removePixmap(const QString &name)
+{
+ m_pixmaps.remove(name);
+}
+
+QPixmap
+PixmapCollection::getPixmap(const QString &name)
+{
+ if(!m_pixmaps.contains(name))
+ {
+ kdDebug() << " The icon " << name << " you requested is not in the collection" << endl;
+ return QPixmap();
+ }
+
+ if(m_pixmaps[name].second != 0)
+ {
+ return kapp->iconLoader()->loadIcon(m_pixmaps[name].first, KIcon::NoGroup, m_pixmaps[name].second);
+ }
+ else
+ return QPixmap(m_pixmaps[name].first);
+}
+
+bool
+PixmapCollection::contains(const QString &name)
+{
+ return m_pixmaps.contains(name);
+}
+
+void
+PixmapCollection::save(QDomNode parentNode)
+{
+ if(m_pixmaps.isEmpty())
+ return;
+
+ QDomDocument domDoc = parentNode.ownerDocument();
+ QDomElement collection = domDoc.createElement("collection");
+ parentNode.appendChild(collection);
+
+ PixmapMap::ConstIterator it;
+ PixmapMap::ConstIterator endIt = m_pixmaps.constEnd();
+ for(it = m_pixmaps.constBegin(); it != endIt; ++it)
+ {
+ QDomElement item = domDoc.createElement("pixmap");
+ collection.appendChild(item);
+ item.setAttribute("name", it.key());
+ if(it.data().second != 0)
+ item.setAttribute("size", QString::number(it.data().second));
+
+ QString text = it.data().first;
+ QDomText textNode = domDoc.createTextNode(text);
+ item.appendChild(textNode);
+ }
+}
+
+void
+PixmapCollection::load(QDomNode node)
+{
+ QDomDocument domDoc = node.ownerDocument();
+ for(QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling())
+ {
+ QDomElement el = n.toElement();
+ QPair<QString, int> pair = qMakePair(el.text(), el.attribute("size").toInt());
+ m_pixmaps[el.attribute("name")] = pair;
+ }
+}
+
+//// A dialog to load a KDE icon by its name
+LoadIconDialog::LoadIconDialog(QWidget *parent)
+: KDialogBase(parent, "loadicon_dialog", true, i18n("Load KDE Icon by Name"), Ok|Cancel, Ok, false)
+{
+ QFrame *frame = makeMainWidget();
+ QGridLayout *l = new QGridLayout(frame, 2, 3, 0, 6);
+
+ // Name input
+ QLabel *name = new QLabel(i18n("&Name:"), frame);
+ l->addWidget(name, 0, 0);
+ name->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
+ m_nameInput = new KLineEdit("kexi", frame);
+ l->addWidget(m_nameInput, 0, 1);
+ name->setBuddy(m_nameInput);
+
+ // Choose size
+ QLabel *size = new QLabel(i18n("&Size:"), frame);
+ l->addWidget(size, 1, 0);
+ size->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
+
+ KComboBox *combo = new KComboBox(frame);
+ l->addWidget(combo, 1, 1);
+ size->setBuddy(combo);
+ QStringList list;
+ list << i18n("Small") << i18n("Medium") << i18n("Large") << i18n("Huge");
+ combo->insertStringList(list);
+ combo->setCurrentItem(2);
+ connect(combo, SIGNAL(activated(int)), this, SLOT(changeIconSize(int)));
+
+
+ // Icon chooser button
+ m_button = new KIconButton(frame);
+ m_button->setIcon("kexi");
+ m_button->setIconSize(KIcon::SizeMedium);
+ l->addMultiCellWidget(m_button, 0, 1, 2, 2);
+ connect(m_button, SIGNAL(iconChanged(QString)), this, SLOT(updateIconName(QString)));
+ connect(m_nameInput, SIGNAL(textChanged(const QString &)), this, SLOT(setIcon(const QString &)));
+}
+
+void
+LoadIconDialog::updateIconName(QString icon)
+{
+ m_nameInput->setText(icon);
+}
+
+void
+LoadIconDialog::setIcon(const QString &icon)
+{
+ m_button->setIcon(icon);
+}
+
+void
+LoadIconDialog::changeIconSize(int index)
+{
+ int size = KIcon::SizeMedium;
+ switch(index)
+ {
+ case 0: size = KIcon::SizeSmall; break;
+ //case 1: size = KIcon::SizeSmallMedium; break;
+ case 1: size = KIcon::SizeMedium; break;
+ case 2: size = KIcon::SizeLarge; break;
+#if !defined(Q_WS_WIN) && KDE_IS_VERSION(3,1,9)
+ case 3: size = KIcon::SizeHuge; break;
+#endif
+ default:;
+ }
+
+ m_button->setIconSize(size);
+}
+
+int LoadIconDialog::iconSize()
+{
+ return m_button->iconSize();
+}
+
+QString LoadIconDialog::iconName()
+{
+ return m_button->icon();
+}
+
+/// Pixmap Collection Editor Dialog
+PixmapCollectionEditor::PixmapCollectionEditor(PixmapCollection *collection, QWidget *parent)
+: KDialogBase(parent, "pixcollection_dialog", true,
+ i18n("Edit Pixmap Collection: %1").arg(collection->collectionName()), Close, Close, false)
+{
+ m_collection = collection;
+ QFrame *frame = makeMainWidget();
+ QHBoxLayout *l = new QHBoxLayout(frame, 0, 6);
+ setInitialSize(QSize(400, 200), true);
+
+ //// Setup the icon toolbar /////////////////
+ QVBoxLayout *vlayout = new QVBoxLayout(l, 3);
+ QToolButton *newItemPath = new QToolButton(frame);
+ newItemPath->setIconSet(BarIconSet("fileopen"));
+ newItemPath->setTextLabel(i18n("&Add File"), true);
+ vlayout->addWidget(newItemPath);
+ m_buttons.insert(BNewItemPath, newItemPath);
+ connect(newItemPath, SIGNAL(clicked()), this, SLOT(newItemByPath()));
+
+ QToolButton *newItemName = new QToolButton(frame);
+ newItemName->setIconSet(BarIconSet("icons"));
+ newItemName->setTextLabel(i18n("&Add an Icon"), true);
+ vlayout->addWidget(newItemName);
+ m_buttons.insert(BNewItemName, newItemName);
+ connect(newItemName, SIGNAL(clicked()), this, SLOT(newItemByName()));
+
+ QToolButton *delItem = new QToolButton(frame);
+ delItem->setIconSet(BarIconSet("edit_remove"));
+ delItem->setTextLabel(i18n("&Remove Selected Item"), true);
+ vlayout->addWidget(delItem);
+ m_buttons.insert(BDelItem, delItem);
+ connect(delItem, SIGNAL(clicked()), this, SLOT(removeItem()));
+ vlayout->addStretch();
+
+ // Setup the iconView
+ m_iconView = new KIconView(frame, "pixcollection_iconView");
+ m_iconView->resize(100,100);
+ m_iconView->setArrangement(QIconView::LeftToRight);
+ m_iconView->setAutoArrange(true);
+ m_iconView->setMode(KIconView::Select);
+ l->addWidget(m_iconView);
+ connect(m_iconView, SIGNAL(contextMenuRequested(QIconViewItem*, const QPoint&)), this, SLOT(displayMenu(QIconViewItem*, const QPoint&)));
+ connect(m_iconView, SIGNAL(itemRenamed(QIconViewItem*, const QString &)), this, SLOT(renameCollectionItem(QIconViewItem*, const QString&)));
+
+ PixmapMap::ConstIterator it;
+ PixmapMap::ConstIterator endIt = collection->m_pixmaps.end();
+ for(it = collection->m_pixmaps.constBegin(); it != endIt; ++it)
+ createIconViewItem(it.key());
+}
+
+void
+PixmapCollectionEditor::newItemByName()
+{
+ LoadIconDialog d(parentWidget());
+ if(d.exec()== QDialog::Accepted)
+ {
+ if(d.iconName().isEmpty())
+ return;
+
+ QString name = m_collection->addPixmapName(d.iconName(), d.iconSize());
+ createIconViewItem(name);
+ }
+}
+
+void
+PixmapCollectionEditor::newItemByPath()
+{
+ KURL url = KFileDialog::getImageOpenURL("::kexi", parentWidget());
+ if(url.isEmpty())
+ return;
+ QString name = m_collection->addPixmapPath(url);
+ createIconViewItem(name);
+}
+
+void
+PixmapCollectionEditor::removeItem()
+{
+ QIconViewItem *item = m_iconView->currentItem();
+ if( !item )
+ return;
+
+ int confirm = KMessageBox::questionYesNo(parentWidget(), QString("<qt>")+
+ i18n("Do you want to remove item \"%1\" from collection \"%2\"?")
+ .arg(item->text()).arg(m_collection->collectionName()) + "</qt>");
+ if(confirm == KMessageBox::No)
+ return;
+
+ m_collection->removePixmap(item->text());
+ delete item;
+}
+
+void
+PixmapCollectionEditor::renameItem()
+{
+ if(m_iconView->currentItem())
+ m_iconView->currentItem()->rename();
+}
+
+void
+PixmapCollectionEditor::createIconViewItem(const QString &name)
+{
+ PixmapIconViewItem *item = new PixmapIconViewItem(m_iconView, name, getPixmap(name));
+ item->setRenameEnabled(true);
+}
+
+QPixmap
+PixmapCollectionEditor::getPixmap(const QString &name)
+{
+ QPixmap pixmap = m_collection->getPixmap(name);
+ if((pixmap.width() <= 48) && (pixmap.height() <= 48))
+ return pixmap;
+
+ KPixmapIO io;
+ QImage image = io.convertToImage(pixmap);
+ pixmap = io.convertToPixmap(image.scale(48, 48, QImage::ScaleMin));
+ return pixmap;
+}
+
+void
+PixmapCollectionEditor::renameCollectionItem(QIconViewItem *it, const QString &name)
+{
+ PixmapIconViewItem *item = static_cast<PixmapIconViewItem*>(it);
+ if(!m_collection->m_pixmaps.contains(item->name()))
+ return;
+
+ // We just rename the collection item
+ QPair<QString, int> pair = m_collection->m_pixmaps[item->name()];
+ m_collection->m_pixmaps.remove(item->name());
+ m_collection->m_pixmaps[name] = pair;
+ item->setName(name);
+}
+
+void
+PixmapCollectionEditor::displayMenu(QIconViewItem *it, const QPoint &p)
+{
+ if(!it) return;
+ KPopupMenu *menu = new KPopupMenu();
+ menu->insertItem(SmallIconSet("edit"), i18n("Rename Item"), this, SLOT(renameItem()));
+ menu->insertItem(SmallIconSet("remove"), i18n("Remove Item"), this, SLOT(removeItem()));
+ menu->exec(p);
+}
+
+//// A Dialog to choose a pixmap from the PixmapCollection
+PixmapCollectionChooser::PixmapCollectionChooser(PixmapCollection *collection, const QString &selectedItem, QWidget *parent)
+: KDialogBase(parent, "pixchoose_dialog", true, i18n("Select Pixmap From %1").arg(collection->collectionName()),
+ User1|Ok|Cancel, Ok, false, KGuiItem(i18n("Edit Collection...")))
+{
+ m_collection = collection;
+ setInitialSize(QSize(400, 200), true);
+
+ m_iconView = new KIconView(this, "pixchooser_iconView");
+ setMainWidget(m_iconView);
+ m_iconView->setArrangement(QIconView::LeftToRight);
+ m_iconView->setAutoArrange(true);
+ m_iconView->setMode(KIconView::Select);
+
+ PixmapMap::ConstIterator it;
+ PixmapMap::ConstIterator endIt = collection->m_pixmaps.constEnd();
+ for(it = collection->m_pixmaps.constBegin(); it != endIt; ++it)
+ new PixmapIconViewItem(m_iconView, it.key(), getPixmap(it.key()));
+
+ QIconViewItem *item = m_iconView->findItem(selectedItem, Qt::ExactMatch);
+ if(item && !selectedItem.isEmpty())
+ m_iconView->setCurrentItem(item);
+}
+
+QPixmap
+PixmapCollectionChooser::pixmap()
+{
+ if(! m_iconView->currentItem())
+ return QPixmap();
+ QString name = m_iconView->currentItem()->text();
+ return m_collection->getPixmap(name);
+}
+
+QString
+PixmapCollectionChooser::pixmapName()
+{
+ return m_iconView->currentItem() ? m_iconView->currentItem()->text() : QString("");
+}
+
+QPixmap
+PixmapCollectionChooser::getPixmap(const QString &name)
+{
+ QPixmap pixmap = m_collection->getPixmap(name);
+ if((pixmap.width() <= 48) && (pixmap.height() <= 48))
+ return pixmap;
+
+ // We scale the pixmap down to 48x48 to fit in the iconView
+ KPixmapIO io;
+ QImage image = io.convertToImage(pixmap);
+ pixmap = io.convertToPixmap(image.scale(48, 48, QImage::ScaleMin));
+ return pixmap;
+}
+
+void
+PixmapCollectionChooser::slotUser1()
+{
+ PixmapCollectionEditor dialog(m_collection, parentWidget());
+ dialog.exec();
+
+ m_iconView->clear();
+ PixmapMap::ConstIterator it;
+ PixmapMap::ConstIterator endIt = m_collection->m_pixmaps.constEnd();
+ for(it = m_collection->m_pixmaps.constBegin(); it != endIt; ++it)
+ new PixmapIconViewItem(m_iconView, it.key(), getPixmap(it.key()));
+}
+
+#include "pixmapcollection.moc"
diff --git a/kexi/widget/pixmapcollection.h b/kexi/widget/pixmapcollection.h
new file mode 100644
index 000000000..a46a20438
--- /dev/null
+++ b/kexi/widget/pixmapcollection.h
@@ -0,0 +1,162 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFORMDESIGNERPIXMAPCOLLECTION_H
+#define KFORMDESIGNERPIXMAPCOLLECTION_H
+
+#include <qobject.h>
+#include <qmap.h>
+#include <qpair.h>
+#include <qintdict.h>
+#include <qtoolbutton.h>
+
+#include <kicontheme.h>
+#include <kdialogbase.h>
+#include <kiconview.h>
+#include <kurl.h>
+
+class QPixmap;
+class KIconView;
+class KIconButton;
+class KLineEdit;
+class QDomNode;
+
+typedef QMap<QString, QPair<QString, int> > PixmapMap;
+
+//! A class that store pixmaps (by path or by name for KDE icons)
+class KEXIEXTWIDGETS_EXPORT PixmapCollection : public QObject
+{
+ Q_OBJECT
+
+ public:
+ PixmapCollection(const QString &collectionName, QObject *parent = 0, const char *name = 0);
+ ~PixmapCollection() {;}
+
+ QString addPixmapPath(const KURL &url);
+ QString addPixmapName(const QString &name, int size = KIcon::SizeMedium);
+ void removePixmap(const QString &name);
+
+ bool contains(const QString &name);
+ QPixmap getPixmap(const QString &name);
+
+ void save(QDomNode parentNode);
+ void load(QDomNode node);
+
+ QString collectionName() {return m_name; }
+
+ signals:
+ void itemRenamed(const QString &oldName, const QString &newName);
+ void itemRemoved(const QString &name);
+
+ protected:
+ QString m_name;
+ PixmapMap m_pixmaps;
+
+ friend class PixmapCollectionEditor;
+ friend class PixmapCollectionChooser;
+};
+
+//! A dialog to edit the contents of a PixmapCollection
+class KEXIEXTWIDGETS_EXPORT PixmapCollectionEditor : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ PixmapCollectionEditor(PixmapCollection *collection, QWidget *parent = 0);
+ ~PixmapCollectionEditor() {;}
+
+ protected:
+ QPixmap getPixmap(const QString &name);
+ void createIconViewItem(const QString &name);
+
+ protected slots:
+ void newItemByPath();
+ void newItemByName();
+ void removeItem();
+ void renameItem();
+ void renameCollectionItem(QIconViewItem *item, const QString &name);
+ void displayMenu(QIconViewItem *item, const QPoint &p);
+
+ private:
+ enum { BNewItemPath = 101, BNewItemName, BDelItem};
+ KIconView *m_iconView;
+ QIntDict<QToolButton> m_buttons;
+ PixmapCollection *m_collection;
+};
+
+//! A dialog to choose an icon in a PixmapCollection
+class KEXIEXTWIDGETS_EXPORT PixmapCollectionChooser : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ PixmapCollectionChooser(PixmapCollection *collection, const QString &selectedItem, QWidget *parent = 0);
+ ~PixmapCollectionChooser() {;}
+
+ QPixmap pixmap();
+ QString pixmapName();
+
+ protected:
+ QPixmap getPixmap(const QString &name);
+ protected slots:
+ virtual void slotUser1();
+
+ private:
+ PixmapCollection *m_collection;
+ KIconView *m_iconView;
+};
+
+//! A simple dialog to choose a KDE icon
+class KEXIEXTWIDGETS_EXPORT LoadIconDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ LoadIconDialog(QWidget *parent = 0);
+ ~LoadIconDialog() {;}
+
+ int iconSize();
+ QString iconName();
+
+ protected slots:
+ void changeIconSize(int);
+ void updateIconName(QString);
+ void setIcon(const QString &);
+
+ private:
+ KLineEdit *m_nameInput;
+ KIconButton *m_button;
+};
+
+//! A Special KIconViewItem that holds the name of its associated pixmap (to allow renaming)
+class KEXIEXTWIDGETS_EXPORT PixmapIconViewItem : public KIconViewItem
+{
+ public:
+ PixmapIconViewItem(KIconView *parent, const QString &text, const QPixmap &icon)
+ : KIconViewItem(parent, text, icon) { m_name = text; }
+ ~PixmapIconViewItem() {;}
+
+ void setName(const QString &name) { m_name = name; }
+ QString name() { return m_name;}
+
+ private:
+ QString m_name;
+};
+
+#endif
diff --git a/kexi/widget/relations/Makefile.am b/kexi/widget/relations/Makefile.am
new file mode 100644
index 000000000..f09e939a9
--- /dev/null
+++ b/kexi/widget/relations/Makefile.am
@@ -0,0 +1,38 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexirelationsview.la
+
+libkexirelationsview_la_SOURCES = kexirelationview.cpp kexirelationviewconnection.cpp \
+ kexirelationviewtable.cpp kexirelationwidget.cpp
+
+libkexirelationsview_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved
+libkexirelationsview_la_LIBADD = ../../core/libkexicore.la
+
+libkexirelationsview_la_METASOURCES = AUTO
+
+SUBDIRS = .
+
+# kde_appsdir Where your application's menu entry (.desktop) should go to.
+# kde_icondir Where your icon should go to - better use KDE_ICON.
+# kde_sounddir Where your sounds should go to.
+# kde_htmldir Where your docs should go to. (contains lang subdirs)
+# kde_datadir Where you install application data. (Use a subdir)
+# kde_locale Where translation files should go to. (contains lang subdirs)
+# kde_cgidir Where cgi-bin executables should go to.
+# kde_confdir Where config files should go to (system-wide ones with default values).
+# kde_mimedir Where mimetypes .desktop files should go to.
+# kde_servicesdir Where services .desktop files should go to.
+# kde_servicetypesdir Where servicetypes .desktop files should go to.
+# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON).
+# kde_wallpaperdir Where general wallpapers should go to.
+# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to.
+# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS.
+# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES.
+# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES.
+# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3).
+# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3).
+
+# set the include path for X, qt and KDE
+INCLUDES= -I$(top_srcdir)/kexi $(LIB_KEXI_KMDI_INCLUDES) \
+ -I$(top_srcdir)/kexi/core $(all_includes)
+
diff --git a/kexi/widget/relations/kexirelationview.cpp b/kexi/widget/relations/kexirelationview.cpp
new file mode 100644
index 000000000..9d68a7558
--- /dev/null
+++ b/kexi/widget/relations/kexirelationview.cpp
@@ -0,0 +1,639 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kdebug.h>
+
+#include <qstringlist.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qheader.h>
+#include <qevent.h>
+#include <qpainter.h>
+#include <qstyle.h>
+#include <qlineedit.h>
+#include <qpopupmenu.h>
+
+#include <klocale.h>
+#include <kaction.h>
+#include <kpopupmenu.h>
+#include <kglobalsettings.h>
+#include <kmessagebox.h>
+
+#include <kexidb/tableschema.h>
+#include <kexidb/indexschema.h>
+#include <kexidb/utils.h>
+
+#include "kexirelationview.h"
+#include "kexirelationviewtable.h"
+#include "kexirelationviewconnection.h"
+#include <kexi.h>
+
+KexiRelationView::KexiRelationView(QWidget *parent, const char *name)
+ : QScrollView(parent, name, WStaticContents)
+{
+// m_relation=relation;
+// m_relation->incUsageCount();
+ m_selectedConnection = 0;
+ m_readOnly=false;
+ m_focusedTableView = 0;
+ setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
+
+// connect(relation, SIGNAL(relationListUpdated(QObject *)), this, SLOT(slotListUpdate(QObject *)));
+
+ viewport()->setPaletteBackgroundColor(colorGroup().mid());
+ setFocusPolicy(WheelFocus);
+ setResizePolicy(Manual);
+/*MOVED TO KexiRelationDialog
+ //actions
+ m_tableQueryPopup = new KPopupMenu(this, "m_popup");
+ m_tableQueryPopup->insertTitle(i18n("Table"));
+ m_connectionPopup = new KPopupMenu(this, "m_connectionPopup");
+ m_connectionPopup->insertTitle(i18n("Relation"));
+ m_areaPopup = new KPopupMenu(this, "m_areaPopup");
+
+ plugSharedAction("edit_delete", i18n("Hide Table"), m_tableQueryPopup);
+ plugSharedAction("edit_delete",m_connectionPopup);
+ plugSharedAction("edit_delete",this, SLOT(removeSelectedObject()));
+*/
+#if 0
+ m_removeSelectedTableQueryAction = new KAction(i18n("&Hide Selected Table/Query"), "editdelete", "",
+ this, SLOT(removeSelectedTableQuery()), parent->actionCollection(), "relationsview_removeSelectedTableQuery");
+ m_removeSelectedConnectionAction = new KAction(i18n("&Remove Selected Relationship"), "button_cancel", "",
+ this, SLOT(removeSelectedConnection()), parent->actionCollection(), "relationsview_removeSelectedConnection");
+ m_openSelectedTableQueryAction = new KAction(i18n("&Open Selected Table/Query"), "", "",
+ this, SLOT(openSelectedTableQuery()), 0/*parent->actionCollection()*/, "relationsview_openSelectedTableQuery");
+#endif
+
+// invalidateActions();
+
+#if 0
+
+
+ m_popup = new KPopupMenu(this, "m_popup");
+ m_openSelectedTableQueryAction->plug( m_popup );
+ m_removeSelectedTableQueryAction->plug( m_popup );
+ m_removeSelectedConnectionAction->plug( m_popup );
+
+ invalidateActions();
+#endif
+
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, true);
+}
+
+KexiRelationView::~KexiRelationView()
+{
+}
+
+/*KexiRelationViewTableContainer*
+KexiRelationView::containerForTable(KexiDB::TableSchema* tableSchema)
+{
+ if (!tableSchema)
+ return 0;
+ for (TablesDictIterator it(m_tables); it.current(); ++it) {
+ if (it.current()->schema()->table()==tableSchema)
+ return it.current();
+ }
+ return 0;
+}*/
+
+KexiRelationViewTableContainer *
+KexiRelationView::tableContainer(KexiDB::TableSchema *t) const
+{
+ return t ? m_tables.find(t->name()) : 0;
+}
+
+KexiRelationViewTableContainer*
+KexiRelationView::addTableContainer(KexiDB::TableSchema *t, const QRect &rect)
+{
+ if(!t)
+ return 0;
+
+ kdDebug() << "KexiRelationView::addTable(): " << t->name() << ", " << viewport() << endl;
+
+ KexiRelationViewTableContainer* c = tableContainer(t);
+ if (c) {
+ kdWarning() << "KexiRelationView::addTable(): table already added" << endl;
+ return c;
+ }
+
+ c = new KexiRelationViewTableContainer(this,
+/*! @todo what about query? */
+ new KexiDB::TableOrQuerySchema(t)
+ );
+ connect(c, SIGNAL(endDrag()), this, SLOT(slotTableViewEndDrag()));
+ connect(c, SIGNAL(gotFocus()), this, SLOT(slotTableViewGotFocus()));
+// connect(c, SIGNAL(headerContextMenuRequest(const QPoint&)),
+// this, SLOT(tableHeaderContextMenuRequest(const QPoint&)));
+ connect(c, SIGNAL(contextMenuRequest(const QPoint&)),
+ this, SIGNAL(tableContextMenuRequest(const QPoint&)));
+
+ addChild(c, 100,100);
+ if (rect.isValid()) {//predefined size
+ QSize finalSize = c->size().expandedTo( c->sizeHint() );
+ QRect r = rect;
+ r.setSize( finalSize + QSize(0,10) );
+ moveChild( c, rect.left(), rect.top() );
+ //we're doing this instead of setGeometry(rect)
+ //because the geomenty might be saved on other system with bigger fonts :)
+ c->resize(c->sizeHint());
+// c->setGeometry(r);
+//TODO
+
+// moveChild( c, rect.left(), rect.top() ); // setGeometry(rect);
+// c->resize( finalSize );
+// c->updateGeometry();
+ }
+ c->show();
+ updateGeometry();
+ if (!rect.isValid()) {
+ c->updateGeometry();
+ c->resize(c->sizeHint());
+ }
+ int x, y;
+
+ if(m_tables.count() > 0)
+ {
+ int place = -10;
+ QDictIterator<KexiRelationViewTableContainer> it(m_tables);
+ for(; it.current(); ++it)
+ {
+ int right = (*it)->x() + (*it)->width();
+ if(right > place)
+ place = right;
+ }
+
+ x = place + 30;
+ }
+ else
+ {
+ x = 5;
+ }
+
+ y = 5;
+ QPoint p = viewportToContents(QPoint(x, y));
+ recalculateSize(p.x() + c->width(), p.y() + c->height());
+ if (!rect.isValid()) {
+ moveChild(c, x, y);
+ }
+
+ m_tables.insert(t->name(), c);
+
+ connect(c, SIGNAL(moved(KexiRelationViewTableContainer *)), this,
+ SLOT(containerMoved(KexiRelationViewTableContainer *)));
+
+ if (hasFocus()) //ok?
+ c->setFocus();
+
+ return c;
+}
+
+void
+KexiRelationView::addConnection(const SourceConnection& _conn)
+{
+ SourceConnection conn = _conn;
+ kdDebug() << "KexiRelationView::addConnection()" << endl;
+
+ KexiRelationViewTableContainer *master = m_tables[conn.masterTable];
+ KexiRelationViewTableContainer *details = m_tables[conn.detailsTable];
+ if (!master || !details)
+ return;
+
+/*! @todo what about query? */
+ KexiDB::TableSchema *masterTable = master->schema()->table();
+/*! @todo what about query? */
+ KexiDB::TableSchema *detailsTable = details->schema()->table();
+ if (!masterTable || !detailsTable)
+ return;
+
+ // ok, but we need to know where is the 'master' and where is the 'details' side:
+ KexiDB::Field *masterFld = masterTable->field(conn.masterField);
+ KexiDB::Field *detailsFld = detailsTable->field(conn.detailsField);
+ if (!masterFld || !detailsFld)
+ return;
+
+ if (!masterFld->isUniqueKey()) {
+ if (detailsFld->isUniqueKey()) {
+ //SWAP:
+ KexiDB::Field *tmpFld = masterFld;
+ masterFld = detailsFld;
+ detailsFld = tmpFld;
+ KexiDB::TableSchema *tmpTable = masterTable;
+ masterTable = detailsTable;
+ detailsTable = tmpTable;
+ KexiRelationViewTableContainer *tmp = master;
+ master = details;
+ details = tmp;
+ QString tmp_masterTable = conn.masterTable;
+ conn.masterTable = conn.detailsTable;
+ conn.detailsTable = tmp_masterTable;
+ QString tmp_masterField = conn.masterField;
+ conn.masterField = conn.detailsField;
+ conn.detailsField = tmp_masterField;
+ }
+ }
+
+// kdDebug() << "KexiRelationView::addConnection(): finalSRC = " << m_tables[conn.srcTable] << endl;
+
+ KexiRelationViewConnection *connView = new KexiRelationViewConnection(master, details, conn, this);
+ m_connectionViews.append(connView);
+ updateContents(connView->connectionRect());
+
+/*js: will be moved up to relation/query part as this is only visual class
+ KexiDB::TableSchema *mtable = m_conn->tableSchema(conn.srcTable);
+ KexiDB::TableSchema *ftable = m_conn->tableSchema(conn.rcvTable);
+ KexiDB::IndexSchema *forign = new KexiDB::IndexSchema(ftable);
+
+ forign->addField(mtable->field(conn.srcField));
+ new KexiDB::Reference(forign, mtable->primaryKey());
+*/
+#if 0
+ if(!interactive)
+ {
+ kdDebug() << "KexiRelationView::addConnection: adding self" << endl;
+ RelationList l = m_relation->projectRelations();
+ l.append(conn);
+ m_relation->updateRelationList(this, l);
+ }
+#endif
+}
+
+void
+KexiRelationView::drawContents(QPainter *p, int cx, int cy, int cw, int ch)
+{
+ KexiRelationViewConnection *cview;
+// p->translate(0, (double)contentsY());
+
+ QRect clipping(cx, cy, cw, ch);
+ for(cview = m_connectionViews.first(); cview; cview = m_connectionViews.next())
+ {
+ if(clipping.intersects(cview->oldRect() | cview->connectionRect()))
+ cview->drawConnection(p);
+ }
+}
+
+void
+KexiRelationView::slotTableScrolling(const QString& table)
+{
+ KexiRelationViewTableContainer *c = m_tables[table];
+
+ if(c)
+ containerMoved(c);
+}
+
+void
+KexiRelationView::containerMoved(KexiRelationViewTableContainer *c)
+{
+ KexiRelationViewConnection *cview;
+ QRect r;
+ for (ConnectionListIterator it(m_connectionViews); ((cview=it.current())); ++it) {
+//! @todo optimize
+ if(cview->masterTable() == c || cview->detailsTable() == c
+ || cview->connectionRect().intersects(r))
+ {
+ r |= cview->oldRect();
+ kdDebug() << r << endl;
+ r |= cview->connectionRect();
+ kdDebug() << r << endl;
+ }
+// updateContents(cview->oldRect());
+// updateContents(cview->connectionRect());
+// }
+ }
+//! @todo optimize!
+//didn't work well: updateContents(r);
+ updateContents();
+
+// QRect w(c->x() - 5, c->y() - 5, c->width() + 5, c->height() + 5);
+// updateContents(w);
+
+ QPoint p = viewportToContents(QPoint(c->x(), c->y()));
+ recalculateSize(p.x() + c->width(), p.y() + c->height());
+
+ emit tablePositionChanged(c);
+}
+
+void
+KexiRelationView::setReadOnly(bool b)
+{
+ m_readOnly=b;
+//TODO
+// invalidateActions();
+/* TableList::Iterator it, end( m_tables.end() );
+ for ( it=m_tables.begin(); it != end; ++it)
+ {
+// (*it)->setReadOnly(b);
+#ifndef Q_WS_WIN
+ #warning readonly needed
+#endif
+ }*/
+}
+
+void
+KexiRelationView::slotListUpdate(QObject *)
+{
+#if 0
+ if(s != this)
+ {
+ m_connectionViews.clear();
+ RelationList rl = m_relation->projectRelations();
+ if(!rl.isEmpty())
+ {
+ RelationList::ConstIterator it, end( rl.constEnd() );
+ for( it = rl.begin(); it != end; ++it)
+ {
+ addConnection((*it), true);
+ }
+ }
+ }
+
+ updateContents();
+#endif
+}
+
+void
+KexiRelationView::contentsMousePressEvent(QMouseEvent *ev)
+{
+ KexiRelationViewConnection *cview;
+ for(cview = m_connectionViews.first(); cview; cview = m_connectionViews.next())
+ {
+ if(!cview->matchesPoint(ev->pos(), 3))
+ continue;
+ clearSelection();
+ setFocus();
+ cview->setSelected(true);
+ updateContents(cview->connectionRect());
+ m_selectedConnection = cview;
+ emit connectionViewGotFocus();
+// invalidateActions();
+
+ if(ev->button() == RightButton) {//show popup
+ kdDebug() << "KexiRelationView::contentsMousePressEvent(): context" << endl;
+// QPopupMenu m;
+// m_removeSelectedTableQueryAction->plug( &m );
+// m_removeSelectedConnectionAction->plug( &m );
+ emit connectionContextMenuRequest( ev->globalPos() );
+// executePopup( ev->globalPos() );
+ }
+ return;
+ }
+ //connection not found
+ clearSelection();
+// invalidateActions();
+ if(ev->button() == RightButton) {//show popup on view background area
+// QPopupMenu m;
+// m_removeSelectedConnectionAction->plug( &m );
+ emit emptyAreaContextMenuRequest( ev->globalPos() );
+// executePopup(ev->globalPos());
+ }
+ else {
+ emit emptyAreaGotFocus();
+ }
+ setFocus();
+// QScrollView::contentsMousePressEvent(ev);
+}
+
+void KexiRelationView::clearSelection()
+{
+ if (m_focusedTableView) {
+ m_focusedTableView->unsetFocus();
+ m_focusedTableView = 0;
+// setFocus();
+// invalidateActions();
+ }
+ if (m_selectedConnection) {
+ m_selectedConnection->setSelected(false);
+ updateContents(m_selectedConnection->connectionRect());
+ m_selectedConnection = 0;
+// invalidateActions();
+ }
+}
+
+void
+KexiRelationView::keyPressEvent(QKeyEvent *ev)
+{
+ kdDebug() << "KexiRelationView::keyPressEvent()" << endl;
+
+ if (ev->key()==KGlobalSettings::contextMenuKey()) {
+ if (m_selectedConnection) {
+ emit connectionContextMenuRequest(
+ mapToGlobal(m_selectedConnection->connectionRect().center()) );
+ }
+// m_popup->exec( mapToGlobal( m_focusedTableView ? m_focusedTableView->pos() + m_focusedTableView->rect().center() : rect().center() ) );
+// executePopup();
+ }
+ else {
+ if(ev->key() == Key_Delete)
+ removeSelectedObject();
+ }
+}
+
+void
+KexiRelationView::recalculateSize(int width, int height)
+{
+ kdDebug() << "recalculateSize(" << width << ", " << height << ")" << endl;
+ int newW = contentsWidth(), newH = contentsHeight();
+ kdDebug() << "contentsSize(" << newW << ", " << newH << ")" << endl;
+
+ if(newW < width)
+ newW = width;
+
+ if(newH < height)
+ newH = height;
+
+ resizeContents(newW, newH);
+}
+
+/*! Resizes contents to size exactly enough to fit tableViews.
+ Executed on every tableView's drop event.
+*/
+void
+KexiRelationView::stretchExpandSize()
+{
+ int max_x=-1, max_y=-1;
+ QDictIterator<KexiRelationViewTableContainer> it(m_tables);
+ for (;it.current(); ++it) {
+ if (it.current()->right()>max_x)
+ max_x = it.current()->right();
+ if (it.current()->bottom()>max_y)
+ max_y = it.current()->bottom();
+ }
+ QPoint p = viewportToContents(QPoint(max_x, max_y) + QPoint(3,3)); //3 pixels margin
+ resizeContents(p.x(), p.y());
+}
+
+void KexiRelationView::slotTableViewEndDrag()
+{
+ kdDebug() << "END DRAG!" <<endl;
+ stretchExpandSize();
+
+}
+
+void
+KexiRelationView::removeSelectedObject()
+{
+ if (m_selectedConnection) {
+ removeConnection(m_selectedConnection);
+
+#if 0
+ RelationList l = m_relation->projectRelations();
+ RelationList nl;
+ for(RelationList::Iterator it = l.begin(); it != l.end(); ++it)
+ {
+ if((*it).srcTable == m_selectedConnection->connection().srcTable
+ && (*it).rcvTable == m_selectedConnection->connection().rcvTable
+ && (*it).srcField == m_selectedConnection->connection().srcField
+ && (*it).rcvField == m_selectedConnection->connection().rcvField)
+ {
+ kdDebug() << "KexiRelationView::removeSelectedConnection(): matching found!" << endl;
+// l.remove(it);
+ }
+ else
+ {
+ nl.append(*it);
+ }
+ }
+
+ kdDebug() << "KexiRelationView::removeSelectedConnection(): d2" << endl;
+ m_relation->updateRelationList(this, nl);
+ kdDebug() << "KexiRelationView::removeSelectedConnection(): d3" << endl;
+#endif
+ delete m_selectedConnection;
+ m_selectedConnection = 0;
+// invalidateActions();
+ }
+ else if (m_focusedTableView) {
+ KexiRelationViewTableContainer *tmp = m_focusedTableView;
+ m_focusedTableView = 0;
+ hideTable(tmp);
+ }
+}
+
+void
+KexiRelationView::hideTable(KexiRelationViewTableContainer* tableView)
+{
+/*! @todo what about query? */
+ KexiDB::TableSchema *ts = tableView->schema()->table();
+ //for all connections: find and remove all connected with this table
+ QPtrListIterator<KexiRelationViewConnection> it(m_connectionViews);
+ for (;it.current();) {
+ if (it.current()->masterTable() == tableView
+ || it.current()->detailsTable() == tableView)
+ {
+ //remove this
+ removeConnection(it.current());
+ }
+ else {
+ ++it;
+ }
+ }
+ m_tables.take(tableView->schema()->name());
+ delete tableView;
+ emit tableHidden( *ts );
+}
+
+void
+KexiRelationView::hideAllTablesExcept( KexiDB::TableSchema::List* tables )
+{
+//! @todo what about queries?
+ for (TablesDictIterator it(m_tables); it.current();) {
+ KexiDB::TableSchema *table = it.current()->schema()->table();
+ if (!table || tables->findRef( table )!=-1) {
+ ++it;
+ continue;
+ }
+ hideTable(it.current());
+ }
+}
+
+void
+KexiRelationView::removeConnection(KexiRelationViewConnection *conn)
+{
+ emit aboutConnectionRemove(conn);
+ m_connectionViews.remove(conn);
+ updateContents(conn->connectionRect());
+ kdDebug() << "KexiRelationView::removeConnection()" << endl;
+}
+
+void KexiRelationView::slotTableViewGotFocus()
+{
+ if (m_focusedTableView == sender())
+ return;
+ kdDebug() << "GOT FOCUS!" <<endl;
+ clearSelection();
+// if (m_focusedTableView)
+// m_focusedTableView->unsetFocus();
+ m_focusedTableView = (KexiRelationViewTableContainer*)sender();
+// invalidateActions();
+ emit tableViewGotFocus();
+}
+
+QSize KexiRelationView::sizeHint() const
+{
+ return QSize(QScrollView::sizeHint());//.width(), 600);
+}
+
+void KexiRelationView::clear()
+{
+ removeAllConnections();
+ m_tables.setAutoDelete(true);
+ m_tables.clear();
+ m_tables.setAutoDelete(false);
+ updateContents();
+}
+
+void KexiRelationView::removeAllConnections()
+{
+ clearSelection(); //sanity
+ m_connectionViews.setAutoDelete(true);
+ m_connectionViews.clear();
+ m_connectionViews.setAutoDelete(false);
+ updateContents();
+}
+
+/*
+
+void KexiRelationView::tableHeaderContextMenuRequest(const QPoint& pos)
+{
+ if (m_focusedTableView != sender())
+ return;
+ kdDebug() << "HEADER CTXT MENU!" <<endl;
+ invalidateActions();
+ m_tableQueryPopup->exec(pos);
+}
+
+//! Invalidates all actions availability
+void KexiRelationView::invalidateActions()
+{
+ setAvailable("edit_delete", m_selectedConnection || m_focusedTableView);
+}
+
+void KexiRelationView::executePopup( QPoint pos )
+{
+ if (pos==QPoint(-1,-1)) {
+ pos = mapToGlobal( m_focusedTableView ? m_focusedTableView->pos() + m_focusedTableView->rect().center() : rect().center() );
+ }
+ if (m_focusedTableView)
+ m_tableQueryPopup->exec(pos);
+ else if (m_selectedConnection)
+ m_connectionPopup->exec(pos);
+}
+*/
+
+#include "kexirelationview.moc"
diff --git a/kexi/widget/relations/kexirelationview.h b/kexi/widget/relations/kexirelationview.h
new file mode 100644
index 000000000..2de6620d5
--- /dev/null
+++ b/kexi/widget/relations/kexirelationview.h
@@ -0,0 +1,167 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIRELATIONVIEW_H
+#define KEXIRELATIONVIEW_H
+
+#include <qguardedptr.h>
+#include <qscrollview.h>
+#include <qptrlist.h>
+#include <qdict.h>
+
+#include <kexidb/tableschema.h>
+
+#include "kexirelationviewconnection.h"
+
+class QFrame;
+
+class KexiRelationViewTable;
+class KexiRelationViewTableContainer;
+class KAction;
+class KPopupMenu;
+
+namespace KexiDB
+{
+ class Reference;
+ class Connection;
+}
+
+typedef QDict<KexiRelationViewTableContainer> TablesDict;
+typedef QDictIterator<KexiRelationViewTableContainer> TablesDictIterator;
+typedef QPtrList<KexiRelationViewConnection> ConnectionList;
+typedef QPtrListIterator<KexiRelationViewConnection> ConnectionListIterator;
+
+struct SourceConnection
+{
+ QString masterTable;
+ QString detailsTable;
+ QString masterField;
+ QString detailsField;
+};
+
+/*! @short provides a view for displaying relations between database tables.
+
+ It is currently used for two purposes:
+ - displaying global database relations
+ - displaying relations defined for a database query
+
+ The class is for displaying only - retrieving data and updating data on the backend side is implemented
+ in KexiRelationWidget, and more specifically in: Kexi Relation Part and Kexi Query Part.
+*/
+class KEXIRELATIONSVIEW_EXPORT KexiRelationView : public QScrollView
+{
+ Q_OBJECT
+
+ public:
+ KexiRelationView(QWidget *parent, const char *name=0);
+ virtual ~KexiRelationView();
+
+ //! \return a dictionary of added tables
+ TablesDict* tables() { return &m_tables; }
+
+ /*! Adds a table \a t to the area. This changes only visual representation.
+ If \a rect is valid, table widget geometry will be initialized.
+ \return added table container or 0 on failure.
+ */
+ KexiRelationViewTableContainer* addTableContainer(KexiDB::TableSchema *t,
+ const QRect &rect = QRect());
+
+ /*! \return table container for table \a t. */
+ KexiRelationViewTableContainer * tableContainer(KexiDB::TableSchema *t) const;
+
+ //! Adds a connection \a con to the area. This changes only visual representation.
+ void addConnection(const SourceConnection& _conn /*, bool interactive=true*/);
+
+ void setReadOnly(bool);
+
+ inline KexiRelationViewConnection* selectedConnection() const { return m_selectedConnection; }
+
+ inline KexiRelationViewTableContainer* focusedTableView() const { return m_focusedTableView; }
+
+ virtual QSize sizeHint() const;
+
+ const ConnectionList* connections() const { return &m_connectionViews; }
+
+// KexiRelationViewTableContainer* containerForTable(KexiDB::TableSchema* tableSchema);
+
+ signals:
+ void tableContextMenuRequest( const QPoint& pos );
+ void connectionContextMenuRequest( const QPoint& pos );
+ void emptyAreaContextMenuRequest( const QPoint& pos );
+ void tableViewGotFocus();
+ void connectionViewGotFocus();
+ void emptyAreaGotFocus();
+ void tableHidden(KexiDB::TableSchema& t);
+ void tablePositionChanged(KexiRelationViewTableContainer*);
+ void aboutConnectionRemove(KexiRelationViewConnection*);
+
+ public slots:
+ //! Clears current selection - table/query or connection
+ void clearSelection();
+
+ /*! Removes all tables and connections from the view.
+ Does not emit signals like tableHidden(). */
+ void clear();
+
+ /*! Removes all coonections from the view. */
+ void removeAllConnections();
+
+ /*! Hides all tables except \a tables. */
+ void hideAllTablesExcept( KexiDB::TableSchema::List* tables );
+
+ void slotTableScrolling(const QString&);
+
+ //! removes selected table or connection
+ void removeSelectedObject();
+
+
+ protected slots:
+ void containerMoved(KexiRelationViewTableContainer *c);
+ void slotListUpdate(QObject *s);
+ void slotTableViewEndDrag();
+ void slotTableViewGotFocus();
+
+ protected:
+// /*! executes popup menu at \a pos, or,
+// if \a pos not specified: at center of selected table view (if any selected),
+// or at center point of the relations view. */
+// void executePopup( QPoint pos = QPoint(-1,-1) );
+
+ void drawContents(QPainter *p, int cx, int cy, int cw, int ch);
+ void contentsMousePressEvent(QMouseEvent *ev);
+ virtual void keyPressEvent(QKeyEvent *ev);
+
+ void recalculateSize(int width, int height);
+ void stretchExpandSize();
+// void invalidateActions();
+// void clearTableSelection();
+// void clearConnSelection();
+
+ void hideTable(KexiRelationViewTableContainer* tableView);
+ void removeConnection(KexiRelationViewConnection *conn);
+
+ TablesDict m_tables;
+ bool m_readOnly;
+ ConnectionList m_connectionViews;
+ KexiRelationViewConnection* m_selectedConnection;
+ QGuardedPtr<KexiRelationViewTableContainer> m_focusedTableView;
+};
+
+#endif
diff --git a/kexi/widget/relations/kexirelationviewconnection.cpp b/kexi/widget/relations/kexirelationviewconnection.cpp
new file mode 100644
index 000000000..2c27de87b
--- /dev/null
+++ b/kexi/widget/relations/kexirelationviewconnection.cpp
@@ -0,0 +1,298 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qcolor.h>
+#include <qapplication.h>
+#include <qpointarray.h>
+
+#include <kdebug.h>
+
+#include <math.h>
+
+#include "kexirelationview.h"
+#include "kexirelationviewtable.h"
+#include "kexirelationviewconnection.h"
+#include <kexidb/tableschema.h>
+#include <kexidb/utils.h>
+#include <core/kexi.h>
+
+//#include "r1.xpm"
+//#include "rn.xpm"
+
+KexiRelationViewConnection::KexiRelationViewConnection(
+ KexiRelationViewTableContainer *masterTbl, KexiRelationViewTableContainer *detailsTbl,
+ SourceConnection &c, KexiRelationView *parent)
+{
+ m_parent = parent;
+// kdDebug() << "KexiRelationViewConnection::KexiRelationViewConnection()" << endl;
+
+ m_masterTable = masterTbl;
+ if(!masterTbl || !detailsTbl)
+ {
+ kdDebug() << "KexiRelationViewConnection::KexiRelationViewConnection(): expect sig11" << endl;
+ kdDebug() << "KexiRelationViewConnection::KexiRelationViewConnection()" << masterTbl << endl;
+ kdDebug() << "KexiRelationViewConnection::KexiRelationViewConnection()" << detailsTbl << endl;
+ }
+
+ m_detailsTable = detailsTbl;
+ m_masterField = c.masterField;
+ m_detailsField = c.detailsField;
+
+ m_selected = false;
+}
+
+KexiRelationViewConnection::~KexiRelationViewConnection()
+{
+}
+
+void
+KexiRelationViewConnection::drawConnection(QPainter *p)
+{
+ p->setPen(m_parent->palette().active().foreground());
+ int sx = m_masterTable->x() + m_masterTable->width() + m_parent->contentsX();
+ int sy = m_masterTable->globalY(m_masterField);
+ int rx = m_detailsTable->x() + m_parent->contentsX();
+ int ry = m_detailsTable->globalY(m_detailsField);
+
+ QFont f( Kexi::smallFont( m_parent ) );
+ QFontMetrics fm(f);
+ int side1x=0, side1y=sy - fm.height(),
+ sideNx=0, sideNy=ry - fm.height();
+//! @todo details char can be also just a '1' for some cases
+ QChar sideNChar(0x221E); //infinity char
+ uint sideNCharWidth = 2+2+ fm.width( sideNChar );
+ QChar side1Char('1');
+ uint side1CharWidth = 2+2+ fm.width( side1Char );
+ p->setBrush(p->pen().color());
+
+ if(m_masterTable->x() < m_detailsTable->x())
+ {
+ //det. side
+ p->drawLine(rx - sideNCharWidth, ry, rx, ry);
+ QPointArray pa(3);
+ pa.setPoint(0, rx - 4, ry - 3);
+ pa.setPoint(1, rx - 4, ry + 3);
+ pa.setPoint(2, rx - 1, ry);
+ p->drawPolygon(pa, true);
+
+ //master side
+ p->drawLine(sx, sy - 1, sx + side1CharWidth -1, sy - 1);
+ p->drawLine(sx, sy, sx + side1CharWidth -1, sy);
+ p->drawLine(sx, sy + 1, sx + side1CharWidth -1, sy + 1);
+
+ side1x = sx;
+// side1y = sy - 7;
+
+ sideNx = rx - sideNCharWidth - 1;
+// sideNy = ry - 6;
+
+ QPen pen(p->pen());
+ if(m_selected)
+ {
+ QPen pen(p->pen());
+ pen.setWidth(2);
+ p->setPen(pen);
+ }
+
+ p->drawLine(sx + side1CharWidth, sy, rx - sideNCharWidth, ry);
+
+ if(m_selected)
+ {
+ QPen pen(p->pen());
+ pen.setWidth(1);
+ p->setPen(pen);
+ }
+
+ }
+ else
+ {
+ int lx = rx + m_detailsTable->width();
+ int rx = sx - m_masterTable->width();
+
+ //det. side
+ p->drawLine(lx, ry, lx + sideNCharWidth, ry);
+ QPointArray pa(3);
+ pa.setPoint(0, lx + 3, ry - 3);
+ pa.setPoint(1, lx + 3, ry + 3);
+ pa.setPoint(2, lx, ry);
+ p->drawPolygon(pa, true);
+
+// p->drawLine(lx, ry, lx + 8, ry);
+// p->drawPoint(lx + 1, ry - 1);
+// p->drawPoint(lx + 1, ry + 1);
+// p->drawLine(lx + 2, ry - 2, lx + 2, ry + 2);
+
+ //master side
+ p->drawLine(rx - side1CharWidth +1, sy - 1, rx, sy - 1);
+ p->drawLine(rx - side1CharWidth +1, sy + 1, rx, sy + 1);
+ p->drawLine(rx - side1CharWidth +1, sy, rx, sy);
+
+ side1x = rx - side1CharWidth;
+// side1y = sy - 7;
+
+ sideNx = lx + 1;
+// sideNy = ry - 6;
+
+ if(m_selected)
+ {
+ QPen pen(p->pen());
+ pen.setWidth(2);
+ p->setPen(pen);
+ }
+
+ p->drawLine(lx + sideNCharWidth, ry, rx - side1CharWidth, sy);
+
+ if(m_selected)
+ {
+ QPen pen(p->pen());
+ pen.setWidth(1);
+ p->setPen(pen);
+ }
+ }
+
+ p->drawText(side1x, side1y, side1CharWidth, fm.height(), Qt::AlignCenter, side1Char);
+ p->drawText(sideNx, sideNy, sideNCharWidth, fm.height(), Qt::AlignCenter, sideNChar);
+ //p->drawRect(QRect(connectionRect().topLeft(), QSize(50,50)));
+// p->drawPixmap(side1, QPixmap(r1_xpm));
+// p->drawPixmap(sideN, QPixmap(rn_xpm));
+}
+
+const QRect
+KexiRelationViewConnection::connectionRect()
+{
+ int sx = m_masterTable->x() + m_parent->contentsX();
+ int rx = m_detailsTable->x() + m_parent->contentsX();
+ int ry = m_detailsTable->globalY(m_detailsField);
+ int sy = m_masterTable->globalY(m_masterField);
+
+ int width, leftX, rightX;
+
+ if(sx < rx)
+ {
+ leftX = sx;
+ rightX = rx;
+ width = m_masterTable->width();
+ }
+ else
+ {
+ leftX = rx;
+ rightX = sx;
+ width = m_detailsTable->width();
+ }
+
+
+ int dx = QABS((leftX + width) - rightX);
+ int dy = QABS(sy - ry) + 2;
+
+ int top = QMIN(sy, ry);
+ int left = leftX + width;
+
+
+// return QRect(sx - 1, sy - 1, (rx + m_detailsTable->width()) - sx + 1, ry - sy + 1);
+ QRect rect(left - 150, top - 150, dx + 150, dy + 150);
+// kdDebug() << "KexiRelationViewConnection::connectionRect():" << m_oldRect << "," << rect << endl;
+
+ m_oldRect = rect;
+
+ return rect;
+}
+
+bool
+KexiRelationViewConnection::matchesPoint(const QPoint &p, int tolerance)
+{
+ QRect we = connectionRect();
+
+ if(!we.contains(p))
+ return false;
+
+ /** get our coordinats
+ * you know what i mean the x1, y1 is the top point
+ * and the x2, y2 is the bottom point
+ * (quite tirvial :) although that was the entrace to the magic
+ * gate...
+ */
+
+ int sx = m_masterTable->x() + m_masterTable->width();
+ int sy = m_masterTable->globalY(m_masterField);
+ int rx = m_detailsTable->x();
+ int ry = m_detailsTable->globalY(m_detailsField);
+
+ int x1 = sx + 8;
+ int y1 = sy;
+ int x2 = rx - 8;
+ int y2 = ry;
+
+ if(sx > rx)
+ {
+ x1 = m_detailsTable->x() + m_detailsTable->width();
+ x2 = m_masterTable->x();
+ y2 = sy;
+ y1 = ry;
+ }
+
+ /*
+ here we call pythagoras (the greek math geek :p)
+ see: http://w1.480.telia.com/%7Eu48019406/geekporn.gif if you don't know
+ how these people have got sex :)
+ */
+ float mx = x2-x1;
+ float my = y2-y1;
+ float mag = sqrt(mx * mx + my * my);
+ float u = (((p.x() - x1)*(x2 - x1))+((p.y() - y1)*(y2 - y1)))/(mag * mag);
+ kdDebug() << "KexiRelationViewConnection::matchesPoint(): u: " << u << endl;
+
+ float iX = x1 + u * (x2 - x1);
+ float iY = y1 + u * (y2 - y1);
+ kdDebug() << "KexiRelationViewConnection::matchesPoint(): px: " << p.x() << endl;
+ kdDebug() << "KexiRelationViewConnection::matchesPoint(): py: " << p.y() << endl;
+ kdDebug() << "KexiRelationViewConnection::matchesPoint(): ix: " << iX << endl;
+ kdDebug() << "KexiRelationViewConnection::matchesPoint(): iy: " << iY << endl;
+
+ float dX = iX - p.x();
+ float dY = iY - p.y();
+
+ kdDebug() << "KexiRelationViewConnection::matchesPoint(): dx: " << dX << endl;
+ kdDebug() << "KexiRelationViewConnection::matchesPoint(): dy: " << dY << endl;
+
+ float distance = sqrt(dX * dX + dY * dY);
+ kdDebug() << "KexiRelationViewConnection::matchesPoint(): distance: " << distance << endl;
+
+ if(distance <= tolerance)
+ return true;
+
+ return false;
+}
+
+QString
+KexiRelationViewConnection::toString() const
+{
+ QString str;
+/*! @todo what about query? */
+ if (m_masterTable && m_masterTable->schema()->table()) {
+ str += (QString(m_masterTable->schema()->name()) + "." + m_masterField);
+ }
+ if (m_detailsTable && m_detailsTable->schema()->table()) {
+ str += " - ";
+ str += (QString(m_detailsTable->schema()->name()) + "." + m_detailsField);
+ }
+ return str;
+}
diff --git a/kexi/widget/relations/kexirelationviewconnection.h b/kexi/widget/relations/kexirelationviewconnection.h
new file mode 100644
index 000000000..699fdf4fe
--- /dev/null
+++ b/kexi/widget/relations/kexirelationviewconnection.h
@@ -0,0 +1,75 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIRELATIONVIEWCONNECTION_H
+#define KEXIRELATIONVIEWCONNECTION_H
+
+#include <qstring.h>
+#include <qguardedptr.h>
+
+class QPainter;
+class KexiRelationViewTableContainer;
+class KexiRelationView;
+
+class KEXIRELATIONSVIEW_EXPORT KexiRelationViewConnection
+{
+ public:
+
+ KexiRelationViewConnection(KexiRelationViewTableContainer *masterTbl,
+ KexiRelationViewTableContainer *detailsTbl, struct SourceConnection &s, KexiRelationView *parent);
+ ~KexiRelationViewConnection();
+
+
+ /*
+ C++PROGRAMMIERER bestehen darauf, da�der Elefant eine Klasse sei,
+ und somit schlie�ich seine Fang-Methoden selbst mitzubringen habe.
+
+ http://www.c-plusplus.de ;)
+ */
+ void drawConnection(QPainter *p);
+
+ bool selected() { return m_selected; }
+ void setSelected(bool s) { m_selected = s; }
+
+ const QRect connectionRect();
+ const QRect oldRect() const { return m_oldRect; }
+
+ KexiRelationViewTableContainer *masterTable() { return m_masterTable; }
+ KexiRelationViewTableContainer *detailsTable() { return m_detailsTable; }
+ QString masterField() const { return m_masterField; }
+ QString detailsField() const { return m_detailsField; }
+
+
+ bool matchesPoint(const QPoint &p, int tolerance=3);
+// SourceConnection connection() { return m_conn; }
+
+ QString toString() const;
+
+ private:
+ QGuardedPtr<KexiRelationViewTableContainer> m_masterTable;
+ QGuardedPtr<KexiRelationViewTableContainer> m_detailsTable;
+ QString m_masterField;
+ QString m_detailsField;
+ QRect m_oldRect;
+ bool m_selected;
+ QGuardedPtr<KexiRelationView> m_parent;
+};
+
+#endif
diff --git a/kexi/widget/relations/kexirelationviewtable.cpp b/kexi/widget/relations/kexirelationviewtable.cpp
new file mode 100644
index 000000000..97beaa874
--- /dev/null
+++ b/kexi/widget/relations/kexirelationviewtable.cpp
@@ -0,0 +1,429 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <stdlib.h>
+
+#include <qheader.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qapplication.h>
+#include <qbitmap.h>
+#include <qstyle.h>
+
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <kdeversion.h>
+#include <kconfig.h>
+#include <kglobalsettings.h>
+
+#include <kexidb/tableschema.h>
+#include <kexidb/utils.h>
+#include <kexidragobjects.h>
+#include "kexirelationviewtable.h"
+#include "kexirelationview.h"
+
+KexiRelationViewTableContainer::KexiRelationViewTableContainer(
+ KexiRelationView *parent, KexiDB::TableOrQuerySchema *schema)
+ : QFrame(parent,"KexiRelationViewTableContainer" )
+// , m_table(t)
+ , m_parent(parent)
+// , m_mousePressed(false)
+{
+
+// setFixedSize(100, 150);
+//js: resize(100, 150);
+ //setMouseTracking(true);
+
+ setFrameStyle( QFrame::WinPanel | QFrame::Raised );
+
+ QVBoxLayout *lyr = new QVBoxLayout(this,4,1); //js: using Q*BoxLayout is a good idea
+
+ m_tableHeader = new KexiRelationViewTableContainerHeader(schema->name(), this);
+
+ m_tableHeader->unsetFocus();
+ m_tableHeader->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
+ lyr->addWidget(m_tableHeader);
+ connect(m_tableHeader,SIGNAL(moved()),this,SLOT(moved()));
+ connect(m_tableHeader, SIGNAL(endDrag()), this, SIGNAL(endDrag()));
+
+ m_tableView = new KexiRelationViewTable(schema, parent, this, "KexiRelationViewTable");
+ //m_tableHeader->setFocusProxy( m_tableView );
+ m_tableView->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
+
+ m_tableView->setMaximumSize( m_tableView->sizeHint() );
+
+// m_tableView->resize( m_tableView->sizeHint() );
+ lyr->addWidget(m_tableView, 0);
+ connect(m_tableView, SIGNAL(tableScrolling()), this, SLOT(moved()));
+ connect(m_tableView, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)),
+ this, SLOT(slotContextMenu(KListView*, QListViewItem*, const QPoint&)));
+}
+
+KexiRelationViewTableContainer::~KexiRelationViewTableContainer()
+{
+}
+
+KexiDB::TableOrQuerySchema* KexiRelationViewTableContainer::schema() const
+{
+ return m_tableView->schema();
+}
+
+void KexiRelationViewTableContainer::slotContextMenu(KListView *, QListViewItem *, const QPoint &p)
+{
+// m_parent->executePopup(p);
+ emit contextMenuRequest( p );
+}
+
+void KexiRelationViewTableContainer::moved() {
+// kdDebug()<<"finally emitting moved"<<endl;
+ emit moved(this);
+}
+
+int KexiRelationViewTableContainer::globalY(const QString &field)
+{
+// kdDebug() << "KexiRelationViewTableContainer::globalY()" << endl;
+// QPoint o = mapFromGlobal(QPoint(0, (m_tableView->globalY(field))/*+m_parent->contentsY()*/));
+
+ QPoint o(0, (m_tableView->globalY(field)) + m_parent->contentsY());
+// kdDebug() << "KexiRelationViewTableContainer::globalY() db2" << endl;
+ return m_parent->viewport()->mapFromGlobal(o).y();
+}
+
+#if 0//js
+QSize KexiRelationViewTableContainer::sizeHint()
+{
+#ifdef Q_WS_WIN
+ QSize s = m_tableView->sizeHint()
+ + QSize( 2 * 5 , m_tableHeader->height() + 2 * 5 );
+#else
+ QSize s = m_tableView->sizeHint();
+ s.setWidth(s.width() + 4);
+ s.setHeight(m_tableHeader->height() + s.height());
+#endif
+ return s;
+}
+#endif
+
+void KexiRelationViewTableContainer::setFocus()
+{
+ kdDebug() << "SET FOCUS" << endl;
+ //select 1st:
+ if (m_tableView->firstChild()) {
+ if (!m_tableView->selectedItems().first())
+ m_tableView->setSelected( m_tableView->firstChild(), true );
+ }
+ m_tableHeader->setFocus();
+ m_tableView->setFocus();
+/* QPalette p = qApp->palette();
+ p.setColor( QPalette::Active, QColorGroup::Highlight, KGlobalSettings::highlightColor() );
+ p.setColor( QPalette::Active, QColorGroup::HighlightedText, KGlobalSettings::highlightedTextColor() );
+ m_tableView->setPalette(p);*/
+
+ raise();
+ repaint();
+ emit gotFocus();
+}
+
+void KexiRelationViewTableContainer::unsetFocus()
+{
+ kdDebug() << "UNSET FOCUS" << endl;
+// if (m_tableView->selectedItem()) //unselect item if was selected
+// m_tableView->setSelected(m_tableView->selectedItem(), false);
+// m_tableView->clearSelection();
+ m_tableHeader->unsetFocus();
+
+ m_tableView->clearSelection();
+
+// m_tableView->unsetPalette();
+/* QPalette p = m_tableView->palette();
+// p.setColor( QPalette::Active, QColorGroup::Highlight, KGlobalSettings::highlightColor() );
+// p.setColor( QPalette::Active, QColorGroup::HighlightedText, KGlobalSettings::highlightedTextColor() );
+ p.setColor( QPalette::Active, QColorGroup::Highlight, p.color(QPalette::Active, QColorGroup::Background ) );
+// p.setColor( QPalette::Active, QColorGroup::Highlight, gray );
+ p.setColor( QPalette::Active, QColorGroup::HighlightedText, p.color(QPalette::Active, QColorGroup::Foreground ) );
+// p.setColor( QPalette::Active, QColorGroup::Highlight, green );
+// p.setColor( QPalette::Active, QColorGroup::HighlightedText, blue );
+ m_tableView->setPalette(p);*/
+
+ clearFocus();
+ repaint();
+}
+
+
+//END KexiRelationViewTableContainer
+
+//============================================================================
+//BEGIN KexiRelatoinViewTableContainerHeader
+
+KexiRelationViewTableContainerHeader::KexiRelationViewTableContainerHeader(
+ const QString& text,QWidget *parent)
+ :QLabel(text,parent),m_dragging(false)
+{
+ setMargin(1);
+ m_activeBG = KGlobalSettings::activeTitleColor();
+ m_activeFG = KGlobalSettings::activeTextColor();
+ m_inactiveBG = KGlobalSettings::inactiveTitleColor();
+ m_inactiveFG = KGlobalSettings::inactiveTextColor();
+
+ installEventFilter(this);
+}
+
+KexiRelationViewTableContainerHeader::~KexiRelationViewTableContainerHeader()
+{
+}
+
+void KexiRelationViewTableContainerHeader::setFocus()
+{
+ setPaletteBackgroundColor(m_activeBG);
+ setPaletteForegroundColor(m_activeFG);
+}
+
+void KexiRelationViewTableContainerHeader::unsetFocus()
+{
+ setPaletteBackgroundColor(m_inactiveBG);
+ setPaletteForegroundColor(m_inactiveFG);
+}
+
+bool KexiRelationViewTableContainerHeader::eventFilter(QObject *, QEvent *ev)
+{
+ if (ev->type()==QEvent::MouseMove)
+ {
+ if (m_dragging && static_cast<QMouseEvent*>(ev)->state()==Qt::LeftButton) {
+ int diffX,diffY;
+ diffX=static_cast<QMouseEvent*>(ev)->globalPos().x()-m_grabX;
+ diffY=static_cast<QMouseEvent*>(ev)->globalPos().y()-m_grabY;
+ if ((abs(diffX)>2) || (abs(diffY)>2))
+ {
+ QPoint newPos=parentWidget()->pos()+QPoint(diffX,diffY);
+//correct the x position
+ if (newPos.x()<0) {
+ m_offsetX+=newPos.x();
+ newPos.setX(0);
+ }
+ else if (m_offsetX<0) {
+ m_offsetX+=newPos.x();
+ if (m_offsetX>0) {
+ newPos.setX(m_offsetX);
+ m_offsetX=0;
+ }
+ else newPos.setX(0);
+ }
+//correct the y position
+ if (newPos.y()<0) {
+ m_offsetY+=newPos.y();
+ newPos.setY(0);
+ }
+ else
+ if (m_offsetY<0) {
+ m_offsetY+=newPos.y();
+ if (m_offsetY>0) {
+ newPos.setY(m_offsetY);
+ m_offsetY=0;
+ }
+ else newPos.setY(0);
+ }
+//move and update helpers
+
+ parentWidget()->move(newPos);
+ m_grabX=static_cast<QMouseEvent*>(ev)->globalPos().x();
+ m_grabY=static_cast<QMouseEvent*>(ev)->globalPos().y();
+// kdDebug()<<"HEADER:emitting moved"<<endl;
+ emit moved();
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+void KexiRelationViewTableContainerHeader::mousePressEvent(QMouseEvent *ev) {
+ kdDebug()<<"KexiRelationViewTableContainerHeader::Mouse Press Event"<<endl;
+ parentWidget()->setFocus();
+ ev->accept();
+ if (ev->button()==Qt::LeftButton) {
+ m_dragging=true;
+ m_grabX=ev->globalPos().x();
+ m_grabY=ev->globalPos().y();
+ m_offsetX=0;
+ m_offsetY=0;
+ setCursor(Qt::SizeAllCursor);
+ return;
+ }
+ if (ev->button()==Qt::RightButton) {
+ emit static_cast<KexiRelationViewTableContainer*>(parentWidget())
+ ->contextMenuRequest(ev->globalPos());
+ }
+// QLabel::mousePressEvent(ev);
+}
+
+void KexiRelationViewTableContainerHeader::mouseReleaseEvent(QMouseEvent *ev) {
+ kdDebug()<<"KexiRelationViewTableContainerHeader::Mouse Release Event"<<endl;
+ if (m_dragging && ev->button() & Qt::LeftButton) {
+ setCursor(Qt::ArrowCursor);
+ m_dragging=false;
+ emit endDrag();
+ }
+ ev->accept();
+}
+
+//END KexiRelatoinViewTableContainerHeader
+
+
+//=====================================================================================
+
+KexiRelationViewTable::KexiRelationViewTable(KexiDB::TableOrQuerySchema* tableOrQuerySchema,
+ KexiRelationView *view, QWidget *parent, const char *name)
+ : KexiFieldListView(parent, name, KexiFieldListView::ShowAsterisk)
+ , m_view(view)
+{
+ setSchema(tableOrQuerySchema);
+ header()->hide();
+
+ connect(this, SIGNAL(dropped(QDropEvent *, QListViewItem *)), this, SLOT(slotDropped(QDropEvent *)));
+ connect(this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotContentsMoving(int,int)));
+}
+
+KexiRelationViewTable::~KexiRelationViewTable()
+{
+}
+
+QSize KexiRelationViewTable::sizeHint() const
+{
+ QFontMetrics fm(fontMetrics());
+
+// kdDebug() << schema()->name() << " cw=" << columnWidth(0) + fm.width("i")
+// << ", " << fm.width(schema()->name()+" ") << endl;
+
+ int maxWidth = -1;
+ const int iconWidth = IconSize(KIcon::Small) + fm.width("i")+20;
+ for (QListViewItem *item = firstChild(); item; item = item->nextSibling())
+ maxWidth = QMAX(maxWidth, iconWidth + fm.width(item->text(0)));
+
+ const uint rowCount = QMIN( 8, childCount() );
+
+ QSize s(
+ QMAX( maxWidth, fm.width(schema()->name()+" ")),
+ rowCount*firstChild()->totalHeight() + 4 );
+ return s;
+}
+
+#if 0
+void KexiRelationViewTable::setReadOnly(bool b)
+{
+ setAcceptDrops(!b);
+ viewport()->setAcceptDrops(!b);
+}
+#endif
+
+int
+KexiRelationViewTable::globalY(const QString &item)
+{
+ QListViewItem *i = findItem(item, 0);
+ if (!i)
+ return -1;
+ int y = itemRect(i).y() + (itemRect(i).height() / 2);
+ if (contentsY() > itemPos(i))
+ y = 0;
+ else if (y == 0)
+ y = height();
+ return mapToGlobal(QPoint(0, y)).y();
+}
+
+bool
+KexiRelationViewTable::acceptDrag(QDropEvent *ev) const
+{
+// kdDebug() << "KexiRelationViewTable::acceptDrag()" << endl;
+ QListViewItem *receiver = itemAt(ev->pos() - QPoint(0,contentsY()));
+ if (!receiver || !KexiFieldDrag::canDecodeSingle(ev))
+ return false;
+ QString sourceMimeType;
+ QString srcTable;
+ QString srcField;
+ if (!KexiFieldDrag::decodeSingle(ev,sourceMimeType,srcTable,srcField))
+ return false;
+ if (sourceMimeType!="kexi/table" && sourceMimeType=="kexi/query")
+ return false;
+ QString f = receiver->text(0).stripWhiteSpace();
+ if (!srcField.stripWhiteSpace().startsWith("*") && !f.startsWith("*") && ev->source() != (QWidget*)this)
+ return true;
+
+ return false;
+}
+
+void
+KexiRelationViewTable::slotDropped(QDropEvent *ev)
+{
+ QListViewItem *recever = itemAt(ev->pos() - QPoint(0,contentsY()));
+ if (!recever || !KexiFieldDrag::canDecodeSingle(ev)) {
+ ev->ignore();
+ return;
+ }
+ QString sourceMimeType;
+ QString srcTable;
+ QString srcField;
+ if (!KexiFieldDrag::decodeSingle(ev,sourceMimeType,srcTable,srcField))
+ return;
+ if (sourceMimeType!="kexi/table" && sourceMimeType=="kexi/query")
+ return;
+// kdDebug() << "KexiRelationViewTable::slotDropped() srcfield: " << srcField << endl;
+
+ QString rcvField = recever->text(0);
+
+ SourceConnection s;
+ s.masterTable = srcTable;
+ s.detailsTable = schema()->name();
+ s.masterField = srcField;
+ s.detailsField = rcvField;
+
+ m_view->addConnection(s);
+
+ kdDebug() << "KexiRelationViewTable::slotDropped() " << srcTable << ":" << srcField << " "
+ << schema()->name() << ":" << rcvField << endl;
+ ev->accept();
+}
+
+void
+KexiRelationViewTable::slotContentsMoving(int,int)
+{
+ emit tableScrolling();
+}
+
+void KexiRelationViewTable::contentsMousePressEvent(QMouseEvent *ev)
+{
+ parentWidget()->setFocus();
+ setFocus();
+ KListView::contentsMousePressEvent(ev);
+// if (ev->button()==Qt::RightButton)
+// static_cast<KexiRelationView*>(parentWidget())->executePopup(ev->pos());
+}
+
+QRect KexiRelationViewTable::drawItemHighlighter(QPainter *painter, QListViewItem *item)
+{
+ if (painter) {
+ style().drawPrimitive(QStyle::PE_FocusRect, painter, itemRect(item), colorGroup(),
+ QStyle::Style_FocusAtBorder);
+ }
+ return itemRect(item);
+}
+
+#include "kexirelationviewtable.moc"
diff --git a/kexi/widget/relations/kexirelationviewtable.h b/kexi/widget/relations/kexirelationviewtable.h
new file mode 100644
index 000000000..cc90e16d7
--- /dev/null
+++ b/kexi/widget/relations/kexirelationviewtable.h
@@ -0,0 +1,157 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIRELATIONVIEWTABLE_H
+#define KEXIRELATIONVIEWTABLE_H
+
+#include <qframe.h>
+#include <qstringlist.h>
+#include <qlabel.h>
+#include <klistview.h>
+
+#include <widget/kexifieldlistview.h>
+
+class KexiRelationView;
+class KexiRelationViewTable;
+class KexiRelationViewTableContainerHeader;
+
+namespace KexiDB
+{
+ class TableOrQuerySchema;
+}
+
+class KEXIRELATIONSVIEW_EXPORT KexiRelationViewTableContainer : public QFrame
+{
+ Q_OBJECT
+
+ public:
+// KexiRelationViewTableContainer(KexiRelationView *parent, KexiDB::TableSchema *t);
+ KexiRelationViewTableContainer(
+ KexiRelationView *parent, KexiDB::TableOrQuerySchema *schema);
+
+ virtual ~KexiRelationViewTableContainer();
+
+ int globalY(const QString &field);
+// KexiDB::TableSchema *table();
+
+ KexiRelationViewTable* tableView() const { return m_tableView; }
+ KexiDB::TableOrQuerySchema* schema() const;
+
+ int right() { return x() + width() - 1; }
+ int bottom() { return y() + height() - 1; }
+
+ signals:
+ void moved(KexiRelationViewTableContainer *);
+ void endDrag();
+ void gotFocus();
+ void contextMenuRequest(const QPoint& pos);
+
+ public slots:
+ virtual void setFocus();
+ virtual void unsetFocus();
+
+ protected slots:
+ void moved();
+ void slotContextMenu(KListView *lv, QListViewItem *i, const QPoint& p);
+
+ protected:
+// KexiDB::TableSchema *m_table;
+ KexiRelationViewTableContainerHeader *m_tableHeader;
+ KexiRelationViewTable *m_tableView;
+ KexiRelationView *m_parent;
+
+ friend class KexiRelationViewTableContainerHeader;
+};
+
+/*
+class KEXIRELATIONSVIEW_EXPORT KexiRelationViewTableItem : public KListViewItem
+{
+ public:
+ KexiRelationViewTableItem(QListView *parent, QListViewItem *after,
+ QString key, QString field);
+ virtual void paintFocus ( QPainter * p, const QColorGroup & cg, const QRect & r );
+};*/
+
+
+class KEXIRELATIONSVIEW_EXPORT KexiRelationViewTable : public KexiFieldListView
+{
+ Q_OBJECT
+
+ public:
+ KexiRelationViewTable(KexiDB::TableOrQuerySchema* tableOrQuerySchema,
+ KexiRelationView *view, QWidget *parent, const char *name = 0);
+// KexiRelationViewTable(QWidget *parent, KexiRelationView *view, KexiDB::TableSchema *t, const char *name=0);
+ virtual ~KexiRelationViewTable();
+
+// KexiDB::TableSchema *table() const { return m_table; };
+ int globalY(const QString &item);
+// void setReadOnly(bool);
+ virtual QSize sizeHint() const;
+
+ signals:
+ void tableScrolling();
+
+ protected slots:
+ void slotDropped(QDropEvent *e);
+ void slotContentsMoving(int, int);
+// void slotItemDoubleClicked( QListViewItem *i, const QPoint &, int );
+
+ protected:
+ virtual void contentsMousePressEvent( QMouseEvent * e );
+ virtual bool acceptDrag(QDropEvent *e) const;
+//moved virtual QDragObject *dragObject();
+ virtual QRect drawItemHighlighter(QPainter *painter, QListViewItem *item);
+
+ private:
+// QStringList m_fieldList;
+// KexiDB::TableSchema *m_table;
+ KexiRelationView *m_view;
+// QPixmap m_keyIcon, m_noIcon;
+};
+
+class KEXIRELATIONSVIEW_EXPORT KexiRelationViewTableContainerHeader : public QLabel
+{
+ Q_OBJECT
+ public:
+ KexiRelationViewTableContainerHeader(const QString& text,QWidget *parent);
+ virtual ~KexiRelationViewTableContainerHeader();
+
+ virtual void setFocus();
+ virtual void unsetFocus();
+
+ signals:
+ void moved();
+ void endDrag();
+
+ protected:
+ bool eventFilter(QObject *obj, QEvent *ev);
+ void mousePressEvent(QMouseEvent *ev);
+ void mouseReleaseEvent(QMouseEvent *ev);
+
+ bool m_dragging;
+ int m_grabX;
+ int m_grabY;
+ int m_offsetX;
+ int m_offsetY;
+
+ QColor m_activeBG, m_activeFG, m_inactiveBG, m_inactiveFG;
+};
+
+#endif
diff --git a/kexi/widget/relations/kexirelationwidget.cpp b/kexi/widget/relations/kexirelationwidget.cpp
new file mode 100644
index 000000000..14ec4ce05
--- /dev/null
+++ b/kexi/widget/relations/kexirelationwidget.cpp
@@ -0,0 +1,425 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexirelationwidget.h"
+
+#include <qlayout.h>
+#include <qlistbox.h>
+#include <qpushbutton.h>
+#include <qtimer.h>
+
+#include <kcombobox.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <kpushbutton.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/utils.h>
+
+#include <kexiproject.h>
+#include <keximainwindow.h>
+#include "kexirelationview.h"
+#include "kexirelationviewtable.h"
+#include "kexirelationviewconnection.h"
+
+KexiRelationWidget::KexiRelationWidget(KexiMainWindow *win, QWidget *parent,
+ const char *name)
+ : KexiViewBase(win, parent, name)
+ , m_win(win)
+{
+ m_conn = m_win->project()->dbConnection();
+
+ QHBoxLayout *hlyr = new QHBoxLayout(0);
+ QGridLayout *g = new QGridLayout(this);
+ g->addLayout( hlyr, 0, 0 );
+
+ m_tableCombo = new KComboBox(this, "tables_combo");
+ m_tableCombo->setMinimumWidth(QFontMetrics(font()).width("w")*20);
+ QLabel *lbl = new QLabel(m_tableCombo, i18n("Table")+": ", this);
+ lbl->setIndent(3);
+ m_tableCombo->setInsertionPolicy(QComboBox::NoInsertion);
+ hlyr->addWidget(lbl);
+ hlyr->addWidget(m_tableCombo);
+ m_tableCombo->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred));
+ fillTablesCombo();
+
+ m_btnAdd = new KPushButton(i18n("&Add"), this);
+ hlyr->addWidget(m_btnAdd);
+ hlyr->addStretch(1);
+ connect(m_btnAdd, SIGNAL(clicked()), this, SLOT(slotAddTable()));
+
+ m_relationView = new KexiRelationView(this, "relation_view");
+ setViewWidget(m_relationView);
+ g->addWidget(m_relationView, 1, 0);
+ //m_relationView->setFocus();
+
+ //actions
+ m_tableQueryPopup = new KPopupMenu(this, "m_popup");
+ m_tableQueryPopupTitleID = m_tableQueryPopup->insertTitle(SmallIcon("table"), "");
+ connect(m_tableQueryPopup, SIGNAL(aboutToShow()), this, SLOT(aboutToShowPopupMenu()));
+
+ m_connectionPopup = new KPopupMenu(this, "m_connectionPopup");
+ m_connectionPopupTitleID = m_connectionPopup->insertTitle("");
+ connect(m_connectionPopup, SIGNAL(aboutToShow()), this, SLOT(aboutToShowPopupMenu()));
+
+ m_areaPopup = new KPopupMenu(this, "m_areaPopup");
+
+ m_openSelectedTableAction = new KAction(i18n("&Open Table"), SmallIcon("fileopen"), KShortcut(),
+ this, SLOT(openSelectedTable()), this, "relationsview_openTable");
+ m_openSelectedTableAction->plug( m_tableQueryPopup );
+ m_designSelectedTableAction = new KAction(i18n("&Design Table"), SmallIcon("edit"), KShortcut(),
+ this, SLOT(designSelectedTable()), this, "relationsview_designTable");
+ m_designSelectedTableAction->plug( m_tableQueryPopup );
+ m_tableQueryPopup->insertSeparator();
+
+ KAction* hide_action = plugSharedAction("edit_delete", i18n("&Hide Table"), m_tableQueryPopup);
+ hide_action->setIconSet(QIconSet());
+
+ plugSharedAction("edit_delete",m_connectionPopup);
+ plugSharedAction("edit_delete",this, SLOT(removeSelectedObject()));
+
+ connect(m_relationView, SIGNAL(tableViewGotFocus()),
+ this, SLOT(tableViewGotFocus()));
+ connect(m_relationView, SIGNAL(connectionViewGotFocus()),
+ this, SLOT(connectionViewGotFocus()));
+ connect(m_relationView, SIGNAL(emptyAreaGotFocus()),
+ this, SLOT(emptyAreaGotFocus()));
+ connect(m_relationView, SIGNAL(tableContextMenuRequest( const QPoint& )),
+ this, SLOT(tableContextMenuRequest( const QPoint& )));
+ connect(m_relationView, SIGNAL(connectionContextMenuRequest( const QPoint& )),
+ this, SLOT(connectionContextMenuRequest( const QPoint& )));
+ connect(m_relationView, SIGNAL(tableHidden(KexiDB::TableSchema&)),
+ this, SLOT(slotTableHidden(KexiDB::TableSchema&)));
+ connect(m_relationView, SIGNAL(tablePositionChanged(KexiRelationViewTableContainer*)),
+ this, SIGNAL(tablePositionChanged(KexiRelationViewTableContainer*)));
+ connect(m_relationView, SIGNAL(aboutConnectionRemove(KexiRelationViewConnection*)),
+ this, SIGNAL(aboutConnectionRemove(KexiRelationViewConnection*)));
+
+#if 0
+ if(!embedd)
+ {
+/*todo setContextHelp(i18n("Relations"), i18n("To create a relationship simply drag the source field onto the target field. "
+ "An arrowhead is used to show which table is the parent (master) and which table is the child (slave) in the relationship."));*/
+ }
+#endif
+// else
+//js: while embedding means read-only? m_relationView->setReadOnly(true);
+
+#ifdef TESTING_KexiRelationWidget
+ for (int i=0;i<(int)m_db->tableNames().count();i++)
+ QTimer::singleShot(100,this,SLOT(slotAddTable()));
+#endif
+
+ invalidateActions();
+}
+
+KexiRelationWidget::~KexiRelationWidget()
+{
+}
+
+TablesDict* KexiRelationWidget::tables() const
+{
+ return m_relationView->tables();
+}
+
+KexiRelationViewTableContainer* KexiRelationWidget::table(const QString& name) const
+{
+ return m_relationView->tables()->find( name );
+}
+
+const ConnectionList* KexiRelationWidget::connections() const
+{
+ return m_relationView->connections();
+}
+
+void
+KexiRelationWidget::slotAddTable()
+{
+ if (m_tableCombo->currentItem()==-1)
+ return;
+ QString tname = m_tableCombo->text(m_tableCombo->currentItem());
+ KexiDB::TableSchema *t = m_conn->tableSchema(tname);
+ addTable(t);
+}
+
+void
+KexiRelationWidget::addTable(KexiDB::TableSchema *t, const QRect &rect)
+{
+ if (!t)
+ return;
+ if (!m_relationView->tableContainer(t)) {
+ KexiRelationViewTableContainer *c = m_relationView->addTableContainer(t, rect);
+ kdDebug() << "KexiRelationWidget::slotAddTable(): adding table " << t->name() << endl;
+ if (!c)
+ return;
+ connect(c->tableView(), SIGNAL(doubleClicked(QListViewItem*,const QPoint&,int)),
+ this, SLOT(slotTableFieldDoubleClicked(QListViewItem*,const QPoint&,int)));
+ }
+
+ const QString tname = t->name().lower();
+ const int count = m_tableCombo->count();
+ int i = 0;
+ for (; i < count; i++ ) {
+ if (m_tableCombo->text(i).lower() == tname )
+ break;
+ }
+ if (i<count) {
+ int oi = m_tableCombo->currentItem();
+ kdDebug()<<"KexiRelationWidget::slotAddTable(): removing a table from the combo box"<<endl;
+ m_tableCombo->removeItem(i);
+ if (m_tableCombo->count()>0) {
+ if (oi>=m_tableCombo->count()) {
+ oi=m_tableCombo->count()-1;
+ }
+ m_tableCombo->setCurrentItem(oi);
+ }
+ else {
+ m_tableCombo->setEnabled(false);
+ m_btnAdd->setEnabled(false);
+ }
+ }
+ emit tableAdded(*t);
+}
+
+void
+KexiRelationWidget::addConnection(const SourceConnection& conn)
+{
+ m_relationView->addConnection(conn);
+}
+
+void
+KexiRelationWidget::addTable(const QString& t)
+{
+ for(int i=0; i < m_tableCombo->count(); i++)
+ {
+ if(m_tableCombo->text(i) == t)
+ {
+ m_tableCombo->setCurrentItem(i);
+ slotAddTable();
+ }
+ }
+}
+
+void KexiRelationWidget::tableViewGotFocus()
+{
+// if (m_relationView->focusedTableView == sender())
+// return;
+// kdDebug() << "GOT FOCUS!" <<endl;
+// clearSelection();
+// if (m_focusedTableView)
+// m_focusedTableView->unsetFocus();
+// m_focusedTableView = (KexiRelationViewTableContainer*)sender();
+ invalidateActions();
+}
+
+void KexiRelationWidget::connectionViewGotFocus()
+{
+ invalidateActions();
+}
+
+void KexiRelationWidget::emptyAreaGotFocus()
+{
+ invalidateActions();
+}
+
+void KexiRelationWidget::tableContextMenuRequest(const QPoint& pos)
+{
+ invalidateActions();
+ executePopup( pos );
+}
+
+void KexiRelationWidget::connectionContextMenuRequest(const QPoint& pos)
+{
+ invalidateActions();
+ executePopup( pos );
+// m_connectionPopup->exec(pos);
+}
+
+void KexiRelationWidget::emptyAreaContextMenuRequest( const QPoint& /*pos*/ )
+{
+ invalidateActions();
+ //TODO
+}
+
+void KexiRelationWidget::invalidateActions()
+{
+ setAvailable("edit_delete", m_relationView->selectedConnection() || m_relationView->focusedTableView());
+}
+
+void KexiRelationWidget::executePopup( QPoint pos )
+{
+ if (pos==QPoint(-1,-1)) {
+ pos = mapToGlobal(
+ m_relationView->focusedTableView() ? m_relationView->focusedTableView()->pos() + m_relationView->focusedTableView()->rect().center() : rect().center() );
+ }
+ if (m_relationView->focusedTableView())
+ m_tableQueryPopup->exec(pos);
+ else if (m_relationView->selectedConnection())
+ m_connectionPopup->exec(pos);
+}
+
+void KexiRelationWidget::removeSelectedObject()
+{
+ m_relationView->removeSelectedObject();
+}
+
+void KexiRelationWidget::openSelectedTable()
+{
+/*! @todo what about query? */
+ if (!m_relationView->focusedTableView() || !m_relationView->focusedTableView()->schema()->table())
+ return;
+ bool openingCancelled;
+ m_win->openObject("kexi/table", m_relationView->focusedTableView()->schema()->name(),
+ Kexi::DataViewMode, openingCancelled);
+}
+
+void KexiRelationWidget::designSelectedTable()
+{
+/*! @todo what about query? */
+ if (!m_relationView->focusedTableView() || !m_relationView->focusedTableView()->schema()->table())
+ return;
+ bool openingCancelled;
+ m_win->openObject("kexi/table", m_relationView->focusedTableView()->schema()->name(),
+ Kexi::DesignViewMode, openingCancelled);
+}
+
+QSize KexiRelationWidget::sizeHint() const
+{
+ return m_relationView->sizeHint();
+}
+
+void KexiRelationWidget::slotTableHidden(KexiDB::TableSchema &table)
+{
+ const QString &t = table.name().lower();
+ int i;
+ for (i=0; i<m_tableCombo->count() && t > m_tableCombo->text(i).lower(); i++)
+ ;
+ m_tableCombo->insertItem(table.name(), i);
+ if (!m_tableCombo->isEnabled()) {
+ m_tableCombo->setCurrentItem(0);
+ m_tableCombo->setEnabled(true);
+ m_btnAdd->setEnabled(true);
+ }
+
+ emit tableHidden(table);
+}
+
+void KexiRelationWidget::aboutToShowPopupMenu()
+{
+/*! @todo what about query? */
+ if (m_relationView->focusedTableView() && m_relationView->focusedTableView()->schema()->table()) {
+ m_tableQueryPopup->changeTitle(m_tableQueryPopupTitleID, SmallIcon("table"),
+ QString(m_relationView->focusedTableView()->schema()->name()) + " : " + i18n("Table"));
+ }
+ else if (m_relationView->selectedConnection()) {
+ m_connectionPopup->changeTitle( m_connectionPopupTitleID,
+ m_relationView->selectedConnection()->toString() + " : " + i18n("Relationship") );
+ }
+}
+
+void
+KexiRelationWidget::slotTableFieldDoubleClicked(QListViewItem *i,const QPoint&,int)
+{
+ if (!sender()->isA("KexiRelationViewTable"))
+ return;
+ const KexiRelationViewTable* t = static_cast<const KexiRelationViewTable*>(sender());
+ const QStringList selectedFieldNames( t->selectedFieldNames() );
+ if (selectedFieldNames.count()==1)
+ emit tableFieldDoubleClicked( t->schema()->table(), selectedFieldNames.first() );
+}
+
+void
+KexiRelationWidget::clear()
+{
+ m_relationView->clear();
+ fillTablesCombo();
+}
+
+/*! Removes all coonections from the view. */
+void KexiRelationWidget::removeAllConnections()
+{
+ m_relationView->removeAllConnections();
+}
+
+void
+KexiRelationWidget::fillTablesCombo()
+{
+ m_tableCombo->clear();
+ QStringList tmp = m_conn->tableNames();
+ tmp.sort();
+ m_tableCombo->insertStringList(tmp);
+}
+
+void
+KexiRelationWidget::objectCreated(const QCString &mime, const QCString& name)
+{
+ if (mime=="kexi/table" || mime=="kexi/query") {
+//! @todo query?
+ m_tableCombo->insertItem(QString(name));
+ m_tableCombo->listBox()->sort();
+ }
+}
+
+void
+KexiRelationWidget::objectDeleted(const QCString &mime, const QCString& name)
+{
+ if (mime=="kexi/table" || mime=="kexi/query") {
+ QString strName(name);
+ for (int i=0; i<m_tableCombo->count(); i++) {
+//! @todo query?
+ if (m_tableCombo->text(i)==strName) {
+ m_tableCombo->removeItem(i);
+ if (m_tableCombo->currentItem()==i) {
+ if (i==(m_tableCombo->count()-1))
+ m_tableCombo->setCurrentItem(i-1);
+ else
+ m_tableCombo->setCurrentItem(i);
+ }
+ break;
+ }
+ }
+ }
+}
+
+void
+KexiRelationWidget::objectRenamed(const QCString &mime, const QCString& name, const QCString& newName)
+{
+ if (mime=="kexi/table" || mime=="kexi/query") {
+ QString strName(name);
+ for (int i=0; i<m_tableCombo->count(); i++) {
+//! @todo query?
+ if (m_tableCombo->text(i)==strName) {
+ m_tableCombo->changeItem(QString(newName), i);
+ m_tableCombo->listBox()->sort();
+ break;
+ }
+ }
+ }
+}
+
+void
+KexiRelationWidget::hideAllTablesExcept( KexiDB::TableSchema::List* tables )
+{
+ m_relationView->hideAllTablesExcept(tables);
+}
+
+#include "kexirelationwidget.moc"
diff --git a/kexi/widget/relations/kexirelationwidget.h b/kexi/widget/relations/kexirelationwidget.h
new file mode 100644
index 000000000..3beb0b349
--- /dev/null
+++ b/kexi/widget/relations/kexirelationwidget.h
@@ -0,0 +1,137 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIRELATIONWIDGET_H
+#define KEXIRELATIONWIDGET_H
+
+//#include <qwidget.h>
+//#include "kexiactionproxy.h"
+#include "kexiviewbase.h"
+#include "kexirelationview.h"
+
+class KComboBox;
+class KPushButton;
+class KPopupMenu;
+class KAction;
+class QListViewItem;
+
+class KexiMainWindow;
+
+namespace KexiDB
+{
+ class Connection;
+ class TableSchema;
+ class Reference;
+}
+
+class KEXIRELATIONSVIEW_EXPORT KexiRelationWidget : public KexiViewBase
+{
+ Q_OBJECT
+
+ public:
+ KexiRelationWidget(KexiMainWindow *win, QWidget *parent, const char *name=0);
+ virtual ~KexiRelationWidget();
+
+ //! \return a dictionary of added tables
+ TablesDict* tables() const;
+ KexiRelationViewTableContainer* table(const QString& name) const;
+ const ConnectionList* connections() const;
+
+// KexiRelationView *relationView() const { return m_relationView; }
+ void addTable(const QString& t);
+
+// void openTable(KexiDB::TableSchema* table, bool designMode);
+
+ virtual QSize sizeHint() const;
+
+ /*! Used to add newly created object information to the combo box. */
+ void objectCreated(const QCString &mime, const QCString& name);
+ void objectDeleted(const QCString &mime, const QCString& name);
+ void objectRenamed(const QCString &mime, const QCString& name, const QCString& newName);
+
+ signals:
+ void tableAdded(KexiDB::TableSchema& t);
+ void tableHidden(KexiDB::TableSchema& t);
+ void tablePositionChanged(KexiRelationViewTableContainer*);
+ void aboutConnectionRemove(KexiRelationViewConnection*);
+ void tableFieldDoubleClicked( KexiDB::TableSchema* table, const QString& fieldName );
+
+ public slots:
+ /*! Adds a table \a t to the area. This changes only visual representation.
+ If \a rect is valid, table widget rgeometry will be initialized.
+ */
+ void addTable(KexiDB::TableSchema *t, const QRect &rect = QRect());
+
+ //! Adds a connection \a con to the area. This changes only visual representation.
+ void addConnection(const SourceConnection& conn);
+
+ void removeSelectedObject();
+
+ /*! Removes all tables and coonections from the widget. */
+ void clear();
+
+ /*! Removes all coonections from the view. */
+ void removeAllConnections();
+
+ /*! Hides all tables except \a tables. */
+ void hideAllTablesExcept( KexiDB::TableSchema::List* tables );
+
+ protected slots:
+ void slotAddTable();
+ void tableViewGotFocus();
+ void connectionViewGotFocus();
+ void emptyAreaGotFocus();
+ void tableContextMenuRequest(const QPoint& pos);
+ void connectionContextMenuRequest(const QPoint& pos);
+ void emptyAreaContextMenuRequest( const QPoint& pos );
+ void openSelectedTable();
+ void designSelectedTable();
+ void slotTableHidden(KexiDB::TableSchema &table);
+ void aboutToShowPopupMenu();
+ void slotTableFieldDoubleClicked(QListViewItem *i,const QPoint&,int);
+
+ protected:
+ /*! executes popup menu at \a pos, or,
+ if \a pos not specified: at center of selected table view (if any selected),
+ or at center point of the relations view. */
+ void executePopup( QPoint pos = QPoint(-1,-1) );
+
+ //! Invalidates all actions availability.
+ void invalidateActions();
+
+ //! Fills table's combo box with all available table names.
+ void fillTablesCombo();
+
+ private:
+ KexiMainWindow *m_win;
+ KComboBox *m_tableCombo;
+ KPushButton *m_btnAdd;
+ KexiRelationView *m_relationView;
+ KexiDB::Connection *m_conn;
+
+ KPopupMenu *m_tableQueryPopup //over table/query
+ , *m_connectionPopup //over connection
+ , *m_areaPopup; //over outer area
+ KAction *m_openSelectedTableAction, *m_designSelectedTableAction;
+
+ int m_tableQueryPopupTitleID, m_connectionPopupTitleID;
+};
+
+#endif
diff --git a/kexi/widget/relations/r1.xpm b/kexi/widget/relations/r1.xpm
new file mode 100644
index 000000000..11b8dc664
--- /dev/null
+++ b/kexi/widget/relations/r1.xpm
@@ -0,0 +1,10 @@
+/* XPM */
+static const char * r1_xpm[] = {
+"2 5 2 1",
+" c None",
+". c #000000",
+" .",
+"..",
+" .",
+" .",
+" ."};
diff --git a/kexi/widget/relations/rn.xpm b/kexi/widget/relations/rn.xpm
new file mode 100644
index 000000000..d721e5f58
--- /dev/null
+++ b/kexi/widget/relations/rn.xpm
@@ -0,0 +1,9 @@
+/* XPM */
+static const char * rn_xpm[] = {
+"7 4 2 1",
+" c None",
+". c #000000",
+" .. .. ",
+". . .",
+". . .",
+" .. .. "};
diff --git a/kexi/widget/tableview/Makefile.am b/kexi/widget/tableview/Makefile.am
new file mode 100644
index 000000000..69bb400d5
--- /dev/null
+++ b/kexi/widget/tableview/Makefile.am
@@ -0,0 +1,49 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexidatatable.la
+libkexidatatable_la_SOURCES = kexidataawareobjectiface.cpp \
+ kexitableview.cpp kexitableview_p.cpp kexidatatableview.cpp \
+ kexicelleditorfactory.cpp kexitableedit.cpp \
+ kexitableviewheader.cpp kexitableitem.cpp kexitableviewdata.cpp \
+ kexidatetableedit.cpp kexitimetableedit.cpp kexidatetimetableedit.cpp \
+ kexiinputtableedit.cpp kexiblobtableedit.cpp kexibooltableedit.cpp \
+ kexicomboboxbase.cpp kexicomboboxtableedit.cpp kexicomboboxpopup.cpp \
+ kexidataawarepropertyset.cpp kexitextformatter.cpp
+
+noinst_HEADERS = kexitableview_p.h
+
+libkexidatatable_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved
+libkexidatatable_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la $(top_builddir)/kexi/widget/utils/libkexiguiutils.la \
+ $(top_builddir)/lib/koproperty/libkoproperty.la $(LIB_KDEUI)
+
+#TODO: remove libkexicore link when kexiutils arrive
+
+SUBDIRS = .
+
+
+# kde_appsdir Where your application's menu entry (.desktop) should go to.
+# kde_icondir Where your icon should go to - better use KDE_ICON.
+# kde_sounddir Where your sounds should go to.
+# kde_htmldir Where your docs should go to. (contains lang subdirs)
+# kde_datadir Where you install application data. (Use a subdir)
+# kde_locale Where translation files should go to. (contains lang subdirs)
+# kde_cgidir Where cgi-bin executables should go to.
+# kde_confdir Where config files should go to (system-wide ones with default values).
+# kde_mimedir Where mimetypes .desktop files should go to.
+# kde_servicesdir Where services .desktop files should go to.
+# kde_servicetypesdir Where servicetypes .desktop files should go to.
+# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON).
+# kde_wallpaperdir Where general wallpapers should go to.
+# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to.
+# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS.
+# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES.
+# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES.
+# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3).
+# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3).
+
+# set the include path for X, qt and KDE
+INCLUDES= -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi/kexidb \
+ -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore -I$(top_srcdir)/lib/koproperty/ $(all_includes)
+
+METASOURCES = AUTO
+
diff --git a/kexi/widget/tableview/autonumber.png b/kexi/widget/tableview/autonumber.png
new file mode 100644
index 000000000..23fff353c
--- /dev/null
+++ b/kexi/widget/tableview/autonumber.png
Binary files differ
diff --git a/kexi/widget/tableview/kexiblobtableedit.cpp b/kexi/widget/tableview/kexiblobtableedit.cpp
new file mode 100644
index 000000000..db0799a4e
--- /dev/null
+++ b/kexi/widget/tableview/kexiblobtableedit.cpp
@@ -0,0 +1,595 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexiblobtableedit.h"
+
+#include <stdlib.h>
+
+#include <qdatastream.h>
+#include <qfile.h>
+#include <qpopupmenu.h>
+#include <qtextedit.h>
+#include <qlayout.h>
+#include <qstatusbar.h>
+#include <qlabel.h>
+#include <qpixmap.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qtooltip.h>
+#include <qapplication.h>
+#include <qclipboard.h>
+#include <qbuffer.h>
+
+#include <kdebug.h>
+#include <ktempfile.h>
+#include <kmimetype.h>
+#include <kmimemagic.h>
+#include <kuserprofile.h>
+#include <kservice.h>
+#include <kprocess.h>
+#include <kopenwith.h>
+#include <kurl.h>
+#include <karrowbutton.h>
+#include <klocale.h>
+#include <kfiledialog.h>
+#include <kio/job.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+#include <kstdaccel.h>
+
+#include <kexiutils/utils.h>
+#include <widget/utils/kexidropdownbutton.h>
+#include <widget/utils/kexicontextmenuutils.h>
+
+//! @internal
+class KexiBlobTableEdit::Private
+{
+public:
+ Private()
+ : popup(0)
+ , readOnly(false)
+ , setValueInternalEnabled(true)
+ {
+ }
+
+ QByteArray value;
+ KexiDropDownButton *button;
+ QSize totalSize;
+ KexiImageContextMenu *popup;
+ bool readOnly : 1; //!< cached for slotUpdateActionsAvailabilityRequested()
+ bool setValueInternalEnabled : 1; //!< used to disable KexiBlobTableEdit::setValueInternal()
+};
+
+//======================================================
+
+KexiBlobTableEdit::KexiBlobTableEdit(KexiTableViewColumn &column, QWidget *parent)
+ : KexiTableEdit(column, parent)
+ , d ( new Private() )
+{
+ setName("KexiBlobTableEdit");
+// m_proc = 0;
+// m_content = 0;
+ m_hasFocusableWidget = false;
+ d->button = new KexiDropDownButton( parentWidget() /*usually a viewport*/ );
+ d->button->hide();
+ QToolTip::add(d->button, i18n("Click to show available actions for this cell"));
+
+ d->popup = new KexiImageContextMenu(this);
+ d->popup->installEventFilter(this);
+ if (column.columnInfo)
+ KexiImageContextMenu::updateTitle( d->popup, column.columnInfo->captionOrAliasOrName(),
+//! @todo pixmaplabel icon is hardcoded...
+ "pixmaplabel" );
+ d->button->setPopup( d->popup );
+
+ //force edit requested to start editing... (this will call slotUpdateActionsAvailabilityRequested())
+ //connect(d->popup, SIGNAL(aboutToShow()), this, SIGNAL(editRequested()));
+
+ connect(d->popup, SIGNAL(updateActionsAvailabilityRequested(bool&, bool&)),
+ this, SLOT(slotUpdateActionsAvailabilityRequested(bool&, bool&)));
+
+ connect(d->popup, SIGNAL(insertFromFileRequested(const KURL&)),
+ this, SLOT(handleInsertFromFileAction(const KURL&)));
+ connect(d->popup, SIGNAL(saveAsRequested(const QString&)),
+ this, SLOT(handleSaveAsAction(const QString&)));
+ connect(d->popup, SIGNAL(cutRequested()),
+ this, SLOT(handleCutAction()));
+ connect(d->popup, SIGNAL(copyRequested()),
+ this, SLOT(handleCopyAction()));
+ connect(d->popup, SIGNAL(pasteRequested()),
+ this, SLOT(handlePasteAction()));
+ connect(d->popup, SIGNAL(clearRequested()),
+ this, SLOT(clear()));
+ connect(d->popup, SIGNAL(showPropertiesRequested()),
+ this, SLOT(handleShowPropertiesAction()));
+}
+
+KexiBlobTableEdit::~KexiBlobTableEdit()
+{
+ delete d;
+#if 0
+ kdDebug() << "KexiBlobTableEdit: Cleaning up..." << endl;
+ if (m_tempFile) {
+ m_tempFile->unlink();
+ //todo
+ }
+ delete m_proc;
+ m_proc = 0;
+ kdDebug() << "KexiBlobTableEdit: Ready." << endl;
+#endif
+}
+
+//! initializes this editor with \a add value
+void KexiBlobTableEdit::setValueInternal(const QVariant& add, bool removeOld)
+{
+ if (!d->setValueInternalEnabled)
+ return;
+ if (removeOld)
+ d->value = add.toByteArray();
+ else //do not add "m_origValue" to "add" as this is QByteArray
+ d->value = m_origValue.toByteArray();
+
+#if 0 //todo?
+ QByteArray val = m_origValue.toByteArray();
+ kdDebug() << "KexiBlobTableEdit: Size of BLOB: " << val.size() << endl;
+ m_tempFile = new KTempFile();
+ m_tempFile->setAutoDelete(true);
+ kdDebug() << "KexiBlobTableEdit: Creating temporary file: " << m_tempFile->name() << endl;
+ m_tempFile->dataStream()->writeRawBytes(val.data(), val.size());
+ m_tempFile->close();
+ delete m_tempFile;
+ m_tempFile = 0;
+
+ KMimeMagicResult* mmr = KMimeMagic::self()->findFileType(m_tempFile->name());
+ kdDebug() << "KexiBlobTableEdit: Mimetype = " << mmr->mimeType() << endl;
+
+ setViewWidget( new QWidget(this) );
+#endif
+}
+
+bool KexiBlobTableEdit::valueIsNull()
+{
+//TODO
+ d->value.size();
+ return d->value.isEmpty();
+}
+
+bool KexiBlobTableEdit::valueIsEmpty()
+{
+//TODO
+ return d->value.isEmpty();
+}
+
+QVariant
+KexiBlobTableEdit::value()
+{
+ return d->value;
+#if 0
+ //todo
+// ok = true;
+
+ if(m_content && m_content->isModified())
+ {
+ return QVariant(m_content->text());
+ }
+ QByteArray value;
+ QFile f( m_tempFile->name() );
+ f.open(IO_ReadOnly);
+ QDataStream stream(&f);
+ char* data = (char*) malloc(f.size());
+ value.resize(f.size());
+ stream.readRawBytes(data, f.size());
+ value.duplicate(data, f.size());
+ free(data);
+ kdDebug() << "KexiBlobTableEdit: Size of BLOB: " << value.size() << endl;
+ return QVariant(value);
+#endif
+}
+
+void KexiBlobTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h )
+{
+// d->currentEditorWidth = w;
+ if (!d->readOnly && w > d->button->width())
+ w -= d->button->width();
+ p->drawRect(x, y, w, h);
+}
+
+void
+KexiBlobTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h )
+{
+ Q_UNUSED(focused);
+ Q_UNUSED(txt);
+ Q_UNUSED(align);
+
+//! @todo optimize: load to m_pixmap, downsize
+ QPixmap pixmap;
+ x = 0;
+ w -= 1; //a place for border
+ h -= 1; //a place for border
+ if (p && val.canCast(QVariant::ByteArray) && pixmap.loadFromData(val.toByteArray())) {
+ KexiUtils::drawPixmap( *p, 0/*lineWidth*/, QRect(x, y_offset, w, h),
+ pixmap, Qt::AlignCenter, true/*scaledContents*/, true/*keepAspectRatio*/);
+ }
+}
+
+bool KexiBlobTableEdit::cursorAtStart()
+{
+ return true;
+}
+
+bool KexiBlobTableEdit::cursorAtEnd()
+{
+ return true;
+}
+
+void KexiBlobTableEdit::handleInsertFromFileAction(const KURL& url)
+{
+ if (isReadOnly())
+ return;
+
+ QString fileName( url.isLocalFile() ? url.path() : url.prettyURL() );
+
+ //! @todo download the file if remote, then set fileName properly
+ QFile f(fileName);
+ if (!f.open(IO_ReadOnly)) {
+ //! @todo err msg
+ return;
+ }
+ QByteArray ba = f.readAll();
+ if (f.status()!=IO_Ok) {
+ //! @todo err msg
+ f.close();
+ return;
+ }
+ f.close();
+// m_valueMimeType = KImageIO::mimeType( fileName );
+ setValueInternal( ba, true );
+ signalEditRequested();
+ //emit acceptRequested();
+}
+
+void KexiBlobTableEdit::handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty)
+{
+ Q_UNUSED(origFilename);
+ Q_UNUSED(fileExtension);
+ dataIsEmpty = valueIsEmpty();
+//! @todo no fname stored for now
+}
+
+void KexiBlobTableEdit::handleSaveAsAction(const QString& fileName)
+{
+ QFile f(fileName);
+ if (!f.open(IO_WriteOnly)) {
+ //! @todo err msg
+ return;
+ }
+ f.writeBlock( d->value );
+ if (f.status()!=IO_Ok) {
+ //! @todo err msg
+ f.close();
+ return;
+ }
+ f.close();
+}
+
+void KexiBlobTableEdit::handleCutAction()
+{
+ if (isReadOnly())
+ return;
+ handleCopyAction();
+ clear();
+}
+
+void KexiBlobTableEdit::handleCopyAction()
+{
+ executeCopyAction(d->value);
+}
+
+void KexiBlobTableEdit::executeCopyAction(const QByteArray& data)
+{
+ QPixmap pixmap;
+ if (!pixmap.loadFromData(data))
+ return;
+ qApp->clipboard()->setPixmap(pixmap, QClipboard::Clipboard);
+}
+
+void KexiBlobTableEdit::handlePasteAction()
+{
+ if (isReadOnly())
+ return;
+ QPixmap pm( qApp->clipboard()->pixmap(QClipboard::Clipboard) );
+ QByteArray ba;
+ QBuffer buffer( ba );
+ buffer.open( IO_WriteOnly );
+ if (pm.save( &buffer, "PNG" )) {// write pixmap into ba in PNG format
+ setValueInternal( ba, true );
+ }
+ else {
+ setValueInternal( QByteArray(), true );
+ }
+ signalEditRequested();
+ //emit acceptRequested();
+ repaintRelatedCell();
+}
+
+void KexiBlobTableEdit::clear()
+{
+ setValueInternal( QByteArray(), true );
+ signalEditRequested();
+ //emit acceptRequested();
+ repaintRelatedCell();
+}
+
+void KexiBlobTableEdit::handleShowPropertiesAction()
+{
+ //! @todo
+}
+
+void KexiBlobTableEdit::showFocus( const QRect& r, bool readOnly )
+{
+ d->readOnly = readOnly; //cache for slotUpdateActionsAvailabilityRequested()
+// d->button->move( pos().x()+ width(), pos().y() );
+ updateFocus( r );
+// d->button->setEnabled(!readOnly);
+ if (d->readOnly)
+ d->button->hide();
+ else
+ d->button->show();
+}
+
+void KexiBlobTableEdit::resize(int w, int h)
+{
+ d->totalSize = QSize(w,h);
+ const int addWidth = d->readOnly ? 0 : d->button->width();
+ QWidget::resize(w - addWidth, h);
+ if (!d->readOnly)
+ d->button->resize( h, h );
+ m_rightMarginWhenFocused = m_rightMargin + addWidth;
+ QRect r( pos().x(), pos().y(), w+1, h+1 );
+ r.moveBy(m_scrollView->contentsX(),m_scrollView->contentsY());
+ updateFocus( r );
+//todo if (d->popup) {
+//todo d->popup->updateSize();
+//todo }
+}
+
+void KexiBlobTableEdit::updateFocus( const QRect& r )
+{
+ if (!d->readOnly) {
+ if (d->button->width() > r.width())
+ moveChild(d->button, r.right() + 1, r.top());
+ else
+ moveChild(d->button, r.right() - d->button->width(), r.top() );
+ }
+}
+
+void KexiBlobTableEdit::hideFocus()
+{
+ d->button->hide();
+}
+
+QSize KexiBlobTableEdit::totalSize() const
+{
+ return d->totalSize;
+}
+
+void KexiBlobTableEdit::slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly)
+{
+ emit editRequested();
+ valueIsNull = this->valueIsNull();
+ valueIsReadOnly = d->readOnly || isReadOnly();
+}
+
+void KexiBlobTableEdit::signalEditRequested()
+{
+ d->setValueInternalEnabled = false;
+ emit editRequested();
+ d->setValueInternalEnabled = true;
+}
+
+bool KexiBlobTableEdit::handleKeyPress( QKeyEvent* ke, bool editorActive )
+{
+ Q_UNUSED(editorActive);
+
+ const int k = ke->key();
+ KKey kkey(ke);
+ if (!d->readOnly) {
+ if ((ke->state()==Qt::NoButton && k==Qt::Key_F4)
+ || (ke->state()==Qt::AltButton && k==Qt::Key_Down)) {
+ d->button->animateClick();
+ QMouseEvent me( QEvent::MouseButtonPress, QPoint(2,2), Qt::LeftButton, Qt::NoButton );
+ QApplication::sendEvent( d->button, &me );
+ }
+ else if ((ke->state()==NoButton && (k==Qt::Key_F2 || k==Qt::Key_Space || k==Qt::Key_Enter || k==Qt::Key_Return))) {
+ d->popup->insertFromFile();
+ }
+ else
+ return false;
+ }
+ else
+ return false;
+ return true;
+}
+
+bool KexiBlobTableEdit::handleDoubleClick()
+{
+ d->popup->insertFromFile();
+ return true;
+}
+
+void KexiBlobTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue)
+{
+ Q_UNUSED(visibleValue);
+ executeCopyAction(value.toByteArray());
+}
+
+void KexiBlobTableEdit::handleAction(const QString& actionName)
+{
+ if (actionName=="edit_paste") {
+ d->popup->paste();
+ }
+ else if (actionName=="edit_cut") {
+ emit editRequested();
+ d->popup->cut();
+ }
+}
+
+bool KexiBlobTableEdit::eventFilter( QObject *o, QEvent *e )
+{
+ if (o == d->popup && e->type()==QEvent::KeyPress) {
+ QKeyEvent* ke = static_cast<QKeyEvent*>(e);
+ const int state = ke->state();
+ const int k = ke->key();
+ if ( (state==Qt::NoButton && (k==Qt::Key_Tab || k==Qt::Key_Left || k==Qt::Key_Right))
+ || (state==Qt::ShiftButton && k==Qt::Key_Backtab)
+ )
+ {
+ d->popup->hide();
+ QApplication::sendEvent( this, ke ); //re-send to move cursor
+ return true;
+ }
+ }
+ return false;
+}
+
+KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiBlobEditorFactoryItem, KexiBlobTableEdit)
+
+//=======================
+// KexiKIconTableEdit class is temporarily here:
+
+//! @internal
+class KexiKIconTableEdit::Private
+{
+public:
+ Private()
+ : pixmapCache(17, 17, false)
+ {
+ }
+ //! We've no editor widget that would store current value, so we do this here
+ QVariant currentValue;
+
+ QCache<QPixmap> pixmapCache;
+};
+
+KexiKIconTableEdit::KexiKIconTableEdit(KexiTableViewColumn &column, QWidget *parent)
+ : KexiTableEdit(column, parent)
+ , d( new Private() )
+{
+ setName("KexiKIconTableEdit");
+ init();
+}
+
+KexiKIconTableEdit::~KexiKIconTableEdit()
+{
+ delete d;
+}
+
+void KexiKIconTableEdit::init()
+{
+ m_hasFocusableWidget = false;
+ d->pixmapCache.setAutoDelete(true);
+}
+
+void KexiKIconTableEdit::setValueInternal(const QVariant& /*add*/, bool /*removeOld*/)
+{
+ d->currentValue = m_origValue;
+}
+
+bool KexiKIconTableEdit::valueIsNull()
+{
+ return d->currentValue.isNull();
+}
+
+bool KexiKIconTableEdit::valueIsEmpty()
+{
+ return d->currentValue.isNull();
+}
+
+QVariant KexiKIconTableEdit::value()
+{
+ return d->currentValue;
+}
+
+void KexiKIconTableEdit::clear()
+{
+ d->currentValue = QVariant();
+}
+
+bool KexiKIconTableEdit::cursorAtStart()
+{
+ return true;
+}
+
+bool KexiKIconTableEdit::cursorAtEnd()
+{
+ return true;
+}
+
+void KexiKIconTableEdit::setupContents( QPainter *p, bool /*focused*/, const QVariant& val,
+ QString &/*txt*/, int &/*align*/, int &/*x*/, int &y_offset, int &w, int &h )
+{
+ Q_UNUSED( y_offset );
+
+#if 0
+#ifdef Q_WS_WIN
+ y_offset = -1;
+#else
+ y_offset = 0;
+#endif
+ int s = QMAX(h - 5, 12);
+ s = QMIN( h-3, s );
+ s = QMIN( w-3, s );//avoid too large box
+ QRect r( QMAX( w/2 - s/2, 0 ) , h/2 - s/2 /*- 1*/, s, s);
+ p->setPen(QPen(colorGroup().text(), 1));
+ p->drawRect(r);
+ if (val.asBool()) {
+ p->drawLine(r.x(), r.y(), r.right(), r.bottom());
+ p->drawLine(r.x(), r.bottom(), r.right(), r.y());
+ }
+#endif
+
+ QString key = val.toString();
+ QPixmap *pix = 0;
+ if (!key.isEmpty() && !(pix = d->pixmapCache[ key ])) {
+ //cache pixmap
+ QPixmap pm = KGlobal::iconLoader()->loadIcon( key, KIcon::Small,
+ 0, KIcon::DefaultState, 0L, true/*canReturnNull*/ );
+ if (!pm.isNull()) {
+ pix = new QPixmap(pm);
+ d->pixmapCache.insert(key, pix);
+ }
+ }
+
+ if (p && pix) {
+ p->drawPixmap( (w-pix->width())/2, (h-pix->height())/2, *pix );
+ }
+}
+
+void KexiKIconTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue)
+{
+ Q_UNUSED(value);
+ Q_UNUSED(visibleValue);
+}
+
+KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiKIconTableEditorFactoryItem, KexiKIconTableEdit)
+
+#include "kexiblobtableedit.moc"
diff --git a/kexi/widget/tableview/kexiblobtableedit.h b/kexi/widget/tableview/kexiblobtableedit.h
new file mode 100644
index 000000000..a44559be5
--- /dev/null
+++ b/kexi/widget/tableview/kexiblobtableedit.h
@@ -0,0 +1,170 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _KEXIBLOBTABLEEDIT_H_
+#define _KEXIBLOBTABLEEDIT_H_
+
+#include <qcstring.h>
+#include <qcache.h>
+
+#include <kurl.h>
+
+#include "kexitableedit.h"
+#include "kexicelleditorfactory.h"
+
+class KTempFile;
+class KProcess;
+class QTextEdit;
+
+class KexiBlobTableEdit : public KexiTableEdit
+{
+ Q_OBJECT
+ public:
+ KexiBlobTableEdit(KexiTableViewColumn &column, QWidget *parent=0);
+ virtual ~KexiBlobTableEdit();
+
+ bool valueIsNull();
+ bool valueIsEmpty();
+
+ virtual QVariant value();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+
+ /*! Reimplemented: resizes a view(). */
+ virtual void resize(int w, int h);
+
+ virtual void showFocus( const QRect& r, bool readOnly );
+
+ virtual void hideFocus();
+
+ /*! \return total size of this editor, including popup button. */
+ virtual QSize totalSize() const;
+
+ virtual void paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h );
+
+ /*! Reimplemented to handle the key events. */
+ virtual bool handleKeyPress( QKeyEvent* ke, bool editorActive );
+
+ /*! Handles double click request coming from the table view.
+ \return true if it has been consumed.
+ Reimplemented in KexiBlobTableEdit (to execute "insert file" action. */
+ virtual bool handleDoubleClick();
+
+ /*! Handles action having standard name \a actionName.
+ Action could be: "edit_cut", "edit_paste", etc. */
+ virtual void handleAction(const QString& actionName);
+
+ /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate
+ for the editor's impementation, e.g. for image cell it can be a pixmap.
+ \a visibleValue is unused here. Reimplemented after KexiTableEdit. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue);
+
+ virtual void setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h );
+
+ protected slots:
+ void slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly);
+
+ void handleInsertFromFileAction(const KURL& url);
+ void handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty);
+ void handleSaveAsAction(const QString& fileName);
+ void handleCutAction();
+ void handleCopyAction();
+ void handlePasteAction();
+ virtual void clear();
+ void handleShowPropertiesAction();
+
+ protected:
+ //! initializes this editor with \a add value
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ //todo QString openWithDlg(const QString& file);
+ //todo void execute(const QString& app, const QString& file);
+
+ //! @internal
+ void updateFocus( const QRect& r );
+
+ void signalEditRequested();
+
+ //! @internal
+ void executeCopyAction(const QByteArray& data);
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ class Private;
+ Private *d;
+//todo KTempFile* m_tempFile;
+//todo KProcess* m_proc;
+//todo QTextEdit *m_content;
+};
+
+KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiBlobEditorFactoryItem)
+
+
+//=======================
+//This class is temporarily here:
+
+/*! @short Cell editor for displaying kde icon (using icon name provided as string).
+ Read only.
+*/
+class KexiKIconTableEdit : public KexiTableEdit
+{
+ public:
+ KexiKIconTableEdit(KexiTableViewColumn &column, QWidget *parent=0);
+
+ virtual ~KexiKIconTableEdit();
+
+ //! \return true if editor's value is null (not empty)
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not null).
+ //! Only few field types can accept "EMPTY" property
+ //! (check this with KexiDB::Field::hasEmptyProperty()),
+ virtual bool valueIsEmpty();
+
+ virtual QVariant value();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+
+ virtual void clear();
+
+ virtual void setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h );
+
+ /*! Handles copy action for value. Does nothing.
+ \a visibleValue is unused here. Reimplemented after KexiTableEdit. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue);
+
+ protected:
+ //! initializes this editor with \a add value
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ void showHintButton();
+ void init();
+
+ class Private;
+ Private *d;
+};
+
+KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiKIconTableEditorFactoryItem)
+
+#endif
diff --git a/kexi/widget/tableview/kexibooltableedit.cpp b/kexi/widget/tableview/kexibooltableedit.cpp
new file mode 100644
index 000000000..7b7bc0aca
--- /dev/null
+++ b/kexi/widget/tableview/kexibooltableedit.cpp
@@ -0,0 +1,180 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexibooltableedit.h"
+
+#include <kexidb/field.h>
+
+#include <qpainter.h>
+#include <qapplication.h>
+#include <qclipboard.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kglobalsettings.h>
+
+
+KexiBoolTableEdit::KexiBoolTableEdit(KexiTableViewColumn &column, QWidget *parent)
+ : KexiTableEdit(column, parent)
+{
+ setName("KexiBoolTableEdit");
+ kdDebug() << "KexiBoolTableEdit: m_origValue.typeName()==" << m_origValue.typeName() << endl;
+ kdDebug() << "KexiBoolTableEdit: type== " << field()->typeName() << endl;
+ m_hasFocusableWidget = false;
+ m_acceptEditorAfterDeleteContents = true;
+ m_usesSelectedTextColor = false;
+}
+
+KexiBoolTableEdit::~KexiBoolTableEdit()
+{
+}
+
+void KexiBoolTableEdit::setValueInternal(const QVariant& /*add*/, bool /*removeOld*/)
+{
+ m_currentValue = m_origValue;
+ //nothing to do more...
+}
+
+bool KexiBoolTableEdit::valueIsNull()
+{
+ return m_currentValue.isNull();
+}
+
+bool KexiBoolTableEdit::valueIsEmpty()
+{
+ return m_currentValue.isNull();
+}
+
+QVariant KexiBoolTableEdit::value()
+{
+// ok = true;
+ return m_currentValue;
+}
+
+void KexiBoolTableEdit::clear()
+{
+ if (field()->isNotNull())
+ m_currentValue = QVariant(false, 0);
+ else
+ m_currentValue = QVariant();
+}
+
+bool KexiBoolTableEdit::cursorAtStart()
+{
+ return true;
+}
+
+bool KexiBoolTableEdit::cursorAtEnd()
+{
+ return true;
+}
+
+void KexiBoolTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h )
+{
+ Q_UNUSED(focused);
+ Q_UNUSED(txt);
+ Q_UNUSED(align);
+ Q_UNUSED(x);
+#ifdef Q_WS_WIN
+// x = 1;
+ y_offset = -1;
+#else
+// x = 1;
+ y_offset = 0;
+#endif
+ if (p) {
+ int s = QMAX(h - 5, 12);
+ s = QMIN( h-3, s );
+ s = QMIN( w-3, s );//avoid too large box
+ QRect r( QMAX( w/2 - s/2, 0 ) , h/2 - s/2 /*- 1*/, s, s);
+//already set ouotside: p->setPen(QPen(colorGroup().text(), 1));
+ p->drawRect(r);
+ if (val.isNull()) { // && !field()->isNotNull()) {
+ p->drawText( r, Qt::AlignCenter, "?" );
+ }
+ else if (val.toBool()) {
+ p->drawLine(r.x(), r.y(), r.right(), r.bottom());
+ p->drawLine(r.x(), r.bottom(), r.right(), r.y());
+ }
+ }
+}
+
+void KexiBoolTableEdit::clickedOnContents()
+{
+ if (field()->isNotNull())
+ m_currentValue = QVariant( !m_currentValue.toBool(), 0 );
+ else {
+ // null allowed: use the cycle: true -> false -> null
+ if (m_currentValue.isNull())
+ m_currentValue = QVariant( true, 1 );
+ else
+ m_currentValue = m_currentValue.toBool() ? QVariant( false, 1 ) : QVariant();
+ }
+}
+
+void KexiBoolTableEdit::handleAction(const QString& actionName)
+{
+ if (actionName=="edit_paste") {
+ emit editRequested();
+ bool ok;
+ const int value = qApp->clipboard()->text( QClipboard::Clipboard ).toInt(&ok);
+ if (ok) {
+ m_currentValue = (value==0) ? QVariant(false, 0) : QVariant(true, 1);
+ }
+ else {
+ m_currentValue = field()->isNotNull()
+ ? QVariant(0, false)/*0 instead of NULL - handle case when null is not allowed*/
+ : QVariant();
+ }
+ repaintRelatedCell();
+ }
+ else if (actionName=="edit_cut") {
+ emit editRequested();
+//! @todo handle defaultValue...
+ m_currentValue = field()->isNotNull()
+ ? QVariant(0, false)/*0 instead of NULL - handle case when null is not allowed*/
+ : QVariant();
+ handleCopyAction(m_origValue, QVariant());
+ repaintRelatedCell();
+ }
+}
+
+void KexiBoolTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue)
+{
+ Q_UNUSED(visibleValue);
+ if (value.type()==QVariant::Bool)
+ qApp->clipboard()->setText(value.toBool() ? "1" : "0");
+ else
+ qApp->clipboard()->setText(QString::null);
+}
+
+int KexiBoolTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm )
+{
+ Q_UNUSED(fm);
+ return val.toPixmap().width();
+}
+
+//======================================================
+
+KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiBoolEditorFactoryItem, KexiBoolTableEdit)
+
+#include "kexibooltableedit.moc"
+
diff --git a/kexi/widget/tableview/kexibooltableedit.h b/kexi/widget/tableview/kexibooltableedit.h
new file mode 100644
index 000000000..3320a573f
--- /dev/null
+++ b/kexi/widget/tableview/kexibooltableedit.h
@@ -0,0 +1,87 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIBOOLTABLEEDIT_H
+#define KEXIBOOLTABLEEDIT_H
+
+#include <qvariant.h>
+
+#include "kexitableedit.h"
+#include "kexicelleditorfactory.h"
+
+/*! @short Cell editor for boolean type.
+*/
+class KexiBoolTableEdit : public KexiTableEdit
+{
+ Q_OBJECT
+
+ public:
+ KexiBoolTableEdit(KexiTableViewColumn &column, QWidget *parent=0);
+
+ virtual ~KexiBoolTableEdit();
+
+ //! \return true if editor's value is null (not empty)
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not null).
+ //! Only few field types can accept "EMPTY" property
+ //! (check this with KexiDB::Field::hasEmptyProperty()),
+ virtual bool valueIsEmpty();
+
+ virtual QVariant value();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+
+ virtual void clear();
+
+ virtual void setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h );
+
+ virtual void clickedOnContents();
+
+ /*! Handles action having standard name \a actionName.
+ Action could be: "edit_cut", "edit_paste", etc. */
+ virtual void handleAction(const QString& actionName);
+
+ /*! Handles copy action for value. Copies empty string for null, "1" for true, "0" for false.
+ \a visibleValue is unused here. Reimplemented after KexiTableEdit. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue);
+
+ /*! \return width of \a value. Reimplemented after KexiTableEdit. */
+ virtual int widthForValue( QVariant &val, const QFontMetrics &fm );
+
+ protected slots:
+
+ protected:
+ //! initializes this editor with \a add value
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ void showHintButton();
+
+ //! We've no editor widget that would store current value, so we do this here
+ QVariant m_currentValue;
+
+ signals:
+ void hintClicked();
+};
+
+KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiBoolEditorFactoryItem)
+
+#endif
diff --git a/kexi/widget/tableview/kexicelleditorfactory.cpp b/kexi/widget/tableview/kexicelleditorfactory.cpp
new file mode 100644
index 000000000..a20eac071
--- /dev/null
+++ b/kexi/widget/tableview/kexicelleditorfactory.cpp
@@ -0,0 +1,198 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicelleditorfactory.h"
+
+#include <qptrdict.h>
+#include <qintdict.h>
+#include <kstaticdeleter.h>
+
+#include <kexidb/indexschema.h>
+#include <kexidb/tableschema.h>
+#include "kexitableviewdata.h"
+#include "kexidatetableedit.h"
+#include "kexitimetableedit.h"
+#include "kexidatetimetableedit.h"
+#include "kexitableedit.h"
+#include "kexiinputtableedit.h"
+#include "kexicomboboxtableedit.h"
+#include "kexiblobtableedit.h"
+#include "kexibooltableedit.h"
+
+//============= KexiCellEditorFactoryItem ============
+
+KexiCellEditorFactoryItem::KexiCellEditorFactoryItem()
+{
+}
+
+KexiCellEditorFactoryItem::~KexiCellEditorFactoryItem()
+{
+}
+
+//============= KexiCellEditorFactoryPrivate ============
+
+//! @internal
+class KexiCellEditorFactoryPrivate
+{
+ public:
+ KexiCellEditorFactoryPrivate()
+ : items(101)
+ , items_by_type(101, false)
+ {
+ items.setAutoDelete( true );
+ items_by_type.setAutoDelete( false );
+ }
+ ~KexiCellEditorFactoryPrivate() {}
+
+ QString key(uint type, const QString& subType) const
+ {
+ QString key = QString::number(type);
+ if (!subType.isEmpty())
+ key += (QString(" ") + subType);
+ return key;
+ }
+
+ void registerItem( KexiCellEditorFactoryItem& item, uint type, const QString& subType = QString::null )
+ {
+ if (!items[ &item ])
+ items.insert( &item, &item );
+
+ items_by_type.insert( key(type, subType), &item );
+ }
+
+ KexiCellEditorFactoryItem *findItem(uint type, const QString& subType)
+ {
+ KexiCellEditorFactoryItem *item = items_by_type[ key(type, subType) ];
+ if (item)
+ return item;
+ item = items_by_type[ key(type, QString::null) ];
+ if (item)
+ return item;
+ return items_by_type[ key( KexiDB::Field::InvalidType, QString::null ) ];
+ }
+
+ QPtrDict<KexiCellEditorFactoryItem> items; //!< list of editor factory items (for later destroy)
+
+ QDict<KexiCellEditorFactoryItem> items_by_type; //!< editor factory items accessed by a key
+};
+
+static KStaticDeleter<KexiCellEditorFactoryPrivate> KexiCellEditorFactory_deleter;
+static KexiCellEditorFactoryPrivate *KexiCellEditorFactory_static = 0;
+
+//============= KexiCellEditorFactory ============
+
+KexiCellEditorFactory::KexiCellEditorFactory()
+{
+}
+
+KexiCellEditorFactory::~KexiCellEditorFactory()
+{
+}
+
+
+// Initializes standard editor cell editor factories
+void KexiCellEditorFactory::init()
+{
+ if (KexiCellEditorFactory_static)
+ return;
+ KexiCellEditorFactory_deleter.setObject(KexiCellEditorFactory_static, new KexiCellEditorFactoryPrivate());
+
+ KexiCellEditorFactory_static->registerItem( *new KexiBlobEditorFactoryItem(), KexiDB::Field::BLOB );
+ KexiCellEditorFactory_static->registerItem( *new KexiDateEditorFactoryItem(), KexiDB::Field::Date );
+ KexiCellEditorFactory_static->registerItem( *new KexiTimeEditorFactoryItem(), KexiDB::Field::Time );
+ KexiCellEditorFactory_static->registerItem( *new KexiDateTimeEditorFactoryItem(), KexiDB::Field::DateTime );
+ KexiCellEditorFactory_static->registerItem( *new KexiComboBoxEditorFactoryItem(), KexiDB::Field::Enum );
+ KexiCellEditorFactory_static->registerItem( *new KexiBoolEditorFactoryItem(), KexiDB::Field::Boolean );
+ KexiCellEditorFactory_static->registerItem( *new KexiKIconTableEditorFactoryItem(), KexiDB::Field::Text, "KIcon" );
+ //default type
+ KexiCellEditorFactory_static->registerItem( *new KexiInputEditorFactoryItem(), KexiDB::Field::InvalidType );
+}
+
+void KexiCellEditorFactory::registerItem( KexiCellEditorFactoryItem& item, uint type, const QString& subType )
+{
+ init();
+ KexiCellEditorFactory_static->registerItem( item, type, subType );
+}
+
+static bool hasEnumType( const KexiTableViewColumn &column )
+{
+ /*not db-aware case*/
+ if (column.relatedData())
+ return true;
+ /*db-aware case*/
+ if (!column.field() || !column.field()->table())
+ return false;
+ KexiDB::LookupFieldSchema *lookupFieldSchema = column.field()->table()->lookupFieldSchema( *column.field() );
+ if (!lookupFieldSchema)
+ return false;
+ if (lookupFieldSchema->rowSource().name().isEmpty())
+ return false;
+ return true;
+}
+
+KexiTableEdit* KexiCellEditorFactory::createEditor(KexiTableViewColumn &column, QWidget* parent)
+{
+ init();
+ KexiDB::Field *realField;
+ if (column.visibleLookupColumnInfo) {
+ realField = column.visibleLookupColumnInfo->field;
+ }
+ else {
+ realField = column.field();
+ }
+
+ KexiCellEditorFactoryItem *item = 0;
+
+ if (hasEnumType(column)) {
+ //--we need to create combo box because of relationship:
+ item = KexiCellEditorFactory::item( KexiDB::Field::Enum );
+ }
+ else {
+ item = KexiCellEditorFactory::item( realField->type(), realField->subType() );
+ }
+
+#if 0 //js: TODO LATER
+ //--check if we need to create combo box because of relationship:
+ //WARNING: it's assumed that indices are one-field long
+ KexiDB::TableSchema *table = f.table();
+ if (table) {
+ //find index that contain this field
+ KexiDB::IndexSchema::ListIterator it = table->indicesIterator();
+ for (;it.current();++it) {
+ KexiDB::IndexSchema *idx = it.current();
+ if (idx->fields()->findRef(&f)!=-1) {
+ //find details-side rel. for this index
+ KexiDB::Relationship *rel = idx->detailsRelationships()->first();
+ if (rel) {
+
+ }
+ }
+ }
+ }
+#endif
+
+ return item->createEditor(column, parent);
+}
+
+KexiCellEditorFactoryItem* KexiCellEditorFactory::item( uint type, const QString& subType )
+{
+ init();
+ return KexiCellEditorFactory_static->findItem(type, subType);
+}
+
diff --git a/kexi/widget/tableview/kexicelleditorfactory.h b/kexi/widget/tableview/kexicelleditorfactory.h
new file mode 100644
index 000000000..1b68cb8d4
--- /dev/null
+++ b/kexi/widget/tableview/kexicelleditorfactory.h
@@ -0,0 +1,79 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXICELLEDITORFACTORY_H
+#define KEXICELLEDITORFACTORY_H
+
+#include <qvariant.h>
+#include <qwidget.h>
+
+#include <kexidb/field.h>
+
+class KexiCellEditorFactoryItem;
+class KexiTableEdit;
+class KexiTableViewColumn;
+
+//! A singleton class providing access to cell editor factories
+class KEXIDATATABLE_EXPORT KexiCellEditorFactory
+{
+ public:
+ KexiCellEditorFactory();
+ virtual ~KexiCellEditorFactory();
+
+ /*! Registers factory item for \a type and (optional) \a subType.
+ \a subType is usually obtained (e.g. in KexiTableView) from KexiDB::Field::subType().
+ Passing KexiDB::Field::Invalid as type will set default item,
+ i.e. the one that will be used when no other item is defined for given data type.
+ You can register the same \a item many times for different types and subtypes.
+ Once registered, \a item object will be owned by the factory, so you shouldn't
+ care about deleting it. */
+ static void registerItem( KexiCellEditorFactoryItem& item, uint type,
+ const QString& subType = QString::null );
+
+ /*! \return item for \a type and (optional) \a subType.
+ If no item found, the one with empty subtype is tried.
+ If still no item found, the default is tried. Eventually, may return NULL. */
+ static KexiCellEditorFactoryItem* item( uint type, const QString& subType = QString::null );
+
+// static KexiTableEdit* createEditor(KexiDB::Field &f, QScrollView* parent = 0);
+ /*! Creates a new editor for \a column. If \a parent is of QScrollView, the new editor
+ will be created inside parent->viewport() instead. */
+ static KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0);
+
+ protected:
+ static void init();
+};
+
+//! A base class for implementing cell editor factories
+class KEXIDATATABLE_EXPORT KexiCellEditorFactoryItem
+{
+ public:
+ KexiCellEditorFactoryItem();
+ virtual ~KexiCellEditorFactoryItem();
+ QString className() { return m_className; }
+
+ protected:
+// virtual KexiTableEdit* createEditor(KexiDB::Field &f, QScrollView* parent = 0) = 0;
+ virtual KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0) = 0;
+
+ QString m_className;
+ friend class KexiCellEditorFactory;
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexicomboboxbase.cpp b/kexi/widget/tableview/kexicomboboxbase.cpp
new file mode 100644
index 000000000..2d6d52acb
--- /dev/null
+++ b/kexi/widget/tableview/kexicomboboxbase.cpp
@@ -0,0 +1,597 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <qlayout.h>
+#include <qstyle.h>
+#include <qwindowsstyle.h>
+#include <qpainter.h>
+
+#include "kexicomboboxbase.h"
+#include <widget/utils/kexicomboboxdropdownbutton.h>
+#include "kexicomboboxpopup.h"
+#include "kexitableview.h"
+#include "kexitableitem.h"
+#include "kexi.h"
+
+#include <klineedit.h>
+
+KexiComboBoxBase::KexiComboBoxBase()
+{
+ m_internalEditorValueChanged = false; //user has text or other value inside editor
+ m_slotInternalEditorValueChanged_enabled = true;
+ m_mouseBtnPressedWhenPopupVisible = false;
+ m_insideCreatePopup = false;
+ m_setValueOrTextInInternalEditor_enabled = true;
+ m_updatePopupSelectionOnShow = true;
+ m_moveCursorToEndInInternalEditor_enabled = true;
+ m_selectAllInInternalEditor_enabled = true;
+ m_setValueInInternalEditor_enabled = true;
+ m_setVisibleValueOnSetValueInternal = false;
+}
+
+KexiComboBoxBase::~KexiComboBoxBase()
+{
+}
+
+KexiDB::LookupFieldSchema *KexiComboBoxBase::lookupFieldSchema() const
+{
+ if (field() && field()->table()) {
+ KexiDB::LookupFieldSchema *lookupFieldSchema = field()->table()->lookupFieldSchema( *field() );
+ if (lookupFieldSchema && !lookupFieldSchema->rowSource().name().isEmpty())
+ return lookupFieldSchema;
+ }
+ return 0;
+}
+
+int KexiComboBoxBase::rowToHighlightForLookupTable() const
+{
+ if (!popup())
+ return -1;//err
+ KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+ if (!lookupFieldSchema)
+ return -1;
+ if (lookupFieldSchema->boundColumn()==-1)
+ return -1; //err
+ bool ok;
+ const int rowUid = origValue().toInt();
+//! @todo for now we're assuming the id is INTEGER
+ KexiTableViewData *tvData = popup()->tableView()->data();
+ const int boundColumn = lookupFieldSchema->boundColumn();
+ KexiTableViewData::Iterator it(tvData->iterator());
+ int row=0;
+ for (;it.current();++it, row++)
+ {
+ if (it.current()->at(boundColumn).toInt(&ok) == rowUid && ok || !ok)
+ break;
+ }
+ if (!ok || !it.current()) //item not found: highlight 1st row, if available
+ return -1;
+ return row;
+}
+
+void KexiComboBoxBase::setValueInternal(const QVariant& add_, bool removeOld)
+{
+ Q_UNUSED(removeOld);
+ m_mouseBtnPressedWhenPopupVisible = false;
+ m_updatePopupSelectionOnShow = true;
+ QString add(add_.toString());
+ if (add.isEmpty()) {
+ KexiTableViewData *relData = column() ? column()->relatedData() : 0;
+ QVariant valueToSet;
+ bool hasValueToSet = true;
+ int rowToHighlight = -1;
+ KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+ if (lookupFieldSchema) {
+ //use 'lookup field' model
+//! @todo support more RowSourceType's, not only table
+ if (lookupFieldSchema->boundColumn()==-1)
+//! @todo errmsg
+ return;
+ if (m_setVisibleValueOnSetValueInternal) {
+ //only for table views
+ if (!popup())
+ createPopup(false/*!show*/);
+ }
+ if (popup()) {
+ const int rowToHighlight = rowToHighlightForLookupTable();
+ popup()->tableView()->setHighlightedRow(rowToHighlight);
+
+ const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() );
+ if (m_setVisibleValueOnSetValueInternal && -1!=visibleColumn) {
+ //only for table views
+ KexiTableItem *it = popup()->tableView()->highlightedItem();
+ if (it)
+ valueToSet = it->at( visibleColumn );
+ }
+ else {
+ hasValueToSet = false;
+ }
+ }
+ }
+ else if (relData) {
+ //use 'related table data' model
+ valueToSet = valueForString(origValue().toString(), &rowToHighlight, 0, 1);
+ }
+ else {
+ //use 'enum hints' model
+ const int row = origValue().toInt();
+ valueToSet = field()->enumHint(row).stripWhiteSpace();
+ }
+ if (hasValueToSet)
+ setValueOrTextInInternalEditor( valueToSet );
+ /*impl.*/moveCursorToEndInInternalEditor();
+ /*impl.*/selectAllInInternalEditor();
+
+ if (popup()) {
+ if (origValue().isNull()) {
+ popup()->tableView()->clearSelection();
+ popup()->tableView()->setHighlightedRow(0);
+ } else {
+ if (relData) {
+ if (rowToHighlight!=-1)
+ popup()->tableView()->setHighlightedRow(rowToHighlight);
+ }
+ else if (!lookupFieldSchema) {
+ //popup()->tableView()->selectRow(origValue().toInt());
+ popup()->tableView()->setHighlightedRow(origValue().toInt());
+ }
+ }
+ }
+ }
+ else {
+ //todo: autocompl.?
+ if (popup())
+ popup()->tableView()->clearSelection();
+ /*impl.*/setValueInInternalEditor(add); //not setLineEditText(), because 'add' is entered by user!
+ //setLineEditText( add );
+ /*impl.*/moveCursorToEndInInternalEditor();
+ }
+}
+
+KexiTableItem* KexiComboBoxBase::selectItemForEnteredValueInLookupTable(const QVariant& v)
+{
+ KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+ if (!popup() || !lookupFieldSchema)
+ return 0; //safety
+//-not effective for large sets: please cache it!
+//.stripWhiteSpace() is not generic!
+
+ const bool valueIsText = v.type()==QVariant::String || v.type()==QVariant::CString; //most common case
+ const QString txt( valueIsText ? v.toString().stripWhiteSpace().lower() : QString::null );
+ KexiTableViewData *lookupData = popup()->tableView()->data();
+ const int visibleColumn = lookupFieldSchema->visibleColumn( lookupData->columnsCount() );
+ if (-1 == visibleColumn)
+ return 0;
+ KexiTableViewData::Iterator it(lookupData->iterator());
+ int row;
+ for (row = 0;it.current();++it, row++) {
+ if (valueIsText) {
+ if (it.current()->at(visibleColumn).toString().stripWhiteSpace().lower() == txt)
+ break;
+ }
+ else {
+ if (it.current()->at(visibleColumn) == v)
+ break;
+ }
+ }
+
+ m_setValueOrTextInInternalEditor_enabled = false; // <-- this is the entered value,
+ // so do not change the internal editor's contents
+ if (it.current())
+ popup()->tableView()->selectRow(row);
+ else
+ popup()->tableView()->clearSelection();
+
+ m_setValueOrTextInInternalEditor_enabled = true;
+
+ return it.current();
+}
+
+QString KexiComboBoxBase::valueForString(const QString& str, int* row,
+ uint lookInColumn, uint returnFromColumn, bool allowNulls)
+{
+ KexiTableViewData *relData = column() ? column()->relatedData() : 0;
+ if (!relData)
+ return QString::null; //safety
+ //use 'related table data' model
+ //-not effective for large sets: please cache it!
+ //.stripWhiteSpace() is not generic!
+
+ const QString txt = str.stripWhiteSpace().lower();
+ KexiTableViewData::Iterator it( relData->iterator() );
+ for (*row = 0;it.current();++it, (*row)++) {
+ if (it.current()->at(lookInColumn).toString().stripWhiteSpace().lower()==txt)
+ break;
+ }
+ if (it.current())
+ return it.current()->at(returnFromColumn).toString();
+
+ *row = -1;
+
+ if (column() && column()->relatedDataEditable())
+ return str; //new value entered and that's allowed
+
+ kexiwarn << "KexiComboBoxBase::valueForString(): no related row found, ID will be painted!" << endl;
+ if (allowNulls)
+ return QString::null;
+ return str; //for sanity but it's weird to show id to the user
+}
+
+QVariant KexiComboBoxBase::value()
+{
+ KexiTableViewData *relData = column() ? column()->relatedData() : 0;
+ KexiDB::LookupFieldSchema *lookupFieldSchema = 0;
+ if (relData) {
+ if (m_internalEditorValueChanged) {
+ //we've user-entered text: look for id
+//TODO: make error if matching text not found?
+ int rowToHighlight;
+ return valueForString(m_userEnteredValue.toString(), &rowToHighlight, 1, 0, true/*allowNulls*/);
+ }
+ else {
+ //use 'related table data' model
+ KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
+ return it ? it->at(0) : origValue();//QVariant();
+ }
+ }
+ else if ((lookupFieldSchema = this->lookupFieldSchema()))
+ {
+ if (lookupFieldSchema->boundColumn()==-1)
+ return origValue();
+ KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
+ if (/*!it &&*/ m_internalEditorValueChanged && !m_userEnteredValue.toString().isEmpty()) { //
+ //try to select a row using the user-entered text
+ if (!popup()) {
+ QVariant prevUserEnteredValue = m_userEnteredValue;
+ createPopup(false);
+ m_userEnteredValue = prevUserEnteredValue;
+ }
+ it = selectItemForEnteredValueInLookupTable( m_userEnteredValue );
+ }
+ return it ? it->at( lookupFieldSchema->boundColumn() ) : QVariant();
+ }
+ else if (popup()) {
+ //use 'enum hints' model
+ const int row = popup()->tableView()->currentRow();
+ if (row>=0)
+ return QVariant( row );
+ }
+
+ if (valueFromInternalEditor().toString().isEmpty())
+ return QVariant();
+/*! \todo don't return just 1st row, but use autocompletion feature
+ and: show message box if entered text does not match! */
+ return origValue(); //unchanged
+}
+
+QVariant KexiComboBoxBase::visibleValueForLookupField()
+{
+ KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+ if (!popup() || !lookupFieldSchema)
+ return QVariant();
+ const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() );
+ if (-1 == visibleColumn)
+ return QVariant();
+ KexiTableItem *it = popup()->tableView()->selectedItem();
+ return it ? it->at( QMIN( (uint)visibleColumn, it->count()-1)/*sanity*/ ) : QVariant();
+}
+
+QVariant KexiComboBoxBase::visibleValue()
+{
+ return m_visibleValue;
+}
+
+void KexiComboBoxBase::clear()
+{
+ if (popup())
+ popup()->hide();
+ slotInternalEditorValueChanged(QVariant());
+}
+
+tristate KexiComboBoxBase::valueChangedInternal()
+{
+ //avoid comparing values:
+ KexiTableViewData *relData = column() ? column()->relatedData() : 0;
+ KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+ if (relData || lookupFieldSchema) {
+ if (m_internalEditorValueChanged)
+ return true;
+
+ //use 'related table data' model
+ KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
+ if (!it)
+ return false;
+ }
+ else {
+ //use 'enum hints' model
+ const int row = popup() ? popup()->tableView()->currentRow() : -1;
+ if (row<0 && !m_internalEditorValueChanged/*true if text box is cleared*/)
+ return false;
+ }
+
+ return cancelled;
+}
+
+bool KexiComboBoxBase::valueIsNull()
+{
+// bool ok;
+ QVariant v( value() );
+ return v.isNull();
+// return !ok || v.isNull();
+}
+
+bool KexiComboBoxBase::valueIsEmpty()
+{
+ return valueIsNull();
+}
+
+void KexiComboBoxBase::showPopup()
+{
+ createPopup(true);
+}
+
+void KexiComboBoxBase::createPopup(bool show)
+{
+ if (!field())
+ return;
+ m_insideCreatePopup = true;
+ QWidget* thisWidget = dynamic_cast<QWidget*>(this);
+ QWidget *widgetToFocus = internalEditor() ? internalEditor() : thisWidget;
+ if (!popup()) {
+ setPopup( column() ? new KexiComboBoxPopup(thisWidget, *column())
+ : new KexiComboBoxPopup(thisWidget, *field()) );
+ QObject::connect(popup(), SIGNAL(rowAccepted(KexiTableItem*,int)),
+ thisWidget, SLOT(slotRowAccepted(KexiTableItem*,int)));
+ QObject::connect(popup()->tableView(), SIGNAL(itemSelected(KexiTableItem*)),
+ thisWidget, SLOT(slotItemSelected(KexiTableItem*)));
+
+ popup()->setFocusProxy( widgetToFocus );
+ popup()->tableView()->setFocusProxy( widgetToFocus );
+ popup()->installEventFilter(thisWidget);
+
+ if (origValue().isNull())
+ popup()->tableView()->clearSelection();
+ else {
+ popup()->tableView()->selectRow( 0 );
+ popup()->tableView()->setHighlightedRow( 0 );
+ }
+ }
+ if (show && internalEditor() && !internalEditor()->isVisible())
+ /*emit*/editRequested();
+
+ QPoint posMappedToGlobal = mapFromParentToGlobal(thisWidget->pos());
+ if (posMappedToGlobal != QPoint(-1,-1)) {
+//! todo alter the position to fit the popup within screen boundaries
+ popup()->move( posMappedToGlobal + QPoint(0, thisWidget->height()) );
+ //to avoid flickering: first resize to 0-height, then show and resize back to prev. height
+ const int w = popupWidthHint();
+ popup()->resize(w, 0);
+ if (show)
+ popup()->show();
+ popup()->updateSize(w);
+ if (m_updatePopupSelectionOnShow) {
+ int rowToHighlight = -1;
+ KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+ KexiTableViewData *relData = column() ? column()->relatedData() : 0;
+ if (lookupFieldSchema) {
+ rowToHighlight = rowToHighlightForLookupTable();
+ }
+ else if (relData) {
+ (void)valueForString(origValue().toString(), &rowToHighlight, 0, 1);
+ }
+ else //enum hint
+ rowToHighlight = origValue().toInt();
+
+/*-->*/ m_moveCursorToEndInInternalEditor_enabled = show;
+ m_selectAllInInternalEditor_enabled = show;
+ m_setValueInInternalEditor_enabled = show;
+ if (rowToHighlight==-1) {
+ rowToHighlight = QMAX( popup()->tableView()->highlightedRow(), 0);
+ setValueInInternalEditor(QVariant());
+ }
+ popup()->tableView()->selectRow( rowToHighlight );
+ popup()->tableView()->setHighlightedRow( rowToHighlight );
+ if (rowToHighlight < popup()->tableView()->rowsPerPage())
+ popup()->tableView()->ensureCellVisible( 0, -1 );
+
+/*-->*/ m_moveCursorToEndInInternalEditor_enabled = true;
+ m_selectAllInInternalEditor_enabled = true;
+ m_setValueInInternalEditor_enabled = true;
+ }
+ }
+
+ if (show) {
+ moveCursorToEndInInternalEditor();
+ selectAllInInternalEditor();
+ widgetToFocus->setFocus();
+ }
+ m_insideCreatePopup = false;
+}
+
+void KexiComboBoxBase::hide()
+{
+ if (popup())
+ popup()->hide();
+}
+
+void KexiComboBoxBase::slotRowAccepted(KexiTableItem * item, int row)
+{
+ Q_UNUSED(row);
+ //update our value
+ //..nothing to do?
+ updateButton();
+ slotItemSelected(item);
+ /*emit*/acceptRequested();
+}
+
+void KexiComboBoxBase::acceptPopupSelection()
+{
+ if (!popup())
+ return;
+ KexiTableItem *item = popup()->tableView()->highlightedItem();
+ if (item) {
+ popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
+ slotRowAccepted(item, -1);
+ }
+ popup()->hide();
+}
+
+void KexiComboBoxBase::slotItemSelected(KexiTableItem*)
+{
+ kexidbg << "KexiComboBoxBase::slotItemSelected(): m_visibleValue = " << m_visibleValue << endl;
+
+ QVariant valueToSet;
+ KexiTableViewData *relData = column() ? column()->relatedData() : 0;
+ KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+
+ m_visibleValue = lookupFieldSchema ? visibleValueForLookupField() : QVariant();
+
+ if (relData) {
+ //use 'related table data' model
+ KexiTableItem *item = popup()->tableView()->selectedItem();
+ if (item)
+ valueToSet = item->at(1);
+ }
+ else if (lookupFieldSchema) {
+ KexiTableItem *item = popup()->tableView()->selectedItem();
+ const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() );
+ if (item && visibleColumn!=-1 /* && (int)item->size() >= visibleColumn --already checked*/) {
+ valueToSet = item->at( QMIN( (uint)visibleColumn, item->count()-1)/*sanity*/ );
+ }
+ }
+ else {
+ //use 'enum hints' model
+ valueToSet = field()->enumHint( popup()->tableView()->currentRow() );
+ if (valueToSet.toString().isEmpty() && !m_insideCreatePopup) {
+ clear();
+ QWidget* thisWidget = dynamic_cast<QWidget*>(this);
+ thisWidget->parentWidget()->setFocus();
+ return;
+ }
+ }
+ setValueOrTextInInternalEditor( valueToSet );
+ if (m_setValueOrTextInInternalEditor_enabled) {
+ moveCursorToEndInInternalEditor();
+ selectAllInInternalEditor();
+ }
+ // a new (temp) popup table index is selected: do not update selection next time:
+ m_updatePopupSelectionOnShow = false;
+}
+
+void KexiComboBoxBase::slotInternalEditorValueChanged(const QVariant& v)
+{
+ if (!m_slotInternalEditorValueChanged_enabled)
+ return;
+ m_userEnteredValue = v;
+ m_internalEditorValueChanged = true;
+ if (v.toString().isEmpty()) {
+ if (popup()) {
+ popup()->tableView()->clearSelection();
+ }
+ return;
+ }
+}
+
+void KexiComboBoxBase::setValueOrTextInInternalEditor(const QVariant& value)
+{
+ if (!m_setValueOrTextInInternalEditor_enabled)
+ return;
+ setValueInInternalEditor( value );
+ //this text is not entered by hand:
+ m_userEnteredValue = QVariant();
+ m_internalEditorValueChanged = false;
+}
+
+bool KexiComboBoxBase::handleKeyPressForPopup( QKeyEvent *ke )
+{
+ const int k = ke->key();
+ int highlightedOrSelectedRow = popup() ? popup()->tableView()->highlightedRow() : -1;
+ if (popup() && highlightedOrSelectedRow < 0)
+ highlightedOrSelectedRow = popup()->tableView()->currentRow();
+
+ const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return;
+
+ // The editor may be active but the pull down menu not existant/visible,
+ // e.g. when the user has pressed a normal button to activate the editor
+ // Don't handle the event here in that case.
+ if (!popup() || (!enterPressed && !popup()->isVisible())) {
+ return false;
+ }
+
+ switch (k) {
+ case Qt::Key_Up:
+ popup()->tableView()->setHighlightedRow(
+ QMAX(highlightedOrSelectedRow-1, 0) );
+ updateTextForHighlightedRow();
+ return true;
+ case Qt::Key_Down:
+ popup()->tableView()->setHighlightedRow(
+ QMIN(highlightedOrSelectedRow+1, popup()->tableView()->rows()-1) );
+ updateTextForHighlightedRow();
+ return true;
+ case Qt::Key_PageUp:
+ popup()->tableView()->setHighlightedRow(
+ QMAX(highlightedOrSelectedRow-popup()->tableView()->rowsPerPage(), 0) );
+ updateTextForHighlightedRow();
+ return true;
+ case Qt::Key_PageDown:
+ popup()->tableView()->setHighlightedRow(
+ QMIN(highlightedOrSelectedRow+popup()->tableView()->rowsPerPage(),
+ popup()->tableView()->rows()-1) );
+ updateTextForHighlightedRow();
+ return true;
+ case Qt::Key_Home:
+ popup()->tableView()->setHighlightedRow( 0 );
+ updateTextForHighlightedRow();
+ return true;
+ case Qt::Key_End:
+ popup()->tableView()->setHighlightedRow( popup()->tableView()->rows()-1 );
+ updateTextForHighlightedRow();
+ return true;
+ case Qt::Key_Enter:
+ case Qt::Key_Return: //accept
+ //select row that is highlighted
+ if (popup()->tableView()->highlightedRow()>=0)
+ popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
+ //do not return true: allow to process event
+ default: ;
+ }
+ return false;
+}
+
+void KexiComboBoxBase::updateTextForHighlightedRow()
+{
+ KexiTableItem *item = popup() ? popup()->tableView()->highlightedItem() : 0;
+ if (item)
+ slotItemSelected(item);
+}
+
+void KexiComboBoxBase::undoChanges()
+{
+ KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+ if (lookupFieldSchema) {
+// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue BEFORE = " << m_visibleValue << endl;
+ if (popup())
+ popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
+ m_visibleValue = visibleValueForLookupField();
+// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue AFTER = " << m_visibleValue << endl;
+ setValueOrTextInInternalEditor( m_visibleValue );
+ }
+}
diff --git a/kexi/widget/tableview/kexicomboboxbase.h b/kexi/widget/tableview/kexicomboboxbase.h
new file mode 100644
index 000000000..1433ab0fa
--- /dev/null
+++ b/kexi/widget/tableview/kexicomboboxbase.h
@@ -0,0 +1,170 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _KEXICOMBOBOXBASE_H_
+#define _KEXICOMBOBOXBASE_H_
+
+#include "kexidb/field.h"
+#include "kexiinputtableedit.h"
+#include <kexidb/lookupfieldschema.h>
+
+class KPushButton;
+class KLineEdit;
+class KexiComboBoxPopup;
+class KexiTableItem;
+class KexiTableViewColumn;
+
+/*! @short A base class for handling data-aware combo boxes.
+ This class is used by KexiComboBoxTableEdit and KexiDBComboBox.
+*/
+class KEXIDATATABLE_EXPORT KexiComboBoxBase
+{
+ public:
+ KexiComboBoxBase();
+ virtual ~KexiComboBoxBase();
+
+ //! \return column related to this combo; for KexiComboBoxTableEdit 0 is returned here
+ virtual KexiTableViewColumn *column() const = 0;
+
+ //! \return database field related to this combo
+ virtual KexiDB::Field *field() const = 0;
+
+ //! \return the original value
+ virtual QVariant origValue() const = 0;
+
+ //! Note: Generally in current implementation this is integer > 0; may be null if no value is set
+ virtual QVariant value();
+
+ virtual QVariant visibleValue();
+
+ //! Reimplement this and call this impl.: used to clear internal editor
+ virtual void clear();
+
+ virtual tristate valueChangedInternal();
+ virtual bool valueIsNull();
+ virtual bool valueIsEmpty();
+
+ public:
+ virtual void hide();
+
+ void createPopup(bool show);
+
+ void showPopup();
+
+ //! Call this from slot
+ virtual void slotRowAccepted(KexiTableItem *item, int row);
+
+ //! Call this from slot
+ virtual void slotItemSelected(KexiTableItem*);
+
+ //! Call this from slot
+ void slotInternalEditorValueChanged(const QVariant &v);
+
+ //! Implement this to return the internal editor
+ virtual QWidget *internalEditor() const = 0;
+
+ protected:
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ //! Used to select row item for an user-entered value \a v.
+ //! Only for "lookup table" mode.
+ KexiTableItem* selectItemForEnteredValueInLookupTable(const QVariant& v);
+
+ /*! \return value from \a returnFromColumn related to \a str value from column \a lookInColumn.
+ If \a allowNulls is true, NULL is returend if no matched column found, else:
+ \a str is returned.
+ Example: lookInColumn=0, returnFromColumn=1 --returns user-visible string
+ for column #1 for id-column #0 */
+ QString valueForString(const QString& str, int* row, uint lookInColumn,
+ uint returnFromColumn, bool allowNulls = false);
+
+ //! sets \a value for the line edit without setting a flag (m_userEnteredValue) that indicates that
+ //! the text has been entered by hand (by a user)
+ void setValueOrTextInInternalEditor(const QVariant& value); //QString& text);
+
+ //! \return lookup field schema for this combo box, if present and if is valid (i.e. has defined row source)
+ KexiDB::LookupFieldSchema* lookupFieldSchema() const;
+
+ int rowToHighlightForLookupTable() const;
+
+ //! Implement this to perform "move cursor to end" in the internal editor
+ virtual void moveCursorToEndInInternalEditor() = 0;
+
+ //! Implement this to perform "select all" in the internal editor
+ virtual void selectAllInInternalEditor() = 0;
+
+ //! Implement this to perform "set value" in the internal editor
+ virtual void setValueInInternalEditor(const QVariant& value) = 0;
+
+ //! Implement this to return value from the internal editor
+ virtual QVariant valueFromInternalEditor() = 0;
+
+ //! Implement this as signal
+ virtual void editRequested() = 0;
+
+ //! Implement this as signal
+ virtual void acceptRequested() = 0;
+
+ //! Implement this to return a position \a pos mapped from parent (e.g. viewport)
+ //! to global coordinates. QPoint(-1, -1) should be returned if this cannot be computed.
+ virtual QPoint mapFromParentToGlobal(const QPoint& pos) const = 0;
+
+ //! Implement this to return a hint for popup width.
+ virtual int popupWidthHint() const = 0;
+
+ //! Implement this to update button state. Table view just updates on/off state
+ //! for the button depending on visibility of the popup
+ virtual void updateButton() {}
+
+ virtual KexiComboBoxPopup *popup() const = 0;
+ virtual void setPopup(KexiComboBoxPopup *popup) = 0;
+
+ virtual QVariant visibleValueForLookupField();
+
+ void updateTextForHighlightedRow();
+
+ bool handleKeyPressForPopup( QKeyEvent *ke );
+
+ void acceptPopupSelection();
+
+ //! Used by KexiDBComboBox.
+ void undoChanges();
+
+ QVariant m_visibleValue;
+
+ QVariant m_userEnteredValue; //!< value (usually a text) entered by hand (by the user)
+
+ bool m_internalEditorValueChanged : 1; //!< true if user has text or other value inside editor
+ bool m_slotInternalEditorValueChanged_enabled : 1; //!< Used in slotInternalEditorValueChanged()
+ bool m_setValueOrTextInInternalEditor_enabled : 1; //!< Used in setValueOrTextInInternalEditor() and slotItemSelected()
+ bool m_mouseBtnPressedWhenPopupVisible : 1; //!< Used only by KexiComboBoxTableEdit
+ bool m_insideCreatePopup : 1; //!< true if we're inside createPopup(); used in slotItemSelected()
+ bool m_updatePopupSelectionOnShow : 1; //!< Set to false as soon as the item corresponding with the current
+ //!< value is selected in the popup table. This avoids selecting item
+ //!< for origValue() and thus loosing the recent choice.
+ bool m_moveCursorToEndInInternalEditor_enabled : 1;
+ bool m_selectAllInInternalEditor_enabled : 1;
+ bool m_setValueInInternalEditor_enabled : 1;
+ bool m_setVisibleValueOnSetValueInternal : 1; //!< Used in setValueInternal() to control whether
+ //!< we want to set visible value on setValueInternal()
+ //!< - true for table view's combo box
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexicomboboxpopup.cpp b/kexi/widget/tableview/kexicomboboxpopup.cpp
new file mode 100644
index 000000000..5cd65d0d8
--- /dev/null
+++ b/kexi/widget/tableview/kexicomboboxpopup.cpp
@@ -0,0 +1,373 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexicomboboxpopup.h"
+
+#include "kexidatatableview.h"
+#include "kexitableview_p.h"
+#include "kexitableitem.h"
+#include "kexitableedit.h"
+
+#include <kexidb/lookupfieldschema.h>
+#include <kexidb/expression.h>
+#include <kexidb/parser/sqlparser.h>
+
+#include <kdebug.h>
+
+#include <qlayout.h>
+#include <qevent.h>
+
+/*! @internal
+ Helper for KexiComboBoxPopup. */
+class KexiComboBoxPopup_KexiTableView : public KexiDataTableView
+{
+ public:
+ KexiComboBoxPopup_KexiTableView(QWidget* parent=0)
+ : KexiDataTableView(parent, "KexiComboBoxPopup_tv")
+ {
+ init();
+ }
+ void init()
+ {
+ setReadOnly( true );
+ setLineWidth( 0 );
+ d->moveCursorOnMouseRelease = true;
+ KexiTableView::Appearance a(appearance());
+ a.navigatorEnabled = false;
+//! @todo add option for backgroundAltering??
+ a.backgroundAltering = false;
+ a.fullRowSelection = true;
+ a.rowHighlightingEnabled = true;
+ a.rowMouseOverHighlightingEnabled = true;
+ a.persistentSelections = false;
+ a.rowMouseOverHighlightingColor = colorGroup().highlight();
+ a.rowMouseOverHighlightingTextColor = colorGroup().highlightedText();
+ a.rowHighlightingTextColor = a.rowMouseOverHighlightingTextColor;
+ a.gridEnabled = false;
+ setAppearance(a);
+ setInsertingEnabled( false );
+ setSortingEnabled( false );
+ setVerticalHeaderVisible( false );
+ setHorizontalHeaderVisible( false );
+ setContextMenuEnabled( false );
+ setScrollbarToolTipsEnabled( false );
+ installEventFilter(this);
+ setBottomMarginInternal( - horizontalScrollBar()->sizeHint().height() );
+ }
+ virtual void setData( KexiTableViewData *data, bool owner = true )
+ { KexiTableView::setData( data, owner ); }
+ bool setData(KexiDB::Cursor *cursor)
+ { return KexiDataTableView::setData( cursor ); }
+};
+
+//========================================
+
+//! @internal
+class KexiComboBoxPopupPrivate
+{
+ public:
+ KexiComboBoxPopupPrivate()
+ : int_f(0)
+ , privateQuery(0)
+ {
+ max_rows = KexiComboBoxPopup::defaultMaxRows;
+ }
+ ~KexiComboBoxPopupPrivate() {
+ delete int_f;
+ delete privateQuery;
+ }
+
+ KexiComboBoxPopup_KexiTableView *tv;
+ KexiDB::Field *int_f; //TODO: remove this -temporary
+ KexiDB::QuerySchema* privateQuery;
+ int max_rows;
+};
+
+//========================================
+
+const int KexiComboBoxPopup::defaultMaxRows = 8;
+
+KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KexiTableViewColumn &column)
+ : QFrame( parent, "KexiComboBoxPopup", WType_Popup )
+{
+ init();
+ //setup tv data
+ setData(&column, 0);
+}
+
+KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KexiDB::Field &field)
+ : QFrame( parent, "KexiComboBoxPopup", WType_Popup )
+{
+ init();
+ //setup tv data
+ setData(0, &field);
+}
+
+KexiComboBoxPopup::~KexiComboBoxPopup()
+{
+ delete d;
+}
+
+void KexiComboBoxPopup::init()
+{
+ d = new KexiComboBoxPopupPrivate();
+ setPaletteBackgroundColor(palette().color(QPalette::Active,QColorGroup::Base));
+ setLineWidth( 1 );
+ setFrameStyle( Box | Plain );
+
+ d->tv = new KexiComboBoxPopup_KexiTableView(this);
+ installEventFilter(this);
+
+ connect(d->tv, SIGNAL(itemReturnPressed(KexiTableItem*,int,int)),
+ this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int)));
+
+ connect(d->tv, SIGNAL(itemMouseReleased(KexiTableItem*,int,int)),
+ this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int)));
+
+ connect(d->tv, SIGNAL(itemDblClicked(KexiTableItem*,int,int)),
+ this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int)));
+}
+
+void KexiComboBoxPopup::setData(KexiTableViewColumn *column, KexiDB::Field *field)
+{
+ if (column && !field)
+ field = column->field();
+ if (!field) {
+ kexiwarn << "KexiComboBoxPopup::setData(): !field" << endl;
+ return;
+ }
+
+ // case 1: simple related data
+ if (column && column->relatedData()) {
+ d->tv->setColumnStretchEnabled( true, -1 ); //only needed when using single column
+ setDataInternal( column->relatedData(), false /*!owner*/ );
+ return;
+ }
+ // case 2: lookup field
+ KexiDB::LookupFieldSchema *lookupFieldSchema = 0;
+ if (field->table())
+ lookupFieldSchema = field->table()->lookupFieldSchema( *field );
+ delete d->privateQuery;
+ d->privateQuery = 0;
+ if (lookupFieldSchema) {
+ const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() );
+ const bool multipleLookupColumnJoined = visibleColumns.count() > 1;
+//! @todo support more RowSourceType's, not only table and query
+ KexiDB::Cursor *cursor = 0;
+ switch (lookupFieldSchema->rowSource().type()) {
+ case KexiDB::LookupFieldSchema::RowSource::Table: {
+ KexiDB::TableSchema *lookupTable
+ = field->table()->connection()->tableSchema( lookupFieldSchema->rowSource().name() );
+ if (!lookupTable)
+//! @todo errmsg
+ return;
+ if (multipleLookupColumnJoined) {
+ kdDebug() << "--- Orig query: " << endl;
+ lookupTable->query()->debug();
+ d->privateQuery = new KexiDB::QuerySchema(*lookupTable->query());
+ }
+ else {
+ cursor = field->table()->connection()->prepareQuery( *lookupTable );
+ }
+ break;
+ }
+ case KexiDB::LookupFieldSchema::RowSource::Query: {
+ KexiDB::QuerySchema *lookupQuery
+ = field->table()->connection()->querySchema( lookupFieldSchema->rowSource().name() );
+ if (!lookupQuery)
+//! @todo errmsg
+ return;
+ if (multipleLookupColumnJoined) {
+ kdDebug() << "--- Orig query: " << endl;
+ lookupQuery->debug();
+ d->privateQuery = new KexiDB::QuerySchema(*lookupQuery);
+ }
+ else {
+ cursor = field->table()->connection()->prepareQuery( *lookupQuery );
+ }
+ break;
+ }
+ default:;
+ }
+ if (d->privateQuery) {
+ // append column computed using multiple columns
+ const KexiDB::QueryColumnInfo::Vector fieldsExpanded( d->privateQuery->fieldsExpanded() );
+ uint fieldsExpandedSize( fieldsExpanded.size() );
+ KexiDB::BaseExpr *expr = 0;
+ int count = visibleColumns.count();
+ for (QValueList<uint>::ConstIterator it( visibleColumns.at(count-1) ); count>0; count--, --it) {
+ KexiDB::QueryColumnInfo *ci = ((*it) < fieldsExpandedSize) ? fieldsExpanded.at( *it ) : 0;
+ if (!ci) {
+ kdWarning() << "KexiComboBoxPopup::setData(): " << *it << " >= fieldsExpandedSize" << endl;
+ continue;
+ }
+ KexiDB::VariableExpr *fieldExpr
+ = new KexiDB::VariableExpr( ci->field->table()->name()+"."+ci->field->name() );
+ fieldExpr->field = ci->field;
+ fieldExpr->tablePositionForField = d->privateQuery->tableBoundToColumn( *it );
+ if (expr) {
+//! @todo " " separator hardcoded...
+//! @todo use SQL sub-parser here...
+ KexiDB::ConstExpr *constExpr = new KexiDB::ConstExpr(CHARACTER_STRING_LITERAL, " ");
+ expr = new KexiDB::BinaryExpr(KexiDBExpr_Arithm, constExpr, CONCATENATION, expr);
+ expr = new KexiDB::BinaryExpr(KexiDBExpr_Arithm, fieldExpr, CONCATENATION, expr);
+ }
+ else
+ expr = fieldExpr;
+ }
+ expr->debug();
+ kdDebug() << expr->toString() << endl;
+
+ KexiDB::Field *f = new KexiDB::Field();
+ f->setExpression( expr );
+ d->privateQuery->addField( f );
+#if 0 //does not work yet
+// <remove later>
+//! @todo temp: improved display by hiding all columns except the computed one
+ const int numColumntoHide = d->privateQuery->fieldsExpanded().count() - 1;
+ for (int i=0; i < numColumntoHide; i++)
+ d->privateQuery->setColumnVisible(i, false);
+// </remove later>
+#endif
+//todo...
+ kdDebug() << "--- Private query: " << endl;
+ d->privateQuery->debug();
+ cursor = field->table()->connection()->prepareQuery( *d->privateQuery );
+ }
+ if (!cursor)
+//! @todo errmsg
+ return;
+
+ if (d->tv->data())
+ d->tv->data()->disconnect( this );
+ d->tv->setData( cursor );
+
+ connect( d->tv, SIGNAL(dataRefreshed()), this, SLOT(slotDataReloadRequested()));
+ updateSize();
+ return;
+ }
+
+ kdWarning() << "KexiComboBoxPopup::setData(KexiTableViewColumn &): no column relatedData \n - moving to setData(KexiDB::Field &)" << endl;
+
+ // case 3: enum hints
+ d->tv->setColumnStretchEnabled( true, -1 ); //only needed when using single column
+
+//! @todo THIS IS PRIMITIVE: we'd need to employ KexiDB::Reference here!
+ d->int_f = new KexiDB::Field(field->name(), KexiDB::Field::Text);
+ KexiTableViewData *data = new KexiTableViewData();
+ data->addColumn( new KexiTableViewColumn( *d->int_f ) );
+ QValueVector<QString> hints = field->enumHints();
+ for(uint i=0; i < hints.size(); i++) {
+ KexiTableItem *item = data->createItem();//new KexiTableItem(1);
+ (*item)[0]=QVariant(hints[i]);
+ kdDebug() << "added: '" << hints[i] <<"'"<<endl;
+ data->append( item );
+ }
+ setDataInternal( data, true );
+}
+
+void KexiComboBoxPopup::setDataInternal( KexiTableViewData *data, bool owner )
+{
+ if (d->tv->data())
+ d->tv->data()->disconnect( this );
+ d->tv->setData( data, owner );
+ connect( d->tv, SIGNAL(dataRefreshed()), this, SLOT(slotDataReloadRequested()));
+
+ updateSize();
+}
+
+void KexiComboBoxPopup::updateSize(int minWidth)
+{
+ const int rows = QMIN( d->max_rows, d->tv->rows() );
+
+ d->tv->adjustColumnWidthToContents(-1);
+
+ KexiTableEdit *te = dynamic_cast<KexiTableEdit*>(parentWidget());
+ const int width = QMAX( d->tv->tableSize().width(),
+ (te ? te->totalSize().width() : (parentWidget()?parentWidget()->width():0/*sanity*/)) );
+ kexidbg << "KexiComboBoxPopup::updateSize(): size=" << size() << endl;
+ resize( QMAX(minWidth, width)/*+(d->tv->columns()>1?2:0)*/ /*(d->updateSizeCalled?0:1)*/, d->tv->rowHeight() * rows +2 );
+ kexidbg << "KexiComboBoxPopup::updateSize(): size after=" << size() << endl;
+
+ //stretch the last column
+ d->tv->setColumnStretchEnabled(true, d->tv->columns()-1);
+}
+
+KexiTableView* KexiComboBoxPopup::tableView()
+{
+ return d->tv;
+}
+
+void KexiComboBoxPopup::resize( int w, int h )
+{
+ d->tv->horizontalScrollBar()->hide();
+ d->tv->verticalScrollBar()->hide();
+ d->tv->move(1,1);
+ d->tv->resize( w-2, h-2 );
+ QFrame::resize(w,h);
+ update();
+ updateGeometry();
+}
+
+void KexiComboBoxPopup::setMaxRows(int r)
+{
+ d->max_rows = r;
+}
+
+int KexiComboBoxPopup::maxRows() const
+{
+ return d->max_rows;
+}
+
+void KexiComboBoxPopup::slotTVItemAccepted(KexiTableItem *item, int row, int)
+{
+ hide();
+ emit rowAccepted(item, row);
+}
+
+bool KexiComboBoxPopup::eventFilter( QObject *o, QEvent *e )
+{
+ if (o==this && e->type()==QEvent::Hide) {
+ emit hidden();
+ }
+ else if (e->type()==QEvent::MouseButtonPress) {
+ kdDebug() << "QEvent::MousePress" << endl;
+ }
+ else if (o==d->tv) {
+ if (e->type()==QEvent::KeyPress) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ const int k = ke->key();
+ if ((ke->state()==NoButton && (k==Key_Escape || k==Key_F4))
+ || (ke->state()==AltButton && k==Key_Up))
+ {
+ hide();
+ emit cancelled();
+ return true;
+ }
+ }
+ }
+ return QFrame::eventFilter( o, e );
+}
+
+void KexiComboBoxPopup::slotDataReloadRequested()
+{
+ updateSize();
+}
+
+#include "kexicomboboxpopup.moc"
diff --git a/kexi/widget/tableview/kexicomboboxpopup.h b/kexi/widget/tableview/kexicomboboxpopup.h
new file mode 100644
index 000000000..42a154040
--- /dev/null
+++ b/kexi/widget/tableview/kexicomboboxpopup.h
@@ -0,0 +1,92 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXICOMBOBOXPOPUP_H
+#define KEXICOMBOBOXPOPUP_H
+
+#include <qframe.h>
+
+class KexiComboBoxPopupPrivate;
+class KexiTableView;
+class KexiTableViewData;
+class KexiTableViewColumn;
+class KexiTableItem;
+namespace KexiDB {
+ class Field;
+}
+
+//! Internal class for displaying popup table view
+class KexiComboBoxPopup : public QFrame
+{
+ Q_OBJECT
+ public:
+//js TODO: more ctors!
+ /*! Constructor for creating a popup using definition from \a column.
+ If the column is lookup column, it's definition is used to display
+ one or more column within the popup. Otherwise column.field() is used
+ to display single-column data. */
+ KexiComboBoxPopup(QWidget* parent, KexiTableViewColumn &column);
+
+ /*! Alternative constructor supporting lookup fields and enum hints. */
+ KexiComboBoxPopup(QWidget* parent, KexiDB::Field &field);
+
+ virtual ~KexiComboBoxPopup();
+
+ KexiTableView* tableView();
+
+ /*! Sets maximum number of rows for this popup. */
+ void setMaxRows(int r);
+
+ /*! \return maximum number of rows for this popup. */
+ int maxRows() const;
+
+ /*! Default maximum number of rows for KexiComboBoxPopup objects. */
+ static const int defaultMaxRows;
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ signals:
+ void rowAccepted(KexiTableItem *item, int row);
+ void cancelled();
+ void hidden();
+
+ public slots:
+ virtual void resize( int w, int h );
+ void updateSize(int minWidth = 0);
+
+ protected slots:
+ void slotTVItemAccepted(KexiTableItem *item, int row, int col);
+ void slotDataReloadRequested();
+
+ protected:
+ void init();
+ //! The main function for setting data; data can be set either by passing \a column or \a field.
+ //! The second case is used for lookup
+ void setData(KexiTableViewColumn *column, KexiDB::Field *field);
+
+ //! used by setData()
+ void setDataInternal( KexiTableViewData *data, bool owner = true ); //!< helper
+
+ KexiComboBoxPopupPrivate *d;
+
+ friend class KexiComboBoxTableEdit;
+};
+
+#endif
+
diff --git a/kexi/widget/tableview/kexicomboboxtableedit.cpp b/kexi/widget/tableview/kexicomboboxtableedit.cpp
new file mode 100644
index 000000000..75815a8ad
--- /dev/null
+++ b/kexi/widget/tableview/kexicomboboxtableedit.cpp
@@ -0,0 +1,446 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <qlayout.h>
+#include <qstyle.h>
+#include <qwindowsstyle.h>
+#include <qpainter.h>
+#include <qapplication.h>
+#include <qclipboard.h>
+
+#include "kexicomboboxtableedit.h"
+#include <widget/utils/kexicomboboxdropdownbutton.h>
+#include "kexicomboboxpopup.h"
+#include "kexitableview.h"
+#include "kexitableitem.h"
+#include "kexi.h"
+
+#include <klineedit.h>
+
+//! @internal
+class KexiComboBoxTableEdit::Private
+{
+public:
+ Private()
+ : popup(0)
+ , currentEditorWidth(0)
+ , visibleTableViewColumn(0)
+ , internalEditor(0)
+ {
+ }
+ ~Private()
+ {
+ delete internalEditor;
+ delete visibleTableViewColumn;
+ }
+
+ KPushButton *button;
+ KexiComboBoxPopup *popup;
+ int currentEditorWidth;
+ QSize totalSize;
+ KexiTableViewColumn* visibleTableViewColumn;
+ KexiTableEdit* internalEditor;
+};
+
+//======================================================
+
+KexiComboBoxTableEdit::KexiComboBoxTableEdit(KexiTableViewColumn &column, QWidget *parent)
+ : KexiInputTableEdit(column, parent)
+ , KexiComboBoxBase()
+ , d(new Private())
+{
+ setName("KexiComboBoxTableEdit");
+ m_setVisibleValueOnSetValueInternal = true;
+ d->button = new KexiComboBoxDropDownButton( parentWidget() /*usually a viewport*/ );
+ d->button->hide();
+ d->button->setFocusPolicy( NoFocus );
+ connect(d->button, SIGNAL(clicked()), this, SLOT(slotButtonClicked()));
+
+ connect(m_lineedit, SIGNAL(textChanged(const QString&)), this, SLOT(slotLineEditTextChanged(const QString&)));
+
+// m_lineedit = new KLineEdit(this, "lineedit");
+// m_lineedit->setFrame(false);
+// m_lineedit->setFrameStyle( QFrame::Plain | QFrame::Box );
+// m_lineedit->setLineWidth( 1 );
+// if (f.isNumericType()) {
+// m_lineedit->setAlignment(AlignRight);
+// }
+// setView( m_lineedit );
+
+// layout->addWidget(m_view);
+// m_combo->setEditable( true );
+// m_combo->clear();
+// m_combo->insertStringList(f.enumHints());
+// QStringList::ConstIterator it, end( f.enumHints().constEnd() );
+// for ( it = f.enumHints().constBegin(); it != end; ++it) {
+// if(!hints.at(i).isEmpty())
+// m_combo->insertItem(hints.at(i));
+// }
+
+//js: TODO
+//js static_cast<KComboBox*>(m_view)->insertStringList(list);
+//js static_cast<KComboBox*>(m_view)->setCurrentItem(static_cast<int>(t));
+}
+
+KexiComboBoxTableEdit::~KexiComboBoxTableEdit()
+{
+ delete d;
+}
+
+void KexiComboBoxTableEdit::createInternalEditor(KexiDB::QuerySchema& schema)
+{
+ if (!m_column->visibleLookupColumnInfo || d->visibleTableViewColumn/*sanity*/)
+ return;
+ const KexiDB::Field::Type t = m_column->visibleLookupColumnInfo->field->type();
+//! @todo subtype?
+ KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item(t);
+ if (!item || item->className()=="KexiInputTableEdit")
+ return; //unsupported type or there is no need to use subeditor for KexiInputTableEdit
+ //special cases: BLOB, Bool datatypes
+//todo
+ //find real type to display
+ KexiDB::QueryColumnInfo *ci = m_column->visibleLookupColumnInfo;
+ KexiDB::QueryColumnInfo *visibleLookupColumnInfo = 0;
+ if (ci->indexForVisibleLookupValue() != -1) {
+ //Lookup field is defined
+ visibleLookupColumnInfo = schema.expandedOrInternalField( ci->indexForVisibleLookupValue() );
+ }
+ d->visibleTableViewColumn = new KexiTableViewColumn(schema, *ci, visibleLookupColumnInfo);
+//! todo set d->internalEditor visible and use it to enable data entering by hand
+ d->internalEditor = KexiCellEditorFactory::createEditor(*d->visibleTableViewColumn, 0);
+ m_lineedit->hide();
+}
+
+KexiComboBoxPopup *KexiComboBoxTableEdit::popup() const
+{
+ return d->popup;
+}
+
+void KexiComboBoxTableEdit::setPopup(KexiComboBoxPopup *popup)
+{
+ d->popup = popup;
+}
+
+void KexiComboBoxTableEdit::showFocus( const QRect& r, bool readOnly )
+{
+// d->button->move( pos().x()+ width(), pos().y() );
+ updateFocus( r );
+ d->button->setEnabled(!readOnly);
+ if (readOnly)
+ d->button->hide();
+ else
+ d->button->show();
+}
+
+void KexiComboBoxTableEdit::resize(int w, int h)
+{
+ d->totalSize = QSize(w,h);
+ if (!column()->isReadOnly()) {
+ d->button->resize( h, h );
+ QWidget::resize(w - d->button->width(), h);
+ }
+ m_rightMarginWhenFocused = m_rightMargin + (column()->isReadOnly() ? 0 : d->button->width());
+ QRect r( pos().x(), pos().y(), w+1, h+1 );
+ if (m_scrollView)
+ r.moveBy(m_scrollView->contentsX(), m_scrollView->contentsY());
+ updateFocus( r );
+ if (popup()) {
+ popup()->updateSize();
+ }
+}
+
+// internal
+void KexiComboBoxTableEdit::updateFocus( const QRect& r )
+{
+ if (!column()->isReadOnly()) {
+ if (d->button->width() > r.width())
+ moveChild(d->button, r.right() + 1, r.top());
+ else
+ moveChild(d->button, r.right() - d->button->width(), r.top() );
+ }
+}
+
+void KexiComboBoxTableEdit::hideFocus()
+{
+ d->button->hide();
+}
+
+QVariant KexiComboBoxTableEdit::visibleValue()
+{
+ return KexiComboBoxBase::visibleValue();
+/* KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
+ if (!popup() || !lookupFieldSchema)
+ return QVariant();
+ KexiTableItem *it = popup()->tableView()->selectedItem();
+ return it ? it->at( lookupFieldSchema->visibleColumn() ) : QVariant();*/
+}
+
+void KexiComboBoxTableEdit::clear()
+{
+ m_lineedit->clear();
+ KexiComboBoxBase::clear();
+}
+
+bool KexiComboBoxTableEdit::valueChanged()
+{
+ const tristate res = valueChangedInternal();
+ if (~res) //no result: just compare values
+ return KexiInputTableEdit::valueChanged();
+ return res == true;
+}
+
+void KexiComboBoxTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h )
+{
+// d->currentEditorWidth = w;
+ if (!column()->isReadOnly()) {
+ if (w > d->button->width())
+ w -= d->button->width();
+ }
+ p->drawRect(x, y, w, h);
+}
+
+void KexiComboBoxTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h )
+{
+ if (d->internalEditor) {
+ d->internalEditor->setupContents( p, focused, val, txt, align, x, y_offset, w, h );
+ }
+ else {
+ KexiInputTableEdit::setupContents( p, focused, val, txt, align, x, y_offset, w, h );
+ }
+ if (!column()->isReadOnly() && focused && (w > d->button->width()))
+ w -= (d->button->width() - x);
+ if (!val.isNull()) {
+ KexiTableViewData *relData = column()->relatedData();
+ KexiDB::LookupFieldSchema *lookupFieldSchema = 0;
+ if (relData) {
+ int rowToHighlight;
+ txt = valueForString(val.toString(), &rowToHighlight, 0, 1);
+ }
+ else if ((lookupFieldSchema = this->lookupFieldSchema())) {
+ /* handled at at KexiTableView level
+ if (popup()) {
+ KexiTableItem *it = popup()->tableView()->selectedItem();
+ if (it && lookupFieldSchema->visibleColumn()!=-1 && (int)it->size() >= lookupFieldSchema->visibleColumn())
+ txt = it->at( lookupFieldSchema->visibleColumn() ).toString();
+ }*/
+ }
+ else {
+ //use 'enum hints' model
+ txt = field()->enumHint( val.toInt() );
+ }
+ }
+}
+
+void KexiComboBoxTableEdit::slotButtonClicked()
+{
+ // this method is sometimes called by hand:
+ // do not allow to simulate clicks when the button is disabled
+ if (column()->isReadOnly() || !d->button->isEnabled())
+ return;
+
+ if (m_mouseBtnPressedWhenPopupVisible) {
+ m_mouseBtnPressedWhenPopupVisible = false;
+ d->button->setOn(false);
+ return;
+ }
+ kdDebug() << "KexiComboBoxTableEdit::slotButtonClicked()" << endl;
+ if (!popup() || !popup()->isVisible()) {
+ kdDebug() << "SHOW POPUP" << endl;
+ showPopup();
+ d->button->setOn(true);
+ }
+}
+
+void KexiComboBoxTableEdit::slotPopupHidden()
+{
+ d->button->setOn(false);
+// d->currentEditorWidth = 0;
+}
+
+void KexiComboBoxTableEdit::updateButton()
+{
+ d->button->setOn(popup()->isVisible());
+}
+
+void KexiComboBoxTableEdit::hide()
+{
+ KexiInputTableEdit::hide();
+ KexiComboBoxBase::hide();
+ d->button->setOn(false);
+}
+
+void KexiComboBoxTableEdit::show()
+{
+ KexiInputTableEdit::show();
+ if (!column()->isReadOnly()) {
+ d->button->show();
+ }
+}
+
+bool KexiComboBoxTableEdit::handleKeyPress( QKeyEvent *ke, bool editorActive )
+{
+ const int k = ke->key();
+ if ((ke->state()==NoButton && k==Qt::Key_F4)
+ || (ke->state()==AltButton && k==Qt::Key_Down))
+ {
+ //show popup
+ slotButtonClicked();
+ return true;
+ }
+ else if (editorActive) {
+ const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return;
+ if (enterPressed && m_internalEditorValueChanged) {
+ createPopup(false);
+ selectItemForEnteredValueInLookupTable( m_userEnteredValue );
+ return false;
+ }
+
+ return handleKeyPressForPopup( ke );
+ }
+
+ return false;
+}
+
+void KexiComboBoxTableEdit::slotLineEditTextChanged(const QString& s)
+{
+ slotInternalEditorValueChanged(s);
+}
+
+int KexiComboBoxTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm )
+{
+ KexiTableViewData *relData = column() ? column()->relatedData() : 0;
+ if (lookupFieldSchema() || relData) {
+ // in 'lookupFieldSchema' or or 'related table data' model
+ // we're assuming val is already the text, not the index
+//! @todo ok?
+ return QMAX(KEXITV_MINIMUM_COLUMN_WIDTH, fm.width(val.toString()));
+ }
+ //use 'enum hints' model
+ QValueVector<QString> hints = field()->enumHints();
+ bool ok;
+ int idx = val.toInt(&ok);
+ if (!ok || idx < 0 || idx > int(hints.size()-1))
+ return KEXITV_MINIMUM_COLUMN_WIDTH;
+ QString txt = hints.at( idx, &ok );
+ if (!ok)
+ return KEXITV_MINIMUM_COLUMN_WIDTH;
+ return fm.width( txt );
+}
+
+bool KexiComboBoxTableEdit::eventFilter( QObject *o, QEvent *e )
+{
+ if (!column()->isReadOnly() && e->type()==QEvent::MouseButtonPress && m_scrollView) {
+ QPoint gp = static_cast<QMouseEvent*>(e)->globalPos()
+ + QPoint(m_scrollView->childX(d->button), m_scrollView->childY(d->button));
+ QRect r(d->button->mapToGlobal(d->button->geometry().topLeft()),
+ d->button->mapToGlobal(d->button->geometry().bottomRight()));
+ if (o==popup() && popup()->isVisible() && r.contains( gp )) {
+ m_mouseBtnPressedWhenPopupVisible = true;
+ }
+ }
+ return false;
+}
+
+QSize KexiComboBoxTableEdit::totalSize() const
+{
+ return d->totalSize;
+}
+
+QWidget *KexiComboBoxTableEdit::internalEditor() const
+{
+ return m_lineedit;
+}
+
+void KexiComboBoxTableEdit::moveCursorToEndInInternalEditor()
+{
+ moveCursorToEnd();
+}
+
+void KexiComboBoxTableEdit::selectAllInInternalEditor()
+{
+ selectAll();
+}
+
+void KexiComboBoxTableEdit::moveCursorToEnd()
+{
+ m_lineedit->end(false/*!mark*/);
+}
+
+void KexiComboBoxTableEdit::moveCursorToStart()
+{
+ m_lineedit->home(false/*!mark*/);
+}
+
+void KexiComboBoxTableEdit::selectAll()
+{
+ m_lineedit->selectAll();
+}
+
+void KexiComboBoxTableEdit::setValueInInternalEditor(const QVariant& value)
+{
+ m_lineedit->setText(value.toString());
+}
+
+QVariant KexiComboBoxTableEdit::valueFromInternalEditor()
+{
+ return m_lineedit->text();
+}
+
+QPoint KexiComboBoxTableEdit::mapFromParentToGlobal(const QPoint& pos) const
+{
+ KexiTableView *tv = dynamic_cast<KexiTableView*>(m_scrollView);
+ if (!tv)
+ return QPoint(-1,-1);
+ return tv->viewport()->mapToGlobal(pos);
+}
+
+int KexiComboBoxTableEdit::popupWidthHint() const
+{
+ return m_lineedit->width() + m_leftMargin + m_rightMarginWhenFocused; //QMAX(popup()->width(), d->currentEditorWidth);
+}
+
+void KexiComboBoxTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue)
+{
+ Q_UNUSED(value);
+//! @todo does not work with BLOBs!
+ qApp->clipboard()->setText( visibleValue.toString() );
+}
+
+void KexiComboBoxTableEdit::handleAction(const QString& actionName)
+{
+ const bool alreadyVisible = m_lineedit->isVisible();
+
+ if (actionName=="edit_paste") {
+ if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode
+ emit editRequested();
+ m_lineedit->clear();
+ }
+//! @todo does not work with BLOBs!
+ setValueInInternalEditor( qApp->clipboard()->text() );
+ }
+ else
+ KexiInputTableEdit::handleAction(actionName);
+}
+
+
+KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiComboBoxEditorFactoryItem, KexiComboBoxTableEdit)
+
+#include "kexicomboboxtableedit.moc"
diff --git a/kexi/widget/tableview/kexicomboboxtableedit.h b/kexi/widget/tableview/kexicomboboxtableedit.h
new file mode 100644
index 000000000..713fa55ea
--- /dev/null
+++ b/kexi/widget/tableview/kexicomboboxtableedit.h
@@ -0,0 +1,166 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _KEXICOMBOBOXTABLEEDIT_H_
+#define _KEXICOMBOBOXTABLEEDIT_H_
+
+#include "kexidb/field.h"
+#include "kexiinputtableedit.h"
+#include "kexicomboboxbase.h"
+#include <kexidb/lookupfieldschema.h>
+
+class KPushButton;
+class KLineEdit;
+class KexiComboBoxPopup;
+class KexiTableItem;
+class KexiTableViewColumn;
+
+/*! @short Drop-down cell editor.
+*/
+class KexiComboBoxTableEdit : public KexiInputTableEdit, public KexiComboBoxBase
+{
+ Q_OBJECT
+
+ public:
+ KexiComboBoxTableEdit(KexiTableViewColumn &column, QWidget *parent=0);
+ virtual ~KexiComboBoxTableEdit();
+
+ //! Implemented for KexiComboBoxBase
+ virtual KexiTableViewColumn *column() const { return m_column; }
+
+ //! Implemented for KexiComboBoxBase
+ virtual KexiDB::Field *field() const { return m_column->field(); }
+
+ //! Implemented for KexiComboBoxBase
+ virtual QVariant origValue() const { return m_origValue; }
+
+ virtual void setValueInternal(const QVariant& add, bool removeOld)
+ { KexiComboBoxBase::setValueInternal(add, removeOld); }
+
+ virtual QVariant value() { return KexiComboBoxBase::value(); }
+
+ virtual void clear();
+
+ virtual bool valueChanged();
+
+ virtual QVariant visibleValue();
+
+ /*! Reimplemented: resizes a view(). */
+ virtual void resize(int w, int h);
+
+ virtual void showFocus( const QRect& r, bool readOnly );
+
+ virtual void hideFocus();
+
+ virtual void paintFocusBorders( QPainter *p, QVariant &cal, int x, int y, int w, int h );
+
+ /*! Setups contents of the cell. As a special case, if there is lookup field schema
+ defined, \a val already contains the visible value (usually the text)
+ set by \ref KexiTableView::paintcell(), so there is noo need to lookup the value
+ in the combo box's popup. */
+ virtual void setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h );
+
+ /*! Used to handle key press events for the item. */
+ virtual bool handleKeyPress( QKeyEvent *ke, bool editorActive );
+
+ virtual int widthForValue( QVariant &val, const QFontMetrics &fm );
+
+ virtual void hide();
+ virtual void show();
+
+ /*! \return total size of this editor, including popup button. */
+ virtual QSize totalSize() const;
+
+ virtual void createInternalEditor(KexiDB::QuerySchema& schema);
+
+ /*! Reimplemented after KexiInputTableEdit. */
+ virtual void handleAction(const QString& actionName);
+
+ /*! Reimplemented after KexiInputTableEdit.
+ For a special case (combo box), \a visibleValue can be provided,
+ so it can be copied to the clipboard instead of unreadable \a value. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue);
+
+ public slots:
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToEnd();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToStart();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void selectAll();
+
+ protected slots:
+ void slotButtonClicked();
+ void slotRowAccepted(KexiTableItem *item, int row) { KexiComboBoxBase::slotRowAccepted(item, row); }
+ void slotItemSelected(KexiTableItem* item) { KexiComboBoxBase::slotItemSelected(item); }
+ void slotInternalEditorValueChanged(const QVariant& v)
+ { KexiComboBoxBase::slotInternalEditorValueChanged(v); }
+ void slotLineEditTextChanged(const QString& s);
+ void slotPopupHidden();
+
+ protected:
+ //! internal
+ void updateFocus( const QRect& r );
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ //! Implemented for KexiComboBoxBase
+ virtual QWidget *internalEditor() const;
+
+ //! Implemented for KexiComboBoxBase
+ virtual void moveCursorToEndInInternalEditor();
+
+ //! Implemented for KexiComboBoxBase
+ virtual void selectAllInInternalEditor();
+
+ //! Implemented for KexiComboBoxBase
+ virtual void setValueInInternalEditor(const QVariant& value);
+
+ //! Implemented for KexiComboBoxBase
+ virtual QVariant valueFromInternalEditor();
+
+ //! Implemented for KexiComboBoxBase
+ virtual void editRequested() { KexiInputTableEdit::editRequested(); }
+
+ //! Implemented for KexiComboBoxBase
+ virtual void acceptRequested() { KexiInputTableEdit::acceptRequested(); }
+
+ //! Implemented for KexiComboBoxBase
+ virtual QPoint mapFromParentToGlobal(const QPoint& pos) const;
+
+ //! Implemented for KexiComboBoxBase
+ virtual int popupWidthHint() const;
+
+ //! Implemented this to update button state.
+ virtual void updateButton();
+
+ virtual KexiComboBoxPopup *popup() const;
+ virtual void setPopup(KexiComboBoxPopup *popup);
+
+ class Private;
+ Private *d;
+};
+
+KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiComboBoxEditorFactoryItem)
+
+#endif
diff --git a/kexi/widget/tableview/kexidataawareobjectiface.cpp b/kexi/widget/tableview/kexidataawareobjectiface.cpp
new file mode 100644
index 000000000..59edbed36
--- /dev/null
+++ b/kexi/widget/tableview/kexidataawareobjectiface.cpp
@@ -0,0 +1,2108 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ Based on KexiTableView code.
+ Copyright (C) 2002 Till Busch <till@bux.at>
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidataawareobjectiface.h"
+
+#include <qscrollview.h>
+#include <qlabel.h>
+#include <qtooltip.h>
+
+#include <kmessagebox.h>
+
+#include <kexi.h>
+#include <kexiutils/validator.h>
+#include <widget/utils/kexirecordnavigator.h>
+#include <widget/utils/kexirecordmarker.h>
+#include <kexidb/roweditbuffer.h>
+#include <kexidataiteminterface.h>
+
+#include "kexitableviewheader.h"
+
+using namespace KexiUtils;
+
+KexiDataAwareObjectInterface::KexiDataAwareObjectInterface()
+{
+ m_data = 0;
+ m_itemIterator = 0;
+ m_readOnly = -1; //don't know
+ m_insertingEnabled = -1; //don't know
+ m_isSortingEnabled = true;
+ m_isFilteringEnabled = true;
+ m_deletionPolicy = AskDelete;
+ m_inside_acceptEditor = false;
+ m_acceptsRowEditAfterCellAccepting = false;
+ m_internal_acceptsRowEditAfterCellAccepting = false;
+ m_contentsMousePressEvent_dblClick = false;
+ m_navPanel = 0;
+ m_initDataContentsOnShow = false;
+ m_cursorPositionSetExplicityBeforeShow = false;
+ m_verticalHeader = 0;
+ m_horizontalHeader = 0;
+ m_insertItem = 0;
+// m_rowEditBuffer = 0;
+ m_spreadSheetMode = false;
+ m_dropsAtRowEnabled = false;
+ m_updateEntireRowWhenMovingToOtherRow = false;
+ m_dragIndicatorLine = -1;
+ m_emptyRowInsertingEnabled = false;
+ m_popupMenu = 0;
+ m_contextMenuEnabled = true;
+ m_rowWillBeDeleted = -1;
+ m_alsoUpdateNextRow = false;
+ m_verticalHeaderAlreadyAdded = false;
+ m_vScrollBarValueChanged_enabled = true;
+ m_scrollbarToolTipsEnabled = true;
+ m_scrollBarTipTimerCnt = 0;
+ m_scrollBarTip = 0;
+ m_recentSearchDirection = KexiSearchAndReplaceViewInterface::Options::DefaultSearchDirection;
+
+ // setup scrollbar tooltip and related members
+ m_scrollBarTip = new QLabel("",0, "vScrollBarToolTip",
+ Qt::WStyle_Customize |Qt::WStyle_NoBorder|Qt::WX11BypassWM|Qt::WStyle_StaysOnTop|Qt::WStyle_Tool);
+ m_scrollBarTip->setPalette(QToolTip::palette());
+ m_scrollBarTip->setMargin(2);
+ m_scrollBarTip->setIndent(0);
+ m_scrollBarTip->setAlignment(Qt::AlignCenter);
+ m_scrollBarTip->setFrameStyle( QFrame::Plain | QFrame::Box );
+ m_scrollBarTip->setLineWidth(1);
+
+ clearVariables();
+}
+
+KexiDataAwareObjectInterface::~KexiDataAwareObjectInterface()
+{
+ delete m_insertItem;
+// delete m_rowEditBuffer;
+ delete m_itemIterator;
+ delete m_scrollBarTip;
+ //we cannot delete m_data here... subclasses should do this
+}
+
+void KexiDataAwareObjectInterface::clearVariables()
+{
+ m_editor = 0;
+// m_rowEditBuffer = 0;
+ m_rowEditing = false;
+ m_newRowEditing = false;
+ m_curRow = -1;
+ m_curCol = -1;
+ m_currentItem = 0;
+}
+
+void KexiDataAwareObjectInterface::setData( KexiTableViewData *data, bool owner )
+{
+ const bool theSameData = m_data && m_data==data;
+ if (m_owner && m_data && m_data!=data/*don't destroy if it's the same*/) {
+ kexidbg << "KexiDataAwareObjectInterface::setData(): destroying old data (owned)" << endl;
+ delete m_itemIterator;
+ delete m_data; //destroy old data
+ m_data = 0;
+ m_itemIterator = 0;
+ }
+ m_owner = owner;
+ m_data = data;
+ if (m_data)
+ m_itemIterator = m_data->createIterator();
+
+ kdDebug(44021) << "KexiDataAwareObjectInterface::setData(): using shared data" << endl;
+ //add columns
+//OK?
+ clearColumnsInternal(false);
+ if (m_data) {
+ int i = 0;
+ for (KexiTableViewColumn::ListIterator it(m_data->columns);
+ it.current(); ++it, i++)
+ {
+ KexiDB::Field *f = it.current()->field();
+ if (it.current()->visible()) {
+ int wid = f->width();
+ if (wid==0)
+ wid=KEXI_DEFAULT_DATA_COLUMN_WIDTH;//default col width in pixels
+//! @todo add col width configuration and storage
+ addHeaderColumn(it.current()->isHeaderTextVisible()
+ ? it.current()->captionAliasOrName() : QString::null,
+ f->description(), it.current()->icon(), wid);
+ }
+ }
+ }
+ if (m_verticalHeader) {
+ m_verticalHeader->clear();
+ if (m_data)
+ m_verticalHeader->addLabels(m_data->count());
+ }
+ if (m_data && m_data->count()==0)
+ m_navPanel->setCurrentRecordNumber(0+1);
+
+ if (m_data && !theSameData) {
+//! @todo: store sorting settings?
+ setSorting(-1);
+// connect(m_data, SIGNAL(refreshRequested()), this, SLOT(slotRefreshRequested()));
+ connectToReloadDataSlot(m_data, SIGNAL(reloadRequested()));
+ QObject* thisObject = dynamic_cast<QObject*>(this);
+ if (thisObject) {
+ QObject::connect(m_data, SIGNAL(destroying()), thisObject, SLOT(slotDataDestroying()));
+ QObject::connect(m_data, SIGNAL(rowsDeleted( const QValueList<int> & )),
+ thisObject, SLOT(slotRowsDeleted( const QValueList<int> & )));
+ QObject::connect(m_data, SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)),
+ thisObject, SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)));
+ QObject::connect(m_data, SIGNAL(rowDeleted()), thisObject, SLOT(slotRowDeleted()));
+ QObject::connect(m_data, SIGNAL(rowInserted(KexiTableItem*,bool)),
+ thisObject, SLOT(slotRowInserted(KexiTableItem*,bool)));
+ QObject::connect(m_data, SIGNAL(rowInserted(KexiTableItem*,uint,bool)),
+ thisObject, SLOT(slotRowInserted(KexiTableItem*,uint,bool))); //not db-aware
+ QObject::connect(m_data, SIGNAL(rowRepaintRequested(KexiTableItem&)),
+ thisObject, SLOT(slotRowRepaintRequested(KexiTableItem&)));
+ // setup scrollbar's tooltip
+ QObject::connect(verticalScrollBar(),SIGNAL(sliderReleased()),
+ thisObject,SLOT(vScrollBarSliderReleased()));
+ QObject::connect(verticalScrollBar(),SIGNAL(valueChanged(int)),
+ thisObject,SLOT(vScrollBarValueChanged(int)));
+ QObject::connect(&m_scrollBarTipTimer,SIGNAL(timeout()),
+ thisObject,SLOT(scrollBarTipTimeout()));
+ }
+ }
+
+ if (!m_data) {
+// clearData();
+ cancelRowEdit();
+ //m_data->clearInternal();
+ clearVariables();
+ }
+ else {
+ if (!m_insertItem) {//first setData() call - add 'insert' item
+ m_insertItem = m_data->createItem(); //new KexiTableItem(m_data->columns.count());
+ }
+ else {//just reinit
+ m_insertItem->init(m_data->columns.count());
+ }
+ }
+
+ //update gui mode
+ m_navPanel->setInsertingEnabled(m_data && isInsertingEnabled());
+ if (m_verticalHeader)
+ m_verticalHeader->showInsertRow(m_data && isInsertingEnabled());
+
+ initDataContents();
+ updateIndicesForVisibleValues();
+
+ if (m_data)
+ /*emit*/ dataSet( m_data );
+}
+
+void KexiDataAwareObjectInterface::initDataContents()
+{
+ m_editor = 0;
+// QSize s(tableSize());
+// resizeContents(s.width(),s.height());
+
+ m_navPanel->setRecordCount(rows());
+
+ if (m_data && !m_cursorPositionSetExplicityBeforeShow) {
+ //set current row:
+ m_currentItem = 0;
+ int curRow = -1, curCol = -1;
+ if (m_data->columnsCount()>0) {
+ if (rows()>0) {
+ m_itemIterator->toFirst();
+ m_currentItem = **m_itemIterator;
+ curRow = 0;
+ curCol = 0;
+ }
+ else {//no data
+ if (isInsertingEnabled()) {
+ m_currentItem = m_insertItem;
+ curRow = 0;
+ curCol = 0;
+ }
+ }
+ }
+ setCursorPosition(curRow, curCol, true/*force*/);
+ }
+ ensureCellVisible(m_curRow, m_curCol);
+// updateRowCountInfo();
+// setNavRowCount(rows());
+
+//OK?
+// updateContents();
+ updateWidgetContents();
+
+ m_cursorPositionSetExplicityBeforeShow = false;
+
+ /*emit*/ dataRefreshed();
+}
+
+void KexiDataAwareObjectInterface::setSortingEnabled(bool set)
+{
+ if (m_isSortingEnabled && !set)
+ setSorting(-1);
+ m_isSortingEnabled = set;
+ /*emit*/ reloadActions();
+}
+
+void KexiDataAwareObjectInterface::setSorting(int col, bool ascending)
+{
+ if (!m_data || !m_isSortingEnabled)
+ return;
+// d->pTopHeader->setSortIndicator(col, ascending ? Ascending : Descending);
+ setLocalSortingOrder(col, ascending ? 1 : -1);
+ m_data->setSorting(col, ascending);
+}
+
+int KexiDataAwareObjectInterface::dataSortedColumn() const
+{
+ if (m_data && m_isSortingEnabled)
+ return m_data->sortedColumn();
+ return -1;
+}
+
+int KexiDataAwareObjectInterface::dataSortingOrder() const
+{
+ return m_data ? m_data->sortingOrder() : 0;
+}
+
+bool KexiDataAwareObjectInterface::sort()
+{
+ if (!m_data || !m_isSortingEnabled)
+ return false;
+
+ if (rows() < 2)
+ return true;
+
+ if (!acceptRowEdit())
+ return false;
+
+ const int oldRow = m_curRow;
+ if (m_data->sortedColumn()!=-1)
+ m_data->sort();
+
+ //locate current record
+ if (!m_currentItem) {
+ m_itemIterator->toFirst();
+ m_currentItem = **m_itemIterator; //m_data->first();
+ m_curRow = 0;
+ if (!m_currentItem)
+ return true;
+ }
+ if (m_currentItem != m_insertItem) {
+ m_curRow = m_data->findRef(m_currentItem);
+ int jump = m_curRow - oldRow;
+ if (jump<0)
+ (*m_itemIterator) -= -jump;
+ else
+ (*m_itemIterator) += jump;
+ }
+
+ updateGUIAfterSorting();
+ editorShowFocus( m_curRow, m_curCol );
+ if (m_verticalHeader)
+ m_verticalHeader->setCurrentRow(m_curRow);
+ if (m_horizontalHeader)
+ m_horizontalHeader->setSelectedSection(m_curCol);
+ if (m_navPanel)
+ m_navPanel->setCurrentRecordNumber(m_curRow+1);
+ return true;
+}
+
+void KexiDataAwareObjectInterface::sortAscending()
+{
+ if (currentColumn()<0)
+ return;
+ sortColumnInternal( currentColumn(), 1 );
+}
+
+void KexiDataAwareObjectInterface::sortDescending()
+{
+ if (currentColumn()<0)
+ return;
+ sortColumnInternal( currentColumn(), -1 );
+}
+
+void KexiDataAwareObjectInterface::sortColumnInternal(int col, int order)
+{
+ //-select sorting
+ bool asc;
+ if (order == 0) {// invert
+ if (col==dataSortedColumn() && dataSortingOrder()==1)
+ asc = dataSortingOrder()==-1; //inverse sorting for this column -> descending order
+ else
+ asc = true;
+ }
+ else
+ asc = (order==1);
+
+ int prevSortOrder = currentLocalSortingOrder();
+ const int prevSortColumn = currentLocalSortingOrder();
+ setSorting( col, asc );
+ //-perform sorting
+ if (!sort())
+ setLocalSortingOrder(prevSortColumn, prevSortOrder); //this will also remove indicator
+ //if prevSortColumn==-1
+ if (col != prevSortColumn)
+ /*emit*/ sortedColumnChanged(col);
+}
+
+bool KexiDataAwareObjectInterface::isInsertingEnabled() const
+{
+ if (isReadOnly())
+ return false;
+ if (m_insertingEnabled == 1 || m_insertingEnabled == 0)
+ return (bool)m_insertingEnabled;
+ if (!hasData())
+ return true;
+ return m_data->isInsertingEnabled();
+}
+
+void KexiDataAwareObjectInterface::setFilteringEnabled(bool set)
+{
+ m_isFilteringEnabled = set;
+}
+
+bool KexiDataAwareObjectInterface::isDeleteEnabled() const
+{
+ return (m_deletionPolicy != NoDelete) && !isReadOnly();
+}
+
+void KexiDataAwareObjectInterface::setDeletionPolicy(DeletionPolicy policy)
+{
+ m_deletionPolicy = policy;
+// updateContextMenu();
+}
+
+void KexiDataAwareObjectInterface::setReadOnly(bool set)
+{
+ if (isReadOnly() == set || (m_data && m_data->isReadOnly() && !set))
+ return; //not allowed!
+ m_readOnly = (set ? 1 : 0);
+ if (set)
+ setInsertingEnabled(false);
+ updateWidgetContents();
+ /*emit*/ reloadActions();
+}
+
+bool KexiDataAwareObjectInterface::isReadOnly() const
+{
+ if (!hasData())
+ return true;
+ if (m_readOnly == 1 || m_readOnly == 0)
+ return (bool)m_readOnly;
+ if (!hasData())
+ return true;
+ return m_data->isReadOnly();
+}
+
+void KexiDataAwareObjectInterface::setInsertingEnabled(bool set)
+{
+ if (isInsertingEnabled() == set || (m_data && !m_data->isInsertingEnabled() && set))
+ return; //not allowed!
+ m_insertingEnabled = (set ? 1 : 0);
+ m_navPanel->setInsertingEnabled(set);
+ if (m_verticalHeader)
+ m_verticalHeader->showInsertRow(set);
+ if (set)
+ setReadOnly(false);
+// update();
+ updateWidgetContents();
+ /*emit*/ reloadActions();
+}
+
+void KexiDataAwareObjectInterface::setSpreadSheetMode()
+{
+ m_spreadSheetMode = true;
+ setSortingEnabled( false );
+ setInsertingEnabled( false );
+ setAcceptsRowEditAfterCellAccepting( true );
+ setFilteringEnabled( false );
+ setEmptyRowInsertingEnabled( true );
+ m_navPanelEnabled = false;
+}
+
+void KexiDataAwareObjectInterface::selectNextRow()
+{
+ selectRow( QMIN( rows() - 1 +(isInsertingEnabled()?1:0), m_curRow + 1 ) );
+}
+
+void KexiDataAwareObjectInterface::selectPrevPage()
+{
+ selectRow(
+ QMAX( 0, m_curRow - rowsPerPage() )
+ );
+}
+
+void KexiDataAwareObjectInterface::selectNextPage()
+{
+ selectRow(
+ QMIN(
+ rows() - 1 + (isInsertingEnabled()?1:0),
+ m_curRow + rowsPerPage()
+ )
+ );
+}
+
+void KexiDataAwareObjectInterface::selectFirstRow()
+{
+ selectRow(0);
+}
+
+void KexiDataAwareObjectInterface::selectLastRow()
+{
+// selectRow(rows() - 1 + (isInsertingEnabled()?1:0));
+ selectRow(rows() - 1);
+}
+
+void KexiDataAwareObjectInterface::selectRow(int row)
+{
+ m_vScrollBarValueChanged_enabled = false; //disable tooltip
+ setCursorPosition(row, -1);
+ m_vScrollBarValueChanged_enabled = true;
+}
+
+void KexiDataAwareObjectInterface::selectPrevRow()
+{
+ selectRow( QMAX( 0, m_curRow - 1 ) );
+}
+
+void KexiDataAwareObjectInterface::clearSelection()
+{
+// selectRow( -1 );
+ int oldRow = m_curRow;
+// int oldCol = m_curCol;
+ m_curRow = -1;
+ m_curCol = -1;
+ m_currentItem = 0;
+ updateRow( oldRow );
+ m_navPanel->setCurrentRecordNumber(0);
+// setNavRowNumber(-1);
+}
+
+void KexiDataAwareObjectInterface::setCursorPosition(int row, int col/*=-1*/, bool forceSet)
+{
+ int newrow = row;
+ int newcol = col;
+
+ if(rows() <= 0) {
+ if (m_verticalHeader)
+ m_verticalHeader->setCurrentRow(-1);
+ if (m_horizontalHeader)
+ m_horizontalHeader->setSelectedSection(-1);
+ if (isInsertingEnabled()) {
+ m_currentItem=m_insertItem;
+ newrow=0;
+ if (col>=0)
+ newcol=col;
+ else
+ newcol=0;
+ }
+ else {
+ m_currentItem=0;
+ m_curRow=-1;
+ m_curCol=-1;
+ return;
+ }
+ }
+
+ if(col>=0)
+ {
+ newcol = QMAX(0, col);
+ newcol = QMIN(columns() - 1, newcol);
+ }
+ else {
+ newcol = m_curCol; //no changes
+ newcol = QMAX(0, newcol); //may not be < 0 !
+ }
+ newrow = QMAX(0, row);
+ newrow = QMIN(rows() - 1 + (isInsertingEnabled()?1:0), newrow);
+
+// d->pCurrentItem = itemAt(d->curRow);
+// kdDebug(44021) << "setCursorPosition(): d->curRow=" << d->curRow << " oldRow=" << oldRow << " d->curCol=" << d->curCol << " oldCol=" << oldCol << endl;
+
+ if ( forceSet || m_curRow != newrow || m_curCol != newcol )
+ {
+ kexidbg << "setCursorPosition(): " <<QString("old:%1,%2 new:%3,%4").arg(m_curCol)
+ .arg(m_curRow).arg(newcol).arg(newrow) << endl;
+
+ // cursor moved: get rid of editor
+ if (m_editor) {
+ if (!m_contentsMousePressEvent_dblClick) {
+ if (!acceptEditor()) {
+ return;
+ }
+ //update row num. again
+ newrow = QMIN( rows() - 1 + (isInsertingEnabled()?1:0), newrow);
+ }
+ }
+ if (m_errorMessagePopup) {
+ m_errorMessagePopup->close();
+ }
+
+ if (m_curRow != newrow || forceSet) {//update current row info
+ m_navPanel->setCurrentRecordNumber(newrow+1);
+// setNavRowNumber(newrow);
+// d->navBtnPrev->setEnabled(newrow>0);
+// d->navBtnFirst->setEnabled(newrow>0);
+// d->navBtnNext->setEnabled(newrow<(rows()-1+(isInsertingEnabled()?1:0)));
+// d->navBtnLast->setEnabled(newrow!=(rows()-1));
+ }
+
+ // cursor moved to other row: end of row editing
+ bool newRowInserted = false;
+ if (m_rowEditing && m_curRow != newrow) {
+ newRowInserted = m_newRowEditing;
+ if (!acceptRowEdit()) {
+ //accepting failed: cancel setting the cursor
+ return;
+ }
+ //update row number, because number of rows changed
+ newrow = QMIN( rows() - 1 + (isInsertingEnabled()?1:0), newrow);
+
+ m_navPanel->setCurrentRecordNumber(newrow+1); //refresh
+ }
+
+ //change position
+ int oldRow = m_curRow;
+ int oldCol = m_curCol;
+ m_curRow = newrow;
+ m_curCol = newcol;
+
+// int cw = columnWidth( d->curCol );
+// int rh = rowHeight();
+// ensureVisible( columnPos( d->curCol ) + cw / 2, rowPos( d->curRow ) + rh / 2, cw / 2, rh / 2 );
+// center(columnPos(d->curCol) + cw / 2, rowPos(d->curRow) + rh / 2, cw / 2, rh / 2);
+// kdDebug(44021) << " contentsY() = "<< contentsY() << endl;
+
+//js if (oldRow > d->curRow)
+//js ensureVisible(columnPos(d->curCol), rowPos(d->curRow) + rh, columnWidth(d->curCol), rh);
+//js else// if (oldRow <= d->curRow)
+//js ensureVisible(columnPos(d->curCol), rowPos(d->curRow), columnWidth(d->curCol), rh);
+
+
+ //show editor-dependent focus, if we're changing the current column
+ if (oldCol>=0 && oldCol<columns() && m_curCol!=oldCol) {
+ //find the editor for this column
+ KexiDataItemInterface *edit = editor( oldCol );
+ if (edit) {
+ edit->hideFocus();
+ }
+ }
+
+ // position changed, so subsequent searching should be started from scratch
+ // (e.g. from the current cell or the top-left cell)
+ m_positionOfRecentlyFoundValue.exists = false;
+
+ //show editor-dependent focus, if needed
+ editorShowFocus( m_curRow, m_curCol );
+
+ if (m_updateEntireRowWhenMovingToOtherRow)
+ updateRow( oldRow );
+ else
+ updateCell( oldRow, oldCol );
+
+// //quite clever: ensure the cell is visible:
+// ensureCellVisible(m_curRow, m_curCol);
+
+// QPoint pcenter = QRect( columnPos(d->curCol), rowPos(d->curRow), columnWidth(d->curCol), rh).center();
+// ensureVisible(pcenter.x(), pcenter.y(), columnWidth(d->curCol)/2, rh/2);
+
+// ensureVisible(columnPos(d->curCol), rowPos(d->curRow) - contentsY(), columnWidth(d->curCol), rh);
+ if (m_verticalHeader && (oldRow != m_curRow || forceSet))
+ m_verticalHeader->setCurrentRow(m_curRow);
+
+ if (m_updateEntireRowWhenMovingToOtherRow)
+ updateRow( m_curRow );
+ else
+ updateCell( m_curRow, m_curCol );
+
+ if (m_curCol != oldCol || m_curRow != oldRow || forceSet) {//ensure this is also refreshed
+ if (!m_updateEntireRowWhenMovingToOtherRow) //only if entire row has not been updated
+ updateCell( oldRow, m_curCol );
+ }
+ //update row
+ if (forceSet || m_curRow != oldRow) {
+ if (isInsertingEnabled() && m_curRow == rows()) {
+ kdDebug(44021) << "NOW insert item is current" << endl;
+ m_currentItem = m_insertItem;
+ }
+ else {
+ kdDebug(44021) << QString("NOW item at %1 (%2) is current")
+ .arg(m_curRow).arg((ulong)itemAt(m_curRow)) << endl;
+ //NOT EFFECTIVE!!!!!!!!!!!
+ //set item iterator
+ if (!newRowInserted && isInsertingEnabled() && m_currentItem == m_insertItem && m_curRow == (rows()-1)) {
+ //moving from 'insert item' to last item
+ m_itemIterator->toLast();
+ }
+ else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && 0==m_curRow)
+ m_itemIterator->toFirst();
+ else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && oldRow>=0 && (oldRow+1)==m_curRow) //just move next
+ ++(*m_itemIterator);
+ else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && oldRow>=0 && (oldRow-1)==m_curRow) //just move back
+ --(*m_itemIterator);
+ else { //move at:
+ m_itemIterator->toFirst();
+ (*m_itemIterator)+=m_curRow;
+ }
+ if (!**m_itemIterator) { //sanity
+ m_itemIterator->toFirst();
+ (*m_itemIterator)+=m_curRow;
+ }
+ m_currentItem = **m_itemIterator;
+ //itemAt(m_curRow);
+ }
+ }
+
+ //quite clever: ensure the cell is visible:
+ ensureCellVisible(m_curRow, m_curCol);
+
+ if (m_horizontalHeader && (oldCol != m_curCol || forceSet))
+ m_horizontalHeader->setSelectedSection(m_curCol);
+
+ /*emit*/ itemSelected(m_currentItem);
+ /*emit*/ cellSelected(m_curCol, m_curRow);
+ /* only needed for forms */
+ selectCellInternal();
+ }
+ else {
+ kexidbg << "setCursorPosition(): NO CHANGE" << endl;
+ }
+
+ if(m_initDataContentsOnShow) {
+ m_cursorPositionSetExplicityBeforeShow = true;
+ }
+}
+
+bool KexiDataAwareObjectInterface::acceptRowEdit()
+{
+ if (!m_rowEditing || /*sanity*/!m_data->rowEditBuffer())
+ return true;
+ if (m_inside_acceptEditor) {
+ m_internal_acceptsRowEditAfterCellAccepting = true;
+ return true;
+ }
+ m_internal_acceptsRowEditAfterCellAccepting = false;
+
+ const int columnEditedBeforeAccepting = m_editor ? currentColumn() : -1;
+ if (!acceptEditor())
+ return false;
+ kdDebug() << "EDIT ROW ACCEPTING..." << endl;
+
+ bool success = true;
+// bool allow = true;
+// int faultyColumn = -1; // will be !=-1 if cursor has to be moved to that column
+ const bool inserting = m_newRowEditing;
+// QString msg, desc;
+// bool inserting = d->pInsertItem && d->pInsertItem==d->pCurrentItem;
+
+ if (m_data->rowEditBuffer()->isEmpty() && !m_newRowEditing) {
+/* if (d->newRowEditing) {
+ cancelRowEdit();
+ kdDebug() << "-- NOTHING TO INSERT!!!" << endl;
+ return true;
+ }
+ else {*/
+ kdDebug() << "-- NOTHING TO ACCEPT!!!" << endl;
+// }
+ }
+ else {//not empty edit buffer or new row to insert:
+ if (m_newRowEditing) {
+// emit aboutToInsertRow(d->pCurrentItem, m_data->rowEditBuffer(), success, &faultyColumn);
+// if (success) {
+ kdDebug() << "-- INSERTING: " << endl;
+ m_data->rowEditBuffer()->debug();
+ success = m_data->saveNewRow(*m_currentItem);
+// if (!success) {
+// }
+// }
+ }
+ else {
+// emit aboutToUpdateRow(d->pCurrentItem, m_data->rowEditBuffer(), success, &faultyColumn);
+ if (success) {
+ //accept changes for this row:
+ kdDebug() << "-- UPDATING: " << endl;
+ m_data->rowEditBuffer()->debug();
+ kdDebug() << "-- BEFORE: " << endl;
+ m_currentItem->debug();
+ success = m_data->saveRowChanges(*m_currentItem);//, &msg, &desc, &faultyColumn);
+ kdDebug() << "-- AFTER: " << endl;
+ m_currentItem->debug();
+
+// if (!success) {
+// }
+ }
+ }
+ }
+
+ if (success) {
+ //editing is finished:
+ if (m_newRowEditing) {
+ //update current-item-iterator
+ m_itemIterator->toLast();
+ m_currentItem = **m_itemIterator;
+ }
+ m_rowEditing = false;
+ m_newRowEditing = false;
+ //indicate on the vheader that we are not editing
+ if (m_verticalHeader)
+ m_verticalHeader->setEditRow(-1);
+
+ updateAfterAcceptRowEdit();
+
+ kdDebug() << "EDIT ROW ACCEPTED:" << endl;
+// /*debug*/itemAt(m_curRow);
+
+ if (inserting) {
+// emit rowInserted(d->pCurrentItem);
+ //update navigator's data
+ m_navPanel->setRecordCount(rows());
+ }
+ else {
+// emit rowUpdated(d->pCurrentItem);
+ }
+
+ /*emit*/ rowEditTerminated(m_curRow);
+ }
+ else {
+// if (!allow) {
+// kdDebug() << "INSERT/EDIT ROW - DISALLOWED by signal!" << endl;
+// }
+// else {
+// kdDebug() << "EDIT ROW - ERROR!" << endl;
+// }
+ int faultyColumn = -1;
+ if (m_data->result()->column >= 0 && m_data->result()->column < columns())
+ faultyColumn = m_data->result()->column;
+ else if (columnEditedBeforeAccepting >= 0)
+ faultyColumn = columnEditedBeforeAccepting;
+ if (faultyColumn >= 0) {
+ setCursorPosition(m_curRow, faultyColumn);
+ }
+
+ const int button = showErrorMessageForResult( m_data->result() );
+ if (KMessageBox::No == button) {
+ //discard changes
+ cancelRowEdit();
+ }
+ else {
+ if (faultyColumn >= 0) {
+ //edit this cell
+ startEditCurrentCell();
+ }
+ }
+ }
+
+ return success;
+}
+
+bool KexiDataAwareObjectInterface::cancelRowEdit()
+{
+ if (!hasData())
+ return false;
+ if (!m_rowEditing)
+ return false;
+ cancelEditor();
+ m_rowEditing = false;
+ //indicate on the vheader that we are not editing
+ if (m_verticalHeader)
+ m_verticalHeader->setEditRow(-1);
+ m_alsoUpdateNextRow = m_newRowEditing;
+ if (m_newRowEditing) {
+ m_newRowEditing = false;
+ //remove current edited row (it is @ the end of list)
+ m_data->removeLast();
+ //current item is now empty, last row
+ m_currentItem = m_insertItem;
+ //update visibility
+ if (m_verticalHeader)
+ m_verticalHeader->removeLabel(false); //-1 label
+// updateContents(columnPos(0), rowPos(rows()),
+// viewport()->width(), d->rowHeight*3 + (m_navPanel ? m_navPanel->height() : 0)*3 );
+// updateContents(); //js: above did not work well so we do that dirty
+ updateWidgetContents();
+//TODO: still doesn't repaint properly!!
+// QSize s(tableSize());
+// resizeContents(s.width(), s.height());
+ updateWidgetContentsSize();
+// m_verticalHeader->update();
+ //--no cancel action is needed for datasource,
+ // because the row was not yet stored.
+ }
+
+ m_data->clearRowEditBuffer();
+ updateAfterCancelRowEdit();
+
+//! \todo (js): cancel changes for this row!
+ kexidbg << "EDIT ROW CANCELLED." << endl;
+
+ /*emit*/ rowEditTerminated(m_curRow);
+ return true;
+}
+
+void KexiDataAwareObjectInterface::updateAfterCancelRowEdit()
+{
+ updateRow(m_curRow);
+ if (m_alsoUpdateNextRow)
+ updateRow(m_curRow+1);
+ m_alsoUpdateNextRow = false;
+}
+
+void KexiDataAwareObjectInterface::updateAfterAcceptRowEdit()
+{
+ updateRow(m_curRow);
+}
+
+void KexiDataAwareObjectInterface::removeEditor()
+{
+ if (!m_editor)
+ return;
+ m_editor->hideWidget();
+ m_editor = 0;
+}
+
+bool KexiDataAwareObjectInterface::cancelEditor()
+{
+ if (m_errorMessagePopup) {
+ m_errorMessagePopup->close();
+ }
+ if (!m_editor)
+ return false;
+ removeEditor();
+ return true;
+}
+
+//! @internal
+class KexiDataAwareObjectInterfaceToolTip : public QToolTip {
+ public:
+ KexiDataAwareObjectInterfaceToolTip( const QString & text, const QPoint & pos, QWidget * widget )
+ : QToolTip(widget), m_text(text)
+ {
+ tip( QRect(pos, QSize(100, 100)), text );
+ }
+ virtual void maybeTip(const QPoint & p) {
+ tip( QRect(p, QSize(100, 100)), m_text);
+ }
+ QString m_text;
+};
+
+bool KexiDataAwareObjectInterface::acceptEditor()
+{
+ if (!hasData())
+ return true;
+ if (!m_editor || m_inside_acceptEditor)
+ return true;
+
+ m_inside_acceptEditor = true;//avoid recursion
+
+ QVariant newval;
+ Validator::Result res = Validator::Ok;
+ QString msg, desc;
+ bool setNull = false;
+// bool allow = true;
+// static const QString msg_NOT_NULL = i18n("\"%1\" column requires a value to be entered.");
+
+ //autoincremented field can be omitted (left as null or empty) if we're inserting a new row
+ const bool autoIncColumnCanBeOmitted = m_newRowEditing && m_editor->field()->isAutoIncrement();
+// const bool autoIncColumnCanBeOmitted = m_newRowEditing && m_editor->columnInfo()->field->isAutoIncrement();
+
+ bool valueChanged = m_editor->valueChanged();
+ bool editCurrentCellAgain = false;
+
+ if (valueChanged) {
+ if (!m_editor->valueIsValid()) {
+ //used e.g. for date or time values - the value can be null but not necessary invalid
+ res = Validator::Error;
+ editCurrentCellAgain = true;
+ QWidget *par = dynamic_cast<QScrollView*>(this) ? dynamic_cast<QScrollView*>(this)->viewport() :
+ dynamic_cast<QWidget*>(this);
+ QWidget *edit = dynamic_cast<QWidget*>(m_editor);
+ if (par && edit) {
+//! @todo allow displaying user-defined warning
+//! @todo also use for other error messages
+ if (!m_errorMessagePopup) {
+// m_errorMessagePopup->close();
+ m_errorMessagePopup = new KexiArrowTip(
+ i18n("Error: %1").arg(m_editor->columnInfo()->field->typeName())+"?",
+ dynamic_cast<QWidget*>(this));
+ m_errorMessagePopup->move(
+ par->mapToGlobal(edit->pos()) + QPoint(6, edit->height() + 0) );
+ m_errorMessagePopup->show();
+ }
+ m_editor->setFocus();
+ }
+ }
+ else if (m_editor->valueIsNull()) {//null value entered
+// if (m_editor->columnInfo()->field->isNotNull() && !autoIncColumnCanBeOmitted) {
+ if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL NOT ALLOWED!" << endl;
+ res = Validator::Error;
+// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName())
+ msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName())
+ + "\n\n" + Kexi::msgYouCanImproveData();
+ desc = i18n("The column's constraint is declared as NOT NULL.");
+ editCurrentCellAgain = true;
+ // allow = false;
+ // removeEditor();
+ // return true;
+ }
+ else {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL VALUE WILL BE SET" << endl;
+ //ok, just leave newval as NULL
+ setNull = true;
+ }
+ }
+ else if (m_editor->valueIsEmpty()) {//empty value entered
+// if (m_editor->columnInfo()->field->hasEmptyProperty()) {
+ if (m_editor->field()->hasEmptyProperty()) {
+// if (m_editor->columnInfo()->field->isNotEmpty() && !autoIncColumnCanBeOmitted) {
+ if (m_editor->field()->isNotEmpty() && !autoIncColumnCanBeOmitted) {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): EMPTY NOT ALLOWED!" << endl;
+ res = Validator::Error;
+// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName())
+ msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName())
+ + "\n\n" + Kexi::msgYouCanImproveData();
+ desc = i18n("The column's constraint is declared as NOT EMPTY.");
+ editCurrentCellAgain = true;
+ // allow = false;
+ // removeEditor();
+ // return true;
+ }
+ else {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): EMPTY VALUE WILL BE SET" << endl;
+ }
+ }
+ else {
+// if (m_editor->columnInfo()->field->isNotNull() && !autoIncColumnCanBeOmitted) {
+ if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NEITHER NULL NOR EMPTY VALUE CAN BE SET!" << endl;
+ res = Validator::Error;
+// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName())
+ msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName())
+ + "\n\n" + Kexi::msgYouCanImproveData();
+ desc = i18n("The column's constraint is declared as NOT EMPTY and NOT NULL.");
+ editCurrentCellAgain = true;
+// allow = false;
+ // removeEditor();
+ // return true;
+ }
+ else {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL VALUE WILL BE SET BECAUSE EMPTY IS NOT ALLOWED" << endl;
+ //ok, just leave newval as NULL
+ setNull = true;
+ }
+ }
+ }
+ }//changed
+
+ const int realFieldNumber = fieldNumberForColumn(m_curCol);
+ if (realFieldNumber < 0) {
+ kdWarning() << "KexiDataAwareObjectInterface::acceptEditor(): fieldNumberForColumn(m_curCol) < 0" << endl;
+ m_inside_acceptEditor = false;
+ return false;
+ }
+
+ KexiTableViewColumn *currentTVColumn = column(m_curCol);
+
+ //try to get the value entered:
+ if (res == Validator::Ok) {
+ if ((!setNull && !valueChanged)
+ || (m_editor->field()->type()!=KexiDB::Field::Boolean && setNull && m_currentItem->at( realFieldNumber ).isNull())) {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): VALUE NOT CHANGED." << endl;
+ removeEditor();
+ if (m_acceptsRowEditAfterCellAccepting || m_internal_acceptsRowEditAfterCellAccepting)
+ acceptRowEdit();
+ m_inside_acceptEditor = false;
+ return true;
+ }
+ if (!setNull) {//get the new value
+// bool ok;
+ newval = m_editor->value();
+//! @todo validation rules for this value?
+/*
+ if (!ok) {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): INVALID VALUE - NOT CHANGED." << endl;
+ res = KexiValidator::Error;
+//js: TODO get detailed info on why m_editor->value() failed
+ msg = i18n("Entered value is invalid.")
+ + "\n\n" + KexiValidator::msgYouCanImproveData();
+ editCurrentCellAgain = true;
+// removeEditor();
+// return true;
+ }*/
+ }
+
+ //Check other validation rules:
+ //1. check using validator
+// KexiValidator *validator = m_data->column(m_curCol)->validator();
+ Validator *validator = currentTVColumn->validator();
+ if (validator) {
+// res = validator->check(m_data->column(m_curCol)->field()->captionOrName(),
+ res = validator->check(currentTVColumn->field()->captionOrName(),
+ newval, msg, desc);
+ }
+ }
+
+ //show the validation result if not OK:
+ if (res == Validator::Error) {
+ if (!msg.isEmpty()) {
+ if (desc.isEmpty())
+ KMessageBox::sorry(dynamic_cast<QWidget*>(this), msg);
+ else
+ KMessageBox::detailedSorry(dynamic_cast<QWidget*>(this), msg, desc);
+ }
+ editCurrentCellAgain = true;
+// allow = false;
+ }
+ else if (res == Validator::Warning) {
+ //js: todo: message!!!
+ KMessageBox::messageBox(dynamic_cast<QWidget*>(this), KMessageBox::Sorry, msg + "\n" + desc);
+ editCurrentCellAgain = true;
+ }
+
+ if (res == Validator::Ok) {
+ //2. check using signal
+ //bool allow = true;
+// emit aboutToChangeCell(d->pCurrentItem, newval, allow);
+// if (allow) {
+ //send changes to the backend
+ QVariant visibleValue;
+ if (!newval.isNull()/* visible value should be null if value is null */
+ && currentTVColumn->visibleLookupColumnInfo)
+ {
+ visibleValue = m_editor->visibleValue(); //visible value for lookup field
+ }
+ //should be also added to the buffer
+ if (m_data->updateRowEditBufferRef(m_currentItem, m_curCol, currentTVColumn,
+ newval, /*allowSignals*/true, currentTVColumn->visibleLookupColumnInfo ? &visibleValue : 0))
+ {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): ------ EDIT BUFFER CHANGED TO:" << endl;
+ m_data->rowEditBuffer()->debug();
+ } else {
+ kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): ------ CHANGE FAILED in KexiDataAwareObjectInterface::updateRowEditBuffer()" << endl;
+ res = Validator::Error;
+
+ //now: there might be called cancelEditor() in updateRowEditBuffer() handler,
+ //if this is true, d->pEditor is NULL.
+
+ if (m_editor && m_data->result()->column>=0 && m_data->result()->column<columns()) {
+ //move to faulty column (if m_editor is not cleared)
+ setCursorPosition(m_curRow, m_data->result()->column);
+ }
+ if (!m_data->result()->msg.isEmpty()) {
+ const int button = showErrorMessageForResult( m_data->result() );
+ if (KMessageBox::No == button) {
+ //discard changes
+ cancelEditor();
+ if (m_acceptsRowEditAfterCellAccepting)
+ cancelRowEdit();
+ m_inside_acceptEditor = false;
+ return false;
+ }
+ }
+ }
+ }
+
+ if (res == Validator::Ok) {
+ removeEditor();
+ /*emit*/ itemChanged(m_currentItem, m_curRow, m_curCol,
+ m_currentItem->at( realFieldNumber ));
+ /*emit*/ itemChanged(m_currentItem, m_curRow, m_curCol);
+ }
+ m_inside_acceptEditor = false;
+ if (res == Validator::Ok) {
+ if (m_acceptsRowEditAfterCellAccepting || m_internal_acceptsRowEditAfterCellAccepting)
+ acceptRowEdit();
+ return true;
+ }
+ if (m_editor) {
+ //allow to edit the cell again, (if m_pEditor is not cleared)
+
+ if (m_editor->hasFocusableWidget()) {
+ m_editor->showWidget();
+ m_editor->setFocus();
+ }
+// startEditCurrentCell(newval.type()==QVariant::String ? newval.toString() : QString::null);
+// m_editor->setFocus();
+ }
+ return false;
+}
+
+void KexiDataAwareObjectInterface::startEditCurrentCell(const QString &setText)
+{
+ kdDebug() << "** KexiDataAwareObjectInterface::startEditCurrentCell("<<setText<<")"<<endl;
+// if (columnType(d->curCol) == KexiDB::Field::Boolean)
+// return;
+ if (isReadOnly() || !columnEditable(m_curCol))
+ return;
+ if (m_editor) {
+ if (m_editor->hasFocusableWidget()) {
+ m_editor->showWidget();
+ m_editor->setFocus();
+ }
+ }
+// ensureVisible(columnPos(m_curCol), rowPos(m_curRow)+rowHeight(),
+// columnWidth(m_curCol), rowHeight());
+//OK?
+ //ensureCellVisible(m_curRow+1, m_curCol);
+ if (!m_editor)
+ createEditor(m_curRow, m_curCol, setText, !setText.isEmpty());
+}
+
+void KexiDataAwareObjectInterface::deleteAndStartEditCurrentCell()
+{
+ if (isReadOnly() || !columnEditable(m_curCol))
+ return;
+ if (m_editor) {//if we've editor - just clear it
+ m_editor->clear();
+ return;
+ }
+//js if (columnType(m_curCol) == KexiDB::Field::Boolean)
+//js return;
+// ensureVisible(columnPos(m_curCol), rowPos(m_curRow) + rowHeight(),
+// columnWidth(m_curCol), rowHeight());
+//OK?
+ ensureCellVisible(m_curRow+1, m_curCol);
+ createEditor(m_curRow, m_curCol, QString::null, false/*removeOld*/);
+ if (!m_editor)
+ return;
+ m_editor->clear();
+ if (m_editor->acceptEditorAfterDeleteContents())
+ acceptEditor();
+ if (!m_editor || !m_editor->hasFocusableWidget())
+ updateCell(m_curRow, m_curCol);
+}
+
+void KexiDataAwareObjectInterface::deleteCurrentRow()
+{
+ if (m_newRowEditing) {//we're editing fresh new row: just cancel this!
+ cancelRowEdit();
+ return;
+ }
+
+ if (!acceptRowEdit())
+ return;
+
+ if (!isDeleteEnabled() || !m_currentItem || m_currentItem == m_insertItem)
+ return;
+ switch (m_deletionPolicy) {
+ case NoDelete:
+ return;
+ case ImmediateDelete:
+ break;
+ case AskDelete:
+ if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<QWidget*>(this),
+ i18n("Do you want to delete selected row?"), 0,
+ KGuiItem(i18n("&Delete Row"),"editdelete"),
+ "dontAskBeforeDeleteRow"/*config entry*/,
+ KMessageBox::Notify|KMessageBox::Dangerous))
+ return;
+ break;
+ case SignalDelete:
+ /*emit*/ itemDeleteRequest(m_currentItem, m_curRow, m_curCol);
+ /*emit*/ currentItemDeleteRequest();
+ return;
+ default:
+ return;
+ }
+
+ if (!deleteItem(m_currentItem)) {//nothing
+ }
+}
+
+KexiTableItem *KexiDataAwareObjectInterface::insertEmptyRow(int row)
+{
+ if ( !acceptRowEdit() || !isEmptyRowInsertingEnabled()
+ || (row!=-1 && row >= ((int)rows()+(isInsertingEnabled()?1:0) ) ) )
+ return 0;
+
+ KexiTableItem *newItem = m_data->createItem(); //new KexiTableItem(m_data->columns.count());
+ insertItem(newItem, row);
+ return newItem;
+}
+
+void KexiDataAwareObjectInterface::insertItem(KexiTableItem *newItem, int row)
+{
+ const bool changeCurrentRow = row==-1 || row==m_curRow;
+ if (changeCurrentRow) {
+ //change current row
+ row = (m_curRow >= 0 ? m_curRow : 0);
+ m_currentItem = newItem;
+ m_curRow = row;
+ }
+ else if (m_curRow >= row) {
+ m_curRow++;
+ }
+
+ m_data->insertRow(*newItem, row, true /*repaint*/);
+
+ if (changeCurrentRow) {
+ //update iter...
+ m_itemIterator->toFirst();
+ (*m_itemIterator)+=m_curRow;
+ }
+/*
+ QSize s(tableSize());
+ resizeContents(s.width(),s.height());
+
+ //redraw only this row and below:
+ int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() );
+// updateContents( columnPos( leftcol ), rowPos(d->curRow),
+// clipper()->width(), clipper()->height() - (rowPos(d->curRow) - contentsY()) );
+ updateContents( columnPos( leftcol ), rowPos(row),
+ clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) );
+
+ m_verticalHeader->addLabel();
+
+ //update navigator's data
+ setNavRowCount(rows());
+
+ if (d->curRow >= row) {
+ //update
+ editorShowFocus( d->curRow, d->curCol );
+ }
+ */
+}
+
+void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem *item, bool repaint)
+{
+ int row = m_data->findRef(item);
+ slotRowInserted( item, row, repaint );
+}
+
+void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem * /*item*/, uint row, bool repaint)
+{
+ if (repaint && (int)row<rows()) {
+ updateWidgetContentsSize();
+
+/* updateAllVisibleRowsBelow() used instead
+ //redraw only this row and below:
+ int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() );
+ updateContents( columnPos( leftcol ), rowPos(row),
+ clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) );
+*/
+ updateAllVisibleRowsBelow(row);
+
+ if (!m_verticalHeaderAlreadyAdded) {
+ if (m_verticalHeader)
+ m_verticalHeader->addLabel();
+ }
+ else //it was added because this inserting was interactive
+ m_verticalHeaderAlreadyAdded = false;
+
+ //update navigator's data
+ m_navPanel->setRecordCount(rows());
+
+ if (m_curRow >= (int)row) {
+ //update
+ editorShowFocus( m_curRow, m_curCol );
+ }
+ }
+}
+
+tristate KexiDataAwareObjectInterface::deleteAllRows(bool ask, bool repaint)
+{
+ if (!hasData())
+ return true;
+ if (m_data->count()<1)
+ return true;
+
+ if (ask) {
+ QString tableName = m_data->dbTableName();
+ if (!tableName.isEmpty()) {
+ tableName.prepend(" \"");
+ tableName.append("\"");
+ }
+ if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<QWidget*>(this),
+ i18n("Do you want to clear the contents of table %1?").arg(tableName),
+ 0, KGuiItem(i18n("&Clear Contents")) ))
+ return cancelled;
+ }
+
+ cancelRowEdit();
+// acceptRowEdit();
+// m_verticalHeader->clear();
+ const bool repaintLater = repaint && m_spreadSheetMode;
+ const int oldRows = rows();
+
+ bool res = m_data->deleteAllRows(repaint && !repaintLater);
+
+ if (res) {
+ if (m_spreadSheetMode) {
+// const uint columns = m_data->columns.count();
+ for (int i=0; i<oldRows; i++) {
+ m_data->append(m_data->createItem());//new KexiTableItem(columns));
+ }
+ }
+ }
+ if (repaintLater)
+ m_data->reload();
+
+// d->clearVariables();
+// m_verticalHeader->setCurrentRow(-1);
+
+// d->pUpdateTimer->start(1,true);
+// if (repaint)
+// viewport()->repaint();
+ return res;
+}
+
+void KexiDataAwareObjectInterface::clearColumns(bool repaint)
+{
+ cancelRowEdit();
+ m_data->clearInternal();
+
+ clearColumnsInternal(repaint);
+ updateIndicesForVisibleValues();
+
+ if (repaint)
+// viewport()->repaint();
+//OK?
+ updateWidgetContents();
+
+/* for(int i=0; i < rows(); i++)
+ {
+ m_verticalHeader->removeLabel();
+ }
+
+ editorCancel();
+ m_contents->clear();
+
+ d->clearVariables();
+ d->numCols = 0;
+
+ while(d->pTopHeader->count()>0)
+ d->pTopHeader->removeLabel(0);
+
+ m_verticalHeader->setCurrentRow(-1);
+
+ viewport()->repaint();
+
+// d->pColumnTypes.resize(0);
+// d->pColumnModes.resize(0);
+// d->pColumnDefaults.clear();*/
+}
+
+void KexiDataAwareObjectInterface::reloadData()
+{
+// cancelRowEdit();
+ acceptRowEdit();
+ if (m_verticalHeader)
+ m_verticalHeader->clear();
+
+ if (m_curCol>=0 && m_curCol<columns()) {
+ //find the editor for this column
+ KexiDataItemInterface *edit = editor( m_curCol );
+ if (edit) {
+ edit->hideFocus();
+ }
+ }
+// setCursorPosition(-1, -1, true);
+ clearVariables();
+ if (m_verticalHeader)
+ m_verticalHeader->setCurrentRow(-1);
+
+ if (dynamic_cast<QWidget*>(this) && dynamic_cast<QWidget*>(this)->isVisible())
+ initDataContents();
+ else
+ m_initDataContentsOnShow = true;
+
+ if (m_verticalHeader)
+ m_verticalHeader->addLabels(m_data->count());
+
+ updateWidgetScrollBars();
+}
+
+int KexiDataAwareObjectInterface::columnType(int col)
+{
+ KexiTableViewColumn* c = m_data ? column(col) : 0;
+ return c ? c->field()->type() : KexiDB::Field::InvalidType;
+}
+
+bool KexiDataAwareObjectInterface::columnEditable(int col)
+{
+ KexiTableViewColumn* c = m_data ? column(col) : 0;
+ return c ? (! c->isReadOnly()) : false;
+}
+
+int KexiDataAwareObjectInterface::rows() const
+{
+ if (!hasData())
+ return 0;
+ return m_data->count();
+}
+
+int KexiDataAwareObjectInterface::dataColumns() const
+{
+ if (!hasData())
+ return 0;
+ return m_data->columns.count();
+}
+
+QVariant KexiDataAwareObjectInterface::columnDefaultValue(int /*col*/) const
+{
+ return QVariant(0);
+//TODO(js)
+// return m_data->columns[col].defaultValue;
+}
+
+void KexiDataAwareObjectInterface::setAcceptsRowEditAfterCellAccepting(bool set)
+{
+ m_acceptsRowEditAfterCellAccepting = set;
+}
+
+void KexiDataAwareObjectInterface::setDropsAtRowEnabled(bool set)
+{
+// const bool old = d->dropsAtRowEnabled;
+ if (!set)
+ m_dragIndicatorLine = -1;
+ if (m_dropsAtRowEnabled && !set) {
+ m_dropsAtRowEnabled = false;
+// update();
+ updateWidgetContents();
+ }
+ else {
+ m_dropsAtRowEnabled = set;
+ }
+}
+
+void KexiDataAwareObjectInterface::setEmptyRowInsertingEnabled(bool set)
+{
+ m_emptyRowInsertingEnabled = set;
+ /*emit*/ reloadActions();
+}
+
+void KexiDataAwareObjectInterface::slotAboutToDeleteRow(KexiTableItem& item,
+ KexiDB::ResultInfo* /*result*/, bool repaint)
+{
+ if (repaint) {
+ m_rowWillBeDeleted = m_data->findRef(&item);
+ }
+}
+
+void KexiDataAwareObjectInterface::slotRowDeleted()
+{
+ if (m_rowWillBeDeleted >= 0) {
+ if (m_rowWillBeDeleted > 0 && m_rowWillBeDeleted >= (rows()-1) && !m_spreadSheetMode)
+ m_rowWillBeDeleted = rows()-1; //move up if it's the last row
+ updateWidgetContentsSize();
+
+ if (! (m_spreadSheetMode && m_rowWillBeDeleted>=(rows()-1)))
+ setCursorPosition(m_rowWillBeDeleted, m_curCol, true/*forceSet*/);
+ if (m_verticalHeader)
+ m_verticalHeader->removeLabel();
+
+ updateAllVisibleRowsBelow(m_curRow); //needed for KexiTableView
+
+ //update navigator's data
+ m_navPanel->setRecordCount(rows());
+
+ m_rowWillBeDeleted = -1;
+ }
+}
+
+bool KexiDataAwareObjectInterface::beforeDeleteItem(KexiTableItem *)
+{
+ //always return
+ return true;
+}
+
+bool KexiDataAwareObjectInterface::deleteItem(KexiTableItem *item)/*, bool moveCursor)*/
+{
+ if (!item || !beforeDeleteItem(item))
+ return false;
+
+ QString msg, desc;
+// bool current = (item == d->pCurrentItem);
+ const bool lastRowDeleted = m_spreadSheetMode && m_data->last() == item; //we need to know this so we
+ //can return to the last row
+ //after reinserting it
+ if (!m_data->deleteRow(*item, true /*repaint*/)) {
+ /*const int button =*/
+ showErrorMessageForResult( m_data->result() );
+// if (KMessageBox::No == button) {
+ //discard changes
+ // }
+ return false;
+ }
+ else {
+//setCursorPosition() wil lset this! if (current)
+ //d->pCurrentItem = m_data->current();
+ }
+
+// repaintAfterDelete();
+ if (m_spreadSheetMode) { //append empty row for spreadsheet mode
+ m_data->append(m_data->createItem());//new KexiTableItem(m_data->columns.count()));
+ if (m_verticalHeader)
+ m_verticalHeader->addLabels(1);
+ if (lastRowDeleted) //back to the last row
+ setCursorPosition(rows()-1, m_curCol, true/*forceSet*/);
+ /*emit*/ newItemAppendedForAfterDeletingInSpreadSheetMode();
+ }
+ return true;
+}
+
+KexiTableViewColumn* KexiDataAwareObjectInterface::column(int col)
+{
+ return m_data->column(col);
+}
+
+bool KexiDataAwareObjectInterface::hasDefaultValueAt(const KexiTableViewColumn& tvcol)
+{
+ if (m_rowEditing && m_data->rowEditBuffer() && m_data->rowEditBuffer()->isDBAware()) {
+ return m_data->rowEditBuffer()->hasDefaultValueAt( *tvcol.columnInfo );
+ }
+ return false;
+}
+
+const QVariant* KexiDataAwareObjectInterface::bufferedValueAt(int col, bool useDefaultValueIfPossible)
+{
+ if (m_rowEditing && m_data->rowEditBuffer())
+ {
+ KexiTableViewColumn* tvcol = column(col);
+ if (tvcol->isDBAware) {
+ //get the stored value
+ const int realFieldNumber = fieldNumberForColumn(col);
+ if (realFieldNumber < 0) {
+ kdWarning() << "KexiDataAwareObjectInterface::bufferedValueAt(): "
+ "fieldNumberForColumn(m_curCol) < 0" << endl;
+ return 0;
+ }
+ QVariant *storedValue = &m_currentItem->at( realFieldNumber );
+
+ //db-aware data: now, try to find a buffered value (or default one)
+ const QVariant *cv = m_data->rowEditBuffer()->at( *tvcol->columnInfo,
+ storedValue->isNull() && useDefaultValueIfPossible);
+ if (cv)
+ return cv;
+ return storedValue;
+ }
+ //not db-aware data:
+ const QVariant *cv = m_data->rowEditBuffer()->at( tvcol->field()->name() );
+ if (cv)
+ return cv;
+ }
+ //not db-aware data:
+ const int realFieldNumber = fieldNumberForColumn(col);
+ if (realFieldNumber < 0) {
+ kdWarning() << "KexiDataAwareObjectInterface::bufferedValueAt(): "
+ "fieldNumberForColumn(m_curCol) < 0" << endl;
+ return 0;
+ }
+ return &m_currentItem->at( realFieldNumber );
+}
+
+void KexiDataAwareObjectInterface::startEditOrToggleValue()
+{
+ if ( !isReadOnly() && columnEditable(m_curCol) ) {
+ if (columnType(m_curCol) == KexiDB::Field::Boolean) {
+ boolToggled();
+ }
+ else {
+ startEditCurrentCell();
+ return;
+ }
+ }
+}
+
+void KexiDataAwareObjectInterface::boolToggled()
+{
+ startEditCurrentCell();
+ if (m_editor) {
+ m_editor->clickedOnContents();
+ }
+ acceptEditor();
+ updateCell(m_curRow, m_curCol);
+
+/* int s = m_currentItem->at(m_curCol).toInt();
+ QVariant oldValue=m_currentItem->at(m_curCol);
+ (*m_currentItem)[m_curCol] = QVariant(s ? 0 : 1);
+ updateCell(m_curRow, m_curCol);
+// emit itemChanged(m_currentItem, m_curRow, m_curCol, oldValue);
+// emit itemChanged(m_currentItem, m_curRow, m_curCol);*/
+}
+
+void KexiDataAwareObjectInterface::slotDataDestroying()
+{
+ m_data = 0;
+ m_itemIterator = 0;
+}
+
+void KexiDataAwareObjectInterface::addNewRecordRequested()
+{
+ if (!isInsertingEnabled())
+ return;
+ if (m_rowEditing) {
+ if (!acceptRowEdit())
+ return;
+ }
+// setFocus();
+ selectRow(rows());
+ startEditCurrentCell();
+ if (m_editor)
+ m_editor->setFocus();
+}
+
+bool KexiDataAwareObjectInterface::handleKeyPress(QKeyEvent *e, int &curRow, int &curCol,
+ bool fullRowSelection, bool *moveToFirstField, bool *moveToLastField)
+{
+ if (moveToFirstField)
+ *moveToFirstField = false;
+ if (moveToLastField)
+ *moveToLastField = false;
+
+ const bool nobtn = e->state()==Qt::NoButton;
+ const int k = e->key();
+ //kdDebug() << "-----------" << e->state() << " " << k << endl;
+
+ if ((k == Qt::Key_Up && nobtn) || (k == Qt::Key_PageUp && e->state()==Qt::ControlButton)) {
+ selectPrevRow();
+ e->accept();
+ }
+ else if ((k == Qt::Key_Down && nobtn) || (k == Qt::Key_PageDown && e->state()==Qt::ControlButton)) {
+ selectNextRow();
+ e->accept();
+ }
+ else if (k == Qt::Key_PageUp && nobtn) {
+ selectPrevPage();
+ e->accept();
+ }
+ else if (k == Qt::Key_PageDown && nobtn) {
+ selectNextPage();
+ e->accept();
+ }
+ else if (k == Qt::Key_Home) {
+ if (fullRowSelection) {
+ //we're in row-selection mode: home key always moves to 1st row
+ curRow = 0;//to 1st row
+ }
+ else {//cell selection mode: different actions depending on ctrl and shift keys state
+ if (nobtn) {
+ curCol = 0;//to 1st col
+ }
+ else if (e->state()==Qt::ControlButton) {
+ curRow = 0;//to 1st row and col
+ curCol = 0;
+ }
+ else
+ return false;
+ }
+ if (moveToFirstField)
+ *moveToFirstField = true;
+ //do not accept yet
+ e->ignore();
+ }
+ else if (k == Qt::Key_End) {
+ if (fullRowSelection) {
+ //we're in row-selection mode: home key always moves to last row
+ curRow = m_data->count()-1+(isInsertingEnabled()?1:0);//to last row
+ }
+ else {//cell selection mode: different actions depending on ctrl and shift keys state
+ if (nobtn) {
+ curCol = columns()-1;//to last col
+ }
+ else if (e->state()==Qt::ControlButton) {
+ curRow = m_data->count()-1 /*+(isInsertingEnabled()?1:0)*/; //to last row and col
+ curCol = columns()-1;//to last col
+ }
+ else
+ return false;
+ }
+ if (moveToLastField)
+ *moveToLastField = true;
+ //do not accept yet
+ e->ignore();
+ }
+ else if (isInsertingEnabled() && (e->state()==Qt::ControlButton && k == Qt::Key_Equal
+ || e->state()==(Qt::ControlButton|Qt::ShiftButton) && k == Qt::Key_Equal)) {
+ curRow = m_data->count(); //to the new row
+ curCol = 0;//to first col
+ if (moveToFirstField)
+ *moveToFirstField = true;
+ //do not accept yet
+ e->ignore();
+ }
+ else
+ return false;
+
+ return true;
+}
+
+void KexiDataAwareObjectInterface::vScrollBarValueChanged(int v)
+{
+ Q_UNUSED(v);
+ if (!m_vScrollBarValueChanged_enabled)
+ return;
+
+ if (m_scrollbarToolTipsEnabled) {
+ const QRect r( verticalScrollBar()->sliderRect() );
+ const int row = lastVisibleRow()+1;
+ if (row<=0) {
+ m_scrollBarTipTimer.stop();
+ m_scrollBarTip->hide();
+ return;
+ }
+ m_scrollBarTip->setText( i18n("Row: ") + QString::number(row) );
+ m_scrollBarTip->adjustSize();
+ QWidget* thisWidget = dynamic_cast<QWidget*>(this);
+ m_scrollBarTip->move(
+ thisWidget->mapToGlobal( r.topLeft() + verticalScrollBar()->pos() )
+ + QPoint( - m_scrollBarTip->width()-5,
+ r.height()/2 - m_scrollBarTip->height()/2) );
+ if (verticalScrollBar()->draggingSlider()) {
+ kdDebug(44021) << " draggingSlider() " << endl;
+ m_scrollBarTipTimer.stop();
+ m_scrollBarTip->show();
+ m_scrollBarTip->raise();
+ }
+ else {
+ m_scrollBarTipTimerCnt++;
+ if (m_scrollBarTipTimerCnt>4) {
+ m_scrollBarTipTimerCnt=0;
+ m_scrollBarTip->show();
+ m_scrollBarTip->raise();
+ m_scrollBarTipTimer.start(500, true);
+ }
+ }
+ }
+ //update bottom view region
+/* if (m_navPanel && (contentsHeight() - contentsY() - clipper()->height()) <= QMAX(d->rowHeight,m_navPanel->height())) {
+ slotUpdate();
+ triggerUpdate();
+ }*/
+}
+
+bool KexiDataAwareObjectInterface::scrollbarToolTipsEnabled() const
+{
+ return m_scrollbarToolTipsEnabled;
+}
+
+void KexiDataAwareObjectInterface::setScrollbarToolTipsEnabled(bool set)
+{
+ m_scrollbarToolTipsEnabled = set;
+}
+
+void KexiDataAwareObjectInterface::vScrollBarSliderReleased()
+{
+ kdDebug(44021) << "vScrollBarSliderReleased()" << endl;
+ m_scrollBarTip->hide();
+}
+
+void KexiDataAwareObjectInterface::scrollBarTipTimeout()
+{
+ if (m_scrollBarTip->isVisible()) {
+// kdDebug(44021) << "TIMEOUT! - hide" << endl;
+ if (m_scrollBarTipTimerCnt>0) {
+ m_scrollBarTipTimerCnt=0;
+ m_scrollBarTipTimer.start(500, true);
+ return;
+ }
+ m_scrollBarTip->hide();
+ }
+ m_scrollBarTipTimerCnt=0;
+}
+
+void KexiDataAwareObjectInterface::focusOutEvent(QFocusEvent* e)
+{
+ Q_UNUSED(e);
+ m_scrollBarTipTimer.stop();
+ m_scrollBarTip->hide();
+
+ updateCell(m_curRow, m_curCol);
+}
+
+int KexiDataAwareObjectInterface::showErrorMessageForResult(KexiDB::ResultInfo* resultInfo)
+{
+ QWidget *thisWidget = dynamic_cast<QWidget*>(this);
+ if (resultInfo->allowToDiscardChanges) {
+ return KMessageBox::questionYesNo(thisWidget, resultInfo->msg
+ + (resultInfo->desc.isEmpty() ? QString::null : ("\n"+resultInfo->desc)),
+ QString::null,
+ KGuiItem(i18n("Correct Changes", "Correct"), QString::null, i18n("Correct changes")),
+ KGuiItem(i18n("Discard Changes")) );
+ }
+
+ if (resultInfo->desc.isEmpty())
+ KMessageBox::sorry(thisWidget, resultInfo->msg);
+ else
+ KMessageBox::detailedSorry(thisWidget, resultInfo->msg, resultInfo->desc);
+
+ return KMessageBox::Ok;
+}
+
+void KexiDataAwareObjectInterface::updateIndicesForVisibleValues()
+{
+ m_indicesForVisibleValues.resize( m_data ? m_data->columnsCount() : 0 );
+ if (!m_data)
+ return;
+ for (uint i=0; i < m_data->columnsCount(); i++) {
+ KexiTableViewColumn* tvCol = m_data->column(i);
+ if (tvCol->columnInfo && tvCol->columnInfo->indexForVisibleLookupValue()!=-1)
+ // retrieve visible value from lookup field
+ m_indicesForVisibleValues[ i ] = tvCol->columnInfo->indexForVisibleLookupValue();
+ else
+ m_indicesForVisibleValues[ i ] = i;
+ }
+}
+
+/*! Performs searching \a stringValue in \a where string.
+ \a matchAnyPartOfField, \a matchWholeField, \a wholeWordsOnly options are used to control how to search.
+
+ If \a matchWholeField is true, \a wholeWordsOnly is not checked.
+ \a firstCharacter is in/out parameter. If \a matchAnyPartOfField is true and \a matchWholeField is false,
+ \a firstCharacter >= 0, the search will be performed after skipping first \a firstCharacter characters.
+
+ If \a forward is false, we are searching backwart from \a firstCharacter position. \a firstCharacter == -1
+ means then the last character. \a firstCharacter == INT_MAX means "before first" place, so searching fails
+ immediately.
+ On success, true is returned and \a firstCharacter is set to position of the matched string. */
+static inline bool findInString(const QString& stringValue, int stringLength, const QString& where,
+ int& firstCharacter, bool matchAnyPartOfField, bool matchWholeField,
+ bool caseSensitive, bool wholeWordsOnly, bool forward)
+{
+ if (where.isEmpty()) {
+ firstCharacter = -1;
+ return false;
+ }
+
+ if (matchAnyPartOfField) {
+ if (forward) {
+ int pos = firstCharacter == -1 ? 0 : firstCharacter;
+ if (wholeWordsOnly) {
+ const int whereLength = where.length();
+ while (true) {
+ pos = where.find( stringValue, pos, caseSensitive );
+ if (pos == -1)
+ break;
+ if ((pos > 0 && where.at(pos-1).isLetterOrNumber())
+ ||((pos+stringLength-1) < (whereLength-1) && where.at(pos+stringLength-1+1).isLetterOrNumber()))
+ {
+ pos++; // invalid match because before or after the string there is non-white space
+ }
+ else
+ break;
+ }//while
+ firstCharacter = pos;
+ }
+ else {// !wholeWordsOnly
+ firstCharacter = where.find( stringValue, pos, caseSensitive );
+ }
+ return firstCharacter != -1;
+ }
+ else { // !matchAnyPartOfField
+ if (firstCharacter == INT_MAX) {
+ firstCharacter = -1; //next time we'll be looking at different cell
+ return false;
+ }
+ int pos = firstCharacter;
+ if (wholeWordsOnly) {
+ const int whereLength = where.length();
+ while (true) {
+ pos = where.findRev( stringValue, pos, caseSensitive );
+ if (pos == -1)
+ break;
+ if ((pos > 0 && where.at(pos-1).isLetterOrNumber())
+ ||((pos+stringLength-1) < (whereLength-1) && where.at(pos+stringLength-1+1).isLetterOrNumber()))
+ {
+ // invalid match because before or after the string there is non-white space
+ pos--;
+ if (pos < 0) // it can make pos < 0
+ break;
+ }
+ else
+ break;
+ }//while
+ firstCharacter = pos;
+ }
+ else {// !wholeWordsOnly
+ firstCharacter = where.findRev( stringValue, pos, caseSensitive );
+ }
+ return firstCharacter != -1;
+ }
+ }
+ else if (matchWholeField) {
+ if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char
+ firstCharacter = -1;
+ }
+ else if ( (caseSensitive ? where : where.lower()) == stringValue) {
+ firstCharacter = 0;
+ return true;
+ }
+ }
+ else {// matchStartOfField
+ if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char
+ firstCharacter = -1;
+ }
+ else if (where.startsWith(stringValue, caseSensitive)) {
+ if (wholeWordsOnly) {
+ // If where.length() < stringValue.length(), true will be returned too - fine.
+ return !where.at( stringValue.length() ).isLetterOrNumber();
+ }
+ firstCharacter = 0;
+ return true;
+ }
+ }
+ return false;
+}
+
+tristate KexiDataAwareObjectInterface::find(const QVariant& valueToFind,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool next)
+{
+ if (!hasData())
+ return cancelled;
+ const QVariant prevSearchedValue( m_recentlySearchedValue );
+ m_recentlySearchedValue = valueToFind;
+ const KexiSearchAndReplaceViewInterface::Options::SearchDirection prevSearchDirection = m_recentSearchDirection;
+ m_recentSearchDirection = options.searchDirection;
+ if (valueToFind.isNull() || valueToFind.toString().isEmpty())
+ return cancelled;
+
+ const bool forward = (options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchUp)
+ ? !next : next; //direction can be reversed
+
+ if ((!prevSearchedValue.isNull() && prevSearchedValue!=valueToFind)
+ || (prevSearchDirection!=options.searchDirection && options.searchDirection==KexiSearchAndReplaceViewInterface::Options::SearchAllRows))
+ {
+ // restart searching when value has been changed or new direction is SearchAllRows
+ m_positionOfRecentlyFoundValue.exists = false;
+ }
+
+ const bool startFrom1stRowAndCol = !m_positionOfRecentlyFoundValue.exists && next
+ && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRows;
+ const bool startFromLastRowAndCol =
+ (!m_positionOfRecentlyFoundValue.exists && !next && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRows)
+ ||(m_curRow >= rows() && !forward); //we're at "insert" row, and searching backwards: move to the last cell
+
+ if (!startFrom1stRowAndCol && !startFromLastRowAndCol && m_curRow >= rows()) {
+ //we're at "insert" row, and searching forward: no chances to find something
+ return false;
+ }
+ KexiTableViewData::Iterator it( (startFrom1stRowAndCol || startFromLastRowAndCol)
+ ? m_data->iterator() : *m_itemIterator /*start from the current cell*/ );
+ if (startFromLastRowAndCol)
+ it.toLast();
+ int firstCharacter;
+ if (m_positionOfRecentlyFoundValue.exists) {// start after the next/prev char position
+ if (forward)
+ firstCharacter = m_positionOfRecentlyFoundValue.lastCharacter + 1;
+ else {
+ firstCharacter = (m_positionOfRecentlyFoundValue.firstCharacter > 0) ?
+ (m_positionOfRecentlyFoundValue.firstCharacter - 1) : INT_MAX /* this means 'before first'*/;
+ }
+ }
+ else {
+ firstCharacter = -1; //forward ? -1 : INT_MAX;
+ }
+
+ const int columnsCount = m_data->columnsCount();
+ int row, col;
+ if (startFrom1stRowAndCol) {
+ row = 0;
+ col = 0;
+ }
+ else if (startFromLastRowAndCol) {
+ row = rows()-1;
+ col = columnsCount-1;
+ }
+ else {
+ row = m_curRow;
+ col = m_curCol;
+ }
+
+ //sache some flags for efficiency
+ const bool matchAnyPartOfField
+ = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchAnyPartOfField;
+ const bool matchWholeField
+ = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchWholeField;
+ const bool caseSensitive = options.caseSensitive;
+ const bool wholeWordsOnly = options.wholeWordsOnly;
+//unused const bool promptOnReplace = options.promptOnReplace;
+ int columnNumber = (options.columnNumber == KexiSearchAndReplaceViewInterface::Options::CurrentColumn)
+ ? m_curCol : options.columnNumber;
+ if (columnNumber>=0)
+ col = columnNumber;
+ const bool lookInAllColumns = columnNumber == KexiSearchAndReplaceViewInterface::Options::AllColumns;
+ int firstColumn; // real number of the first column, can be smaller than lastColumn if forward==true
+ int lastColumn; // real number of the last column
+ if (lookInAllColumns) {
+ firstColumn = forward ? 0 : columnsCount-1;
+ lastColumn = forward ? columnsCount-1 : 0;
+ }
+ else {
+ firstColumn = columnNumber;
+ lastColumn = columnNumber;
+ }
+ const QString stringValue( caseSensitive ? valueToFind.toString() : valueToFind.toString().lower() );
+ const int stringLength = stringValue.length();
+
+ // search
+ const int prevRow = m_curRow;
+ KexiTableItem *item;
+ while ( (item = it.current()) ) {
+ for (; forward ? col <= lastColumn : col >= lastColumn;
+ col = forward ? (col+1) : (col-1))
+ {
+ const QVariant cell( item->at( m_indicesForVisibleValues[ col ] ) );
+ if (findInString(stringValue, stringLength, cell.toString(), firstCharacter,
+ matchAnyPartOfField, matchWholeField, caseSensitive, wholeWordsOnly, forward))
+ {
+ //*m_itemIterator = it;
+ //m_currentItem = *it;
+ //m_curRow = row;
+ //m_curCol = col;
+ setCursorPosition(row, col, true/*forceSet*/);
+ if (prevRow != m_curRow)
+ updateRow(prevRow);
+ // remember the exact position for the found value
+ m_positionOfRecentlyFoundValue.exists = true;
+ m_positionOfRecentlyFoundValue.firstCharacter = firstCharacter;
+//! @todo for regexp lastCharacter should be computed
+ m_positionOfRecentlyFoundValue.lastCharacter = firstCharacter + stringLength - 1;
+ return true;
+ }
+ }//for
+ if (forward) {
+ ++it;
+ ++row;
+ }
+ else {
+ --it;
+ --row;
+ }
+ col = firstColumn;
+ }//while
+ return false;
+}
+
+tristate KexiDataAwareObjectInterface::findNextAndReplace(
+ const QVariant& valueToFind, const QVariant& replacement,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll)
+{
+ Q_UNUSED(replacement);
+ Q_UNUSED(options);
+ Q_UNUSED(replaceAll);
+
+ if (isReadOnly())
+ return cancelled;
+ if (valueToFind.isNull() || valueToFind.toString().isEmpty())
+ return cancelled;
+ //! @todo implement KexiDataAwareObjectInterface::findAndReplace()
+ return false;
+}
diff --git a/kexi/widget/tableview/kexidataawareobjectiface.h b/kexi/widget/tableview/kexidataawareobjectiface.h
new file mode 100644
index 000000000..4cf2aa6ae
--- /dev/null
+++ b/kexi/widget/tableview/kexidataawareobjectiface.h
@@ -0,0 +1,918 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ Based on KexiTableView code.
+ Copyright (C) 2002 Till Busch <till@bux.at>
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDATAAWAREOBJECTINTERFACE_H
+#define KEXIDATAAWAREOBJECTINTERFACE_H
+
+#include <qguardedptr.h>
+
+#include <qtimer.h>
+#include <kdebug.h>
+#include <widget/utils/kexiarrowtip.h>
+#include <kexisearchandreplaceiface.h>
+#include "kexitableviewdata.h"
+
+class QObject;
+class QScrollBar;
+class KPopupMenu;
+class KexiTableItem;
+class KexiTableViewData;
+class KexiRecordMarker;
+class KexiTableViewHeader;
+class KexiRecordNavigator;
+#include <kexidataiteminterface.h>
+
+namespace KexiDB {
+ class RowEditBuffer;
+}
+
+//! default column width in pixels
+#define KEXI_DEFAULT_DATA_COLUMN_WIDTH 120
+
+//! \brief The KexiDataAwareObjectInterface is an interface for record-based data object.
+/** This interface is implemented by KexiTableView and KexiFormView
+ and used by KexiDataAwareView. If yu're implementing this interface,
+ add KEXI_DATAAWAREOBJECTINTERFACE convenience macro just after Q_OBJECT.
+
+ You should add following code to your destructor so data is deleted:
+ \code
+ if (m_owner)
+ delete m_data;
+ m_data = 0;
+ \endcode
+ This is not performed in KexiDataAwareObjectInterface because you may need
+ to access m_data in your desctructor.
+*/
+class KEXIDATATABLE_EXPORT KexiDataAwareObjectInterface
+{
+ public:
+ KexiDataAwareObjectInterface();
+ virtual ~KexiDataAwareObjectInterface();
+
+ /*! Sets data for this object. if \a owner is true, the object will own
+ \a data and therefore will be destroyed when needed, else: \a data is (possibly) shared and
+ not owned by the widget.
+ If widget already has _different_ data object assigned (and owns this data),
+ old data is destroyed before new assignment.
+ */
+ void setData( KexiTableViewData *data, bool owner = true );
+
+ /*! \return data structure displayed for this object */
+ inline KexiTableViewData *data() const { return m_data; }
+
+ /*! \return currently selected column number or -1. */
+ inline int currentColumn() const { return m_curCol; }
+
+ /*! \return currently selected row number or -1. */
+ inline int currentRow() const { return m_curRow; }
+
+ /*! \return last row visible on the screen (counting from 0).
+ The returned value is guaranteed to be smaller or equal to currentRow() or -1
+ if there are no rows. */
+ virtual int lastVisibleRow() const = 0;
+
+ /*! \return currently selected item (row data) or null. */
+ KexiTableItem *selectedItem() const { return m_currentItem; }
+
+ /*! \return number of rows in this view. */
+ int rows() const;
+
+ /*! \return number of visible columns in this view.
+ By default returns dataColumns(), what is proper table view.
+ In case of form view, there can be a number of duplicated columns defined
+ (data-aware widgets, see KexiFormScrollView::columns()),
+ so columns() can return greater number than dataColumns(). */
+ virtual int columns() const { return dataColumns(); }
+
+ /*! Helper function.
+ \return number of columns of data. */
+ int dataColumns() const;
+
+ /*! \return true if data represented by this object
+ is not editable (it can be editable with other ways although,
+ outside of this object). */
+ virtual bool isReadOnly() const;
+
+ /*! Sets readOnly flag for this object.
+ Unless the flag is set, the widget inherits readOnly flag from it's data
+ structure assigned with setData(). The default value if false.
+
+ This method is useful when you need to switch on the flag indepentently
+ from the data structure.
+ Note: it is not allowed to force readOnly off
+ when internal data is readOnly - in that case the method does nothing.
+ You can check internal data flag calling data()->isReadOnly().
+
+ If \a set is true, insertingEnabled flag will be cleared automatically.
+ \sa isInsertingEnabled()
+ */
+ void setReadOnly(bool set);
+
+ /*! \return true if sorting is enabled. */
+ inline bool isSortingEnabled() const { return m_isSortingEnabled; }
+
+ /*! Sets sorting on column \a col, or (when \a col == -1) sets rows unsorted
+ this will do not work if sorting is disabled with setSortingEnabled() */
+ virtual void setSorting(int col, bool ascending=true);
+
+ /*! Enables or disables sorting for this object
+ This method is different that setSorting() because it prevents both user
+ and programmer from sorting by clicking a column's header or calling setSorting().
+ By default sorting is enabled.
+ */
+ virtual void setSortingEnabled(bool set);
+
+ /*! \return sorted column number or -1 if no column is sorted within data.
+ This does not mean that any sorting has been performed within GUI of this object,
+ because the data could be changed in the meantime outside of this GUI object. */
+ int dataSortedColumn() const;
+
+ /*! \return 1 if ascending order for data sorting data is set, -1 for descending,
+ 0 for no sorting.
+ This does not mean that any sorting has been performed within GUI of this objetct,
+ because the data could be changed in the meantime outside of this GUI object.
+ */
+ int dataSortingOrder() const;
+
+ /*! Sorts all rows by column selected with setSorting().
+ If there is currently row edited, it is accepted.
+ If acception failed, sort() will return false.
+ \return true on success. */
+ virtual bool sort();
+
+ /*! Sorts currently selected column in ascending order.
+ This slot is used typically for "data_sort_az" action. */
+ void sortAscending();
+
+ /*! Sorts currently selected column in descending order.
+ This slot is used typically for "data_sort_za" action. */
+ void sortDescending();
+
+ /*! \return true if data inserting is enabled (the default). */
+ virtual bool isInsertingEnabled() const;
+
+ /*! Sets insertingEnabled flag. If true, empty row is available
+ at the end of this widget for new entering new data.
+ Unless the flag is set, the widget inherits insertingEnabled flag from it's data
+ structure assigned with setData(). The default value if false.
+
+ Note: it is not allowed to force insertingEnabled on when internal data
+ has insertingEnabled set off - in that case the method does nothing.
+ You can check internal data flag calling data()->insertingEnabled().
+
+ Setting this flag to true will have no effect if read-only flag is true.
+ \sa setReadOnly()
+ */
+ void setInsertingEnabled(bool set);
+
+ /*! \return true if row deleting is enabled.
+ Equal to deletionPolicy() != NoDelete && !isReadOnly()). */
+ bool isDeleteEnabled() const;
+
+ /*! \return true if inserting empty rows are enabled (false by default).
+ Mostly usable for not db-aware objects (e.g. used in Kexi Alter Table).
+ Note, that if inserting is disabled, or the data set is read-only,
+ this flag will be ignored. */
+ bool isEmptyRowInsertingEnabled() const { return m_emptyRowInsertingEnabled; }
+
+ /*! Sets emptyRowInserting flag.
+ Note, that if inserting is disabled, this flag is ignored. */
+ void setEmptyRowInsertingEnabled(bool set);
+
+ /*! Enables or disables filtering. Filtering is enabled by default. */
+ virtual void setFilteringEnabled(bool set);
+
+ /*! \return true if filtering is enabled. */
+ inline bool isFilteringEnabled() const { return m_isFilteringEnabled; }
+
+ /*! Added for convenience: configure this object
+ to behave more like spreadsheet (it's used for things like alter-table view).
+ - hides navigator
+ - disables sorting, inserting and filtering
+ - enables accepting row after cell accepting; see setAcceptsRowEditAfterCellAccepting()
+ - enables inserting empty row; see setEmptyRowInsertingEnabled() */
+ virtual void setSpreadSheetMode();
+
+ /*! \return true id "spreadSheetMode" is enabled. It's false by default. */
+ bool spreadSheetMode() const { return m_spreadSheetMode; }
+
+ /*! \return true if currently selected row is edited. */
+ inline bool rowEditing() const { return m_rowEditing; }
+
+ enum DeletionPolicy
+ {
+ NoDelete = 0,
+ AskDelete = 1,
+ ImmediateDelete = 2,
+ SignalDelete = 3
+ };
+
+ /*! \return deletion policy for this object.
+ The default (after allocating) is AskDelete. */
+ DeletionPolicy deletionPolicy() const { return m_deletionPolicy; }
+
+ virtual void setDeletionPolicy(DeletionPolicy policy);
+
+ /*! Deletes currently selected record; does nothing if no record
+ is currently selected. If record is in edit mode, editing
+ is cancelled before deleting. */
+ virtual void deleteCurrentRow();
+
+ /*! Inserts one empty row above row \a row. If \a row is -1 (the default),
+ new row is inserted above the current row (or above 1st row if there is no current).
+ A new item becomes current if row is -1 or if row is equal currentRow().
+ This method does nothing if:
+ -inserting flag is disabled (see isInsertingEnabled())
+ -read-only flag is set (see isReadOnly())
+ \ return inserted row's data
+ */
+ virtual KexiTableItem *insertEmptyRow(int row = -1);
+
+ /*! For reimplementation: called by deleteItem(). If returns false, deleting is aborted.
+ Default implementation just returns true. */
+ virtual bool beforeDeleteItem(KexiTableItem *item);
+
+ /*! Deletes \a item. Used by deleteCurrentRow(). Calls beforeDeleteItem() before deleting,
+ to double-check if deleting is allowed.
+ \return true on success. */
+ bool deleteItem(KexiTableItem *item);//, bool moveCursor=true);
+
+ /*! Inserts newItem at \a row. -1 means current row. Used by insertEmptyRow(). */
+ void insertItem(KexiTableItem *newItem, int row = -1);
+
+ /*! Clears entire table data, its visible representation
+ and deletes data at database backend (if this is db-aware object).
+ Does not clear columns information.
+ Does not destroy KexiTableViewData object (if present) but only clears its contents.
+ Displays confirmation dialog if \a ask is true (the default is false).
+ Repaints widget if \a repaint is true (the default).
+ For empty tables, true is returned immediately.
+ If isDeleteEnabled() is false, false is returned.
+ For spreadsheet mode all current rows are just replaced by empty rows.
+ \return true on success, false on failure, and cancelled if user cancelled deletion
+ (only possible if \a ask is true).
+ */
+ tristate deleteAllRows(bool ask = false, bool repaint = true);
+
+ /*! \return maximum number of rows that can be displayed per one "page"
+ for current view's size. */
+ virtual int rowsPerPage() const = 0;
+
+ virtual void selectRow(int row);
+ virtual void selectNextRow();
+ virtual void selectPrevRow();
+ virtual void selectNextPage(); //!< page down action
+ virtual void selectPrevPage(); //!< page up action
+ virtual void selectFirstRow();
+ virtual void selectLastRow();
+ virtual void addNewRecordRequested();
+
+ /*! Clears current selection. Current row and column will be now unspecified:
+ currentRow(), currentColumn() will return -1, and selectedItem() will return null. */
+ virtual void clearSelection();
+
+ /*! Moves cursor to \a row and \a col. If \a col is -1, current column number is used.
+ If forceSet is true, cursor position is updated even if \a row and \a col doesn't
+ differ from actual position. */
+ virtual void setCursorPosition(int row, int col = -1, bool forceSet = false);
+
+ /*! Ensures that cell at \a row and \a col is visible.
+ If \a col is -1, current column number is used. \a row and \a col (if not -1) must
+ be between 0 and rows() (or cols() accordingly). */
+ virtual void ensureCellVisible(int row, int col/*=-1*/) = 0;
+
+ /*! Specifies, if this object automatically accepts
+ row editing (using acceptRowEdit()) on accepting any cell's edit
+ (i.e. after acceptEditor()). \sa acceptsRowEditAfterCellAccepting() */
+ virtual void setAcceptsRowEditAfterCellAccepting(bool set);
+
+ /*! \return true, if this object automatically accepts
+ row editing (using acceptRowEdit()) on accepting any cell's edit
+ (i.e. after acceptEditor()).
+ By default this flag is set to false.
+ Not that if the query for this table has given constraints defined,
+ like NOT NULL / NOT EMPTY for more than one field - editing a record would
+ be impossible for the flag set to true, because of constraints violation.
+ However, setting this flag to true can be useful especially for not-db-aware
+ data set (it's used e.g. in Kexi Alter Table's field editor). */
+ bool acceptsRowEditAfterCellAccepting() const { return m_acceptsRowEditAfterCellAccepting; }
+
+ /*! \return true, if this table accepts dropping data on the rows. */
+ bool dropsAtRowEnabled() const { return m_dropsAtRowEnabled; }
+
+ /*! Specifies, if this table accepts dropping data on the rows.
+ If enabled:
+ - dragging over row is indicated by drawing a line at bottom side of this row
+ - dragOverRow() signal will be emitted on dragging,
+ -droppedAtRow() will be emitted on dropping
+ By default this flag is set to false. */
+ virtual void setDropsAtRowEnabled(bool set);
+
+ /*! \return currently used data (field/cell) editor or 0 if there is no data editing. */
+ inline KexiDataItemInterface *editor() const { return m_editor; }
+
+ /*! Cancels row editing All changes made to the editing
+ row during this current session will be undone.
+ \return true on success or false on failure (e.g. when editor does not exist) */
+ virtual bool cancelRowEdit();
+
+ /*! Accepts row editing. All changes made to the editing
+ row during this current session will be accepted (saved).
+ \return true if accepting was successful, false otherwise
+ (e.g. when current row contain data that does not meet given constraints). */
+ virtual bool acceptRowEdit();
+
+ virtual void removeEditor();
+
+ /*! Cancels changes made to the currently active editor.
+ Reverts the editor's value to old one.
+ \return true on success or false on failure (e.g. when editor does not exist) */
+ virtual bool cancelEditor();
+
+ //! Accepst changes made to the currently active editor.
+ //! \return true on success or false on failure (e.g. when editor does not exist or there is data validation error)
+ virtual bool acceptEditor();
+
+ //! Creates editors and shows it, what usually means the beginning of a cell editing
+ virtual void createEditor(int row, int col, const QString& addText = QString::null,
+ bool removeOld = false) = 0;
+
+ /*! Used when Return key is pressed on cell, the cell has been double clicked
+ or "+" navigator's button is clicked.
+ Also used when we want to continue editing a cell after "invalid value" message
+ was displayed (in this case, \a setText is usually not empty, what means
+ that text will be set in the cell replacing previous value).
+ */
+ virtual void startEditCurrentCell(const QString& setText = QString::null);
+
+ /*! Deletes currently selected cell's contents, if allowed.
+ In most cases delete is not accepted immediately but "row editing" mode is just started. */
+ virtual void deleteAndStartEditCurrentCell();
+
+ inline KexiTableItem *itemAt(int row) const;
+
+ /*! \return column information for column number \a col.
+ Default implementation just returns column # col,
+ but for Kexi Forms column data
+ corresponding to widget number is used here
+ (see KexiFormScrollView::fieldNumberForColumn()). */
+ virtual KexiTableViewColumn* column(int col);
+
+ /*! \return field number within data model connected to a data-aware
+ widget at column \a col. Can return -1 if there's no such column. */
+ virtual int fieldNumberForColumn(int col) { return col; }
+
+ bool hasDefaultValueAt(const KexiTableViewColumn& tvcol);
+
+ const QVariant* bufferedValueAt(int col, bool useDefaultValueIfPossible = true);
+
+ //! \return a type of column \a col - one of KexiDB::Field::Type
+ int columnType(int col);
+
+ //! \return default value for column \a col
+ QVariant columnDefaultValue(int col) const;
+
+ /*! \return true is column \a col is editable.
+ Default implementation takes information about 'readOnly' flag from data member.
+ Within forms, this is reimplemented for checking 'readOnly' flag from a widget
+ ('readOnly' flag from data member is still checked though).
+ */
+ virtual bool columnEditable(int col);
+
+ /*! Redraws the current cell. To be implemented. */
+ virtual void updateCurrentCell() = 0;
+
+ inline KexiRecordMarker* verticalHeader() const { return m_verticalHeader; }
+
+ //! signals
+ virtual void itemChanged(KexiTableItem *, int row, int col) = 0;
+ virtual void itemChanged(KexiTableItem *, int row, int col, QVariant oldValue) = 0;
+ virtual void itemDeleteRequest(KexiTableItem *, int row, int col) = 0;
+ virtual void currentItemDeleteRequest() = 0;
+ //! Emitted for spreadsheet mode when an item was deleted and a new item has been appended
+ virtual void newItemAppendedForAfterDeletingInSpreadSheetMode() = 0;
+
+ /*! Data has been refreshed on-screen - emitted from initDataContents(). */
+ virtual void dataRefreshed() = 0;
+ virtual void dataSet( KexiTableViewData *data ) = 0;
+
+ /*! \return a pointer to context menu. This can be used to plug some actions there. */
+ KPopupMenu* contextMenu() const { return m_popupMenu; }
+
+ /*! \return true if the context menu is enabled (visible) for the view.
+ True by default. */
+ bool contextMenuEnabled() const { return m_contextMenuEnabled; }
+
+ /*! Enables or disables the context menu for the view. */
+ void setContextMenuEnabled(bool set) { m_contextMenuEnabled = set; }
+
+ /*! \return true if vertical scrollbar's tooltips are enabled (true by default). */
+ bool scrollbarToolTipsEnabled() const;
+
+ /*! Enables or disables vertical scrollbar's tooltip. */
+ void setScrollbarToolTipsEnabled(bool set);
+
+ /*! Typically handles pressing Enter or F2 key:
+ if current cell has boolean type, toggles it's value,
+ otherwise starts editing (startEditCurrentCell()). */
+ void startEditOrToggleValue();
+
+ /*! \return true if new row is edited; implies: rowEditing==true. */
+ inline bool newRowEditing() const { return m_newRowEditing; }
+
+ /*! Reaction on toggling a boolean value of a cell:
+ we're starting to edit the cell and inverting it's state. */
+ virtual void boolToggled();
+
+ virtual void connectCellSelectedSignal(const QObject* receiver,
+ const char* intIntMember) = 0;
+
+ virtual void connectRowEditStartedSignal(const QObject* receiver,
+ const char* intMember) = 0;
+
+ virtual void connectRowEditTerminatedSignal(const QObject* receiver,
+ const char* voidMember) = 0;
+
+ virtual void connectReloadActionsSignal(const QObject* receiver,
+ const char* voidMember) = 0;
+
+ virtual void connectDataSetSignal(const QObject* receiver,
+ const char* kexiTableViewDataMember) = 0;
+
+ virtual void connectToReloadDataSlot(const QObject* sender,
+ const char* voidSignal) = 0;
+
+ virtual void slotDataDestroying();
+
+ //! Copy current selection to a clipboard (e.g. cell)
+ virtual void copySelection() = 0;
+
+ //! Cut current selection to a clipboard (e.g. cell)
+ virtual void cutSelection() = 0;
+
+ //! Paste current clipboard contents (e.g. to a cell)
+ virtual void paste() = 0;
+
+ /*! Finds \a valueToFind within the data items
+ \a options are used to control the process. Selection is moved to found value.
+ If \a next is true, "find next" is performed, else "find previous" is performed.
+
+ Searching behaviour also depends on status of the previous search: for every search,
+ position of the cells containing the found value is stored internally
+ by the data-aware interface (not in options).
+ Moreover, position (start, end) of the found value is also stored.
+ Thus, the subsequent search will reuse this information to be able to start
+ searching exactly after the previously found value (or before for "find previous" option).
+ The flags can be zeroed, what will lead to seaching from the first character
+ of the current item (cell).
+
+ \return true if value has been found, false if value has not been found,
+ and cancelled if there is nothing to find or there is no data to search in. */
+ virtual tristate find(const QVariant& valueToFind,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool next);
+
+ /*! Finds \a valueToFind within the data items and replaces with \a replacement
+ \a options are used to control the process.
+ \return true if value has been found and replaced, false if value
+ has not been found and replaced, and cancelled if there is nothing
+ to find or there is no data to search in or the data is read only.
+ If \a replaceAll is true, all found values are replaced. */
+ virtual tristate findNextAndReplace(const QVariant& valueToFind,
+ const QVariant& replacement,
+ const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll);
+
+ /*! \return vertical scrollbar */
+ virtual QScrollBar* verticalScrollBar() const = 0;
+
+ /*! Used in KexiTableView::keyPressEvent() (and in continuous forms).
+ \return true when the key press event \e was consumed.
+ You should also check e->isAccepted(), if it's true, nothing should be done;
+ if it is false, you should call setCursorPosition() for the altered \a curCol
+ and \c curRow variables.
+
+ If \a moveToFirstField is not 0, *moveToFirstField will be set to true
+ when the cursor should be moved to the first field (in tab order) and to false otherwise.
+ If \a moveToLastField is not 0, *moveToLastField will be set to true
+ when the cursor should be moved to the last field (in tab order) and to false otherwise.
+ Note for forms: if moveToFirstField and moveToLastField are not 0,
+ \a curCol is altered after calling this method, so setCursorPosition() will set to
+ the index of an appropriate column (field). This is needed because field widgets can be
+ inserted and ordered in custom tab order, so the very first field in the data source
+ can be other than the very first field in the form.
+
+ Used by KexiTableView::keyPressEvent() and KexiTableView::keyPressEvent(). */
+ virtual bool handleKeyPress(QKeyEvent *e, int &curRow, int &curCol, bool fullRowSelection,
+ bool *moveToFirstField = 0, bool *moveToLastField = 0);
+
+ protected:
+ /*! Reimplementation for KexiDataAwareObjectInterface.
+ Initializes data contents (resizes it, sets cursor at 1st row).
+ Sets record count for record navigator.
+ Sets cursor positin (using setCursorPosition()) to first row or sets
+ (-1, -1) position if no rows are available.
+ Called on setData(). Also called once on show event after
+ refreshRequested() signal was received from KexiTableViewData object. */
+ virtual void initDataContents();
+
+ /*! Clears columns information and thus all internal table data
+ and its visible representation. Repaints widget if \a repaint is true. */
+ virtual void clearColumns(bool repaint = true);
+
+ /*! Called by clearColumns() to clear internals of the object.
+ For example, KexiTableView removes contents of it's horizontal header. */
+ virtual void clearColumnsInternal(bool repaint) = 0;
+
+ /*! @internal for implementation
+ This should append another section within horizontal header or any sort of caption
+ for a field using provided names. \a width is a hint for new field's width. */
+ virtual void addHeaderColumn(const QString& caption, const QString& description,
+ const QIconSet& icon, int size) = 0;
+
+ /*! @internal for implementation
+ \return sorting order (within GUI): -1: descending, 1: ascending, 0: no sorting.
+ This does not mean that any sorting has been performed within GUI of this object,
+ because the data could be changed in the meantime outside of this GUI object.
+ @see dataSortingOrder()*/
+ virtual int currentLocalSortingOrder() const = 0;
+
+ /*! @internal for implementation
+ \return sorted column number for this widget or -1 if no column
+ is sorted witin GUI.
+ This does not mean that the same sorting is performed within data member
+ which is used by this widget, because the data could be changed in the meantime
+ outside of this GUI widget.
+ @see dataSortedColumn() */
+ virtual int currentLocalSortColumn() const = 0;
+
+ /*! @internal for implementation
+ Shows sorting indicator order within GUI: -1: descending, 1: ascending,
+ 0: no sorting. This should not perform any sorting within data member
+ which is used by this object.
+ col = -1 should mean "no sorting" as well. */
+ virtual void setLocalSortingOrder(int col, int order) = 0;
+
+ /*! @internal Sets order for \a column: -1: descending, 1: ascending,
+ 0: invert order */
+ virtual void sortColumnInternal(int col, int order = 0);
+
+ /*! @internal for implementation
+ Updates GUI after sorting.
+ After sorting you need to ensure current row and column
+ is visible to avoid user confusion. For exaple, in KexiTableView
+ implementation, current cell is centered (if possible)
+ and updateContents() is called. */
+ virtual void updateGUIAfterSorting() = 0;
+
+ /*! Emitted in initActions() to force reload actions
+ You should remove existing actions and add them again.
+ Define and emit reloadActions() signal here. */
+ virtual void reloadActions() = 0;
+
+ /*! Reloads data for this object. */
+ virtual void reloadData();
+
+ /*! for implementation as a signal */
+ virtual void itemSelected(KexiTableItem *) = 0;
+
+ /*! for implementation as a signal */
+ virtual void cellSelected(int col, int row) = 0;
+
+ /*! for implementation as a signal */
+ virtual void sortedColumnChanged(int col) = 0;
+
+ /*! for implementation as a signal */
+ virtual void rowEditTerminated(int row) = 0;
+
+ /*! Clear temporary members like the pointer to current editor.
+ If you reimplement this method, don't forget to call this one. */
+ virtual void clearVariables();
+
+ /*! @internal
+ Creates editor structure without filling it with data.
+ Used in createEditor() and few places to be able to display cell contents
+ dependending on its type. If \a ignoreMissingEditor is false (the default),
+ and editor cannot be instantiated, current row editing (if present) is cancelled.
+ */
+ virtual KexiDataItemInterface *editor( int col, bool ignoreMissingEditor = false ) = 0;
+
+ /*! Updates editor's position, size and shows its focus (not the editor!)
+ for \a row and \a col, using editor(). Does nothing if editor not found. */
+ virtual void editorShowFocus( int row, int col ) = 0;
+
+ /*! Redraws specified cell. */
+ virtual void updateCell(int row, int col) = 0;
+
+ /*! Redraws all cells of specified row. */
+ virtual void updateRow(int row) = 0;
+
+ /*! Updates contents of the widget. Just call update() here on your widget. */
+ virtual void updateWidgetContents() = 0;
+
+ /*! Updates widget's contents size e.g. using QScrollView::resizeContents(). */
+ virtual void updateWidgetContentsSize() = 0;
+
+ /*! Updates scrollbars of the widget.
+ QScrollView::updateScrollbars() will be usually called here. */
+ virtual void updateWidgetScrollBars() = 0;
+
+ /*! @internal
+ Updates row appearance after canceling row edit.
+ Used by cancelRowEdit(). By default just calls updateRow(m_curRow).
+ Reimplemented by KexiFormScrollView. */
+ virtual void updateAfterCancelRowEdit();
+
+ /*! @internal
+ Updates row appearance after accepting row edit.
+ Used by acceptRowEdit(). By default just calls updateRow(m_curRow).
+ Reimplemented by KexiFormScrollView. */
+ virtual void updateAfterAcceptRowEdit();
+
+ //! Handles KexiTableViewData::rowRepaintRequested() signal
+ virtual void slotRowRepaintRequested(KexiTableItem& item) { Q_UNUSED( item ); }
+
+ //! Handles KexiTableViewData::aboutToDeleteRow() signal. Prepares info for slotRowDeleted().
+ virtual void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result,
+ bool repaint);
+
+ //! Handles KexiTableViewData::rowDeleted() signal to repaint when needed.
+ virtual void slotRowDeleted();
+
+ //! Handles KexiTableViewData::rowInserted() signal to repaint when needed.
+ virtual void slotRowInserted(KexiTableItem *item, bool repaint);
+
+ //! Like above, not db-aware version
+ virtual void slotRowInserted(KexiTableItem *item, uint row, bool repaint);
+
+ virtual void slotRowsDeleted( const QValueList<int> & ) {}
+
+ //! for sanity checks (return true if m_data is present; else: outputs warning)
+ inline bool hasData() const;
+
+ /*! Only needed for forms: called by KexiDataAwareObjectInterface::setCursorPosition()
+ if cursor's position is really changed. */
+ virtual void selectCellInternal() {}
+
+ /*! Used in KexiDataAwareObjectInterface::slotRowDeleted()
+ to repaint tow \a row and all visible below.
+ Implemented if there is more than one row displayed, i.e. currently for KexiTableView. */
+ virtual void updateAllVisibleRowsBelow(int row) { Q_UNUSED( row ); }
+
+ //! Call this from the subclass. */
+ virtual void focusOutEvent(QFocusEvent* e);
+
+ /*! Handles verticalScrollBar()'s valueChanged(int) signal.
+ Called when vscrollbar's value has been changed.
+ Call this method from the subclass. */
+ virtual void vScrollBarValueChanged(int v);
+
+ /*! Handles sliderReleased() signal of the verticalScrollBar(). Used to hide the "row number" tooltip. */
+ virtual void vScrollBarSliderReleased();
+
+ /*! Handles timeout() signal of the m_scrollBarTipTimer. If the tooltip is visible,
+ m_scrollBarTipTimerCnt is set to 0 and m_scrollBarTipTimerCnt is restarted;
+ else the m_scrollBarTipTimerCnt is just set to 0.*/
+ virtual void scrollBarTipTimeout();
+
+ /*! Shows error message box suitable for \a resultInfo. This can be "sorry" or "detailedSorry"
+ message box or "queryYesNo" if resultInfo->allowToDiscardChanges is true.
+ \return code of button clicked: KMessageBox::Ok in case of "sorry" or "detailedSorry" messages
+ and KMessageBox::Yes or KMessageBox::No in case of "queryYesNo" message. */
+ int showErrorMessageForResult(KexiDB::ResultInfo* resultInfo);
+
+ /*! Prepares array of indices of visible values to search within.
+ This is per-interface global cache.
+ Needed for faster lookup because there could be lookup values.
+ Called whenever columns definition changes, i.e. in setData() and clearColumns().
+ @see find() */
+ void updateIndicesForVisibleValues();
+
+ //! data structure displayed for this object
+ KexiTableViewData *m_data;
+
+ //! true if m_data member is owned by this object
+ bool m_owner : 1;
+
+ //! cursor position
+ int m_curRow, m_curCol;
+
+ //! current data item
+ KexiTableItem *m_currentItem;
+
+ //! data item's iterator
+ KexiTableViewData::Iterator *m_itemIterator;
+
+ //! item data for inserting
+ KexiTableItem *m_insertItem;
+
+ //! when (current or new) row is edited - changed field values are temporary stored here
+// KexiDB::RowEditBuffer *m_rowEditBuffer;
+
+ /*! true if currently selected row is edited */
+ bool m_rowEditing : 1;
+
+ /*! true if new row is edited; implies: rowEditing==true. */
+ bool m_newRowEditing : 1;
+
+ /*! 'sorting by column' availability flag for widget */
+ bool m_isSortingEnabled : 1;
+
+ /*! true if filtering is enabled for the view. */
+ bool m_isFilteringEnabled : 1;
+
+ /*! Public version of 'acceptsRowEditAfterCellAcceptin' flag (available for a user).
+ It's OR'es together with above flag.
+ */
+ bool m_acceptsRowEditAfterCellAccepting : 1;
+
+ /*! Used in acceptEditor() to avoid infinite recursion,
+ eg. when we're calling acceptRowEdit() during cell accepting phase. */
+ bool m_inside_acceptEditor : 1;
+
+ /*! @internal if true, this object automatically accepts
+ row editing (using acceptRowEdit()) on accepting any cell's edit
+ (i.e. after acceptEditor()). */
+ bool m_internal_acceptsRowEditAfterCellAccepting : 1;
+
+ /*! true, if inserting empty rows are enabled (false by default) */
+ bool m_emptyRowInsertingEnabled : 1;
+
+ /*! Contains 1 if the object is readOnly, 0 if not;
+ otherwise (-1 means "do not know") the 'readOnly' flag from object's
+ internal data structure (KexiTableViewData *KexiTableView::m_data) is reused.
+ */
+ int m_readOnly;
+
+//! @todo really keep this here and not in KexiTableView?
+ /*! true if currently double click action was is performed
+ (so accept/cancel editor shoudn't be executed) */
+ bool m_contentsMousePressEvent_dblClick : 1;
+
+ /*! like for readOnly: 1 if inserting is enabled */
+ int m_insertingEnabled;
+
+ /*! true, if initDataContents() should be called on show event. */
+ bool m_initDataContentsOnShow : 1;
+
+ /*! Set to true in setCursorPosition() to indicate that cursor position was set
+ before show() and it shouldn't be changed on show().
+ Only used if initDataContentsOnShow is true. */
+ bool m_cursorPositionSetExplicityBeforeShow : 1;
+
+ /*! true if spreadSheetMode is enabled. False by default.
+ @see KexiTableView::setSpreadSheetMode() */
+ bool m_spreadSheetMode : 1;
+
+ /*! true, if this table accepts dropping data on the rows (false by default). */
+ bool m_dropsAtRowEnabled : 1;
+
+ /*! true, if this entire (visible) row should be updated when boving to other row.
+ False by default. For table view with 'row highlighting' flag enabled, it is true. */
+ bool m_updateEntireRowWhenMovingToOtherRow : 1;
+
+ DeletionPolicy m_deletionPolicy;
+
+//! @todo make generic interface out of KexiRecordMarker
+ KexiRecordMarker *m_verticalHeader;
+
+//! @todo make generic interface out of KexiTableViewHeader
+ KexiTableViewHeader *m_horizontalHeader;
+
+ KexiDataItemInterface *m_editor;
+// KexiTableEdit *m_editor;
+
+ /*! Navigation panel, used if navigationPanelEnabled is true. */
+ KexiRecordNavigator *m_navPanel; //!< main navigation widget
+
+ bool m_navPanelEnabled : 1;
+
+ /*! true, if certical header shouldn't be increased in
+ KexiTableView::slotRowInserted() because it was already done
+ in KexiTableView::createEditor(). */
+ bool m_verticalHeaderAlreadyAdded : 1;
+
+ /*! Row number that over which user drags a mouse pointer.
+ Used to indicate dropping possibility for that row.
+ Equal -1 if no indication is needed. */
+ int m_dragIndicatorLine;
+
+ /*! Context menu widget. */
+ KPopupMenu *m_popupMenu;
+
+ bool m_contextMenuEnabled : 1;
+
+ //! Used by updateAfterCancelRowEdit()
+ bool m_alsoUpdateNextRow : 1;
+
+ /*! Row number (>=0 or -1 == no row) that will be deleted in deleteRow().
+ It is set in slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)) slot
+ received from KexiTableViewData member.
+ This value will be used in slotRowDeleted() after rowDeleted() signal
+ is received from KexiTableViewData member and then cleared (set to -1). */
+ int m_rowWillBeDeleted;
+
+ /*! Displays passive error popup label used when invalid data has been entered. */
+ QGuardedPtr<KexiArrowTip> m_errorMessagePopup;
+
+ /*! Used to enable/disable execution of vScrollBarValueChanged()
+ when users navigate through rows using keyboard, so vscrollbar tooltips are not visible. */
+ bool m_vScrollBarValueChanged_enabled : 1;
+
+ /*! True, if vscrollbar tooltips are enabled (true by default). */
+ bool m_scrollbarToolTipsEnabled : 1;
+
+ QLabel* m_scrollBarTip; //!< scrollbar tooltip
+ QTimer m_scrollBarTipTimer; //!< scrollbar tooltip's timer
+ uint m_scrollBarTipTimerCnt; //!< helper for timeout counting (scrollbar tooltip)
+
+ //! Used to mark recently found value
+ class PositionOfValue {
+ public:
+ PositionOfValue() : firstCharacter(0), lastCharacter(0), exists(false)
+ {}
+ uint firstCharacter;
+ uint lastCharacter;
+ bool exists : 1;
+ };
+
+ /*! Used to mark recently found value. Updated on succesful execution of find().
+ If the current cursor's position changes, or data in the current cell changes,
+ positionOfRecentlyFoundValue.exists is set to false. */
+ PositionOfValue m_positionOfRecentlyFoundValue;
+
+ /*! Used to compare whether we're looking for new value. */
+ QVariant m_recentlySearchedValue;
+
+ /*! Used to compare whether the search direction has changed. */
+ KexiSearchAndReplaceViewInterface::Options::SearchDirection m_recentSearchDirection;
+
+ //! Setup by updateIndicesForVisibleValues() and used by find()
+ QValueVector<uint> m_indicesForVisibleValues;
+};
+
+inline bool KexiDataAwareObjectInterface::hasData() const
+{
+ if (!m_data)
+ kdDebug() << "KexiDataAwareObjectInterface: No data assigned!" << endl;
+ return m_data!=0;
+}
+
+inline KexiTableItem *KexiDataAwareObjectInterface::itemAt(int row) const
+{
+ KexiTableItem *item = m_data->at(row);
+ if (!item)
+ kdDebug() << "KexiTableView::itemAt(" << row << "): NO ITEM!!" << endl;
+ else {
+/* kdDebug() << "KexiTableView::itemAt(" << row << "):" << endl;
+ int i=1;
+ for (KexiTableItem::Iterator it = item->begin();it!=item->end();++it,i++)
+ kdDebug() << i<<": " << (*it).toString()<< endl;*/
+ }
+ return item;
+}
+
+//! Convenience macro used for KexiDataAwareObjectInterface implementations.
+#define KEXI_DATAAWAREOBJECTINTERFACE \
+public: \
+ void connectCellSelectedSignal(const QObject* receiver, const char* intIntMember) { \
+ connect(this, SIGNAL(cellSelected(int,int)), receiver, intIntMember); \
+ } \
+ void connectRowEditStartedSignal(const QObject* receiver, const char* intMember) { \
+ connect(this, SIGNAL(rowEditStarted(int)), receiver, intMember); \
+ } \
+ void connectRowEditTerminatedSignal(const QObject* receiver, const char* voidMember) { \
+ connect(this, SIGNAL(rowEditTerminated(int)), receiver, voidMember); \
+ } \
+ void connectReloadActionsSignal(const QObject* receiver, const char* voidMember) { \
+ connect(this, SIGNAL(reloadActions()), receiver, voidMember); \
+ } \
+ void connectDataSetSignal(const QObject* receiver, \
+ const char* kexiTableViewDataMember) { \
+ connect(this, SIGNAL(dataSet(KexiTableViewData*)), receiver, kexiTableViewDataMember); \
+ } \
+ void connectToReloadDataSlot(const QObject* sender, const char* voidSignal) { \
+ connect(sender, voidSignal, this, SLOT(reloadData())); \
+ }
+
+#endif
diff --git a/kexi/widget/tableview/kexidataawarepropertyset.cpp b/kexi/widget/tableview/kexidataawarepropertyset.cpp
new file mode 100644
index 000000000..92fda11e5
--- /dev/null
+++ b/kexi/widget/tableview/kexidataawarepropertyset.cpp
@@ -0,0 +1,260 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidataawarepropertyset.h"
+#include "kexitableviewdata.h"
+#include "kexidataawareobjectiface.h"
+
+#include <koproperty/property.h>
+#include <kexiviewbase.h>
+
+#define MAX_FIELDS 101 //nice prime number (default prop. set vector size)
+
+KexiDataAwarePropertySet::KexiDataAwarePropertySet(KexiViewBase *view,
+ KexiDataAwareObjectInterface* dataObject)
+ : QObject( view, QCString(view->name())+"KexiDataAwarePropertySet" )
+ , m_view(view)
+ , m_dataObject(dataObject)
+ , m_row(-99)
+{
+ m_sets.setAutoDelete(true);
+
+// connect(m_dataObject, SIGNAL(dataSet(KexiTableViewData*)),
+// this, SLOT(slotDataSet(KexiTableViewData*)));
+ m_dataObject->connectDataSetSignal(this, SLOT(slotDataSet(KexiTableViewData*)));
+// connect(m_dataObject, SIGNAL(cellSelected(int,int)),
+// this, SLOT(slotCellSelected(int,int)));
+ m_dataObject->connectCellSelectedSignal(this, SLOT(slotCellSelected(int,int)));
+//
+ slotDataSet( m_dataObject->data() );
+ const bool wasDirty = view->dirty();
+ clear();
+ if (!wasDirty)
+ view->setDirty(false);
+}
+
+KexiDataAwarePropertySet::~KexiDataAwarePropertySet()
+{
+}
+
+void KexiDataAwarePropertySet::slotDataSet( KexiTableViewData *data )
+{
+ if (!m_currentTVData.isNull()) {
+ m_currentTVData->disconnect( this );
+ clear();
+ }
+ m_currentTVData = data;
+ if (!m_currentTVData.isNull()) {
+ connect(m_currentTVData, SIGNAL(rowDeleted()), this, SLOT(slotRowDeleted()));
+ connect(m_currentTVData, SIGNAL(rowsDeleted( const QValueList<int> & )),
+ this, SLOT(slotRowsDeleted( const QValueList<int> & )));
+ connect(m_currentTVData, SIGNAL(rowInserted(KexiTableItem*,uint,bool)),
+ this, SLOT(slotRowInserted(KexiTableItem*,uint,bool)));
+ connect(m_currentTVData, SIGNAL(reloadRequested()),
+ this, SLOT(slotReloadRequested()));
+ }
+}
+
+void KexiDataAwarePropertySet::removeCurrentPropertySet()
+{
+ remove( m_dataObject->currentRow() );
+}
+
+void KexiDataAwarePropertySet::remove(uint row)
+{
+ KoProperty::Set *set = m_sets.at(row);
+ if (!set)
+ return;
+ set->debug();
+ m_sets.remove(row);
+ m_view->setDirty();
+ m_view->propertySetSwitched();
+}
+
+uint KexiDataAwarePropertySet::size() const
+{
+ return m_sets.size();
+}
+
+void KexiDataAwarePropertySet::clear(uint minimumSize)
+{
+ m_sets.clear();
+ m_sets.resize(QMAX(minimumSize, MAX_FIELDS));
+ m_view->setDirty(true);
+ m_view->propertySetSwitched();
+}
+
+void KexiDataAwarePropertySet::slotReloadRequested()
+{
+ clear();
+}
+
+void KexiDataAwarePropertySet::insert(uint row, KoProperty::Set* set, bool newOne)
+{
+ if (!set || row >= m_sets.size()) {
+ kexiwarn << "KexiDataAwarePropertySet::insert() invalid args: rew="<< row<< " propertyset="<< set<< endl;
+ return;
+ }
+ if (set->parent() && set->parent()!=this) {
+ kexiwarn << "KexiDataAwarePropertySet::insert() propertyset's parent must be NULL or this KexiDataAwarePropertySet" << endl;
+ return;
+ }
+
+ m_sets.insert(row, set);
+
+ connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), m_view, SLOT(setDirty()));
+
+ if (newOne) {
+ //add a special property indicating that this is brand new set,
+ //not just changed
+ KoProperty::Property* prop = new KoProperty::Property("newrow");
+ prop->setVisible(false);
+ set->addProperty( prop );
+ m_view->setDirty();
+ }
+}
+
+KoProperty::Set* KexiDataAwarePropertySet::currentPropertySet() const
+{
+ return (m_dataObject->currentRow() >= 0) ? m_sets.at( m_dataObject->currentRow() ) : 0;
+}
+
+uint KexiDataAwarePropertySet::currentRow() const
+{
+ return m_dataObject->currentRow();
+}
+
+void KexiDataAwarePropertySet::slotRowDeleted()
+{
+ m_view->setDirty();
+ removeCurrentPropertySet();
+
+ //let's move up all property sets that are below that deleted
+ m_sets.setAutoDelete(false);//to avoid auto deleting in insert()
+ const int r = m_dataObject->currentRow();
+ for (int i=r;i<int(m_sets.size()-1);i++) {
+ KoProperty::Set *set = m_sets[i+1];
+ m_sets.insert( i , set );
+ }
+ m_sets.insert( m_sets.size()-1, 0 );
+ m_sets.setAutoDelete(true);//revert the flag
+
+ m_view->propertySetSwitched();
+ emit rowDeleted();
+}
+
+void KexiDataAwarePropertySet::slotRowsDeleted( const QValueList<int> &rows )
+{
+ //let's move most property sets up & delete unwanted
+ m_sets.setAutoDelete(false);//to avoid auto deleting in insert()
+ const int orig_size = size();
+ int prev_r = -1;
+ int num_removed = 0, cur_r = -1;
+ for (QValueList<int>::ConstIterator r_it = rows.constBegin(); r_it!=rows.constEnd() && *r_it < orig_size; ++r_it) {
+ cur_r = *r_it;// - num_removed;
+ if (prev_r>=0) {
+// kdDebug() << "move " << prev_r+num_removed-1 << ".." << cur_r-1 << " to " << prev_r+num_removed-1 << ".." << cur_r-2 << endl;
+ int i=prev_r;
+ KoProperty::Set *set = m_sets.take(i+num_removed);
+ kdDebug() << "property set " << i+num_removed << " deleted" << endl;
+ delete set;
+ num_removed++;
+ for (; (i+num_removed)<cur_r; i++) {
+ m_sets.insert( i, m_sets[i+num_removed] );
+ kdDebug() << i << " <- " << i+num_removed << endl;
+ }
+ }
+ prev_r = cur_r - num_removed;
+ }
+ //move remaining property sets up
+ if (cur_r>=0) {
+ KoProperty::Set *set = m_sets.take(cur_r);
+ kdDebug() << "property set " << cur_r << " deleted" << endl;
+ delete set;
+ num_removed++;
+ for (int i=prev_r; (i+num_removed)<orig_size; i++) {
+ m_sets.insert( i, m_sets[i+num_removed] );
+ kdDebug() << i << " <- " << i+num_removed << endl;
+ }
+ }
+ //finally: clear last rows
+ for (int i=orig_size-num_removed; i<orig_size; i++) {
+ kdDebug() << i << " <- zero" << endl;
+ m_sets.insert( i, 0 );
+ }
+ m_sets.setAutoDelete(true);//revert the flag
+
+ if (num_removed>0)
+ m_view->setDirty();
+ m_view->propertySetSwitched();
+}
+
+//void KexiDataAwarePropertySet::slotEmptyRowInserted(KexiTableItem*, uint /*index*/)
+void KexiDataAwarePropertySet::slotRowInserted(KexiTableItem*, uint row, bool /*repaint*/)
+{
+ m_view->setDirty();
+
+ //let's move down all property set that are below
+ m_sets.setAutoDelete(false);//to avoid auto deleting in insert()
+// const int r = m_dataObject->currentRow();
+ m_sets.resize(m_sets.size()+1);
+ for (int i=int(m_sets.size())-1; i>(int)row; i--) {
+ KoProperty::Set *set = m_sets[i-1];
+ m_sets.insert( i , set );
+ }
+ m_sets.insert( row, 0 );
+ m_sets.setAutoDelete(true);//revert the flag
+
+ m_view->propertySetSwitched();
+
+ emit rowInserted();
+}
+
+void KexiDataAwarePropertySet::slotCellSelected(int, int row)
+{
+ if(row == m_row)
+ return;
+ m_row = row;
+ m_view->propertySetSwitched();
+}
+
+KoProperty::Set* KexiDataAwarePropertySet::findPropertySetForItem(KexiTableItem& item)
+{
+ if (m_currentTVData.isNull())
+ return 0;
+ int idx = m_currentTVData->findRef(&item);
+ if (idx<0)
+ return 0;
+ return m_sets[idx];
+}
+
+int KexiDataAwarePropertySet::findRowForPropertyValue(const QCString& propertyName, const QVariant& value)
+{
+ const int size = m_sets.size();
+ for (int i=0; i<size; i++) {
+ KoProperty::Set *set = m_sets[i];
+ if (!set || !set->contains(propertyName))
+ continue;
+ if (set->property(propertyName).value() == value)
+ return i;
+ }
+ return -1;
+}
+
+#include "kexidataawarepropertyset.moc"
diff --git a/kexi/widget/tableview/kexidataawarepropertyset.h b/kexi/widget/tableview/kexidataawarepropertyset.h
new file mode 100644
index 000000000..cee55da04
--- /dev/null
+++ b/kexi/widget/tableview/kexidataawarepropertyset.h
@@ -0,0 +1,149 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDATAAWAREPROPERTYSET_H
+#define KEXIDATAAWAREPROPERTYSET_H
+
+#include <qguardedptr.h>
+#include <qptrvector.h>
+#include <koproperty/set.h>
+
+typedef QPtrVector<KoProperty::Set> SetVector;
+
+class KexiViewBase;
+class KexiTableItem;
+class KexiTableViewData;
+class KexiDataAwareObjectInterface;
+
+/*! This helper class handles data changes of a single
+ object implementing KexiDataAwareObjectInterface (e.g. KexiTableView) inside
+ a KexiViewBase container.
+
+ It is currently used in KexiAlterTableDialog and KexiQueryDesignerGuiEditor,
+ and may be used for similar purposes, when each KexiDataAwareObjectInterface's
+ row can be associated with single KoProperty::Set object, and given
+ KexiDataAwareObjectInterface object has to inform the world about currently
+ selected row/property set.
+
+ Following functionality is built-in:
+ - auto-initializing after resetting of table view's data
+ - destroying single property set that is associated with deleted row
+ - inserting single property set that and associating it with new row
+ - all property sets are cleared when view's data is cleared (using clear())
+ - setting view's 'dirty' flag when needed
+ - signalling via KexiViewBase::propertySetSwitched() that current property
+ set has changed (e.g. on moving to other row)
+*/
+class KEXIDATATABLE_EXPORT KexiDataAwarePropertySet : public QObject
+{
+ Q_OBJECT
+
+ public:
+ /*! You can instantiate KexiDataAwarePropertySet object
+ for existing \a tableView and \a view. \a tableView can have data assigned
+ (KexiDataAwareObjectInterface::setData()) now but it can be done later as well
+ (but assigning data is needed for proper functionality).
+ Any changed reassignments of table view's data will be handled automatically. */
+ KexiDataAwarePropertySet(KexiViewBase *view, KexiDataAwareObjectInterface* dataObject);
+
+ virtual ~KexiDataAwarePropertySet();
+
+ uint size() const;
+
+ KoProperty::Set* currentPropertySet() const;
+
+ uint currentRow() const;
+
+ inline KoProperty::Set* at(uint row) const { return m_sets[row]; }
+
+ /*! \return a pointer to property set assigned for \a item or null if \a item has no
+ property set assigned or it's not owned by assigned table view or
+ if assigned table view has no data set. */
+ KoProperty::Set* findPropertySetForItem(KexiTableItem& item);
+
+ /*! \return number of the first row containing \a propertyName property equal to \a value.
+ This is used e.g. in the Table Designer to find a row by field name.
+ If no such row has been found, -1 is returned. */
+ int findRowForPropertyValue(const QCString& propertyName, const QVariant& value);
+
+ signals:
+ /*! Emmited when row is deleted.
+ KexiDataAwareObjectInterface::rowDeleted() signal is usually used but when you're using
+ KexiDataAwarePropertySet, you never know if currentPropertySet() is updated.
+ So use this signal instead. */
+ void rowDeleted();
+
+ /*! Emmited when row is inserted.
+ Purpose of this signal is similar to rowDeleted() signal. */
+ void rowInserted();
+
+ public slots:
+ void removeCurrentPropertySet();
+
+ void clear(uint minimumSize = 0);
+
+ /*! Inserts \a set property set at \a row position.
+ If there was a buffer at this position before, it will be destroyed.
+ If \a newOne is true, the property set will be marked as newly created,
+ simply by adding "newrow" property.
+
+ The property set \a set will be owned by this object, so you should not
+ delete this property set by hand but call removeCurrentPropertySet()
+ or remove(uint) instead.
+ Note that property set's parent (QObject::parent()) must be null
+ or qual to this KexiDataAwarePropertySet object, otherwise this method
+ will fail with a warning.
+ */
+ void insert(uint row, KoProperty::Set* set, bool newOne = false);
+
+ /*! Removes a property set at \a row position. */
+ void remove(uint row);
+
+ protected slots:
+ /*! Handles table view's data source changes. */
+ void slotDataSet( KexiTableViewData *data );
+
+ //! Called on row delete in a tableview.
+ void slotRowDeleted();
+
+ //! Called on multiple rows delete in a tableview.
+ void slotRowsDeleted( const QValueList<int> &rows );
+
+ //! Called on \a row insertion in a tableview.
+ void slotRowInserted(KexiTableItem* item, uint row, bool repaint);
+
+ //! Called on selecting another cell in a tableview.
+ void slotCellSelected(int, int row);
+
+ //! Called on clearing tableview's data: just clears all property sets.
+ void slotReloadRequested();
+
+ protected:
+ SetVector m_sets; //!< prop. sets vector
+
+ QGuardedPtr<KexiViewBase> m_view;
+ KexiDataAwareObjectInterface* m_dataObject;
+// QGuardedPtr<KexiTableView> m_tableView;
+ QGuardedPtr<KexiTableViewData> m_currentTVData;
+
+ int m_row; //!< used to know if a new row is selected in slotCellSelected()
+};
+
+#endif
+
diff --git a/kexi/widget/tableview/kexidatatableview.cpp b/kexi/widget/tableview/kexidatatableview.cpp
new file mode 100644
index 000000000..9248e8901
--- /dev/null
+++ b/kexi/widget/tableview/kexidatatableview.cpp
@@ -0,0 +1,121 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <qtimer.h>
+#include <qapplication.h>
+
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kaction.h>
+
+#include <kexidb/connection.h>
+#include <kexidb/cursor.h>
+
+#include "kexidatatableview.h"
+
+
+KexiDataTableView::KexiDataTableView(QWidget *parent, const char *name)
+ : KexiTableView(0, parent, name)
+{
+ init();
+}
+
+KexiDataTableView::KexiDataTableView(QWidget *parent, const char *name, KexiDB::Cursor *cursor)
+ : KexiTableView(0, parent, name)
+{
+ init();
+ setData(cursor);
+}
+
+KexiDataTableView::~KexiDataTableView()
+{
+}
+
+void
+KexiDataTableView::init()
+{
+ m_cursor = 0;
+
+// m_maxRecord = 0;
+// m_records = 0;
+// m_first = false;
+
+// connect(this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotMoving(int)));
+// connect(verticalScrollBar(), SIGNAL(sliderMoved(int)), this, SLOT(slotMoving(int)));
+}
+
+/*void KexiDataTableView::initActions(KActionCollection *col)
+{
+ KexiTableView::initActions(col);
+ new KAction(i18n("Filter"), "filter", 0, this, SLOT(filter()), col, "tablepart_filter");
+}*/
+
+bool KexiDataTableView::setData(KexiDB::Cursor *cursor)
+{
+//js if (!m_first)
+//js clearColumns();
+ if (!cursor) {
+ clearColumns();
+ m_cursor = 0;
+ return true;
+ }
+ if (cursor!=m_cursor) {
+ clearColumns();
+ }
+ m_cursor = cursor;
+
+ if (!m_cursor->query()) {
+ kdDebug() << "KexiDataTableView::setData(): WARNING: cursor should have query schema defined!\n--aborting setData()." << endl;
+ m_cursor->debug();
+ clearColumns();
+ return false;
+ }
+
+ if (m_cursor->fieldCount()<1) {
+ clearColumns();
+ return true;
+ }
+
+ if (!m_cursor->isOpened() && !m_cursor->open()) {
+ kdDebug() << "KexiDataTableView::setData(): WARNING: cannot open cursor\n--aborting setData(). \n" <<
+ m_cursor->serverErrorMsg() << endl;
+ m_cursor->debug();
+ clearColumns();
+ return false;
+ }
+
+ KexiTableViewData *tv_data = new KexiTableViewData(m_cursor);
+
+ QString caption = m_cursor->query()->caption();
+ if (caption.isEmpty())
+ caption = m_cursor->query()->name();
+
+ setCaption( caption );
+
+ //PRIMITIVE!! data setting:
+ tv_data->preloadAllRows();
+
+ KexiTableView::setData(tv_data);
+ return true;
+}
+
+#include "kexidatatableview.moc"
diff --git a/kexi/widget/tableview/kexidatatableview.h b/kexi/widget/tableview/kexidatatableview.h
new file mode 100644
index 000000000..6f4218947
--- /dev/null
+++ b/kexi/widget/tableview/kexidatatableview.h
@@ -0,0 +1,94 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDATATABLEVIEW_H
+#define KEXIDATATABLEVIEW_H
+
+#include "kexitableview.h"
+
+class KexiTableItem;
+class QVariant;
+class KXMLGUIClient;
+
+namespace KexiDB {
+ class Cursor;
+}
+
+/**
+ * Database aware table widget.
+ */
+class KEXIDATATABLE_EXPORT KexiDataTableView : public KexiTableView
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * creates a blank widget
+ */
+ KexiDataTableView(QWidget *parent, const char *name =0);
+
+ /*! Creates a table widget and fills it using data from \a cursor.
+ Cursor will be opened (with open()) if it is not yet opened.
+ Cursor must be defined on query schema, not raw statement (see Connection::prepareQuery()
+ and Connection::executeQuery()), otherwise the table view remain not filled with data.
+ Cursor \a cursor will not be owned by this object.
+ */
+ KexiDataTableView(QWidget *parent, const char *name, KexiDB::Cursor *cursor);
+
+ ~KexiDataTableView();
+
+// virtual void initActions(KActionCollection *col);
+
+ /*! Fills table view with data using \a cursor. \return true on success.
+ Cursor \a cursor will not be owned by this object. */
+ bool setData(KexiDB::Cursor *cursor);
+
+ /*! \return cursor used as data source for this table view,
+ or NULL if no valid cursor is defined. */
+ KexiDB::Cursor *cursor() { return m_cursor; }
+
+ /**
+ * @returns the number of records in the data set, (if data set is present)
+ * @note not all of the records have to be processed
+ */
+ int recordCount() { return m_data->count(); }
+
+ #ifndef KEXI_NO_PRINT
+// virtual void print(KPrinter &printer);
+ #endif
+
+ protected:
+ void init();
+
+ /*! Reimplemented: called by deleteItem() - we are deleting data associated with \a item. */
+// virtual bool beforeDeleteItem(KexiTableItem *item);
+
+ protected slots:
+// void slotClearData();
+
+ private:
+ //db stuff
+ KexiDB::Cursor *m_cursor;
+
+// QMap<KexiDBUpdateRecord*,KexiTableItem*> m_insertMapping;
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexidatetableedit.cpp b/kexi/widget/tableview/kexidatetableedit.cpp
new file mode 100644
index 000000000..8a1fbcaef
--- /dev/null
+++ b/kexi/widget/tableview/kexidatetableedit.cpp
@@ -0,0 +1,290 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003-2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexidatetableedit.h"
+
+#include <qapplication.h>
+#include <qpainter.h>
+#include <qvariant.h>
+#include <qrect.h>
+#include <qpalette.h>
+#include <qcolor.h>
+#include <qfontmetrics.h>
+#include <qdatetime.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qlayout.h>
+#include <qtoolbutton.h>
+#include <qdatetimeedit.h>
+#include <qclipboard.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kdatepicker.h>
+#include <kdatetbl.h>
+#include <klineedit.h>
+#include <kpopupmenu.h>
+#include <kdatewidget.h>
+
+#include <kexiutils/utils.h>
+
+
+KexiDateTableEdit::KexiDateTableEdit(KexiTableViewColumn &column, QWidget *parent)
+ : KexiInputTableEdit(column, parent)
+{
+ setName("KexiDateTableEdit");
+
+//! @todo add QValidator so date like "2006-59-67" cannot be even entered
+
+ m_lineedit->setInputMask( m_formatter.inputMask() );
+}
+
+KexiDateTableEdit::~KexiDateTableEdit()
+{
+}
+
+void KexiDateTableEdit::setValueInInternalEditor(const QVariant &value)
+{
+ if (value.isValid() && value.toDate().isValid())
+ m_lineedit->setText( m_formatter.dateToString( value.toDate() ) );
+ else
+ m_lineedit->setText( QString::null );
+}
+
+void KexiDateTableEdit::setValueInternal(const QVariant& add_, bool removeOld)
+{
+ if (removeOld) {
+ //new date entering... just fill the line edit
+//! @todo cut string if too long..
+ QString add(add_.toString());
+ m_lineedit->setText(add);
+ m_lineedit->setCursorPosition(add.length());
+ return;
+ }
+ setValueInInternalEditor( m_origValue );
+ m_lineedit->setCursorPosition(0); //ok?
+}
+
+void KexiDateTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h )
+{
+ Q_UNUSED(p);
+ Q_UNUSED(focused);
+ Q_UNUSED(x);
+ Q_UNUSED(w);
+ Q_UNUSED(h);
+#ifdef Q_WS_WIN
+ y_offset = -1;
+#else
+ y_offset = 0;
+#endif
+ if (val.toDate().isValid())
+ txt = m_formatter.dateToString(val.toDate());
+// txt = val.toDate().toString(Qt::LocalDate);
+ align |= AlignLeft;
+}
+
+bool KexiDateTableEdit::valueIsNull()
+{
+// if (m_lineedit->text().replace(m_formatter.separator(),"").stripWhiteSpace().isEmpty())
+ if (m_formatter.isEmpty(m_lineedit->text())) //empty date is null
+ return true;
+ return dateValue().isNull();
+}
+
+bool KexiDateTableEdit::valueIsEmpty()
+{
+ return valueIsNull();//js OK? TODO (nonsense?)
+}
+
+QDate KexiDateTableEdit::dateValue() const
+{
+ return m_formatter.stringToDate( m_lineedit->text() );
+}
+
+QVariant KexiDateTableEdit::value()
+{
+ return m_formatter.stringToVariant( m_lineedit->text() );
+}
+
+bool KexiDateTableEdit::valueIsValid()
+{
+ if (m_formatter.isEmpty(m_lineedit->text())) //empty date is valid
+ return true;
+ return m_formatter.stringToDate( m_lineedit->text() ).isValid();
+}
+
+void KexiDateTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue)
+{
+ Q_UNUSED(visibleValue);
+ if (!value.isNull() && value.toDate().isValid())
+ qApp->clipboard()->setText( m_formatter.dateToString(value.toDate()) );
+ else
+ qApp->clipboard()->setText( QString::null );
+}
+
+void KexiDateTableEdit::handleAction(const QString& actionName)
+{
+ const bool alreadyVisible = m_lineedit->isVisible();
+
+ if (actionName=="edit_paste") {
+ const QVariant newValue( m_formatter.stringToDate(qApp->clipboard()->text()) );
+ if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode
+ emit editRequested();
+ m_lineedit->clear();
+ }
+ setValueInInternalEditor( newValue );
+ }
+ else
+ KexiInputTableEdit::handleAction(actionName);
+}
+
+/*
+void
+KexiDateTableEdit::slotDateChanged(QDate date)
+{
+ m_edit->setDate(date);
+ repaint();
+}
+
+void
+KexiDateTableEdit::slotShowDatePicker()
+{
+ QDate date = m_edit->date();
+
+ m_datePicker->setDate(date);
+ m_datePicker->setFocus();
+ m_datePicker->show();
+ m_datePicker->setFocus();
+}
+
+//! @internal helper
+void KexiDateTableEdit::moveToFirstSection()
+{
+ if (!m_dte_date_obj)
+ return;
+#ifdef QDateTimeEditor_HACK
+ if (m_dte_date)
+ m_dte_date->setFocusSection(0);
+#else
+#ifdef Q_WS_WIN //tmp
+ QKeyEvent ke_left(QEvent::KeyPress, Qt::Key_Left, 0, 0);
+ for (int i=0; i<8; i++)
+ QApplication::sendEvent( m_dte_date_obj, &ke_left );
+#endif
+#endif
+}
+
+bool KexiDateTableEdit::eventFilter( QObject *o, QEvent *e )
+{
+ if (o==m_datePicker) {
+ kdDebug() << e->type() << endl;
+ switch (e->type()) {
+ case QEvent::Hide:
+ m_datePickerPopupMenu->hide();
+ break;
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease: {
+ kdDebug() << "ok!" << endl;
+ QKeyEvent *ke = (QKeyEvent *)e;
+ if (ke->key()==Key_Enter || ke->key()==Key_Return) {
+ //accepting picker
+ acceptDate();
+ return true;
+ }
+ else if (ke->key()==Key_Escape) {
+ //canceling picker
+ m_datePickerPopupMenu->hide();
+ kdDebug() << "reject" << endl;
+ return true;
+ }
+ else m_datePickerPopupMenu->setFocus();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+#ifdef Q_WS_WIN //tmp
+ else if (e->type()==QEvent::FocusIn && o->parent() && o->parent()->parent()==m_edit
+ && m_setNumberOnFocus >= 0 && m_dte_date_obj)
+ {
+ // there was a number character passed as 'add' parameter in init():
+ moveToFirstSection();
+ QKeyEvent ke(QEvent::KeyPress, int(Qt::Key_0)+m_setNumberOnFocus,
+ '0'+m_setNumberOnFocus, 0, QString::number(m_setNumberOnFocus));
+ QApplication::sendEvent( m_dte_date_obj, &ke );
+ m_setNumberOnFocus = -1;
+ }
+#endif
+#ifdef QDateTimeEditor_HACK
+ else if (e->type()==QEvent::KeyPress && m_dte_date) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ if ((ke->key()==Qt::Key_Right && !m_sentEvent && cursorAtEnd())
+ || (ke->key()==Qt::Key_Left && !m_sentEvent && cursorAtStart()))
+ {
+ //the editor should send this key event:
+ m_sentEvent = true; //avoid recursion
+ QApplication::sendEvent( this, ke );
+ m_sentEvent = false;
+ ke->ignore();
+ return true;
+ }
+ }
+#endif
+ return false;
+}
+
+void KexiDateTableEdit::acceptDate()
+{
+ m_edit->setDate(m_datePicker->date());
+ m_datePickerPopupMenu->hide();
+ kdDebug() << "accept" << endl;
+}
+
+bool KexiDateTableEdit::cursorAtStart()
+{
+#ifdef QDateTimeEditor_HACK
+ return m_dte_date && m_edit->hasFocus() && m_dte_date->focusSection()==0;
+#else
+ return false;
+#endif
+}
+
+bool KexiDateTableEdit::cursorAtEnd()
+{
+#ifdef QDateTimeEditor_HACK
+ return m_dte_date && m_edit->hasFocus()
+ && m_dte_date->focusSection()==int(m_dte_date->sectionCount()-1);
+#else
+ return false;
+#endif
+}
+
+void KexiDateTableEdit::clear()
+{
+ m_edit->setDate(QDate());
+}*/
+
+KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiDateEditorFactoryItem, KexiDateTableEdit)
+
+#include "kexidatetableedit.moc"
diff --git a/kexi/widget/tableview/kexidatetableedit.h b/kexi/widget/tableview/kexidatetableedit.h
new file mode 100644
index 000000000..4f2a4f594
--- /dev/null
+++ b/kexi/widget/tableview/kexidatetableedit.h
@@ -0,0 +1,66 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003-2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDATETABLEEDIT_H
+#define KEXIDATETABLEEDIT_H
+
+#include "kexiinputtableedit.h"
+#include <widget/utils/kexidatetimeformatter.h>
+
+/*! @short Editor class for Date type.
+ It is a replacement QDateEdit due to usability problems:
+ people are accustomed to use single-character cursor.
+ Date format is retrieved from the KDE global settings.
+ and input/output is performed using KLineEdit (from KexiInputTableEdit).
+*/
+class KexiDateTableEdit : public KexiInputTableEdit
+{
+ Q_OBJECT
+
+ public:
+ KexiDateTableEdit(KexiTableViewColumn &column, QWidget *parent=0);
+ virtual ~KexiDateTableEdit();
+ virtual void setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h );
+ virtual QVariant value();
+ virtual bool valueIsNull();
+ virtual bool valueIsEmpty();
+ virtual bool valueIsValid();
+
+ /*! Reimplemented after KexiInputTableEdit. */
+ virtual void handleAction(const QString& actionName);
+
+ /*! Reimplemented after KexiInputTableEdit. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue);
+
+ protected:
+ //! helper
+ void setValueInInternalEditor(const QVariant &value);
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+ QDate dateValue() const;
+
+ //! Used to format and convert date values
+ KexiDateFormatter m_formatter;
+};
+
+KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiDateEditorFactoryItem)
+
+#endif
diff --git a/kexi/widget/tableview/kexidatetimetableedit.cpp b/kexi/widget/tableview/kexidatetimetableedit.cpp
new file mode 100644
index 000000000..fbca7cd6f
--- /dev/null
+++ b/kexi/widget/tableview/kexidatetimetableedit.cpp
@@ -0,0 +1,165 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003-2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexidatetimetableedit.h"
+
+#include <qapplication.h>
+#include <qpainter.h>
+#include <qvariant.h>
+#include <qrect.h>
+#include <qpalette.h>
+#include <qcolor.h>
+#include <qfontmetrics.h>
+#include <qdatetime.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qlayout.h>
+#include <qtoolbutton.h>
+#include <qdatetimeedit.h>
+#include <qclipboard.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kdatepicker.h>
+#include <kdatetbl.h>
+#include <klineedit.h>
+#include <kpopupmenu.h>
+#include <kdatewidget.h>
+
+#include <kexiutils/utils.h>
+
+KexiDateTimeTableEdit::KexiDateTimeTableEdit(KexiTableViewColumn &column, QWidget *parent)
+ : KexiInputTableEdit(column, parent)
+{
+ setName("KexiDateTimeTableEdit");
+
+//! @todo add QValidator so time like "99:88:77" cannot be even entered
+
+ m_lineedit->setInputMask(
+ dateTimeInputMask( m_dateFormatter, m_timeFormatter ) );
+}
+
+KexiDateTimeTableEdit::~KexiDateTimeTableEdit()
+{
+}
+
+void KexiDateTimeTableEdit::setValueInInternalEditor(const QVariant &value)
+{
+ if (value.isValid() && value.toDateTime().isValid())
+ m_lineedit->setText(
+ m_dateFormatter.dateToString( value.toDateTime().date() ) + " " +
+ m_timeFormatter.timeToString( value.toDateTime().time() ) );
+ else
+ m_lineedit->setText( QString::null );
+}
+
+void KexiDateTimeTableEdit::setValueInternal(const QVariant& add_, bool removeOld)
+{
+ if (removeOld) {
+ //new time entering... just fill the line edit
+//! @todo cut string if too long..
+ QString add(add_.toString());
+ m_lineedit->setText(add);
+ m_lineedit->setCursorPosition(add.length());
+ return;
+ }
+ setValueInInternalEditor( m_origValue );
+ m_lineedit->setCursorPosition(0); //ok?
+}
+
+void KexiDateTimeTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h )
+{
+ Q_UNUSED(p);
+ Q_UNUSED(focused);
+ Q_UNUSED(x);
+ Q_UNUSED(w);
+ Q_UNUSED(h);
+#ifdef Q_WS_WIN
+ y_offset = -1;
+#else
+ y_offset = 0;
+#endif
+ if (val.toDateTime().isValid())
+ txt = m_dateFormatter.dateToString(val.toDateTime().date()) + " "
+ + m_timeFormatter.timeToString(val.toDateTime().time());
+ align |= AlignLeft;
+}
+
+bool KexiDateTimeTableEdit::valueIsNull()
+{
+ if (textIsEmpty())
+ return true;
+ return !stringToDateTime(m_dateFormatter, m_timeFormatter, m_lineedit->text()).isValid();
+}
+
+bool KexiDateTimeTableEdit::valueIsEmpty()
+{
+ return valueIsNull();//js OK? TODO (nonsense?)
+}
+
+QVariant KexiDateTimeTableEdit::value()
+{
+ if (textIsEmpty())
+ return QVariant();
+ return stringToDateTime(m_dateFormatter, m_timeFormatter, m_lineedit->text());
+}
+
+bool KexiDateTimeTableEdit::valueIsValid()
+{
+ return dateTimeIsValid( m_dateFormatter, m_timeFormatter, m_lineedit->text() );
+}
+
+bool KexiDateTimeTableEdit::textIsEmpty() const
+{
+ return dateTimeIsEmpty( m_dateFormatter, m_timeFormatter, m_lineedit->text() );
+}
+
+void KexiDateTimeTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue)
+{
+ Q_UNUSED(visibleValue);
+ if (!value.isNull() && value.toDateTime().isValid())
+ qApp->clipboard()->setText( m_dateFormatter.dateToString(value.toDateTime().date()) + " "
+ + m_timeFormatter.timeToString(value.toDateTime().time()) );
+ else
+ qApp->clipboard()->setText( QString::null );
+}
+
+void KexiDateTimeTableEdit::handleAction(const QString& actionName)
+{
+ const bool alreadyVisible = m_lineedit->isVisible();
+
+ if (actionName=="edit_paste") {
+ const QVariant newValue( stringToDateTime(m_dateFormatter, m_timeFormatter, qApp->clipboard()->text()) );
+ if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode
+ emit editRequested();
+ m_lineedit->clear();
+ }
+ setValueInInternalEditor( newValue );
+ }
+ else
+ KexiInputTableEdit::handleAction(actionName);
+}
+
+KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiDateTimeEditorFactoryItem, KexiDateTimeTableEdit)
+
+#include "kexidatetimetableedit.moc"
diff --git a/kexi/widget/tableview/kexidatetimetableedit.h b/kexi/widget/tableview/kexidatetimetableedit.h
new file mode 100644
index 000000000..c2f9eba87
--- /dev/null
+++ b/kexi/widget/tableview/kexidatetimetableedit.h
@@ -0,0 +1,69 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003-2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIDATETIMETABLEEDIT_H
+#define KEXIDATETIMETABLEEDIT_H
+
+#include "kexidatetableedit.h"
+#include "kexitimetableedit.h"
+
+/*! @short Editor class for Date/Time type.
+ It is a replacement QDateTimeEdit due to usability problems:
+ people are accustomed to use single-character cursor.
+ Date and Time format is retrieved from the KDE global settings
+ and input/output is performed using KLineEdit (from KexiInputTableEdit).
+*/
+class KexiDateTimeTableEdit : public KexiInputTableEdit
+{
+ Q_OBJECT
+
+ public:
+ KexiDateTimeTableEdit(KexiTableViewColumn &column, QWidget *parent=0);
+ virtual ~KexiDateTimeTableEdit();
+ virtual void setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h );
+ virtual QVariant value();
+ virtual bool valueIsNull();
+ virtual bool valueIsEmpty();
+ virtual bool valueIsValid();
+
+ /*! Reimplemented after KexiInputTableEdit. */
+ virtual void handleAction(const QString& actionName);
+
+ /*! Reimplemented after KexiInputTableEdit. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue);
+
+ protected:
+ //! helper
+ void setValueInInternalEditor(const QVariant &value);
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+ bool textIsEmpty() const;
+
+ //! Used to format and convert date values
+ KexiDateFormatter m_dateFormatter;
+
+ //! Used to format and convert time values
+ KexiTimeFormatter m_timeFormatter;
+};
+
+KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiDateTimeEditorFactoryItem)
+
+#endif
diff --git a/kexi/widget/tableview/kexiinputtableedit.cpp b/kexi/widget/tableview/kexiinputtableedit.cpp
new file mode 100644
index 000000000..9af5c627d
--- /dev/null
+++ b/kexi/widget/tableview/kexiinputtableedit.cpp
@@ -0,0 +1,395 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexiinputtableedit.h"
+
+#include <qregexp.h>
+#include <qevent.h>
+#include <qlayout.h>
+#include <qtimer.h>
+#include <qpainter.h>
+#include <qapplication.h>
+#include <qclipboard.h>
+#include <qtooltip.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <kcompletionbox.h>
+#include <knumvalidator.h>
+#include <kexiutils/longlongvalidator.h>
+#include <kexidb/field.h>
+#include <kexidb/fieldvalidator.h>
+
+//! @internal
+class MyLineEdit : public KLineEdit
+{
+ public:
+ MyLineEdit(QWidget *parent, const char *name) : KLineEdit(parent,name)
+ {}
+ protected:
+ virtual void drawFrame ( QPainter * p ) {
+ p->setPen( QPen( colorGroup().text() ) );
+ QRect r = rect();
+ p->moveTo( r.topLeft() );
+ p->lineTo( r.topRight() );
+ p->lineTo( r.bottomRight() );
+ p->lineTo( r.bottomLeft() );
+ if (pos().x() == 0) //draw left side only when it is @ the edge
+ p->lineTo( r.topLeft() );
+ }
+};
+
+//======================================================
+
+KexiInputTableEdit::KexiInputTableEdit(KexiTableViewColumn &column, QWidget *parent)
+ : KexiTableEdit(column, parent)
+{
+ setName("KexiInputTableEdit");
+// m_type = f.type(); //copied because the rest of code uses m_type
+// m_field = &f;
+// m_origValue = value;//original value
+ init();
+}
+
+KexiInputTableEdit::~KexiInputTableEdit()
+{
+}
+
+void KexiInputTableEdit::init()
+{
+// kdDebug() << "KexiInputTableEdit: m_origValue.typeName()==" << m_origValue.typeName() << endl;
+// kdDebug() << "KexiInputTableEdit: type== " << field()->typeName() << endl;
+// kdDebug() << "KexiInputTableEdit: displayed type== " << displayedField()->typeName() << endl;
+
+ m_textFormatter.setField( field() );
+
+ //init settings
+ m_decsym = KGlobal::locale()->decimalSymbol();
+ if (m_decsym.isEmpty())
+ m_decsym=".";//default
+
+ const bool align_right = displayedField()->isNumericType();
+
+ if (!align_right) {
+ //create layer for internal editor
+ QHBoxLayout *lyr = new QHBoxLayout(this);
+ lyr->addSpacing(4);
+ lyr->setAutoAdd(true);
+ }
+
+ //create internal editor
+ m_lineedit = new MyLineEdit(this, "KexiInputTableEdit-KLineEdit");
+ setViewWidget(m_lineedit);
+ if (align_right)
+ m_lineedit->setAlignment(AlignRight);
+// m_cview->setFrame(false);
+// m_cview->setFrameStyle( QFrame::Plain | QFrame::Box );
+// m_cview->setLineWidth( 1 );
+ m_calculatedCell = false;
+
+#if 0 //js TODO
+ connect(m_cview->completionBox(), SIGNAL(activated(const QString &)),
+ this, SLOT(completed(const QString &)));
+ connect(m_cview->completionBox(), SIGNAL(highlighted(const QString &)),
+ this, SLOT(completed(const QString &)));
+ m_cview->completionBox()->setTabHandling(true);
+#endif
+
+}
+
+void KexiInputTableEdit::setValueInternal(const QVariant& add, bool removeOld)
+{
+ QString text( m_textFormatter.valueToText(removeOld ? QVariant() : m_origValue, add.toString()) );
+ if (text.isEmpty()) {
+ if (m_origValue.toString().isEmpty()) {
+ //we have to set NULL initial value:
+ m_lineedit->setText(QString::null);
+ }
+ }
+ else {
+ m_lineedit->setText(text);
+ }
+
+#if 0
+//move to end is better by default
+ m_cview->selectAll();
+#else
+//js TODO: by default we're moving to the end of editor, ADD OPTION allowing "select all chars"
+ m_lineedit->end(false);
+#endif
+
+ if (!m_lineedit->validator()) {
+ QValidator *validator = new KexiDB::FieldValidator(
+ *field(), m_lineedit, "KexiInputTableEdit-validator");
+ m_lineedit->setValidator( validator );
+ }
+}
+
+#if 0
+//moved to KexiTextFormatter
+QString KexiInputTableEdit::valueToText(KexiDB::Field* field, const QVariant& value, const QString& add)
+{
+ QString text; //result
+
+ if (field->isFPNumericType()) {
+//! @todo precision!
+//! @todo support 'g' format
+ text = QString::number(value.toDouble(), 'f',
+ QMAX(field->visibleDecimalPlaces(), 10)); //<-- 10 is quite good maximum for fractional digits
+ //! @todo add command line settings?
+ if (value.toDouble() == 0.0) {
+ text = add.isEmpty() ? "0" : add; //eat 0
+ }
+ else {
+//! @todo (js): get decimal places settings here...
+ QStringList sl = QStringList::split(".", text);
+ if (text.isEmpty()) {
+ //nothing
+ }
+ else if (sl.count()==2) {
+// kdDebug() << "sl.count()=="<<sl.count()<< " " <<sl[0] << " | " << sl[1] << endl;
+ const QString sl1 = sl[1];
+ int pos = sl1.length()-1;
+ if (pos>=1) {
+ for (;pos>=0 && sl1[pos]=='0';pos--)
+ ;
+ pos++;
+ }
+ if (pos>0)
+ text = sl[0] + m_decsym + sl1.left(pos);
+ else
+ text = sl[0]; //no decimal point
+ }
+ text += add;
+ }
+/*moved to KexiDB::FieldValidator
+ if (setValidator && !m_lineedit->validator()) {
+ QValidator *validator = new KDoubleValidator(m_lineedit);
+ m_lineedit->setValidator( validator );
+ }*/
+ }
+ else {
+ text = value.toString();
+ if (field->isIntegerType()) {
+ if (value.toInt() == 0) {
+ text = add; //eat 0
+ }
+ else {
+ text += add;
+ }
+/*moved to KexiDB::FieldValidator
+//! @todo implement ranges here!
+ if (setValidator && !m_lineedit->validator()) {
+ QValidator *validator;
+ if (KexiDB::Field::BigInteger == field()->type()) {
+//! @todo use field->isUnsigned() for KexiUtils::ULongLongValidator
+ validator = new KexiUtils::LongLongValidator(m_lineedit);
+ }
+ else {
+ validator = new KIntValidator(m_lineedit);
+ }
+ m_lineedit->setValidator( validator );
+ }*/
+ }
+ else {//default: text
+ text += add;
+ }
+ }
+
+ return text;
+}
+#endif
+
+void KexiInputTableEdit::paintEvent ( QPaintEvent * /*e*/ )
+{
+ QPainter p(this);
+ p.setPen( QPen( colorGroup().text() ) );
+ p.drawRect( rect() );
+}
+
+void
+KexiInputTableEdit::setRestrictedCompletion()
+{
+#if 0 //js TODO
+kdDebug() << "KexiInputTableEdit::setRestrictedCompletion()" << endl;
+// KLineEdit *content = static_cast<KLineEdit*>(m_view);
+ if(m_cview->text().isEmpty())
+ return;
+
+ kdDebug() << "KexiInputTableEdit::setRestrictedCompletion(): something to do" << endl;
+
+ m_cview->useGlobalKeyBindings();
+
+ QStringList newC;
+ QStringList::ConstIterator it, end( m_comp.constEnd() );
+ for( it = m_comp.constBegin(); it != end; ++it)
+ {
+ if((*it).startsWith(m_cview->text()))
+ newC.append(*it);
+ }
+ m_cview->setCompletedItems(newC);
+#endif
+}
+
+void
+KexiInputTableEdit::completed(const QString &s)
+{
+// kdDebug() << "KexiInputTableEdit::completed(): " << s << endl;
+ m_lineedit->setText(s);
+}
+
+bool KexiInputTableEdit::valueChanged()
+{
+ //not needed? if (m_lineedit->text()!=m_origValue.toString())
+ //not needed? return true;
+ return KexiTableEdit::valueChanged();
+}
+
+bool KexiInputTableEdit::valueIsNull()
+{
+ return m_lineedit->text().isNull();
+}
+
+bool KexiInputTableEdit::valueIsEmpty()
+{
+ return !m_lineedit->text().isNull() && m_lineedit->text().isEmpty();
+}
+
+QVariant KexiInputTableEdit::value()
+{
+ if (field()->isFPNumericType()) {//==KexiDB::Field::Double || m_type==KexiDB::Field::Float) {
+ //! js @todo PRESERVE PRECISION!
+ QString txt = m_lineedit->text();
+ if (m_decsym!=".")
+ txt = txt.replace(m_decsym,".");//convert back
+ bool ok;
+ const double result = txt.toDouble(&ok);
+ return ok ? QVariant(result) : QVariant();
+ }
+ else if (field()->isIntegerType()) {
+//! @todo check constraints
+ bool ok;
+ if (KexiDB::Field::BigInteger == field()->type()) {
+ if (field()->isUnsigned()) {
+ const Q_ULLONG result = m_lineedit->text().toULongLong(&ok);
+ return ok ? QVariant(result) : QVariant();
+ }
+ else {
+ const Q_LLONG result = m_lineedit->text().toLongLong(&ok);
+ return ok ? QVariant(result) : QVariant();
+ }
+ }
+ if (KexiDB::Field::Integer == field()->type()) {
+ if (field()->isUnsigned()) {
+ const uint result = m_lineedit->text().toUInt(&ok);
+ return ok ? QVariant(result) : QVariant();
+ }
+ }
+ //default: signed int
+ const int result = m_lineedit->text().toInt(&ok);
+ return ok ? QVariant(result) : QVariant();
+ }
+ //default: text
+ return m_lineedit->text();
+}
+
+void
+KexiInputTableEdit::clear()
+{
+ m_lineedit->clear();
+}
+
+bool KexiInputTableEdit::cursorAtStart()
+{
+ return m_lineedit->cursorPosition()==0;
+}
+
+bool KexiInputTableEdit::cursorAtEnd()
+{
+ return m_lineedit->cursorPosition()==(int)m_lineedit->text().length();
+}
+
+QSize KexiInputTableEdit::totalSize()
+{
+ if (!m_lineedit)
+ return size();
+ return m_lineedit->size();
+}
+
+void KexiInputTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue)
+{
+ Q_UNUSED(visibleValue);
+//! @todo handle rich text?
+ qApp->clipboard()->setText( m_textFormatter.valueToText(value, QString::null) );
+}
+
+void KexiInputTableEdit::handleAction(const QString& actionName)
+{
+ const bool alreadyVisible = m_lineedit->isVisible();
+
+ if (actionName=="edit_paste") {
+ if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode
+ emit editRequested();
+ m_lineedit->clear();
+ }
+ m_lineedit->paste();
+ }
+ else if (actionName=="edit_cut") {
+//! @todo handle rich text?
+ if (!alreadyVisible) { //cut the entire text if the cell was not in edit mode
+ emit editRequested();
+ m_lineedit->selectAll();
+ }
+ m_lineedit->cut();
+ }
+}
+
+bool KexiInputTableEdit::showToolTipIfNeeded(const QVariant& value, const QRect& rect,
+ const QFontMetrics& fm, bool focused)
+{
+ QString text( value.type()==QVariant::String ? value.toString()
+ : m_textFormatter.valueToText(value, QString::null) );
+ QRect internalRect(rect);
+ internalRect.setLeft(rect.x()+leftMargin());
+ internalRect.setWidth(internalRect.width()-rightMargin(focused)-2*3);
+ kexidbg << rect << " " << internalRect << " " << fm.width(text) << endl;
+ return fm.width(text) > internalRect.width();
+}
+
+void KexiInputTableEdit::moveCursorToEnd()
+{
+ m_lineedit->end(false/*!mark*/);
+}
+
+void KexiInputTableEdit::moveCursorToStart()
+{
+ m_lineedit->home(false/*!mark*/);
+}
+
+void KexiInputTableEdit::selectAll()
+{
+ m_lineedit->selectAll();
+}
+
+KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiInputEditorFactoryItem, KexiInputTableEdit)
+
+#include "kexiinputtableedit.moc"
diff --git a/kexi/widget/tableview/kexiinputtableedit.h b/kexi/widget/tableview/kexiinputtableedit.h
new file mode 100644
index 000000000..df7702875
--- /dev/null
+++ b/kexi/widget/tableview/kexiinputtableedit.h
@@ -0,0 +1,126 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXIINPUTTABLEEDIT_H
+#define KEXIINPUTTABLEEDIT_H
+
+#include <klineedit.h>
+#include <qvariant.h>
+
+#include "kexitableedit.h"
+#include "kexicelleditorfactory.h"
+#include "kexitextformatter.h"
+
+/*! @short General purpose cell editor using line edit widget.
+*/
+class KEXIDATATABLE_EXPORT KexiInputTableEdit : public KexiTableEdit
+{
+ Q_OBJECT
+
+ public:
+ KexiInputTableEdit(KexiTableViewColumn &column, QWidget *parent=0);
+
+ virtual ~KexiInputTableEdit();
+
+#if 0
+//moved to KexiTextFormatter
+ /*! \return text for \a value and \a field.
+ \a add is a text that should be added to the value if possible.
+ Used in setValueInternal(), by form widgets and for reporting/printing. */
+ static QString valueToText(KexiDB::Field* field, const QVariant& value, const QString& add);
+#endif
+
+ virtual bool valueChanged();
+
+ //! \return true if editor's value is null (not empty)
+ virtual bool valueIsNull();
+
+ //! \return true if editor's value is empty (not null).
+ //! Only few field types can accept "EMPTY" property
+ //! (check this with KexiDB::Field::hasEmptyProperty()),
+ virtual bool valueIsEmpty();
+
+ virtual QVariant value();
+
+ virtual bool cursorAtStart();
+ virtual bool cursorAtEnd();
+
+// virtual bool eventFilter(QObject* watched, QEvent* e);
+//js void end(bool mark);
+//js void backspace();
+ virtual void clear();
+
+ /*! \return total size of this editor, including any buttons, etc. (if present). */
+ virtual QSize totalSize();
+
+ /*! Handles action having standard name \a actionName.
+ Action could be: "edit_cut", "edit_paste", etc. */
+ virtual void handleAction(const QString& actionName);
+
+ /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate
+ for the editor's impementation, e.g. for image cell it can be a pixmap.
+ \a visibleValue is unused here. Reimplemented after KexiTableEdit. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue);
+
+ /*! Shows a special tooltip for \a value if needed, i.e. if the value could not fit inside \a rect
+ for a given font metrics \a fm.
+ \return true a normal tooltip should be displayed (using QToolTip,) and false if
+ no tooltip should be displayed or a custom tooltip was displayed internally (not yet supported).
+ This implementation converts the value to text using valueToText() if \a calue is not string to see
+ whether it can fit inside the cell's \a rect.
+ If the cell is currentl focused (selected), \a focused is true. */
+ virtual bool showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm,
+ bool focused);
+
+ public slots:
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToEnd();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void moveCursorToStart();
+
+ //! Implemented for KexiDataItemInterface
+ virtual void selectAll();
+
+ protected slots:
+ void setRestrictedCompletion();
+ void completed(const QString &);
+
+ protected:
+ //! initializes this editor with \a add value
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+
+ void showHintButton();
+ void init();
+ virtual void paintEvent( QPaintEvent *e );
+
+ KexiTextFormatter m_textFormatter;
+ bool m_calculatedCell;
+ QString m_decsym; //! decimal symbol
+ QString m_origText; //! orig. Line Edit's text after conversion - for easy comparing
+ KLineEdit *m_lineedit;
+
+ signals:
+ void hintClicked();
+};
+
+KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiInputEditorFactoryItem)
+
+#endif
diff --git a/kexi/widget/tableview/kexitableedit.cpp b/kexi/widget/tableview/kexitableedit.cpp
new file mode 100644
index 000000000..8c3f56123
--- /dev/null
+++ b/kexi/widget/tableview/kexitableedit.cpp
@@ -0,0 +1,237 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexitableedit.h"
+#include "kexidataawareobjectiface.h"
+#include <kexidb/field.h>
+#include <kexidb/utils.h>
+
+#include <qpalette.h>
+#include <qpainter.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+KexiTableEdit::KexiTableEdit(KexiTableViewColumn &column, QWidget* parent)
+: QWidget(dynamic_cast<QScrollView*>(parent) ? dynamic_cast<QScrollView*>(parent)->viewport() : parent)
+ ,m_column(&column)
+// ,m_field(&f)
+// ,m_type(f.type()) //copied because the rest of code uses m_type
+ ,m_scrollView(dynamic_cast<QScrollView*>(parent))
+ ,m_usesSelectedTextColor(true)
+ ,m_view(0)
+// ,m_hasFocusableWidget(true)
+// ,m_acceptEditorAfterDeleteContents(false)
+{
+ setPaletteBackgroundColor( palette().color(QPalette::Active, QColorGroup::Base) );
+ installEventFilter(this);
+
+ //margins
+ if (displayedField()->isFPNumericType()) {
+#ifdef Q_WS_WIN
+ m_leftMargin = 0;
+#else
+ m_leftMargin = 0;
+#endif
+ }
+ else if (displayedField()->isIntegerType()) {
+#ifdef Q_WS_WIN
+ m_leftMargin = 1;
+#else
+ m_leftMargin = 0;
+#endif
+ }
+ else {//default
+#ifdef Q_WS_WIN
+ m_leftMargin = 5;
+#else
+ m_leftMargin = 5;
+#endif
+ }
+
+ m_rightMargin = 0;
+ m_rightMarginWhenFocused = 0;
+}
+
+KexiTableEdit::~KexiTableEdit()
+{
+}
+
+KexiDB::Field *KexiTableEdit::displayedField() const
+{
+ if (m_column->visibleLookupColumnInfo)
+ return m_column->visibleLookupColumnInfo->field; //mainly for lookup field in KexiComboBoxTableEdit:
+
+ return m_column->field(); //typical case
+}
+
+void KexiTableEdit::setViewWidget(QWidget *v)
+{
+ m_view = v;
+ m_view->move(0,0);
+ m_view->installEventFilter(this);
+ setFocusProxy(m_view);
+}
+
+void KexiTableEdit::moveChild( QWidget * child, int x, int y )
+{
+ if (m_scrollView)
+ m_scrollView->moveChild(child, x, y);
+}
+
+void KexiTableEdit::resize(int w, int h)
+{
+ QWidget::resize(w, h);
+ if (m_view) {
+ if (!layout()) { //if there is layout (eg. KexiInputTableEdit), resize is automatic
+ m_view->move(0,0);
+ m_view->resize(w, h);
+ }
+ }
+}
+
+bool
+KexiTableEdit::eventFilter(QObject* watched, QEvent* e)
+{
+/* if (watched == m_view) {
+ if(e->type() == QEvent::KeyPress) {
+ QKeyEvent* ev = static_cast<QKeyEvent*>(e);
+// if (ev->key()==Key_Tab) {
+
+// }
+ }
+ }*/
+
+ if(watched == this)
+ {
+ if(e->type() == QEvent::KeyPress)
+ {
+ QKeyEvent* ev = static_cast<QKeyEvent*>(e);
+
+ if(ev->key() == Key_Escape)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return false;
+// return QWidget::eventFilter(watched, e);
+}
+
+void KexiTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h )
+{
+ p->drawRect(x, y, w, h);
+}
+
+void KexiTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &/*x*/, int &y_offset, int &w, int &h )
+{
+ Q_UNUSED(p);
+ Q_UNUSED(focused);
+ Q_UNUSED(h);
+ KexiDB::Field *realField = displayedField();
+
+#ifdef Q_WS_WIN
+// x = 1;
+ y_offset = -1;
+#else
+// x = 1;
+ y_offset = 0;
+#endif
+
+ if (realField->isFPNumericType()) {
+//! @todo ADD OPTION to displaying NULL VALUES as e.g. "(null)"
+ if (!val.isNull()) {
+ txt = KexiDB::formatNumberForVisibleDecimalPlaces(
+ val.toDouble(), realField->visibleDecimalPlaces());
+ }
+ w -= 6;
+ align |= AlignRight;
+ }
+ else if (realField->isIntegerType()) {
+ Q_LLONG num = val.toLongLong();
+ w -= 6;
+ align |= AlignRight;
+ if (!val.isNull())
+ txt = QString::number(num);
+ }
+ else {//default:
+ if (!val.isNull()) {
+ txt = val.toString();
+ }
+ align |= AlignLeft;
+ }
+}
+
+void KexiTableEdit::paintSelectionBackground( QPainter *p, bool /*focused*/,
+ const QString& txt, int align, int x, int y_offset, int w, int h, const QColor& fillColor,
+ const QFontMetrics &fm, bool readOnly, bool fullRowSelection )
+{
+ if (!readOnly && !fullRowSelection && !txt.isEmpty()) {
+ QRect bound=fm.boundingRect(x, y_offset, w - (x+x), h, align, txt);
+ bound.setY(0);
+ bound.setWidth( QMIN( bound.width()+2, w - (x+x)+1 ) );
+ if (align & Qt::AlignLeft) {
+ bound.setX(bound.x()-1);
+ }
+ else if (align & Qt::AlignRight) {
+ bound.moveLeft( w - bound.width() ); //move to left, if too wide
+ }
+//TODO align center
+ bound.setHeight(h-1);
+ p->fillRect(bound, fillColor);
+ }
+ else if (fullRowSelection) {
+ p->fillRect(0, 0, w, h, fillColor);
+ }
+}
+
+int KexiTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm )
+{
+ return fm.width( val.toString() );
+}
+
+void KexiTableEdit::repaintRelatedCell()
+{
+ if (dynamic_cast<KexiDataAwareObjectInterface*>(m_scrollView))
+ dynamic_cast<KexiDataAwareObjectInterface*>(m_scrollView)->updateCurrentCell();
+}
+
+bool KexiTableEdit::showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm,
+ bool focused)
+{
+ Q_UNUSED(value);
+ Q_UNUSED(rect);
+ Q_UNUSED(fm);
+ Q_UNUSED(focused);
+ return false;
+}
+
+int KexiTableEdit::rightMargin(bool focused) const
+{
+ return focused ? m_rightMarginWhenFocused : m_rightMargin;
+}
+
+#include "kexitableedit.moc"
diff --git a/kexi/widget/tableview/kexitableedit.h b/kexi/widget/tableview/kexitableedit.h
new file mode 100644
index 000000000..ef38a11f2
--- /dev/null
+++ b/kexi/widget/tableview/kexitableedit.h
@@ -0,0 +1,233 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _KEXITABLEEDIT_H_
+#define _KEXITABLEEDIT_H_
+
+#include <kexidataiteminterface.h>
+
+#include <qvariant.h>
+#include <qscrollview.h>
+
+#include "kexitableviewdata.h"
+
+namespace KexiDB {
+ class Field;
+ class QueryColumnInfo;
+}
+
+/*! @short Abstract class for a cell editor.
+ Handles cell painting and displaying the editor widget.
+*/
+class KEXIDATATABLE_EXPORT KexiTableEdit : public QWidget, public KexiDataItemInterface
+{
+ Q_OBJECT
+
+ public:
+ KexiTableEdit(KexiTableViewColumn &column, QWidget* parent = 0);
+
+ virtual ~KexiTableEdit();
+
+ //! Implemented for KexiDataItemInterface.
+ //! \return field information for this item
+ virtual KexiDB::Field *field() const { return m_column->field(); }
+
+ /*! A rich field information for db-aware data.
+ For not-db-aware data it is always 0 (use field() instead. */
+ virtual KexiDB::QueryColumnInfo *columnInfo() const { return m_column->columnInfo; }
+
+ //! Implemented for KexiDataItemInterface.
+ //! Does nothing because instead KexiTableViewColumn is used to get field's schema.
+ virtual void setColumnInfo(KexiDB::QueryColumnInfo *) { }
+
+ //! \return column information for this item
+ //! (extended information, comparing to field()).
+ inline KexiTableViewColumn *column() const { return m_column; }
+
+ /*! \return displayed field. This is equal to field() in typical case but can return a different field
+ definition if the column contains a lookup field. This distiction is especially used for
+ displaying data dependent on the type and specifics of the field definition
+ (e.g. text type versus integer type). Note that to compute the editor's value
+ we still use field(). */
+ KexiDB::Field *displayedField() const;
+
+ /*! Reimplemented: resizes a view(). */
+ virtual void resize(int w, int h);
+
+ /*! \return the view widget of this editor, e.g. line edit widget. */
+ virtual QWidget* widget() { return m_view; }
+
+ /*! Hides item's widget, if available. */
+ inline virtual void hideWidget() { hide(); }
+
+ /*! Shows item's widget, if available. */
+ inline virtual void showWidget() { show(); }
+
+ /*! Paints a border for the cell described by \a x, \a y, \a w, \a h on \a p painter.
+ The cell's value is \a val (may be useful if you want to reimplement this method).
+ */
+ virtual void paintFocusBorders( QPainter *p, QVariant &cal, int x, int y, int w, int h );
+
+ /*! For reimplementation.
+ Sets up and paints cell's contents using context of \a val value.
+ \a focused is true if the cell is focused. \a align is set using Qt::AlignmentFlags.
+ Some additional things may be painted using \a p,
+ but it is not needed to paint the text (this is done automatically outside of this method).
+
+ Before calling, \a x, \a y_offset, \a w, \a h parameters are initialized,
+ but you can tune these values depending on the context.
+ You should set \a txt to a text representation of \a val,
+ otherwise no text will be painted.
+
+ \a p can be 0 - in this case no painting should be performed, becasue caller only expects
+ that \a x, \a y_offset, \a w, \a h, \a txt parameters are tuned, if needed.
+ \a p painter's pen is set to foreground color (usually black) that should be used to paint
+ foreground information, if needed. For example boolean editor widget paints
+ a rectangle using this color. */
+ virtual void setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h );
+
+ /*! \return true if "selected text" color should be used to paint contents of the editor.
+ True by default. It's false e.g. in boolean editor, where no selection is painted
+ using paintSelectionBackground().
+ This flag is set in editor's constructor and checked in KexiTableView::paintCell().
+ Depending on it, appropriate ("text" or "selected text" color is set for painter) before
+ setupContents() is called. */
+ bool usesSelectedTextColor() const { return m_usesSelectedTextColor; }
+
+ /*! For reimplementation.
+ Paints selection's background using \a p. Most parameters are similar to these from
+ setupContents(). */
+ virtual void paintSelectionBackground( QPainter *p, bool focused, const QString& txt,
+ int align, int x, int y_offset, int w, int h, const QColor& fillColor,
+ const QFontMetrics &fm, bool readOnly, bool fullRowSelection );
+
+ /*! Sometimes, editor can contain non-standard margin, for example combobox editor contains
+ dropdown button at the right side. \return left margin's size;
+ 0 by default. For reimplementation. */
+ int leftMargin() const { return m_leftMargin; }
+
+ /*! Sometimes, editor can contain non-standard margin, for example combobox editor contains
+ dropdown button at the right side. THe dropdown button's width is counted only if \a focused is true.
+ \return right margin's size; 0 by default. For reimplementation. */
+ int rightMargin(bool focused) const;
+
+ /*! Handles \a ke key event that came over the column that is bound to this editor.
+ For implementation: true should be returned if \a ke should be accepted.
+ If \a editorActive is true, this editor is currently active, i.e. the table view is in edit mode.
+ By default false is returned. */
+ virtual bool handleKeyPress( QKeyEvent* ke, bool editorActive ) {
+ Q_UNUSED(ke); Q_UNUSED(editorActive); return false; }
+
+ /*! Handles double click request coming from the table view.
+ \return true if it has been consumed.
+ Reimplemented in KexiBlobTableEdit (to execute "insert file" action. */
+ virtual bool handleDoubleClick() { return false; }
+
+ /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate
+ for the editor's impementation, e.g. for image cell it can be a pixmap.
+ For a special case (combo box), \a visibleValue can be provided,
+ so it can be copied to the clipboard instead of unreadable \a value.
+ For reimplementation. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue) = 0;
+
+ /*! \return width of \a value. For the default implementation \a val is converted to a string
+ and width of this string is returned. */
+ virtual int widthForValue( QVariant &val, const QFontMetrics &fm );
+
+ /*! \return total size of this editor, including any buttons, etc. (if present).
+ Reimpelment this if you want to return more appropriate size. This impelmentation just
+ returns QWidget::size(). */
+ virtual QSize totalSize() { return QWidget::size(); }
+
+ /*! Shows a special tooltip for \a value if needed, i.e. if the value could not fit inside \a rect
+ for a given font metrics \a fm.
+ \return true a normal tooltip should be displayed (using QToolTip,) and false if
+ no tooltip should be displayed or a custom tooltip was displayed internally (not yet supported).
+ Default implementation does nothing and returns false.
+ If the cell is currentl focused (selected), \a focused is true. */
+ virtual bool showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm,
+ bool focused);
+
+ /*! Created internal editor for this editor is needed. This method is only implemented
+ in KexiComboBoxTableEdit since it's visible value differs from internal value,
+ so a different KexiTableEdit object is used to displaying the data. */
+ virtual void createInternalEditor(KexiDB::QuerySchema& schema) { Q_UNUSED(schema); }
+
+ signals:
+ void editRequested();
+ void cancelRequested();
+ void acceptRequested();
+
+ protected:
+ virtual bool eventFilter(QObject* watched, QEvent* e);
+
+ /*! Sets \a v as view widget for this editor. The view will be assigned as focus proxy
+ for the editor, its events will be filtered, it will be resized when neede, and so on. */
+ void setViewWidget(QWidget *v);
+
+ /*! Moves child widget within the viewport if the parent is scrollview (otherwise does nothing).
+ Use this for child widgets that are outside of this editor widget, instead of calling QWidget::move(). */
+ void moveChild( QWidget * child, int x, int y );
+
+ /*! Allows to force redrawing the related cell by the editor itself. Usable when the editor is not
+ displayed by a QWidget but rather by table view cell itself, for example KexiBlobTableEdit. */
+ void repaintRelatedCell();
+
+ KexiTableViewColumn *m_column;
+ int m_leftMargin;
+ int m_rightMargin, m_rightMarginWhenFocused;
+ QScrollView* m_scrollView; //!< may be 0 if the parent is not a scrollview
+ bool m_usesSelectedTextColor : 1; //!< set in ctor, @see usesSelectedTextColor()
+
+ private:
+ QWidget* m_view;
+};
+
+//! Declaration of cell editor factory
+#define KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(factoryclassname) \
+ class factoryclassname : public KexiCellEditorFactoryItem \
+ { \
+ public: \
+ factoryclassname(); \
+ virtual ~factoryclassname(); \
+ \
+ protected: \
+ virtual KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0); \
+ };
+
+//! Implementation of cell editor factory
+#define KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(factoryclassname, itemclassname) \
+factoryclassname::factoryclassname() \
+ : KexiCellEditorFactoryItem() \
+{ \
+ m_className = "" #itemclassname ""; \
+} \
+\
+factoryclassname::~factoryclassname() \
+{} \
+\
+KexiTableEdit* factoryclassname::createEditor( \
+ KexiTableViewColumn &column, QWidget* parent) \
+{ \
+ return new itemclassname(column, parent); \
+}
+
+#endif
diff --git a/kexi/widget/tableview/kexitableitem.cpp b/kexi/widget/tableview/kexitableitem.cpp
new file mode 100644
index 000000000..e1669a358
--- /dev/null
+++ b/kexi/widget/tableview/kexitableitem.cpp
@@ -0,0 +1,62 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Original Author: Till Busch <till@bux.at>
+ Original Project: buX (www.bux.at)
+*/
+
+#include "kexitableitem.h"
+
+#include <kdebug.h>
+
+KexiTableItem::KexiTableItem(int numCols)
+: KexiTableItemBase(numCols)
+{
+//js m_userData=0;
+//js m_columns.resize(numCols);
+//js m_insertItem = false;
+//js m_pTable = 0;
+}
+
+KexiTableItem::~KexiTableItem()
+{
+}
+
+void
+KexiTableItem::init(int numCols)
+{
+ clear();
+ resize(numCols);
+}
+
+void
+KexiTableItem::clearValues()
+{
+ init(count());
+}
+
+void
+KexiTableItem::debug() const
+{
+ QString s = QString("KexiTableItem (%1 items)").arg(size());
+ for (uint i = 0; i < size(); i++)
+ s.append( QString::number(i)+":"+at(i).toString()+" " );
+ kexidbg << s << endl;
+}
diff --git a/kexi/widget/tableview/kexitableitem.h b/kexi/widget/tableview/kexitableitem.h
new file mode 100644
index 000000000..00074e825
--- /dev/null
+++ b/kexi/widget/tableview/kexitableitem.h
@@ -0,0 +1,58 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Original Author: Till Busch <till@bux.at>
+ Original Project: buX (www.bux.at)
+*/
+
+#ifndef KEXITABLEITEM_H
+#define KEXITABLEITEM_H
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qvaluevector.h>
+#include <qvariant.h>
+
+#include <kexidb/connection.h>
+
+typedef KexiDB::RowData KexiTableItemBase;
+
+class KEXIDATATABLE_EXPORT KexiTableItem : public KexiTableItemBase
+{
+ public:
+ ~KexiTableItem();
+
+ /*! Clears existing column values and inits new \a numCols
+ columns with empty values. ist of values is resized to \a numCols. */
+ void init(int numCols);
+
+ /*! Clears existing column values, current number of columns is preserved. */
+ void clearValues();
+
+ /*! Prints debug string for this item. */
+ void debug() const;
+
+ protected:
+ KexiTableItem(int numCols);
+
+ friend class KexiTableViewData;
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexitableview.cpp b/kexi/widget/tableview/kexitableview.cpp
new file mode 100644
index 000000000..108b16966
--- /dev/null
+++ b/kexi/widget/tableview/kexitableview.cpp
@@ -0,0 +1,2607 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Till Busch <till@bux.at>
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Original Author: Till Busch <till@bux.at>
+ Original Project: buX (www.bux.at)
+*/
+
+#include <qpainter.h>
+#include <qkeycode.h>
+#include <qlineedit.h>
+#include <qcombobox.h>
+#include <qwmatrix.h>
+#include <qtimer.h>
+#include <qpopupmenu.h>
+#include <qcursor.h>
+#include <qstyle.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qwhatsthis.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+
+#ifndef KEXI_NO_PRINT
+# include <kprinter.h>
+#endif
+
+#include "kexitableview.h"
+#include <kexiutils/utils.h>
+#include <kexiutils/validator.h>
+
+#include "kexicelleditorfactory.h"
+#include "kexitableviewheader.h"
+#include "kexitableview_p.h"
+#include <widget/utils/kexirecordmarker.h>
+#include <widget/utils/kexidisplayutils.h>
+#include <kexidb/cursor.h>
+
+KexiTableView::Appearance::Appearance(QWidget *widget)
+ : alternateBackgroundColor( KGlobalSettings::alternateBackgroundColor() )
+{
+ //set defaults
+ if (qApp) {
+ QPalette p = widget ? widget->palette() : qApp->palette();
+ baseColor = p.active().base();
+ textColor = p.active().text();
+ borderColor = QColor(200,200,200);
+ emptyAreaColor = p.active().color(QColorGroup::Base);
+ rowHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), baseColor, 33, 66);
+ rowMouseOverHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), baseColor, 10, 90);
+ rowMouseOverAlternateHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), alternateBackgroundColor, 10, 90);
+ rowHighlightingTextColor = textColor;
+ rowMouseOverHighlightingTextColor = textColor;
+ }
+ backgroundAltering = true;
+ rowMouseOverHighlightingEnabled = true;
+ rowHighlightingEnabled = true;
+ persistentSelections = true;
+ navigatorEnabled = true;
+ fullRowSelection = false;
+ gridEnabled = true;
+}
+
+//-----------------------------------------
+
+//! @internal A special What's This class displaying information about a given column
+class KexiTableView::WhatsThis : public QWhatsThis
+{
+ public:
+ WhatsThis(KexiTableView* tv) : QWhatsThis(tv), m_tv(tv)
+ {
+ Q_ASSERT(tv);
+ }
+ virtual ~WhatsThis()
+ {
+ }
+ virtual QString text( const QPoint & pos)
+ {
+ const int leftMargin = m_tv->verticalHeaderVisible() ? m_tv->verticalHeader()->width() : 0;
+ //const int topMargin = m_tv->horizontalHeaderVisible() ? m_tv->d->pTopHeader->height() : 0;
+ //const int bottomMargin = m_tv->d->appearance.navigatorEnabled ? m_tv->m_navPanel->height() : 0;
+ if (KexiUtils::hasParent(m_tv->verticalHeader(), m_tv->childAt(pos))) {
+ return i18n("Contains a pointer to the currently selected row");
+ }
+ else if (KexiUtils::hasParent(m_tv->m_navPanel, m_tv->childAt(pos))) {
+ return i18n("Row navigator");
+// return QWhatsThis::textFor(m_tv->m_navPanel, QPoint( pos.x(), pos.y() - m_tv->height() + bottomMargin ));
+ }
+ KexiDB::Field *f = m_tv->field( m_tv->columnAt(pos.x()-leftMargin) );
+ if (!f)
+ return QString::null;
+ return f->description().isEmpty() ? f->captionOrName() : f->description();
+ }
+ protected:
+ KexiTableView *m_tv;
+};
+
+//-----------------------------------------
+
+KexiTableViewCellToolTip::KexiTableViewCellToolTip( KexiTableView * tableView )
+ : QToolTip(tableView->viewport())
+ , m_tableView(tableView)
+{
+}
+
+KexiTableViewCellToolTip::~KexiTableViewCellToolTip()
+{
+ remove(parentWidget());
+}
+
+void KexiTableViewCellToolTip::maybeTip( const QPoint & p )
+{
+ const QPoint cp( m_tableView->viewportToContents( p ) );
+ const int row = m_tableView->rowAt( cp.y(), true/*ignoreEnd*/ );
+ const int col = m_tableView->columnAt( cp.x() );
+
+ //show tooltip if needed
+ if (col>=0 && row>=0) {
+ KexiTableEdit *editor = m_tableView->tableEditorWidget( col );
+ const bool insertRowSelected = m_tableView->isInsertingEnabled() && row==m_tableView->rows();
+ KexiTableItem *item = insertRowSelected ? m_tableView->m_insertItem : m_tableView->itemAt( row );
+ if (editor && item && (col < (int)item->count())) {
+ int w = m_tableView->columnWidth( col );
+ int h = m_tableView->rowHeight();
+ int x = 0;
+ int y_offset = 0;
+ int align = Qt::SingleLine | Qt::AlignVCenter;
+ QString txtValue;
+ QVariant cellValue;
+ KexiTableViewColumn *tvcol = m_tableView->column(col);
+ if (!m_tableView->getVisibleLookupValue(cellValue, editor, item, tvcol))
+ cellValue = insertRowSelected ? editor->displayedField()->defaultValue() : item->at(col); //display default value if available
+ const bool focused = m_tableView->selectedItem() == item && col == m_tableView->currentColumn();
+ editor->setupContents( 0, focused, cellValue, txtValue, align, x, y_offset, w, h );
+ QRect realRect(m_tableView->columnPos(col)-m_tableView->contentsX(),
+ m_tableView->rowPos(row)-m_tableView->contentsY(), w, h); //m_tableView->cellGeometry( row, col ));
+ if (editor->showToolTipIfNeeded(
+ txtValue.isEmpty() ? item->at(col) : QVariant(txtValue),
+ realRect, m_tableView->fontMetrics(), focused))
+ {
+ QString squeezedTxtValue;
+ if (txtValue.length() > 50)
+ squeezedTxtValue = txtValue.left(100) + "...";
+ else
+ squeezedTxtValue = txtValue;
+ tip( realRect, squeezedTxtValue );
+ }
+ }
+ }
+}
+
+//-----------------------------------------
+
+KexiTableView::KexiTableView(KexiTableViewData* data, QWidget* parent, const char* name)
+: QScrollView(parent, name, /*Qt::WRepaintNoErase | */Qt::WStaticContents /*| Qt::WResizeNoErase*/)
+, KexiRecordNavigatorHandler()
+, KexiSharedActionClient()
+, KexiDataAwareObjectInterface()
+{
+//not needed KexiTableView::initCellEditorFactories();
+
+ d = new KexiTableViewPrivate(this);
+
+ connect( kapp, SIGNAL( settingsChanged(int) ), SLOT( slotSettingsChanged(int) ) );
+ slotSettingsChanged(KApplication::SETTINGS_SHORTCUTS);
+
+ m_data = new KexiTableViewData(); //to prevent crash because m_data==0
+ m_owner = true; //-this will be deleted if needed
+
+ setResizePolicy(Manual);
+ viewport()->setBackgroundMode(Qt::NoBackground);
+// viewport()->setFocusPolicy(StrongFocus);
+ viewport()->setFocusPolicy(WheelFocus);
+ setFocusPolicy(WheelFocus); //<--- !!!!! important (was NoFocus),
+ // otherwise QApplication::setActiveWindow() won't activate
+ // this widget when needed!
+// setFocusProxy(viewport());
+ viewport()->installEventFilter(this);
+
+ //setup colors defaults
+ setBackgroundMode(Qt::PaletteBackground);
+// setEmptyAreaColor(d->appearance.baseColor);//palette().active().color(QColorGroup::Base));
+
+// d->baseColor = colorGroup().base();
+// d->textColor = colorGroup().text();
+
+// d->altColor = KGlobalSettings::alternateBackgroundColor();
+// d->grayColor = QColor(200,200,200);
+ d->diagonalGrayPattern = QBrush(d->appearance.borderColor, Qt::BDiagPattern);
+
+ setLineWidth(1);
+ horizontalScrollBar()->installEventFilter(this);
+ horizontalScrollBar()->raise();
+ verticalScrollBar()->raise();
+
+ //context menu
+ m_popupMenu = new KPopupMenu(this, "contextMenu");
+#if 0 //moved to mainwindow's actions
+ d->menu_id_addRecord = m_popupMenu->insertItem(i18n("Add Record"), this, SLOT(addRecord()), Qt::CTRL+Qt::Key_Insert);
+ d->menu_id_removeRecord = m_popupMenu->insertItem(
+ kapp->iconLoader()->loadIcon("button_cancel", KIcon::Small),
+ i18n("Remove Record"), this, SLOT(removeRecord()), Qt::CTRL+Qt::Key_Delete);
+#endif
+
+#ifdef Q_WS_WIN
+ d->rowHeight = fontMetrics().lineSpacing() + 4;
+#else
+ d->rowHeight = fontMetrics().lineSpacing() + 1;
+#endif
+
+ if(d->rowHeight < 17)
+ d->rowHeight = 17;
+
+ d->pUpdateTimer = new QTimer(this);
+
+// setMargins(14, fontMetrics().height() + 4, 0, 0);
+
+ // Create headers
+ m_horizontalHeader = new KexiTableViewHeader(this, "topHeader");
+ m_horizontalHeader->setSelectionBackgroundColor( palette().active().highlight() );
+ m_horizontalHeader->setOrientation(Qt::Horizontal);
+ m_horizontalHeader->setTracking(false);
+ m_horizontalHeader->setMovingEnabled(false);
+ connect(m_horizontalHeader, SIGNAL(sizeChange(int,int,int)), this, SLOT(slotTopHeaderSizeChange(int,int,int)));
+
+ m_verticalHeader = new KexiRecordMarker(this, "rm");
+ m_verticalHeader->setSelectionBackgroundColor( palette().active().highlight() );
+ m_verticalHeader->setCellHeight(d->rowHeight);
+// m_verticalHeader->setFixedWidth(d->rowHeight);
+ m_verticalHeader->setCurrentRow(-1);
+
+ setMargins(
+ QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight),
+ m_horizontalHeader->sizeHint().height(), 0, 0);
+
+ setupNavigator();
+
+// setMinimumHeight(horizontalScrollBar()->height() + d->rowHeight + topMargin());
+
+// navPanelLyr->addStretch(25);
+// enableClipper(true);
+
+ if (data)
+ setData( data );
+
+#if 0//(js) doesn't work!
+ d->scrollTimer = new QTimer(this);
+ connect(d->scrollTimer, SIGNAL(timeout()), this, SLOT(slotAutoScroll()));
+#endif
+
+// setBackgroundAltering(true);
+// setFullRowSelectionEnabled(false);
+
+ setAcceptDrops(true);
+ viewport()->setAcceptDrops(true);
+
+ // Connect header, table and scrollbars
+ connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), m_horizontalHeader, SLOT(setOffset(int)));
+ connect(verticalScrollBar(), SIGNAL(valueChanged(int)), m_verticalHeader, SLOT(setOffset(int)));
+ connect(m_horizontalHeader, SIGNAL(sizeChange(int, int, int)), this, SLOT(slotColumnWidthChanged(int, int, int)));
+ connect(m_horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)), this, SLOT(slotSectionHandleDoubleClicked(int)));
+ connect(m_horizontalHeader, SIGNAL(clicked(int)), this, SLOT(sortColumnInternal(int)));
+
+ connect(d->pUpdateTimer, SIGNAL(timeout()), this, SLOT(slotUpdate()));
+
+// horizontalScrollBar()->show();
+ updateScrollBars();
+// resize(sizeHint());
+// updateContents();
+// setMinimumHeight(horizontalScrollBar()->height() + d->rowHeight + topMargin());
+
+//TMP
+//setVerticalHeaderVisible(false);
+//setHorizontalHeaderVisible(false);
+
+//will be updated by setAppearance: updateFonts();
+ setAppearance(d->appearance); //refresh
+
+ d->cellToolTip = new KexiTableViewCellToolTip(this);
+ new WhatsThis(this);
+}
+
+KexiTableView::~KexiTableView()
+{
+ cancelRowEdit();
+
+ KexiTableViewData *data = m_data;
+ m_data = 0;
+ if (m_owner) {
+ if (data)
+ data->deleteLater();
+ }
+ delete d;
+}
+
+void KexiTableView::clearVariables()
+{
+ KexiDataAwareObjectInterface::clearVariables();
+ d->clearVariables();
+}
+
+/*void KexiTableView::initActions(KActionCollection *ac)
+{
+ emit reloadActions(ac);
+}*/
+
+void KexiTableView::setupNavigator()
+{
+ updateScrollBars();
+
+ m_navPanel = new KexiRecordNavigator(this, leftMargin(), "navPanel");
+ m_navPanel->setRecordHandler(this);
+ m_navPanel->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Preferred);
+}
+
+void KexiTableView::initDataContents()
+{
+ updateWidgetContentsSize();
+
+ KexiDataAwareObjectInterface::initDataContents();
+
+ m_navPanel->showEditingIndicator(false);
+}
+
+void KexiTableView::addHeaderColumn(const QString& caption, const QString& description,
+ const QIconSet& icon, int width)
+{
+ const int nr = m_horizontalHeader->count();
+ if (icon.isNull())
+ m_horizontalHeader->addLabel(caption, width);
+ else
+ m_horizontalHeader->addLabel(icon, caption, width);
+
+ if (!description.isEmpty())
+ m_horizontalHeader->setToolTip(nr, description);
+}
+
+void KexiTableView::updateWidgetContentsSize()
+{
+ QSize s(tableSize());
+ resizeContents(s.width(), s.height());
+}
+
+void KexiTableView::slotRowsDeleted( const QValueList<int> &rows )
+{
+ viewport()->repaint();
+ updateWidgetContentsSize();
+ setCursorPosition(QMAX(0, (int)m_curRow - (int)rows.count()), -1, true);
+}
+
+
+/*void KexiTableView::addDropFilter(const QString &filter)
+{
+ d->dropFilters.append(filter);
+ viewport()->setAcceptDrops(true);
+}*/
+
+void KexiTableView::setFont( const QFont &font )
+{
+ QScrollView::setFont(font);
+ updateFonts(true);
+}
+
+void KexiTableView::updateFonts(bool repaint)
+{
+#ifdef Q_WS_WIN
+ d->rowHeight = fontMetrics().lineSpacing() + 4;
+#else
+ d->rowHeight = fontMetrics().lineSpacing() + 1;
+#endif
+ if (d->appearance.fullRowSelection) {
+ d->rowHeight -= 1;
+ }
+ if(d->rowHeight < 17)
+ d->rowHeight = 17;
+// if(d->rowHeight < 22)
+// d->rowHeight = 22;
+ setMargins(
+ QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight),
+ m_horizontalHeader->sizeHint().height(), 0, 0);
+// setMargins(14, d->rowHeight, 0, 0);
+ m_verticalHeader->setCellHeight(d->rowHeight);
+
+ KexiDisplayUtils::initDisplayForAutonumberSign(d->autonumberSignDisplayParameters, this);
+ KexiDisplayUtils::initDisplayForDefaultValue(d->defaultValueDisplayParameters, this);
+
+ if (repaint)
+ updateContents();
+}
+
+void KexiTableView::updateAllVisibleRowsBelow(int row)
+{
+ //get last visible row
+ int r = rowAt(clipper()->height()+contentsY());
+ if (r==-1) {
+ r = rows()+1+(isInsertingEnabled()?1:0);
+ }
+ //update all visible rows below
+ int leftcol = m_horizontalHeader->sectionAt( m_horizontalHeader->offset() );
+// int row = m_curRow;
+ updateContents( columnPos( leftcol ), rowPos(row),
+ clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) );
+}
+
+void KexiTableView::clearColumnsInternal(bool /*repaint*/)
+{
+ while(m_horizontalHeader->count()>0)
+ m_horizontalHeader->removeLabel(0);
+}
+
+void KexiTableView::slotUpdate()
+{
+// kdDebug(44021) << " KexiTableView::slotUpdate() -- " << endl;
+// QSize s(tableSize());
+// viewport()->setUpdatesEnabled(false);
+/// resizeContents(s.width(), s.height());
+// viewport()->setUpdatesEnabled(true);
+
+ updateContents();
+ updateScrollBars();
+ if (m_navPanel)
+ m_navPanel->updateGeometry(leftMargin());
+// updateNavPanelGeometry();
+
+ updateWidgetContentsSize();
+// updateContents(0, contentsY()+clipper()->height()-2*d->rowHeight, clipper()->width(), d->rowHeight*3);
+
+ //updateGeometries();
+// updateContents(0, 0, viewport()->width(), contentsHeight());
+// updateGeometries();
+}
+
+int KexiTableView::currentLocalSortingOrder() const
+{
+ if (m_horizontalHeader->sortIndicatorSection()==-1)
+ return 0;
+ return (m_horizontalHeader->sortIndicatorOrder() == Qt::Ascending) ? 1 : -1;
+}
+
+void KexiTableView::setLocalSortingOrder(int col, int order)
+{
+ if (order == 0)
+ col = -1;
+ if (col>=0)
+ m_horizontalHeader->setSortIndicator(col, (order==1) ? Qt::Ascending : Qt::Descending);
+}
+
+int KexiTableView::currentLocalSortColumn() const
+{
+ return m_horizontalHeader->sortIndicatorSection();
+}
+
+void KexiTableView::updateGUIAfterSorting()
+{
+ int cw = columnWidth(m_curCol);
+ int rh = rowHeight();
+
+// m_verticalHeader->setCurrentRow(m_curRow);
+ center(columnPos(m_curCol) + cw / 2, rowPos(m_curRow) + rh / 2);
+// updateCell(oldRow, m_curCol);
+// updateCell(m_curRow, m_curCol);
+// slotUpdate();
+
+ updateContents();
+// d->pUpdateTimer->start(1,true);
+}
+
+QSizePolicy KexiTableView::sizePolicy() const
+{
+ // this widget is expandable
+ return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+}
+
+QSize KexiTableView::sizeHint() const
+{
+ const QSize &ts = tableSize();
+ int w = QMAX( ts.width() + leftMargin()+ verticalScrollBar()->sizeHint().width() + 2*2,
+ (m_navPanel->isVisible() ? m_navPanel->width() : 0) );
+ int h = QMAX( ts.height()+topMargin()+horizontalScrollBar()->sizeHint().height(),
+ minimumSizeHint().height() );
+ w = QMIN( w, qApp->desktop()->width()*3/4 ); //stretch
+ h = QMIN( h, qApp->desktop()->height()*3/4 ); //stretch
+
+// kexidbg << "KexiTableView::sizeHint()= " <<w <<", " <<h << endl;
+
+ return QSize(w, h);
+ /*QSize(
+ QMAX( ts.width() + leftMargin() + 2*2, (m_navPanel ? m_navPanel->width() : 0) ),
+ //+ QMIN(m_verticalHeader->width(),d->rowHeight) + margin()*2,
+ QMAX( ts.height()+topMargin()+horizontalScrollBar()->sizeHint().height(),
+ minimumSizeHint().height() )
+ );*/
+// QMAX(ts.height() + topMargin(), minimumSizeHint().height()) );
+}
+
+QSize KexiTableView::minimumSizeHint() const
+{
+ return QSize(
+ leftMargin() + ((columns()>0)?columnWidth(0):KEXI_DEFAULT_DATA_COLUMN_WIDTH) + 2*2,
+ d->rowHeight*5/2 + topMargin() + (m_navPanel && m_navPanel->isVisible() ? m_navPanel->height() : 0)
+ );
+}
+
+void KexiTableView::createBuffer(int width, int height)
+{
+ if(!d->pBufferPm)
+ d->pBufferPm = new QPixmap(width, height);
+ else
+ if(d->pBufferPm->width() < width || d->pBufferPm->height() < height)
+ d->pBufferPm->resize(width, height);
+// d->pBufferPm->fill();
+}
+
+//internal
+inline void KexiTableView::paintRow(KexiTableItem *item,
+ QPainter *pb, int r, int rowp, int cx, int cy,
+ int colfirst, int collast, int maxwc)
+{
+ if (!item)
+ return;
+ // Go through the columns in the row r
+ // if we know from where to where, go through [colfirst, collast],
+ // else go through all of them
+ if (colfirst==-1)
+ colfirst=0;
+ if (collast==-1)
+ collast=columns()-1;
+
+ int transly = rowp-cy;
+
+ if (d->appearance.rowHighlightingEnabled && r == m_curRow && !d->appearance.fullRowSelection) {
+ pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowHighlightingColor);
+ }
+ else if (d->appearance.rowMouseOverHighlightingEnabled && r == d->highlightedRow) {
+ if(d->appearance.backgroundAltering && (r%2 != 0))
+ pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowMouseOverAlternateHighlightingColor);
+ else
+ pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowMouseOverHighlightingColor);
+ }
+ else {
+ if(d->appearance.backgroundAltering && (r%2 != 0))
+ pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.alternateBackgroundColor);
+ else
+ pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.baseColor);
+ }
+
+ for(int c = colfirst; c <= collast; c++)
+ {
+ // get position and width of column c
+ int colp = columnPos(c);
+ if (colp==-1)
+ continue; //invisible column?
+ int colw = columnWidth(c);
+ int translx = colp-cx;
+
+ // Translate painter and draw the cell
+ pb->saveWorldMatrix();
+ pb->translate(translx, transly);
+ paintCell( pb, item, c, r, QRect(colp, rowp, colw, d->rowHeight));
+ pb->restoreWorldMatrix();
+ }
+
+ if (m_dragIndicatorLine>=0) {
+ int y_line = -1;
+ if (r==(rows()-1) && m_dragIndicatorLine==rows()) {
+ y_line = transly+d->rowHeight-3; //draw at last line
+ }
+ if (m_dragIndicatorLine==r) {
+ y_line = transly+1;
+ }
+ if (y_line>=0) {
+ RasterOp op = pb->rasterOp();
+ pb->setRasterOp(XorROP);
+ pb->setPen( QPen(Qt::white, 3) );
+ pb->drawLine(0, y_line, maxwc, y_line);
+ pb->setRasterOp(op);
+ }
+ }
+}
+
+void KexiTableView::drawContents( QPainter *p, int cx, int cy, int cw, int ch)
+{
+ if (d->disableDrawContents)
+ return;
+ int colfirst = columnAt(cx);
+ int rowfirst = rowAt(cy);
+ int collast = columnAt(cx + cw-1);
+ int rowlast = rowAt(cy + ch-1);
+ bool inserting = isInsertingEnabled();
+ bool plus1row = false; //true if we should show 'inserting' row at the end
+ bool paintOnlyInsertRow = false;
+
+/* kdDebug(44021) << QString(" KexiTableView::drawContents(cx:%1 cy:%2 cw:%3 ch:%4)")
+ .arg(cx).arg(cy).arg(cw).arg(ch) << endl;*/
+
+ if (rowlast == -1) {
+ rowlast = rows() - 1;
+ plus1row = inserting;
+ if (rowfirst == -1) {
+ if (rowAt(cy - d->rowHeight) != -1) {
+ paintOnlyInsertRow = true;
+// kdDebug(44021) << "-- paintOnlyInsertRow --" << endl;
+ }
+ }
+ }
+// kdDebug(44021) << "rowfirst="<<rowfirst<<" rowlast="<<rowlast<<" rows()="<<rows()<<endl;
+// kdDebug(44021)<<" plus1row=" << plus1row<<endl;
+
+ if ( collast == -1 )
+ collast = columns() - 1;
+
+ if (colfirst>collast) {
+ int tmp = colfirst;
+ colfirst = collast;
+ collast = tmp;
+ }
+ if (rowfirst>rowlast) {
+ int tmp = rowfirst;
+ rowfirst = rowlast;
+ rowlast = tmp;
+ }
+
+// qDebug("cx:%3d cy:%3d w:%3d h:%3d col:%2d..%2d row:%2d..%2d tsize:%4d,%4d",
+// cx, cy, cw, ch, colfirst, collast, rowfirst, rowlast, tableSize().width(), tableSize().height());
+// triggerUpdate();
+
+ if (rowfirst == -1 || colfirst == -1) {
+ if (!paintOnlyInsertRow && !plus1row) {
+ paintEmptyArea(p, cx, cy, cw, ch);
+ return;
+ }
+ }
+
+ createBuffer(cw, ch);
+ if(d->pBufferPm->isNull())
+ return;
+ QPainter *pb = new QPainter(d->pBufferPm, this);
+// pb->fillRect(0, 0, cw, ch, colorGroup().base());
+
+// int maxwc = QMIN(cw, (columnPos(d->numCols - 1) + columnWidth(d->numCols - 1)));
+ int maxwc = columnPos(columns() - 1) + columnWidth(columns() - 1);
+// kdDebug(44021) << "KexiTableView::drawContents(): maxwc: " << maxwc << endl;
+
+ pb->fillRect(cx, cy, cw, ch, d->appearance.baseColor);
+
+ int rowp;
+ int r;
+ if (paintOnlyInsertRow) {
+ r = rows();
+ rowp = rowPos(r); // 'insert' row's position
+ }
+ else {
+ QPtrListIterator<KexiTableItem> it = m_data->iterator();
+ it += rowfirst;//move to 1st row
+ rowp = rowPos(rowfirst); // row position
+ for (r = rowfirst;r <= rowlast; r++, ++it, rowp+=d->rowHeight) {
+ paintRow(it.current(), pb, r, rowp, cx, cy, colfirst, collast, maxwc);
+ }
+ }
+
+ if (plus1row) { //additional - 'insert' row
+ paintRow(m_insertItem, pb, r, rowp, cx, cy, colfirst, collast, maxwc);
+ }
+
+ delete pb;
+
+ p->drawPixmap(cx,cy,*d->pBufferPm, 0,0,cw,ch);
+
+ //(js)
+ paintEmptyArea(p, cx, cy, cw, ch);
+}
+
+bool KexiTableView::isDefaultValueDisplayed(KexiTableItem *item, int col, QVariant* value)
+{
+ const bool cursorAtInsertRowOrEditingNewRow = (item == m_insertItem || (m_newRowEditing && m_currentItem == item));
+ KexiTableViewColumn *tvcol;
+ if (cursorAtInsertRowOrEditingNewRow
+ && (tvcol = m_data->column(col))
+ && hasDefaultValueAt(*tvcol)
+ && !tvcol->field()->isAutoIncrement())
+ {
+ if (value)
+ *value = tvcol->field()->defaultValue();
+ return true;
+ }
+ return false;
+}
+
+void KexiTableView::paintCell(QPainter* p, KexiTableItem *item, int col, int row, const QRect &cr, bool print)
+{
+ p->save();
+ Q_UNUSED(print);
+ int w = cr.width();
+ int h = cr.height();
+ int x2 = w - 1;
+ int y2 = h - 1;
+
+/* if (0==qstrcmp("KexiComboBoxPopup",parentWidget()->className())) {
+ kexidbg << parentWidget()->className() << " >>>>>> KexiTableView::paintCell(col=" << col <<"row="<<row<<") w="<<w<<endl;
+ }*/
+
+ // Draw our lines
+ QPen pen(p->pen());
+
+ if (d->appearance.gridEnabled) {
+ p->setPen(d->appearance.borderColor);
+ p->drawLine( x2, 0, x2, y2 ); // right
+ p->drawLine( 0, y2, x2, y2 ); // bottom
+ }
+ p->setPen(pen);
+
+ if (m_editor && row == m_curRow && col == m_curCol //don't paint contents of edited cell
+ && m_editor->hasFocusableWidget() //..if it's visible
+ ) {
+ p->restore();
+ return;
+ }
+
+ KexiTableEdit *edit = tableEditorWidget( col, /*ignoreMissingEditor=*/true );
+// if (!edit)
+// return;
+
+ int x = edit ? edit->leftMargin() : 0;
+ int y_offset=0;
+
+ int align = Qt::SingleLine | Qt::AlignVCenter;
+ QString txt; //text to draw
+
+ KexiTableViewColumn *tvcol = m_data->column(col);
+
+ QVariant cellValue;
+ if (col < (int)item->count()) {
+ if (m_currentItem == item) {
+ if (m_editor && row == m_curRow && col == m_curCol
+ && !m_editor->hasFocusableWidget())
+ {
+ //we're over editing cell and the editor has no widget
+ // - we're displaying internal values, not buffered
+// bool ok;
+ cellValue = m_editor->value();
+ }
+ else {
+ //we're displaying values from edit buffer, if available
+ // this assignment will also get default value if there's no actual value set
+ cellValue = *bufferedValueAt(col);
+ }
+ }
+ else {
+ cellValue = item->at(col);
+ }
+ }
+
+ bool defaultValueDisplayed = isDefaultValueDisplayed(item, col);
+
+ if ((item == m_insertItem /*|| m_newRowEditing*/) && cellValue.isNull()) {
+ if (!tvcol->field()->isAutoIncrement() && !tvcol->field()->defaultValue().isNull()) {
+ //display default value in the "insert row", if available
+ //(but not if there is autoincrement flag set)
+ cellValue = tvcol->field()->defaultValue();
+ defaultValueDisplayed = true;
+ }
+ }
+
+ const bool columnReadOnly = tvcol->isReadOnly();
+ const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted
+ = d->appearance.rowHighlightingEnabled && !d->appearance.persistentSelections
+ && m_curRow /*d->highlightedRow*/ >= 0 && row != m_curRow; //d->highlightedRow;
+
+ // setup default pen
+ QPen defaultPen;
+ const bool usesSelectedTextColor = edit && edit->usesSelectedTextColor();
+ if (defaultValueDisplayed) {
+ if (col == m_curCol && row == m_curRow && usesSelectedTextColor)
+ defaultPen = d->defaultValueDisplayParameters.selectedTextColor;
+ else
+ defaultPen = d->defaultValueDisplayParameters.textColor;
+ }
+ else if (d->appearance.fullRowSelection
+ && (row == d->highlightedRow || (row == m_curRow && d->highlightedRow==-1))
+ && usesSelectedTextColor )
+ {
+ defaultPen = d->appearance.rowHighlightingTextColor; //special case: highlighted row
+ }
+ else if (d->appearance.fullRowSelection && row == m_curRow && usesSelectedTextColor)
+ {
+ defaultPen = d->appearance.textColor; //special case for full row selection
+ }
+ else if (m_currentItem == item && col == m_curCol && !columnReadOnly
+ && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted
+ && usesSelectedTextColor)
+ {
+ defaultPen = colorGroup().highlightedText(); //selected text
+ }
+ else if (d->appearance.rowHighlightingEnabled && row == m_curRow
+ && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted
+ && usesSelectedTextColor)
+ {
+ defaultPen = d->appearance.rowHighlightingTextColor;
+ }
+ else if (d->appearance.rowMouseOverHighlightingEnabled && row == d->highlightedRow
+ && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted
+ && usesSelectedTextColor)
+ {
+ defaultPen = d->appearance.rowMouseOverHighlightingTextColor;
+ }
+ else
+ defaultPen = d->appearance.textColor;
+
+ if (edit) {
+ if (defaultValueDisplayed)
+ p->setFont( d->defaultValueDisplayParameters.font );
+ p->setPen( defaultPen );
+
+ //get visible lookup value if available
+ getVisibleLookupValue(cellValue, edit, item, tvcol);
+
+ edit->setupContents( p, m_currentItem == item && col == m_curCol,
+ cellValue, txt, align, x, y_offset, w, h );
+ }
+ if (!d->appearance.gridEnabled)
+ y_offset++; //correction because we're not drawing cell borders
+
+ if (d->appearance.fullRowSelection && d->appearance.fullRowSelection) {
+// p->fillRect(x, y_offset, x+w-1, y_offset+h-1, red);
+ }
+ if (m_currentItem == item && (col == m_curCol || d->appearance.fullRowSelection)) {
+ if (edit && ((d->appearance.rowHighlightingEnabled && !d->appearance.fullRowSelection) || (row == m_curRow && d->highlightedRow==-1 && d->appearance.fullRowSelection))) //!dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted)
+ edit->paintSelectionBackground( p, isEnabled(), txt, align, x, y_offset, w, h,
+ isEnabled() ? colorGroup().highlight() : QColor(200,200,200),//d->grayColor,
+ p->fontMetrics(), columnReadOnly, d->appearance.fullRowSelection );
+ }
+
+ if (!edit) {
+ p->fillRect(0, 0, x2, y2, d->diagonalGrayPattern);
+ }
+
+// If we are in the focus cell, draw indication
+ if(m_currentItem == item && col == m_curCol //js: && !d->recordIndicator)
+ && !d->appearance.fullRowSelection)
+ {
+// kexidbg << ">>> CURRENT CELL ("<<m_curCol<<"," << m_curRow<<") focus="<<has_focus<<endl;
+// if (has_focus) {
+ if (isEnabled()) {
+ p->setPen(d->appearance.textColor);
+ }
+ else {
+ QPen gray_pen(p->pen());
+ gray_pen.setColor(d->appearance.borderColor);
+ p->setPen(gray_pen);
+ }
+ if (edit)
+ edit->paintFocusBorders( p, cellValue, 0, 0, x2, y2 );
+ else
+ p->drawRect(0, 0, x2, y2);
+ }
+
+/// bool autonumber = false;
+ if ((!m_newRowEditing && item == m_insertItem)
+ || (m_newRowEditing && item == m_currentItem && cellValue.isNull())) {
+ //we're in "insert row"
+ if (tvcol->field()->isAutoIncrement()) {
+ //"autonumber" column
+// txt = i18n("(autonumber)");
+// autonumber = true;
+// if (autonumber) {
+ KexiDisplayUtils::paintAutonumberSign(d->autonumberSignDisplayParameters, p,
+ x, y_offset, w - x - x - ((align & Qt::AlignLeft)?2:0), h, align);
+// }
+ }
+ }
+
+ // draw text
+ if (!txt.isEmpty()) {
+ if (defaultValueDisplayed)
+ p->setFont( d->defaultValueDisplayParameters.font );
+ p->setPen( defaultPen );
+ p->drawText(x, y_offset, w - (x + x)- ((align & Qt::AlignLeft)?2:0)/*right space*/, h,
+ align, txt);
+ }
+ p->restore();
+}
+
+QPoint KexiTableView::contentsToViewport2( const QPoint &p )
+{
+ return QPoint( p.x() - contentsX(), p.y() - contentsY() );
+}
+
+void KexiTableView::contentsToViewport2( int x, int y, int& vx, int& vy )
+{
+ const QPoint v = contentsToViewport2( QPoint( x, y ) );
+ vx = v.x();
+ vy = v.y();
+}
+
+QPoint KexiTableView::viewportToContents2( const QPoint& vp )
+{
+ return QPoint( vp.x() + contentsX(),
+ vp.y() + contentsY() );
+}
+
+void KexiTableView::paintEmptyArea( QPainter *p, int cx, int cy, int cw, int ch )
+{
+// qDebug("%s: paintEmptyArea(x:%d y:%d w:%d h:%d)", (const char*)parentWidget()->caption(),cx,cy,cw,ch);
+
+ // Regions work with shorts, so avoid an overflow and adjust the
+ // table size to the visible size
+ QSize ts( tableSize() );
+// ts.setWidth( QMIN( ts.width(), visibleWidth() ) );
+// ts.setHeight( QMIN( ts.height() - (m_navPanel ? m_navPanel->height() : 0), visibleHeight()) );
+/* kdDebug(44021) << QString(" (cx:%1 cy:%2 cw:%3 ch:%4)")
+ .arg(cx).arg(cy).arg(cw).arg(ch) << endl;
+ kdDebug(44021) << QString(" (w:%3 h:%4)")
+ .arg(ts.width()).arg(ts.height()) << endl;*/
+
+ // Region of the rect we should draw, calculated in viewport
+ // coordinates, as a region can't handle bigger coordinates
+ contentsToViewport2( cx, cy, cx, cy );
+ QRegion reg( QRect( cx, cy, cw, ch ) );
+
+//kexidbg << "---cy-- " << contentsY() << endl;
+
+ // Subtract the table from it
+// reg = reg.subtract( QRect( QPoint( 0, 0 ), ts-QSize(0,m_navPanel->isVisible() ? m_navPanel->height() : 0) ) );
+ reg = reg.subtract( QRect( QPoint( 0, 0 ), ts
+ -QSize(0,QMAX((m_navPanel ? m_navPanel->height() : 0), horizontalScrollBar()->sizeHint().height())
+ - (horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height()/2 : 0)
+ + (horizontalScrollBar()->isVisible() ? 0 :
+ d->internal_bottomMargin
+// horizontalScrollBar()->sizeHint().height()/2
+ )
+//- /*d->bottomMargin */ horizontalScrollBar()->sizeHint().height()*3/2
+ + contentsY()
+// - (verticalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height()/2 : 0)
+ )
+ ) );
+// reg = reg.subtract( QRect( QPoint( 0, 0 ), ts ) );
+
+ // And draw the rectangles (transformed inc contents coordinates as needed)
+ QMemArray<QRect> r = reg.rects();
+ for ( int i = 0; i < (int)r.count(); i++ ) {
+ QRect rect( viewportToContents2(r[i].topLeft()), r[i].size() );
+/* kdDebug(44021) << QString("- pEA: p->fillRect(x:%1 y:%2 w:%3 h:%4)")
+ .arg(rect.x()).arg(rect.y())
+ .arg(rect.width()).arg(rect.height()) << endl;*/
+// p->fillRect( QRect(viewportToContents2(r[i].topLeft()),r[i].size()), d->emptyAreaColor );
+ p->fillRect( rect, d->appearance.emptyAreaColor );
+// p->fillRect( QRect(viewportToContents2(r[i].topLeft()),r[i].size()), viewport()->backgroundBrush() );
+ }
+}
+
+void KexiTableView::contentsMouseDoubleClickEvent(QMouseEvent *e)
+{
+// kdDebug(44021) << "KexiTableView::contentsMouseDoubleClickEvent()" << endl;
+ m_contentsMousePressEvent_dblClick = true;
+ contentsMousePressEvent(e);
+ m_contentsMousePressEvent_dblClick = false;
+
+ if(m_currentItem)
+ {
+ if(d->editOnDoubleClick && columnEditable(m_curCol) && columnType(m_curCol) != KexiDB::Field::Boolean) {
+ KexiTableEdit *edit = tableEditorWidget( m_curCol, /*ignoreMissingEditor=*/true );
+ if (edit && edit->handleDoubleClick()) {
+ //nothing to do: editors like BLOB editor has custom handling of double clicking
+ }
+ else {
+ startEditCurrentCell();
+ // createEditor(m_curRow, m_curCol, QString::null);
+ }
+ }
+
+ emit itemDblClicked(m_currentItem, m_curRow, m_curCol);
+ }
+}
+
+void KexiTableView::contentsMousePressEvent( QMouseEvent* e )
+{
+// kdDebug(44021) << "KexiTableView::contentsMousePressEvent() ??" << endl;
+ setFocus();
+ if(m_data->count()==0 && !isInsertingEnabled()) {
+ QScrollView::contentsMousePressEvent( e );
+ return;
+ }
+
+ if (columnAt(e->pos().x())==-1) { //outside a colums
+ QScrollView::contentsMousePressEvent( e );
+ return;
+ }
+// d->contentsMousePressEvent_ev = *e;
+// d->contentsMousePressEvent_enabled = true;
+// QTimer::singleShot(2000, this, SLOT( contentsMousePressEvent_Internal() ));
+// d->contentsMousePressEvent_timer.start(100,true);
+
+// if (!d->contentsMousePressEvent_enabled)
+// return;
+// d->contentsMousePressEvent_enabled=false;
+
+ if (!d->moveCursorOnMouseRelease) {
+ if (!handleContentsMousePressOrRelease(e, false))
+ return;
+ }
+
+// kdDebug(44021)<<"void KexiTableView::contentsMousePressEvent( QMouseEvent* e ) by now the current items should be set, if not -> error + crash"<<endl;
+ if(e->button() == Qt::RightButton)
+ {
+ showContextMenu(e->globalPos());
+ }
+ else if(e->button() == Qt::LeftButton)
+ {
+ if(columnType(m_curCol) == KexiDB::Field::Boolean && columnEditable(m_curCol))
+ {
+ //only accept clicking on the [x] rect (copied from KexiBoolTableEdit::setupContents())
+ int s = QMAX(d->rowHeight - 5, 12);
+ s = QMIN( d->rowHeight-3, s );
+ s = QMIN( columnWidth(m_curCol)-3, s ); //avoid too large box
+ const QRect r( columnPos(m_curCol) + QMAX( columnWidth(m_curCol)/2 - s/2, 0 ), rowPos(m_curRow) +d->rowHeight/2 - s/2 /*- 1*/, s, s);
+ //kexidbg << r << endl;
+ if (r.contains(e->pos())) {
+// kexidbg << "e->x:" << e->x() << " e->y:" << e->y() << " " << rowPos(m_curRow) <<
+// " " << columnPos(m_curCol) << endl;
+ boolToggled();
+ }
+ }
+#if 0 //js: TODO
+ else if(columnType(m_curCol) == QVariant::StringList && columnEditable(m_curCol))
+ {
+ createEditor(m_curRow, m_curCol);
+ }
+#endif
+ }
+//ScrollView::contentsMousePressEvent( e );
+}
+
+void KexiTableView::contentsMouseReleaseEvent( QMouseEvent* e )
+{
+// kdDebug(44021) << "KexiTableView::contentsMousePressEvent() ??" << endl;
+ if(m_data->count()==0 && !isInsertingEnabled())
+ return;
+
+ if (d->moveCursorOnMouseRelease)
+ handleContentsMousePressOrRelease(e, true);
+
+ int col = columnAt(e->pos().x());
+ int row = rowAt(e->pos().y());
+
+ if (!m_currentItem || col==-1 || row==-1 || col!=m_curCol || row!=m_curRow)//outside a current cell
+ return;
+
+ QScrollView::contentsMouseReleaseEvent( e );
+
+ emit itemMouseReleased(m_currentItem, m_curRow, m_curCol);
+}
+
+bool KexiTableView::handleContentsMousePressOrRelease(QMouseEvent* e, bool release)
+{
+ // remember old focus cell
+ int oldRow = m_curRow;
+ int oldCol = m_curCol;
+ kdDebug(44021) << "oldRow=" << oldRow <<" oldCol=" << oldCol <<endl;
+ bool onInsertItem = false;
+
+ int newrow, newcol;
+ //compute clicked row nr
+ if (isInsertingEnabled()) {
+ if (rowAt(e->pos().y())==-1) {
+ newrow = rowAt(e->pos().y() - d->rowHeight);
+ if (newrow==-1 && m_data->count()>0) {
+ if (release)
+ QScrollView::contentsMouseReleaseEvent( e );
+ else
+ QScrollView::contentsMousePressEvent( e );
+ return false;
+ }
+ newrow++;
+ kdDebug(44021) << "Clicked just on 'insert' row." << endl;
+ onInsertItem=true;
+ }
+ else {
+ // get new focus cell
+ newrow = rowAt(e->pos().y());
+ }
+ }
+ else {
+ if (rowAt(e->pos().y())==-1 || columnAt(e->pos().x())==-1) {
+ if (release)
+ QScrollView::contentsMouseReleaseEvent( e );
+ else
+ QScrollView::contentsMousePressEvent( e );
+ return false; //clicked outside a grid
+ }
+ // get new focus cell
+ newrow = rowAt(e->pos().y());
+ }
+ newcol = columnAt(e->pos().x());
+
+ if(e->button() != Qt::NoButton) {
+ setCursorPosition(newrow,newcol);
+ }
+ return true;
+}
+
+void KexiTableView::showContextMenu(const QPoint& _pos)
+{
+ if (!d->contextMenuEnabled || m_popupMenu->count()<1)
+ return;
+ QPoint pos(_pos);
+ if (pos==QPoint(-1,-1)) {
+ pos = viewport()->mapToGlobal( QPoint( columnPos(m_curCol), rowPos(m_curRow) + d->rowHeight ) );
+ }
+ //show own context menu if configured
+// if (updateContextMenu()) {
+ selectRow(m_curRow);
+ m_popupMenu->exec(pos);
+/* }
+ else {
+ //request other context menu
+ emit contextMenuRequested(m_currentItem, m_curCol, pos);
+ }*/
+}
+
+void KexiTableView::contentsMouseMoveEvent( QMouseEvent *e )
+{
+ int row;
+ const int col = columnAt(e->x());
+ if (col < 0) {
+ row = -1;
+ } else {
+ row = rowAt( e->y(), true /*ignoreEnd*/ );
+ if (row > (rows() - 1 + (isInsertingEnabled()?1:0)))
+ row = -1; //no row to paint
+ }
+// kexidbg << " row="<<row<< " col="<<col<<endl;
+ //update row highlight if needed
+ if (d->appearance.rowMouseOverHighlightingEnabled) {
+ if (row != d->highlightedRow) {
+ const int oldRow = d->highlightedRow;
+ d->highlightedRow = row;
+ updateRow(oldRow);
+ updateRow(d->highlightedRow);
+ //currently selected (not necessary highlighted) row needs to be repainted
+ updateRow(m_curRow);
+ m_verticalHeader->setHighlightedRow(d->highlightedRow);
+ }
+ }
+
+#if 0//(js) doesn't work!
+
+ // do the same as in mouse press
+ int x,y;
+ contentsToViewport(e->x(), e->y(), x, y);
+
+ if(y > visibleHeight())
+ {
+ d->needAutoScroll = true;
+ d->scrollTimer->start(70, false);
+ d->scrollDirection = ScrollDown;
+ }
+ else if(y < 0)
+ {
+ d->needAutoScroll = true;
+ d->scrollTimer->start(70, false);
+ d->scrollDirection = ScrollUp;
+ }
+ else if(x > visibleWidth())
+ {
+ d->needAutoScroll = true;
+ d->scrollTimer->start(70, false);
+ d->scrollDirection = ScrollRight;
+ }
+ else if(x < 0)
+ {
+ d->needAutoScroll = true;
+ d->scrollTimer->start(70, false);
+ d->scrollDirection = ScrollLeft;
+ }
+ else
+ {
+ d->needAutoScroll = false;
+ d->scrollTimer->stop();
+ contentsMousePressEvent(e);
+ }
+#endif
+ QScrollView::contentsMouseMoveEvent(e);
+}
+
+#if 0//(js) doesn't work!
+void KexiTableView::contentsMouseReleaseEvent(QMouseEvent *)
+{
+ if(d->needAutoScroll)
+ {
+ d->scrollTimer->stop();
+ }
+}
+#endif
+
+static bool overrideEditorShortcutNeeded(QKeyEvent *e)
+{
+ //perhaps more to come...
+ return e->key() == Qt::Key_Delete && e->state()==Qt::ControlButton;
+}
+
+bool KexiTableView::shortCutPressed( QKeyEvent *e, const QCString &action_name )
+{
+ const int k = e->key();
+ KAction *action = m_sharedActions[action_name];
+ if (action) {
+ if (!action->isEnabled())//this action is disabled - don't process it!
+ return false;
+ if (action->shortcut() == KShortcut( KKey(e) )) {
+ //special cases when we need to override editor's shortcut
+ if (overrideEditorShortcutNeeded(e)) {
+ return true;
+ }
+ return false;//this shortcut is owned by shared action - don't process it!
+ }
+ }
+
+ //check default shortcut (when user app has no action shortcuts defined
+ // but we want these shortcuts to still work)
+ if (action_name=="data_save_row")
+ return (k == Qt::Key_Return || k == Qt::Key_Enter) && e->state()==Qt::ShiftButton;
+ if (action_name=="edit_delete_row")
+ return k == Qt::Key_Delete && e->state()==Qt::ControlButton;
+ if (action_name=="edit_delete")
+ return k == Qt::Key_Delete && e->state()==Qt::NoButton;
+ if (action_name=="edit_edititem")
+ return k == Qt::Key_F2 && e->state()==Qt::NoButton;
+ if (action_name=="edit_insert_empty_row")
+ return k == Qt::Key_Insert && e->state()==(Qt::ShiftButton | Qt::ControlButton);
+
+ return false;
+}
+
+void KexiTableView::keyPressEvent(QKeyEvent* e)
+{
+ if (!hasData())
+ return;
+// kexidbg << "KexiTableView::keyPressEvent: key=" <<e->key() << " txt=" <<e->text()<<endl;
+
+ const int k = e->key();
+ const bool ro = isReadOnly();
+ QWidget *w = focusWidget();
+// if (!w || w!=viewport() && w!=this && (!m_editor || w!=m_editor->view() && w!=m_editor)) {
+// if (!w || w!=viewport() && w!=this && (!m_editor || w!=m_editor->view())) {
+ if (!w || w!=viewport() && w!=this && (!m_editor || !KexiUtils::hasParent(dynamic_cast<QObject*>(m_editor), w))) {
+ //don't process stranger's events
+ e->ignore();
+ return;
+ }
+ if (d->skipKeyPress) {
+ d->skipKeyPress=false;
+ e->ignore();
+ return;
+ }
+
+ if(m_currentItem == 0 && (m_data->count() > 0 || isInsertingEnabled()))
+ {
+ setCursorPosition(0,0);
+ }
+ else if(m_data->count() == 0 && !isInsertingEnabled())
+ {
+ e->accept();
+ return;
+ }
+
+ if(m_editor) {// if a cell is edited, do some special stuff
+ if (k == Qt::Key_Escape) {
+ cancelEditor();
+ e->accept();
+ return;
+ } else if (k == Qt::Key_Return || k == Qt::Key_Enter) {
+ if (columnType(m_curCol) == KexiDB::Field::Boolean) {
+ boolToggled();
+ }
+ else {
+ acceptEditor();
+ }
+ e->accept();
+ return;
+ }
+ }
+ else if (m_rowEditing) {// if a row is in edit mode, do some special stuff
+ if (shortCutPressed( e, "data_save_row")) {
+ kexidbg << "shortCutPressed!!!" <<endl;
+ acceptRowEdit();
+ return;
+ }
+ }
+
+ if(k == Qt::Key_Return || k == Qt::Key_Enter)
+ {
+ emit itemReturnPressed(m_currentItem, m_curRow, m_curCol);
+ }
+
+ int curRow = m_curRow;
+ int curCol = m_curCol;
+
+ const bool nobtn = e->state()==NoButton;
+ bool printable = false;
+
+ //check shared shortcuts
+ if (!ro) {
+ if (shortCutPressed(e, "edit_delete_row")) {
+ deleteCurrentRow();
+ e->accept();
+ return;
+ } else if (shortCutPressed(e, "edit_delete")) {
+ deleteAndStartEditCurrentCell();
+ e->accept();
+ return;
+ }
+ else if (shortCutPressed(e, "edit_insert_empty_row")) {
+ insertEmptyRow();
+ e->accept();
+ return;
+ }
+ }
+
+/* case Qt::Key_Delete:
+ if (e->state()==Qt::ControlButton) {//remove current row
+ deleteCurrentRow();
+ }
+ else if (nobtn) {//remove contents of the current cell
+ deleteAndStartEditCurrentCell();
+ }
+ break;*/
+
+// bool _return;
+ if (k == Qt::Key_Shift || k == Qt::Key_Alt || k == Qt::Key_Control || k == Qt::Key_Meta) {
+ e->ignore();
+ }
+ else if (KexiDataAwareObjectInterface::handleKeyPress(e, curRow, curCol, d->appearance.fullRowSelection)) {
+ if (e->isAccepted())
+ return;
+ }
+ else if (k == Qt::Key_Backspace && nobtn) {
+ if (!ro && columnType(curCol) != KexiDB::Field::Boolean && columnEditable(curCol))
+ createEditor(curRow, curCol, QString::null, true);
+ }
+ else if (k == Qt::Key_Space) {
+ if (nobtn && !ro && columnEditable(curCol)) {
+ if (columnType(curCol) == KexiDB::Field::Boolean) {
+ boolToggled();
+ }
+ else
+ printable = true; //just space key
+ }
+ }
+ else if (k == Qt::Key_Escape) {
+ if (nobtn && m_rowEditing) {
+ cancelRowEdit();
+ return;
+ }
+ }
+ else {
+ //others:
+ if (nobtn && (k==Qt::Key_Tab || k==Qt::Key_Right)) {
+//! \todo add option for stopping at 1st column for Qt::Key_left
+ //tab
+ if (acceptEditor()) {
+ if (curCol == (columns() - 1)) {
+ if (curRow < (rows()-1+(isInsertingEnabled()?1:0))) {//skip to next row
+ curRow++;
+ curCol = 0;
+ }
+ }
+ else
+ curCol++;
+ }
+ }
+ else if ((e->state()==Qt::ShiftButton && k==Qt::Key_Tab)
+ || (nobtn && k==Qt::Key_Backtab)
+ || (e->state()==Qt::ShiftButton && k==Qt::Key_Backtab)
+ || (nobtn && k==Qt::Key_Left)
+ ) {
+//! \todo add option for stopping at last column
+ //backward tab
+ if (acceptEditor()) {
+ if (curCol == 0) {
+ if (curRow>0) {//skip to previous row
+ curRow--;
+ curCol = columns() - 1;
+ }
+ }
+ else
+ curCol--;
+ }
+ }
+ else if (nobtn && k==d->contextMenuKey) { //Qt::Key_Menu:
+ showContextMenu();
+ }
+ else {
+ KexiTableEdit *edit = tableEditorWidget( m_curCol );
+ if (edit && edit->handleKeyPress(e, m_editor==edit)) {
+ //try to handle the event @ editor's level
+ e->accept();
+ return;
+ }
+ else if ( nobtn && (k==Qt::Key_Enter || k==Qt::Key_Return || shortCutPressed(e, "edit_edititem")) ) {
+ //this condition is moved after handleKeyPress() to allow to everride enter key as well
+ startEditOrToggleValue();
+ }
+ else {
+ kexidbg << "KexiTableView::KeyPressEvent(): default" << endl;
+ if (e->text().isEmpty() || !e->text().isEmpty() && !e->text()[0].isPrint() ) {
+ kdDebug(44021) << "NOT PRINTABLE: 0x0" << QString("%1").arg(k,0,16) <<endl;
+ // e->ignore();
+ QScrollView::keyPressEvent(e);
+ return;
+ }
+ printable = true;
+ }
+ }
+ }
+ //finally: we've printable char:
+ if (printable && !ro) {
+ KexiTableViewColumn *tvcol = m_data->column(curCol);
+ if (tvcol->acceptsFirstChar(e->text()[0])) {
+ kdDebug(44021) << "KexiTableView::KeyPressEvent(): ev pressed: acceptsFirstChar()==true" << endl;
+ // if (e->text()[0].isPrint())
+ createEditor(curRow, curCol, e->text(), true);
+ }
+ else {
+//TODO show message "key not allowed eg. on a statusbar"
+ kdDebug(44021) << "KexiTableView::KeyPressEvent(): ev pressed: acceptsFirstChar()==false" << endl;
+ }
+ }
+
+ m_vScrollBarValueChanged_enabled=false;
+
+ // if focus cell changes, repaint
+ setCursorPosition(curRow, curCol);
+
+ m_vScrollBarValueChanged_enabled=true;
+
+ e->accept();
+}
+
+void KexiTableView::emitSelected()
+{
+ if(m_currentItem)
+ emit itemSelected(m_currentItem);
+}
+
+int KexiTableView::rowsPerPage() const
+{
+ return visibleHeight() / d->rowHeight;
+}
+
+KexiDataItemInterface *KexiTableView::editor( int col, bool ignoreMissingEditor )
+{
+ if (!m_data || col<0 || col>=columns())
+ return 0;
+ KexiTableViewColumn *tvcol = m_data->column(col);
+// int t = tvcol->field->type();
+
+ //find the editor for this column
+ KexiTableEdit *editor = d->editors[ tvcol ];
+ if (editor)
+ return editor;
+
+ //not found: create
+// editor = KexiCellEditorFactory::createEditor(*m_data->column(col)->field, this);
+ editor = KexiCellEditorFactory::createEditor(*tvcol, this);
+ if (!editor) {//create error!
+ if (!ignoreMissingEditor) {
+ //js TODO: show error???
+ cancelRowEdit();
+ }
+ return 0;
+ }
+ editor->hide();
+ if (m_data->cursor() && m_data->cursor()->query())
+ editor->createInternalEditor(*m_data->cursor()->query());
+
+ connect(editor,SIGNAL(editRequested()),this,SLOT(slotEditRequested()));
+ connect(editor,SIGNAL(cancelRequested()),this,SLOT(cancelEditor()));
+ connect(editor,SIGNAL(acceptRequested()),this,SLOT(acceptEditor()));
+
+ editor->resize(columnWidth(col)-1, rowHeight()-1);
+ editor->installEventFilter(this);
+ if (editor->widget())
+ editor->widget()->installEventFilter(this);
+ //store
+ d->editors.insert( tvcol, editor );
+ return editor;
+}
+
+void KexiTableView::editorShowFocus( int /*row*/, int col )
+{
+ KexiDataItemInterface *edit = editor( col );
+ /*nt p = rowPos(row);
+ (!edit || (p < contentsY()) || (p > (contentsY()+clipper()->height()))) {
+ kexidbg<< "KexiTableView::editorShowFocus() : OUT" << endl;
+ return;
+ }*/
+ if (edit) {
+ kexidbg<< "KexiTableView::editorShowFocus() : IN" << endl;
+ QRect rect = cellGeometry( m_curRow, m_curCol );
+// rect.moveBy( -contentsX(), -contentsY() );
+ edit->showFocus( rect, isReadOnly() || m_data->column(col)->isReadOnly() );
+ }
+}
+
+void KexiTableView::slotEditRequested()
+{
+ createEditor(m_curRow, m_curCol);
+}
+
+void KexiTableView::reloadData() {
+ KexiDataAwareObjectInterface::reloadData();
+ updateContents();
+}
+
+void KexiTableView::createEditor(int row, int col, const QString& addText, bool removeOld)
+{
+ kdDebug(44021) << "KexiTableView::createEditor('"<<addText<<"',"<<removeOld<<")"<<endl;
+ if (isReadOnly()) {
+ kdDebug(44021) << "KexiTableView::createEditor(): DATA IS READ ONLY!"<<endl;
+ return;
+ }
+
+ if (m_data->column(col)->isReadOnly()) {//d->pColumnModes.at(d->numCols-1) & ColumnReadOnly)
+ kdDebug(44021) << "KexiTableView::createEditor(): COL IS READ ONLY!"<<endl;
+ return;
+ }
+
+ const bool startRowEdit = !m_rowEditing; //remember if we're starting row edit
+
+ if (!m_rowEditing) {
+ //we're starting row editing session
+ m_data->clearRowEditBuffer();
+
+ m_rowEditing = true;
+ //indicate on the vheader that we are editing:
+ m_verticalHeader->setEditRow(m_curRow);
+ if (isInsertingEnabled() && m_currentItem==m_insertItem) {
+ //we should know that we are in state "new row editing"
+ m_newRowEditing = true;
+ //'insert' row editing: show another row after that:
+ m_data->append( m_insertItem );
+ //new empty 'inserting' item
+ m_insertItem = m_data->createItem();
+ m_verticalHeader->addLabel();
+ m_verticalHeaderAlreadyAdded = true;
+ updateWidgetContentsSize();
+ //refr. current and next row
+ updateContents(columnPos(0), rowPos(row), viewport()->width(), d->rowHeight*2);
+// updateContents(columnPos(0), rowPos(row+1), viewport()->width(), d->rowHeight);
+//js: warning this breaks behaviour (cursor is skipping, etc.): qApp->processEvents(500);
+ ensureVisible(columnPos(m_curCol), rowPos(row+1)+d->rowHeight-1, columnWidth(m_curCol), d->rowHeight);
+
+ m_verticalHeader->setOffset(contentsY());
+ }
+ }
+
+ KexiTableEdit *editorWidget = tableEditorWidget( col );
+ m_editor = editorWidget;
+ if (!editorWidget)
+ return;
+
+ m_editor->setValue(*bufferedValueAt(col, !removeOld/*useDefaultValueIfPossible*/), addText, removeOld);
+ if (m_editor->hasFocusableWidget()) {
+ moveChild(editorWidget, columnPos(m_curCol), rowPos(m_curRow));
+
+ editorWidget->resize(columnWidth(m_curCol)-1, rowHeight()-1);
+ editorWidget->show();
+
+ m_editor->setFocus();
+ }
+
+ if (startRowEdit) {
+ m_navPanel->showEditingIndicator(true); //this will allow to enable 'next' btn
+// m_navPanel->updateButtons(rows()); //refresh 'next' btn
+ emit rowEditStarted(m_curRow);
+ }
+}
+
+void KexiTableView::focusInEvent(QFocusEvent* e)
+{
+ Q_UNUSED(e);
+ updateCell(m_curRow, m_curCol);
+}
+
+void KexiTableView::focusOutEvent(QFocusEvent* e)
+{
+ KexiDataAwareObjectInterface::focusOutEvent(e);
+}
+
+bool KexiTableView::focusNextPrevChild(bool /*next*/)
+{
+ return false; //special Tab/BackTab meaning
+/* if (m_editor)
+ return true;
+ return QScrollView::focusNextPrevChild(next);*/
+}
+
+void KexiTableView::resizeEvent(QResizeEvent *e)
+{
+ QScrollView::resizeEvent(e);
+ //updateGeometries();
+
+ if (m_navPanel)
+ m_navPanel->updateGeometry(leftMargin());
+// updateNavPanelGeometry();
+
+ if ((contentsHeight() - e->size().height()) <= d->rowHeight) {
+ slotUpdate();
+ triggerUpdate();
+ }
+// d->pTopHeader->repaint();
+
+
+/* m_navPanel->setGeometry(
+ frameWidth(),
+ viewport()->height() +d->pTopHeader->height()
+ -(horizontalScrollBar()->isVisible() ? 0 : horizontalScrollBar()->sizeHint().height())
+ +frameWidth(),
+ m_navPanel->sizeHint().width(), // - verticalScrollBar()->sizeHint().width() - horizontalScrollBar()->sizeHint().width(),
+ horizontalScrollBar()->sizeHint().height()
+ );*/
+// updateContents();
+// m_navPanel->setGeometry(1,horizontalScrollBar()->pos().y(),
+ // m_navPanel->width(), horizontalScrollBar()->height());
+// updateContents(0,0,2000,2000);//js
+// erase(); repaint();
+}
+
+void KexiTableView::viewportResizeEvent( QResizeEvent *e )
+{
+ QScrollView::viewportResizeEvent( e );
+ updateGeometries();
+// erase(); repaint();
+}
+
+void KexiTableView::showEvent(QShowEvent *e)
+{
+ QScrollView::showEvent(e);
+ if (!d->maximizeColumnsWidthOnShow.isEmpty()) {
+ maximizeColumnsWidth(d->maximizeColumnsWidthOnShow);
+ d->maximizeColumnsWidthOnShow.clear();
+ }
+
+ if (m_initDataContentsOnShow) {
+ //full init
+ m_initDataContentsOnShow = false;
+ initDataContents();
+ }
+ else {
+ //just update size
+ QSize s(tableSize());
+// QRect r(cellGeometry(rows() - 1 + (isInsertingEnabled()?1:0), columns() - 1 ));
+// resizeContents(r.right() + 1, r.bottom() + 1);
+ resizeContents(s.width(),s.height());
+ }
+ updateGeometries();
+
+ //now we can ensure cell's visibility ( if there was such a call before show() )
+ if (d->ensureCellVisibleOnShow!=QPoint(-1,-1)) {
+ ensureCellVisible( d->ensureCellVisibleOnShow.x(), d->ensureCellVisibleOnShow.y() );
+ d->ensureCellVisibleOnShow = QPoint(-1,-1); //reset the flag
+ }
+ if (m_navPanel)
+ m_navPanel->updateGeometry(leftMargin());
+// updateNavPanelGeometry();
+}
+
+void KexiTableView::contentsDragMoveEvent(QDragMoveEvent *e)
+{
+ if (!hasData())
+ return;
+ if (m_dropsAtRowEnabled) {
+ QPoint p = e->pos();
+ int row = rowAt(p.y());
+ KexiTableItem *item = 0;
+// if (row==(rows()-1) && (p.y() % d->rowHeight) > (d->rowHeight*2/3) ) {
+ if ((p.y() % d->rowHeight) > (d->rowHeight*2/3) ) {
+ row++;
+ }
+ item = m_data->at(row);
+ emit dragOverRow(item, row, e);
+ if (e->isAccepted()) {
+ if (m_dragIndicatorLine>=0 && m_dragIndicatorLine != row) {
+ //erase old indicator
+ updateRow(m_dragIndicatorLine);
+ }
+ if (m_dragIndicatorLine != row) {
+ m_dragIndicatorLine = row;
+ updateRow(m_dragIndicatorLine);
+ }
+ }
+ else {
+ if (m_dragIndicatorLine>=0) {
+ //erase old indicator
+ updateRow(m_dragIndicatorLine);
+ }
+ m_dragIndicatorLine = -1;
+ }
+ }
+ else
+ e->acceptAction(false);
+/* QStringList::ConstIterator it, end( d->dropFilters.constEnd() );
+ for( it = d->dropFilters.constBegin(); it != end; it++)
+ {
+ if(e->provides((*it).latin1()))
+ {
+ e->acceptAction(true);
+ return;
+ }
+ }*/
+// e->acceptAction(false);
+}
+
+void KexiTableView::contentsDropEvent(QDropEvent *e)
+{
+ if (!hasData())
+ return;
+ if (m_dropsAtRowEnabled) {
+ //we're no longer dragging over the table
+ if (m_dragIndicatorLine>=0) {
+ int row2update = m_dragIndicatorLine;
+ m_dragIndicatorLine = -1;
+ updateRow(row2update);
+ }
+ QPoint p = e->pos();
+ int row = rowAt(p.y());
+ if ((p.y() % d->rowHeight) > (d->rowHeight*2/3) ) {
+ row++;
+ }
+ KexiTableItem *item = m_data->at(row);
+ KexiTableItem *newItem = 0;
+ emit droppedAtRow(item, row, e, newItem);
+ if (newItem) {
+ const int realRow = (row==m_curRow ? -1 : row);
+ insertItem(newItem, realRow);
+ setCursorPosition(row, 0);
+// m_currentItem = newItem;
+ }
+ }
+}
+
+void KexiTableView::viewportDragLeaveEvent( QDragLeaveEvent *e )
+{
+ Q_UNUSED(e);
+ if (!hasData())
+ return;
+ if (m_dropsAtRowEnabled) {
+ //we're no longer dragging over the table
+ if (m_dragIndicatorLine>=0) {
+ int row2update = m_dragIndicatorLine;
+ m_dragIndicatorLine = -1;
+ updateRow(row2update);
+ }
+ }
+}
+
+void KexiTableView::updateCell(int row, int col)
+{
+// kdDebug(44021) << "updateCell("<<row<<", "<<col<<")"<<endl;
+ updateContents(cellGeometry(row, col));
+/* QRect r = cellGeometry(row, col);
+ r.setHeight(r.height()+6);
+ r.setTop(r.top()-3);
+ updateContents();*/
+}
+
+void KexiTableView::updateCurrentCell()
+{
+ updateCell(m_curRow, m_curCol);
+}
+
+void KexiTableView::updateRow(int row)
+{
+// kdDebug(44021) << "updateRow("<<row<<")"<<endl;
+ if (row < 0 || row >= (rows() + 2/* sometimes we want to refresh the row after last*/ ))
+ return;
+ //int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() );
+
+ //kexidbg << contentsX() << " " << contentsY() << endl;
+ //kexidbg << QRect( columnPos( leftcol ), rowPos(row), clipper()->width(), rowHeight() ) << endl;
+ // updateContents( QRect( columnPos( leftcol ), rowPos(row), clipper()->width(), rowHeight() ) ); //columnPos(rightcol)+columnWidth(rightcol), rowHeight() ) );
+ updateContents( QRect( contentsX(), rowPos(row), clipper()->width(), rowHeight() ) ); //columnPos(rightcol)+columnWidth(rightcol), rowHeight() ) );
+}
+
+void KexiTableView::slotColumnWidthChanged( int, int, int )
+{
+ QSize s(tableSize());
+ int w = contentsWidth();
+ viewport()->setUpdatesEnabled(false);
+ resizeContents( s.width(), s.height() );
+ viewport()->setUpdatesEnabled(true);
+ if (contentsWidth() < w) {
+ updateContents(contentsX(), 0, viewport()->width(), contentsHeight());
+// repaintContents( s.width(), 0, w - s.width() + 1, contentsHeight(), true );
+ }
+ else {
+ // updateContents( columnPos(col), 0, contentsWidth(), contentsHeight() );
+ updateContents(contentsX(), 0, viewport()->width(), contentsHeight());
+ // viewport()->repaint();
+ }
+
+// updateContents(0, 0, d->pBufferPm->width(), d->pBufferPm->height());
+ QWidget *editorWidget = dynamic_cast<QWidget*>(m_editor);
+ if (editorWidget)
+ {
+ editorWidget->resize(columnWidth(m_curCol)-1, rowHeight()-1);
+ moveChild(editorWidget, columnPos(m_curCol), rowPos(m_curRow));
+ }
+ updateGeometries();
+ updateScrollBars();
+ if (m_navPanel)
+ m_navPanel->updateGeometry(leftMargin());
+// updateNavPanelGeometry();
+}
+
+void KexiTableView::slotSectionHandleDoubleClicked( int section )
+{
+ adjustColumnWidthToContents(section);
+ slotColumnWidthChanged(0,0,0); //to update contents and redraw
+}
+
+
+void KexiTableView::updateGeometries()
+{
+ QSize ts = tableSize();
+ if (m_horizontalHeader->offset() && ts.width() < (m_horizontalHeader->offset() + m_horizontalHeader->width()))
+ horizontalScrollBar()->setValue(ts.width() - m_horizontalHeader->width());
+
+// m_verticalHeader->setGeometry(1, topMargin() + 1, leftMargin(), visibleHeight());
+ m_horizontalHeader->setGeometry(leftMargin() + 1, 1, visibleWidth(), topMargin());
+ m_verticalHeader->setGeometry(1, topMargin() + 1, leftMargin(), visibleHeight());
+}
+
+int KexiTableView::columnWidth(int col) const
+{
+ if (!hasData())
+ return 0;
+ int vcID = m_data->visibleColumnID( col );
+ return (vcID==-1) ? 0 : m_horizontalHeader->sectionSize( vcID );
+}
+
+int KexiTableView::rowHeight() const
+{
+ return d->rowHeight;
+}
+
+int KexiTableView::columnPos(int col) const
+{
+ if (!hasData())
+ return 0;
+ //if this column is hidden, find first column before that is visible
+ int c = QMIN(col, (int)m_data->columnsCount()-1), vcID = 0;
+ while (c>=0 && (vcID=m_data->visibleColumnID( c ))==-1)
+ c--;
+ if (c<0)
+ return 0;
+ if (c==col)
+ return m_horizontalHeader->sectionPos(vcID);
+ return m_horizontalHeader->sectionPos(vcID)+m_horizontalHeader->sectionSize(vcID);
+}
+
+int KexiTableView::rowPos(int row) const
+{
+ return d->rowHeight*row;
+}
+
+int KexiTableView::columnAt(int pos) const
+{
+ if (!hasData())
+ return -1;
+ int r = m_horizontalHeader->sectionAt(pos);
+ if (r<0)
+ return r;
+ return m_data->globalColumnID( r );
+
+// if (r==-1)
+// kexidbg << "columnAt("<<pos<<")==-1 !!!" << endl;
+// return r;
+}
+
+int KexiTableView::rowAt(int pos, bool ignoreEnd) const
+{
+ if (!hasData())
+ return -1;
+ pos /=d->rowHeight;
+ if (pos < 0)
+ return 0;
+ if ((pos >= (int)m_data->count()) && !ignoreEnd)
+ return -1;
+ return pos;
+}
+
+QRect KexiTableView::cellGeometry(int row, int col) const
+{
+ return QRect(columnPos(col), rowPos(row),
+ columnWidth(col), rowHeight());
+}
+
+QSize KexiTableView::tableSize() const
+{
+ if ((rows()+ (isInsertingEnabled()?1:0) ) > 0 && columns() > 0) {
+/* kexidbg << "tableSize()= " << columnPos( columns() - 1 ) + columnWidth( columns() - 1 )
+ << ", " << rowPos( rows()-1+(isInsertingEnabled()?1:0)) + d->rowHeight
+// + QMAX(m_navPanel ? m_navPanel->height() : 0, horizontalScrollBar()->sizeHint().height())
+ + (m_navPanel->isVisible() ? QMAX( m_navPanel->height(), horizontalScrollBar()->sizeHint().height() ) :0 )
+ + margin() << endl;
+*/
+// kexidbg<< m_navPanel->isVisible() <<" "<<m_navPanel->height()<<" "
+// <<horizontalScrollBar()->sizeHint().height()<<" "<<rowPos( rows()-1+(isInsertingEnabled()?1:0))<<endl;
+
+ //int xx = horizontalScrollBar()->sizeHint().height()/2;
+
+ QSize s(
+ columnPos( columns() - 1 ) + columnWidth( columns() - 1 ),
+// + verticalScrollBar()->sizeHint().width(),
+ rowPos( rows()-1+(isInsertingEnabled()?1:0) ) + d->rowHeight
+ + (horizontalScrollBar()->isVisible() ? 0 : horizontalScrollBar()->sizeHint().height())
+ + d->internal_bottomMargin
+// horizontalScrollBar()->sizeHint().height()/2
+// - /*d->bottomMargin */ horizontalScrollBar()->sizeHint().height()*3/2
+
+// + ( (m_navPanel && m_navPanel->isVisible() && verticalScrollBar()->isVisible()
+ // && !horizontalScrollBar()->isVisible())
+ // ? horizontalScrollBar()->sizeHint().height() : 0)
+
+// + QMAX( (m_navPanel && m_navPanel->isVisible()) ? m_navPanel->height() : 0,
+// horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height() : 0)
+
+// + (m_navPanel->isVisible()
+// ? QMAX( m_navPanel->height(), horizontalScrollBar()->sizeHint().height() ) :0 )
+
+// - (horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height() :0 )
+ + margin()
+//-2*d->rowHeight
+ );
+
+// kexidbg << rows()-1 <<" "<< (isInsertingEnabled()?1:0) <<" "<< (m_rowEditing?1:0) << " " << s << endl;
+ return s;
+// +horizontalScrollBar()->sizeHint().height() + margin() );
+ }
+ return QSize(0,0);
+}
+
+void KexiTableView::ensureCellVisible(int row, int col/*=-1*/)
+{
+ if (!isVisible()) {
+ //the table is invisible: we can't ensure visibility now
+ d->ensureCellVisibleOnShow = QPoint(row,col);
+ return;
+ }
+
+ //quite clever: ensure the cell is visible:
+ QRect r( columnPos(col==-1 ? m_curCol : col), rowPos(row) +(d->appearance.fullRowSelection?1:0),
+ columnWidth(col==-1 ? m_curCol : col), rowHeight());
+
+/* if (m_navPanel && horizontalScrollBar()->isHidden() && row == rows()-1) {
+ //when cursor is moved down and navigator covers the cursor's area,
+ //area is scrolled up
+ if ((viewport()->height() - m_navPanel->height()) < r.bottom()) {
+ scrollBy(0,r.bottom() - (viewport()->height() - m_navPanel->height()));
+ }
+ }*/
+
+ if (m_navPanel && m_navPanel->isVisible() && horizontalScrollBar()->isHidden()) {
+ //a hack: for visible navigator: increase height of the visible rect 'r'
+ r.setBottom(r.bottom()+m_navPanel->height());
+ }
+
+ QPoint pcenter = r.center();
+ ensureVisible(pcenter.x(), pcenter.y(), r.width()/2, r.height()/2);
+// updateContents();
+// updateNavPanelGeometry();
+// slotUpdate();
+}
+
+void KexiTableView::updateAfterCancelRowEdit()
+{
+ KexiDataAwareObjectInterface::updateAfterCancelRowEdit();
+ m_navPanel->showEditingIndicator(false);
+}
+
+void KexiTableView::updateAfterAcceptRowEdit()
+{
+ KexiDataAwareObjectInterface::updateAfterAcceptRowEdit();
+ m_navPanel->showEditingIndicator(false);
+}
+
+bool KexiTableView::getVisibleLookupValue(QVariant& cellValue, KexiTableEdit *edit,
+ KexiTableItem *item, KexiTableViewColumn *tvcol) const
+{
+ if (edit->columnInfo() && edit->columnInfo()->indexForVisibleLookupValue()!=-1
+ && edit->columnInfo()->indexForVisibleLookupValue() < (int)item->count())
+ {
+ const QVariant *visibleFieldValue = 0;
+ if (m_currentItem == item && m_data->rowEditBuffer()) {
+ visibleFieldValue = m_data->rowEditBuffer()->at(
+ *tvcol->visibleLookupColumnInfo, false/*!useDefaultValueIfPossible*/ );
+ }
+
+ if (visibleFieldValue)
+ //(use bufferedValueAt() - try to get buffered visible value for lookup field)
+ cellValue = *visibleFieldValue; //txt = visibleFieldValue->toString();
+ else
+ cellValue /*txt*/ = item->at( edit->columnInfo()->indexForVisibleLookupValue() ); //.toString();
+ return true;
+ }
+ return false;
+}
+
+//reimpl.
+void KexiTableView::removeEditor()
+{
+ if (!m_editor)
+ return;
+ KexiDataAwareObjectInterface::removeEditor();
+ viewport()->setFocus();
+}
+
+void KexiTableView::slotRowRepaintRequested(KexiTableItem& item)
+{
+ updateRow( m_data->findRef(&item) );
+}
+
+//(js) unused
+void KexiTableView::slotAutoScroll()
+{
+ kdDebug(44021) << "KexiTableView::slotAutoScroll()" <<endl;
+ if (!d->needAutoScroll)
+ return;
+
+ switch(d->scrollDirection)
+ {
+ case ScrollDown:
+ setCursorPosition(m_curRow + 1, m_curCol);
+ break;
+
+ case ScrollUp:
+ setCursorPosition(m_curRow - 1, m_curCol);
+ break;
+ case ScrollLeft:
+ setCursorPosition(m_curRow, m_curCol - 1);
+ break;
+
+ case ScrollRight:
+ setCursorPosition(m_curRow, m_curCol + 1);
+ break;
+ }
+}
+
+#ifndef KEXI_NO_PRINT
+void
+KexiTableView::print(KPrinter &/*printer*/)
+{
+// printer.setFullPage(true);
+#if 0
+ int leftMargin = printer.margins().width() + 2 + d->rowHeight;
+ int topMargin = printer.margins().height() + 2;
+// int bottomMargin = topMargin + ( printer.realPageSize()->height() * printer.resolution() + 36 ) / 72;
+ int bottomMargin = 0;
+ kdDebug(44021) << "KexiTableView::print: bottom = " << bottomMargin << endl;
+
+ QPainter p(&printer);
+
+ KexiTableItem *i;
+ int width = leftMargin;
+ for(int col=0; col < columns(); col++)
+ {
+ p.fillRect(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight, QBrush(Qt::gray));
+ p.drawRect(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight);
+ p.drawText(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight, Qt::AlignLeft | Qt::AlignVCenter,
+ m_horizontalHeader->label(col));
+ width = width + columnWidth(col);
+ }
+
+ int yOffset = topMargin;
+ int row = 0;
+ int right = 0;
+ for(i = m_data->first(); i; i = m_data->next())
+ {
+ if(!i->isInsertItem())
+ { kdDebug(44021) << "KexiTableView::print: row = " << row << " y = " << yOffset << endl;
+ int xOffset = leftMargin;
+ for(int col=0; col < columns(); col++)
+ {
+ kdDebug(44021) << "KexiTableView::print: col = " << col << " x = " << xOffset << endl;
+ p.saveWorldMatrix();
+ p.translate(xOffset, yOffset);
+ paintCell(&p, i, col, QRect(0, 0, columnWidth(col) + 1, d->rowHeight), true);
+ p.restoreWorldMatrix();
+// p.drawRect(xOffset, yOffset, columnWidth(col), d->rowHeight);
+ xOffset = xOffset + columnWidth(col);
+ right = xOffset;
+ }
+
+ row++;
+ yOffset = topMargin + row * d->rowHeight;
+ }
+
+ if(yOffset > 900)
+ {
+ p.drawLine(leftMargin, topMargin, leftMargin, yOffset);
+ p.drawLine(leftMargin, topMargin, right - 1, topMargin);
+ printer.newPage();
+ yOffset = topMargin;
+ row = 0;
+ }
+ }
+ p.drawLine(leftMargin, topMargin, leftMargin, yOffset);
+ p.drawLine(leftMargin, topMargin, right - 1, topMargin);
+
+// p.drawLine(60,60,120,150);
+ p.end();
+#endif
+}
+#endif
+
+QString KexiTableView::columnCaption(int colNum) const
+{
+ return m_horizontalHeader->label(colNum);
+}
+
+KexiDB::Field* KexiTableView::field(int colNum) const
+{
+ if (!m_data || !m_data->column(colNum))
+ return 0;
+ return m_data->column(colNum)->field();
+}
+
+void KexiTableView::adjustColumnWidthToContents(int colNum)
+{
+ if (!hasData())
+ return;
+ if (colNum==-1) {
+ const int cols = columns();
+ for (int i=0; i<cols; i++)
+ adjustColumnWidthToContents(i);
+ return;
+ }
+
+ int indexOfVisibleColumn = (m_data->column(colNum) && m_data->column(colNum)->columnInfo)
+ ? m_data->column(colNum)->columnInfo->indexForVisibleLookupValue() : -1;
+ if (-1==indexOfVisibleColumn)
+ indexOfVisibleColumn = colNum;
+
+ if (indexOfVisibleColumn < 0)
+ return;
+
+ QPtrListIterator<KexiTableItem> it = m_data->iterator();
+ if (it.current() && it.current()->count()<=(uint)indexOfVisibleColumn)
+ return;
+
+ KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item( columnType(indexOfVisibleColumn) );
+ if (!item)
+ return;
+ QFontMetrics fm(fontMetrics());
+ int maxw = horizontalHeaderVisible()
+ ? fm.width( m_horizontalHeader->label( colNum/* not indexOfVisibleColumn*/ ) ) : 0;
+ if (maxw == 0 && m_data->isEmpty())
+ return; //nothing to adjust
+
+//! \todo js: this is NOT EFFECTIVE for big data sets!!!!
+
+ KexiTableEdit *ed = tableEditorWidget( colNum/* not indexOfVisibleColumn*/ );
+ if (ed) {
+ for (it = m_data->iterator(); it.current(); ++it) {
+ const int wfw = ed->widthForValue( it.current()->at( indexOfVisibleColumn ), fm );
+ maxw = QMAX( maxw, wfw );
+ }
+ const bool focused = currentColumn() == colNum;
+ maxw += (fm.width(" ") + ed->leftMargin() + ed->rightMargin(focused));
+ }
+ if (maxw < KEXITV_MINIMUM_COLUMN_WIDTH )
+ maxw = KEXITV_MINIMUM_COLUMN_WIDTH; //not too small
+ kexidbg << "KexiTableView: setColumnWidth(colNum=" << colNum
+ << ", indexOfVisibleColumn=" << indexOfVisibleColumn << ", width=" << maxw <<" )" << endl;
+ setColumnWidth( colNum/* not indexOfVisibleColumn*/, maxw );
+}
+
+void KexiTableView::setColumnWidth(int colNum, int width)
+{
+ if (columns()<=colNum || colNum < 0)
+ return;
+ const int oldWidth = m_horizontalHeader->sectionSize( colNum );
+ m_horizontalHeader->resizeSection( colNum, width );
+ slotTopHeaderSizeChange( colNum, oldWidth, m_horizontalHeader->sectionSize( colNum ) );
+}
+
+void KexiTableView::maximizeColumnsWidth( const QValueList<int> &columnList )
+{
+ if (!isVisible()) {
+ d->maximizeColumnsWidthOnShow += columnList;
+ return;
+ }
+ if (width() <= m_horizontalHeader->headerWidth())
+ return;
+ //sort the list and make it unique
+ QValueList<int> cl, sortedList = columnList;
+ qHeapSort(sortedList);
+ int i=-999;
+
+ QValueList<int>::ConstIterator it, end( sortedList.constEnd() );
+ for ( it = sortedList.constBegin(); it != end; ++it) {
+ if (i != (*it)) {
+ cl += (*it);
+ i = (*it);
+ }
+ }
+ //resize
+ int sizeToAdd = (width() - m_horizontalHeader->headerWidth()) / cl.count() - verticalHeader()->width();
+ if (sizeToAdd<=0)
+ return;
+ end = cl.constEnd();
+ for ( it = cl.constBegin(); it != end; ++it) {
+ int w = m_horizontalHeader->sectionSize(*it);
+ if (w>0) {
+ m_horizontalHeader->resizeSection(*it, w+sizeToAdd);
+ }
+ }
+ updateContents();
+ editorShowFocus( m_curRow, m_curCol );
+}
+
+void KexiTableView::adjustHorizontalHeaderSize()
+{
+ m_horizontalHeader->adjustHeaderSize();
+}
+
+void KexiTableView::setColumnStretchEnabled( bool set, int colNum )
+{
+ m_horizontalHeader->setStretchEnabled( set, colNum );
+}
+
+void KexiTableView::setEditableOnDoubleClick(bool set)
+{
+ d->editOnDoubleClick = set;
+}
+bool KexiTableView::editableOnDoubleClick() const
+{
+ return d->editOnDoubleClick;
+}
+
+bool KexiTableView::verticalHeaderVisible() const
+{
+ return m_verticalHeader->isVisible();
+}
+
+void KexiTableView::setVerticalHeaderVisible(bool set)
+{
+ int left_width;
+ if (set) {
+ m_verticalHeader->show();
+ left_width = QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight);
+ }
+ else {
+ m_verticalHeader->hide();
+ left_width = 0;
+ }
+ setMargins( left_width, horizontalHeaderVisible() ? m_horizontalHeader->sizeHint().height() : 0, 0, 0);
+}
+
+bool KexiTableView::horizontalHeaderVisible() const
+{
+ return d->horizontalHeaderVisible;
+}
+
+void KexiTableView::setHorizontalHeaderVisible(bool set)
+{
+ int top_height;
+ d->horizontalHeaderVisible = set; //needed because isVisible() is not always accurate
+ if (set) {
+ m_horizontalHeader->show();
+ top_height = m_horizontalHeader->sizeHint().height();
+ }
+ else {
+ m_horizontalHeader->hide();
+ top_height = 0;
+ }
+ setMargins( verticalHeaderVisible() ? m_verticalHeader->width() : 0, top_height, 0, 0);
+}
+
+void KexiTableView::triggerUpdate()
+{
+// kdDebug(44021) << "KexiTableView::triggerUpdate()" << endl;
+// if (!d->pUpdateTimer->isActive())
+ d->pUpdateTimer->start(20, true);
+// d->pUpdateTimer->start(200, true);
+}
+
+void KexiTableView::setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h )
+{
+/*todo*/
+ kdDebug(44021)<<"KexiTableView::setHBarGeometry"<<endl;
+ if (d->appearance.navigatorEnabled) {
+ m_navPanel->setHBarGeometry( hbar, x, y, w, h );
+ }
+ else {
+ hbar.setGeometry( x , y, w, h );
+ }
+}
+
+void KexiTableView::setSpreadSheetMode()
+{
+ KexiDataAwareObjectInterface::setSpreadSheetMode();
+ //copy m_navPanelEnabled flag
+ Appearance a = d->appearance;
+ a.navigatorEnabled = m_navPanelEnabled;
+ setAppearance( a );
+}
+
+int KexiTableView::validRowNumber(const QString& text)
+{
+ bool ok=true;
+ int r = text.toInt(&ok);
+ if (!ok || r<1)
+ r = 1;
+ else if (r > (rows()+(isInsertingEnabled()?1:0)))
+ r = rows()+(isInsertingEnabled()?1:0);
+ return r-1;
+}
+
+void KexiTableView::moveToRecordRequested( uint r )
+{
+ if (r > uint(rows()+(isInsertingEnabled()?1:0)))
+ r = rows()+(isInsertingEnabled()?1:0);
+ setFocus();
+ selectRow( r );
+}
+
+void KexiTableView::moveToLastRecordRequested()
+{
+ setFocus();
+ selectRow(rows()>0 ? (rows()-1) : 0);
+}
+
+void KexiTableView::moveToPreviousRecordRequested()
+{
+ setFocus();
+ selectPrevRow();
+}
+
+void KexiTableView::moveToNextRecordRequested()
+{
+ setFocus();
+ selectNextRow();
+}
+
+void KexiTableView::moveToFirstRecordRequested()
+{
+ setFocus();
+ selectFirstRow();
+}
+
+void KexiTableView::copySelection()
+{
+ if (m_currentItem && m_curCol!=-1) {
+ KexiTableEdit *edit = tableEditorWidget( m_curCol );
+ QVariant defaultValue;
+ const bool defaultValueDisplayed
+ = isDefaultValueDisplayed(m_currentItem, m_curCol, &defaultValue);
+ if (edit) {
+ QVariant visibleValue;
+ getVisibleLookupValue(visibleValue, edit, m_currentItem, m_data->column(m_curCol));
+ edit->handleCopyAction(
+ defaultValueDisplayed ? defaultValue : m_currentItem->at( m_curCol ),
+ visibleValue );
+ }
+ }
+}
+
+void KexiTableView::cutSelection()
+{
+ //try to handle @ editor's level
+ KexiTableEdit *edit = tableEditorWidget( m_curCol );
+ if (edit)
+ edit->handleAction("edit_cut");
+}
+
+void KexiTableView::paste()
+{
+ //try to handle @ editor's level
+ KexiTableEdit *edit = tableEditorWidget( m_curCol );
+ if (edit)
+ edit->handleAction("edit_paste");
+}
+
+bool KexiTableView::eventFilter( QObject *o, QEvent *e )
+{
+ //don't allow to stole key my events by others:
+// kexidbg << "spontaneous " << e->spontaneous() << " type=" << e->type() << endl;
+
+ if (e->type()==QEvent::KeyPress) {
+ if (e->spontaneous() /*|| e->type()==QEvent::AccelOverride*/) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ const int k = ke->key();
+ int s = ke->state();
+ //cell editor's events:
+ //try to handle the event @ editor's level
+ KexiTableEdit *edit = tableEditorWidget( m_curCol );
+ if (edit && edit->handleKeyPress(ke, m_editor==edit)) {
+ ke->accept();
+ return true;
+ }
+ else if (m_editor && (o==dynamic_cast<QObject*>(m_editor) || o==m_editor->widget())) {
+ if ( (k==Qt::Key_Tab && (s==Qt::NoButton || s==Qt::ShiftButton))
+ || (overrideEditorShortcutNeeded(ke))
+ || (k==Qt::Key_Enter || k==Qt::Key_Return || k==Qt::Key_Up || k==Qt::Key_Down)
+ || (k==Qt::Key_Left && m_editor->cursorAtStart())
+ || (k==Qt::Key_Right && m_editor->cursorAtEnd())
+ )
+ {
+ //try to steal the key press from editor or it's internal widget...
+ keyPressEvent(ke);
+ if (ke->isAccepted())
+ return true;
+ }
+ }
+ /*
+ else if (e->type()==QEvent::KeyPress && (o==this || (m_editor && o==m_editor->widget()))){//|| o==viewport())
+ keyPressEvent(ke);
+ if (ke->isAccepted())
+ return true;
+ }*/
+/*todo else if ((k==Qt::Key_Tab || k==(Qt::SHIFT|Qt::Key_Tab)) && o==d->navRowNumber) {
+ //tab key focuses tv
+ ke->accept();
+ setFocus();
+ return true;
+ }*/
+ }
+ }
+ else if (o==horizontalScrollBar()) {
+ if ((e->type()==QEvent::Show && !horizontalScrollBar()->isVisible())
+ || (e->type()==QEvent::Hide && horizontalScrollBar()->isVisible())) {
+ updateWidgetContentsSize();
+ }
+ }
+ else if (e->type()==QEvent::Leave) {
+ if (o==viewport() && d->appearance.rowMouseOverHighlightingEnabled
+ && d->appearance.persistentSelections)
+ {
+ if (d->highlightedRow!=-1) {
+ int oldRow = d->highlightedRow;
+ d->highlightedRow = -1;
+ updateRow(oldRow);
+ const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted
+ = d->appearance.rowHighlightingEnabled && !d->appearance.persistentSelections;
+ if (oldRow!=m_curRow && m_curRow>=0) {
+ if (!dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted)
+ //no highlight for now: show selection again
+ updateRow(m_curRow);
+ m_verticalHeader->setHighlightedRow(-1);
+ }
+ }
+ }
+ d->recentCellWithToolTip = QPoint(-1,-1);
+ }
+/* else if (e->type()==QEvent::FocusOut && o->inherits("QWidget")) {
+ //hp==true if currently focused widget is a child of this table view
+ const bool hp = KexiUtils::hasParent( static_cast<QWidget*>(o), focusWidget());
+ if (!hp && KexiUtils::hasParent( this, static_cast<QWidget*>(o))) {
+ //accept row editing if focus is moved to foreign widget
+ //(not a child, like eg. editor) from one of our table view's children
+ //or from table view itself
+ if (!acceptRowEdit()) {
+ static_cast<QWidget*>(o)->setFocus();
+ return true;
+ }
+ }
+ }*/
+ return QScrollView::eventFilter(o,e);
+}
+
+void KexiTableView::slotTopHeaderSizeChange(
+ int /*section*/, int /*oldSize*/, int /*newSize*/ )
+{
+ editorShowFocus( m_curRow, m_curCol );
+}
+
+void KexiTableView::setBottomMarginInternal(int pixels)
+{
+ d->internal_bottomMargin = pixels;
+}
+
+void KexiTableView::paletteChange( const QPalette &oldPalette )
+{
+ Q_UNUSED(oldPalette);
+ //update:
+ if (m_verticalHeader)
+ m_verticalHeader->setSelectionBackgroundColor( palette().active().highlight() );
+ if (m_horizontalHeader)
+ m_horizontalHeader->setSelectionBackgroundColor( palette().active().highlight() );
+}
+
+const KexiTableView::Appearance& KexiTableView::appearance() const
+{
+ return d->appearance;
+}
+
+void KexiTableView::setAppearance(const Appearance& a)
+{
+// if (d->appearance.fullRowSelection != a.fullRowSelection) {
+ if (a.fullRowSelection) {
+ d->rowHeight -= 1;
+ }
+ else {
+ d->rowHeight += 1;
+ }
+ if (m_verticalHeader)
+ m_verticalHeader->setCellHeight(d->rowHeight);
+ if (m_horizontalHeader) {
+ setMargins(
+ QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight),
+ m_horizontalHeader->sizeHint().height(), 0, 0);
+ }
+// }
+ if (a.rowHighlightingEnabled)
+ m_updateEntireRowWhenMovingToOtherRow = true;
+
+ if(!a.navigatorEnabled)
+ m_navPanel->hide();
+ else
+ m_navPanel->show();
+// }
+
+ d->highlightedRow = -1;
+//! @todo is setMouseTracking useful for other purposes?
+ viewport()->setMouseTracking(a.rowMouseOverHighlightingEnabled);
+
+ d->appearance = a;
+
+ setFont(font()); //this also updates contents
+}
+
+int KexiTableView::highlightedRow() const
+{
+ return d->highlightedRow;
+}
+
+void KexiTableView::setHighlightedRow(int row)
+{
+ if (row!=-1) {
+ row = QMIN(rows() - 1 + (isInsertingEnabled()?1:0), row);
+ row = QMAX(0, row);
+ ensureCellVisible(row, -1);
+ }
+ const int previouslyHighlightedRow = d->highlightedRow;
+ if (previouslyHighlightedRow == row) {
+ if (previouslyHighlightedRow!=-1)
+ updateRow(previouslyHighlightedRow);
+ return;
+ }
+ d->highlightedRow = row;
+ if (d->highlightedRow!=-1)
+ updateRow(d->highlightedRow);
+
+ if (previouslyHighlightedRow!=-1)
+ updateRow(previouslyHighlightedRow);
+
+ if (m_curRow>=0 && (previouslyHighlightedRow==-1 || previouslyHighlightedRow==m_curRow)
+ && d->highlightedRow!=m_curRow && !d->appearance.persistentSelections)
+ {
+ //currently selected row needs to be repainted
+ updateRow(m_curRow);
+ }
+}
+
+KexiTableItem *KexiTableView::highlightedItem() const
+{
+ return d->highlightedRow == -1 ? 0 : m_data->at(d->highlightedRow);
+}
+
+void KexiTableView::slotSettingsChanged(int category)
+{
+ if (category==KApplication::SETTINGS_SHORTCUTS) {
+ d->contextMenuKey = KGlobalSettings::contextMenuKey();
+ }
+}
+
+int KexiTableView::lastVisibleRow() const
+{
+ return rowAt( contentsY() );
+}
+
+#include "kexitableview.moc"
+
diff --git a/kexi/widget/tableview/kexitableview.h b/kexi/widget/tableview/kexitableview.h
new file mode 100644
index 000000000..9f9c632e9
--- /dev/null
+++ b/kexi/widget/tableview/kexitableview.h
@@ -0,0 +1,639 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Till Busch <till@bux.at>
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Original Author: Till Busch <till@bux.at>
+ Original Project: buX (www.bux.at)
+*/
+
+#ifndef KEXITABLEVIEW_H
+#define KEXITABLEVIEW_H
+
+#include <qscrollview.h>
+#include <qvariant.h>
+#include <qptrlist.h>
+#include <qheader.h>
+#include <qtooltip.h>
+
+#include <kdebug.h>
+
+#include "kexitableviewdata.h"
+#include "kexitableedit.h"
+#include <kexiutils/tristate.h>
+#include <widget/utils/kexirecordnavigator.h>
+#include <widget/utils/kexisharedactionclient.h>
+#include "kexidataawareobjectiface.h"
+
+class KPopupMenu;
+class KPrinter;
+class KAction;
+
+class KexiTableHeader;
+class KexiTableItem;
+class KexiTableView;
+class KexiTableEdit;
+class KexiTableViewPrivate;
+class KActionCollection;
+
+namespace KexiDB {
+ class RowEditBuffer;
+}
+
+//! minimum column width in pixels
+#define KEXITV_MINIMUM_COLUMN_WIDTH 10
+
+//! @short KexiTableView class provides a widget for displaying data in a tabular view.
+/*! @see KexiFormScrollView
+*/
+class KEXIDATATABLE_EXPORT KexiTableView :
+ public QScrollView,
+ public KexiRecordNavigatorHandler,
+ public KexiSharedActionClient,
+ public KexiDataAwareObjectInterface
+{
+Q_OBJECT
+KEXI_DATAAWAREOBJECTINTERFACE
+public:
+
+ /*! Defines table view's detailed appearance settings. */
+ class KEXIDATATABLE_EXPORT Appearance {
+ public:
+ Appearance(QWidget *widget = 0);
+
+ /*! base color for cells, default is "Base" color for application's
+ current active palette */
+ QColor baseColor;
+
+ /*! text color for cells, default is "Text" color for application's
+ current active palette */
+ QColor textColor;
+
+ /*! border color for cells, default is QColor(200,200,200) */
+ QColor borderColor;
+
+ /*! empty area color, default is "Base" color for application's
+ current active palette */
+ QColor emptyAreaColor;
+
+ /*! alternate background color, default is KGlobalSettings::alternateBackgroundColor() */
+ QColor alternateBackgroundColor;
+
+ /*! true if background altering should be enabled, true by default */
+ bool backgroundAltering : 1;
+
+ /*! true if full-row-selection mode is set,
+ what means that all cells of the current row are always selected, instead of single cell.
+ This mode is usable for read-only table views, when we're interested only in navigating
+ by rows. False by default, even for read-only table views.
+ */
+ bool fullRowSelection : 1;
+
+ /*! true if fullgrid is enabled. True by default.
+ It is set to false for comboboxpopup table, to mimic original
+ combobox look and feel. */
+ bool gridEnabled : 1;
+
+ /*! \if the navigation panel is enabled (visible) for the view.
+ True by default. */
+ bool navigatorEnabled : 1;
+
+ /*! true if "row highlight" behaviour is enabled. False by default. */
+ bool rowHighlightingEnabled : 1;
+
+ /*! true if "row highlight over " behaviour is enabled. False by default. */
+ bool rowMouseOverHighlightingEnabled : 1;
+
+ /*! true if selection of a row should be kept when a user moved mouse
+ pointer over other rows. Makes only sense when rowMouseOverHighlightingEnabled is true.
+ True by default. It is set to false for comboboxpopup table, to mimic original
+ combobox look and feel. */
+ bool persistentSelections : 1;
+
+ /*! color for row highlight, default is intermediate (33%/60%) between
+ active highlight and base color. */
+ QColor rowHighlightingColor;
+
+ /*! color for text under row highlight, default is the same as textColor.
+ Used when rowHighlightingEnabled is true; */
+ QColor rowHighlightingTextColor;
+
+ /*! color for row highlight for mouseover, default is intermediate (20%/80%) between
+ active highlight and base color. Used when rowMouseOverHighlightingEnabled is true. */
+ QColor rowMouseOverHighlightingColor;
+
+ /*! color for text under row highlight for mouseover, default is the same as textColor.
+ Used when rowMouseOverHighlightingEnabled is true; */
+ QColor rowMouseOverHighlightingTextColor;
+
+ /*! Like rowMouseOverHighlightingColor but for areas painted with alternate color.
+ This is computed using active highlight color and alternateBackgroundColor. */
+ QColor rowMouseOverAlternateHighlightingColor;
+ };
+
+ KexiTableView(KexiTableViewData* data=0, QWidget* parent=0, const char* name=0);
+ virtual ~KexiTableView();
+
+ /*! \return current appearance settings */
+ const Appearance& appearance() const;
+
+ /*! Sets appearance settings. Table view is updated automatically. */
+ void setAppearance(const Appearance& a);
+
+ /*! \return string displayed for column's header \a colNum */
+ QString columnCaption(int colNum) const;
+
+ /*! Convenience function.
+ \return field object that define column \a colNum or NULL if there is no such column */
+ KexiDB::Field* field(int colNum) const;
+
+ /*! Reimplementation for KexiDataAwareObjectInterface */
+ virtual void setSpreadSheetMode();
+
+ /*! \return true if vertical scrollbar's tooltips are enabled (true by default). */
+//moved bool scrollbarToolTipsEnabled() const;
+
+ /*! Enables or disables vertical scrollbar's. */
+//moved void setScrollbarToolTipsEnabled(bool set);
+
+ /*! \return maximum number of rows that can be displayed per one "page"
+ for current table view's size. */
+ virtual int rowsPerPage() const;
+
+ QRect cellGeometry(int row, int col) const;
+ int columnWidth(int col) const;
+ int rowHeight() const;
+ int columnPos(int col) const;
+ int rowPos(int row) const;
+ int columnAt(int pos) const;
+ int rowAt(int pos, bool ignoreEnd=false) const;
+
+ /*! \return last row visible on the screen (counting from 0).
+ The returned value is guaranteed to be smaller or equal to currentRow() or -1
+ if there are no rows. */
+ virtual int lastVisibleRow() const;
+
+ /*! Redraws specified cell. */
+ virtual void updateCell(int row, int col);
+
+ /*! Redraws the current cell. Implemented after KexiDataAwareObjectInterface. */
+ virtual void updateCurrentCell();
+
+ /*! Redraws all cells of specified row. */
+ virtual void updateRow(int row);
+
+ bool editableOnDoubleClick() const;
+ void setEditableOnDoubleClick(bool set);
+
+ //! \return true if the vertical header is visible
+ bool verticalHeaderVisible() const;
+
+ //! Sets vertical header's visibility
+ void setVerticalHeaderVisible(bool set);
+
+ //! \return true if the horizontal header is visible
+ bool horizontalHeaderVisible() const;
+
+ //! Sets horizontal header's visibility
+ void setHorizontalHeaderVisible(bool set);
+
+#ifndef KEXI_NO_PRINT
+ // printing
+// void setupPrinter(KPrinter &printer);
+ void print(KPrinter &printer);
+#endif
+
+ // reimplemented for internal reasons
+ virtual QSizePolicy sizePolicy() const;
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ /*! Reimplemented to update cached fonts and row sizes for the painter. */
+ void setFont(const QFont &f);
+
+ virtual QSize tableSize() const;
+
+ void emitSelected();
+
+ //! single shot after 1ms for contents updatinh
+ void triggerUpdate();
+
+ typedef enum ScrollDirection
+ {
+ ScrollUp,
+ ScrollDown,
+ ScrollLeft,
+ ScrollRight
+ };
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ //! Initializes standard editor cell editor factories. This is called internally, once.
+ static void initCellEditorFactories();
+
+ /*! \return highlighted row number or -1 if no row is highlighted.
+ Makes sense if row highlighting is enabled.
+ @see Appearance::rowHighlightingEnabled setHighlightedRow() */
+ int highlightedRow() const;
+
+ KexiTableItem *highlightedItem() const;
+
+ /*! \return vertical scrollbar. Implemented for KexiDataAwareObjectInterface. */
+ virtual QScrollBar* verticalScrollBar() const { return QScrollView::verticalScrollBar(); }
+
+public slots:
+ virtual void setData( KexiTableViewData *data, bool owner = true )
+ { KexiDataAwareObjectInterface::setData( data, owner ); }
+
+ virtual void clearColumnsInternal(bool repaint);
+
+ /*! Adjusts \a colNum column's width to its (current) contents.
+ If \a colNum == -1, all columns' width is adjusted. */
+ void adjustColumnWidthToContents(int colNum = -1);
+
+ //! Sets width of column width to \a width.
+ void setColumnWidth(int col, int width);
+
+ /*! If \a set is true, \a colNum column is resized to take full possible width.
+ If \a set is false, no automatic resize will be performed.
+ If \a colNum is -1, all columns are equally resized, when needed, to take full possible width.
+ This method behaves like QHeader::setStretchEnabled ( bool b, int section ). */
+ void setColumnStretchEnabled( bool set, int colNum );
+
+ /*! Maximizes widths of columns selected by \a columnList, so the horizontal
+ header has maximum overall width. Each selected column's width will be increased
+ by the same value. Does nothing if \a columnList is empty or there is no free space
+ to resize columns. If this table view is not visible, resizing will be performed on showing. */
+ void maximizeColumnsWidth( const QValueList<int> &columnList );
+
+ /*! Adjusts the size of the sections to fit the size of the horizontal header
+ as completely as possible. Only sections for which column stretch is enabled will be resized.
+ \sa setColumnStretchEnabled() QHeader::adjustHeaderSize() */
+ void adjustHorizontalHeaderSize();
+
+ /*! Sets highlighted row number or -1 if no row has to be highlighted.
+ Makes sense if row highlighting is enabled.
+ @see Appearance::rowHighlightingEnabled */
+ void setHighlightedRow(int row);
+
+ /*! Sets no row that will be highlighted. Equivalent to setHighlightedRow(-1). */
+ inline void clearHighlightedRow() { setHighlightedRow(-1); }
+
+ /*! Ensures that cell at \a row and \a col is visible.
+ If \a col is -1, current column number is used. \a row and \a col (if not -1) must
+ be between 0 and rows() (or cols() accordingly). */
+ virtual void ensureCellVisible(int row, int col/*=-1*/);
+
+// void gotoNext();
+//js int findString(const QString &string);
+
+ /*! Deletes currently selected record; does nothing if no record
+ is currently selected. If record is in edit mode, editing
+ is cancelled before deleting. */
+ virtual void deleteCurrentRow() { KexiDataAwareObjectInterface::deleteCurrentRow(); }
+
+ /*! Inserts one empty row above row \a row. If \a row is -1 (the default),
+ new row is inserted above the current row (or above 1st row if there is no current).
+ A new item becomes current if row is -1 or if row is equal currentRow().
+ This method does nothing if:
+ -inserting flag is disabled (see isInsertingEnabled())
+ -read-only flag is set (see isReadOnly())
+ \ return inserted row's data
+ */
+ virtual KexiTableItem *insertEmptyRow(int row = -1)
+ { return KexiDataAwareObjectInterface::insertEmptyRow(row); }
+
+ /*! Used when Return key is pressed on cell or "+" nav. button is clicked.
+ Also used when we want to continue editing a cell after "invalid value" message
+ was displayed (in this case, \a setText is usually not empty, what means
+ that text will be set in the cell replacing previous value).
+ */
+ virtual void startEditCurrentCell(const QString& setText = QString::null)
+ { KexiDataAwareObjectInterface::startEditCurrentCell(setText); }
+
+ /*! Deletes currently selected cell's contents, if allowed.
+ In most cases delete is not accepted immediately but "row editing" mode is just started. */
+ virtual void deleteAndStartEditCurrentCell()
+ { KexiDataAwareObjectInterface::deleteAndStartEditCurrentCell(); }
+
+ /*! Cancels row editing All changes made to the editing
+ row during this current session will be undone.
+ \return true on success or false on failure (e.g. when editor does not exist) */
+ virtual bool cancelRowEdit() { return KexiDataAwareObjectInterface::cancelRowEdit(); }
+
+ /*! Accepts row editing. All changes made to the editing
+ row during this current session will be accepted (saved).
+ \return true if accepting was successful, false otherwise
+ (e.g. when current row contain data that does not meet given constraints). */
+ virtual bool acceptRowEdit() { return KexiDataAwareObjectInterface::acceptRowEdit(); }
+
+ /*! Specifies, if this table view automatically accepts
+ row editing (using acceptRowEdit()) on accepting any cell's edit
+ (i.e. after acceptEditor()). \sa acceptsRowEditAfterCellAccepting() */
+ virtual void setAcceptsRowEditAfterCellAccepting(bool set)
+ { KexiDataAwareObjectInterface::setAcceptsRowEditAfterCellAccepting(set); }
+
+ /*! Specifies, if this table accepts dropping data on the rows.
+ If enabled:
+ - dragging over row is indicated by drawing a line at bottom side of this row
+ - dragOverRow() signal will be emitted on dragging,
+ -droppedAtRow() will be emitted on dropping
+ By default this flag is set to false. */
+ virtual void setDropsAtRowEnabled(bool set) { KexiDataAwareObjectInterface::setDropsAtRowEnabled(set); }
+
+ virtual bool cancelEditor() { return KexiDataAwareObjectInterface::cancelEditor(); }
+ virtual bool acceptEditor() { return KexiDataAwareObjectInterface::acceptEditor(); }
+
+signals:
+ virtual void dataSet( KexiTableViewData *data );
+
+ virtual void itemSelected(KexiTableItem *);
+ virtual void cellSelected(int col, int row);
+
+ void itemReturnPressed(KexiTableItem *, int row, int col);
+ void itemDblClicked(KexiTableItem *, int row, int col);
+ void itemMouseReleased(KexiTableItem *, int row, int col);
+
+ void dragOverRow(KexiTableItem *item, int row, QDragMoveEvent* e);
+ void droppedAtRow(KexiTableItem *item, int row, QDropEvent *e, KexiTableItem*& newItem);
+
+ /*! Data has been refreshed on-screen - emitted from initDataContents(). */
+ virtual void dataRefreshed();
+
+ virtual void itemChanged(KexiTableItem *, int row, int col);
+ virtual void itemChanged(KexiTableItem *, int row, int col, QVariant oldValue);
+ virtual void itemDeleteRequest(KexiTableItem *, int row, int col);
+ virtual void currentItemDeleteRequest();
+ //! Emitted for spreadsheet mode when an item was deleted and a new item has been appended
+ virtual void newItemAppendedForAfterDeletingInSpreadSheetMode();
+// void addRecordRequest();
+// void contextMenuRequested(KexiTableItem *, int row, int col, const QPoint &);
+ void sortedColumnChanged(int col);
+
+ //! emmited when row editing is started (for updating or inserting)
+ void rowEditStarted(int row);
+
+ //! emmited when row editing is terminated (for updating or inserting)
+ //! no matter if accepted or not
+ void rowEditTerminated(int row);
+
+ //! Emitted in initActions() to force reload actions
+ //! You should remove existing actions and add them again.
+ void reloadActions();
+
+protected slots:
+ void slotSettingsChanged(int category);
+
+ virtual void slotDataDestroying() { KexiDataAwareObjectInterface::slotDataDestroying(); }
+
+ virtual void slotRowsDeleted( const QValueList<int> & );
+
+ //! updates display after many rows deletion
+ void slotColumnWidthChanged( int col, int os, int ns );
+
+ void slotSectionHandleDoubleClicked( int section );
+
+ void slotUpdate();
+ //! implemented because we needed this as slot
+ virtual void sortColumnInternal(int col, int order = 0)
+ { KexiDataAwareObjectInterface::sortColumnInternal(col, order); }
+
+ void slotAutoScroll();
+
+ //! internal, used when top header's size changed
+ void slotTopHeaderSizeChange( int section, int oldSize, int newSize );
+
+ //! receives a signal from cell editors
+ void slotEditRequested();
+
+ /*! Reloads data for this widget.
+ Handles KexiTableViewData::reloadRequested() signal. */
+ virtual void reloadData();
+
+ //! Handles KexiTableViewData::rowRepaintRequested() signal
+ virtual void slotRowRepaintRequested(KexiTableItem& item);
+
+ //! Handles KexiTableViewData::aboutToDeleteRow() signal. Prepares info for slotRowDeleted().
+ virtual void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint)
+ { KexiDataAwareObjectInterface::slotAboutToDeleteRow(item, result, repaint); }
+
+ //! Handles KexiTableViewData::rowDeleted() signal to repaint when needed.
+ virtual void slotRowDeleted() { KexiDataAwareObjectInterface::slotRowDeleted(); }
+
+ //! Handles KexiTableViewData::rowInserted() signal to repaint when needed.
+ virtual void slotRowInserted(KexiTableItem *item, bool repaint)
+ { KexiDataAwareObjectInterface::slotRowInserted(item, repaint); }
+
+ //! Like above, not db-aware version
+ virtual void slotRowInserted(KexiTableItem *item, uint row, bool repaint)
+ { KexiDataAwareObjectInterface::slotRowInserted(item, row, repaint); }
+
+ /*! Handles verticalScrollBar()'s valueChanged(int) signal.
+ Called when vscrollbar's value has been changed. */
+ virtual void vScrollBarValueChanged(int v) { KexiDataAwareObjectInterface::vScrollBarValueChanged(v); }
+
+ /*! Handles sliderReleased() signal of the verticalScrollBar(). Used to hide the "row number" tooltip. */
+ virtual void vScrollBarSliderReleased() { KexiDataAwareObjectInterface::vScrollBarSliderReleased(); }
+
+ /*! Handles timeout() signal of the m_scrollBarTipTimer. If the tooltip is visible,
+ m_scrollBarTipTimerCnt is set to 0 and m_scrollBarTipTimerCnt is restarted;
+ else the m_scrollBarTipTimerCnt is just set to 0.*/
+ virtual void scrollBarTipTimeout() { KexiDataAwareObjectInterface::scrollBarTipTimeout(); }
+
+protected:
+ /*! Reimplementation for KexiDataAwareObjectInterface
+ Initializes data contents (resizes it, sets cursor at 1st row).
+ Called on setData(). Also called once on show event after
+ reloadRequested() signal was received from KexiTableViewData object. */
+ virtual void initDataContents();
+
+ /*! Implementation for KexiDataAwareObjectInterface.
+ Updates widget's contents size using QScrollView::resizeContents()
+ depending on tableSize(). */
+ virtual void updateWidgetContentsSize();
+
+ /*! Reimplementation for KexiDataAwareObjectInterface */
+ virtual void clearVariables();
+
+ /*! Implementation for KexiDataAwareObjectInterface */
+ virtual int currentLocalSortingOrder() const;
+
+ /*! Implementation for KexiDataAwareObjectInterface */
+ virtual int currentLocalSortColumn() const;
+
+ /*! Implementation for KexiDataAwareObjectInterface */
+ virtual void setLocalSortingOrder(int col, int order);
+
+ /*! Implementation for KexiDataAwareObjectInterface */
+ virtual void updateGUIAfterSorting();
+
+ /*! Implementation for KexiDataAwareObjectInterface */
+ virtual void updateWidgetScrollBars() { updateScrollBars(); }
+
+// /*! Implementation for KexiDataAwareObjectInterface */
+// virtual void emitSortedColumnChanged(int col) { emit sortedColumnChanged(col); }
+
+// /*! Implementation for KexiDataAwareObjectInterface */
+// virtual void emitRowEditTerminated(int row) { emit rowEditTerminated(row); }
+
+ /*! Implementation for KexiDataAwareObjectInterface.
+ Adds another section within the horizontal header. */
+ virtual void addHeaderColumn(const QString& caption, const QString& description,
+ const QIconSet& icon, int size);
+
+ /*! @internal \return true if the row defined by \a item has default
+ value at column \a col. If this is the case and \a value is not NULL,
+ *value is set to the default value. */
+ bool isDefaultValueDisplayed(KexiTableItem *item, int col, QVariant* value = 0);
+
+ //! painting and layout
+ void drawContents(QPainter *p, int cx, int cy, int cw, int ch);
+ void createBuffer(int width, int height);
+ void paintCell(QPainter* p, KexiTableItem *item, int col, int row, const QRect &cr, bool print=false);
+ void paintEmptyArea(QPainter *p, int cx, int cy, int cw, int ch);
+ void updateGeometries();
+
+ QPoint contentsToViewport2( const QPoint &p );
+ void contentsToViewport2( int x, int y, int& vx, int& vy );
+ QPoint viewportToContents2( const QPoint& vp );
+
+ // event handling
+ virtual void contentsMousePressEvent(QMouseEvent* e);
+ virtual void contentsMouseReleaseEvent(QMouseEvent* e);
+ //! @internal called by contentsMouseOrEvent() contentsMouseReleaseEvent() to move cursor
+ bool handleContentsMousePressOrRelease(QMouseEvent* e, bool release);
+ virtual void contentsMouseMoveEvent(QMouseEvent* e);
+ virtual void contentsMouseDoubleClickEvent(QMouseEvent* e);
+ virtual void keyPressEvent(QKeyEvent* e);
+ virtual void focusInEvent(QFocusEvent* e);
+ virtual void focusOutEvent(QFocusEvent* e);
+ virtual void resizeEvent(QResizeEvent* e);
+ virtual void viewportResizeEvent(QResizeEvent *e);
+ virtual void showEvent(QShowEvent *e);
+ virtual void contentsDragMoveEvent(QDragMoveEvent *e);
+ virtual void contentsDropEvent(QDropEvent *e);
+ virtual void viewportDragLeaveEvent(QDragLeaveEvent *e);
+ virtual void paletteChange( const QPalette &oldPalette );
+
+ /*! Implementation for KexiDataAwareObjectInterface */
+ virtual KexiDataItemInterface *editor( int col, bool ignoreMissingEditor = false );
+
+ inline KexiTableEdit *tableEditorWidget( int col, bool ignoreMissingEditor = false )
+ { return dynamic_cast<KexiTableEdit*>( editor( col, ignoreMissingEditor ) ); }
+
+ /*! Implementation for KexiDataAwareObjectInterface */
+ virtual void editorShowFocus( int row, int col );
+
+ //! Creates editors and shows it, what usually means the beginning of a cell editing
+ virtual void createEditor(int row, int col, const QString& addText = QString::null,
+ bool removeOld = false);
+
+ bool focusNextPrevChild(bool next);
+
+ /*! Used in key event: \return true if event \a e should execute action \a action_name.
+ Action shortcuts defined by shortCutPressed() are reused, if present, and if \a e matches
+ given action's shortcut - false is returned (beause action is already performed at main
+ window's level).
+ */
+ bool shortCutPressed( QKeyEvent *e, const QCString &action_name );
+
+#if 0 //we have now KexiActionProxy
+ /*! Updates visibility/accesibility of popup menu items,
+ returns false if no items are visible after update. */
+ bool updateContextMenu();
+#endif
+
+ /*! Shows context menu at \a pos for selected cell
+ if menu is configured,
+ else: contextMenuRequested() signal is emmited.
+ Method used in contentsMousePressEvent() (for right button)
+ and keyPressEvent() for Qt::Key_Menu key.
+ If \a pos is QPoint(-1,-1) (the default), menu is positioned below the current cell.
+ */
+ void showContextMenu( const QPoint& pos = QPoint(-1,-1) );
+
+ /*! internal */
+ inline void paintRow(KexiTableItem *item,
+ QPainter *pb, int r, int rowp, int cx, int cy,
+ int colfirst, int collast, int maxwc);
+
+ virtual void setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h );
+
+ //! Setups navigator widget
+ void setupNavigator();
+
+ //! internal, to determine valid row number when navigator text changed
+ int validRowNumber(const QString& text);
+
+ /*! Reimplementation for KexiDataAwareObjectInterface
+ (viewport()->setFocus() is just added) */
+ virtual void removeEditor();
+
+ //! Internal: updated sched fonts for painting.
+ void updateFonts(bool repaint = false);
+
+ /*! @internal Changes bottom margin settings, in pixels.
+ At this time, it's used by KexiComboBoxPopup to decrease margin for popup's table. */
+ void setBottomMarginInternal(int pixels);
+
+ virtual void updateWidgetContents() { update(); }
+
+ //! for navigator
+ virtual void moveToRecordRequested(uint r);
+ virtual void moveToLastRecordRequested();
+ virtual void moveToPreviousRecordRequested();
+ virtual void moveToNextRecordRequested();
+ virtual void moveToFirstRecordRequested();
+ virtual void addNewRecordRequested() { KexiDataAwareObjectInterface::addNewRecordRequested(); }
+
+ //! Copy current selection to a clipboard (e.g. cell)
+ virtual void copySelection();
+
+ //! Cut current selection to a clipboard (e.g. cell)
+ virtual void cutSelection();
+
+ //! Paste current clipboard contents (e.g. to a cell)
+ virtual void paste();
+
+ /*! Used in KexiDataAwareObjectInterface::slotRowDeleted()
+ to repaint tow \a row and all visible below. */
+ virtual void updateAllVisibleRowsBelow(int row);
+
+ void updateAfterCancelRowEdit();
+ void updateAfterAcceptRowEdit();
+
+ /*! Sets \a cellValue if there is a lookup value for the cell \a item.
+ Used in KexiTableView::paintCell() and KexiTableViewCellToolTip::maybeTip()
+ \return true is \a cellValue has been found. */
+ bool getVisibleLookupValue(QVariant& cellValue, KexiTableEdit *edit,
+ KexiTableItem *item, KexiTableViewColumn *tvcol) const;
+
+// //! Called to repaint contents after a row is deleted.
+// void repaintAfterDelete();
+
+ KexiTableViewPrivate *d;
+
+ class WhatsThis;
+ friend class KexiTableItem;
+ friend class WhatsThis;
+ friend class KexiTableViewCellToolTip;
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexitableview_p.cpp b/kexi/widget/tableview/kexitableview_p.cpp
new file mode 100644
index 000000000..7cf774dbc
--- /dev/null
+++ b/kexi/widget/tableview/kexitableview_p.cpp
@@ -0,0 +1,67 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Till Busch <till@bux.at>
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Original Author: Till Busch <till@bux.at>
+ Original Project: buX (www.bux.at)
+*/
+
+#include "kexitableview_p.h"
+#include "kexitableedit.h"
+
+#include <qlabel.h>
+
+#include <kglobalsettings.h>
+
+
+KexiTableViewPrivate::KexiTableViewPrivate(KexiTableView* t)
+ : appearance(t)
+{
+ clearVariables();
+ tv = t;
+ editOnDoubleClick = true;
+ pBufferPm = 0;
+ disableDrawContents = false;
+ navigatorEnabled = true;
+ contextMenuEnabled = true;
+ skipKeyPress = false;
+//moved vScrollBarValueChanged_enabled = true;
+//moved scrollbarToolTipsEnabled = true;
+//moved scrollBarTipTimerCnt = 0;
+//moved scrollBarTip = 0;
+ ensureCellVisibleOnShow = QPoint(-1,-1);
+ internal_bottomMargin = tv->horizontalScrollBar()->sizeHint().height()/2;
+ highlightedRow = -1;
+ moveCursorOnMouseRelease = false;
+ horizontalHeaderVisible = true;
+ recentCellWithToolTip = QPoint(-1,-1);
+}
+
+KexiTableViewPrivate::~KexiTableViewPrivate()
+{
+ delete pBufferPm;
+//moved delete scrollBarTip;
+}
+
+void KexiTableViewPrivate::clearVariables()
+{
+ // Initialize variables
+}
diff --git a/kexi/widget/tableview/kexitableview_p.h b/kexi/widget/tableview/kexitableview_p.h
new file mode 100644
index 000000000..58fe85745
--- /dev/null
+++ b/kexi/widget/tableview/kexitableview_p.h
@@ -0,0 +1,155 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Till Busch <till@bux.at>
+ Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Original Author: Till Busch <till@bux.at>
+ Original Project: buX (www.bux.at)
+*/
+
+#ifndef KEXITABLEVIEW_P_H
+#define KEXITABLEVIEW_P_H
+
+#include "kexitableview.h"
+
+#include <kexidb/roweditbuffer.h>
+#include <widget/utils/kexidisplayutils.h>
+
+#include <qevent.h>
+#include <qtimer.h>
+#include <qvalidator.h>
+#include <qasciidict.h>
+
+#include <kpushbutton.h>
+#include <ktoolbarbutton.h>
+#include <klineedit.h>
+#include <kpopupmenu.h>
+#include <kaction.h>
+
+class KexiTableItem;
+class KexiTableRM;
+class KexiTableEdit;
+class QLabel;
+class KexiTableViewHeader;
+
+//! @short a dynamic tooltip for table view cells
+/*! @internal */
+class KexiTableViewCellToolTip : public QToolTip
+{
+ public:
+ KexiTableViewCellToolTip( KexiTableView * tableView );
+ virtual ~KexiTableViewCellToolTip();
+ protected:
+ virtual void maybeTip( const QPoint & p );
+
+ KexiTableView *m_tableView;
+};
+
+/*! KexiTableView's internal structures
+ @internal */
+class KexiTableViewPrivate
+{
+ public:
+
+ KexiTableViewPrivate(KexiTableView* t);
+ ~KexiTableViewPrivate();
+
+ void clearVariables();
+
+ KexiTableView *tv;
+
+ //! editors: one for each column (indexed by KexiTableViewColumn)
+ QPtrDict<KexiTableEdit> editors;
+
+ int rowHeight;
+
+ QPixmap *pBufferPm;
+ QTimer *pUpdateTimer;
+ int menu_id_addRecord;
+ int menu_id_removeRecord;
+
+#if 0//(js) doesn't work!
+ QTimer *scrollTimer;
+#endif
+
+ KexiTableView::ScrollDirection scrollDirection;
+
+ bool editOnDoubleClick : 1;
+
+ bool needAutoScroll : 1;
+
+ bool disableDrawContents : 1;
+
+ /*! true if the navigation panel is enabled (visible) for the view.
+ True by default. */
+ bool navigatorEnabled : 1;
+
+ /*! true if the context menu is enabled (visible) for the view.
+ True by default. */
+ bool contextMenuEnabled : 1;
+
+ /*! used to force single skip keyPress event. */
+ bool skipKeyPress : 1;
+
+ /*! Needed because m_horizontalHeader->isVisible() is not always accurate. True by default. */
+ bool horizontalHeaderVisible : 1;
+
+ /*! true if cursor should be moved on mouse release evenr rather than mouse press
+ in handleContentsMousePressOrRelease().
+ False by default. Used by KeixComboBoxPopup. */
+ bool moveCursorOnMouseRelease : 1;
+
+ KexiTableView::Appearance appearance;
+
+ //! brushes, fonts
+ QBrush diagonalGrayPattern;
+
+ //! Parameters for displaying autonumbers
+ KexiDisplayUtils::DisplayParameters autonumberSignDisplayParameters;
+
+ //! Parameters for displaying default values
+ KexiDisplayUtils::DisplayParameters defaultValueDisplayParameters;
+
+ //! Used by delayed mode of maximizeColumnsWidth()
+ QValueList<int> maximizeColumnsWidthOnShow;
+
+ /*! Used for delayed call of ensureCellVisible() after show().
+ It's equal to (-1,-1) if ensureCellVisible() shouldn't e called. */
+ QPoint ensureCellVisibleOnShow;
+
+ /*! @internal Changes bottom margin settings, in pixels.
+ At this time, it's used by KexiComboBoxPopup to decrease margin for popup's table. */
+ int internal_bottomMargin;
+
+ /*! Helper for "highlighted row" effect. */
+ int highlightedRow;
+
+ /*! Id of context menu key (cached). */
+ int contextMenuKey;
+
+ /*! Specifies currently displayed cell tooltip.
+ Value of QPoint(-1,-1) means "no tooltip". */
+ QPoint recentCellWithToolTip;
+
+ /*! Table cell tooltip */
+ KexiTableViewCellToolTip *cellToolTip;
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexitableviewdata.cpp b/kexi/widget/tableview/kexitableviewdata.cpp
new file mode 100644
index 000000000..62259db39
--- /dev/null
+++ b/kexi/widget/tableview/kexitableviewdata.cpp
@@ -0,0 +1,886 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Original Author: Till Busch <till@bux.at>
+ Original Project: buX (www.bux.at)
+*/
+
+#include "kexitableviewdata.h"
+
+#include <kexiutils/validator.h>
+
+#include <kexidb/field.h>
+#include <kexidb/queryschema.h>
+#include <kexidb/roweditbuffer.h>
+#include <kexidb/cursor.h>
+#include <kexidb/utils.h>
+#include <kexi.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qapplication.h>
+
+unsigned short KexiTableViewData::charTable[]=
+{
+ #include "chartable.txt"
+};
+
+KexiTableViewColumn::KexiTableViewColumn(KexiDB::Field& f, bool owner)
+: columnInfo(0)
+, visibleLookupColumnInfo(0)
+, m_field(&f)
+{
+ isDBAware = false;
+ m_fieldOwned = owner;
+ m_captionAliasOrName = m_field->captionOrName();
+ init();
+}
+
+KexiTableViewColumn::KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype,
+ uint cconst,
+ uint options,
+ uint length, uint precision,
+ QVariant defaultValue,
+ const QString& caption, const QString& description, uint width
+)
+: columnInfo(0)
+, visibleLookupColumnInfo(0)
+{
+ m_field = new KexiDB::Field(
+ name, ctype,
+ cconst,
+ options,
+ length, precision,
+ defaultValue,
+ caption, description, width);
+
+ isDBAware = false;
+ m_fieldOwned = true;
+ m_captionAliasOrName = m_field->captionOrName();
+ init();
+}
+
+KexiTableViewColumn::KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype,
+ const QString& caption, const QString& description)
+: columnInfo(0)
+, visibleLookupColumnInfo(0)
+{
+ m_field = new KexiDB::Field(
+ name, ctype,
+ KexiDB::Field::NoConstraints,
+ KexiDB::Field::NoOptions,
+ 0, 0,
+ QVariant(),
+ caption, description);
+
+ isDBAware = false;
+ m_fieldOwned = true;
+ m_captionAliasOrName = m_field->captionOrName();
+ init();
+}
+
+// db-aware
+KexiTableViewColumn::KexiTableViewColumn(
+ const KexiDB::QuerySchema &query, KexiDB::QueryColumnInfo& aColumnInfo,
+ KexiDB::QueryColumnInfo* aVisibleLookupColumnInfo)
+: columnInfo(&aColumnInfo)
+, visibleLookupColumnInfo(aVisibleLookupColumnInfo)
+, m_field(aColumnInfo.field)
+{
+ isDBAware = true;
+ m_fieldOwned = false;
+
+ //setup column's caption:
+ if (!columnInfo->field->caption().isEmpty()) {
+ m_captionAliasOrName = columnInfo->field->caption();
+ }
+ else {
+ //reuse alias if available:
+ m_captionAliasOrName = columnInfo->alias;
+ //last hance: use field name
+ if (m_captionAliasOrName.isEmpty())
+ m_captionAliasOrName = columnInfo->field->name();
+ //todo: compute other auto-name?
+ }
+ init();
+ //setup column's readonly flag: true, if
+ // - it's not from parent table's field, or
+ // - if the query itself is coming from read-only connection, or
+ // - if the query itself is stored (i.e. has connection) and lookup column is defined
+ const bool columnFromMasterTable = query.masterTable()==columnInfo->field->table();
+ m_readOnly = !columnFromMasterTable
+ || (query.connection() && query.connection()->isReadOnly());
+// || (query.connection() && (query.connection()->isReadOnly() || visibleLookupColumnInfo));
+//! @todo 2.0: remove this when queries become editable ^^^^^^^^^^^^^^
+// kdDebug() << "KexiTableViewColumn: query.masterTable()=="
+// << (query.masterTable() ? query.masterTable()->name() : "notable") << ", columnInfo->field->table()=="
+// << (columnInfo->field->table() ? columnInfo->field->table()->name() : "notable") << endl;
+
+// m_visible = query.isFieldVisible(&f);
+}
+
+KexiTableViewColumn::KexiTableViewColumn(bool)
+: columnInfo(0)
+, visibleLookupColumnInfo(0)
+, m_field(0)
+{
+ isDBAware = false;
+ init();
+}
+
+KexiTableViewColumn::~KexiTableViewColumn()
+{
+ if (m_fieldOwned)
+ delete m_field;
+ setValidator( 0 );
+ delete m_relatedData;
+}
+
+void KexiTableViewColumn::init()
+{
+ m_relatedData = 0;
+ m_readOnly = false;
+ m_visible = true;
+ m_data = 0;
+ m_validator = 0;
+ m_relatedDataEditable = false;
+ m_headerTextVisible = true;
+}
+
+void KexiTableViewColumn::setValidator( KexiUtils::Validator* v )
+{
+ if (m_validator) {//remove old one
+ if (!m_validator->parent()) //destroy if has no parent
+ delete m_validator;
+ }
+ m_validator = v;
+}
+
+void KexiTableViewColumn::setRelatedData(KexiTableViewData *data)
+{
+ if (isDBAware)
+ return;
+ if (m_relatedData)
+ delete m_relatedData;
+ m_relatedData = 0;
+ if (!data)
+ return;
+ //find a primary key
+ KexiTableViewColumn::ListIterator it( data->columns );
+ for (int id = 0;it.current();++it, id++) {
+ if (it.current()->field()->isPrimaryKey()) {
+ //found, remember
+ m_relatedDataPKeyID = id;
+ m_relatedData = data;
+ return;
+ }
+ }
+}
+
+void KexiTableViewColumn::setRelatedDataEditable(bool set)
+{
+ m_relatedDataEditable = set;
+}
+
+bool KexiTableViewColumn::isReadOnly() const
+{
+ return m_readOnly || (m_data && m_data->isReadOnly());
+}
+
+bool KexiTableViewColumn::acceptsFirstChar(const QChar& ch) const
+{
+ // the field we're looking at can be related to "visible lookup column"
+ // if lookup column is present
+ KexiDB::Field *visibleField = visibleLookupColumnInfo
+ ? visibleLookupColumnInfo->field : m_field;
+ if (visibleField->isNumericType()) {
+ if (ch=='.' || ch==',')
+ return visibleField->isFPNumericType();
+ if (ch=='-')
+ return !visibleField->isUnsigned();
+ if (ch=='+' || (ch>='0' && ch<='9'))
+ return true;
+ return false;
+ }
+
+ switch (visibleField->type()) {
+ case KexiDB::Field::Boolean:
+ return false;
+ case KexiDB::Field::Date:
+ case KexiDB::Field::DateTime:
+ case KexiDB::Field::Time:
+ return ch>='0' && ch<='9';
+ default:;
+ }
+ return true;
+}
+
+
+//------------------------------------------------------
+
+KexiTableViewData::KexiTableViewData()
+ : QObject()
+ , KexiTableViewDataBase()
+{
+ init();
+}
+
+// db-aware ctor
+KexiTableViewData::KexiTableViewData(KexiDB::Cursor *c)
+ : QObject()
+ , KexiTableViewDataBase()
+{
+ init();
+ m_cursor = c;
+ m_containsROWIDInfo = m_cursor->containsROWIDInfo();
+ if (m_cursor && m_cursor->query()) {
+ const KexiDB::QuerySchema::FieldsExpandedOptions fieldsExpandedOptions
+ = m_containsROWIDInfo ? KexiDB::QuerySchema::WithInternalFieldsAndRowID
+ : KexiDB::QuerySchema::WithInternalFields;
+ m_itemSize = m_cursor->query()->fieldsExpanded( fieldsExpandedOptions ).count();
+ }
+ else
+ m_itemSize = columns.count()+(m_containsROWIDInfo?1:0);
+
+ // Allocate KexiTableViewColumn objects for each visible query column
+ const KexiDB::QueryColumnInfo::Vector fields = m_cursor->query()->fieldsExpanded();
+ const uint fieldsCount = fields.count();
+ for (uint i=0;i < fieldsCount;i++) {
+ KexiDB::QueryColumnInfo *ci = fields[i];
+ if (ci->visible) {
+ KexiDB::QueryColumnInfo *visibleLookupColumnInfo = 0;
+ if (ci->indexForVisibleLookupValue() != -1) {
+ //Lookup field is defined
+ visibleLookupColumnInfo = m_cursor->query()->expandedOrInternalField( ci->indexForVisibleLookupValue() );
+ /* not needed
+ if (visibleLookupColumnInfo) {
+ // 2. Create a KexiTableViewData object for each found lookup field
+ }*/
+ }
+ KexiTableViewColumn* col = new KexiTableViewColumn(*m_cursor->query(), *ci, visibleLookupColumnInfo);
+ addColumn( col );
+ }
+ }
+}
+
+KexiTableViewData::KexiTableViewData(
+ const QValueList<QVariant> &keys, const QValueList<QVariant> &values,
+ KexiDB::Field::Type keyType, KexiDB::Field::Type valueType)
+ : QObject()
+ , KexiTableViewDataBase()
+{
+ init(keys, values, keyType, valueType);
+}
+
+KexiTableViewData::KexiTableViewData(
+ KexiDB::Field::Type keyType, KexiDB::Field::Type valueType)
+{
+ const QValueList<QVariant> empty;
+ init(empty, empty, keyType, valueType);
+}
+
+KexiTableViewData::~KexiTableViewData()
+{
+ emit destroying();
+ clearInternal();
+}
+
+void KexiTableViewData::init(
+ const QValueList<QVariant> &keys, const QValueList<QVariant> &values,
+ KexiDB::Field::Type keyType, KexiDB::Field::Type valueType)
+{
+ init();
+ KexiDB::Field *keyField = new KexiDB::Field("key", keyType);
+ keyField->setPrimaryKey(true);
+ KexiTableViewColumn *keyColumn = new KexiTableViewColumn(*keyField, true);
+ keyColumn->setVisible(false);
+ addColumn(keyColumn);
+
+ KexiDB::Field *valueField = new KexiDB::Field("value", valueType);
+ KexiTableViewColumn *valueColumn = new KexiTableViewColumn(*valueField, true);
+ addColumn(valueColumn);
+
+ uint cnt = QMIN(keys.count(), values.count());
+ QValueList<QVariant>::ConstIterator it_keys = keys.constBegin();
+ QValueList<QVariant>::ConstIterator it_values = values.constBegin();
+ for (;cnt>0;++it_keys, ++it_values, cnt--) {
+ KexiTableItem *item = new KexiTableItem(2);
+ (*item)[0] = (*it_keys);
+ (*item)[1] = (*it_values);
+ append( item );
+ }
+}
+
+void KexiTableViewData::init()
+{
+ m_sortedColumn = 0;
+ m_realSortedColumn = 0;
+// m_order = 1;
+ m_order = 0;
+ m_type = 1;
+ m_pRowEditBuffer = 0;
+ m_cursor = 0;
+ m_readOnly = false;
+ m_insertingEnabled = true;
+
+ setAutoDelete(true);
+ columns.setAutoDelete(true);
+ m_visibleColumnsCount=0;
+ m_visibleColumnsIDs.resize(100);
+ m_globalColumnsIDs.resize(100);
+
+ m_autoIncrementedColumn = -2;
+ m_containsROWIDInfo = false;
+ m_itemSize = 0;
+}
+
+void KexiTableViewData::deleteLater()
+{
+ m_cursor = 0;
+ QObject::deleteLater();
+}
+
+void KexiTableViewData::addColumn( KexiTableViewColumn* col )
+{
+// if (!col->isDBAware) {
+// if (!m_simpleColumnsByName)
+// m_simpleColumnsByName = new QDict<KexiTableViewColumn>(101);
+// m_simpleColumnsByName->insert(col->caption,col);//for faster lookup
+// }
+ columns.append( col );
+ col->m_data = this;
+ if (m_globalColumnsIDs.size() < columns.count()) {//sanity
+ m_globalColumnsIDs.resize( m_globalColumnsIDs.size()*2 );
+ }
+ if (col->visible()) {
+ m_visibleColumnsCount++;
+ if (m_visibleColumnsIDs.size() < m_visibleColumnsCount) {//sanity
+ m_visibleColumnsIDs.resize( m_visibleColumnsIDs.size()*2 );
+ }
+ m_visibleColumnsIDs[ columns.count()-1 ] = m_visibleColumnsCount-1;
+ m_globalColumnsIDs[ m_visibleColumnsCount-1 ] = columns.count()-1;
+ }
+ else {
+ m_visibleColumnsIDs[ columns.count()-1 ] = -1;
+ }
+ m_autoIncrementedColumn = -2; //clear cache;
+ if (!m_cursor || !m_cursor->query())
+ m_itemSize = columns.count()+(m_containsROWIDInfo?1:0);
+}
+
+QString KexiTableViewData::dbTableName() const
+{
+ if (m_cursor && m_cursor->query() && m_cursor->query()->masterTable())
+ return m_cursor->query()->masterTable()->name();
+ return QString::null;
+}
+
+void KexiTableViewData::setSorting(int column, bool ascending)
+{
+ if (column>=0 && column<(int)columns.count()) {
+ m_order = (ascending ? 1 : -1);
+ }
+ else {
+ m_order = 0;
+ m_sortedColumn = -1;
+ m_realSortedColumn = -1;
+ return;
+ }
+ // find proper column information for sorting (lookup column points to alternate column with visible data)
+ const KexiTableViewColumn *tvcol = columns.at(column);
+ KexiDB::QueryColumnInfo* visibleLookupColumnInfo = tvcol->visibleLookupColumnInfo;
+ const KexiDB::Field *field = visibleLookupColumnInfo ? visibleLookupColumnInfo->field : tvcol->field();
+ m_sortedColumn = column;
+ m_realSortedColumn = tvcol->columnInfo->indexForVisibleLookupValue()!=-1
+ ? tvcol->columnInfo->indexForVisibleLookupValue() : m_sortedColumn;
+
+ // setup compare function
+ const int t = field->type();
+ if (field->isTextType())
+ cmpFunc = &KexiTableViewData::cmpStr;
+ else if (KexiDB::Field::isFPNumericType(t))
+ cmpFunc = &KexiTableViewData::cmpDouble;
+ else if (t==KexiDB::Field::BigInteger) {
+ if (field->isUnsigned())
+ cmpFunc = &KexiTableViewData::cmpULongLong;
+ else
+ cmpFunc = &KexiTableViewData::cmpLongLong;
+ }
+ else if (t == KexiDB::Field::Integer && field->isUnsigned())
+ cmpFunc = &KexiTableViewData::cmpUInt;
+ else if (t == KexiDB::Field::Boolean || KexiDB::Field::isNumericType(t))
+ cmpFunc = &KexiTableViewData::cmpInt; //other integers
+ else if (t == KexiDB::Field::Date)
+ cmpFunc = &KexiTableViewData::cmpDate;
+ else if (t == KexiDB::Field::Time)
+ cmpFunc = &KexiTableViewData::cmpTime;
+ else if (t == KexiDB::Field::DateTime)
+ cmpFunc = &KexiTableViewData::cmpDateTime;
+ else if (t == KexiDB::Field::BLOB)
+//! TODO allow users to define BLOB sorting function?
+ cmpFunc = &KexiTableViewData::cmpBLOB;
+ else
+ cmpFunc = &KexiTableViewData::cmpStr; //anything else
+}
+
+int KexiTableViewData::compareItems(Item item1, Item item2)
+{
+ return ((this->*cmpFunc) (item1, item2));
+}
+
+//! compare NULLs : NULL is smaller than everything
+#define CMP_NULLS(item1, item2) \
+ m_leftTmp = ((KexiTableItem *)item1)->at(m_realSortedColumn); \
+ if (m_leftTmp.isNull()) \
+ return -m_order; \
+ m_rightTmp = ((KexiTableItem *)item2)->at(m_realSortedColumn); \
+ if (m_rightTmp.isNull()) \
+ return m_order
+
+#define CAST_AND_COMPARE(casting, item1, item2) \
+ CMP_NULLS(item1, item2); \
+ if (m_leftTmp.casting() < m_rightTmp.casting()) \
+ return -m_order; \
+ if (m_leftTmp.casting() > m_rightTmp.casting()) \
+ return m_order; \
+ return 0
+
+int KexiTableViewData::cmpInt(Item item1, Item item2)
+{
+ CAST_AND_COMPARE(toInt, item1, item2);
+}
+
+int KexiTableViewData::cmpUInt(Item item1, Item item2)
+{
+ CAST_AND_COMPARE(toUInt, item1, item2);
+}
+
+int KexiTableViewData::cmpLongLong(Item item1, Item item2)
+{
+ CAST_AND_COMPARE(toLongLong, item1, item2);
+}
+
+int KexiTableViewData::cmpULongLong(Item item1, Item item2)
+{
+ CAST_AND_COMPARE(toULongLong, item1, item2);
+}
+
+int KexiTableViewData::cmpDouble(Item item1, Item item2)
+{
+ CAST_AND_COMPARE(toDouble, item1, item2);
+}
+
+int KexiTableViewData::cmpDate(Item item1, Item item2)
+{
+ CAST_AND_COMPARE(toDate, item1, item2);
+}
+
+int KexiTableViewData::cmpDateTime(Item item1, Item item2)
+{
+ CAST_AND_COMPARE(toDateTime, item1, item2);
+}
+
+int KexiTableViewData::cmpTime(Item item1, Item item2)
+{
+ CAST_AND_COMPARE(toDate, item1, item2);
+}
+
+int KexiTableViewData::cmpStr(Item item1, Item item2)
+{
+ CMP_NULLS(item1, item2);
+ const QString &as = m_leftTmp.toString();
+ const QString &bs = m_rightTmp.toString();
+
+ const QChar *a = as.unicode();
+ const QChar *b = bs.unicode();
+
+ if ( a == b )
+ return 0;
+ if ( a == 0 )
+ return -1;
+ if ( b == 0 )
+ return 1;
+
+ unsigned short au;
+ unsigned short bu;
+
+ int l=QMIN(as.length(),bs.length());
+
+ au = a->unicode();
+ bu = b->unicode();
+ au = (au <= 0x17e ? charTable[au] : 0xffff);
+ bu = (bu <= 0x17e ? charTable[bu] : 0xffff);
+
+ while (l-- && au == bu)
+ {
+ a++,b++;
+ au = a->unicode();
+ bu = b->unicode();
+ au = (au <= 0x17e ? charTable[au] : 0xffff);
+ bu = (bu <= 0x17e ? charTable[bu] : 0xffff);
+ }
+
+ if ( l==-1 )
+ return m_order*(as.length()-bs.length());
+
+ return m_order*(au-bu);
+}
+
+int KexiTableViewData::cmpBLOB(Item item1, Item item2)
+{
+ CMP_NULLS(item1, item2);
+ return m_leftTmp.toByteArray().size() - m_rightTmp.toByteArray().size();
+}
+
+void KexiTableViewData::setReadOnly(bool set)
+{
+ if (m_readOnly == set)
+ return;
+ m_readOnly = set;
+ if (m_readOnly)
+ setInsertingEnabled(false);
+}
+
+void KexiTableViewData::setInsertingEnabled(bool set)
+{
+ if (m_insertingEnabled == set)
+ return;
+ m_insertingEnabled = set;
+ if (m_insertingEnabled)
+ setReadOnly(false);
+}
+
+void KexiTableViewData::clearRowEditBuffer()
+{
+ //init row edit buffer
+ if (!m_pRowEditBuffer)
+ m_pRowEditBuffer = new KexiDB::RowEditBuffer(isDBAware());
+ else
+ m_pRowEditBuffer->clear();
+}
+
+bool KexiTableViewData::updateRowEditBufferRef(KexiTableItem *item,
+ int colnum, KexiTableViewColumn* col, QVariant& newval, bool allowSignals,
+ QVariant *visibleValueForLookupField)
+{
+ m_result.clear();
+ if (allowSignals)
+ emit aboutToChangeCell(item, colnum, newval, &m_result);
+ if (!m_result.success)
+ return false;
+
+ kdDebug() << "KexiTableViewData::updateRowEditBufferRef() column #"
+ << colnum << " = " << newval.toString() << endl;
+ if (!col) {
+ kdWarning() << "KexiTableViewData::updateRowEditBufferRef(): column #"
+ << colnum << " not found! col==0" << endl;
+ return false;
+ }
+ if (!m_pRowEditBuffer)
+ m_pRowEditBuffer = new KexiDB::RowEditBuffer(isDBAware());
+ if (m_pRowEditBuffer->isDBAware()) {
+ if (!(col->columnInfo)) {
+ kdWarning() << "KexiTableViewData::updateRowEditBufferRef(): column #"
+ << colnum << " not found!" << endl;
+ return false;
+ }
+ m_pRowEditBuffer->insert( *col->columnInfo, newval);
+
+ if (col->visibleLookupColumnInfo && visibleValueForLookupField) {
+ //this is value for lookup table: update visible value as well
+ m_pRowEditBuffer->insert( *col->visibleLookupColumnInfo, *visibleValueForLookupField);
+ }
+ return true;
+ }
+ if (!(col->field())) {
+ kdDebug() << "KexiTableViewData::updateRowEditBufferRef(): column #" << colnum<<" not found!" << endl;
+ return false;
+ }
+ //not db-aware:
+ const QString colname = col->field()->name();
+ if (colname.isEmpty()) {
+ kdDebug() << "KexiTableViewData::updateRowEditBufferRef(): column #" << colnum<<" not found!" << endl;
+ return false;
+ }
+ m_pRowEditBuffer->insert(colname, newval);
+ return true;
+}
+
+//get a new value (if present in the buffer), or the old one, otherwise
+//(taken here for optimization)
+#define GET_VALUE if (!val) { \
+ val = m_cursor \
+ ? m_pRowEditBuffer->at( *it_f.current()->columnInfo, true /* useDefaultValueIfPossible */ ) \
+ : m_pRowEditBuffer->at( *f ); \
+ if (!val) \
+ val = &(*it_r); /* get old value */ \
+ }
+
+//! @todo if there're multiple views for this data, we need multiple buffers!
+bool KexiTableViewData::saveRow(KexiTableItem& item, bool insert, bool repaint)
+{
+ if (!m_pRowEditBuffer)
+ return true; //nothing to do
+
+ //check constraints:
+ //-check if every NOT NULL and NOT EMPTY field is filled
+ KexiTableViewColumn::ListIterator it_f(columns);
+ KexiDB::RowData::ConstIterator it_r = item.constBegin();
+ int col = 0;
+ const QVariant *val;
+ for (;it_f.current() && it_r!=item.constEnd();++it_f,++it_r,col++) {
+ KexiDB::Field *f = it_f.current()->field();
+ val = 0;
+ if (f->isNotNull()) {
+ GET_VALUE;
+ //check it
+ if (val->isNull() && !f->isAutoIncrement()) {
+ //NOT NULL violated
+ m_result.msg = i18n("\"%1\" column requires a value to be entered.")
+ .arg(f->captionOrName()) + "\n\n" + Kexi::msgYouCanImproveData();
+ m_result.desc = i18n("The column's constraint is declared as NOT NULL.");
+ m_result.column = col;
+ return false;
+ }
+ }
+ if (f->isNotEmpty()) {
+ GET_VALUE;
+ if (!f->isAutoIncrement() && (val->isNull() || KexiDB::isEmptyValue( f, *val ))) {
+ //NOT EMPTY violated
+ m_result.msg = i18n("\"%1\" column requires a value to be entered.")
+ .arg(f->captionOrName()) + "\n\n" + Kexi::msgYouCanImproveData();
+ m_result.desc = i18n("The column's constraint is declared as NOT EMPTY.");
+ m_result.column = col;
+ return false;
+ }
+ }
+ }
+
+ if (m_cursor) {//db-aware
+ if (insert) {
+ if (!m_cursor->insertRow( static_cast<KexiDB::RowData&>(item), *m_pRowEditBuffer,
+ m_containsROWIDInfo/*also retrieve ROWID*/ ))
+ {
+ m_result.msg = i18n("Row inserting failed.") + "\n\n"
+ + Kexi::msgYouCanImproveData();
+ KexiDB::getHTMLErrorMesage(m_cursor, &m_result);
+
+/* if (desc)
+ *desc =
+js: TODO: use KexiMainWindowImpl::showErrorMessage(const QString &title, KexiDB::Object *obj)
+ after it will be moved somewhere to kexidb (this will require moving other
+ showErrorMessage() methods from KexiMainWindowImpl to libkexiutils....)
+ then: just call: *desc = KexiDB::errorMessage(m_cursor);
+*/
+ return false;
+ }
+ }
+ else { // row updating
+// if (m_containsROWIDInfo)
+// ROWID = item[columns.count()].toULongLong();
+ if (!m_cursor->updateRow( static_cast<KexiDB::RowData&>(item), *m_pRowEditBuffer,
+ m_containsROWIDInfo/*use ROWID*/))
+ {
+ m_result.msg = i18n("Row changing failed.") + "\n\n" + Kexi::msgYouCanImproveData();
+//! @todo set m_result.column if possible
+ KexiDB::getHTMLErrorMesage(m_cursor, m_result.desc);
+ return false;
+ }
+ }
+ }
+ else {//not db-aware version
+ KexiDB::RowEditBuffer::SimpleMap b = m_pRowEditBuffer->simpleBuffer();
+ for (KexiDB::RowEditBuffer::SimpleMap::ConstIterator it = b.constBegin();it!=b.constEnd();++it) {
+ uint i=0;
+ for (KexiTableViewColumn::ListIterator it2(columns);it2.current();++it2, i++) {
+ if (it2.current()->field()->name()==it.key()) {
+ kdDebug() << it2.current()->field()->name()<< ": "<<item[i].toString()<<" -> "<<it.data().toString()<<endl;
+ item[i] = it.data();
+ }
+ }
+ }
+ }
+
+ m_pRowEditBuffer->clear();
+
+ if (repaint)
+ emit rowRepaintRequested(item);
+ return true;
+}
+
+bool KexiTableViewData::saveRowChanges(KexiTableItem& item, bool repaint)
+{
+ kdDebug() << "KexiTableViewData::saveRowChanges()..." << endl;
+ m_result.clear();
+ emit aboutToUpdateRow(&item, m_pRowEditBuffer, &m_result);
+ if (!m_result.success)
+ return false;
+
+ if (saveRow(item, false /*update*/, repaint)) {
+ emit rowUpdated(&item);
+ return true;
+ }
+ return false;
+}
+
+bool KexiTableViewData::saveNewRow(KexiTableItem& item, bool repaint)
+{
+ kdDebug() << "KexiTableViewData::saveNewRow()..." << endl;
+ m_result.clear();
+ emit aboutToInsertRow(&item, &m_result, repaint);
+ if (!m_result.success)
+ return false;
+
+ if (saveRow(item, true /*insert*/, repaint)) {
+ emit rowInserted(&item, repaint);
+ return true;
+ }
+ return false;
+}
+
+bool KexiTableViewData::deleteRow(KexiTableItem& item, bool repaint)
+{
+ m_result.clear();
+ emit aboutToDeleteRow(item, &m_result, repaint);
+ if (!m_result.success)
+ return false;
+
+ if (m_cursor) {//db-aware
+ m_result.success = false;
+ if (!m_cursor->deleteRow( static_cast<KexiDB::RowData&>(item), m_containsROWIDInfo/*use ROWID*/ )) {
+ m_result.msg = i18n("Row deleting failed.");
+/*js: TODO: use KexiDB::errorMessage() for description (desc) as in KexiTableViewData::saveRow() */
+ KexiDB::getHTMLErrorMesage(m_cursor, &m_result);
+ m_result.success = false;
+ return false;
+ }
+ }
+
+ if (!removeRef(&item)) {
+ //aah - this shouldn't be!
+ kdWarning() << "KexiTableViewData::deleteRow(): !removeRef() - IMPL. ERROR?" << endl;
+ m_result.success = false;
+ return false;
+ }
+ emit rowDeleted();
+ return true;
+}
+
+void KexiTableViewData::deleteRows( const QValueList<int> &rowsToDelete, bool repaint )
+{
+ Q_UNUSED( repaint );
+
+ if (rowsToDelete.isEmpty())
+ return;
+ int last_r=0;
+ first();
+ for (QValueList<int>::ConstIterator r_it = rowsToDelete.constBegin(); r_it!=rowsToDelete.constEnd(); ++r_it) {
+ for (; last_r<(*r_it); last_r++) {
+ next();
+ }
+ remove();
+ last_r++;
+ }
+//DON'T CLEAR BECAUSE KexiTableViewPropertyBuffer will clear BUFFERS!
+//--> emit reloadRequested(); //! \todo more effective?
+ emit rowsDeleted( rowsToDelete );
+}
+
+void KexiTableViewData::insertRow(KexiTableItem& item, uint index, bool repaint)
+{
+ if (!insert( index = QMIN(index, count()), &item ))
+ return;
+ emit rowInserted(&item, index, repaint);
+}
+
+void KexiTableViewData::clearInternal()
+{
+ clearRowEditBuffer();
+// qApp->processEvents( 1 );
+//TODO: this is time consuming: find better data model
+// KexiTableViewDataBase::clear();
+ const uint c = count();
+ for (uint i=0; i<c; i++) {
+ removeLast();
+#ifndef KEXI_NO_PROCESS_EVENTS
+ if (i % 1000 == 0)
+ qApp->processEvents( 1 );
+#endif
+ }
+}
+
+bool KexiTableViewData::deleteAllRows(bool repaint)
+{
+ clearInternal();
+
+ bool res = true;
+ if (m_cursor) {
+ //db-aware
+ res = m_cursor->deleteAllRows();
+ }
+
+ if (repaint)
+ emit reloadRequested();
+ return res;
+}
+
+int KexiTableViewData::autoIncrementedColumn()
+{
+ if (m_autoIncrementedColumn==-2) {
+ //find such a column
+ m_autoIncrementedColumn = 0;
+ KexiTableViewColumn::ListIterator it(columns);
+ for (; it.current(); ++it, m_autoIncrementedColumn++) {
+ if (it.current()->field()->isAutoIncrement())
+ break;
+ }
+ if (!it.current())
+ m_autoIncrementedColumn = -1;
+ }
+ return m_autoIncrementedColumn;
+}
+
+void KexiTableViewData::preloadAllRows()
+{
+ if (!m_cursor)
+ return;
+
+ //const uint fcount = m_cursor->fieldCount() + (m_containsROWIDInfo ? 1 : 0);
+ m_cursor->moveFirst();
+ for (int i=0;!m_cursor->eof();i++) {
+ KexiTableItem *item = new KexiTableItem(0);
+ m_cursor->storeCurrentRow(*item);
+// item->debug();
+ append( item );
+ m_cursor->moveNext();
+#ifndef KEXI_NO_PROCESS_EVENTS
+ if ((i % 1000) == 0)
+ qApp->processEvents( 1 );
+#endif
+ }
+}
+
+bool KexiTableViewData::isReadOnly() const
+{
+ return m_readOnly || (m_cursor && m_cursor->connection()->isReadOnly());
+}
+
+#include "kexitableviewdata.moc"
diff --git a/kexi/widget/tableview/kexitableviewdata.h b/kexi/widget/tableview/kexitableviewdata.h
new file mode 100644
index 000000000..970d1d23a
--- /dev/null
+++ b/kexi/widget/tableview/kexitableviewdata.h
@@ -0,0 +1,540 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+
+ Original Author: Till Busch <till@bux.at>
+ Original Project: buX (www.bux.at)
+*/
+
+#ifndef KEXITABLEVIEWDATA_H
+#define KEXITABLEVIEWDATA_H
+
+#include <qptrlist.h>
+#include <qvariant.h>
+#include <qvaluevector.h>
+#include <qstring.h>
+#include <qobject.h>
+
+#include "kexitableitem.h"
+
+#include <kexidb/error.h>
+
+namespace KexiDB {
+class Field;
+class QuerySchema;
+class RowEditBuffer;
+class Cursor;
+}
+
+namespace KexiUtils {
+class Validator;
+}
+class KexiTableViewData;
+
+
+/*! Single column definition. */
+class KEXIDATATABLE_EXPORT KexiTableViewColumn {
+ public:
+ typedef QPtrList<KexiTableViewColumn> List;
+ typedef QPtrListIterator<KexiTableViewColumn> ListIterator;
+
+ /*! Not db-aware ctor. if \a owner is true, the field \a will be owned by this column,
+ so you shouldn't care about destroying this field. */
+ KexiTableViewColumn(KexiDB::Field& f, bool owner = false);
+
+ /*! Not db-aware, convenience ctor, like above. The field is created using specified parameters that are
+ equal to these accepted by KexiDB::Field ctor. The column will be the owner
+ of this automatically generated field.
+ */
+ KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype,
+ uint cconst = KexiDB::Field::NoConstraints,
+ uint options = KexiDB::Field::NoOptions,
+ uint length=0, uint precision=0,
+ QVariant defaultValue=QVariant(),
+ const QString& caption = QString::null,
+ const QString& description = QString::null,
+ uint width = 0);
+
+ /*! Not db-aware, convenience ctor, simplified version of the above. */
+ KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, const QString& caption,
+ const QString& description = QString::null);
+
+ //! Db-aware version.
+ KexiTableViewColumn(const KexiDB::QuerySchema &query, KexiDB::QueryColumnInfo& aColumnInfo,
+ KexiDB::QueryColumnInfo* aVisibleLookupColumnInfo = 0);
+
+ virtual ~KexiTableViewColumn();
+
+ virtual bool acceptsFirstChar(const QChar& ch) const;
+
+ /*! \return true if the column is read-only
+ For db-aware column this can depend on whether the column
+ is in parent table of this query. \sa setReadOnly() */
+ bool isReadOnly() const;
+
+//TODO: synchronize this with table view:
+ //! forces readOnly flag to be set to \a ro
+ inline void setReadOnly(bool ro) { m_readOnly=ro; }
+
+ //! Column visibility. By default column is visible.
+ inline bool visible() const { return columnInfo ? columnInfo->visible : m_visible; }
+
+ //! Changes column visibility.
+ inline void setVisible(bool v) {
+ if (columnInfo)
+ columnInfo->visible = v;
+ m_visible=v;
+ }
+
+ /*! Sets icon for displaying in the caption area (header). */
+ void setIcon(const QIconSet& icon) { m_icon = icon; }
+
+ /*! \return bame of icon displayed in the caption area (header). */
+ QIconSet icon() const { return m_icon; }
+
+ /*! If \a visible is true, caption has to be displayed in the column's header,
+ (or field's name if caption is empty. True by default. */
+ void setHeaderTextVisible(bool visible) { m_headerTextVisible = visible; }
+
+ /*! \return true if caption has to be displayed in the column's header,
+ (or field's name if caption is empty. */
+ bool isHeaderTextVisible() const { return m_headerTextVisible; }
+
+ /*! \return whatever is available:
+ - field's caption
+ - or field's alias (from query)
+ - or finally - field's name */
+ inline QString captionAliasOrName() const { return m_captionAliasOrName; }
+
+ /*! Assigns validator \a v for this column.
+ If the validator has no parent object, it will be owned by the column,
+ so you shouldn't care about destroying it. */
+ void setValidator( KexiUtils::Validator* v );
+
+ //! \return validator assigned for this column of 0 if there is no validator assigned.
+ inline KexiUtils::Validator* validator() const { return m_validator; }
+
+ /*! For not-db-aware data only:
+ Sets related data \a data for this column, what defines simple one-field,
+ one-to-many relationship between this column and the primary key in \a data.
+ The relationship will be used to generate a popup editor instead of just regular editor.
+ This assignment has no result if \a data has no primary key defined.
+ \a data is owned, so is will be destroyed when needed. It is also destroyed
+ when another data (or NULL) is set for the same column. */
+ void setRelatedData(KexiTableViewData *data);
+
+ /*! For not-db-aware data only:
+ Related data \a data for this column, what defines simple one-field.
+ NULL by default. \sa setRelatedData() */
+ inline KexiTableViewData *relatedData() const { return m_relatedData; }
+
+ /*! \return field for this column.
+ For db-aware information is taken from \a columnInfo member. */
+ inline KexiDB::Field* field() const { return m_field; }
+
+ /*! Only usable if related data is set (ie. this is for combo boxes).
+ Sets 'editable' flag for this column, what means a new value can be entered
+ by hand. This is similar to QComboBox::setEditable(). */
+ void setRelatedDataEditable(bool set);
+
+ /*! Only usable if related data is set (ie. this is for combo boxes).
+ \return 'editable' flag for this column.
+ False by default. @see setRelatedDataEditable(bool). */
+ inline bool relatedDataEditable() const { return m_relatedDataEditable; }
+
+ /*! A rich field information for db-aware data.
+ For not-db-aware data it is always 0 (use field() instead). */
+ KexiDB::QueryColumnInfo* columnInfo;
+
+ /*! A rich field information for db-aware data. Specifies information for a column
+ that should be visible instead of columnInfo. For example case see
+ @ref KexiDB::QueryColumnInfo::Vector KexiDB::QuerySchema::fieldsExpanded(KexiDB::QuerySchema::FieldsExpandedOptions options = Default)
+
+ For not-db-aware data it is always 0. */
+ KexiDB::QueryColumnInfo* visibleLookupColumnInfo;
+
+ bool isDBAware : 1; //!< true if data is stored in DB, not only in memeory
+
+/* QString caption;
+ int type; //!< one of KexiDB::Field::Type
+ uint width;
+*/
+// bool isNull() const;
+
+/* virtual QString caption() const;
+ virtual void setCaption(const QString& c);
+ */
+ protected:
+ //! special ctor that do not allocate d member;
+ KexiTableViewColumn(bool);
+
+ void init();
+
+ QString m_captionAliasOrName;
+
+ QIconSet m_icon;
+
+ KexiUtils::Validator* m_validator;
+
+ //! Data that this column is assigned to.
+ KexiTableViewData* m_data;
+
+ KexiTableViewData* m_relatedData;
+ uint m_relatedDataPKeyID;
+
+ KexiDB::Field* m_field;
+
+ bool m_readOnly : 1;
+ bool m_fieldOwned : 1;
+ bool m_visible : 1;
+ bool m_relatedDataEditable : 1;
+ bool m_headerTextVisible : 1;
+
+ friend class KexiTableViewData;
+};
+
+
+/*! List of column definitions. */
+//typedef QValueVector<KexiTableViewColumn> KexiTableViewColumnList;
+
+typedef QPtrList<KexiTableItem> KexiTableViewDataBase;
+
+/*! Reimplements QPtrList to allow configurable sorting and more.
+ Original author: Till Busch.
+ Reimplemented by Jaroslaw Staniek.
+*/
+class KEXIDATATABLE_EXPORT KexiTableViewData : public QObject, protected KexiTableViewDataBase
+{
+ Q_OBJECT
+
+public:
+ typedef QPtrListIterator<KexiTableItem> Iterator;
+
+ //! not db-aware version
+ KexiTableViewData();
+
+ //! db-aware version
+ KexiTableViewData(KexiDB::Cursor *c);
+
+//TODO: make this more generic: allow to add more columns!
+ /*! Defines two-column table usually used with comboboxes.
+ First column is invisible and contains key values.
+ Second column and contains user-visible value.
+ @param keys a list of keys
+ @param values a list of text values (must be of the same length as keys list)
+ @param keyType a type for keys
+ @param valueType a type for values
+ */
+ KexiTableViewData(
+ const QValueList<QVariant> &keys, const QValueList<QVariant> &values,
+ KexiDB::Field::Type keyType = KexiDB::Field::Text,
+ KexiDB::Field::Type valueType = KexiDB::Field::Text);
+
+ /*! Like above constructor, but keys and values are not provided.
+ You can do this later by calling append(KexiTableItem*) method.
+ (KexiTableItem object must have exactly two columns)
+ */
+ KexiTableViewData(KexiDB::Field::Type keyType, KexiDB::Field::Type valueType);
+
+ virtual ~KexiTableViewData();
+//js void setSorting(int key, bool order=true, short type=1);
+
+ /*! Preloads all rows provided by cursor (only for db-aware version). */
+//! @todo change to bool and return false on error!
+ void preloadAllRows();
+
+ /*! Sets sorting for \a column. If \a column is -1, sorting is disabled. */
+ void setSorting(int column, bool ascending=true);
+
+ /*! \return the column number by which the data is sorted,
+ or -1 if sorting is disabled. In this case sortingOrder() will return 0.
+ Initial sorted column number for data after instantiating object is -1. */
+ inline int sortedColumn() const { return m_sortedColumn; }
+
+ /*! \return 1 if ascending sort order is set, -1 id descending sort order is set,
+ or 0 if no sorting is set. This is independent of whether data is sorted now.
+ Initial sorting for data after instantiating object is 0. */
+ inline int sortingOrder() const { return m_order; }
+
+ /*! Adds column \a col.
+ Warning: \a col will be owned by this object, and deleted on its destruction. */
+ void addColumn( KexiTableViewColumn* col );
+
+ inline int globalColumnID(int visibleID) { return m_globalColumnsIDs.at( visibleID ); }
+ inline int visibleColumnID(int globalID) { return m_visibleColumnsIDs.at( globalID ); }
+
+ /*virtual?*/
+ /*! \return true if this db-aware data set. */
+ inline bool isDBAware() { return m_cursor; }
+
+ /*! For db-aware data set only: table name is returned;
+ equivalent to cursor()->query()->parentTable()->name(). */
+ QString dbTableName() const;
+
+ inline KexiDB::Cursor* cursor() const { return m_cursor; }
+
+ inline uint columnsCount() const { return columns.count(); }
+
+ inline KexiTableViewColumn* column(uint c) { return columns.at(c); }
+
+ /*! Columns information */
+ KexiTableViewColumn::List columns;
+
+ /*! \return true if data is not editable. Can be set using setReadOnly()
+ but it's still true if database cursor returned by cursor()
+ is not 0 and has read-only connection. */
+ virtual bool isReadOnly() const;
+
+ /*! Sets readOnly flag for this data.
+ If \a set is true, insertingEnabled flag will be cleared automatically.
+ \sa isInsertingEnabled() */
+ virtual void setReadOnly(bool set);
+
+ /*! \return true if data inserting is enabled (the default). */
+ virtual bool isInsertingEnabled() const { return m_insertingEnabled; }
+
+ /*! Sets insertingEnabled flag. If true, empty row is available
+ If \a set is true, read-only flag will be cleared automatically.
+ \sa setReadOnly() */
+ virtual void setInsertingEnabled(bool set);
+
+ /*! Clears and initializes internal row edit buffer for incoming editing.
+ Creates buffer using KexiDB::RowEditBuffer(false) (false means not db-aware type)
+ if our data is not db-aware,
+ or db-aware buffer if data is db-aware (isDBAware()==true).
+ \sa KexiDB::RowEditBuffer
+ */
+ void clearRowEditBuffer();
+
+ /*! Updates internal row edit buffer: currently edited column \a col (number \a colnum)
+ has now assigned new value of \a newval.
+ Uses column's caption to address the column in buffer
+ if the buffer is of simple type, or db-aware buffer if (isDBAware()==true).
+ (then fields are addressed with KexiDB::Field, instead of caption strings).
+ If \a allowSignals is true (the default), aboutToChangeCell() signal is emitted.
+ \a visibleValueForLookupField allows to pass visible value (usually a text)
+ for a lookup field (only reasonable if col->visibleLookupColumnInfo != 0).
+ Note that \a newval may be changed in aboutToChangeCell() signal handler.
+ \sa KexiDB::RowEditBuffer */
+ bool updateRowEditBufferRef(KexiTableItem *item,
+ int colnum, KexiTableViewColumn* col, QVariant& newval, bool allowSignals = true,
+ QVariant *visibleValueForLookupField = 0);
+
+ /*! Added for convenience. Like above but \a newval is passed by value. */
+ inline bool updateRowEditBuffer(KexiTableItem *item, int colnum, KexiTableViewColumn* col,
+ QVariant newval, bool allowSignals = true)
+ {
+ QVariant newv(newval);
+ return updateRowEditBufferRef(item, colnum, col, newv, allowSignals);
+ }
+
+ /*! Added for convenience. Like above but it's assumed that \a item item's columns are ordered
+ like in table view, not like in form view. Don't use this with form views. */
+ inline bool updateRowEditBuffer(KexiTableItem *item, int colnum,
+ QVariant newval, bool allowSignals = true)
+ {
+ KexiTableViewColumn* col = columns.at(colnum);
+ return col ? updateRowEditBufferRef(item, colnum, col, newval, allowSignals) : false;
+ }
+
+ inline KexiDB::RowEditBuffer* rowEditBuffer() const { return m_pRowEditBuffer; }
+
+ /*! \return last operation's result information (always not null). */
+ inline KexiDB::ResultInfo* result() { return &m_result; }
+
+ bool saveRowChanges(KexiTableItem& item, bool repaint = false);
+
+ bool saveNewRow(KexiTableItem& item, bool repaint = false);
+
+ bool deleteRow(KexiTableItem& item, bool repaint = false);
+
+ /*! Deletes rows (by number) passed with \a rowsToDelete.
+ Currently, this method is only for non data-aware tables. */
+ void deleteRows( const QValueList<int> &rowsToDelete, bool repaint = false );
+
+ /*! Deletes all rows. Works either for db-aware and non db-aware tables.
+ Column's definition is not changed.
+ For db-aware version, all rows are removed from a database.
+ Row-edit buffer is cleared.
+
+ If \a repaint is true, reloadRequested() signal
+ is emitted after deleting (if at least one row was deleted),
+ so presenters can repaint their contents.
+
+ \return true on success. */
+ virtual bool deleteAllRows(bool repaint = false);
+
+ /*! @internal method, used mostly by specialized classes like KexiTableView.
+ Clears internal row structures. Row-edit buffer is cleared.
+ Does not touch data @ database backend.
+ Use deleteAllRows() to safely delete all rows. */
+ virtual void clearInternal();
+
+ /*! Inserts new \a item at index \a index.
+ \a item will be owned by this data object.
+ Note: Reasonable only for not not-db-aware version. */
+ void insertRow(KexiTableItem& item, uint index, bool repaint = false);
+
+/*TODO: add this as well?
+ void insertRow(KexiTableItem& item, KexiTableItem& aboveItem); */
+
+ //! \return index of autoincremented column. The result is cached.
+//! \todo what about multiple autoinc columns?
+//! \todo what about changing column order?
+ int autoIncrementedColumn();
+
+ //! Emits reloadRequested() signal to reload presenters.
+ void reload() { emit reloadRequested(); }
+
+ inline KexiTableItem* at( uint index ) { return KexiTableViewDataBase::at(index); }
+ inline virtual uint count() const { return KexiTableViewDataBase::count(); }
+ inline bool isEmpty () const { return KexiTableViewDataBase::isEmpty(); }
+ inline KexiTableItem* first() { return KexiTableViewDataBase::first(); }
+ inline KexiTableItem* last() { return KexiTableViewDataBase::last(); }
+ inline int findRef( const KexiTableItem* item ) { return KexiTableViewDataBase::findRef(item); }
+ inline void sort() { KexiTableViewDataBase::sort(); }
+ inline bool removeFirst() { return KexiTableViewDataBase::removeFirst(); }
+ inline bool removeLast() { return KexiTableViewDataBase::removeLast(); }
+ inline void append( const KexiTableItem* item ) { KexiTableViewDataBase::append(item); }
+ inline void prepend( const KexiTableItem* item ) { KexiTableViewDataBase::prepend(item); }
+ inline Iterator iterator() { return Iterator(*this); }
+ inline Iterator* createIterator() { return new Iterator(*this); }
+
+ /*! \return true if ROWID information is stored within every row.
+ Only reasonable for db-aware version. ROWID information is available
+ if DriverBehaviour::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false
+ for a KexiDB database driver and a table has no primary key defined.
+ Phisically, ROWID information is stored after last KexiTableItem's element,
+ so every KexiTableItem's length is expanded by one. */
+ inline bool containsROWIDInfo() const { return m_containsROWIDInfo; }
+
+ inline KexiTableItem* createItem() const
+ { return new KexiTableItem(m_itemSize); }
+
+public slots:
+ //! @internal The same as QObject::deleteLater() but also sets smart pointer m_cursor to 0 to avoid crashes...
+ void deleteLater();
+
+signals:
+ void destroying();
+
+ /*! Emitted before change of the single, currently edited cell.
+ Connect this signal to your slot and set \a result->success to false
+ to disallow this change. You can also change \a newValue to other value,
+ or change other columns in \a item row. */
+ void aboutToChangeCell(KexiTableItem *item, int colnum, QVariant& newValue,
+ KexiDB::ResultInfo* result);
+
+ /*! Emited before inserting of a new, current row.
+ Connect this signal to your slot and set \a result->success to false
+ to disallow this inserting. You can also change columns in \a item row. */
+ void aboutToInsertRow(KexiTableItem *item, KexiDB::ResultInfo* result, bool repaint);
+
+ /*! Emited before changing of an edited, current row.
+ Connect this signal to your slot and set \a result->success to false
+ to disallow this change. You can also change columns in \a item row. */
+ void aboutToUpdateRow(KexiTableItem *item, KexiDB::RowEditBuffer* buffer,
+ KexiDB::ResultInfo* result);
+
+ void rowUpdated(KexiTableItem*); //!< Current row has been updated
+
+ void rowInserted(KexiTableItem*, bool repaint); //!< A row has been inserted
+
+ //! A row has been inserted at \a index position (not db-aware data only)
+ void rowInserted(KexiTableItem*, uint index, bool repaint);
+
+ /*! Emited before deleting of a current row.
+ Connect this signal to your slot and set \a result->success to false
+ to disallow this deleting. */
+ void aboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint);
+
+ //! Current row has been deleted
+ void rowDeleted();
+
+ //! Rows have been deleted
+ void rowsDeleted( const QValueList<int> &rowsToDelete );
+
+ //! Displayed data needs to be reloaded in all presenters.
+ void reloadRequested();
+
+ void rowRepaintRequested(KexiTableItem&);
+
+protected:
+ void init();
+ void init(
+ const QValueList<QVariant> &keys, const QValueList<QVariant> &values,
+ KexiDB::Field::Type keyType, KexiDB::Field::Type valueType);
+
+ virtual int compareItems(Item item1, Item item2);
+ int cmpStr(Item item1, Item item2);
+ int cmpInt(Item item1, Item item2);
+ int cmpUInt(Item item1, Item item2);
+ int cmpLongLong(Item item1, Item item2);
+ int cmpULongLong(Item item1, Item item2);
+ int cmpDouble(Item item1, Item item2);
+ int cmpDate(Item item1, Item item2);
+ int cmpDateTime(Item item1, Item item2);
+ int cmpTime(Item item1, Item item2);
+
+ //! Compare function for BLOB data (QByteArray). Uses size as the weight.
+ int cmpBLOB(Item item1, Item item2);
+
+ //! internal: for saveRowChanges() and saveNewRow()
+ bool saveRow(KexiTableItem& item, bool insert, bool repaint);
+
+ //! (logical) sorted column number, set by setSorting()
+ //! can differ from m_realSortedColumn if there's lookup column used
+ int m_sortedColumn;
+ //! real sorted column number, set by setSorting(), used by cmp*() methods
+ int m_realSortedColumn;
+ short m_order;
+ short m_type;
+ int m_itemSize;
+ static unsigned short charTable[];
+ KexiDB::RowEditBuffer *m_pRowEditBuffer;
+ QGuardedPtr<KexiDB::Cursor> m_cursor;
+
+ //! used to faster lookup columns of simple type (not dbaware)
+// QDict<KexiTableViewColumn> *m_simpleColumnsByName;
+
+ KexiDB::ResultInfo m_result;
+
+ uint m_visibleColumnsCount;
+ QValueVector<int> m_visibleColumnsIDs, m_globalColumnsIDs;
+
+ bool m_readOnly : 1;
+ bool m_insertingEnabled : 1;
+
+ /*! Used in acceptEditor() to avoid infinite recursion,
+ eg. when we're calling acceptRowEdit() during cell accepting phase. */
+ bool m_inside_acceptEditor : 1;
+
+ //! @see containsROWIDInfo()
+ bool m_containsROWIDInfo : 1;
+
+ int m_autoIncrementedColumn;
+
+ int (KexiTableViewData::*cmpFunc)(void *, void *);
+
+ //! Temporary, used in compare functions like cmpInt(), cmpString()
+ //! to avoid memory allocations.
+ QVariant m_leftTmp, m_rightTmp;
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexitableviewheader.cpp b/kexi/widget/tableview/kexitableviewheader.cpp
new file mode 100644
index 000000000..3656a0417
--- /dev/null
+++ b/kexi/widget/tableview/kexitableviewheader.cpp
@@ -0,0 +1,202 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitableviewheader.h"
+
+#include <qapplication.h>
+#include <qtooltip.h>
+#include <qstyle.h>
+
+#include <kexiutils/utils.h>
+#include <kexiutils/styleproxy.h>
+
+//! @internal A style that allows to temporary change background color while
+//! drawing header section primitive. Used in KexiTableViewHeader.
+class KexiTableViewHeaderStyle : public KexiUtils::StyleProxy
+{
+ public:
+ KexiTableViewHeaderStyle(QStyle *parentStyle, QWidget *widget)
+ : KexiUtils::StyleProxy(parentStyle)
+ {
+ setBackgroundColor( widget->palette().active().background() );
+ }
+ ~KexiTableViewHeaderStyle() {}
+
+ virtual void drawPrimitive( PrimitiveElement pe,
+ QPainter *p, const QRect &r, const QColorGroup &cg, SFlags flags = Style_Default,
+ const QStyleOption& option = QStyleOption::Default ) const
+ {
+ if (pe==QStyle::PE_HeaderSection) {
+ QColorGroup newCg(cg);
+ newCg.setColor(QColorGroup::Button, m_backgroundColor);
+ newCg.setColor(QColorGroup::Background, m_backgroundColor); //set background color as well (e.g. for thinkeramik)
+ m_style->drawPrimitive( pe, p, r, newCg, flags, option );
+ return;
+ }
+ m_style->drawPrimitive( pe, p, r, cg, flags, option );
+ }
+
+ void setBackgroundColor( const QColor& color ) { m_backgroundColor = color; }
+
+ protected:
+ QColor m_backgroundColor;
+};
+
+KexiTableViewHeader::KexiTableViewHeader(QWidget * parent, const char * name)
+ : QHeader(parent, name)
+ , m_lastToolTipSection(-1)
+ , m_selectionBackgroundColor(qApp->palette().active().highlight())
+ , m_selectedSection(-1)
+ , m_styleChangeEnabled(true)
+{
+ styleChange( style() );
+ installEventFilter(this);
+ connect(this, SIGNAL(sizeChange(int,int,int)),
+ this, SLOT(slotSizeChange(int,int,int)));
+}
+
+KexiTableViewHeader::~KexiTableViewHeader()
+{
+}
+
+void KexiTableViewHeader::styleChange( QStyle& oldStyle )
+{
+ QHeader::styleChange( oldStyle );
+ if (!m_styleChangeEnabled)
+ return;
+ m_styleChangeEnabled = false;
+ setStyle( new KexiTableViewHeaderStyle(&qApp->style(), this) );
+ m_styleChangeEnabled = true;
+}
+
+int KexiTableViewHeader::addLabel ( const QString & s, int size )
+{
+ m_toolTips += "";
+ slotSizeChange(0,0,0);//refresh
+ return QHeader::addLabel(s, size);
+}
+
+int KexiTableViewHeader::addLabel ( const QIconSet & iconset, const QString & s, int size )
+{
+ m_toolTips += "";
+ slotSizeChange(0,0,0);//refresh
+ return QHeader::addLabel(iconset, s, size);
+}
+
+void KexiTableViewHeader::removeLabel( int section )
+{
+ if (section < 0 || section >= count())
+ return;
+ QStringList::Iterator it = m_toolTips.begin();
+ it += section;
+ m_toolTips.remove(it);
+ slotSizeChange(0,0,0);//refresh
+ QHeader::removeLabel(section);
+}
+
+void KexiTableViewHeader::setToolTip( int section, const QString & toolTip )
+{
+ if (section < 0 || section >= (int)m_toolTips.count())
+ return;
+ m_toolTips[ section ] = toolTip;
+}
+
+bool KexiTableViewHeader::eventFilter(QObject * watched, QEvent * e)
+{
+ if (e->type()==QEvent::MouseMove) {
+ const int section = sectionAt( static_cast<QMouseEvent*>(e)->x() );
+ if (section != m_lastToolTipSection && section >= 0 && section < (int)m_toolTips.count()) {
+ QToolTip::remove(this, m_toolTipRect);
+ QString tip = m_toolTips[ section ];
+ if (tip.isEmpty()) { //try label
+ QFontMetrics fm(font());
+ int minWidth = fm.width( label( section ) ) + style().pixelMetric( QStyle::PM_HeaderMargin );
+ QIconSet *iset = iconSet( section );
+ if (iset)
+ minWidth += (2+iset->pixmap( QIconSet::Small, QIconSet::Normal ).width()); //taken from QHeader::sectionSizeHint()
+ if (minWidth > sectionSize( section ))
+ tip = label( section );
+ }
+ if (tip.isEmpty()) {
+ m_lastToolTipSection = -1;
+ }
+ else {
+ QToolTip::add(this, m_toolTipRect = sectionRect(section), tip);
+ m_lastToolTipSection = section;
+ }
+ }
+ }
+// if (e->type()==QEvent::MouseButtonPress) {
+// todo
+// }
+ return QHeader::eventFilter(watched, e);
+}
+
+void KexiTableViewHeader::slotSizeChange(int /*section*/, int /*oldSize*/, int /*newSize*/ )
+{
+ if (m_lastToolTipSection>0)
+ QToolTip::remove(this, m_toolTipRect);
+ m_lastToolTipSection = -1; //tooltip's rect is now invalid
+}
+
+void KexiTableViewHeader::setSelectionBackgroundColor(const QColor &color)
+{
+ m_selectionBackgroundColor = color;
+}
+
+QColor KexiTableViewHeader::selectionBackgroundColor() const
+{
+ return m_selectionBackgroundColor;
+}
+
+void KexiTableViewHeader::setSelectedSection(int section)
+{
+ if (m_selectedSection==section || (section!=-1 && section>=count()))
+ return;
+ const int oldSection = m_selectedSection;
+ m_selectedSection = section;
+ if (oldSection!=-1)
+ update(sRect(oldSection));
+ if (m_selectedSection!=-1)
+ update(sRect(m_selectedSection));
+}
+
+int KexiTableViewHeader::selectedSection() const
+{
+ return m_selectedSection;
+}
+
+void KexiTableViewHeader::paintSection( QPainter * p, int index, const QRect & fr )
+{
+ const bool paintSelection = index==m_selectedSection && index != -1;
+ if (paintSelection) {
+ static_cast<KexiTableViewHeaderStyle&>(style()).setBackgroundColor(
+ KexiUtils::blendedColors(
+ palette().active().background(), m_selectionBackgroundColor, 2, 1) );
+ }
+
+ QHeader::paintSection( p, index, fr );
+
+ if (paintSelection) { //revert the color for subsequent paints
+ static_cast<KexiTableViewHeaderStyle&>(style()).setBackgroundColor(
+ palette().active().background());
+ }
+}
+
+#include "kexitableviewheader.moc"
diff --git a/kexi/widget/tableview/kexitableviewheader.h b/kexi/widget/tableview/kexitableviewheader.h
new file mode 100644
index 000000000..5da3fa7b6
--- /dev/null
+++ b/kexi/widget/tableview/kexitableviewheader.h
@@ -0,0 +1,75 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXITABLEVIEWHEADER_H
+#define KEXITABLEVIEWHEADER_H
+
+#include <qheader.h>
+
+class QStyle;
+
+//! @short A table view header with additional actions.
+/*! Displays field description (Field::description()) text as tool tip, if available.
+ Displays tool tips if a pointed section is not wide enough to fit its label text.
+
+ \todo react on indexChange ( int section, int fromIndex, int toIndex ) signal
+*/
+class KEXIDATATABLE_EXPORT KexiTableViewHeader : public QHeader
+{
+ Q_OBJECT
+
+ public:
+ KexiTableViewHeader(QWidget * parent = 0, const char * name = 0);
+
+ virtual ~KexiTableViewHeader();
+
+ int addLabel( const QString & s, int size = -1 );
+
+ int addLabel( const QIconSet & iconset, const QString & s, int size = -1 );
+
+ void removeLabel( int section );
+
+ /*! Sets \a toolTip for \a section. */
+ void setToolTip( int section, const QString & toolTip );
+
+ virtual bool eventFilter(QObject * watched, QEvent * e);
+
+ void setSelectedSection(int section);
+ int selectedSection() const;
+
+ QColor selectionBackgroundColor() const;
+ void setSelectionBackgroundColor(const QColor &color);
+
+ protected slots:
+ void slotSizeChange(int section, int oldSize, int newSize );
+
+ protected:
+ virtual void paintSection ( QPainter * p, int index, const QRect & fr );
+ virtual void styleChange( QStyle& oldStyle );
+
+ int m_lastToolTipSection;
+ QRect m_toolTipRect;
+
+ QStringList m_toolTips;
+ QColor m_selectionBackgroundColor;
+ int m_selectedSection;
+ bool m_styleChangeEnabled : 1;
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexitextformatter.cpp b/kexi/widget/tableview/kexitextformatter.cpp
new file mode 100644
index 000000000..f4e0b89de
--- /dev/null
+++ b/kexi/widget/tableview/kexitextformatter.cpp
@@ -0,0 +1,237 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <klocale.h>
+
+#include "kexitextformatter.h"
+#include <widget/utils/kexidatetimeformatter.h>
+#include <kexidb/utils.h>
+
+//! @internal
+class KexiTextFormatter::Private
+{
+ public:
+ Private() : field(0), dateFormatter(0), timeFormatter(0)
+ {
+ }
+
+ ~Private()
+ {
+ delete dateFormatter;
+ delete timeFormatter;
+ }
+
+ KexiDB::Field* field;
+ KexiDateFormatter *dateFormatter;
+ KexiTimeFormatter *timeFormatter;
+};
+
+KexiTextFormatter::KexiTextFormatter()
+ : d( new Private )
+{
+}
+
+KexiTextFormatter::~KexiTextFormatter()
+{
+ delete d;
+}
+
+void KexiTextFormatter::setField( KexiDB::Field* field )
+{
+ d->field = field;
+ if (!d->field)
+ return;
+ if (d->field->type() == KexiDB::Field::Date || d->field->type() == KexiDB::Field::DateTime)
+ d->dateFormatter = new KexiDateFormatter();
+ else {
+ delete d->dateFormatter;
+ d->dateFormatter = 0;
+ }
+ if (d->field->type() == KexiDB::Field::Time || d->field->type() == KexiDB::Field::DateTime)
+ d->timeFormatter = new KexiTimeFormatter();
+ else {
+ delete d->timeFormatter;
+ d->timeFormatter = 0;
+ }
+}
+
+QString KexiTextFormatter::valueToText(const QVariant& value, const QString& add) const
+{
+ //cases, in order of expected frequency
+ if (!d->field || d->field->isTextType())
+ return value.toString() + add;
+ else if (d->field->isIntegerType()) {
+ if (value.toInt() == 0)
+ return add; //eat 0
+ }
+ else if (d->field->isFPNumericType()) {
+//! @todo precision!
+//! @todo support 'g' format
+ if (value.toDouble() == 0.0)
+ return add.isEmpty() ? "0" : add; //eat 0
+#if 0 //moved to KexiDB::formatNumberForVisibleDecimalPlaces()
+ QString text( QString::number(value.toDouble(), 'f',
+ QMAX(d->field->visibleDecimalPlaces(), 10)) ); //!<-- 10 is quite good maximum for fractional digits
+ //!< @todo add command line settings?
+//! @todo (js): get decimal places settings here...
+ QStringList sl = QStringList::split(".", text);
+ //nothing
+ }
+ else if (sl.count()==2) {
+// kdDebug() << "sl.count()=="<<sl.count()<< " " <<sl[0] << " | " << sl[1] << endl;
+ const QString sl1 = sl[1];
+ int pos = sl1.length()-1;
+ if (pos>=1) {
+ for (;pos>=0 && sl1[pos]=='0';pos--)
+ ;
+ pos++;
+ }
+ if (pos>0)
+ text = sl[0] + m_decsym + sl1.left(pos);
+ else
+ text = sl[0]; //no decimal point
+ }
+#endif
+ return KexiDB::formatNumberForVisibleDecimalPlaces(
+ value.toDouble(), d->field->visibleDecimalPlaces() ) + add;
+ }
+ else if (d->field->type() == KexiDB::Field::Boolean) {
+//! @todo temporary solution for booleans!
+ const bool boolValue = value.isNull() ? QVariant(add).toBool() : value.toBool();
+ return boolValue ? "1" : "0";
+ }
+ else if (d->field->type() == KexiDB::Field::Date) {
+ return d->dateFormatter->dateToString( value.toString().isEmpty() ? QDate() : value.toDate() );
+ }
+ else if (d->field->type() == KexiDB::Field::Time) {
+ return d->timeFormatter->timeToString(
+ //hack to avoid converting null variant to valid QTime(0,0,0)
+ value.toString().isEmpty() ? value.toTime() : QTime(99,0,0) );
+ }
+ else if (d->field->type() == KexiDB::Field::DateTime) {
+ if (value.toString().isEmpty() )
+ return add;
+ return d->dateFormatter->dateToString( value.toDateTime().date() ) + " " +
+ d->timeFormatter->timeToString( value.toDateTime().time() );
+ }
+ else if (d->field->type() == KexiDB::Field::BigInteger) {
+ if (value.toLongLong() == 0)
+ return add; //eat 0
+ }
+ //default: text
+ return value.toString() + add;
+}
+
+QVariant KexiTextFormatter::textToValue(const QString& text) const
+{
+ if (!d->field)
+ return QVariant();
+ const KexiDB::Field::Type t = d->field->type();
+ switch (t) {
+ case KexiDB::Field::Text:
+ case KexiDB::Field::LongText:
+ return text;
+ case KexiDB::Field::Byte:
+ case KexiDB::Field::ShortInteger:
+ return text.toShort();
+//! @todo uint, etc?
+ case KexiDB::Field::Integer:
+ return text.toInt();
+ case KexiDB::Field::BigInteger:
+ return text.toLongLong();
+ case KexiDB::Field::Boolean:
+//! @todo temporary solution for booleans!
+ return text == "1" ? QVariant(true,1) : QVariant(false,0);
+ case KexiDB::Field::Date:
+ return d->dateFormatter->stringToVariant( text );
+ case KexiDB::Field::Time:
+ return d->timeFormatter->stringToVariant( text );
+ case KexiDB::Field::DateTime:
+ return stringToDateTime(*d->dateFormatter, *d->timeFormatter, text);
+ case KexiDB::Field::Float:
+ case KexiDB::Field::Double: {
+ // replace custom decimal symbol with '.' as required by to{Float|Double}()
+ QString fixedText( text );
+ fixedText.replace(KGlobal::locale()->decimalSymbol(), ".");
+ if (t == KexiDB::Field::Double)
+ return fixedText.toDouble();
+ return fixedText.toFloat();
+ }
+ default:
+ return text;
+ }
+//! @todo more data types!
+}
+
+bool KexiTextFormatter::valueIsEmpty(const QString& text) const
+{
+ if (text.isEmpty())
+ return true;
+
+ if (d->field) {
+ const KexiDB::Field::Type t = d->field->type();
+ if (t == KexiDB::Field::Date)
+ return d->dateFormatter->isEmpty( text );
+ else if (t == KexiDB::Field::Time)
+ return d->timeFormatter->isEmpty( text );
+ else if (t == KexiDB::Field::Time)
+ return dateTimeIsEmpty( *d->dateFormatter, *d->timeFormatter, text );
+ }
+
+//! @todo
+ return text.isEmpty();
+}
+
+bool KexiTextFormatter::valueIsValid(const QString& text) const
+{
+ if (!d->field)
+ return true;
+//! @todo fix for fields with "required" property = true
+ if (valueIsEmpty(text)/*ok?*/)
+ return true;
+
+ const KexiDB::Field::Type t = d->field->type();
+ if (t == KexiDB::Field::Date)
+ return d->dateFormatter->stringToVariant( text ).isValid();
+ else if (t == KexiDB::Field::Time)
+ return d->timeFormatter->stringToVariant( text ).isValid();
+ else if (t == KexiDB::Field::DateTime)
+ return dateTimeIsValid( *d->dateFormatter, *d->timeFormatter, text );
+
+//! @todo
+ return true;
+}
+
+QString KexiTextFormatter::inputMask() const
+{
+ const KexiDB::Field::Type t = d->field->type();
+ if (t==KexiDB::Field::Date) {
+//! @todo use KDateWidget?
+ return d->dateFormatter->inputMask();
+ }
+ else if (t==KexiDB::Field::Time) {
+//! @todo use KTimeWidget
+ d->timeFormatter->inputMask();
+ }
+ else if (t==KexiDB::Field::DateTime) {
+ dateTimeInputMask( *d->dateFormatter, *d->timeFormatter );
+ }
+ return QString::null;
+}
+
diff --git a/kexi/widget/tableview/kexitextformatter.h b/kexi/widget/tableview/kexitextformatter.h
new file mode 100644
index 000000000..3ea611a44
--- /dev/null
+++ b/kexi/widget/tableview/kexitextformatter.h
@@ -0,0 +1,64 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXITEXTFORMATTER_H
+#define KEXITEXTFORMATTER_H
+
+#include <kexidb/field.h>
+
+//! @short Text formatter used to format QVariant values to text for displaying and back to QVariant
+/*! Used by KexiInputTableEdit, KexiDateTableEdit, KexiTimeTableEdit, KexiDateTimeTableEdit,
+ KexiDBLineEdit (forms), etc. */
+class KEXIDATATABLE_EXPORT KexiTextFormatter
+{
+ public:
+ KexiTextFormatter();
+ ~KexiTextFormatter();
+
+ //! Assigns \a field to the formatter. This affects its behaviour.
+ void setField( KexiDB::Field* field );
+
+ /*! \return text for \a value.
+ A field schema set using setField() is used to perform the formatting.
+ \a add is a text that should be added to the value if possible.
+ Used in KexiInputTableEdit::setValueInternal(), by form widgets and for reporting/printing. */
+ QString valueToText(const QVariant& value, const QString& add) const;
+
+ /*! \return value cnverted from \a text
+ A field schema set using setField() is used to perform the formatting.
+ Used in KexiInputTableEdit::setValueInternal(), by form widgets and for reporting/printing. */
+ QVariant textToValue(const QString& text) const;
+
+ /*! \return true if value formatted as \a text is empty.
+ A field schema set using setField() is used to perform the calculation. */
+ bool valueIsEmpty(const QString& text) const;
+
+ /*! \return true if value formatted as \a text is valid.
+ A field schema set using setField() is used to perform the calculation. */
+ bool valueIsValid(const QString& text) const;
+
+ /*! \return input mask for intering values related to a field schema
+ which has been set using setField(). */
+ QString inputMask() const;
+
+ class Private;
+ Private *d;
+};
+
+#endif
diff --git a/kexi/widget/tableview/kexitimetableedit.cpp b/kexi/widget/tableview/kexitimetableedit.cpp
new file mode 100644
index 000000000..3238c58e1
--- /dev/null
+++ b/kexi/widget/tableview/kexitimetableedit.cpp
@@ -0,0 +1,158 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "kexitimetableedit.h"
+
+#include <qapplication.h>
+#include <qpainter.h>
+#include <qvariant.h>
+#include <qrect.h>
+#include <qpalette.h>
+#include <qcolor.h>
+#include <qfontmetrics.h>
+#include <qdatetime.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qlayout.h>
+#include <qtoolbutton.h>
+#include <qdatetimeedit.h>
+#include <qclipboard.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kdatepicker.h>
+#include <kdatetbl.h>
+#include <klineedit.h>
+#include <kpopupmenu.h>
+#include <kdatewidget.h>
+
+#include <kexiutils/utils.h>
+
+KexiTimeTableEdit::KexiTimeTableEdit(KexiTableViewColumn &column, QWidget *parent)
+ : KexiInputTableEdit(column, parent)
+{
+ setName("KexiTimeTableEdit");
+
+//! @todo add QValidator so time like "99:88:77" cannot be even entered
+
+ m_lineedit->setInputMask( m_formatter.inputMask() );
+}
+
+KexiTimeTableEdit::~KexiTimeTableEdit()
+{
+}
+
+void KexiTimeTableEdit::setValueInInternalEditor(const QVariant &value)
+{
+ if (value.isValid() && value.toTime().isValid())
+ m_lineedit->setText( m_formatter.timeToString( value.toTime() ) );
+ else
+ m_lineedit->setText( QString::null );
+}
+
+void KexiTimeTableEdit::setValueInternal(const QVariant& add_, bool removeOld)
+{
+ if (removeOld) {
+ //new time entering... just fill the line edit
+//! @todo cut string if too long..
+ QString add(add_.toString());
+ m_lineedit->setText(add);
+ m_lineedit->setCursorPosition(add.length());
+ return;
+ }
+ setValueInInternalEditor( m_origValue );
+ m_lineedit->setCursorPosition(0); //ok?
+}
+
+void KexiTimeTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h )
+{
+ Q_UNUSED(p);
+ Q_UNUSED(focused);
+ Q_UNUSED(x);
+ Q_UNUSED(w);
+ Q_UNUSED(h);
+#ifdef Q_WS_WIN
+ y_offset = -1;
+#else
+ y_offset = 0;
+#endif
+ if (!val.isNull() && val.canCast(QVariant::Time))
+ txt = m_formatter.timeToString(val.toTime());
+ align |= AlignLeft;
+}
+
+bool KexiTimeTableEdit::valueIsNull()
+{
+ if (m_formatter.isEmpty( m_lineedit->text() )) //empty time is null
+ return true;
+ return !timeValue().isValid();
+}
+
+bool KexiTimeTableEdit::valueIsEmpty()
+{
+ return valueIsNull();// OK? TODO (nonsense?)
+}
+
+QTime KexiTimeTableEdit::timeValue()
+{
+ return m_formatter.stringToTime( m_lineedit->text() );
+}
+
+QVariant KexiTimeTableEdit::value()
+{
+ return m_formatter.stringToVariant( m_lineedit->text() );
+}
+
+bool KexiTimeTableEdit::valueIsValid()
+{
+ if (m_formatter.isEmpty( m_lineedit->text() )) //empty time is valid
+ return true;
+ return m_formatter.stringToTime( m_lineedit->text() ).isValid();
+}
+
+void KexiTimeTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue)
+{
+ Q_UNUSED(visibleValue);
+ if (!value.isNull() && value.toTime().isValid())
+ qApp->clipboard()->setText( m_formatter.timeToString(value.toTime()) );
+ else
+ qApp->clipboard()->setText( QString::null );
+}
+
+void KexiTimeTableEdit::handleAction(const QString& actionName)
+{
+ const bool alreadyVisible = m_lineedit->isVisible();
+
+ if (actionName=="edit_paste") {
+ const QVariant newValue( m_formatter.stringToTime( qApp->clipboard()->text() ) );
+ if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode
+ emit editRequested();
+ m_lineedit->clear();
+ }
+ setValueInInternalEditor( newValue );
+ }
+ else
+ KexiInputTableEdit::handleAction(actionName);
+}
+
+KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiTimeEditorFactoryItem, KexiTimeTableEdit)
+
+#include "kexitimetableedit.moc"
diff --git a/kexi/widget/tableview/kexitimetableedit.h b/kexi/widget/tableview/kexitimetableedit.h
new file mode 100644
index 000000000..4daa68ec2
--- /dev/null
+++ b/kexi/widget/tableview/kexitimetableedit.h
@@ -0,0 +1,64 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004,2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KEXITIMETABLEEDIT_H
+#define KEXITIMETABLEEDIT_H
+
+#include "kexiinputtableedit.h"
+#include <widget/utils/kexidatetimeformatter.h>
+
+/*! @short Editor class for Time type.
+ It is a replacement QTimeEdit due to usability problems:
+ people are accustomed to use single-character cursor.
+ Time format is retrieved from the KDE global settings
+ and input/output is performed using KLineEdit (from KexiInputTableEdit).
+*/
+class KexiTimeTableEdit : public KexiInputTableEdit
+{
+ Q_OBJECT
+
+ public:
+ KexiTimeTableEdit(KexiTableViewColumn &column, QWidget *parent=0);
+ virtual ~KexiTimeTableEdit();
+ virtual void setupContents( QPainter *p, bool focused, const QVariant& val,
+ QString &txt, int &align, int &x, int &y_offset, int &w, int &h );
+ virtual QVariant value();
+ virtual bool valueIsNull();
+ virtual bool valueIsEmpty();
+ virtual bool valueIsValid();
+
+ /*! Reimplemented after KexiInputTableEdit. */
+ virtual void handleAction(const QString& actionName);
+
+ /*! Reimplemented after KexiInputTableEdit. */
+ virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue);
+
+ protected:
+ //! helper
+ void setValueInInternalEditor(const QVariant &value);
+ virtual void setValueInternal(const QVariant& add, bool removeOld);
+ QTime timeValue();
+
+ //! Used to format and convert time values
+ KexiTimeFormatter m_formatter;
+};
+
+KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiTimeEditorFactoryItem)
+
+#endif
diff --git a/kexi/widget/utils/Makefile.am b/kexi/widget/utils/Makefile.am
new file mode 100644
index 000000000..5d210f1aa
--- /dev/null
+++ b/kexi/widget/utils/Makefile.am
@@ -0,0 +1,19 @@
+include $(top_srcdir)/kexi/Makefile.global
+
+lib_LTLIBRARIES = libkexiguiutils.la
+libkexiguiutils_la_SOURCES = kexisharedactionclient.cpp kexirecordnavigator.cpp \
+ kexigradientwidget.cpp kexirecordmarker.cpp kexidisplayutils.cpp \
+ kexiflowlayout.cpp kexidatetimeformatter.cpp kexitooltip.cpp kexiarrowtip.cpp \
+ kexidropdownbutton.cpp kexicomboboxdropdownbutton.cpp kexicontextmenuutils.cpp
+
+libkexiguiutils_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved
+libkexiguiutils_la_LIBADD = $(LIB_KDEUI)
+
+SUBDIRS = .
+
+# set the include path for X, qt and KDE - all_includes must remain last!
+INCLUDES = -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/widget/utils $(all_includes)
+
+METASOURCES = AUTO
+
+noinst_HEADERS = kexigradientwidget.h
diff --git a/kexi/widget/utils/kexiarrowtip.cpp b/kexi/widget/utils/kexiarrowtip.cpp
new file mode 100644
index 000000000..cdffcb023
--- /dev/null
+++ b/kexi/widget/utils/kexiarrowtip.cpp
@@ -0,0 +1,164 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiarrowtip.h"
+
+#include <qpixmap.h>
+#include <qbitmap.h>
+#include <qpainter.h>
+#include <qimage.h>
+#include <qtooltip.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qtimer.h>
+
+#include <kexiutils/utils.h>
+
+KexiArrowTip::KexiArrowTip(const QString& text, QWidget* parent)
+ : KexiToolTip(text, parent)
+ , m_opacity(0.0)
+{
+ QPalette pal( palette() );
+ QColorGroup cg(pal.active());
+ cg.setColor(QColorGroup::Foreground, Qt::red);
+ pal.setActive(cg);
+ setPalette(pal);
+
+ QFontMetrics fm(font());
+ QSize sz(fm.boundingRect(m_value.toString()).size());
+ sz += QSize(14, 10); //+margins
+ m_arrowHeight = sz.height()/2;
+ sz += QSize(0, m_arrowHeight); //+arrow height
+ resize(sz);
+
+ setAutoMask( false );
+
+ //generate mask
+ QPixmap maskPm(size());
+ maskPm.fill( black );
+ QPainter maskPainter(&maskPm);
+ drawFrame(maskPainter);
+ QImage maskImg( maskPm.convertToImage() );
+ QBitmap bm;
+ bm = maskImg.createHeuristicMask();
+ setMask( bm );
+}
+
+KexiArrowTip::~KexiArrowTip()
+{
+}
+
+void KexiArrowTip::show()
+{
+ if (isVisible())
+ return;
+
+ m_opacity = 0.0;
+ setWindowOpacity(0.0);
+ KexiToolTip::show();
+ increaseOpacity();
+}
+
+void KexiArrowTip::hide()
+{
+ if (!isVisible())
+ return;
+
+ decreaseOpacity();
+}
+
+void KexiArrowTip::increaseOpacity()
+{
+ m_opacity += 0.10;
+ setWindowOpacity(m_opacity);
+ if (m_opacity < 1.0)
+ QTimer::singleShot(25, this, SLOT(increaseOpacity()));
+}
+
+void KexiArrowTip::decreaseOpacity()
+{
+ if (m_opacity<=0.0) {
+ KexiToolTip::close();
+ m_opacity = 0.0;
+ return;
+ }
+ m_opacity -= 0.10;
+ setWindowOpacity(m_opacity);
+ QTimer::singleShot(25, this, SLOT(decreaseOpacity()));
+}
+
+bool KexiArrowTip::close ( bool alsoDelete )
+{
+ if (!isVisible()) {
+ return KexiToolTip::close(alsoDelete);
+ }
+ if (m_opacity>0.0)
+ decreaseOpacity();
+ else
+ return KexiToolTip::close(alsoDelete);
+ return m_opacity<=0.0;
+}
+
+void KexiArrowTip::drawContents(QPainter& p)
+{
+ p.setPen( QPen(palette().active().foreground(), 1) );
+ p.drawText(QRect(0,m_arrowHeight,width(),height()-m_arrowHeight),
+ Qt::AlignCenter, m_value.toString());
+}
+
+void KexiArrowTip::drawFrame(QPainter& p)
+{
+ QPen pen(palette().active().foreground(), 1, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
+ p.setPen( pen );
+ /*
+ /\
+ +- -----+
+ | text |
+ +--------+
+ */
+ //1st line
+ const int arrowOffset = 5; //5 pixels to right
+ QPointArray pa(8);
+ pa.setPoint(0, 0, m_arrowHeight-1);
+ pa.setPoint(1, 0, height()-1);
+ pa.setPoint(2, width()-1, height()-1);
+ pa.setPoint(3, width()-1, m_arrowHeight-1);
+ pa.setPoint(4, arrowOffset+m_arrowHeight+m_arrowHeight-2, m_arrowHeight-1);
+ pa.setPoint(5, arrowOffset+m_arrowHeight-1, 0);
+ pa.setPoint(6, arrowOffset, m_arrowHeight-1);
+ pa.setPoint(7, 0, m_arrowHeight-1);
+ p.drawPolyline(pa);
+ //-2nd, internal line
+ pa.resize(12);
+ pa.setPoint(0, 1, m_arrowHeight);
+ pa.setPoint(1, 1, height()-2);
+ pa.setPoint(2, width()-2, height()-2);
+ pa.setPoint(3, width()-2, m_arrowHeight);
+ pa.setPoint(4, arrowOffset+m_arrowHeight+m_arrowHeight-2, m_arrowHeight);
+ pa.setPoint(5, arrowOffset+m_arrowHeight-1, 1);
+ pa.setPoint(6, arrowOffset, m_arrowHeight);
+ pa.setPoint(7, 0, m_arrowHeight);
+ pa.setPoint(8, arrowOffset+1, m_arrowHeight);
+ pa.setPoint(9, arrowOffset+m_arrowHeight-1, 2);
+ pa.setPoint(10, arrowOffset+m_arrowHeight+m_arrowHeight-3, m_arrowHeight);
+ pa.setPoint(11, width()-2, m_arrowHeight);
+ p.drawPolyline(pa);
+}
+
+#include "kexiarrowtip.moc"
diff --git a/kexi/widget/utils/kexiarrowtip.h b/kexi/widget/utils/kexiarrowtip.h
new file mode 100644
index 000000000..d6de61868
--- /dev/null
+++ b/kexi/widget/utils/kexiarrowtip.h
@@ -0,0 +1,56 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIARROWTIP_H
+#define KEXIARROWTIP_H
+
+#include "kexitooltip.h"
+
+//! \brief A tooltip-like widget with additional arrow
+/*! The widget also suppors fade in and fade out effect,
+ if the underlying display system supports this.
+*/
+class KEXIGUIUTILS_EXPORT KexiArrowTip : public KexiToolTip
+{
+ Q_OBJECT
+ public:
+ KexiArrowTip(const QString& text, QWidget* parent);
+ virtual ~KexiArrowTip();
+
+ inline QString text() const { return m_value.toString(); }
+ virtual bool close() { return close(false); }
+ virtual bool close( bool alsoDelete );
+
+ public slots:
+ virtual void show();
+ virtual void hide();
+
+ protected slots:
+ void increaseOpacity();
+ void decreaseOpacity();
+
+ protected:
+ virtual void drawFrame(QPainter& p);
+ virtual void drawContents(QPainter& p);
+
+ int m_arrowHeight;
+ double m_opacity;
+};
+
+#endif
diff --git a/kexi/widget/utils/kexicomboboxdropdownbutton.cpp b/kexi/widget/utils/kexicomboboxdropdownbutton.cpp
new file mode 100644
index 000000000..407bc6fe5
--- /dev/null
+++ b/kexi/widget/utils/kexicomboboxdropdownbutton.cpp
@@ -0,0 +1,87 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicomboboxdropdownbutton.h"
+
+#include <kpopupmenu.h>
+#include <kdebug.h>
+#include <kcombobox.h>
+
+#include <qstyle.h>
+#include <qapplication.h>
+
+KexiComboBoxDropDownButton::KexiComboBoxDropDownButton( QWidget *parent )
+ : KPushButton(parent)
+{
+ m_paintedCombo = new KComboBox(this);
+ m_paintedCombo->hide();
+ m_paintedCombo->setEditable(true);
+
+ setToggleButton(true);
+ styleChange(style());
+ m_paintedCombo->move(0,0);
+ m_paintedCombo->setFixedSize(size());
+}
+
+KexiComboBoxDropDownButton::~KexiComboBoxDropDownButton()
+{
+}
+
+void KexiComboBoxDropDownButton::drawButton(QPainter *p)
+{
+ int flags = QStyle::Style_Enabled | QStyle::Style_HasFocus;
+ if (isDown())
+ flags |= QStyle::Style_Down;
+
+ KPushButton::drawButton(p);
+
+ QRect r = rect();
+ r.setHeight(r.height()+m_fixForHeight);
+ if (m_drawComplexControl) {
+ if (m_fixForHeight>0 && m_paintedCombo->size()!=size()) {
+ m_paintedCombo->move(0,0);
+ m_paintedCombo->setFixedSize(size()+QSize(0, m_fixForHeight)); //last chance to fix size
+ }
+ style().drawComplexControl( QStyle::CC_ComboBox, p,
+ m_fixForHeight>0 ? (const QWidget*)m_paintedCombo : this, r, colorGroup(),
+ flags, (uint)(QStyle::SC_ComboBoxArrow), QStyle::SC_None );
+ }
+ else {
+ r.setWidth(r.width()+2);
+ style().drawPrimitive( QStyle::PE_ArrowDown, p, r, colorGroup(), flags);
+ }
+}
+
+void KexiComboBoxDropDownButton::styleChange( QStyle & oldStyle )
+{
+ //<hack>
+ if (qstricmp(style().name(),"thinkeramik")==0) {
+ m_fixForHeight = 3;
+ }
+ else
+ m_fixForHeight = 0;
+ //</hack>
+ m_drawComplexControl =
+ (style().inherits("KStyle") && qstricmp(style().name(),"qtcurve")!=0)
+ || qstricmp(style().name(),"platinum")==0;
+ if (m_fixForHeight==0)
+ setFixedWidth( style().querySubControlMetrics( QStyle::CC_ComboBox,
+ (const QWidget*)m_paintedCombo, QStyle::SC_ComboBoxArrow ).width() +1 );
+ KPushButton::styleChange(oldStyle);
+}
diff --git a/kexi/widget/utils/kexicomboboxdropdownbutton.h b/kexi/widget/utils/kexicomboboxdropdownbutton.h
new file mode 100644
index 000000000..da53a7e2e
--- /dev/null
+++ b/kexi/widget/utils/kexicomboboxdropdownbutton.h
@@ -0,0 +1,49 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiComboBoxDropDownButton_H
+#define KexiComboBoxDropDownButton_H
+
+#include <kpushbutton.h>
+
+class KComboBox;
+
+//! @short A drop-down button for combo box widgets
+/*! Used in KexiComboBoxTableEdit.
+*/
+class KEXIGUIUTILS_EXPORT KexiComboBoxDropDownButton : public KPushButton
+{
+ public:
+ KexiComboBoxDropDownButton( QWidget *parent );
+ virtual ~KexiComboBoxDropDownButton();
+
+ protected:
+ /*! Reimplemented after @ref KPushButton to draw drop-down arrow. */
+ virtual void drawButton(QPainter *p);
+
+ /*! Reimplemented after @ref KPushButton to adapt size to style changes. */
+ virtual void styleChange( QStyle & oldStyle );
+
+ int m_fixForHeight;
+ bool m_drawComplexControl : 1;
+ KComboBox *m_paintedCombo; //!< fake combo used only to pass it as 'this' for QStyle
+ //!< (because styles use \<static_cast\>)
+};
+
+#endif
diff --git a/kexi/widget/utils/kexicontextmenuutils.cpp b/kexi/widget/utils/kexicontextmenuutils.cpp
new file mode 100644
index 000000000..727cef6f0
--- /dev/null
+++ b/kexi/widget/utils/kexicontextmenuutils.cpp
@@ -0,0 +1,283 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexicontextmenuutils.h"
+
+#include <kactioncollection.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kfiledialog.h>
+#include <kimageio.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+
+#include <qfiledialog.h>
+#include <qapplication.h>
+
+#ifdef Q_WS_WIN
+#include <win32_utils.h>
+#include <krecentdirs.h>
+#endif
+
+//! @internal
+class KexiImageContextMenu::Private
+{
+public:
+ Private(QWidget *parent)
+ : actionCollection(parent)
+ {
+ }
+
+ KActionCollection actionCollection;
+ KAction *insertFromFileAction, *saveAsAction, *cutAction, *copyAction, *pasteAction,
+ *deleteAction
+#ifdef KEXI_NO_UNFINISHED
+ , *propertiesAction
+#endif
+ ;
+};
+
+//------------
+
+KexiImageContextMenu::KexiImageContextMenu(QWidget* parent)
+ : KPopupMenu(parent)
+ , d( new Private(this) )
+{
+ setName("KexiImageContextMenu");
+ insertTitle(QString::null);
+
+ d->insertFromFileAction = new KAction(i18n("Insert From &File..."), SmallIconSet("fileopen"), 0,
+ this, SLOT(insertFromFile()), &d->actionCollection, "insert");
+ d->insertFromFileAction->plug(this);
+ d->saveAsAction = KStdAction::saveAs(this, SLOT(saveAs()), &d->actionCollection);
+// d->saveAsAction->setText(i18n("&Save &As..."));
+ d->saveAsAction->plug(this);
+ insertSeparator();
+ d->cutAction = KStdAction::cut(this, SLOT(cut()), &d->actionCollection);
+ d->cutAction->plug(this);
+ d->copyAction = KStdAction::copy(this, SLOT(copy()), &d->actionCollection);
+ d->copyAction->plug(this);
+ d->pasteAction = KStdAction::paste(this, SLOT(paste()), &d->actionCollection);
+ d->pasteAction->plug(this);
+ d->deleteAction = new KAction(i18n("&Clear"), SmallIconSet("editdelete"), 0,
+ this, SLOT(clear()), &d->actionCollection, "delete");
+ d->deleteAction->plug(this);
+#ifdef KEXI_NO_UNFINISHED
+ d->propertiesAction = 0;
+#else
+ insertSeparator();
+ d->propertiesAction = new KAction(i18n("Properties"), 0, 0,
+ this, SLOT(showProperties()), &d->actionCollection, "properties");
+ d->propertiesAction->plug(this);
+#endif
+ connect(this, SIGNAL(aboutToShow()), this, SLOT(updateActionsAvailability()));
+}
+
+KexiImageContextMenu::~KexiImageContextMenu()
+{
+ delete d;
+}
+
+void KexiImageContextMenu::insertFromFile()
+{
+// QWidget *focusWidget = qApp->focusWidget();
+#ifdef Q_WS_WIN
+ QString recentDir;
+ QString fileName = QFileDialog::getOpenFileName(
+ KFileDialog::getStartURL(":LastVisitedImagePath", recentDir).path(),
+ convertKFileDialogFilterToQFileDialogFilter(KImageIO::pattern(KImageIO::Reading)),
+ this, 0, i18n("Insert Image From File"));
+ KURL url;
+ if (!fileName.isEmpty())
+ url.setPath( fileName );
+#else
+ KURL url( KFileDialog::getImageOpenURL(
+ ":LastVisitedImagePath", this, i18n("Insert Image From File")) );
+// QString fileName = url.isLocalFile() ? url.path() : url.prettyURL();
+
+ //! @todo download the file if remote, then set fileName properly
+#endif
+ if (!url.isValid()) {
+ //focus the app again because to avoid annoying the user with unfocused main window
+ if (qApp->mainWidget()) {
+ //focusWidget->raise();
+ //focusWidget->setFocus();
+ qApp->mainWidget()->raise();
+ }
+ return;
+ }
+ kexipluginsdbg << "fname=" << url.prettyURL() << endl;
+
+#ifdef Q_WS_WIN
+ //save last visited path
+// KURL url(fileName);
+ if (url.isLocalFile())
+ KRecentDirs::add(":LastVisitedImagePath", url.directory());
+#endif
+
+ emit insertFromFileRequested(url);
+ if (qApp->mainWidget()) {
+// focusWidget->raise();
+// focusWidget->setFocus();
+ qApp->mainWidget()->raise();
+ }
+}
+
+void KexiImageContextMenu::saveAs()
+{
+ QString origFilename, fileExtension;
+ bool dataIsEmpty = false;
+ emit aboutToSaveAsRequested(origFilename, fileExtension, dataIsEmpty);
+
+ if (dataIsEmpty) {
+ kdWarning() << "KexiImageContextMenu::saveAs(): no data!" << endl;
+ return;
+ }
+ if (!origFilename.isEmpty())
+ origFilename = QString("/") + origFilename;
+
+ if (fileExtension.isEmpty()) {
+ // PNG data is the default
+ fileExtension = "png";
+ }
+
+#ifdef Q_WS_WIN
+ QString recentDir;
+ QString fileName = QFileDialog::getSaveFileName(
+ KFileDialog::getStartURL(":LastVisitedImagePath", recentDir).path() + origFilename,
+ convertKFileDialogFilterToQFileDialogFilter(KImageIO::pattern(KImageIO::Writing)),
+ this, 0, i18n("Save Image to File"));
+#else
+ //! @todo add originalFileName! (requires access to KRecentDirs)
+ QString fileName = KFileDialog::getSaveFileName(
+ ":LastVisitedImagePath", KImageIO::pattern(KImageIO::Writing), this, i18n("Save Image to File"));
+#endif
+ if (fileName.isEmpty())
+ return;
+
+ if (QFileInfo(fileName).extension().isEmpty())
+ fileName += (QString(".")+fileExtension);
+ kdDebug() << fileName << endl;
+ KURL url;
+ url.setPath( fileName );
+
+#ifdef Q_WS_WIN
+ //save last visited path
+ if (url.isLocalFile())
+ KRecentDirs::add(":LastVisitedImagePath", url.directory());
+#endif
+
+ QFile f(fileName);
+ if (f.exists() && KMessageBox::Yes != KMessageBox::warningYesNo(this,
+ "<qt>"+i18n("File \"%1\" already exists."
+ "<p>Do you want to replace it with a new one?")
+ .arg(QDir::convertSeparators(fileName))+"</qt>",0,
+ KGuiItem(i18n("&Replace")), KGuiItem(i18n("&Don't Replace"))))
+ {
+ return;
+ }
+
+//! @todo use KURL?
+ emit saveAsRequested(fileName);
+}
+
+void KexiImageContextMenu::cut()
+{
+ emit cutRequested();
+}
+
+void KexiImageContextMenu::copy()
+{
+ emit copyRequested();
+}
+
+void KexiImageContextMenu::paste()
+{
+ emit pasteRequested();
+}
+
+void KexiImageContextMenu::clear()
+{
+ emit clearRequested();
+}
+
+void KexiImageContextMenu::showProperties()
+{
+ emit showPropertiesRequested();
+}
+
+void KexiImageContextMenu::updateActionsAvailability()
+{
+ bool valueIsNull = true;
+ bool valueIsReadOnly = true;
+ emit updateActionsAvailabilityRequested(valueIsNull, valueIsReadOnly);
+
+ d->insertFromFileAction->setEnabled( !valueIsReadOnly );
+ d->saveAsAction->setEnabled( !valueIsNull );
+ d->cutAction->setEnabled( !valueIsNull && !valueIsReadOnly );
+ d->copyAction->setEnabled( !valueIsNull );
+ d->pasteAction->setEnabled( !valueIsReadOnly );
+ d->deleteAction->setEnabled( !valueIsNull && !valueIsReadOnly );
+ if (d->propertiesAction)
+ d->propertiesAction->setEnabled( !valueIsNull );
+}
+
+KActionCollection* KexiImageContextMenu::actionCollection() const
+{
+ return &d->actionCollection;
+}
+
+//static
+bool KexiImageContextMenu::updateTitle(QPopupMenu *menu, const QString& title, const QString& iconName)
+{
+ return KexiContextMenuUtils::updateTitle(menu, title, i18n("Image"), iconName);
+}
+
+// -------------------------------------------
+
+//static
+bool KexiContextMenuUtils::updateTitle(QPopupMenu *menu, const QString& objectName,
+ const QString& objectTypeName, const QString& iconName)
+{
+ if (!menu || objectName.isEmpty() || objectTypeName.isEmpty())
+ return false;
+ const int id = menu->idAt(0);
+ QMenuItem *item = menu->findItem(id);
+ if (!item)
+ return false;
+ KPopupTitle *title = dynamic_cast<KPopupTitle *>(item->widget());
+ if (!title)
+ return false;
+
+/*! @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool]
+ (see doc/dev/settings.txt) */
+ QString realTitle( i18n("Object name : Object type", "%1 : %2")
+ .arg( objectName[0].upper() + objectName.mid(1) )
+ .arg( objectTypeName ));
+
+ if (iconName.isEmpty())
+ title->setTitle(realTitle);
+ else {
+ QPixmap pixmap(SmallIcon( iconName ));
+ title->setTitle(realTitle, &pixmap);
+ }
+ return true;
+}
+
+#include "kexicontextmenuutils.moc"
diff --git a/kexi/widget/utils/kexicontextmenuutils.h b/kexi/widget/utils/kexicontextmenuutils.h
new file mode 100644
index 000000000..95258e964
--- /dev/null
+++ b/kexi/widget/utils/kexicontextmenuutils.h
@@ -0,0 +1,112 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiContextMenuUtils_H
+#define KexiContextMenuUtils_H
+
+#include <kexidb/queryschema.h>
+#include <kpopupmenu.h>
+#include <kurl.h>
+
+class KActionCollection;
+class KexiDataItemInterface;
+
+//! @short A set of helpers for updating popup menu titles
+/*! The functions set meaningful titles like "Emploee : Image".
+*/
+class KEXIGUIUTILS_EXPORT KexiContextMenuUtils
+{
+ public:
+ /*! Updates title for context menu.
+ \return true if the title has been updated. */
+ static bool updateTitle(QPopupMenu *menu, const QString& objectName,
+ const QString& objectTypeName, const QString& iconName);
+};
+
+//! @short A context menu used for images within form and table views
+/*! Used in KexiDBImageBox and KexiBlobTableEdit.
+ Contains actions like insert, save, copy, paste, clear.
+
+ Signals like insertFromFileRequested() are all connected to
+ handlers in KexiDBImageBox and KexiBlobTableEdit so these objects can
+ respond on requests for data handling.
+*/
+class KEXIGUIUTILS_EXPORT KexiImageContextMenu : public KPopupMenu
+{
+ Q_OBJECT
+
+ public:
+ KexiImageContextMenu(QWidget *parent);
+ virtual ~KexiImageContextMenu();
+
+ KActionCollection* actionCollection() const;
+
+ /*! Updates title for context menu.
+ Used in KexiDBWidgetContextMenuExtender::createTitle(QPopupMenu *menu) and KexiDBImageBox.
+ \return true if the title has been updated. */
+ static bool updateTitle(QPopupMenu *menu, const QString& title, const QString& iconName = QString::null);
+
+ public slots:
+ void updateActionsAvailability();
+
+ virtual void insertFromFile();
+ virtual void saveAs();
+ virtual void cut();
+ virtual void copy();
+ virtual void paste();
+ virtual void clear();
+ virtual void showProperties();
+
+ signals:
+ //! Emitted when actions availability should be performed. Just connect this signal
+ //! to a slot and set \a valueIsNull and \a valueIsReadOnly.
+ void updateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly);
+
+ /*! Emitted before "insertFromFile" action was requested. */
+ void insertFromFileRequested(const KURL &url);
+
+ /*! Emitted before "saveAs" action was requested.
+ You should fill \a origFilename, \a fileExtension and \a dataIsEmpty values.
+ If \a dataIsEmpty is false, saving will be cancelled. */
+ void aboutToSaveAsRequested(QString& origFilename, QString& fileExtension, bool& dataIsEmpty);
+
+ //! Emitted when "saveAs" action was requested
+ void saveAsRequested(const QString& fileName);
+
+ //! Emitted when "cut" action was requested
+ void cutRequested();
+
+ //! Emitted when "copy" action was requested
+ void copyRequested();
+
+ //! Emitted when "paste" action was requested
+ void pasteRequested();
+
+ //! Emitted when "clear" action was requested
+ void clearRequested();
+
+ //! Emitted when "showProperties" action was requested
+ void showPropertiesRequested();
+
+ protected:
+ class Private;
+ Private *d;
+};
+
+#endif
diff --git a/kexi/widget/utils/kexidatetimeformatter.cpp b/kexi/widget/utils/kexidatetimeformatter.cpp
new file mode 100644
index 000000000..d8f642ca0
--- /dev/null
+++ b/kexi/widget/utils/kexidatetimeformatter.cpp
@@ -0,0 +1,367 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidatetimeformatter.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kdatepicker.h>
+#include <kdatetbl.h>
+#include <klineedit.h>
+#include <kpopupmenu.h>
+#include <kdatewidget.h>
+
+KexiDateFormatter::KexiDateFormatter()
+{
+ // use "short date" format system settings
+//! @todo allow to override the format using column property and/or global app settings
+ QString df( KGlobal::locale()->dateFormatShort() );
+ if (df.length()>2)
+ m_separator = df.mid(2,1);
+ else
+ m_separator = "-";
+ const int separatorLen = m_separator.length();
+ QString yearMask("9999");
+ QString yearDateFormat("yyyy"),
+ monthDateFormat("MM"),
+ dayDateFormat("dd"); //for setting up m_dateFormat
+ bool ok = df.length()>=8;
+ int yearpos, monthpos, daypos; //result of df.find()
+ if (ok) {//look at % variables
+//! @todo more variables are possible here, see void KLocale::setDateFormatShort() docs
+//! http://developer.kde.org/documentation/library/3.5-api/kdelibs-apidocs/kdecore/html/classKLocale.html#a59
+ yearpos = df.find("%y", 0, false); //&y or %y
+ m_longYear = !(yearpos>=0 && df.mid(yearpos+1, 1)=="y");
+ if (!m_longYear) {
+ yearMask = "99";
+ yearDateFormat = "yy";
+ }
+ monthpos = df.find("%m", 0, true); //%m or %n
+ m_monthWithLeadingZero = true;
+ if (monthpos<0) {
+ monthpos = df.find("%n", 0, false);
+ m_monthWithLeadingZero = false;
+ monthDateFormat = "M";
+ }
+ daypos = df.find("%d", 0, true);//%d or %e
+ m_dayWithLeadingZero = true;
+ if (daypos<0) {
+ daypos = df.find("%e", 0, false);
+ m_dayWithLeadingZero = false;
+ dayDateFormat = "d";
+ }
+ ok = (yearpos>=0 && monthpos>=0 && daypos>=0);
+ }
+ m_order = QDateEdit::YMD; //default
+ if (ok) {
+ if (yearpos<monthpos && monthpos<daypos) {
+ //will be set in "default: YMD"
+ }
+ else if (yearpos<daypos && daypos<monthpos) {
+ m_order = QDateEdit::YDM;
+//! @todo use QRegExp (to replace %Y by %1, etc.) instead of hardcoded "%1%299%399"
+//! because df may contain also other characters
+ m_inputMask = QString("%1%299%399").arg(yearMask).arg(m_separator).arg(m_separator);
+ m_qtFormat = yearDateFormat+m_separator+dayDateFormat+m_separator+monthDateFormat;
+ m_yearpos = 0;
+ m_daypos = yearMask.length()+separatorLen;
+ m_monthpos = m_daypos+2+separatorLen;
+ }
+ else if (daypos<monthpos && monthpos<yearpos) {
+ m_order = QDateEdit::DMY;
+ m_inputMask = QString("99%199%2%3").arg(m_separator).arg(m_separator).arg(yearMask);
+ m_qtFormat = dayDateFormat+m_separator+monthDateFormat+m_separator+yearDateFormat;
+ m_daypos = 0;
+ m_monthpos = 2+separatorLen;
+ m_yearpos = m_monthpos+2+separatorLen;
+ }
+ else if (monthpos<daypos && daypos<yearpos) {
+ m_order = QDateEdit::MDY;
+ m_inputMask = QString("99%199%2%3").arg(m_separator).arg(m_separator).arg(yearMask);
+ m_qtFormat = monthDateFormat+m_separator+dayDateFormat+m_separator+yearDateFormat;
+ m_monthpos = 0;
+ m_daypos = 2+separatorLen;
+ m_yearpos = m_daypos+2+separatorLen;
+ }
+ else
+ ok = false;
+ }
+ if (!ok || m_order == QDateEdit::YMD) {//default: YMD
+ m_inputMask = QString("%1%299%399").arg(yearMask).arg(m_separator).arg(m_separator);
+ m_qtFormat = yearDateFormat+m_separator+monthDateFormat+m_separator+dayDateFormat;
+ m_yearpos = 0;
+ m_monthpos = yearMask.length()+separatorLen;
+ m_daypos = m_monthpos+2+separatorLen;
+ }
+ m_inputMask += ";_";
+}
+
+KexiDateFormatter::~KexiDateFormatter()
+{
+}
+
+QDate KexiDateFormatter::stringToDate( const QString& str ) const
+{
+ bool ok = true;
+ int year = str.mid(m_yearpos, m_longYear ? 4 : 2).toInt(&ok);
+ if (!ok)
+ return QDate();
+ if (year < 30) {//2000..2029
+ year = 2000 + year;
+ }
+ else if (year < 100) {//1930..1999
+ year = 1900 + year;
+ }
+
+ int month = str.mid(m_monthpos, 2).toInt(&ok);
+ if (!ok)
+ return QDate();
+
+ int day = str.mid(m_daypos, 2).toInt(&ok);
+ if (!ok)
+ return QDate();
+
+ QDate date(year, month, day);
+ if (!date.isValid())
+ return QDate();
+ return date;
+}
+
+QVariant KexiDateFormatter::stringToVariant( const QString& str ) const
+{
+ if (isEmpty(str))
+ return QVariant();
+ const QDate date( stringToDate( str ) );
+ if (date.isValid())
+ return date;
+ return QVariant();
+}
+
+bool KexiDateFormatter::isEmpty( const QString& str ) const
+{
+ QString s(str);
+ return s.replace(m_separator,"").stripWhiteSpace().isEmpty();
+}
+
+QString KexiDateFormatter::dateToString( const QDate& date ) const
+{
+ return date.toString(m_qtFormat);
+}
+
+//------------------------------------------------
+
+KexiTimeFormatter::KexiTimeFormatter()
+: m_hmsRegExp( new QRegExp("(\\d*):(\\d*):(\\d*).*( am| pm){,1}", false/*!CS*/) )
+ , m_hmRegExp( new QRegExp("(\\d*):(\\d*).*( am| pm){,1}", false/*!CS*/) )
+{
+ QString tf( KGlobal::locale()->timeFormat() );
+ //m_hourpos, m_minpos, m_secpos; are result of tf.find()
+ QString hourVariable, minVariable, secVariable;
+
+ //detect position of HOUR section: find %H or %k or %I or %l
+ m_24h = true;
+ m_hoursWithLeadingZero = true;
+ m_hourpos = tf.find("%H", 0, true);
+ if (m_hourpos>=0) {
+ m_24h = true;
+ m_hoursWithLeadingZero = true;
+ }
+ else {
+ m_hourpos = tf.find("%k", 0, true);
+ if (m_hourpos>=0) {
+ m_24h = true;
+ m_hoursWithLeadingZero = false;
+ }
+ else {
+ m_hourpos = tf.find("%I", 0, true);
+ if (m_hourpos>=0) {
+ m_24h = false;
+ m_hoursWithLeadingZero = true;
+ }
+ else {
+ m_hourpos = tf.find("%l", 0, true);
+ if (m_hourpos>=0) {
+ m_24h = false;
+ m_hoursWithLeadingZero = false;
+ }
+ }
+ }
+ }
+ m_minpos = tf.find("%M", 0, true);
+ m_secpos = tf.find("%S", 0, true); //can be -1
+ m_ampmpos = tf.find("%p", 0, true); //can be -1
+
+ if (m_hourpos<0 || m_minpos<0) {
+ //set default: hr and min are needed, sec are optional
+ tf = "%H:%M:%S";
+ m_24h = true;
+ m_hoursWithLeadingZero = false;
+ m_hourpos = 0;
+ m_minpos = 3;
+ m_secpos = m_minpos + 3;
+ m_ampmpos = -1;
+ }
+ hourVariable = tf.mid(m_hourpos, 2);
+
+ m_inputMask = tf;
+// m_inputMask.replace( hourVariable, "00" );
+// m_inputMask.replace( "%M", "00" );
+// m_inputMask.replace( "%S", "00" ); //optional
+ m_inputMask.replace( hourVariable, "99" );
+ m_inputMask.replace( "%M", "99" );
+ m_inputMask.replace( "%S", "00" ); //optional
+ m_inputMask.replace( "%p", "AA" ); //am or pm
+ m_inputMask += ";_";
+
+ m_outputFormat = tf;
+}
+
+KexiTimeFormatter::~KexiTimeFormatter()
+{
+ delete m_hmsRegExp;
+ delete m_hmRegExp;
+}
+
+QTime KexiTimeFormatter::stringToTime( const QString& str ) const
+{
+ int hour, min, sec;
+ bool pm = false;
+
+ bool tryWithoutSeconds = true;
+ if (m_secpos>=0) {
+ if (-1 != m_hmsRegExp->search(str)) {
+ hour = m_hmsRegExp->cap(1).toInt();
+ min = m_hmsRegExp->cap(2).toInt();
+ sec = m_hmsRegExp->cap(3).toInt();
+ if (m_ampmpos >= 0 && m_hmsRegExp->numCaptures()>3)
+ pm = m_hmsRegExp->cap(4).stripWhiteSpace().lower()=="pm";
+ tryWithoutSeconds = false;
+ }
+ }
+ if (tryWithoutSeconds) {
+ if (-1 == m_hmRegExp->search(str))
+ return QTime(99,0,0);
+ hour = m_hmRegExp->cap(1).toInt();
+ min = m_hmRegExp->cap(2).toInt();
+ sec = 0;
+ if (m_ampmpos >= 0 && m_hmRegExp->numCaptures()>2)
+ pm = m_hmsRegExp->cap(4).lower()=="pm";
+ }
+
+ if (pm && hour < 12)
+ hour += 12; //PM
+ return QTime(hour, min, sec);
+}
+
+QVariant KexiTimeFormatter::stringToVariant( const QString& str )
+{
+ if (isEmpty( str ))
+ return QVariant();
+ const QTime time( stringToTime( str ) );
+ if (time.isValid())
+ return time;
+ return QVariant();
+}
+
+bool KexiTimeFormatter::isEmpty( const QString& str ) const
+{
+ QString s(str);
+ return s.replace(':',"").stripWhiteSpace().isEmpty();
+}
+
+QString KexiTimeFormatter::timeToString( const QTime& time ) const
+{
+ if (!time.isValid())
+ return QString::null;
+
+ QString s(m_outputFormat);
+ if (m_24h) {
+ if (m_hoursWithLeadingZero)
+ s.replace( "%H", QString::fromLatin1(time.hour()<10 ? "0" : "") + QString::number(time.hour()) );
+ else
+ s.replace( "%k", QString::number(time.hour()) );
+ }
+ else {
+ int time12 = (time.hour()>12) ? (time.hour()-12) : time.hour();
+ if (m_hoursWithLeadingZero)
+ s.replace( "%I", QString::fromLatin1(time12<10 ? "0" : "") + QString::number(time12) );
+ else
+ s.replace( "%l", QString::number(time12) );
+ }
+ s.replace( "%M", QString::fromLatin1(time.minute()<10 ? "0" : "") + QString::number(time.minute()) );
+ if (m_secpos>=0)
+ s.replace( "%S", QString::fromLatin1(time.second()<10 ? "0" : "") + QString::number(time.second()) );
+ if (m_ampmpos>=0)
+ s.replace( "%p", KGlobal::locale()->translate( time.hour()>=12 ? "pm" : "am") );
+ return s;
+}
+
+//------------------------------------------------
+
+QString dateTimeInputMask(const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter)
+{
+ QString mask(dateFormatter.inputMask());
+ mask.truncate(dateFormatter.inputMask().length()-2);
+ return mask + " " + timeFormatter.inputMask();
+}
+
+QDateTime stringToDateTime(
+ const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter, const QString& str)
+{
+ QString s( str.stripWhiteSpace() );
+ const int timepos = s.find(" ");
+ const bool emptyTime = timepos >= 0 && timeFormatter.isEmpty(s.mid(timepos+1)); //.replace(':',"").stripWhiteSpace().isEmpty();
+ if (emptyTime)
+ s = s.left(timepos);
+ if (timepos>0 && !emptyTime) {
+ return QDateTime(
+ dateFormatter.stringToDate( s.left(timepos) ),
+ timeFormatter.stringToTime( s.mid(timepos+1) )
+ );
+ }
+ else {
+ return QDateTime(
+ dateFormatter.stringToDate( s ),
+ QTime(0,0,0)
+ );
+ }
+}
+
+bool dateTimeIsEmpty( const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter,
+ const QString& str )
+{
+ int timepos = str.find(" ");
+ const bool emptyTime = timepos >= 0 && timeFormatter.isEmpty(str.mid(timepos+1)); //s.mid(timepos+1).replace(':',"").stripWhiteSpace().isEmpty();
+ return (timepos >= 0 && dateFormatter.isEmpty(str.left(timepos)) //s.left(timepos).replace(m_dateFormatter.separator(), "").stripWhiteSpace().isEmpty()
+ && emptyTime);
+}
+
+bool dateTimeIsValid( const KexiDateFormatter& dateFormatter,
+ const KexiTimeFormatter& timeFormatter, const QString& str )
+{
+ int timepos = str.find(" ");
+ const bool emptyTime = timepos >= 0 && timeFormatter.isEmpty(str.mid(timepos+1)); //s.mid(timepos+1).replace(':',"").stripWhiteSpace().isEmpty();
+ if (timepos >= 0 && dateFormatter.isEmpty(str.left(timepos)) // s.left(timepos).replace(m_dateFormatter.separator(), "").stripWhiteSpace().isEmpty()
+ && emptyTime)
+ //empty date/time is valid
+ return true;
+ return timepos>=0 && dateFormatter.stringToDate( str.left(timepos) ).isValid()
+ && (emptyTime /*date without time is also valid*/ || timeFormatter.stringToTime( str.mid(timepos+1) ).isValid());
+}
diff --git a/kexi/widget/utils/kexidatetimeformatter.h b/kexi/widget/utils/kexidatetimeformatter.h
new file mode 100644
index 000000000..252bc535e
--- /dev/null
+++ b/kexi/widget/utils/kexidatetimeformatter.h
@@ -0,0 +1,165 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDATETIMEFORMATTER_H
+#define KEXIDATETIMEFORMATTER_H
+
+#include <qdatetimeedit.h>
+#include <qregexp.h>
+
+//! @short Date formatter used by KexiDateTableEdit and KexiDateTimeTableEdit
+class KEXIGUIUTILS_EXPORT KexiDateFormatter
+{
+ public:
+ //! Creates new formatter with KDE setting for "short date"
+ KexiDateFormatter();
+
+ //! Creates new formatter with given settings
+//! @todo KexiDateFormatter(... settings ...);
+
+ ~KexiDateFormatter();
+
+ //! Converts string \a str to date using predefined settings.
+ //! \return invalid date if the conversion is impossible
+ QDate stringToDate( const QString& str ) const;
+
+ /*! Converts string \a str to date using predefined settings
+ and returns QVariant containing the date value.
+ This method does the same as stringToDate() but if \a string
+ contains invalid date representation, e.g. contains only spaces
+ and separators, null QVariant() is returned. */
+ QVariant stringToVariant( const QString& str ) const;
+
+ //! Converts \a date to string using predefined settings.
+ //! \return null string if \a date is invalid
+ QString dateToString( const QDate& date ) const;
+
+ //! \return Input mask generated using the formatter settings.
+ //! Can be used in QLineEdit::setInputMask().
+ QString inputMask() const { return m_inputMask; }
+
+ //! \return separator for this date format, a single character like "-" or "/"
+ QString separator() const { return m_separator; }
+
+ //! \return true if \a str contains only spaces
+ //! and separators according to the date format.
+ bool isEmpty( const QString& str ) const;
+
+ protected:
+ //! Input mask generated using the formatter settings. Can be used in QLineEdit::setInputMask().
+ QString m_inputMask;
+
+ //! Order of date sections
+ QDateEdit::Order m_order;
+
+ //! 4 or 2 digits
+ bool m_longYear;
+
+ bool m_monthWithLeadingZero, m_dayWithLeadingZero;
+
+ //! Date format used in dateToString()
+ QString m_qtFormat;
+
+ //! Used in stringToDate() to convert string back to QDate
+ int m_yearpos, m_monthpos, m_daypos;
+
+ QString m_separator;
+};
+
+/*! @short Time formatter used by KexiTimeTableEdit and KexiDateTimeTableEdit
+ Following time formats are allowed: HH:MM:SS (24h), HH:MM (24h), HH:MM AM/PM (12h)
+ Separator MUST be ":" */
+class KEXIGUIUTILS_EXPORT KexiTimeFormatter
+{
+ public:
+ //! Creates new formatter with KDE setting for time
+ KexiTimeFormatter();
+
+ //! Creates new formatter with given settings
+//! @todo KexiDateFormatter(... settings ...);
+
+ ~KexiTimeFormatter();
+
+ //! converts string \a str to time using predefined settings
+ //! \return invalid time if the conversion is impossible
+ QTime stringToTime( const QString& str ) const;
+
+ /*! Converts string \a str to time using predefined settings
+ and returns QVariant containing the time value.
+ This method does the same as stringToTime() but if \a string
+ contains invalid time representation, e.g. contains only spaces
+ and separators, null QVariant() is returned. */
+ QVariant stringToVariant( const QString& str );
+
+ //! converts \a time to string using predefined settings
+ //! \return null string if \a time is invalid
+ QString timeToString( const QTime& time ) const;
+
+ //! \return Input mask generated using the formatter settings.
+ //! Can be used in QLineEdit::setInputMask().
+ QString inputMask() const { return m_inputMask; }
+
+ //! \return true if \a str contains only spaces
+ //! and separators according to the time format.
+ bool isEmpty( const QString& str ) const;
+
+ protected:
+ //! Input mask generated using the formatter settings. Can be used in QLineEdit::setInputMask().
+ QString m_inputMask;
+
+// //! Order of date sections
+// QDateEdit::Order m_order;
+
+ //! 12 or 12h
+ bool m_24h;
+
+ bool m_hoursWithLeadingZero;
+
+ //! Time format used in timeToString(). Notation from KLocale::setTimeFormat() is used.
+ QString m_outputFormat;
+
+ //! Used in stringToTime() to convert string back to QTime
+ int m_hourpos, m_minpos, m_secpos, m_ampmpos;
+
+ QRegExp *m_hmsRegExp, *m_hmRegExp;
+};
+
+//! \return a date/time input mask using date and time formatter.
+//! Date is separated from time by one space character.
+KEXIGUIUTILS_EXPORT QString dateTimeInputMask(
+ const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter);
+
+/*! \return a QDateTime value converted from string using \a dateFormatter and \a timeFormatter.
+ A single space between date and time is assumed.
+ Invalid value is returned when \a str contains no valid date or \a str contains invalid time.
+ Value with time equal 00:00:00 is returned if \a str contains empty time part. */
+KEXIGUIUTILS_EXPORT QDateTime stringToDateTime(
+ const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter, const QString& str);
+
+/*! \return true if \a str contains only spaces and separators according to formats provided by
+ \a dateFormatter and \a timeFormatter. */
+KEXIGUIUTILS_EXPORT bool dateTimeIsEmpty( const KexiDateFormatter& dateFormatter,
+ const KexiTimeFormatter& timeFormatter, const QString& str );
+
+/*! \return true if \a str gives valid date/time value according to formats provided by
+ \a dateFormatter and \a timeFormatter. */
+KEXIGUIUTILS_EXPORT bool dateTimeIsValid( const KexiDateFormatter& dateFormatter,
+ const KexiTimeFormatter& timeFormatter, const QString& str );
+
+#endif
diff --git a/kexi/widget/utils/kexidisplayutils.cpp b/kexi/widget/utils/kexidisplayutils.cpp
new file mode 100644
index 000000000..c7d238b1d
--- /dev/null
+++ b/kexi/widget/utils/kexidisplayutils.cpp
@@ -0,0 +1,172 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidisplayutils.h"
+
+#include <qpixmap.h>
+#include <qpainter.h>
+#include <qimage.h>
+#include <qwidget.h>
+
+#include <klocale.h>
+#include <kstaticdeleter.h>
+
+// a color for displaying default values or autonumbers
+#define SPECIAL_TEXT_COLOR Qt::blue
+
+static KStaticDeleter<QPixmap> KexiDisplayUtils_autonum_deleter;
+QPixmap* KexiDisplayUtils_autonum = 0;
+
+static const unsigned int autonumber_png_len = 245;
+static const unsigned char autonumber_png_data[] = {
+ 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,
+ 0x44,0x52,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x0d,0x08,0x06,0x00,0x00,
+ 0x00,0x7f,0xf5,0x94,0x3b,0x00,0x00,0x00,0x06,0x62,0x4b,0x47,0x44,0x00,
+ 0xff,0x00,0xff,0x00,0xff,0xa0,0xbd,0xa7,0x93,0x00,0x00,0x00,0x09,0x70,
+ 0x48,0x59,0x73,0x00,0x00,0x0b,0x11,0x00,0x00,0x0b,0x11,0x01,0x7f,0x64,
+ 0x5f,0x91,0x00,0x00,0x00,0x07,0x74,0x49,0x4d,0x45,0x07,0xd4,0x08,0x14,
+ 0x0c,0x09,0x11,0x18,0x18,0x1d,0x4f,0x00,0x00,0x00,0x82,0x49,0x44,0x41,
+ 0x54,0x78,0x9c,0x8d,0x91,0x41,0x0e,0x03,0x31,0x08,0x03,0x87,0xbe,0x2e,
+ 0x1c,0xb3,0xff,0xbf,0xf6,0x1d,0xee,0x81,0xa0,0x05,0xaa,0x55,0x6b,0x29,
+ 0x92,0x03,0x06,0x59,0x06,0x49,0x48,0x02,0xa4,0xe4,0xf1,0x5f,0x1b,0xa4,
+ 0x78,0x6b,0xc3,0xc2,0x24,0x61,0x86,0x00,0x24,0x8c,0x83,0x53,0x33,0xe9,
+ 0xe6,0xaf,0x29,0x4a,0x48,0x29,0xf4,0x0d,0xbc,0xc1,0xe1,0xc9,0x46,0xb5,
+ 0x72,0xfa,0xcf,0xe2,0x2a,0x4c,0x71,0xf3,0x5c,0x2d,0xd5,0x5a,0xc0,0xcd,
+ 0x62,0xea,0x6f,0xf4,0x88,0x86,0x95,0xf0,0x4a,0xf2,0xee,0x6b,0xf8,0x1e,
+ 0x03,0x55,0xf8,0x73,0xf3,0x28,0x7e,0x6d,0x6e,0x69,0xc4,0xc6,0xfb,0x52,
+ 0x23,0x8d,0x3c,0x56,0x5e,0xd0,0x2f,0x40,0xd1,0xf4,0x6b,0xc4,0xd5,0xf8,
+ 0x07,0x69,0x14,0xc6,0x69,0x9a,0x12,0x79,0x9a,0x00,0x00,0x00,0x00,0x49,
+ 0x45,0x4e,0x44,0xae,0x42,0x60,0x82
+};
+
+/* Generated by qembed */
+#include <qcstring.h>
+#include <qdict.h>
+static struct Embed {
+ unsigned int size;
+ const unsigned char *data;
+ const char *name;
+} embed_vec[] = {
+ { 245, autonumber_png_data, "autonumber.png" },
+ { 0, 0, 0 }
+};
+
+QPixmap* getPix(int id)
+{
+// QByteArray ba;
+// ba.setRawData( (char*)embed_vec[id].data, embed_vec[id].size );
+ QPixmap *pix = new QPixmap();
+ pix->loadFromData( embed_vec[id].data, embed_vec[id].size );
+ return pix;
+}
+
+static void initDisplayUtilsImages()
+{
+ if (!KexiDisplayUtils_autonum) {
+/*! @warning not reentrant! */
+ KexiDisplayUtils_autonum_deleter.setObject( KexiDisplayUtils_autonum, getPix(0) );
+ }
+}
+
+//-----------------
+
+KexiDisplayUtils::DisplayParameters::DisplayParameters()
+{
+}
+
+KexiDisplayUtils::DisplayParameters::DisplayParameters(QWidget *w)
+{
+ textColor = w->palette().active().foreground();
+ selectedTextColor = w->palette().active().highlightedText();
+ font = w->font();
+}
+
+void KexiDisplayUtils::initDisplayForAutonumberSign(DisplayParameters& par, QWidget *widget)
+{
+ initDisplayUtilsImages();
+
+ par.textColor = SPECIAL_TEXT_COLOR;
+ par.selectedTextColor = SPECIAL_TEXT_COLOR; //hmm, unused anyway
+ par.font = widget->font();
+ par.font.setItalic(true);
+ QFontMetrics fm(par.font);
+ par.textWidth = fm.width(i18n("(autonumber)"));
+ par.textHeight = fm.height();
+}
+
+void KexiDisplayUtils::initDisplayForDefaultValue(DisplayParameters& par, QWidget *widget)
+{
+ par.textColor = SPECIAL_TEXT_COLOR;
+ par.selectedTextColor = widget->palette().active().highlightedText();
+ par.font = widget->font();
+ par.font.setItalic(true);
+}
+
+void KexiDisplayUtils::paintAutonumberSign(const DisplayParameters& par, QPainter* painter,
+ int x, int y, int width, int height, int align, bool overrideColor)
+{
+ painter->save();
+
+ painter->setFont(par.font);
+ if (!overrideColor)
+ painter->setPen(par.textColor);
+
+// int text_x = x;
+ if (!(align & Qt::AlignVertical_Mask))
+ align |= Qt::AlignVCenter;
+ if (!(align & Qt::AlignHorizontal_Mask))
+ align |= Qt::AlignLeft;
+
+ int y_pixmap_pos = 0;
+ if (align & Qt::AlignVCenter) {
+ y_pixmap_pos = QMAX(0, y+1 + (height - KexiDisplayUtils_autonum->height())/2);
+ }
+ else if (align & Qt::AlignTop) {
+ y_pixmap_pos = y + QMAX(0, (par.textHeight - KexiDisplayUtils_autonum->height())/2);
+ }
+ else if (align & Qt::AlignBottom) {
+ y_pixmap_pos = y+1 + height - KexiDisplayUtils_autonum->height()
+ - QMAX(0, (par.textHeight - KexiDisplayUtils_autonum->height())/2);
+ }
+
+ if (align & (Qt::AlignLeft | Qt::AlignJustify)) {
+// text_x = x + KexiDisplayUtils_autonum->width() + 2;
+ if (!overrideColor) {
+ painter->drawPixmap( x, y_pixmap_pos, *KexiDisplayUtils_autonum );
+ x += (KexiDisplayUtils_autonum->width() + 4);
+ }
+ }
+ else if (align & Qt::AlignRight) {
+ if (!overrideColor) {
+ painter->drawPixmap( x + width - par.textWidth - KexiDisplayUtils_autonum->width() - 4,
+ y_pixmap_pos, *KexiDisplayUtils_autonum );
+ }
+ }
+ else if (align & Qt::AlignCenter) {
+ //! @todo
+ if (!overrideColor)
+ painter->drawPixmap( x + (width - par.textWidth)/2 - KexiDisplayUtils_autonum->width() - 4,
+ y_pixmap_pos, *KexiDisplayUtils_autonum );
+ }
+
+ painter->drawText(x, y, width, height, align, i18n("(autonumber)"));
+
+ painter->restore();
+}
+
diff --git a/kexi/widget/utils/kexidisplayutils.h b/kexi/widget/utils/kexidisplayutils.h
new file mode 100644
index 000000000..8790b662b
--- /dev/null
+++ b/kexi/widget/utils/kexidisplayutils.h
@@ -0,0 +1,57 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIDISPUTILS_H
+#define KEXIDISPUTILS_H
+
+#include <qfont.h>
+#include <qcolor.h>
+class QWidget;
+
+//! \brief A set of utilities related to displaying common elements in Kexi, like e.g. (autonumber) sign
+class KEXIGUIUTILS_EXPORT KexiDisplayUtils
+{
+ public:
+ //! Stores set of display parameters used in utility functions
+ class KEXIGUIUTILS_EXPORT DisplayParameters
+ {
+ public:
+ //! Creates uninitialized parameters
+ DisplayParameters();
+
+ //! Copies properties from \a w.
+ DisplayParameters(QWidget *w);
+
+ QColor textColor, selectedTextColor;
+ QFont font;
+ int textWidth, textHeight; //!< used for "(autonumber)" text only
+ };
+
+ //! Initializes display parameters for autonumber sign
+ static void initDisplayForAutonumberSign(DisplayParameters& par, QWidget *widget);
+
+ //! Paints autonumber sign using \a par parameters
+ static void paintAutonumberSign(const DisplayParameters& par, QPainter* painter,
+ int x, int y, int width, int height, int align, bool overrideColor = false);
+
+ //! Initializes display parameters for default value
+ static void initDisplayForDefaultValue(DisplayParameters& par, QWidget *widget);
+};
+
+#endif
diff --git a/kexi/widget/utils/kexidropdownbutton.cpp b/kexi/widget/utils/kexidropdownbutton.cpp
new file mode 100644
index 000000000..a17e5cfb1
--- /dev/null
+++ b/kexi/widget/utils/kexidropdownbutton.cpp
@@ -0,0 +1,82 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexidropdownbutton.h"
+
+#include <kpopupmenu.h>
+#include <kdebug.h>
+
+#include <qstyle.h>
+#include <qapplication.h>
+
+KexiDropDownButton::KexiDropDownButton(QWidget *parent)
+ : QToolButton(parent, "KexiDBImageBox::Button")
+{
+ setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+//! @todo get this from a KStyle
+// setFixedWidth(QMAX(18, qApp->globalStrut().width()));
+ int fixedWidth;
+ //hack
+ if (qstricmp(style().name(),"thinkeramik")==0)
+ fixedWidth = 18; //typical width as in "windows" style
+ else
+ fixedWidth = style().querySubControlMetrics( QStyle::CC_ComboBox,
+ this, QStyle::SC_ComboBoxArrow ).width();
+ setFixedWidth( fixedWidth );
+ setPopupDelay(10/*ms*/);
+}
+
+KexiDropDownButton::~KexiDropDownButton()
+{
+}
+
+void KexiDropDownButton::drawButton( QPainter *p )
+{
+ QToolButton::drawButton(p);
+ QStyle::SFlags arrowFlags = QStyle::Style_Default;
+ if (isDown() || state()==On)
+ arrowFlags |= QStyle::Style_Down;
+ if (isEnabled())
+ arrowFlags |= QStyle::Style_Enabled;
+ style().drawPrimitive(QStyle::PE_ArrowDown, p,
+ QRect((width()-7)/2, height()-9, 7, 7), colorGroup(),
+ arrowFlags, QStyleOption() );
+}
+
+QSize KexiDropDownButton::sizeHint () const
+{
+ return QSize( fontMetrics().maxWidth() + 2*2, fontMetrics().height()*2 + 2*2 );
+}
+
+void KexiDropDownButton::keyPressEvent( QKeyEvent * e )
+{
+ const int k = e->key();
+ const bool dropDown = (e->state() == Qt::NoButton && (k==Qt::Key_Space || k==Qt::Key_Enter || k==Qt::Key_Return || k==Qt::Key_F2 || k==Qt::Key_F4))
+ || (e->state() == Qt::AltButton && k==Qt::Key_Down);
+ if (dropDown) {
+ e->accept();
+ animateClick();
+ QMouseEvent me( QEvent::MouseButtonPress, QPoint(2,2), Qt::LeftButton, Qt::NoButton );
+ QApplication::sendEvent( this, &me );
+ return;
+ }
+ QToolButton::keyPressEvent(e);
+}
+
+#include "kexidropdownbutton.moc"
diff --git a/kexi/widget/utils/kexidropdownbutton.h b/kexi/widget/utils/kexidropdownbutton.h
new file mode 100644
index 000000000..fccbd409a
--- /dev/null
+++ b/kexi/widget/utils/kexidropdownbutton.h
@@ -0,0 +1,45 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KexiDropDownButton_H
+#define KexiDropDownButton_H
+
+#include <qtoolbutton.h>
+#include <qguardedptr.h>
+
+//! @short A button for drop-down "Image" menu
+/*! Used in KexiDBImageBox and KexiBlobTableEdit.
+ Additionally, the button reacts on pressing space, return, enter,
+ F2, F4 and alt+down buttons. */
+class KEXIGUIUTILS_EXPORT KexiDropDownButton : public QToolButton
+{
+ Q_OBJECT
+
+ public:
+ KexiDropDownButton(QWidget *parent);
+ virtual ~KexiDropDownButton();
+
+ virtual void drawButton( QPainter *p );
+
+ virtual QSize sizeHint () const;
+
+ virtual void keyPressEvent ( QKeyEvent * e );
+};
+
+#endif
diff --git a/kexi/widget/utils/kexiflowlayout.cpp b/kexi/widget/utils/kexiflowlayout.cpp
new file mode 100644
index 000000000..b8a8601e1
--- /dev/null
+++ b/kexi/widget/utils/kexiflowlayout.cpp
@@ -0,0 +1,452 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexiflowlayout.h"
+
+#include <kdebug.h>
+
+/// Iterator class
+
+class KexiFlowLayoutIterator : public QGLayoutIterator
+{
+ public:
+ KexiFlowLayoutIterator( QPtrList<QLayoutItem> *list )
+ : m_idx(0), m_list( list )
+ {}
+ uint count() const;
+ QLayoutItem *current();
+ QLayoutItem *next();
+ QLayoutItem *takeCurrent();
+
+ private:
+ int m_idx;
+ QPtrList<QLayoutItem> *m_list;
+};
+
+uint
+KexiFlowLayoutIterator::count() const
+{
+ return m_list->count();
+}
+
+QLayoutItem *
+KexiFlowLayoutIterator::current()
+{
+ return (m_idx < (int)count()) ? m_list->at(m_idx) : 0;
+}
+
+QLayoutItem *
+KexiFlowLayoutIterator::next()
+{
+ m_idx++;
+ return current();
+}
+
+QLayoutItem *
+KexiFlowLayoutIterator::takeCurrent()
+{
+ return (m_idx < (int)count()) ? m_list->take(m_idx) : 0;
+}
+
+//// The layout itself
+
+KexiFlowLayout::KexiFlowLayout(QWidget *parent, int border, int space, const char *name)
+ : QLayout(parent, border, space, name)
+{
+ m_orientation = Horizontal;
+ m_justify = false;
+ m_cached_width = 0;
+}
+
+KexiFlowLayout::KexiFlowLayout(QLayout* parent, int space, const char *name)
+ : QLayout( parent, space, name )
+{
+ m_orientation = Horizontal;
+ m_justify = false;
+ m_cached_width = 0;
+}
+
+KexiFlowLayout::KexiFlowLayout(int space, const char *name)
+ : QLayout(space, name)
+ {
+ m_orientation = Horizontal;
+ m_justify = false;
+ m_cached_width = 0;
+ }
+
+KexiFlowLayout::~KexiFlowLayout()
+{
+ deleteAllItems();
+}
+
+void
+KexiFlowLayout::addItem(QLayoutItem *item)
+{
+ m_list.append(item);
+}
+
+void
+KexiFlowLayout::addSpacing(int size)
+{
+ if (m_orientation == Horizontal)
+ addItem( new QSpacerItem( size, 0, QSizePolicy::Fixed, QSizePolicy::Minimum ) );
+ else
+ addItem( new QSpacerItem( 0, size, QSizePolicy::Minimum, QSizePolicy::Fixed ) );
+}
+
+QLayoutIterator
+KexiFlowLayout::iterator()
+{
+ return QLayoutIterator( new KexiFlowLayoutIterator(&m_list) );
+}
+
+QPtrList<QWidget>*
+KexiFlowLayout::widgetList() const
+{
+ QPtrList<QWidget> *list = new QPtrList<QWidget>();
+ for (QPtrListIterator<QLayoutItem> it(m_list); it.current(); ++it) {
+ if(it.current()->widget())
+ list->append(it.current()->widget());
+ }
+ return list;
+}
+
+void
+KexiFlowLayout::invalidate()
+{
+ QLayout::invalidate();
+ m_cached_sizeHint = QSize();
+ m_cached_minSize = QSize();
+ m_cached_width = 0;
+}
+
+bool
+KexiFlowLayout::isEmpty()
+{
+ return m_list.isEmpty();
+}
+
+bool
+KexiFlowLayout::hasHeightForWidth() const
+{
+ return (m_orientation == Horizontal);
+}
+
+int
+KexiFlowLayout::heightForWidth(int w) const
+{
+ if(m_cached_width != w) {
+ // workaround to allow this method to stay 'const'
+ KexiFlowLayout *mthis = (KexiFlowLayout*)this;
+ int h = mthis->simulateLayout( QRect(0,0,w,0) );
+ mthis->m_cached_hfw = h;
+ mthis->m_cached_width = w;
+ return h;
+ }
+ return m_cached_hfw;
+}
+
+QSize
+KexiFlowLayout::sizeHint() const
+{
+ if(m_cached_sizeHint.isEmpty()) {
+ KexiFlowLayout *mthis = (KexiFlowLayout*)this;
+ QRect r = QRect(0, 0, 2000, 2000);
+ mthis->simulateLayout(r);
+ }
+ return m_cached_sizeHint;
+}
+
+QSize
+KexiFlowLayout::minimumSize() const
+{
+//js: do we really need to simulate layout here?
+// I commented this out because it was impossible to stretch layout conveniently.
+// Now, minimum size is computed automatically based on item's minimumSize...
+#if 0
+ if(m_cached_minSize.isEmpty()) {
+ KexiFlowLayout *mthis = (KexiFlowLayout*)this;
+ QRect r = QRect(0, 0, 2000, 2000);
+ mthis->simulateLayout(r);
+ }
+#endif
+ return m_cached_minSize;
+}
+
+QSizePolicy::ExpandData
+KexiFlowLayout::expanding() const
+{
+ if(m_orientation == Vertical)
+ return QSizePolicy::Vertically;
+ else
+ return QSizePolicy::Horizontally;
+}
+
+void
+KexiFlowLayout::setGeometry(const QRect &r)
+{
+ QLayout::setGeometry(r);
+ if(m_orientation == Horizontal)
+ doHorizontalLayout(r);
+ else
+ doVerticalLayout(r);
+}
+
+int
+KexiFlowLayout::simulateLayout(const QRect &r)
+{
+ if(m_orientation == Horizontal)
+ return doHorizontalLayout(r, true);
+ else
+ return doVerticalLayout(r, true);
+}
+
+int
+KexiFlowLayout::doHorizontalLayout(const QRect &r, bool testOnly)
+{
+ int x = r.x();
+ int y = r.y();
+ int h = 0; // height of this line
+ int availableSpace = r.width() + spacing();
+ int expandingWidgets=0; // number of widgets in the line with QSizePolicy == Expanding
+ QPtrListIterator<QLayoutItem> it(m_list);
+ QPtrList<QLayoutItem> currentLine;
+ QLayoutItem *o;
+ QSize minSize, sizeHint(20, 20);
+ int minSizeHeight = 0 - spacing();
+
+ while ( (o = it.current()) != 0 ) {
+ if(o->isEmpty()) { /// do not consider hidden widgets
+ ++it;
+ continue;
+ }
+
+// kdDebug() << "- doHorizontalLayout(): " << o->widget()->className() << " " << o->widget()->name() << endl;
+ QSize oSizeHint = o->sizeHint(); // we cache these ones because it can take a while to get it (eg for child layouts)
+ if ((x + oSizeHint.width()) > r.right() && h > 0) {
+ // do the layout of current line
+ QPtrListIterator<QLayoutItem> it2(currentLine);
+ QLayoutItem *item;
+ int wx = r.x();
+ int sizeHintWidth = 0 -spacing(), minSizeWidth=0 - spacing(), lineMinHeight=0;
+ while( (item = it2.current()) != 0 ) {
+ QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take
+ QSize itemMinSize = item->minimumSize(); // a while to get them
+ QSize s;
+ if(m_justify) {
+ if(expandingWidgets != 0) {
+ if(item->expanding() == QSizePolicy::Horizontally || item->expanding() == QSizePolicy::BothDirections)
+ s = QSize( QMIN(itemSizeHint.width() + availableSpace / expandingWidgets
+ , r.width()), itemSizeHint.height() );
+ else
+ s = QSize( QMIN(itemSizeHint.width(), r.width()), itemSizeHint.height() );
+ }
+ else
+ s = QSize( QMIN(itemSizeHint.width() + availableSpace / (int)currentLine.count()
+ , r.width()), itemSizeHint.height() );
+ }
+ else
+ s = QSize ( QMIN(itemSizeHint.width(), r.width()), itemSizeHint.height() );
+ if(!testOnly)
+ item->setGeometry( QRect(QPoint(wx, y), s) );
+ wx = wx + s.width() + spacing();
+ minSizeWidth = minSizeWidth + spacing() + itemMinSize.width();
+ sizeHintWidth = sizeHintWidth + spacing() + itemSizeHint.width();
+ lineMinHeight = QMAX( lineMinHeight, itemMinSize.height() );
+ ++it2;
+ }
+ sizeHint = sizeHint.expandedTo( QSize(sizeHintWidth, 0) );
+ minSize = minSize.expandedTo( QSize(minSizeWidth, 0) );
+ minSizeHeight = minSizeHeight + spacing() + lineMinHeight;
+ // start a new line
+ y = y + spacing() + h;
+ h = 0;
+ x = r.x();
+ currentLine.clear();
+ expandingWidgets = 0;
+ availableSpace = r.width() + spacing();
+ }
+
+ x = x + spacing() + oSizeHint.width();
+ h = QMAX( h, oSizeHint.height() );
+ currentLine.append(o);
+ if(o->expanding() == QSizePolicy::Horizontally || o->expanding() == QSizePolicy::BothDirections)
+ ++expandingWidgets;
+ availableSpace = QMAX(0, availableSpace - spacing() - oSizeHint.width());
+ ++it;
+ }
+
+ // don't forget to layout the last line
+ QPtrListIterator<QLayoutItem> it2(currentLine);
+ QLayoutItem *item;
+ int wx = r.x();
+ int sizeHintWidth = 0 -spacing(), minSizeWidth=0 - spacing(), lineMinHeight=0;
+ while( (item = it2.current()) != 0 ) {
+ QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take
+ QSize itemMinSize = item->minimumSize(); // a while to get them
+ QSize s;
+ if(m_justify) {
+ if(expandingWidgets != 0) {
+ if(item->expanding() == QSizePolicy::Horizontally || item->expanding() == QSizePolicy::BothDirections)
+ s = QSize( QMIN(itemSizeHint.width() + availableSpace / expandingWidgets
+ , r.width()), itemSizeHint.height() );
+ else
+ s = QSize( QMIN(itemSizeHint.width(), r.width()), itemSizeHint.height() );
+ }
+ else
+ s = QSize( QMIN(itemSizeHint.width() + availableSpace / (int)currentLine.count()
+ , r.width()), itemSizeHint.height() );
+ }
+ else
+ s = QSize ( QMIN(itemSizeHint.width(), r.width()), itemSizeHint.height() );
+ if(!testOnly)
+ item->setGeometry( QRect(QPoint(wx, y), s) );
+ wx = wx + s.width() + spacing();
+ minSizeWidth = minSizeWidth + spacing() + itemMinSize.width();
+ sizeHintWidth = sizeHintWidth + spacing() + itemSizeHint.width();
+ lineMinHeight = QMAX( lineMinHeight, itemMinSize.height() );
+ ++it2;
+ }
+ sizeHint = sizeHint.expandedTo( QSize(sizeHintWidth, y + spacing() + h) );
+ minSizeHeight = minSizeHeight + spacing() + lineMinHeight;
+ minSize = minSize.expandedTo( QSize(minSizeWidth, minSizeHeight) );
+
+ // store sizeHint() and minimumSize()
+ m_cached_sizeHint = sizeHint + QSize(2* margin(), 2*margin());
+ m_cached_minSize = minSize + QSize(2* margin() , 2*margin());
+ // return our height
+ return y + h - r.y();
+}
+
+int
+KexiFlowLayout::doVerticalLayout(const QRect &r, bool testOnly)
+{
+ int x = r.x();
+ int y = r.y();
+ int w = 0; // width of this line
+ int availableSpace = r.height() + spacing();
+ int expandingWidgets=0; // number of widgets in the line with QSizePolicy == Expanding
+ QPtrListIterator<QLayoutItem> it(m_list);
+ QPtrList<QLayoutItem> currentLine;
+ QLayoutItem *o;
+ QSize minSize, sizeHint(20, 20);
+ int minSizeWidth = 0 - spacing();
+
+ while ( (o = it.current()) != 0 ) {
+ if(o->isEmpty()) { /// do not consider hidden widgets
+ ++it;
+ continue;
+ }
+
+ QSize oSizeHint = o->sizeHint(); // we cache these ones because it can take a while to get it (eg for child layouts)
+ if (y + oSizeHint.height() > r.bottom() && w > 0) {
+ // do the layout of current line
+ QPtrListIterator<QLayoutItem> it2(currentLine);
+ QLayoutItem *item;
+ int wy = r.y();
+ int sizeHintHeight = 0 - spacing(), minSizeHeight = 0 - spacing(), colMinWidth=0;
+ while( (item = it2.current()) != 0 ) {
+ QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take
+ QSize itemMinSize = item->minimumSize(); // a while to get them
+ QSize s;
+ if(m_justify) {
+ if(expandingWidgets != 0) {
+ if(item->expanding() == QSizePolicy::Vertically || item->expanding() == QSizePolicy::BothDirections)
+ s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height() + availableSpace / expandingWidgets
+ , r.height()) );
+ else
+ s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height(), r.height()) );
+ }
+ else
+ s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height() + availableSpace / (int)currentLine.count()
+ , r.height()) );
+ }
+ else
+ s = QSize ( itemSizeHint.width(), QMIN(itemSizeHint.height(), r.height()) );
+ if(!testOnly)
+ item->setGeometry( QRect(QPoint(x, wy), s) );
+ wy = wy + s.height() + spacing();
+ minSizeHeight = minSizeHeight + spacing() + itemMinSize.height();
+ sizeHintHeight = sizeHintHeight + spacing() + itemSizeHint.height();
+ colMinWidth = QMAX( colMinWidth, itemMinSize.width() );
+ ++it2;
+ }
+ sizeHint = sizeHint.expandedTo( QSize(0, sizeHintHeight) );
+ minSize = minSize.expandedTo( QSize(0, minSizeHeight) );
+ minSizeWidth = minSizeWidth + spacing() + colMinWidth;
+ // start a new column
+ x = x + spacing() + w;
+ w = 0;
+ y = r.y();
+ currentLine.clear();
+ expandingWidgets = 0;
+ availableSpace = r.height() + spacing();
+ }
+
+ y = y + spacing() + oSizeHint.height();
+ w = QMAX( w, oSizeHint.width() );
+ currentLine.append(o);
+ if(o->expanding() == QSizePolicy::Vertically || o->expanding() == QSizePolicy::BothDirections)
+ ++expandingWidgets;
+ availableSpace = QMAX(0, availableSpace - spacing() - oSizeHint.height());
+ ++it;
+ }
+
+ // don't forget to layout the last line
+ QPtrListIterator<QLayoutItem> it2(currentLine);
+ QLayoutItem *item;
+ int wy = r.y();
+ int sizeHintHeight = 0 - spacing(), minSizeHeight = 0 - spacing(), colMinWidth=0;
+ while( (item = it2.current()) != 0 ) {
+ QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take
+ QSize itemMinSize = item->minimumSize(); // a while to get them
+ QSize s;
+ if(m_justify) {
+ if(expandingWidgets != 0) {
+ if(item->expanding() == QSizePolicy::Vertically || item->expanding() == QSizePolicy::BothDirections)
+ s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height() + availableSpace / expandingWidgets
+ , r.height()) );
+ else
+ s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height(), r.height()) );
+ }
+ else
+ s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height() + availableSpace / (int)currentLine.count()
+ , r.height()) );
+ }
+ else
+ s = QSize ( itemSizeHint.width(), QMIN(itemSizeHint.height(), r.height()) );
+ if(!testOnly)
+ item->setGeometry( QRect(QPoint(x, wy), s) );
+ wy = wy + s.height() + spacing();
+ minSizeHeight = minSizeHeight + spacing() + itemMinSize.height();
+ sizeHintHeight = sizeHintHeight + spacing() + itemSizeHint.height();
+ colMinWidth = QMAX( colMinWidth, itemMinSize.width() );
+ ++it2;
+ }
+ sizeHint = sizeHint.expandedTo( QSize( x + spacing() + w, sizeHintHeight) );
+ minSizeWidth = minSizeWidth + spacing() + colMinWidth;
+ minSize = minSize.expandedTo( QSize(minSizeWidth, minSizeHeight) );
+
+ // store sizeHint() and minimumSize()
+ m_cached_sizeHint = sizeHint + QSize(2* margin(), 2*margin());
+ m_cached_minSize = minSize + QSize(2* margin(), 2*margin());
+ // return our width
+ return x + w - r.x();
+}
+
diff --git a/kexi/widget/utils/kexiflowlayout.h b/kexi/widget/utils/kexiflowlayout.h
new file mode 100644
index 000000000..173ddad57
--- /dev/null
+++ b/kexi/widget/utils/kexiflowlayout.h
@@ -0,0 +1,79 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIFLOWLAYOUT_H
+#define KEXIFLOWLAYOUT_H
+
+#include <qlayout.h>
+#include <qptrlist.h>
+
+//! @short a special "flow" layout
+class KEXIGUIUTILS_EXPORT KexiFlowLayout : public QLayout
+{
+ public:
+ KexiFlowLayout(QWidget *parent, int border=0, int space=-1, const char *name=0);
+ KexiFlowLayout(QLayout* parent, int space=-1, const char *name=0);
+ KexiFlowLayout(int space=-1, const char *name=0);
+
+ ~KexiFlowLayout();
+
+ /*! \return the widgets in the order of the layout,
+ ie as it is stored in m_list. You must delete the list after using it. */
+ QPtrList<QWidget>* widgetList() const;
+
+ /*! Sets layout's orientation to \a orientation. Default orientation is Vertical. */
+ void setOrientation(Orientation orientation) { m_orientation = orientation; }
+
+ /*! \return layout's orientation. */
+ Qt::Orientation orientation() const { return m_orientation; }
+
+ void setJustified(bool justify) { m_justify = justify; }
+ bool isJustified() const { return m_justify; }
+
+ virtual void addItem(QLayoutItem *item);
+ virtual void addSpacing(int size);
+ virtual QLayoutIterator iterator();
+ virtual void invalidate();
+
+ virtual bool hasHeightForWidth() const;
+ virtual int heightForWidth(int width) const;
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSize() const;
+ virtual QSizePolicy::ExpandData expanding() const;
+
+ virtual bool isEmpty();
+
+ protected:
+ virtual void setGeometry(const QRect&);
+ int simulateLayout(const QRect &r);
+ int doHorizontalLayout(const QRect&, bool testonly = false);
+ int doVerticalLayout(const QRect&, bool testonly = false);
+
+ private:
+ QPtrList<QLayoutItem> m_list;
+ int m_cached_width;
+ int m_cached_hfw;
+ bool m_justify;
+ Orientation m_orientation;
+ QSize m_cached_sizeHint;
+ QSize m_cached_minSize;
+};
+
+#endif
+
diff --git a/kexi/widget/utils/kexigradientwidget.cpp b/kexi/widget/utils/kexigradientwidget.cpp
new file mode 100644
index 000000000..0411318de
--- /dev/null
+++ b/kexi/widget/utils/kexigradientwidget.cpp
@@ -0,0 +1,358 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qapplication.h>
+#include <qbitmap.h>
+#include <qimage.h>
+#include <qobjectlist.h>
+#include <qpainter.h>
+#include <qstyle.h>
+
+#include <kimageeffect.h>
+#include <kpixmap.h>
+
+#include "kexigradientwidget.h"
+
+KexiGradientWidget::KexiGradientWidget( QWidget *parent, const char *name, WFlags f )
+ : QWidget( parent, name, f ), p_displayMode( NoGradient ),
+ p_gradientType( VerticalGradient ),
+ p_color1( Qt::white ), p_color2( Qt::blue ), p_currentChild( 0 ),
+ p_opacity( 0.5 ), p_cacheDirty( true )
+{
+ p_customBackgroundWidgets.setAutoDelete( false );
+ p_knownWidgets.setAutoDelete( false );
+
+ p_backgroundColor = QWidget::paletteBackgroundColor();
+
+ connect ( &p_rebuildDelayTimer, SIGNAL( timeout() ), this, SLOT( setCacheDirty() ) );
+
+ installEventFilter( this );
+}
+
+KexiGradientWidget::~KexiGradientWidget()
+{
+}
+
+bool KexiGradientWidget::isValidChildWidget( QObject* child ) {
+ const QWidget* wgt = dynamic_cast<QWidget*>( child );
+
+ if ( wgt == 0L )
+ return false;
+
+ if ( wgt->inherits( "QScrollView" ) )
+ return false;
+ if ( wgt->inherits( "QComboBox" ) )
+ return false;
+ if ( wgt->inherits( "QLineEdit" ) )
+ return false;
+ if ( wgt->inherits( "KexiDBForm" ) )
+ return false;
+
+ return true;
+}
+
+void KexiGradientWidget::buildChildrenList( WidgetList& list, QWidget* p ) {
+ QObjectList* objects = p->queryList( "QWidget", 0, false, false );
+
+ for ( QObjectList::Iterator it = objects->begin(); it != objects->end(); ++it ) {
+ if ( isValidChildWidget( ( *it ) ) == false )
+ continue;
+ list.append( dynamic_cast<QWidget*>( ( *it ) ) );
+ buildChildrenList( list, dynamic_cast<QWidget*>( ( *it ) ) );
+ }
+
+ delete objects;
+}
+
+void KexiGradientWidget::rebuildCache( void ) {
+ WidgetList childWidgetList;
+ buildChildrenList( childWidgetList, this );
+
+ /**
+ Disable the effect and behave like a normal QWidget.
+ */
+ if ( p_displayMode == NoGradient ) {
+// if ( p_backgroundPixmap.isNull() ) {
+ //unsetPalette();
+ //} else {
+ QWidget::setPaletteBackgroundPixmap( p_backgroundPixmap );
+ //}
+ QWidget::setPaletteBackgroundColor( p_backgroundColor );
+
+ for ( WidgetList::Iterator it = childWidgetList.begin();
+ it != childWidgetList.end(); ++it ) {
+
+ if ( p_customBackgroundWidgets.contains( ( *it ) ) == false ) {
+ ( *it )->unsetPalette();
+ }
+ }
+ /**
+ The cache is now in a current state.
+ */
+ p_cacheDirty = false;
+ return;
+ }
+
+ KPixmap tempPixmap;
+ QImage gradientImage;
+ QImage bgImage;
+
+ /**
+ Draw the gradient
+ */
+ gradientImage = KImageEffect::gradient( size(), p_color1, p_color2,
+ (KImageEffect::GradientType)p_gradientType );
+
+ /**
+ Draw the widget-background in a pixmap and fade it with the gradient.
+ */
+ if ( p_displayMode == FadedGradient ) {
+ tempPixmap.resize( size() );
+ QPainter p( &tempPixmap, this );
+
+ if ( p_backgroundPixmap.isNull() ) {
+ /*
+ Need to unset the palette, otherwise the old gradient
+ will be used as a background, not the widget's default bg.
+ */
+ unsetPalette();
+ p.fillRect( 0, 0, width(), height(), palette().brush(
+ isEnabled() ? QPalette::Active : QPalette::Disabled,
+ QColorGroup::Background ) );
+ } else {
+ p.drawTiledPixmap( 0, 0, width(), height(), p_backgroundPixmap );
+ }
+
+ p.end();
+
+ bgImage = tempPixmap;
+
+ KImageEffect::blend( gradientImage, bgImage, (float)p_opacity );
+
+ tempPixmap.convertFromImage( bgImage );
+ } else if ( p_displayMode == SimpleGradient ) {
+ /**
+ Use the gradient as the final background-pixmap
+ if displaymode is set to SimpleGradient.
+ */
+ tempPixmap.convertFromImage( gradientImage );
+ }
+
+ /**
+ All children need to have our background set.
+ */
+ KPixmap partPixmap;
+ QRect area;
+ QWidget* childWidget = 0;
+ const QPoint topLeft( 0, 0 );
+
+ for ( WidgetList::Iterator it = childWidgetList.begin();
+ it != childWidgetList.end(); ++it ) {
+
+ childWidget = ( *it );
+
+ /**
+ Exclude widgets with a custom palette.
+ */
+ if ( p_customBackgroundWidgets.contains( childWidget ) ) {
+ continue;
+ }
+
+ partPixmap.resize( childWidget->size() );
+ /**
+ Get the part of the tempPixmap that is
+ under the current child-widget.
+ */
+ if ( childWidget->parent() == this ) {
+ area = childWidget->geometry();
+ } else {
+ area.setTopLeft( childWidget->mapTo( this,
+ childWidget->clipRegion().boundingRect().topLeft() ) );
+ area.setSize( childWidget->size() );
+ }
+ bitBlt( &partPixmap, topLeft, &tempPixmap, area );
+
+ p_currentChild = childWidget;
+ childWidget->setPaletteBackgroundPixmap( partPixmap );
+ }
+
+ QWidget::setPaletteBackgroundPixmap( tempPixmap );
+ /**
+ Unset the dirty-flag at the end of the method.
+ QWidget::setPaletteBackgroundPixmap() causes this
+ to get set to true again, so set it to false
+ right after setting the pixmap.
+ */
+ p_cacheDirty = false;
+}
+
+void KexiGradientWidget::paintEvent( QPaintEvent* e ) {
+ /**
+ Rebuild the background-pixmap if necessary.
+ */
+ if ( p_cacheDirty == true ) {
+ rebuildCache();
+ }
+
+ /**
+ Draw the widget as usual
+ */
+ QWidget::paintEvent( e );
+}
+
+bool KexiGradientWidget::eventFilter( QObject* object, QEvent* event ) {
+ QWidget* child = dynamic_cast<QWidget*>( object );
+
+ /**
+ Manage list of child-widgets.
+ */
+ if ( object == this ) {
+ if ( event->type() == QEvent::ChildInserted ) {
+ child = dynamic_cast<QWidget*>( dynamic_cast<QChildEvent*>( event )->child() );
+ if ( isValidChildWidget( child ) == false ) {
+ return false;
+ }
+ /**
+ Add the new child-widget to our list of known widgets.
+ */
+ p_knownWidgets.append( child );
+ /**
+ ... and install 'this' as the child's event-filter.
+ */
+ child->installEventFilter( this );
+ } else if ( event->type() == QEvent::ChildRemoved ) {
+ /**
+ Remove the child-widget from the list of known widgets.
+ */
+ p_knownWidgets.remove( dynamic_cast<QWidget*>( dynamic_cast<QChildEvent*>( event )->child() ) );
+ }
+ return false;
+ }
+
+ /**
+ Manage custombackground-list.
+ */
+ if ( event->type() == QEvent::PaletteChange ) {
+ /**
+ p_currentChild will be == 0L, when the user
+ sets it's palette manually.
+ In this case, it has to be added to the customBackground-list.
+ */
+ if ( p_currentChild == 0L && child != 0L ) {
+ if ( p_customBackgroundWidgets.contains( child ) == false ) {
+ p_customBackgroundWidgets.append( child );
+ return false;
+ }
+ }
+ /**
+ Check if the widget whose PaletteChange-event we handle
+ isn't the widget we set the background in rebuildCache().
+ */
+ if ( child != p_currentChild && child != 0L ) {
+ /**
+ Add the new child to the list of widgets, we don't set
+ the background ourselves if it isn't in the list.
+ */
+ if ( p_customBackgroundWidgets.contains( child ) == false ) {
+ if ( child->paletteBackgroundPixmap() != 0L ) {
+ p_customBackgroundWidgets.append( child );
+ }
+ } else {
+ /**
+ If the palette is now the default-palette again,
+ remove it from the "don't set background in rebuildCache()"-list
+ and rebuild the cache, so it again will get the gradient background.
+ */
+ if ( child->paletteBackgroundPixmap() == 0L ) {
+ p_customBackgroundWidgets.remove( child );
+ if ( p_displayMode != NoGradient ) {
+ p_cacheDirty = true;
+ }
+ }
+ }
+ }
+ p_currentChild = 0;
+ }
+
+ if ( event->type() == QEvent::Move ) {
+ if ( p_customBackgroundWidgets.contains( child ) == false ) {
+ updateChildBackground( child );
+ }
+ }
+ return false;
+}
+
+void KexiGradientWidget::updateChildBackground( QWidget* childWidget )
+{
+ KPixmap partPixmap;
+ KPixmap bgPixmap;
+ QRect area;
+ const QPoint topLeft( 0, 0 );
+
+ bgPixmap = paletteBackgroundPixmap() ? (*paletteBackgroundPixmap()) : QPixmap();
+ if ( bgPixmap.isNull() )
+ return;
+
+ /**
+ Exclude widgtes that don't have a parent.
+ This happens when children are removed
+ which are in the knownWidgets-list.
+ */
+ if ( childWidget->parent() == 0L )
+ return;
+
+ /**
+ Exclude widgets with a custom palette.
+ */
+ if ( p_customBackgroundWidgets.contains( childWidget ) ) {
+ return;
+ }
+
+ partPixmap.resize( childWidget->size() );
+ /**
+ Get the part of the tempPixmap that is
+ under the current child-widget.
+ */
+ if ( childWidget->parent() == this ) {
+ area = childWidget->geometry();
+ } else {
+ area.setTopLeft( childWidget->mapTo( this,
+ childWidget->clipRegion().boundingRect().topLeft() ) );
+ area.setSize( childWidget->size() );
+ }
+ bitBlt( &partPixmap, topLeft, &bgPixmap, area );
+
+ p_currentChild = childWidget;
+ childWidget->setPaletteBackgroundPixmap( partPixmap );
+}
+
+void KexiGradientWidget::setPaletteBackgroundColor( const QColor& color )
+{
+ p_backgroundColor = color;
+ if ( p_displayMode == NoGradient ) {
+ QWidget::setPaletteBackgroundColor( p_backgroundColor );
+ }
+}
+
+const QColor& KexiGradientWidget::paletteBackgroundColor() const
+{
+ return p_backgroundColor;
+}
+
+#include "kexigradientwidget.moc"
diff --git a/kexi/widget/utils/kexigradientwidget.h b/kexi/widget/utils/kexigradientwidget.h
new file mode 100644
index 000000000..0032e7b10
--- /dev/null
+++ b/kexi/widget/utils/kexigradientwidget.h
@@ -0,0 +1,247 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIGRADIENTWIDGET_H
+#define KEXIGRADIENTWIDGET_H
+
+#include <qtimer.h>
+#include <qwidget.h>
+
+#include <kimageeffect.h>
+#include <kpixmap.h>
+
+#define REBUILD_DELAY 100
+
+//! @short A simple widget that can use different types of gradients as the background.
+/*!
+ @author Christian Nitschkowski
+*/
+class KEXIGUIUTILS_EXPORT KexiGradientWidget : public QWidget {
+ typedef QPtrList<QWidget> WidgetList;
+
+ Q_OBJECT
+ Q_PROPERTY(DisplayMode displayMode READ displayMode WRITE setDisplayMode DESIGNABLE true)
+ Q_PROPERTY(GradientType gradientType READ gradientType WRITE setGradientType DESIGNABLE true)
+ Q_PROPERTY(QColor gradientColor1 READ gradientColor1 WRITE setGradientColor1 DESIGNABLE true)
+ Q_PROPERTY(QColor gradientColor2 READ gradientColor2 WRITE setGradientColor2 DESIGNABLE true)
+ Q_PROPERTY(double blendOpacity READ blendOpacity WRITE setBlendOpacity DESIGNABLE true)
+ Q_ENUMS( DisplayMode GradientType )
+
+ public:
+ /*!
+ Modes for displaying the gradient.
+ */
+ enum DisplayMode {
+ NoGradient, //!< No gradient at all. Will behave just like a QWidget
+ FadedGradient, //!< Gradient will be faded with the widgets background
+ SimpleGradient //!< Gradient will replace the usual widget background
+ };
+
+ /*!
+ Gradient type specification.
+ See GradientType for more details (part of the KDEFX library)
+ */
+ enum GradientType {
+ VerticalGradient = KImageEffect::VerticalGradient,
+ HorizontalGradient = KImageEffect::HorizontalGradient,
+ DiagonalGradient = KImageEffect::DiagonalGradient,
+ CrossDiagonalGradient = KImageEffect::CrossDiagonalGradient,
+ PyramidGradient = KImageEffect::PyramidGradient,
+ RectangleGradient = KImageEffect::RectangleGradient,
+ PipeCrossGradient = KImageEffect::PipeCrossGradient,
+ EllipticGradient = KImageEffect::EllipticGradient
+ };
+
+ KexiGradientWidget( QWidget *parent = 0, const char *name = 0, WFlags f = 0 );
+
+ virtual ~KexiGradientWidget();
+
+ virtual void setPaletteBackgroundPixmap( const QPixmap& pixmap ) {
+ p_backgroundPixmap = pixmap;
+ p_rebuildDelayTimer.start( REBUILD_DELAY, true );
+ }
+
+ virtual const QColor& paletteBackgroundColor() const;
+
+ /*!
+ Set the displaymode \a mode.
+ The widget will be updated automatically.
+ */
+ void setDisplayMode( DisplayMode mode ) {
+ p_displayMode = mode;
+ p_cacheDirty = true;
+ update();
+ }
+
+ /*!
+ Get the current displaymode.
+ */
+ DisplayMode displayMode() const {
+ return p_displayMode;
+ }
+
+ /*!
+ Set the gradient-type.
+ */
+ void setGradientType( GradientType type ) {
+ p_gradientType = type;
+ p_cacheDirty = true;
+ update();
+ }
+
+ /*!
+ Get the current gradient-type.
+ */
+ GradientType gradientType() const {
+ return p_gradientType;
+ }
+
+ /*! Set color #1 for the gradient-effect.
+ \a color is the new color. */
+ void setGradientColor1( const QColor& color ) {
+ p_color1 = color;
+ p_cacheDirty = true;
+ }
+
+ /*! Set color #2 for the gradient-effect.
+ \a color is the new color. */
+ void setGradientColor2( const QColor& color ) {
+ p_color2 = color;
+ p_cacheDirty = true;
+ }
+
+ /*!
+ Set both colors for the gradient.
+ \a color1 is the first color,
+ \a color2 the second.
+ */
+ void setGradientColors( const QColor& color1, const QColor& color2 ) {
+ p_color1 = color1;
+ p_color2 = color2;
+ p_cacheDirty = true;
+ }
+
+ /*! \return the color #1 used for the gradient. */
+ QColor gradientColor1() const { return p_color1; }
+
+ /*! \return the color #2 used for the gradient. */
+ QColor gradientColor2() const { return p_color2; }
+
+ /*!
+ Sets the opacity of the gradient when fading with background.
+ \a opacity has to be between 0.0 and 1.0.
+ */
+ void setBlendOpacity( double opacity ) {
+ p_opacity = opacity;
+ p_cacheDirty = true;
+ }
+
+ double blendOpacity() const { return p_opacity; }
+
+ public slots:
+ virtual void setPaletteBackgroundColor( const QColor& color );
+
+ protected:
+ virtual bool eventFilter( QObject* object, QEvent* event );
+ virtual void enabledChange( bool enabled ) {
+ p_cacheDirty = true;
+ QWidget::enabledChange( enabled );
+ }
+
+ virtual void paletteChange( const QPalette& pal ) {
+ p_cacheDirty = true;
+ QWidget::paletteChange( pal );
+ }
+
+ virtual void paintEvent( QPaintEvent* e );
+
+ virtual void resizeEvent( QResizeEvent* e ) {
+ p_rebuildDelayTimer.start( REBUILD_DELAY, true );
+ QWidget::resizeEvent( e );
+ }
+
+ virtual void styleChange( QStyle& style ) {
+ p_cacheDirty = true;
+ QWidget::styleChange( style );
+ }
+
+ private:
+ /*!
+ Builds a list of children of \a p.
+ Only widgets that work correctly with KexiGradientWidget
+ will be in this list.
+ The results will be stored in \a list.
+ The method recursively calls itself until all children of \a p
+ have been found and stored in the list.
+ */
+ static void buildChildrenList( WidgetList& list, QWidget* p );
+ /*!
+ \a return if the \a child is a widget that should
+ get a background set.
+ */
+ static bool isValidChildWidget( QObject* child );
+
+ /*!
+ Rebuilds the cache completely.
+ This is done automatically if necessary.
+ */
+ void rebuildCache();
+
+ /*!
+ Sets the background of \a childWidget.
+ This is necessary when the child has been moved.
+ For performance-reasons this is used only for Move-events.
+ The same code is used for PaletteChange-events, but in a
+ different location.
+ */
+ void updateChildBackground( QWidget* childWidget );
+
+ private:
+ WidgetList p_knownWidgets;
+ WidgetList p_customBackgroundWidgets;
+ DisplayMode p_displayMode;
+ GradientType p_gradientType;
+ KPixmap p_backgroundPixmap;
+ QColor p_color1;
+ QColor p_color2;
+ QTimer p_rebuildDelayTimer;
+ QWidget* p_currentChild;
+ double p_opacity;
+ bool p_cacheDirty;
+
+ QColor p_backgroundColor;
+
+ public slots:
+ /*!
+ The cache needs to be rebuild once the widget
+ is set up completely.
+ */
+ virtual void polish() {
+ QWidget::polish();
+ rebuildCache();
+ }
+
+ private slots:
+ void setCacheDirty() {
+ rebuildCache();
+ }
+
+ };
+
+#endif
diff --git a/kexi/widget/utils/kexirecordmarker.cpp b/kexi/widget/utils/kexirecordmarker.cpp
new file mode 100644
index 000000000..d434fcaf6
--- /dev/null
+++ b/kexi/widget/utils/kexirecordmarker.cpp
@@ -0,0 +1,307 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2002 Till Busch <till@bux.at>
+ Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexirecordmarker.h"
+
+#include <qcolor.h>
+#include <qstyle.h>
+#include <qpixmap.h>
+#include <qpainter.h>
+#include <qimage.h>
+#include <qapplication.h>
+
+#include <kdebug.h>
+#include <kstaticdeleter.h>
+
+#include <kexiutils/utils.h>
+
+static KStaticDeleter<QImage> KexiRecordMarker_pen_deleter, KexiRecordMarker_plus_deleter;
+QImage* KexiRecordMarker_pen = 0, *KexiRecordMarker_plus = 0;
+
+static const unsigned char img_pen_data[] = {
+ 0x00,0x00,0x03,0x30,0x78,0x9c,0xfb,0xff,0xff,0x3f,0xc3,0x7f,0x32,0x30,
+ 0x10,0x80,0x88,0xff,0xe4,0xe8,0x85,0xe9,0xc7,0xc6,0x26,0x55,0x3f,0x3a,
+ 0x4d,0x8e,0x7e,0x72,0xfc,0x32,0xd2,0xf5,0xa3,0xeb,0xa5,0xb5,0x7e,0x5c,
+ 0xe9,0x85,0x54,0xfb,0xb1,0xa5,0x1b,0x52,0xdc,0x0e,0x00,0xf2,0xea,0x0a,
+ 0x13
+};
+static const unsigned char img_plus_data[] = {
+ 0x00,0x00,0x01,0x90,0x78,0x9c,0xfb,0xff,0xff,0x3f,0xc3,0x7f,0x28,0x86,
+ 0x82,0xff,0x50,0x0c,0x17,0x47,0xc7,0xd4,0x50,0x87,0x05,0xc0,0xd5,0xe1,
+ 0x10,0xa7,0x16,0x26,0xca,0x5e,0x7c,0xfe,0x20,0x47,0x1d,0xb2,0x5a,0x5c,
+ 0xea,0x40,0x72,0x00,0x03,0x6e,0x74,0x8c
+};
+
+static struct EmbedImage {
+ int width, height, depth;
+ const unsigned char *data;
+ ulong compressed;
+ int numColors;
+ const QRgb *colorTable;
+ bool alpha;
+ const char *name;
+} embed_image[] = {
+ { 17, 12, 32, (const unsigned char*)img_pen_data, 57, 0, 0, true, "tableview_pen.png" },
+ { 10, 10, 32, (const unsigned char*)img_pen_data, 50, 0, 0, true, "tableview_plus.png" }
+};
+
+QImage* getImg(const unsigned char* data, int id)
+{
+ QByteArray baunzip;
+ baunzip = qUncompress( data, embed_image[id].compressed );
+ QImage *img = new QImage( QImage((uchar*)baunzip.data(),
+ embed_image[id].width, embed_image[id].height,
+ embed_image[id].depth, (QRgb*)embed_image[id].colorTable,
+ embed_image[id].numColors, QImage::BigEndian
+ ).copy() );
+ if ( embed_image[id].alpha )
+ img->setAlphaBuffer(true);
+ return img;
+}
+
+static void initRecordMarkerImages()
+{
+ if (!KexiRecordMarker_pen) {
+/*! @warning not reentrant! */
+ KexiRecordMarker_pen_deleter.setObject( KexiRecordMarker_pen, getImg(img_pen_data, 0) );
+ KexiRecordMarker_plus_deleter.setObject( KexiRecordMarker_plus, getImg(img_plus_data, 1) );
+ }
+}
+
+//----------------------------------------------------------------
+
+//! @internal
+class KexiRecordMarker::Private
+{
+public:
+ Private()
+ : rowHeight(1)
+ , offset(0)
+ , currentRow(-1)
+ , highlightedRow(-1)
+ , editRow(-1)
+ , rows(0)
+ , selectionBackgroundColor(qApp->palette().active().highlight())
+ , showInsertRow(true)
+ {
+ }
+ int rowHeight;
+ int offset;
+ int currentRow;
+ int highlightedRow;
+ int editRow;
+ int rows;
+ QColor selectionBackgroundColor;
+ bool showInsertRow : 1;
+};
+
+//----------------------------------------------------------------
+
+KexiRecordMarker::KexiRecordMarker(QWidget *parent, const char* name)
+ : QWidget(parent, name)
+ , d( new Private() )
+{
+ initRecordMarkerImages();
+}
+
+KexiRecordMarker::~KexiRecordMarker()
+{
+ delete d;
+}
+
+QImage* KexiRecordMarker::penImage()
+{
+ initRecordMarkerImages();
+ return KexiRecordMarker_pen;
+}
+
+QImage* KexiRecordMarker::plusImage()
+{
+ initRecordMarkerImages();
+ return KexiRecordMarker_plus;
+}
+
+void KexiRecordMarker::addLabel(bool upd)
+{
+ d->rows++;
+ if (upd)
+ update();
+}
+
+void KexiRecordMarker::removeLabel(bool upd)
+{
+ if (d->rows > 0) {
+ d->rows--;
+ if (upd)
+ update();
+ }
+}
+
+void KexiRecordMarker::addLabels(int num, bool upd)
+{
+ d->rows += num;
+ if (upd)
+ update();
+}
+
+void KexiRecordMarker::clear(bool upd)
+{
+ d->rows=0;
+ if (upd)
+ update();
+}
+
+int KexiRecordMarker::rows() const
+{
+ if (d->showInsertRow)
+ return d->rows +1;
+ else
+ return d->rows;
+}
+
+void KexiRecordMarker::paintEvent(QPaintEvent *e)
+{
+ QPainter p(this);
+ QRect r(e->rect());
+
+ int first = (r.top() + d->offset) / d->rowHeight;
+ int last = (r.bottom() + d->offset) / d->rowHeight;
+ if(last > (d->rows-1+(d->showInsertRow?1:0)))
+ last = d->rows-1+(d->showInsertRow?1:0);
+
+ QColorGroup selectedColorGroup(colorGroup());
+ selectedColorGroup.setColor( QColorGroup::Button,
+ KexiUtils::blendedColors( selectedColorGroup.color(QColorGroup::Background),
+ d->selectionBackgroundColor, 2, 1) );
+ selectedColorGroup.setColor( QColorGroup::Background,
+ selectedColorGroup.color(QColorGroup::Button) ); //set background color as well (e.g. for thinkeramik)
+ QColorGroup highlightedColorGroup(colorGroup());
+ highlightedColorGroup.setColor( QColorGroup::Button,
+ KexiUtils::blendedColors( highlightedColorGroup.color(QColorGroup::Background),
+ d->selectionBackgroundColor, 4, 1) );
+ highlightedColorGroup.setColor( QColorGroup::Background,
+ highlightedColorGroup.color(QColorGroup::Button) ); //set background color as well (e.g. for thinkeramik)
+ for(int i=first; i <= last; i++)
+ {
+ int y = ((d->rowHeight * i)-d->offset);
+ QRect r(0, y, width(), d->rowHeight);
+ p.drawRect(r);
+ style().drawPrimitive( QStyle::PE_HeaderSection, &p, r,
+ (d->currentRow == i) ? selectedColorGroup : (d->highlightedRow == i ? highlightedColorGroup : colorGroup()),
+ QStyle::Style_Raised | (isEnabled() ? QStyle::Style_Enabled : 0));
+ }
+ if (d->editRow!=-1 && d->editRow >= first && d->editRow <= (last/*+1 for insert row*/)) {
+ //show pen when editing
+ int ofs = d->rowHeight / 4;
+ int pos = ((d->rowHeight*(d->currentRow>=0?d->currentRow:0))-d->offset)-ofs/2+1;
+ p.drawImage((d->rowHeight-KexiRecordMarker_pen->width())/2,
+ (d->rowHeight-KexiRecordMarker_pen->height())/2+pos,*KexiRecordMarker_pen);
+ }
+ else if (d->currentRow >= first && d->currentRow <= last
+ && (!d->showInsertRow || (d->showInsertRow && d->currentRow < last)))/*don't display marker for 'insert' row*/
+ {
+ //show marker
+ p.setBrush(colorGroup().foreground());
+ p.setPen(QPen(Qt::NoPen));
+ QPointArray points(3);
+ int ofs = d->rowHeight / 4;
+ int ofs2 = (width() - ofs) / 2 -1;
+ int pos = ((d->rowHeight*d->currentRow)-d->offset)-ofs/2+2;
+ points.putPoints(0, 3, ofs2, pos+ofs, ofs2 + ofs, pos+ofs*2,
+ ofs2,pos+ofs*3);
+ p.drawPolygon(points);
+// kdDebug() <<"KexiRecordMarker::paintEvent(): POLYGON" << endl;
+/* int half = d->rowHeight / 2;
+ points.setPoints(3, 2, pos + 2, width() - 5, pos + half, 2, pos + (2 * half) - 2);*/
+ }
+ if (d->showInsertRow && d->editRow < last
+ && last == (d->rows-1+(d->showInsertRow?1:0)) ) {
+ //show plus sign
+ int pos = ((d->rowHeight*last)-d->offset)+(d->rowHeight-KexiRecordMarker_plus->height())/2;
+// p.drawImage((width()-d->plusImg.width())/2-1, pos, d->plusImg);
+ p.drawImage((width()-KexiRecordMarker_plus->width())/2, pos, *KexiRecordMarker_plus);
+ }
+}
+
+void KexiRecordMarker::setCurrentRow(int row)
+{
+ if (row == d->currentRow)
+ return;
+ int oldRow = d->currentRow;
+ d->currentRow=row;
+
+ if (oldRow != -1)
+ update(0,(d->rowHeight*(oldRow))-d->offset-1, width()+2, d->rowHeight+2);
+ if (d->currentRow != -1)
+ update(0,(d->rowHeight*d->currentRow)-d->offset-1, width()+2, d->rowHeight+2);
+}
+
+void KexiRecordMarker::setHighlightedRow(int row)
+{
+ if (row == d->highlightedRow)
+ return;
+ int oldRow = d->highlightedRow;
+ d->highlightedRow = row;
+
+ if (oldRow != -1)
+ update(0,(d->rowHeight*(oldRow))-d->offset-1, width()+2, d->rowHeight+2);
+ if (d->currentRow != -1)
+ update(0,(d->rowHeight*d->highlightedRow)-d->offset-1, width()+2, d->rowHeight+2);
+}
+
+void KexiRecordMarker::setOffset(int offset)
+{
+ int oldOff = d->offset;
+ d->offset = offset;
+ scroll(0,oldOff-offset);
+}
+
+void KexiRecordMarker::setCellHeight(int cellHeight)
+{
+ d->rowHeight = cellHeight;
+}
+
+void KexiRecordMarker::setEditRow(int row)
+{
+ d->editRow = row;
+//TODO: update only needed area!
+ update();
+}
+
+void KexiRecordMarker::showInsertRow(bool show)
+{
+ d->showInsertRow = show;
+//TODO: update only needed area!
+ update();
+}
+
+void KexiRecordMarker::setSelectionBackgroundColor(const QColor &color)
+{
+ d->selectionBackgroundColor = color;
+}
+
+QColor KexiRecordMarker::selectionBackgroundColor() const
+{
+ return d->selectionBackgroundColor;
+}
+
+#include "kexirecordmarker.moc"
diff --git a/kexi/widget/utils/kexirecordmarker.h b/kexi/widget/utils/kexirecordmarker.h
new file mode 100644
index 000000000..1408f83b9
--- /dev/null
+++ b/kexi/widget/utils/kexirecordmarker.h
@@ -0,0 +1,72 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
+ Copyright (C) 2002 Till Busch <till@bux.at>
+ Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
+ Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIRECORDMARKER_H
+#define KEXIRECORDMARKER_H
+
+#include <qwidget.h>
+
+class QImage;
+
+//! \brief Record marker, usually displayed at the left side of a table view or a continuous form.
+class KEXIGUIUTILS_EXPORT KexiRecordMarker : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ KexiRecordMarker(QWidget *parent, const char* name = 0);
+ ~KexiRecordMarker();
+
+ int rows() const;
+
+ static QImage* penImage();
+ static QImage* plusImage();
+
+ public slots:
+ void setOffset(int offset);
+ void setCellHeight(int cellHeight);
+ void setCurrentRow(int row);
+ void setHighlightedRow(int row);
+
+ /*! Sets 'edit row' flag for \a row. Use row==-1 if you want to switch the flag off. */
+ void setEditRow(int row);
+ void showInsertRow(bool show);
+
+ QColor selectionBackgroundColor() const;
+ void setSelectionBackgroundColor(const QColor &color);
+
+ void addLabel(bool upd=true);
+ void removeLabel(bool upd=true);
+
+ /*! Adds \a num labels */
+ void addLabels(int num, bool upd=true);
+
+ void clear(bool upd=true);
+
+ protected:
+ virtual void paintEvent(QPaintEvent *e);
+
+ class Private;
+ Private * const d;
+};
+
+#endif
diff --git a/kexi/widget/utils/kexirecordnavigator.cpp b/kexi/widget/utils/kexirecordnavigator.cpp
new file mode 100644
index 000000000..f0dff0878
--- /dev/null
+++ b/kexi/widget/utils/kexirecordnavigator.cpp
@@ -0,0 +1,511 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qtoolbutton.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qvalidator.h>
+#include <qtooltip.h>
+#include <qscrollview.h>
+
+#include <klocale.h>
+#include <kiconloader.h>
+#include <klineedit.h>
+#include <kguiitem.h>
+#include <kstaticdeleter.h>
+
+#include "kexirecordnavigator.h"
+#include "kexirecordmarker.h"
+
+//! @internal
+class KexiRecordNavigatorPrivate
+{
+ public:
+ KexiRecordNavigatorPrivate()
+ : handler(0)
+ , editingIndicatorLabel(0)
+ , editingIndicatorEnabled(false)
+ , editingIndicatorVisible(false)
+ {
+ }
+ KexiRecordNavigatorHandler *handler;
+ QHBoxLayout *lyr;
+
+ QLabel *editingIndicatorLabel;
+ bool editingIndicatorEnabled : 1;
+ bool editingIndicatorVisible : 1;
+};
+
+//--------------------------------------------------
+
+KexiRecordNavigatorHandler::KexiRecordNavigatorHandler()
+{
+}
+
+KexiRecordNavigatorHandler::~KexiRecordNavigatorHandler()
+{
+}
+
+//--------------------------------------------------
+
+KexiRecordNavigator::KexiRecordNavigator(QWidget *parent, int leftMargin, const char *name)
+ : QFrame(parent, name)
+ , m_view(0)
+ , m_isInsertingEnabled(true)
+ , d( new KexiRecordNavigatorPrivate() )
+{
+ if (parent->inherits("QScrollView"))
+ setParentView( dynamic_cast<QScrollView*>(parent) );
+ setFrameStyle(QFrame::NoFrame);
+ d->lyr = new QHBoxLayout(this,0,0,"nav_lyr");
+
+ m_textLabel = new QLabel(this);
+ d->lyr->addWidget( m_textLabel );
+ setLabelText(i18n("Row:"));
+
+ int bw = 6+SmallIcon("navigator_first").width(); //QMIN( horizontalScrollBar()->height(), 20);
+ QFont f = font();
+ f.setPixelSize((bw > 12) ? 12 : bw);
+ QFontMetrics fm(f);
+ m_nav1DigitWidth = fm.width("8");
+
+ d->lyr->addWidget( m_navBtnFirst = new QToolButton(this) );
+ m_navBtnFirst->setFixedWidth(bw);
+ m_navBtnFirst->setFocusPolicy(NoFocus);
+ m_navBtnFirst->setIconSet( SmallIconSet("navigator_first") );
+ QToolTip::add(m_navBtnFirst, i18n("First row"));
+
+ d->lyr->addWidget( m_navBtnPrev = new QToolButton(this) );
+ m_navBtnPrev->setFixedWidth(bw);
+ m_navBtnPrev->setFocusPolicy(NoFocus);
+ m_navBtnPrev->setIconSet( SmallIconSet("navigator_prev") );
+ m_navBtnPrev->setAutoRepeat(true);
+ QToolTip::add(m_navBtnPrev, i18n("Previous row"));
+
+ d->lyr->addSpacing( 6 );
+
+ d->lyr->addWidget( m_navRecordNumber = new KLineEdit(this) );
+ m_navRecordNumber->setAlignment(AlignRight | AlignVCenter);
+ m_navRecordNumber->setFocusPolicy(ClickFocus);
+ m_navRecordNumber->installEventFilter(this);
+// m_navRowNumber->setFixedWidth(fw);
+ m_navRecordNumberValidator = new QIntValidator(1, INT_MAX, this);
+ m_navRecordNumber->setValidator(m_navRecordNumberValidator);
+ m_navRecordNumber->installEventFilter(this);
+ QToolTip::add(m_navRecordNumber, i18n("Current row number"));
+
+ KLineEdit *lbl_of = new KLineEdit(i18n("of"), this);
+ lbl_of->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Preferred);
+ lbl_of->setMaximumWidth(fm.width(lbl_of->text())+8);
+ lbl_of->setReadOnly(true);
+ lbl_of->setLineWidth(0);
+ lbl_of->setFocusPolicy(NoFocus);
+ lbl_of->setAlignment(AlignCenter);
+ d->lyr->addWidget( lbl_of );
+
+ d->lyr->addWidget( m_navRecordCount = new KLineEdit(this) );
+ m_navRecordCount->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Preferred);
+ m_navRecordCount->setReadOnly(true);
+ m_navRecordCount->setLineWidth(0);
+ m_navRecordCount->setFocusPolicy(NoFocus);
+ m_navRecordCount->setAlignment(AlignLeft | AlignVCenter);
+ QToolTip::add(m_navRecordCount, i18n("Number of rows"));
+
+ lbl_of->setFont(f);
+ m_navRecordNumber->setFont(f);
+ m_navRecordCount->setFont(f);
+ setFont(f);
+
+ d->lyr->addWidget( m_navBtnNext = new QToolButton(this) );
+ m_navBtnNext->setFixedWidth(bw);
+ m_navBtnNext->setFocusPolicy(NoFocus);
+ m_navBtnNext->setIconSet( SmallIconSet("navigator_next") );
+ m_navBtnNext->setAutoRepeat(true);
+ QToolTip::add(m_navBtnNext, i18n("Next row"));
+
+ d->lyr->addWidget( m_navBtnLast = new QToolButton(this) );
+ m_navBtnLast->setFixedWidth(bw);
+ m_navBtnLast->setFocusPolicy(NoFocus);
+ m_navBtnLast->setIconSet( SmallIconSet("navigator_last") );
+ QToolTip::add(m_navBtnLast, i18n("Last row"));
+
+ d->lyr->addSpacing( 6 );
+ d->lyr->addWidget( m_navBtnNew = new QToolButton(this) );
+ m_navBtnNew->setFixedWidth(bw);
+ m_navBtnNew->setFocusPolicy(NoFocus);
+ m_navBtnNew->setIconSet( SmallIconSet("navigator_new") );
+ QToolTip::add(m_navBtnNew, i18n("New row"));
+ m_navBtnNext->setEnabled(isInsertingEnabled());
+
+ d->lyr->addSpacing( 6 );
+ d->lyr->addStretch(10);
+
+ connect(m_navBtnPrev,SIGNAL(clicked()),this,SLOT(slotPrevButtonClicked()));
+ connect(m_navBtnNext,SIGNAL(clicked()),this,SLOT(slotNextButtonClicked()));
+ connect(m_navBtnLast,SIGNAL(clicked()),this,SLOT(slotLastButtonClicked()));
+ connect(m_navBtnFirst,SIGNAL(clicked()),this,SLOT(slotFirstButtonClicked()));
+ connect(m_navBtnNew,SIGNAL(clicked()),this,SLOT(slotNewButtonClicked()));
+
+ setRecordCount(0);
+ setCurrentRecordNumber(0);
+
+ updateGeometry(leftMargin);
+}
+
+KexiRecordNavigator::~KexiRecordNavigator()
+{
+ delete d;
+}
+
+void KexiRecordNavigator::setInsertingEnabled(bool set)
+{
+ if (m_isInsertingEnabled==set)
+ return;
+ m_isInsertingEnabled = set;
+ if (isEnabled())
+ m_navBtnNew->setEnabled( m_isInsertingEnabled );
+}
+
+void KexiRecordNavigator::setEnabled( bool set )
+{
+ QFrame::setEnabled(set);
+ if (set && !m_isInsertingEnabled)
+ m_navBtnNew->setEnabled( false );
+}
+
+bool KexiRecordNavigator::eventFilter( QObject *o, QEvent *e )
+{
+ if (o==m_navRecordNumber) {
+ bool recordEntered = false;
+ bool ret;
+ if (e->type()==QEvent::KeyPress) {
+ QKeyEvent *ke = static_cast<QKeyEvent*>(e);
+ switch (ke->key()) {
+ case Qt::Key_Escape: {
+ ke->accept();
+ m_navRecordNumber->undo();
+ if (m_view)
+ m_view->setFocus();
+ return true;
+ }
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ case Qt::Key_Tab:
+ case Qt::Key_BackTab:
+ {
+ recordEntered=true;
+ ke->accept(); //to avoid pressing Enter later
+ ret = true;
+ }
+ default:;
+ }
+ }
+ else if (e->type()==QEvent::FocusOut) {
+ if (static_cast<QFocusEvent*>(e)->reason()!=QFocusEvent::Tab
+ && static_cast<QFocusEvent*>(e)->reason()!=QFocusEvent::Backtab
+ && static_cast<QFocusEvent*>(e)->reason()!=QFocusEvent::Other)
+ recordEntered=true;
+ ret = false;
+ }
+
+ if (recordEntered) {
+ bool ok=true;
+ uint r = m_navRecordNumber->text().toUInt(&ok);
+ if (!ok || r<1)
+ r = (recordCount()>0)?1:0;
+ if (m_view && (hasFocus() || e->type()==QEvent::KeyPress))
+ m_view->setFocus();
+ setCurrentRecordNumber(r);
+ emit recordNumberEntered(r);
+ if (d->handler)
+ d->handler->moveToRecordRequested(r-1);
+ return ret;
+ }
+ }
+/*
+ bool ok=true;
+ int r = text.toInt(&ok);
+ if (!ok || r<1)
+ r = 1;
+ emit recordNumberEntered(r);*/
+ return false;
+}
+
+void KexiRecordNavigator::setCurrentRecordNumber(uint r)
+{
+ uint recCnt = recordCount();
+ if (r>(recCnt+(m_isInsertingEnabled?1:0)))
+ r = recCnt+(m_isInsertingEnabled?1:0);
+ QString n;
+ if (r>0)
+ n = QString::number(r);
+ else
+ n = " ";
+// if (d->navRecordNumber->text().length() != n.length()) {//resize
+// d->navRecordNumber->setFixedWidth(
+// d->nav1DigitWidth*QMAX( QMAX(n.length(),2)+1,d->navRecordCount->text().length()+1)+6
+// );
+// }
+
+ m_navRecordNumber->setText(n);
+ m_navRecordCount->deselect();
+ updateButtons(recCnt);
+}
+
+void KexiRecordNavigator::updateButtons(uint recCnt)
+{
+ const uint r = currentRecordNumber();
+ if (isEnabled()) {
+ m_navBtnPrev->setEnabled(r > 1);
+ m_navBtnFirst->setEnabled(r > 1);
+ m_navBtnNext->setEnabled(r > 0
+ && r < (recCnt +(m_isInsertingEnabled?(1+d->editingIndicatorVisible/*if we're editing, next btn is avail.*/):0) ) );
+ m_navBtnLast->setEnabled(r!=(recCnt+(m_isInsertingEnabled?1:0)) && (m_isInsertingEnabled || recCnt>0));
+ }
+}
+
+void KexiRecordNavigator::setRecordCount(uint count)
+{
+ const QString & n = QString::number(count);
+ if (m_isInsertingEnabled && currentRecordNumber()==0) {
+ setCurrentRecordNumber(1);
+ }
+ if (m_navRecordCount->text().length() != n.length()) {//resize
+ m_navRecordCount->setFixedWidth(m_nav1DigitWidth*n.length()+6);
+
+ if (m_view && m_view->horizontalScrollBar()->isVisible()) {
+ //+width of the delta
+ resize(width()+(n.length()-m_navRecordCount->text().length())*m_nav1DigitWidth, height());
+// horizontalScrollBar()->move(d->navPanel->x()+d->navPanel->width()+20,horizontalScrollBar()->y());
+ }
+ }
+ //update row number widget's width
+ const int w = m_nav1DigitWidth*QMAX( QMAX(n.length(),2)+1,m_navRecordNumber->text().length()+1)+6;
+ if (m_navRecordNumber->width()!=w) //resize
+ m_navRecordNumber->setFixedWidth(w);
+
+ m_navRecordCount->setText(n);
+ m_navRecordCount->deselect();
+ if (m_view)
+ m_view->updateScrollBars();
+ updateButtons(recordCount());
+}
+
+uint KexiRecordNavigator::currentRecordNumber() const
+{
+ bool ok=true;
+ int r = m_navRecordNumber->text().toInt(&ok);
+ if (!ok || r<1)
+ r = 0;
+ return r;
+}
+
+uint KexiRecordNavigator::recordCount() const
+{
+ bool ok=true;
+ int r = m_navRecordCount->text().toInt(&ok);
+ if (!ok || r<1)
+ r = 0;
+ return r;
+}
+
+void KexiRecordNavigator::setParentView(QScrollView *view)
+{
+ m_view = view;
+}
+
+void KexiRecordNavigator::updateGeometry(int leftMargin)
+{
+ QFrame::updateGeometry();
+ if (m_view) {
+ int navWidth;
+ if (m_view->horizontalScrollBar()->isVisible()) {
+ navWidth = sizeHint().width();
+ }
+ else {
+ navWidth = leftMargin + m_view->clipper()->width();
+ }
+
+ setGeometry(
+ m_view->frameWidth(),
+ m_view->height() - m_view->horizontalScrollBar()->sizeHint().height()-m_view->frameWidth(),
+ navWidth,
+ m_view->horizontalScrollBar()->sizeHint().height()
+ );
+
+ m_view->updateScrollBars();
+ }
+}
+
+void KexiRecordNavigator::setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h )
+{
+ hbar.setGeometry( x + width(), y, w - width(), h );
+}
+
+void KexiRecordNavigator::setLabelText(const QString& text)
+{
+ m_textLabel->setText(text.isEmpty() ? QString::null : (QString::fromLatin1(" ")+text+" "));
+}
+
+void KexiRecordNavigator::setInsertingButtonVisible(bool set)
+{
+ if (set)
+ m_navBtnNew->show();
+ else
+ m_navBtnNew->hide();
+}
+
+void KexiRecordNavigator::slotPrevButtonClicked()
+{
+ emit prevButtonClicked();
+ if (d->handler)
+ d->handler->moveToPreviousRecordRequested();
+}
+
+void KexiRecordNavigator::slotNextButtonClicked()
+{
+ emit nextButtonClicked();
+ if (d->handler)
+ d->handler->moveToNextRecordRequested();
+}
+
+void KexiRecordNavigator::slotLastButtonClicked()
+{
+ emit lastButtonClicked();
+ if (d->handler)
+ d->handler->moveToLastRecordRequested();
+}
+
+void KexiRecordNavigator::slotFirstButtonClicked()
+{
+ emit firstButtonClicked();
+ if (d->handler)
+ d->handler->moveToFirstRecordRequested();
+}
+
+void KexiRecordNavigator::slotNewButtonClicked()
+{
+ emit newButtonClicked();
+ if (d->handler)
+ d->handler->addNewRecordRequested();
+}
+
+
+void KexiRecordNavigator::setRecordHandler(KexiRecordNavigatorHandler *handler)
+{
+ d->handler = handler;
+}
+
+bool KexiRecordNavigator::editingIndicatorVisible() const
+{
+ return d->editingIndicatorVisible;
+}
+
+bool KexiRecordNavigator::editingIndicatorEnabled() const
+{
+ return d->editingIndicatorEnabled;
+}
+
+void KexiRecordNavigator::setEditingIndicatorEnabled(bool set)
+{
+ d->editingIndicatorEnabled = set;
+ if (d->editingIndicatorEnabled) {
+ if (!d->editingIndicatorLabel) {
+ d->editingIndicatorLabel = new QLabel(this);
+ d->editingIndicatorLabel->setAlignment(Qt::AlignCenter);
+ QPixmap pix;
+ pix.convertFromImage( *KexiRecordMarker::penImage() );
+ d->editingIndicatorLabel->setFixedWidth( pix.width() + 2*2 );
+ d->lyr->insertWidget( 0, d->editingIndicatorLabel );
+ }
+ d->editingIndicatorLabel->show();
+ }
+ else {
+ if (d->editingIndicatorLabel) {
+ d->editingIndicatorLabel->hide();
+ }
+ }
+}
+
+void KexiRecordNavigator::showEditingIndicator(bool show)
+{
+ d->editingIndicatorVisible = show;
+ updateButtons(recordCount()); //this will refresh 'next btn'
+ if (!d->editingIndicatorEnabled)
+ return;
+ if (d->editingIndicatorVisible) {
+ QPixmap pix;
+ pix.convertFromImage( *KexiRecordMarker::penImage() );
+ d->editingIndicatorLabel->setPixmap( pix );
+ QToolTip::add( d->editingIndicatorLabel, i18n("Editing indicator") );
+ }
+ else {
+ d->editingIndicatorLabel->setPixmap( QPixmap() );
+ QToolTip::remove( d->editingIndicatorLabel );
+ }
+}
+
+//------------------------------------------------
+
+//! @internal
+class KexiRecordNavigatorActionsInternal {
+ public:
+ KexiRecordNavigatorActionsInternal()
+ : moveToFirstRecord(i18n("First row"), "navigator_first", i18n("Go to first row"))
+ , moveToPreviousRecord(i18n("Previous row"), "navigator_prev", i18n("Go to previous row"))
+ , moveToNextRecord(i18n("Next row"), "navigator_next", i18n("Go to next row"))
+ , moveToLastRecord(i18n("Last row"), "navigator_last", i18n("Go to last row"))
+ , moveToNewRecord(i18n("New row"), "navigator_new", i18n("Go to new row"))
+ {
+ }
+ static void init();
+ KGuiItem moveToFirstRecord;
+ KGuiItem moveToPreviousRecord;
+ KGuiItem moveToNextRecord;
+ KGuiItem moveToLastRecord;
+ KGuiItem moveToNewRecord;
+};
+
+static KStaticDeleter<KexiRecordNavigatorActionsInternal> KexiRecordNavigatorActions_deleter;
+KexiRecordNavigatorActionsInternal* KexiRecordNavigatorActions_internal = 0;
+
+void KexiRecordNavigatorActionsInternal::init()
+{
+ if (!KexiRecordNavigatorActions_internal)
+ KexiRecordNavigatorActions_deleter.setObject(KexiRecordNavigatorActions_internal,
+ new KexiRecordNavigatorActionsInternal());
+}
+
+const KGuiItem& KexiRecordNavigator::Actions::moveToFirstRecord()
+{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToFirstRecord; }
+
+const KGuiItem& KexiRecordNavigator::Actions::moveToPreviousRecord()
+{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToPreviousRecord; }
+
+const KGuiItem& KexiRecordNavigator::Actions::moveToNextRecord()
+{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToNextRecord; }
+
+const KGuiItem& KexiRecordNavigator::Actions::moveToLastRecord()
+{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToLastRecord; }
+
+const KGuiItem& KexiRecordNavigator::Actions::moveToNewRecord()
+{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToNewRecord; }
+
+#include "kexirecordnavigator.moc"
diff --git a/kexi/widget/utils/kexirecordnavigator.h b/kexi/widget/utils/kexirecordnavigator.h
new file mode 100644
index 000000000..674746e22
--- /dev/null
+++ b/kexi/widget/utils/kexirecordnavigator.h
@@ -0,0 +1,190 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
+ Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXIRECORDNAVIGATOR_H
+#define KEXIRECORDNAVIGATOR_H
+
+#include <qframe.h>
+#include <kexi_export.h>
+
+class QToolButton;
+class QIntValidator;
+class QScrollView;
+class QScrollBar;
+class QLabel;
+class KLineEdit;
+class KGuiItem;
+class KexiRecordNavigatorPrivate;
+
+//! \brief KexiRecordNavigatorHandler interface handles requests generated by KexiRecordNavigator
+class KEXIGUIUTILS_EXPORT KexiRecordNavigatorHandler
+{
+ public:
+ KexiRecordNavigatorHandler();
+ virtual ~KexiRecordNavigatorHandler();
+
+ //! Moving to record \a r is requested. Records are counted from 0.
+ virtual void moveToRecordRequested(uint r) = 0;
+ virtual void moveToLastRecordRequested() = 0;
+ virtual void moveToPreviousRecordRequested() = 0;
+ virtual void moveToNextRecordRequested() = 0;
+ virtual void moveToFirstRecordRequested() = 0;
+ virtual void addNewRecordRequested() = 0;
+};
+
+
+//! \brief KexiRecordNavigator class provides a record navigator.
+/*! Record navigator is usually used for data tables (e.g. KexiTableView)
+ or data-aware forms.
+
+ You can plug KexiRecordNavigator object to your data-aware object in two ways:
+ 1) By connectiong to slots prevButtonClicked(), etc.
+ 2) A bit cleaner way: by inheriting from KexiRecordNavigatorHandler interface
+ in your data-aware class and implementing all it's prototype methods like
+ moveToRecordRequested(), and then caling setRecordHandler() on navigator's object.
+ Note that using this way, you can allow to exist more than one navigator widget
+ connected with your data-aware object (don't matter if this is sane).
+ */
+class KEXIGUIUTILS_EXPORT KexiRecordNavigator : public QFrame
+{
+ Q_OBJECT
+
+ public:
+ KexiRecordNavigator(QWidget *parent, int leftMargin = 0, const char *name=0);
+ virtual ~KexiRecordNavigator();
+
+ void setParentView(QScrollView *view);
+
+ /*! Sets record navigator handler. This allows to react
+ on actions performed within navigator and vice versa. */
+ void setRecordHandler(KexiRecordNavigatorHandler *handler);
+
+ /*! \return true if data inserting is enabled (the default). */
+ inline bool isInsertingEnabled() const { return m_isInsertingEnabled; }
+
+ /*! \return current record number displayed for this navigator.
+ can return 0, if the 'text box's content is cleared. */
+ uint currentRecordNumber() const;
+
+ /*! \return record count displayed for this navigator. */
+ uint recordCount() const;
+
+ /*! Sets horizontal bar's \a hbar (at the bottom) geometry so this record navigator
+ is properly positioned together with horizontal scroll bar. This method is used
+ in QScrollView::setHBarGeometry() implementations:
+ see KexiTableView::setHBarGeometry() and KexiFormScrollView::setHBarGeometry()
+ for usage examples. */
+ void setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h );
+
+ /*! @internal used for keyboard handling. */
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ /*! \return true if "editing" indicator is visible for this navigator.
+ @see showEditingIndicator() */
+ bool editingIndicatorVisible() const;
+
+ /*! \return true if "editing" indicator is enabled for this navigator.
+ Only meaningful if setEditingIndicatorEnabled(true) is called. */
+ bool editingIndicatorEnabled() const;
+
+ //! @short A set of GUI items usable for displaying related actions.
+ /*! For instance, the items are used by Kexi main window to create shared actions. */
+ class KEXIGUIUTILS_EXPORT Actions {
+ public:
+ static const KGuiItem& moveToFirstRecord();
+ static const KGuiItem& moveToPreviousRecord();
+ static const KGuiItem& moveToNextRecord();
+ static const KGuiItem& moveToLastRecord();
+ static const KGuiItem& moveToNewRecord();
+ };
+
+ public slots:
+ /*! Sets insertingEnabled flag. If true, "+" button will be enabled. */
+ void setInsertingEnabled(bool set);
+
+ /*! Sets visibility of "inserting" button. */
+ void setInsertingButtonVisible(bool set);
+
+ /*! Sets visibility of the place where "editing" indicator will be displayed.
+ "editing" indicator will display KexiRecordMarker::penImage() image when
+ setEditingIndicatorVisible() is called.
+ This method is currently used e.g. within standard kexi forms
+ (see KexiFormScrollView class). */
+ void setEditingIndicatorEnabled(bool set);
+
+ /*! Shows or hides "editing" indicator. */
+ void showEditingIndicator(bool show);
+
+ virtual void setEnabled(bool set);
+
+ /*! Sets current record number for this navigator,
+ i.e. a value that will be displayed in the 'record number' text box.
+ This can also affect button's enabling and disabling.
+ If @p r is 0, 'record number' text box's content is cleared. */
+ void setCurrentRecordNumber(uint r);
+
+ /*! Sets record count for this navigator.
+ This can also affect button's enabling and disabling.
+ By default count is 0. */
+ void setRecordCount(uint count);
+
+ void updateGeometry(int leftMargin);
+
+ /*! Sets label text at the left of the for record navigator's button.
+ By default this label contains translated "Row:" text. */
+ void setLabelText(const QString& text);
+
+ signals:
+ void prevButtonClicked();
+ void nextButtonClicked();
+ void lastButtonClicked();
+ void firstButtonClicked();
+ void newButtonClicked();
+ void recordNumberEntered( uint r );
+
+ protected slots:
+ void slotPrevButtonClicked();
+ void slotNextButtonClicked();
+ void slotLastButtonClicked();
+ void slotFirstButtonClicked();
+ void slotNewButtonClicked();
+ //void slotRecordNumberReturnPressed(const QString& text);
+
+ protected:
+ void updateButtons(uint recCnt);
+
+ QLabel *m_textLabel;
+ QToolButton *m_navBtnFirst;
+ QToolButton *m_navBtnPrev;
+ QToolButton *m_navBtnNext;
+ QToolButton *m_navBtnLast;
+ QToolButton *m_navBtnNew;
+ KLineEdit *m_navRecordNumber;
+ QIntValidator *m_navRecordNumberValidator;
+ KLineEdit *m_navRecordCount; //!< readonly counter
+ uint m_nav1DigitWidth;
+// uint m_recordCount;
+ QScrollView *m_view;
+ bool m_isInsertingEnabled : 1;
+
+ KexiRecordNavigatorPrivate *d;
+};
+
+#endif
diff --git a/kexi/widget/utils/kexisharedactionclient.cpp b/kexi/widget/utils/kexisharedactionclient.cpp
new file mode 100644
index 000000000..4dbd9299d
--- /dev/null
+++ b/kexi/widget/utils/kexisharedactionclient.cpp
@@ -0,0 +1,39 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexisharedactionclient.h"
+
+#include <kaction.h>
+
+KexiSharedActionClient::KexiSharedActionClient()
+ : m_sharedActions(101, false)
+{
+}
+
+KexiSharedActionClient::~KexiSharedActionClient()
+{
+}
+
+void KexiSharedActionClient::plugSharedAction(KAction* a)
+{
+ if (!a)
+ return;
+ m_sharedActions.insert(a->name(), a);
+}
+
diff --git a/kexi/widget/utils/kexisharedactionclient.h b/kexi/widget/utils/kexisharedactionclient.h
new file mode 100644
index 000000000..80181bc7e
--- /dev/null
+++ b/kexi/widget/utils/kexisharedactionclient.h
@@ -0,0 +1,49 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXISHAREDACTIONCLIENT_H
+#define KEXISHAREDACTIONCLIENT_H
+
+#include <qasciidict.h>
+
+class KAction;
+#include <kexi_export.h>
+
+//! The KexiSharedActionClient is an interface using application-wide (shared) actions.
+/** See KexiTableView and KexiFormScrollView for example usage.
+*/
+class KEXIGUIUTILS_EXPORT KexiSharedActionClient
+{
+ public:
+ KexiSharedActionClient();
+ virtual ~KexiSharedActionClient();
+
+ /*! Plugs action \a a for a widget. The action will be later looked up (by name)
+ on key press event, to get proper shortcut. If found, we know that the action is already
+ performed at main window's level, so we should give up. Otherwise - default shortcut
+ will be used (example: Shift+Enter key for "data_save_row" action). \sa KexiTableView::shortCutPressed()
+ */
+ void plugSharedAction(KAction* a);
+
+ protected:
+ //! Actions pluged for this widget using plugSharedAction(), available by name.
+ QAsciiDict<KAction> m_sharedActions;
+};
+
+#endif
diff --git a/kexi/widget/utils/kexitooltip.cpp b/kexi/widget/utils/kexitooltip.cpp
new file mode 100644
index 000000000..69a8b583c
--- /dev/null
+++ b/kexi/widget/utils/kexitooltip.cpp
@@ -0,0 +1,76 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "kexitooltip.h"
+
+#include <qpixmap.h>
+#include <qbitmap.h>
+#include <qpainter.h>
+#include <qimage.h>
+#include <qtooltip.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qtimer.h>
+
+
+KexiToolTip::KexiToolTip(const QVariant& value, QWidget* parent)
+ : QWidget(parent, "KexiToolTip", Qt::WStyle_Customize | Qt::WType_Popup | Qt::WStyle_NoBorder
+ | Qt::WX11BypassWM | Qt::WDestructiveClose)
+ , m_value(value)
+{
+ setPalette( QToolTip::palette() );
+ setFocusPolicy(QWidget::NoFocus);
+}
+
+KexiToolTip::~KexiToolTip()
+{
+}
+
+QSize KexiToolTip::sizeHint() const
+{
+ QSize sz(fontMetrics().boundingRect(m_value.toString()).size());
+ return sz;
+}
+
+void KexiToolTip::show()
+{
+ updateGeometry();
+ QWidget::show();
+}
+
+void KexiToolTip::paintEvent( QPaintEvent *pev )
+{
+ QWidget::paintEvent(pev);
+ QPainter p(this);
+ drawFrame(p);
+ drawContents(p);
+}
+
+void KexiToolTip::drawFrame(QPainter& p)
+{
+ p.setPen( QPen(palette().active().foreground(), 1) );
+ p.drawRect(rect());
+}
+
+void KexiToolTip::drawContents(QPainter& p)
+{
+ p.drawText(rect(), Qt::AlignCenter, m_value.toString());
+}
+
+#include "kexitooltip.moc"
diff --git a/kexi/widget/utils/kexitooltip.h b/kexi/widget/utils/kexitooltip.h
new file mode 100644
index 000000000..cbb0931f4
--- /dev/null
+++ b/kexi/widget/utils/kexitooltip.h
@@ -0,0 +1,47 @@
+/* This file is part of the KDE project
+ Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
+
+ This program 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 program is distributed in the hope that 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 program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXITOOLTIP_H
+#define KEXITOOLTIP_H
+
+#include <qwidget.h>
+#include <qvariant.h>
+
+//! \brief A tooltip that can display rich content
+class KEXIGUIUTILS_EXPORT KexiToolTip : public QWidget
+{
+ Q_OBJECT
+ public:
+ KexiToolTip(const QVariant& value, QWidget* parent);
+ virtual ~KexiToolTip();
+
+ virtual QSize sizeHint() const;
+
+ public slots:
+ virtual void show();
+
+ protected:
+ virtual void paintEvent( QPaintEvent *pev );
+ virtual void drawFrame(QPainter& p);
+ virtual void drawContents(QPainter& p);
+
+ QVariant m_value;
+};
+
+#endif
diff --git a/kexi/widget/utils/klistviewitemtemplate.h b/kexi/widget/utils/klistviewitemtemplate.h
new file mode 100644
index 000000000..1c89f96c3
--- /dev/null
+++ b/kexi/widget/utils/klistviewitemtemplate.h
@@ -0,0 +1,50 @@
+/* This file is part of the KDE project
+ Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>
+
+ 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 Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KLISTVIEWITEMTEMPLATE_H
+#define KLISTVIEWITEMTEMPLATE_H
+
+#include <klistview.h>
+
+//! QListViewItem class with ability for storing additional data member
+template<class type>
+class KListViewItemTemplate : public KListViewItem
+{
+ public:
+ KListViewItemTemplate(type _data, QListView *parent)
+ : KListViewItem(parent), data(_data) {}
+ KListViewItemTemplate(type _data, QListViewItem *parent)
+ : KListViewItem(parent), data(_data) {}
+ KListViewItemTemplate(type _data, QListView *parent, QListViewItem *after)
+ : KListViewItem(parent, after), data(_data) {}
+ KListViewItemTemplate(type _data, QListViewItem *parent, QListViewItem *after)
+ : KListViewItem(parent, after), data(_data) {}
+ KListViewItemTemplate(type _data, QListView *parent, QString label1, QString label2=QString::null, QString label3=QString::null, QString label4=QString::null, QString label5=QString::null, QString label6=QString::null, QString label7=QString::null, QString label8=QString::null)
+ : KListViewItem(parent, label1, label2, label3, label4, label5, label6, label7, label8), data(_data) {}
+ KListViewItemTemplate(type _data, QListViewItem *parent, QString label1, QString label2=QString::null, QString label3=QString::null, QString label4=QString::null, QString label5=QString::null, QString label6=QString::null, QString label7=QString::null, QString label8=QString::null)
+ : KListViewItem(parent, label1, label2, label3, label4, label5, label6, label7, label8), data(_data) {}
+ KListViewItemTemplate(type _data, QListView *parent, QListViewItem *after, QString label1, QString label2=QString::null, QString label3=QString::null, QString label4=QString::null, QString label5=QString::null, QString label6=QString::null, QString label7=QString::null, QString label8=QString::null)
+ : KListViewItem(parent, after, label1, label2, label3, label4, label5, label6, label7, label8), data(_data) {}
+ KListViewItemTemplate(type _data, QListViewItem *parent, QListViewItem *after, QString label1, QString label2=QString::null, QString label3=QString::null, QString label4=QString::null, QString label5=QString::null, QString label6=QString::null, QString label7=QString::null, QString label8=QString::null)
+ : KListViewItem(parent, after, label1, label2, label3, label4, label5, label6, label7, label8), data(_data) {}
+
+ type data;
+};
+
+#endif